Skip to content

[framework] bench2 preview-parts: reproducible assembly component previews - #116

Merged
HaozheZhang6 merged 10 commits into
mainfrom
framework/preview-parts
Aug 2, 2026
Merged

[framework] bench2 preview-parts: reproducible assembly component previews#116
HaozheZhang6 merged 10 commits into
mainfrom
framework/preview-parts

Conversation

@BenchCAD

@BenchCAD BenchCAD commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

What changed

  • uv run bench2 preview-parts <family> renders designs/<family>/preview_parts.png for named-cq.Assembly families: one four-view row per semantic component (own frame, bounding box in mm), the complete assembly in its true pose, then red-on-gray component highlight rows in family.json components order — repeated instances grouped with quantity=N by default, --per-instance for one row per instance.
  • uv run bench2 preview <family> runs the same render automatically when build(**params) returns a named Assembly; single-part families are unchanged.
  • The render comes from the derived stand-alone program — the same artifact every other preview renders. A new export harness (execute_cq_to_parts) executes it in the usual subprocess and walks the Assembly exactly like cq's toCompound(), exporting each shape-bearing node's raw local shape plus its absolute 3x4 world transform, so nested parent/child Location transforms survive without parsing STEP product structure.
  • render_iso's VTK pipeline is generalized to styled multi-actor scenes (render_actors): shared normalized frame, camera fitted over the union of all actors, z-buffer depth occlusion. render_iso is now the one-actor teal case — same pipeline, same bytes.
  • Naming contract, documented in docs/DESIGN_SPEC.md: name every shape-bearing node exactly after its component (quantity 1) or <component>_<NN> for repeated instances; exact declared names win over suffix stripping; semantically distinct components stay separate even with identical geometry. family.json must declare components with solids equal to the quantity sum — bench2 validate now checks that consistency, and every violation (non-Assembly result, unmatched or duplicate instance names, quantity drift) fails clearly instead of producing a misleading image.
  • tests/ (new, wired into validate.yml): hermetic fixture families through the real derive -> subprocess -> manifest -> VTK pipeline — nested locations, translation + rotation, repeated instances, grouped vs --per-instance row layout, metadata mismatches, single-part skip/fail, and byte-determinism of the output PNG.

Example — what it renders (in-repo, committed)

docs/examples/preview_parts_demo/ is a runnable demo assembly — block, flanged bushing, and a bolt_01/bolt_02 pair in a nested, 30-deg-rotated sub-assembly — with both artifacts committed, embedded in DESIGN_SPEC, and kept runnable by a framework test.

Grouped default — one four-view row per component (bbox in mm), the complete assembly in pose, then red-on-gray highlight rows in components order (bolt highlights both instances, quantity=2):

preview_parts.png

--per-instancebolt_01 and bolt_02 each get their own highlight row:

preview_parts_per_instance.png

--transparent — non-highlighted components ghosted (see-through): the bushing shaft buried in its bore and both bolt shanks stay visible when highlighted. The opaque modes above are unchanged (styles gained an opacity channel; existing output is byte-identical):

preview_parts_transparent.png

Why

Multi-component families had no reproducible framework command for preview_parts.png: the STEP path folds an Assembly into a compound, so component names and hierarchy never reach the renderer, and each assembly family hand-rolled its own render script. family-pr-checks and the dossier close-out already expect the artifact; this makes it a one-command, deterministic framework output (hard / seed 0) for any unrelated named-Assembly family.

Draft #96 explores the same surface and its layout is kept here (it matches the reviewer-approved artifact in #80); the framework difference is that this implementation renders the derived program via the standard subprocess harness rather than calling part.build() in-process, so component evidence traces to the exact program the benchmark asks a model to produce.

