Repository navigation
fix: deliver the fork delta of the 1113 sync wave - #875
Blogbotana merged 10 commits into
Conversation
The recheck of #562 on master found four items only partly fixed and a handful of new visible defects. This closes them: - Item 8: a detail callout name now steps off any drawn stroke, so the parent's body edge no longer strikes through "DET B". - Item 15: a selected bolt group is named (the bolt listing is read when it was never loaded) and offers no Apply/Reset, since it has nothing to edit. - Item 47: at 800x600 the snap strip shows chevrons that say and scroll what is cut off, and a short window drops the Menu heading so Help no longer hides under the footer. - Item 52: a colour set picked but not shown yet gets a note naming whose legend is shown. - New: a drawn text the stroke glyphs cannot draw refuses at edit-drafting instead of leaving a published version that can never be exported. - New: finishing a plate (and any window-run edit) keeps the camera; a kept camera now follows the scene's new origin instead of jumping aside. - New: Visibility/Graphics and other floating windows open and stay between the ribbon and the status bar. - New: the status line says "Updating the view..." while an edit's refresh is still loading. - New: small report quantities keep four significant digits instead of every float digit. - New: the start window's environment list stays inside the window and its note describes Blank only when Blank is chosen; an empty model's thumbnail shows a framed placeholder. Refs #562 Claude-Session: https://claude.ai/code/session_01HF6nmWCNmnwESuVYDnreeF Co-authored-by: Claude <[email protected]>
Closes #558 A committed A14.3 bolt group went out of `export-ifc` as an IfcBuildingElementProxy with no representation: the proxy path reads the element's own published mesh, and a bolt group's prim carries none - its fastener geometry was built only for the view snapshot. Every exported file therefore showed an empty element where the bolts and the washers should be, which is the "Болтов нет, шайб нет" of the issue. `bolt_scene::scene_element` now returns the merged solid beside the scene element (same vertices, digest and volume; one caller), and the decode step publishes it as the group's ElementMeta mesh through the existing kernel_mesh + published_mesh path. The export's proxy branch then carries the fasteners unchanged, exactly the mesh a view draws. A weld keeps its path solid, and an element whose prototype is refused still refuses the inspection instead of exporting bolts without their hardware. The new CLI test exports a committed group and reads the proxy's face set back out of the file: two SYN-BOLT-12x50 stacks, well over a hundred vertices. Reproduced on fresh origin/master before the change: the group row had `representation: null` and the written file carried no fastener face set; after the change the same store exports the mesh (816 points, 1608 triangles, no degenerate triangle). Co-authored-by: axisrow <[email protected]>
The scanner kept only IfcSIUnit, IfcProject, IfcUnitAssignment and IfcConversionBasedUnit as resolvable instances, but the units assignment may reference every member of the IfcUnit select type. A file written by export-ifc lists an IfcDerivedUnit (mass density) in the assignment, so the scan reported DANGLING_UNIT_REFERENCE for an instance it counted in its own inventory: every export-ifc file looked damaged to ifc-inspect. Add IfcDerivedUnit and IfcContextDependentUnit to the lookup so it covers the whole select type; both cannot change the length unit and are no loss, like the conversion-based one. A roundtrip test scans a written file and expects zero losses. Co-authored-by: axisrow <[email protected]>
…export-ifc (#601) The exporter read the hole count from the deprecated create-authored holes_mm attribute, so a plate whose holes all came from AddHole looked hole-free: the file said HoleCount 0 and carried GrossVolume and GrossWeight equal to the net ones, against IFCX-QTY-004, while the mesh held the hole. Read the count from the evaluated body, the same source the mesh and the model's hole_count use, so gross quantities follow the operations the geometry actually has (issue 598). Co-authored-by: axisrow <[email protected]>
…599) The forbidden-substring list in the_report_file_equals_the_printed_report carried a bare "tmp" entry. IFC GlobalIds are 22-character base64 strings (alphabet 0-9A-Za-z_$), so with roughly 1-in-2000 probability a random GlobalId contains the literal substring "tmp" and the assertion tripped although no path had leaked. The fixture-root entry already covers real path leakage from the test's own temp tree; anchor the remaining temp-path guard as "/tmp/", which cannot occur inside a GlobalId. Co-authored-by: axisrow <[email protected]>
…603) * Read placed IFC bodies: the ifc-shapes command and the shapes reader The MVP IFC input workflows needed the geometry half of the E04 input slice: ifc-inspect inventories the bytes, but nothing read the placed shapes back. beamcraft_ifc::shapes reads every product whose representation resolves to an IfcProductDefinitionShape over IfcTriangulatedFaceSet items, resolves the IfcLocalPlacement chains and the declared length unit, and returns the placed bodies in millimetres with an explicit loss list and bounded budgets. The new ifc-shapes command reports the same facts headlessly over one JSON reply: the source digest and size, the declared schema, the unit, product and shape counts, per-body identity, vertex and triangle counts, placed bounds and the losses. The committed fixture tests/fixtures/ifc/plate-hole.ifc pins the exact facts of the holed plate of the display issue, and the writer round trip asserts them against a fresh export-ifc output. This is the foundation slice; the display of the read bodies follows in the next slice. * Refuse a face set that declares its own Unit Review finding: read_face_set ignored the IfcTriangulatedFaceSet Unit attribute, so a body declared with a per-body unit silently took the project scale and left mis-scaled with no loss. A declared unit is not interpreted here, so the body is now refused with a FACE_SET_UNIT_NOT_READ loss; $ (the writer's own form) keeps reading. --------- Co-authored-by: axisrow <[email protected]>
) The face-on section test's plane sat at the bounds-centre height plus 5 mm, but `origin_mm` is the bounds centre, so the plane passed 2 mm above the 6 mm plate and kept the whole body: the cut frame was byte-identical to the uncut one and the assertion repeated the uncut frame's own check. The plane now runs through the scene's mid-height as the test's own description always said (the kept half's opened inside, the crossing section outline), and the assertion counts the kept body's lit pixels plus the section outline ink. On the Dx12 software rasterizer (Microsoft Basic Render Driver, windows-2022 platform shards) this also stops the test from tripping a rasterizer fault: with any cut uniform active in a steep view, the software rasterizer draws a fragment invocation of one primitive with another primitive's interpolated inputs, which erased the lit kept body exactly here. The section ink is drawn through the vertex-stage face-on cap, which the rasterizer handles correctly, so the section stays visible there. Co-authored-by: axisrow <[email protected]>
…r recheck (#606) * fix: close the partial items and new defects of the #562 recheck The recheck of #562 on master found four items only partly fixed and a handful of new visible defects. This closes them: - Item 8: a detail callout name now steps off any drawn stroke, so the parent's body edge no longer strikes through "DET B". - Item 15: a selected bolt group is named (the bolt listing is read when it was never loaded) and offers no Apply/Reset, since it has nothing to edit. - Item 47: at 800x600 the snap strip shows chevrons that say and scroll what is cut off, and a short window drops the Menu heading so Help no longer hides under the footer. - Item 52: a colour set picked but not shown yet gets a note naming whose legend is shown. - New: a drawn text the stroke glyphs cannot draw refuses at edit-drafting instead of leaving a published version that can never be exported. - New: finishing a plate (and any window-run edit) keeps the camera; a kept camera now follows the scene's new origin instead of jumping aside. - New: Visibility/Graphics and other floating windows open and stay between the ribbon and the status bar. - New: the status line says "Updating the view..." while an edit's refresh is still loading. - New: small report quantities keep four significant digits instead of every float digit. - New: the start window's environment list stays inside the window and its note describes Blank only when Blank is chosen; an empty model's thumbnail shows a framed placeholder. Refs #562 Claude-Session: https://claude.ai/code/session_01HF6nmWCNmnwESuVYDnreeF * fix: seat bolt nuts on the joint and close the rest of the #562 master recheck The recheck of #562 on master found two items still visible and two small new defects: - Item 5: the nut stack hung from the thread tip, so any listed length longer than the joint needs floated the nut washer 2-6 mm off the far plate on bare shank. The drawn group now measures each fastener's far face with the placement's own bearing-ring rays (engine::bolt::far_faces) and seats the nut stack there; the surplus shows as protrusion. A joint that cannot be measured (a framed plate) keeps the tip-hung stack, so a read never refuses for it. - Item 54: the full-name tooltip of the environment list ran past the 460 px start window; it now wraps inside it. - The ribbon overflow chevron was only painted: a click pressed the half-hidden tool under it and it stayed after scrolling to the end. It is now a button on its own strip that scrolls the ribbon, shows only while there is more on its side, and the plain wheel scrolls the ribbon. - The undo/redo sentence vanished the moment the window re-read the selection after the edit. That re-read now keeps the edit's outcome and shows no "Looking up...". Refs #562 Claude-Session: https://claude.ai/code/session_01HF6nmWCNmnwESuVYDnreeF --------- Co-authored-by: Claude <[email protected]>
…urce (#605) The display half of the IFC reading slice. The shapes reader (ifc-shapes) reads the placed bodies of a received file but had no consumer, so BeamCraft could not show even its own export-ifc output. export-view --source builds the engine's view bundle from a received file: every read body becomes one reference-kind display element through the new engine::scene::foreign_display_element (the kernel TriMesh rules), and the snapshot is pinned to the file's own SHA-256, so the same bytes give the same bundle and from_bundle re-validates it exactly like a store snapshot. The framed store extras are refused: a source file is one read-only display, not a revision. screenshot-view --source relays the file to beamcraft-ui, which exports the bundle from the CLI process and renders it with its own renderer; no store is opened anywhere on the path. --store given a file now refuses with "a file and not a store" instead of a bare integrity error. The end-to-end test renders the 120 x 80 x 10 plate with its 18 mm hole at (60, 40) and asserts that the background shows through the hole. The view export family help and COMMANDS.md document the one-of source form and what a received file does and does not carry: no store, no grids or annotations, catalogue colours only, bundle pinned to the file digest. Refs #596. Co-authored-by: axisrow <[email protected]>
axisrow
left a comment
There was a problem hiding this comment.
Approved — no changes requested.
Scope and receipts. The checkout is clean at the PR head 9df9648, the PR base upstream/master is unchanged at d7448c1, and the head tree matches the published full-scope gate receipt in the body (2900/2900 runnable plus mandatory doctests, format, clippy and docs green). The diff delivers the nine-commit 1113 fork delta: the IFC shapes reader and the read-only --source display path (#603, #605), bolt fastener proxy export (#595), IfcUnit select-type lookup (#600), AddHole hole counts in export quantities (#601), the report temp-path guard (#599), nut seating on the joint (#606), the #594/#602 UI and test fixes, and the committed plate-hole.ifc fixture.
What I verified by reading the diff.
- crates/ifc/src/shapes.rs: every allocation is bounded (instances, total vertices, total triangles, listed bodies, losses); placement chains are memoized with explicit loop and depth guards; triangle indices are range-checked before any conversion; a per-face-set Unit refuses the body instead of mis-scaling it; every refusal is an explicit loss code, and the scanning helpers fail closed on non-char-boundary input.
- crates/ifc/src/read.rs: the instance_head extraction preserves the old counting and listing semantics exactly; the IfcUnit select-type widening makes the own-writer export scan with zero losses, which the new writer round-trip test pins.
- crates/steel (bolt.rs, bolt_scene.rs) and crates/commands (bolt.rs, plate.rs, export.rs): the seat arithmetic moves the nut stack only when the hung washer floats above the measured far face (face below stack_base), never past the shank tip, and falls back to the prototype when the geometry is unmeasurable; a framed plate now skips measurement instead of refusing the whole read; the missing-fastener-definition integrity refusal is unchanged; the AddHole switch reads the body hole count, and the new tests pin the proxy mesh, the hole count and the net-only quantities.
- The --source path (ifc_view.rs, view_export.rs, view_screenshot.rs, ui main.rs, source.rs): --store and --source are mutually exclusive in every entry point; framed extras and non-catalogue colour sets refuse on a source; the UI takes --source only with --screenshot; a source NOT_FOUND stays a relayed rejection instead of the store typed empty state; --store given a plain file refuses with a plain message; display element ids are deterministic digest+ordinal with no collisions; the bundle reopens through from_bundle in a test.
- bom.rs quantity and drawing.rs/sheet.rs: the four-significant-digit fallback caps at 12 places and still falls back to the exact number when that rounds back to zero; the detail callout search is bounded and prefers a line clear of both placed names and drawn strokes, falling back to a name-clear line before giving up.
- Never-crash: no new unwrap/expect/panic or input-derived indexing outside tests in the diff; allocations sized by input are capped.
Observations, none requiring a change:
- The MAX_INDEXED_INSTANCES doc says shapes are not read past the cap; actually the indexed portion still reads and only the remainder is lost — the wording slightly overstates the refusal.
- The attach_bolt_scene comment says a framed plate draws the tip-hung prototype stack; in fact the framed plate is skipped and the nut can seat on the far face of the remaining in-plane parts. The behavior is sensible; the comment describes a different fallback than the code implements.
- read.rs split_args_cap and shapes.rs arg_spans are two parallel argument splitters; the span-based one avoids copying megabyte tessellation arguments, so the duplication is justified — noting it only.
- The scene_element seating unit test uses synthetic negative far faces while far_faces only produces non-negative distances; the mapping it pins is the right one, the setup is just artificial.
I am approving this PR. No inline findings.
The UI unit tests that spawn the CLI found `target/debug/beamcraft` two parents above the test binary - the `target/debug/deps` layout of cargo 1.99. cargo 1.100 keeps the canonical binaries in the build script's `out` directory, so the archived tests extracted there and the fixed walk checked `.../out/beamcraft`, which does not exist: every CLI-spawning UI test failed on CI while the integration tests (whose `CARGO_BIN_EXE` nextest remaps into the archive) kept passing. One shared `workspace_cli` helper walks the test binary's ancestors and takes the first directory that carries the binary; every guard uses it, and the per-test refusal messages stay.
axisrow
left a comment
There was a problem hiding this comment.
Approved — no changes requested.
Delta since the previous review (run 42d751ca, head 9df9648, verdict approved). One new commit, 4a33060, and it is test-only: the twelve copies of the fixed two-parent walk that located the workspace CLI beside the test binary are replaced by one shared workspace_cli() helper in app.rs tests that walks current_exe().ancestors() and takes the first directory carrying beamcraft plus the exe suffix, with the exe parent as the fallback that keeps the per-test is_file assertion meaningful. The cross-module references (super::tests from navigation_tests, super::super::tests from view_panel.rs and work_area.rs) resolve correctly, and no non-test code changed, so the earlier verdict over the batch stands unchanged.
Receipts. The checkout is clean at the PR head 4a33060; the PR base upstream/master is unchanged at d7448c1; the head tree 6166ca68 matches the freshly published full-scope gate receipt in the body (2900/2900 runnable plus mandatory doctests, all stages exit 0, source_unchanged true). The commit message correctly explains the cargo 1.100 out-directory layout that broke the old walk.
I am approving this PR. No inline findings.
* feat: deliver the fork delta of the 1114 sync wave Delivers the fork's post-#875 delta to upstream: the grid picks and views work, the bolt bearing face fix, the received-IFC native window family and the follow-up fixes and contracts that landed on fork master through 2026-11-14. Fork commits carried (refs qualified to the fork): - axisrow#608 grid picks, views merge, default view camera and 3D work plane - axisrow#609 screenshot-view --zoom scale help and empty-frame flag - axisrow#610 bolt bearing faces floated off coplanar plate faces - axisrow#615 open a received IFC file in the native window (--source FILE) - axisrow#620 the menu's Open entry opens another IFC file in a source window - axisrow#619 refuse a file store root before any window - axisrow#621 small leftovers of the #562 master recheck - axisrow#622 keep pick-ray height residue out of snapped points and the status line - axisrow#624 V20 measure tool design note - axisrow#626 free a deleted view's preview texture - axisrow#623 name cut cap corners so a cut member's length can be dimensioned - axisrow#625 rectangular member through-slot contract draft Fork-only CI workflow pinning (axisrow#612, axisrow#613) and fork-only documentation stay out, as in the earlier batch waves. * test: find the CLI through the workspace helper in the wave's new tests The four tests this wave adds walked two parents above the test binary for the workspace CLI, the fixed walk that cargo 1.100 broke: its archived test binaries live deeper in the build directory, so the walk found nothing and the tests refused before any assertion ran. They now use the same workspace_cli helper their neighbours use, which walks the ancestors and works under both layouts; the refusal messages are unchanged. --------- Co-authored-by: axisrow <[email protected]>
What this changes
Delivers the fork delta of the 1113 sync wave on top of
master, as nine commits byte-faithful to the fork squashes (bare fork issue refs qualified), plus one fix commit the wave's CI round required:export-ifc(two SYN-BOLT-12x50 stacks read back out of the file), instead of an emptyIfcBuildingElementProxy.ifc-inspectlookup covers every member of theIfcUnitselect type (IfcDerivedUnit,IfcContextDependentUnit), so anexport-ifcfile no longer reportsDANGLING_UNIT_REFERENCEagainst itself.export-ifcreads a plate's hole count from the evaluated body, so add-hole plates stop claimingHoleCount 0with gross quantities equal to the net ones (IFCX-QTY-004)."/tmp/", so an IFC GlobalId that happens to contain the substring "tmp" no longer trips it.beamcraft_ifc::shapesresolves triangulated product shapes, placement chains and the declared unit into placed bodies with an explicit loss list, and the newifc-shapescommand reports the same facts headlessly; the committedplate-hole.ifcfixture pins the facts.export-view --sourcebuilds the view bundle from a received file (bodies as reference-kind display elements, bundle pinned to the file's SHA-256),screenshot-view --sourcerenders it through the UI's own renderer, and--storegiven a file refuses plainly.The 1112 batch was already merged as #874; this PR carries only what the fork gained since (the fork's master head is axisrow/BeamCraft at
aeeb14de, and its own sync merge of this upstream state lands directly on the fork's master). Excluded per the established strip rules: fork-only CI,tools/test-gatelocal-receipt tweaks,docs/plan,ROADMAP, visual evidence and fork-only workflows — none of the picks touch them.The tenth commit is this wave's own CI fix, not fork content: the stable toolchain moved from 1.99.1 to 1.100.0 the night this PR's CI ran, and cargo 1.100 keeps the canonical binaries in the build script's
outdirectory, so the archive-based shards extracted the UI test binaries there and every test that finds the CLI two parents abovecurrent_exelosttarget/debug/beamcraft(theCARGO_BIN_EXE-based integration tests kept passing, because nextest remaps those into the archive). One sharedworkspace_clihelper walks the test binary's ancestors instead; the fork absorbs it at the next sync.How you verified it
cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspaceRun as the recorded local full gate on this exact head: 2900/2900 runnable tests executed plus mandatory doctests, format, clippy and docs with warnings denied (the receipt is published to this PR). Every behaviour above is covered by the tests the commits bring (callout stroke avoidance, nut seating, unit lookup, hole counts, shapes reader, IFC source display).
Never-crash
Every model file, IFC/DSTV import and keystroke is untrusted input (
AGENTS.md§4).unwrap/expect/panic!/todo!outside testsunsafeProvenance
ATTRIBUTION.mdAnything a reviewer should look at first
The picks land on top of #874's delivered state, so the axisrow#594/606 rechecks supersede the earlier #572 forms they build on (the callout placement replaces the plain stacking with the bounded stroke-avoiding search; the selection paths gain the
keep_noteand bolt-naming logic).--sourcereuses the store snapshot path with the file digest as the pin, so the bundle contract is unchanged for stores. Theworkspace_clihelper only widens where the guards look for the binary; every per-test refusal message is unchanged.