Repository navigation
feat: place members with a rigid rotation and edit their orientation - #243
Conversation
The placement quaternion composes into the member body mesh (the section frame is `R_placement . (axis x up/roll)`; the roll applies about the member axis first), so the stored mesh is the detailed outline in the composed frame and the scene snapshot carries the translation alone. Quantities and the analytic dimensions are invariant under the rotation; a hostile quaternion is refused by code (`INVALID_ARGUMENT` / `NON_FINITE_NUMBER`), never a crash. `update-element` gains the `--orientation-xyzw` patch (validated and fingerprinted, replayable), the CLI grows the matching flag, and the viewport draws inclined and rolled beams from the same snapshot shape as before. The UI's default-workspace helpers (`source::reload` growth, `default_workspace_at`) come with the viewport test suites that use them: those suites grew the placement coverage and share the files. Co-Authored-By: Claude Code <[email protected]>
axisrow
left a comment
There was a problem hiding this comment.
PR #243 - rigid member placement rotation - CHANGES REQUESTED
Two required changes, both inline; the feature itself is solid.
-
A plate can be committed with a rotation its readers refuse.
patch_plateappliesorientation_xyzw, the CLI writes the placement attribute and the revision publishes with exit 0 (the plate branch re-evaluates nothing: the body-rebuild conditionorientation && memberskips plates, and there is no post-commit re-evaluation inupdate_element). But every read path of a committed plate with a non-identity quaternion refuses:scene_elements_with_membersraises UNSUPPORTED_GEOMETRY, andplate-inspectbuilds that scene too, so the store stops inspecting, snapshotting, exporting and picking until someone patches the rotation back. The write path must not admit a state the read paths reject: refuse a non-identity orientation patch for a plate inpatch_plate(UNSUPPORTED_GEOMETRY, mirroringcreate_plate), keep the identity accepted as the documented reset, update theupdate.rsunit test that currently pins the accepting behaviour, and align the model doc comment that promises the patch for a member or plate. -
The new
imagedependency is used by nothing. Nothing underapps/beamcraft-uireferences the crate; the icon PNG path still goes througheframe::icon_data::from_png_bytesintheme.rs, unchanged from master. Drop the dependency and its Cargo.lock entry.
What I checked and found sound:
- The composed frame is right and pinned by hand-derived expectations:
R_placement . (axis x up/roll), roll about the member axis first; the integration test derives the vertex map independently of the engine helper. rotate_vectoris the standard unit-quaternion rotation, normalized before use, and every caller reaches it through a validated (nonzero-norm) quaternion.- Identity keeps the mesh bytes (cached bodies and stored digests stay stable);
qand-qland on the same bytes; quantities, axis length and section area stay invariant - all asserted. - The member cache key gains the quaternion (a shape input) while the translation stays out; scene elements become translation-only for both kinds, consistently.
- The content fingerprint appends the quaternion bytes only when the patch names one, so earlier fingerprints are unchanged.
- Bonus fix worth noting:
Placement::validatenow catches non-finite quaternions; previously NaN slipped through the norm check and validated. - Footprint wiring: an orientation change reads and writes the placement group plus the derived group, the member body rebuild lands in the derived group;
check_footprintsees the declared set. - The ghost preview is display-only, defensively indexed, drawn on its own pipeline with a distinct fragment, and the ground-plane fallback rejects points beyond the coordinate cap.
- The default-workspace bootstrap races are handled honestly:
definition_existsis an outcome rather than a refusal, andDUPLICATE_IDis tolerated on the fork. - Help text and README document the new flags, including
noneon update; the CLI integration tests cover set, clear, replay and hostile input.
Optional, body only: the default-workspace helpers and the axis-format test ride along in a rotation PR; the body discloses the former, and both are harmless, but a follow-up that moves them would keep slices tight.
…sed image dependency Review follow-up. `patch_plate` accepted a non-identity orientation patch, but a committed rotated plate refuses every later read (plate-inspect, view-snapshot, view-export and pick all build the scene, which raises UNSUPPORTED_GEOMETRY) - the write path must not admit a state the read paths reject. A plate now refuses a non-identity orientation patch with UNSUPPORTED_GEOMETRY, mirroring `create_plate`; the identity stays the documented reset, the unit test pins the refusal, and the model doc comment no longer promises the patch for a plate. Also drops the `image` dependency from beamcraft-ui: nothing references it (the icon PNG decode goes through `eframe::icon_data` in theme.rs, unchanged from master), so the crate and its Cargo.lock entry go. Co-Authored-By: Claude Code <[email protected]>
axisrow
left a comment
There was a problem hiding this comment.
PR #243 - rigid member placement rotation (round 2) - APPROVED
Both required changes from the previous review are in and correct:
patch_platenow refuses a non-identityorientation_xyzwwith UNSUPPORTED_GEOMETRY and the create-time message, beforeplacement_after, so a committed plate can no longer carry a rotation its readers refuse; the identity still resets the pose. The unit tests were rewritten to pin the refusal, the identity reset, and the new refusal order (the engine guard fires before the model-level code checks, mirroring create_plate).- The unused
imagedependency is gone fromapps/beamcraft-ui/Cargo.tomlandCargo.lock. - The model doc comment now states the plate refusal instead of promising the patch for a member or plate.
No further change requested. One optional asymmetry, body only: the new guard compares bytes against [0,0,0,1], while is_identity_rotation treats any x = y = z = 0 quaternion (so w = -1 too) as the identity, and create_plate accepts that. A plate patch of [0,0,0,-1] is therefore refused where the same rotation is accepted at creation. It is an over-strict refusal of an exotic input, nothing is corrupted, so this can ride - reusing the placement is_identity_rotation check instead of the byte comparison would align the two paths.
The rest of the feature was reviewed in the previous round and is unchanged here (the fix commit touches only the four files above).
Upstream merged the placement rotation (#243) and the nextest profile (#241), so the placement commit this branch carried collapses into master and the branch's own diff is the auto-refresh feature alone. The app.rs add/add resolves to this branch's side: the same placement changes plus the watcher. Co-Authored-By: Claude Code <[email protected]>
Summary
Members admit a rigid placement rotation, end to end:
frame is
R_placement . (axis x up/roll)(the roll applies about the member axis first), themesh is the detailed outline in that composed frame, and the translation stays a scene
placement. Quantities, the axis length and the section area are invariant; the identity
rotation keeps the mesh bytes (stored digests stay stable); a non-normalized, degenerate or
non-finite quaternion is refused by code, never a crash. The committed member cache key gains
the quaternion (the rotation is a shape input), and
scene_elements_with_membersnow carriesthe translation only.
orientation_xyzwpatch field replaces the stored quaternion(identity resets the pose), validated by the
Placementrules and appended to the contentfingerprint only when present, so earlier fingerprints are unchanged.
create-member/update-elementtake--orientation-xyzw X,Y,Z,W;plate-inspectand the README document inclined beams with a worked example.
meshes arrive already rotated, and the edit/viewport suites pin the behaviour.
SolidMesh::rotated_bybuilds the rotated mesh through the kernel'smesh admission;
TriMesh::placedbecomespubfor it, androtate_vectormoves tosolid.rsas the shared helper.The UI's default-workspace helpers (
source::reloadgrowth,default_workspace_at) areincluded because the viewport test suites that grew this coverage use them.
Tests
crates/engine/tests/member_placement_rotation.rs(replay, reopen, world bounds,feature geometry, refusal codes), engine unit tests for the composed-frame rotation, the
orientation patch and hostile quaternions, and the grown CLI/viewport suites
(
members.rs,edit_tools.rs,viewport.rs).cargo fmt --check,cargo clippy --workspace --all-targets -- -D warningsandcargo test --workspacepass locally on this branch.Notes
(no fork-local references).
🤖 Generated with Claude Code
Linked issues
Fixes #214
Fixes #215
Refs #210. The epic remains open until its complete acceptance is verified.