fix(dimension): bake↔live consistency, edits, baseline/continue, grips

Batch 1-3 of the dimension audit (19 issues). The dominant class was the
baked *D block diverging from the live render: a reloaded dimension renders
from that block, so on save+reopen it silently changed.

Bake now matches the live render:
- text value/format via the shared style-resolved path (DIMDEC/DIMLFAC/
  DIMPOST/units/<>), correct height + rotation ladder, suppression
- angular dims bake their swept arc (no longer collapse to a V)
- diameter draws edge-to-edge through the centre (live + bake), centre mark
- radius/linear/aligned bake arrowheads
- group-10 (base.definition_point) synced from the edited geometry so an
  edited dim no longer shifts on reload

Editing / properties:
- aligned no longer corrupts ext_line_rotation when a coordinate is edited
- DIMTEDIT pins the moved text (text_user_positioned)
- angle properties shown/edited in degrees
- non-linear text respects the user-positioned gate

Commands / geometry:
- DIMBASELINE stacks outward (not toward the points) and its preview no
  longer tilts over non-level points
- DIMCONTINUE/DIMBASELINE inherit the base dim's text rotation (UCS chains)
- angular extension lines reach the arc (no gap)
- text grip sits on the text for auto-placed dims instead of the origin

Refs #181. (acadrust DXF round-trip + QDIM/DIMBREAK/DIMJOGLINE/DIMSPACE/
DIMEDIT to follow.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-27 17:47:50 +03:00
1 parent b691854e7a
commit 3c5aa4453a
7 files changed
+591 -131

No files matched your search

