Observed since 20 Oct, 15:32 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Review a 3D design with an assistant: a web bench where a person marks what is wrong on the model, a ledger of which build step moved what, a requirement board whose rows are proven able to fail, and a Penpot bridge
3d-review is what I used to review a 3D design together with an AI assistant, while designing a 3D-printed toy. The person shows what is wrong on the model itself, the assistant answers with pictures, and the assistant has to prove that what the person asked for is still true after every rebuild.

It has four parts, and each one can be used alone.
pip install 3d-review
pip install "3d-review[penpot]" # adds shapely, for cutting outlines in the Penpot bridge
In Python it is imported as review3d. It uses 3d-base for pictures.
3d-review stages body.stl arm.stl wheel.stl -o model # writes model/stages.json and model/stages/parts.glb
cp "$(python -c 'import review3d,os;print(os.path.dirname(review3d.__file__))')/bench/bench.example.toml" model/bench.toml
3d-review bench --config model/bench.toml --port 8766
Then open http://localhost:8766/twist.html. threads.html lists every thread. From Python, review3d.stages.write_stages writes any number of stages (for example "parts", "assembled" and "printed") from {stage: {part: trimesh.Trimesh}}, and review3d.bench.server.Bench takes your own notifier, rebuild hook and freshness check. The default notifier appends a line to a file, so the assistant can watch that file.
The bench never changes the model. A submission is a request, and applying it is a separate step.
from review3d import ledger
import mybuild
ledger.install(mybuild, watch={"arm", "wheel"}, pipeline="build")
mybuild.build()
print(ledger.missing(mybuild, "build")) # steps the pipeline calls that the ledger does not wrap
ledger.write("ledger.json")
The steps are read from the pipeline's own source, so a step added later is never invisible to it. Moves are measured exactly (the rigid transform between the vertices before and after), and a part whose vertex count changed is reported as rebuilt.
import types
import trimesh
from review3d.board import Board, Row, at_most, mirror_asymmetry
design = types.SimpleNamespace(body=trimesh.load("part.stl")) # any object your rows can read
board = Board([Row("symmetric", "the two sides match", lambda d: mirror_asymmetry(d.body, normal=(1, 0, 0)), at_most(0.2), "mm")])
@board.fault("symmetric")
def bump(design):
lump = trimesh.creation.box(extents=(2, 2, 2))
lump.apply_translation([6, 0, 0])
design.body = trimesh.util.concatenate([design.body, lump])
board.report(design)
print(board.proof_table(board.prove(design)))
A row whose measurement fails or has no input reads unknown, never green. A row with no fault reads unproven. The measurements include mirror symmetry, sphericity, surface roughness, gap and penetration between parts, tipping, first-layer area, overhang share, length along an axis, axis angle, bow, out of round, tunnels, and the brightness of a rendered view.
from review3d.penpot import PenpotClient, PenpotSettings
client = PenpotClient(PenpotSettings.load("penpot.toml")) # or PENPOT_* environment variables
result = client.push({"plate": [(0, 0), (60, 0), (60, 20), (0, 20)]})
edited = client.pull(file_id=result.file_id)
One Penpot unit is one millimetre. Keep credentials in the settings file or the environment, never in code.
3d-review skill # copies it to ~/.claude/skills/3d-review
The skill is the review loop as we ran it. It covers writing the person's requirements as rows before building, proving each row, answering threads with pictures, keeping locks through rebuilds, asking questions with pictures on the bench instead of in words, and the failures that taught each rule.
127.0.0.1 or behind a private network.bench/static/vendor).pull reads the stored geometry and does not need it.python -m venv .venv && .venv/bin/pip install -e '.[test,penpot]'
.venv/bin/pytest
The Penpot tests use a fake server. Set PENPOT_URL and a token to run the live round trip too.
MIT
pypistats · 14 observations shown. Missing days are unknown. Source category: overall: without mirrors.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-17 – 2026-10-17 | 2 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-16 – 2026-10-16 | 2 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-14 – 2026-10-14 | 1 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-13 – 2026-10-13 | 1 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-11 – 2026-10-11 | 3 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-10 – 2026-10-10 | 3 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-09 – 2026-10-09 | 5 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-08 – 2026-10-08 | 7 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-07 – 2026-10-07 | 1 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-06 – 2026-10-06 | 7 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-05 – 2026-10-05 | 6 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-04 – 2026-10-04 | 11 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-03 – 2026-10-03 | 20 | 2026-10-20T17:46:10.7249702Z |
| 2026-10-02 – 2026-10-02 | 175 | 2026-10-20T17:46:10.7249702Z |
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.1 | 2 Oct 2026 | 7 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 15:32 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:1cb943c3b86062de1d68ea8aeccb9fc7414f58d89c394ae7119d147931a2ec28