Record viewport associations for new dimensions

Removes PR3's `viewport_frame` seam in `viewport_ref.rs`; the frame is
PR1's camera-derived one everywhere.

The five dimension commands (DIMLINEAR / DIMALIGNED / DIMRADIUS /
DIMDIAMETER / DIMANGULAR) now record a real association when they
measure through a viewport. The commit path's one association gate
(`dimension_association_allowed`, added when PR2 was stacked) gains an
else-branch: where PR2 committed the dimension unassociated and PR1's
inference guard returned an empty source list, `Scene::attach_viewport_
dimension_association` now stores the whole chain — dimension ->
viewport -> block path -> entity -> feature — from the AcceptedSnaps PR1
captured, in definition-point slot order.

* `Scene::dimension_association_slot_points` names that order, including
  the radial dimensions `dimension_reference_points` does not cover.
* `OpenCADStudio::viewport_dimension_snaps` builds the slot array. A snap
  whose paper point is no longer the definition point it fills has its
  model point recomputed from that definition point through the frame:
  the object pick records one click for both extension origins, and a
  rotated DIMLINEAR projects its origins onto the measuring axis.
* Radial dimensions take their own path inside
  `attach_viewport_dimension_association`. Their single reference is an
  osnap type 10 carrying the radial marker (which circle / which polyline
  arc segment) and the angle around it; `source_reference` would hand
  back the generic curve-parameter marker -2, which
  `radial_source_for_marker` cannot find.

One measurement rule, documented in `viewport_ref::measurement_rule`:
`actual_measurement` is always the RAW measurement in the dimension's own
space (paper, for a viewport dimension), and the viewport compensation
lives only in the magnitude of the negative DIMLFAC override, applied
once by the formatter. PR2's persistence already followed it; PR3's
refresh did not — it substituted the model distance times
`model_factor()`, which made a refreshed dimension read `user_lfac` times
too much versus an identical freshly-created one. Refresh now writes the
paper measurement (a debug assert pins resolved paper points to the model
points through the frame), and `axis_distance` goes with it.

Block sub-entity snaps: verified that `block_cache` stamps the top-level
INSERT's handle onto every expanded and clipped sub-wire, so
`wire_source` can only ever report the INSERT — nested inserts included.
`OpenCADStudio::resolve_source_block_path` now descends from that INSERT
at accept time (using the object-pick path's existing
`resolve_measurable_entity`, so arbitrary depth, not just one level) and
fills `SnapSourceRef.block_path` with the real inner entity. Where the
descent cannot identify one — a non-planar-pickable type such as text,
hatch, spline or solid, or nesting past eight levels — the source stays
the INSERT with an empty path, which is coarser but never wrong. Both the
limitation and where it bites are documented on
`Scene::resolve_block_snap_source` and `SnapResult::source`.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Christopher OverbeckandClaude Fable 5.1 committed 2026-09-15 12:52:00 -05:00
1 parent f2c9e90c34
commit be7ba3d68e
8 files changed
+336 -95

No files matched your search

+12 -2
View File
@@ -93,8 +93,15 @@ pub struct SnapResult {
/// query, never by the engine itself. (PR1)
pub viewport: Option<Handle>,
/// Owning entity of the geometry the snap landed on, when the engine could
/// attribute the feature to a wire. Block sub-entities report the top-level
/// INSERT handle; the block path is resolved later by
/// attribute the feature to a wire.
///
/// This is whatever the *wire* carries, which for anything drawn from a
/// block is the **top-level INSERT's** handle — nested inserts included,
/// because `block_cache` stamps the host insert's name onto every
/// expanded and clipped sub-wire. Resolving that to the inner entity plus
/// the instance path needs the document and the accepted model point, so
/// it happens once at accept time, in
/// `OpenCADStudio::resolve_source_block_path`, which fills
/// [`crate::scene::viewport_ref::SnapSourceRef::block_path`]. (PR1)
pub source: Option<DimensionAssociationSource>,
}
@@ -108,6 +115,9 @@ impl SnapResult {
/// Entity handle carried by a wire (`WireModel::name` is the decimal handle
/// value). Returns `None` for preview / interim wires with a symbolic name.
///
/// For block geometry the value is the top-level INSERT handle, never the
/// inner entity: see [`SnapResult::source`].
#[inline]
pub(crate) fn wire_source(wire: &WireModel) -> Option<DimensionAssociationSource> {
wire.name