Skip to content

feat: place members with a rigid rotation and edit their orientation - #243

Merged
Blogbotana merged 2 commits into
Apibim-com:masterfrom
axisrow:feat/member-placement-rotation
Oct 25, 2026
Merged

Blogbotana merged 2 commits into
Apibim-com:masterfrom
axisrow:feat/member-placement-rotation

Conversation

@axisrow

@axisrow axisrow commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Members admit a rigid placement rotation, end to end:

  • Engine. The placement quaternion composes into the member body mesh — the world section
    frame is R_placement . (axis x up/roll) (the roll applies about the member axis first), the
    mesh 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_members now carries
    the translation only.
  • update-element. A new orientation_xyzw patch field replaces the stored quaternion
    (identity resets the pose), validated by the Placement rules and appended to the content
    fingerprint only when present, so earlier fingerprints are unchanged.
  • CLI. create-member/update-element take --orientation-xyzw X,Y,Z,W; plate-inspect
    and the README document inclined beams with a worked example.
  • Viewport. The scene keeps its shape (translation-only placements); the snapshot's member
    meshes arrive already rotated, and the edit/viewport suites pin the behaviour.
  • Host-frame note. SolidMesh::rotated_by builds the rotated mesh through the kernel's
    mesh admission; TriMesh::placed becomes pub for it, and rotate_vector moves to
    solid.rs as the shared helper.

The UI's default-workspace helpers (source::reload growth, default_workspace_at) are
included because the viewport test suites that grew this coverage use them.

Tests

  • New 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 warnings and
    cargo test --workspace pass locally on this branch.

Notes

  • Developed and shipped on the mirror fork (daily-driven there); adapted for upstream context
    (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.

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
axisrow requested a review from Blogbotana as a code owner October 25, 2026 07:52

@axisrow axisrow left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PR #243 - rigid member placement rotation - CHANGES REQUESTED

Two required changes, both inline; the feature itself is solid.

  1. A plate can be committed with a rotation its readers refuse. patch_plate applies orientation_xyzw, the CLI writes the placement attribute and the revision publishes with exit 0 (the plate branch re-evaluates nothing: the body-rebuild condition orientation && member skips plates, and there is no post-commit re-evaluation in update_element). But every read path of a committed plate with a non-identity quaternion refuses: scene_elements_with_members raises UNSUPPORTED_GEOMETRY, and plate-inspect builds 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 in patch_plate (UNSUPPORTED_GEOMETRY, mirroring create_plate), keep the identity accepted as the documented reset, update the update.rs unit test that currently pins the accepting behaviour, and align the model doc comment that promises the patch for a member or plate.

  2. The new image dependency is used by nothing. Nothing under apps/beamcraft-ui references the crate; the icon PNG path still goes through eframe::icon_data::from_png_bytes in theme.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_vector is 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); q and -q land 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::validate now 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_footprint sees 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_exists is an outcome rather than a refusal, and DUPLICATE_ID is tolerated on the fork.
  • Help text and README document the new flags, including none on 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.

Comment thread crates/engine/src/update.rs Resolved
Comment thread apps/beamcraft-ui/Cargo.toml Resolved
…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 axisrow left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

PR #243 - rigid member placement rotation (round 2) - APPROVED

Both required changes from the previous review are in and correct:

  • patch_plate now refuses a non-identity orientation_xyzw with UNSUPPORTED_GEOMETRY and the create-time message, before placement_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 image dependency is gone from apps/beamcraft-ui/Cargo.toml and Cargo.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).

@Blogbotana
Blogbotana merged commit a17e694 into Apibim-com:master Oct 25, 2026
34 checks passed
axisrow referenced this pull request in axisrow/BeamCraft Oct 25, 2026
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]>
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.

UI: 3D beam placement tool in the viewport CLI: orientation input on create-member and update-element

2 participants