forked from 1b-agent/opencadstudio-upstream
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
f2c9e90c34
commit
be7ba3d68e
8 files changed
+336
-95
No files matched your search
+32
-36
@@ -328,42 +328,38 @@ impl MeasurementScale {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// MERGE SEAM — PR1 (feat/viewport-snap) owns `Scene::viewport_frame`.
|
||||
//
|
||||
// PR3 needs a frame to map a resolved model point onto the sheet, but PR1 is
|
||||
// not visible from this branch. This is the minimal stand-in agreed in the
|
||||
// shared contract: paper_center = vp.center, model_target = vp.view_target,
|
||||
// scale = `vp_effective_scale`, twist = vp.twist_angle. PR1's version is
|
||||
// camera-derived (it must agree with what is actually drawn, including the
|
||||
// auto-fit rescue in `Scene::camera_for_viewport` for stale saved views).
|
||||
//
|
||||
// To merge: delete this whole block. Nothing else in PR3 reads viewport
|
||||
// fields directly — every call goes through `Scene::viewport_frame`.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
impl crate::scene::Scene {
|
||||
/// The planar paper<->model mapping of layout viewport `handle`.
|
||||
///
|
||||
/// `None` when the handle is not a viewport, or when its scale is
|
||||
/// degenerate (a zero-height view cannot map anything).
|
||||
pub fn viewport_frame(&self, handle: Handle) -> Option<ViewportFrame> {
|
||||
let Some(acadrust::EntityType::Viewport(vp)) = self.document.get_entity(handle) else {
|
||||
return None;
|
||||
};
|
||||
let scale = crate::scene::vp_effective_scale(vp.custom_scale, vp.view_height, vp.height);
|
||||
if !scale.is_finite() || scale.abs() < 1e-12 {
|
||||
return None;
|
||||
}
|
||||
Some(ViewportFrame {
|
||||
viewport: handle,
|
||||
paper_center: DVec2::new(vp.center.x, vp.center.y),
|
||||
model_target: DVec2::new(vp.view_target.x, vp.view_target.y),
|
||||
scale,
|
||||
twist: vp.twist_angle,
|
||||
locked: vp.status.locked,
|
||||
})
|
||||
}
|
||||
}
|
||||
/// # Where the viewport compensation lives, once
|
||||
///
|
||||
/// A dimension that sits on a paper layout but measures model geometry seen
|
||||
/// through a viewport has **paper** definition points and displays a **model**
|
||||
/// number. Exactly one rule governs that, everywhere:
|
||||
///
|
||||
/// > `Dimension::actual_measurement` (DXF group 42) is always the **raw**
|
||||
/// > geometric measurement in the dimension's *own* space — paper, for a
|
||||
/// > viewport dimension. The viewport compensation lives **only** in the
|
||||
/// > magnitude of the negative DIMLFAC override
|
||||
/// > ([`MeasurementScale::viewport_dimlfac_override`]), and is applied
|
||||
/// > **once**, at format time, by the linear formatter.
|
||||
///
|
||||
/// So for a 1:10 viewport and a user DIMLFAC of 1: a 100-unit model line is
|
||||
/// 10 paper units, `actual_measurement` is `10`, the override is `-10`,
|
||||
/// [`MeasurementScale::user_lfac_for_space`] resolves that to `10` for a
|
||||
/// paper-space dimension, and the text reads `100`.
|
||||
///
|
||||
/// The consequence for association refresh
|
||||
/// (`Scene::refresh_associative_dimensions`) is that after re-placing a
|
||||
/// viewport dimension's definition points on the sheet it must write the
|
||||
/// **paper** measurement back — it must *not* substitute the model distance,
|
||||
/// and it must *not* multiply by [`MeasurementScale::model_factor`]. Doing
|
||||
/// either makes a refreshed dimension read a different number from an
|
||||
/// identical freshly-created one (`user_lfac` too many times). This is the
|
||||
/// rule that makes `attach_viewport_dimension_association` + refresh agree
|
||||
/// with creation by construction, and it is why `actual_measurement` stays
|
||||
/// raw: that is also what the DXF format specifies and what export writes.
|
||||
///
|
||||
/// Angular dimensions are exempt on both counts — a similarity preserves
|
||||
/// angles, so there is no compensation and no override.
|
||||
pub mod measurement_rule {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
Reference in new issue
Block a user