# Show HN: Zipzi – Open-source Live2D Editor alternative with AI-assisted editing

> Source: <https://github.com/getzipzi/zipzi-2d>
> Published: 2026-11-13 02:54:37+00:00

**An open-source 2D modeling and animation tool for creating alongside AI.**

You shape the artwork. Your AI agent helps with the modeling work. Zipzi connects a visual desktop editor with a Python API, so you and your agent can inspect and edit the same model, share undo history, and build on each other’s changes.

Zipzi 2D is an open-source alternative to Live2D Cubism Editor, built with Rust
and GPUI. Its name comes from Cantonese 摺紙 (*zip3 zi2*), meaning “origami”.

[**Download Alpha**](https://github.com/getzipzi/zipzi-2d/releases) · [Python SDK](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/README.md) · [Report an issue](https://github.com/getzipzi/zipzi-2d/issues) · [Contribute](#contributing)

**Early development:** There is no stable release yet. APIs and file formats
may change. The editor supports macOS and Windows.

- **Open source and commercial friendly** — Create, refine, and use your models
commercially with an open-source editor and an MIT-licensed SDK.
- **Native and high performance** — Built with Rust and GPUI for low memory
usage and responsive editing, even with complex models and dense meshes.
- **Agent-ready** — Create from scratch, collaborate with AI, or import an
existing model for further refinement.

## zipzi-2d-demo.mp4

Describe what you want to your AI coding agent. It uses the Python SDK to inspect models, view rendered images and make edits you can review and undo in the editor. You and your agent work on the same document, with shared undo history.

Bring your own Python-capable agent and point it to the
[SDK guide](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/client/python/zipzi/docs/guide.md).

**You describe the task; your AI agent writes and runs the code.** The examples
below were generated by an AI coding agent and tested against a running Editor.
They show how the agent works through the Python SDK—you do not need to write
these scripts yourself. Your role is to explain what you want and review the
results in the editor.

Ask your agent: **“Look at every mesh and give it a meaningful name.”**

Install the [Python client](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/README.md#installation)
(CPython 3.14), start a compatible Editor with GPU observation support, and open
one model. An image-capable coding agent can run the following two steps.

**1. The agent observes every mesh.** Its generated script exports one image per mesh, with its
wireframe highlighted over the textured model, plus a JSON naming worksheet.
`observe` supplies visual evidence; the agent supplies the semantic interpretation.

``` python
import json
from pathlib import Path
from uuid import uuid4

import zipzi
from zipzi import inspection, observation, spatial

output = Path("mesh-review-" + uuid4().hex[:8]).resolve()
output.mkdir()

with zipzi.connect() as runtime:
    sessions = runtime.sessions.list()
    if len(sessions) != 1:
        raise RuntimeError("Open exactly one model for this example.")
    session = runtime.sessions.attach(sessions[0].id)

    # Follow every page: a single list() call may not contain all meshes.
    meshes, cursor, source = [], None, None
    while True:
        page = session.meshes.list(cursor=cursor)
        if source is None:
            source = page.source
        if page.source != source:
            raise RuntimeError("Model changed; restart the inspection.")
        meshes.extend(page.items)
        cursor = page.next_cursor
        if cursor is None:
            break

    rows = []
    for index, mesh in enumerate(meshes):
        # Use this mesh's bounds to frame it, including hidden geometry.
        # {} evaluates default parameter values, not the editor's current pose.
        query = spatial.Bounds(
            targets=(spatial.MeshTarget(value=mesh.id),), include_hidden=True,
        )
        bounds = session.modeling.spatial(
            ({},), (query,), expected=source,
        ).wait().value.frames[0].results[0].value.model_bounds
        row = dict(id=mesh.id, old_name=mesh.name, new_name=mesh.name, image=None)
        if bounds is not None:
            x0, y0, x1, y1 = bounds
            margin = max(x1 - x0, y1 - y0, 1.0) * 0.15
            view = observation.View(
                width=768, height=768,
                roi=(x0 - margin, y0 - margin, x1 + margin, y1 + margin),
                topology=observation.Overlay(
                    targets=(observation.MeshTarget(value=mesh.id),),
                    wireframe=True, include_hidden=True,
                ),
            )
            # Freeze all observations to the inventory's source. If the model
            # changes, the SDK refuses stale reads; rerun instead of mixing them.
            images = session.modeling.observe(({},), view, expected=source).wait()
            report = inspection.save(images, output / f"mesh-{index:04d}")
            row["image"] = str(report.images[0])
        rows.append(row)

    # SessionId is a typed handle: store its fields for the second script.
    worksheet = output / "names.json"
    worksheet.write_text(json.dumps(
        dict(session_id=dict(runtime=session.id.runtime, serial=session.id.serial),
             meshes=rows), indent=2, ensure_ascii=False,
    ), encoding="utf-8")
    print(worksheet)
```

Have the agent **open each exported PNG**, identify the highlighted mesh in
context, and edit only `new_name` in `names.json`. Keep IDs and `old_name` intact.
The overlay selects diagnostic geometry, not isolated artwork: surrounding
textures remain visible, and hidden or occluded artwork may still be ambiguous.
Keep the original name for uncertain or image-less entries, or inspect additional
poses before naming them.

**2. The agent applies the semantic names together.** After you review the
proposed names, the agent writes and runs the following script with the path to
`names.json`. You do not need to copy the code or run a terminal command. Keep
the same model open and unchanged between inspection and application.

``` python
import json
import sys
from pathlib import Path

import zipzi

review = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
rows = review["meshes"]
if len({row["id"] for row in rows}) != len(rows):
    raise ValueError("Duplicate mesh IDs in the worksheet.")
if any(not isinstance(row["new_name"], str) or not row["new_name"].strip()
       for row in rows):
    raise ValueError("Every mesh needs a nonempty name.")

with zipzi.connect() as runtime:
    # Rebuild the typed handle; a restarted Editor requires a new inspection.
    session = runtime.sessions.attach(zipzi.SessionId(**review["session_id"]))
    # All renames commit together as one undo step. A missing mesh or changed
    # original name aborts the edit instead of leaving a partly renamed model.
    with session.edit("AI: semantic mesh names") as edit:
        for row in rows:
            current = edit.meshes.get(row["id"]).value
            if current.name != row["old_name"]:
                raise RuntimeError("Mesh changed; inspect the model again.")
            if row["new_name"] != row["old_name"]:
                edit.meshes.rename(
                    row["id"], row["new_name"], expected_name=row["old_name"],
                )
    print("Review the names in the editor. Undo to revert, or save to keep them.")
```

No project save happens automatically. The name check detects renamed or removed
meshes, not every possible geometry or texture change; repeat the inspection if
the model has changed. For more workflows, see the
[capability guide](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/CAPABILITIES.md).

Work with meshes, parameters, deformers and BlendShapes, then bring them together in the animation workspace. Generate mesh geometry or refine it by hand. Mesh Edit lets you work on the posed model or switch to the source image, while resampling stored shapes as the geometry changes.

Python scripts can also run independently with a local Runtime for automation
outside the editor. Explore the [Python SDK](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/README.md)
for modeling, animation authoring, previews and inspection.

- **Use the editor:** Download an Alpha build from[GitHub Releases](https://github.com/getzipzi/zipzi-2d/releases) . Check the
release notes for the available platform builds and requirements.
- **Automate with Python:** Follow the[installation and connection guide](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/README.md) .
The SDK currently requires CPython 3.14.
- **Build your own tools:** Explore the[Rust SDK](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-sdk/README.md) for programmatic authoring.

| Area | What you can work with | 
|---|---|
| Modeling | Meshes, parameters, keyforms, parts, deformers, BlendShapes and Glue | 
| Animation | Motion, expression, physics and pose authoring; animation previews | 
| Project I/O | PSD import and Cubism-related formats, including Model3/MOC3, projects and runtime sidecars; support and loss rules vary by format and direction | 
| Automation | Python sessions, batch edits, shared undo/redo, project operations and model inspection | 
| Platforms | macOS and Windows | 

See the [SDK capability guide](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/CAPABILITIES.md) for details.
GPU inspection requires a compatible host and usable GPU. Format support does
not imply lossless round trips for every model.

Try an Alpha build and [tell us what gets in your way](https://github.com/getzipzi/zipzi-2d/issues).
For bug reports, include the build version, platform and steps to reproduce;
share model assets only when you have permission to do so.

For code contributions, start with the [repository map](https://github.com/getzipzi/zipzi-2d/blob/main/MONOREPO.md),
[editor guide](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-editor/README.md) or
[Python SDK guide](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/README.md). The
[documentation index](https://github.com/getzipzi/zipzi-2d/blob/main/docs/README.md) covers deeper references.
Development commands and focused test selection are below.

Use Rust 1.95 or newer, uv 0.10.7 or newer, and CPython 3.14.
The macOS Editor requires Xcode Command Line Tools and a Metal-capable GPU.
The [Windows Editor](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-editor/README.md) requires
MSVC, the Windows SDK and a usable GPU.
Run these commands from the repository root:

```
uv sync --locked
python3 tools/workspace/cargo_domain.py --check

# Focused SDK development
cargo test --locked -p zipzi-sdk --test integration physics_editing::

# Native Editor (macOS)
cargo run --locked -p zipzi-editor
./tools/test-editor.sh fast timeline::

# Repository tooling tests; no native application build
uv run --locked python tools/test_tools.py
```

For the Python SDK, `./tools/test-sdk.sh python` refreshes its native adapter
and runs Local/Connected behavior and static types. `python-connected` installs
the pure wheel and validates real subprocess host discovery and session binding;
add `--editor-host /absolute/path/to/zipzi-editor` for the current desktop host.
Root `pyproject.toml` and `uv.lock` manage development tools.

## Focused test commands and coverage

Choose the smallest gate covering the change, then broaden when needed. Run
commands from the repository root; inspect available modes with
`./tools/test-sdk.sh --help`, `./tools/test-editor.sh --help` or
`uv run --locked python tools/test_tools.py --help`.

| Changed area | Starting command | 
|---|---|
| One Rust crate/module | `cargo test --locked -p <crate> --lib <filter>` ; add its integration target when relevant | 
| Runtime ownership/jobs/events | `./tools/test-sdk.sh runtime` | 
| Local protocol / installed Python client | `./tools/test-sdk.sh python-connected` | 
| SDK authoring behavior | `./tools/test-sdk.sh fast <filter>` | 
| Editor adaptive mesh generation | `./tools/test-editor.sh fast adaptive_mesh::` ;`./tools/test-editor.sh fast mesh_generation::` | 
| SDK alpha mesh generation | `./tools/test-sdk.sh mesh` | 
| wgpu backend / local GPU parity | `cargo test --locked -p zipzi-render-wgpu --tests` ;[host selection](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-render-wgpu/README.md#application-routes-on-macos) | 
| Editor coordination | `./tools/test-editor.sh fast <filter>` | 
| Editor UI (macOS/Metal) | `./tools/test-editor.sh ui <filter>` | 
| Runtime Python SDK ( `zipzi-python` ), Local/Connected and types | `./tools/test-sdk.sh python` ([scope](https://github.com/getzipzi/zipzi-2d/blob/main/crates/zipzi-python/README.md) ) | 
| Shared authoring contract / isolated Python generation | `cargo test --locked -p zipzi-contract` ;`uv run --locked python tools/test_tools.py -p test_python_codegen.py` | 
| Runtime GPU observation / inspection (usable GPU required) | `./tools/test-sdk.sh python --observation` | 
| Python complete-call recovery, Edit state and Job timeout | `./tools/test-sdk.sh python --recovery` (optional`P3ARecovery.test_name` ) | 
| Python collection pages, Session detach/close and offline catalog | `./tools/test-sdk.sh python --discovery` | 
| Installed new client, host discovery and session binding | `./tools/test-sdk.sh python-connected` (optional`--editor-host /absolute/path/to/zipzi-editor` ) | 
| Repository tools | `uv run --locked python tools/test_tools.py -p test_<tool>.py` | 

Filters and filenames are placeholders; omit them to run the selected gate.
For a crate's integration targets, inspect its README and `tests/`, or list cases
with `cargo test --locked -p <crate> --test <target> -- --list`.
Editor shell gates clear `ZIPZI_DEV_SESSION_DIR`; clear it explicitly when
running Editor Cargo tests directly.

[Full coverage, prerequisites and CI](https://github.com/getzipzi/zipzi-2d/blob/main/tools/README.md#test-coverage) define
regression, stress, native acceptance and packaged-wheel gates. Both domain
scripts default to expensive **full** runs when no mode is supplied.
Plain `cargo test` selects only `zipzi-core`, `zipzi-project` and `zipzi-sdk`.
Tooling tests belong in `tools/tests/`, crate tests stay with their crate, and
shared data belongs in [`fixtures/`](https://github.com/getzipzi/zipzi-2d/blob/main/fixtures/README.md).

All owned crates share the root Cargo workspace and lockfile. Local models belong
in `models/local/`; see the [model inventory](https://github.com/getzipzi/zipzi-2d/blob/main/models/README.md). Source archives,
Python wheels and macOS app bundles have separate
[distribution checks](https://github.com/getzipzi/zipzi-2d/blob/main/tools/README.md#local-distribution-artifacts).

SDK components use [MIT](https://github.com/getzipzi/zipzi-2d/blob/main/LICENSE-MIT); Editor components use
[GPL-3.0-only](https://github.com/getzipzi/zipzi-2d/blob/main/LICENSE-GPL-3.0). [LICENSING](https://github.com/getzipzi/zipzi-2d/blob/main/LICENSING.md) identifies scopes and shared-file origins;
[third-party notices](https://github.com/getzipzi/zipzi-2d/blob/main/THIRD_PARTY_NOTICES.md) retain dependency and benchmark exceptions.

Live2D, Cubism, the Live2D Cubism SDK, Cubism Core, Cubism Native Framework, and associated sample data are owned by or licensed through Live2D Inc. and/or their respective rightsholders. This project is independent and is not affiliated with, authorized by, endorsed by, or sponsored by Live2D Inc. The names “Live2D” and “Cubism” describe interoperability only.

This repository does not distribute the proprietary Cubism Core binary, a
Cubism SDK package, or Live2D sample model assets. Users who obtain, build,
link, publish, or distribute software using Live2D materials are responsible
for complying with all applicable terms, including the
[Live2D Proprietary Software License Agreement](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html),
[Live2D Open Software License Agreement](https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html),
and any applicable [sample data terms](https://www.live2d.com/eula/live2d-sample-model-terms_en.html).
The MIT License for this repository grants no rights to third-party software,
models, artwork, trademarks, or other materials.