+31 -6
View File
@@ -34,6 +34,9 @@ pub struct DimBaselineCommand {
base_p1: Vec3,
/// Direction along the measurement direction (0.0 = horizontal, PI/2 = vertical).
rotation: f64,
/// Text reading rotation inherited from the base dim (keeps a UCS-aligned
/// chain's text consistent with the originating DIMLINEAR). 0 = natural.
text_rotation: f64,
/// Unit vector perpendicular to the dimension axis, pointing toward the dim line side.
perp: Vec3,
/// Perpendicular distance of the NEXT dimension line from the extension-line axis.
@@ -50,6 +53,7 @@ impl DimBaselineCommand {
Self {
base_p1: Vec3::ZERO,
rotation: 0.0,
text_rotation: 0.0,
perp: Vec3::Y,
next_offset: DEFAULT_DIMDLI,
dimdli: DEFAULT_DIMDLI,
@@ -69,6 +73,7 @@ impl DimBaselineCommand {
_p2: Vec3,
definition_point: Vec3,
rotation: f64,
text_rotation: f64,
dimdli: f32,
) -> Self {
// Measurement axis = the base dim's rotation angle (any angle, incl. a
@@ -79,13 +84,19 @@ impl DimBaselineCommand {
let dimdli = if dimdli.abs() < 1e-6 {
DEFAULT_DIMDLI
} else {
dimdli
dimdli.abs()
};
// Next baseline dim goes one DIMDLI further from the baseline.
// Stack each baseline outward — in the SIGN direction of the base dim's
// own offset. When the base dim sits on the -perp side (below / left of
// the points) a positive increment would march the stack back toward and
// across the points; the signed increment keeps it moving away. (#181)
let dir = if base_offset >= 0.0 { 1.0 } else { -1.0 };
let dimdli = dimdli * dir;
let next_offset = base_offset + dimdli;
Self {
base_p1: p1,
rotation,
text_rotation,
perp,
next_offset,
dimdli,
@@ -94,6 +105,19 @@ impl DimBaselineCommand {
}
}
impl DimBaselineCommand {
/// The two dimension-line endpoints for the new baseline at `p2`: the fixed
/// base origin and `p2` each projected INDEPENDENTLY onto the dim line (at
/// the current stacking offset). Projecting both keeps the line straight
/// even when the two origins aren't level — a shared offset tilts it. (#181)
fn dim_line_pts(&self, p2: Vec3) -> (Vec3, Vec3) {
let target = self.base_p1.dot(self.perp) + self.next_offset;
let d1 = self.base_p1 + self.perp * (target - self.base_p1.dot(self.perp));
let d2 = p2 + self.perp * (target - p2.dot(self.perp));
(d1, d2)
}
}
impl CadCommand for DimBaselineCommand {
fn name(&self) -> &'static str {
"DIMBASELINE"
@@ -117,9 +141,11 @@ impl CadCommand for DimBaselineCommand {
// Build a new linear dimension.
let mut dim = DimensionLinear::new(v3(p1), v3(p2));
dim.rotation = self.rotation;
if self.text_rotation.abs() > 1e-9 {
dim.base.text_rotation = self.text_rotation;
}
let dim_line_pt = p1 + self.perp * self.next_offset;
let dim_line_pt2 = p2 + self.perp * self.next_offset;
let (dim_line_pt, dim_line_pt2) = self.dim_line_pts(p2);
dim.definition_point = v3(dim_line_pt);
dim.base.definition_point = v3(dim_line_pt);
dim.base.text_middle_point = v3((dim_line_pt + dim_line_pt2) * 0.5);
@@ -141,8 +167,7 @@ impl CadCommand for DimBaselineCommand {
return None;
}
let p1 = self.base_p1;
let dim_line_pt = p1 + self.perp * self.next_offset;
let dim_line_pt2 = pt + self.perp * self.next_offset;
let (dim_line_pt, dim_line_pt2) = self.dim_line_pts(pt);
Some(WireModel {
name: "dimbase_preview".into(),
points: vec![
+15 -1
View File
@@ -31,6 +31,9 @@ pub struct DimContinueCommand {
chain_p1: Vec3,
/// Direction along the dimension axis (0.0 = horizontal, PI/2 = vertical).
rotation: f64,
/// Text reading rotation inherited from the base dim so a UCS-aligned chain
/// keeps its text consistent with the originating DIMLINEAR. 0 = natural.
text_rotation: f64,
/// Absolute perpendicular coordinate (dot with `perp`) of the base
/// dimension line. Each continued dim line is projected onto this exact
/// coordinate so the whole chain stays collinear — even when the extension
@@ -49,6 +52,7 @@ impl DimContinueCommand {
Self {
chain_p1: Vec3::ZERO,
rotation: 0.0,
text_rotation: 0.0,
dim_line_perp: 0.0,
perp: Vec3::Y,
ready: false,
@@ -64,7 +68,13 @@ impl DimContinueCommand {
/// `definition_point` — where the dim line was placed; its perpendicular
/// coordinate is the line the whole chain stays collinear with.
/// `rotation` — 0.0 = horizontal dim, PI/2 = vertical dim.
pub fn from_base(_p1: Vec3, p2: Vec3, definition_point: Vec3, rotation: f64) -> Self {
pub fn from_base(
_p1: Vec3,
p2: Vec3,
definition_point: Vec3,
rotation: f64,
text_rotation: f64,
) -> Self {
// Axis unit vector along the measurement direction — the base dim's
// rotation angle (any angle, incl. a UCS-aligned one), not a world H/V.
let axis = Vec3::new(rotation.cos() as f32, rotation.sin() as f32, 0.0);
@@ -74,6 +84,7 @@ impl DimContinueCommand {
Self {
chain_p1: p2,
rotation,
text_rotation,
dim_line_perp,
perp,
ready: true,
@@ -104,6 +115,9 @@ impl CadCommand for DimContinueCommand {
// Build a new linear dimension.
let mut dim = DimensionLinear::new(v3(p1), v3(p2));
dim.rotation = self.rotation;
if self.text_rotation.abs() > 1e-9 {
dim.base.text_rotation = self.text_rotation;
}
// Dim line stays collinear with the base: project both extension
// origins onto the base dim line's absolute perpendicular coordinate.
+3
View File
@@ -78,6 +78,9 @@ impl CadCommand for DimTeditCommand {
acadrust::types::Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64);
d.base_mut().text_middle_point = new_pt;
d.base_mut().insertion_point = new_pt;
// Pin the text to this location, else the renderer recomputes
// the style-default placement and the move is discarded.
d.base_mut().text_user_positioned = true;
}
return CmdResult::ReplaceEntity(h, vec![ent]);
}
+17 -17
View File
@@ -151,28 +151,28 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
}
}
fn linear_dimension_preview(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec<Vec3> {
/// Project the two extension origins onto the dimension line, which passes
/// through `def` along `axis`. Each origin is projected *independently*: a
/// single shared offset only lands both on the line when they are level, and
/// tilts the dimension line when they are not (e.g. measuring across sloped
/// points). See #181.
fn dim_line_endpoints(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> (Vec3, Vec3) {
let perp = Vec3::new(-axis.y, axis.x, 0.0);
let offset = (def - first).dot(perp);
let d1 = first + perp * offset;
let d2 = second + perp * offset;
vec![
first,
d1,
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
second,
d2,
Vec3::new(f32::NAN, f32::NAN, f32::NAN),
d1,
d2,
]
let dperp = def.dot(perp);
let d1 = first + perp * (dperp - first.dot(perp));
let d2 = second + perp * (dperp - second.dot(perp));
(d1, d2)
}
fn linear_dimension_preview(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec<Vec3> {
let (d1, d2) = dim_line_endpoints(first, second, def, axis);
let nan = Vec3::new(f32::NAN, f32::NAN, f32::NAN);
vec![first, d1, nan, second, d2, nan, d1, d2]
}
fn linear_text_pos(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec3 {
let (d1, d2) = dim_line_endpoints(first, second, def, axis);
let perp = Vec3::new(-axis.y, axis.x, 0.0);
let offset = (def - first).dot(perp);
let d1 = first + perp * offset;
let d2 = second + perp * offset;
(d1 + d2) * 0.5 + perp * 0.15
}