Validation

  • uv run ruff check framework tests — pass
  • 21 unit tests — pass (uv run python -m unittest discover -s tests)
  • bench2 validate on all three merged families — PASS (validate gate addition is inert for single-part families)
  • Real-family smoke: bench2 preview-parts safety_swivel_load_ring (sources from [family] safety_swivel_load_ring #80, run in a scratch tree — that family and its PR are untouched) renders the 5-component grid; two consecutive runs produce byte-identical PNGs (same SHA-256), --per-instance degenerates to the grouped layout as expected since every quantity is 1
  • Rendered output inspected by hand: component rows, pose, occlusion, and highlight order all read correctly
  • Note: boundary edges render red under the current vtkFeatureEdges defaults; that is main's existing single-part behavior (visible in every committed preview.png) and is deliberately preserved byte-for-byte here rather than restyled in the same PR

Framework-only change: no file under designs/ is touched.

Closes #95

…views

Named-Assembly families had no framework command for preview_parts.png:
the STEP path folds an Assembly to a compound, so component names and
hierarchy never reach the renderer and every assembly family hand-rolled
its own render script.

- execute_cq_to_parts: run the DERIVED program (the same artifact every
  other preview renders) with an export harness that keeps the tree —
  one STEP per shape-bearing node plus its absolute 3x4 world transform
  in manifest.json.
- render_actors: the single-mesh VTK pipeline generalized to styled
  multi-actor scenes (shared frame, camera fitted over the union of all
  actors, z-buffer depth occlusion); render_iso is now the one-actor
  teal case — same pipeline, same bytes.
- preview_parts.build_preview_parts: deterministic hard / seed 0 ->
  one four-view row per semantic component (own frame, bbox in mm), the
  complete assembly in its true pose, then red-on-gray highlight rows in
  family.json components order; repeated instances group with
  quantity=N, --per-instance splits them. Contract violations fail
  clearly instead of rendering a misleading image.
- naming contract: exact component name, or <component>_<NN> for
  repeated instances; exact declared names win; semantically distinct
  components stay separate. family.json solids must equal the
  components quantity sum — now also checked by bench2 validate.
- bench2 preview invokes the same render automatically when build()
  returns a named Assembly; single-part families are unchanged.
- tests/: hermetic fixture families through the real pipeline (nested
  locations, rotation, repeated instances, grouped vs per-instance,
  metadata drift, byte determinism) plus a CI step that runs them.

Closes #95

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

bench2 validate

clevis_fork_head

  ✓ family.json: keys + base_plane valid
  ✓ pieces: build(10 params) + PARAM_SPEC + check present
  ✓ PARAM_SPEC: 10 params, all entries complete
  ✓ easy: 4/4 seeds sample+check+build+execute clean
  ✓ medium: 4/4 seeds sample+check+build+execute clean
  ✓ hard: 4/4 seeds sample+check+build+execute clean
  ✓ coverage: d1 reaches all 8 declared values (standard table fully covered)
  ✓ coverage: is_long reaches all 2 declared values (standard table fully covered)
  ✓ difficulty separation: difficulties produce distinct programs
  ✓ geometry novelty: 9/12 unique shapes (25% duplicate)
  ✓ solids: every instance non-degenerate, 1 solid(s) each
PASS — designs/clevis_fork_head

set_screw_shaft_collar

  ✓ family.json: keys + base_plane valid
  ✓ pieces: build(6 params) + PARAM_SPEC + check present
  ✓ PARAM_SPEC: 6 params, all entries complete
  ✓ easy: 4/4 seeds sample+check+build+execute clean
  ✓ medium: 4/4 seeds sample+check+build+execute clean
  ✓ hard: 4/4 seeds sample+check+build+execute clean
  ✓ coverage: bore_d reaches all 15 declared values (standard table fully covered)
  ✓ difficulty separation: difficulties produce distinct programs
  ✓ geometry novelty: 7/12 unique shapes (42% duplicate)
  ✓ solids: every instance non-degenerate, 1 solid(s) each
PASS — designs/set_screw_shaft_collar

slotted_din_rail

  ✓ family.json: keys + base_plane valid
  ✓ pieces: build(10 params) + PARAM_SPEC + check present
  ✓ PARAM_SPEC: 11 params, all entries complete
  ✓ easy: 4/4 seeds sample+check+build+execute clean
  ✓ medium: 4/4 seeds sample+check+build+execute clean
  ✓ hard: 4/4 seeds sample+check+build+execute clean
  ✓ difficulty separation: difficulties produce distinct programs
  ✓ geometry novelty: 6/12 unique shapes (50% duplicate)
  ✓ solids: every instance non-degenerate, 1 solid(s) each
PASS — designs/slotted_din_rail

speaker_pole_mount_socket

  ✓ family.json: keys + base_plane valid
  ✓ pieces: build(8 params) + PARAM_SPEC + check present
  ✓ PARAM_SPEC: 8 params, all entries complete
  ✓ easy: 4/4 seeds sample+check+build+execute clean
  ✓ medium: 4/4 seeds sample+check+build+execute clean
  ✓ hard: 4/4 seeds sample+check+build+execute clean
  ✓ coverage: bore_d reaches all 2 declared values (standard table fully covered)
  ✓ coverage: flange_od reaches all 2 declared values (standard table fully covered)
  ✓ coverage: flange_t reaches all 2 declared values (standard table fully covered)
  ✓ coverage: depth reaches all 6 declared values (standard table fully covered)
  ✓ difficulty separation: difficulties produce distinct programs
  ✓ geometry novelty: 9/12 unique shapes (25% duplicate)
  ✓ solids: every instance non-degenerate, 1 solid(s) each
PASS — designs/speaker_pole_mount_socket

Previews: the previews artifact on this run.

BenchCAD added 2 commits July 31, 2026 06:14
The hosted runner has no display, EGL, or OSMesa, so any VTK offscreen
render segfaults (exit 139) — the new framework tests hit it, and it is
also why the existing 'render previews' step never actually rendered
(its || true swallowed the crash and the artifact just re-uploaded the
committed PNGs). The vtk wheel dlopens the system libOSMesa at runtime;
installing libosmesa6 gives it a software offscreen backend, making the
framework tests and real CI preview renders work.

Also guard the preview step against a missing /tmp/fams.txt: with
always() it runs even when a step failed before family scoping wrote
the list.

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
The maintainer is dropping askable from the public contract (#121);
this PR moved cli._param_caption into preview_parts, so adopt the same
semantics here — caption every declared parameter instead of filtering
on askable/feature — and drop the inert askable key from the test
fixture. Keeps the eventual rebase of either PR onto the other purely
mechanical.

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
…o framework/preview-parts

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>

# Conflicts:
#	framework/bench2/cli.py
…o framework/preview-parts

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>

# Conflicts:
#	framework/bench2/cli.py
Reviewers judge a render feature by its images, so the repo now ships a
complete example: docs/examples/preview_parts_demo (a pillow-block-style
assembly — block, flanged bushing, and a bolt_01/bolt_02 pair in a
nested, 30-deg-rotated sub-assembly) with both artifacts committed
(grouped and --per-instance) and a README covering what it demonstrates
and how to regenerate. DESIGN_SPEC embeds the grouped image next to the
naming contract, and a framework test keeps the example runnable. Lives
outside designs/ on purpose: it is documentation, not a benchmark
family.

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
@BenchCAD

BenchCAD commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator Author

Added the reviewable evidence this PR was missing: a committed, runnable example under docs/examples/preview_parts_demo/ (nested + rotated sub-assembly, repeated bolt_01/bolt_02), its grouped and --per-instance renders embedded in the PR body above and in DESIGN_SPEC, and a framework test that keeps the example runnable (d2037f4).

Maintainer request on the example: keep the opaque highlight rows and
add a transparent variant. Non-highlighted components can hide the very
part being highlighted (a bushing pressed into its bore, a bolt shank
inside its hole); --transparent renders them ghosted (opacity 0.22,
faint edges) so the red component reads through the assembly. Styles
gain an opacity channel (existing styles stay fully opaque — default
output is byte-identical), the demo commits
preview_parts_transparent.png, and DESIGN_SPEC/README/tests cover the
mode.

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
@BenchCAD

BenchCAD commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator Author

Added --transparent per the request — opaque grouped/per-instance modes are unchanged, and the new ghost mode renders non-highlighted components see-through so internal parts (the bushing shaft in its bore, the bolt shanks) stay visible when highlighted. Demo artifact committed and embedded in the body above; DESIGN_SPEC/README/tests cover the mode (6af34db).

@sunzhenchuan007 sunzhenchuan007 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An old preview_parts.png might be left behind after failing

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>

# Conflicts:
#	framework/bench2/render.py
sunzhenchuan007's approval note: a failed run could leave the previous
preview_parts.png in place, reading as current evidence. The artifact is
now removed at the start of every run — a failed run leaves nothing, and
a family that stopped being an assembly sheds its obsolete artifact on
the next preview. Tests cover both paths, and the demo artifacts are
regenerated on the merged renderer (centered framing from #115).

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
@BenchCAD

BenchCAD commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

@sunzhenchuan007 good catch on the stale artifact — fixed in f396456: preview_parts.png is now removed at the start of every run, so a failed run leaves nothing behind (and a family that stopped being an assembly sheds its obsolete artifact on the next preview); both paths are tested.

Also resolved the merge conflict with main: #115's centered-framing fix is ported into the multi-actor render_actors (projected-bounds midpoint over the union of all actors — identical math for the single-actor case), the cutaway panel comes along unchanged, and the demo artifacts are regenerated on the merged renderer. Branch is up to date with main and green locally.

@HaozheZhang6
HaozheZhang6 merged commit 3492acb into main Aug 2, 2026
5 checks passed
@HaozheZhang6
HaozheZhang6 deleted the framework/preview-parts branch August 2, 2026 01:48
HaozheZhang6 pushed a commit that referenced this pull request Aug 2, 2026
build() now returns a named cq.Assembly with components built in their
local frames and placed by Location: body, cover, scroll_plate (key
position applied as its Location), jaw_1/jaw_2/jaw_3 as three declared
components (a matched set with distinct P/3 tooth phases, like the real
1/2/3-marked jaws), and pinion x3 as repeated instances of one local
shape. family.json declares the components (quantity sum = solids = 9)
and preview_parts.png is now the framework bench2 preview-parts output:
per-component four-view rows in one comparable frame, the assembly in
pose, and per-component highlight rows. World geometry is unchanged -
probes reproduce identical intersection and gap numbers; validate
passes the new components gate.

Signed-off-by: BenchCAD <benchcad@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] add reproducible assembly component previews

3 participants