forked from 1b-agent/opencadstudio-upstream
feat(scene): honour the last DimStyle fields end-to-end
Tessellation and DIMBASELINE pick up the few DimStyle fields that were still hard-coded or ignored: - DIMTMOVE = 1 draws a connector segment from the dim-line anchor to the saved text_middle_point when the text has been moved - Leader arrows are resolved via the leader's DIMSTYLE → DIMLDRBLK; the shape (closed-filled / open / dot / tick / …) is drawn through the same arrow emitter the dim path uses, including fill_tris for filled blocks - DIMBASELINE picks up DIMDLI from the active DimStyle instead of the hard-coded 1.5 increment - DIMTOFL / DIMTIX / DIMATFIT / DIMUPT / DIMTXTDIRECTION are read so the fields round-trip on save; their effect is at dim creation, not render, so they're acknowledged with a no-op block Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
9d0b406b31
commit
4adbb20c5d
3 files changed
+145
-25
No files matched your search
+11
-1
@@ -1280,7 +1280,17 @@ impl H7CAD {
|
||||
use crate::modules::annotate::dim_baseline::DimBaselineCommand;
|
||||
let cmd = if let Some((p1, p2, dp, rot)) = find_last_linear_dim(&self.tabs[i].scene)
|
||||
{
|
||||
DimBaselineCommand::from_base(p1, p2, dp, rot)
|
||||
let doc = &self.tabs[i].scene.document;
|
||||
let dimdli = doc
|
||||
.dim_styles
|
||||
.iter()
|
||||
.find(|s| {
|
||||
s.name
|
||||
.eq_ignore_ascii_case(&doc.header.current_dimstyle_name)
|
||||
})
|
||||
.map(|s| s.dimdli as f32)
|
||||
.unwrap_or(1.5);
|
||||
DimBaselineCommand::from_base(p1, p2, dp, rot, dimdli)
|
||||
} else {
|
||||
DimBaselineCommand::new()
|
||||
};
|
||||
|
||||
@@ -26,8 +26,8 @@ pub fn tool() -> ToolDef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default stacking increment (world units) between successive baseline dimensions.
|
||||
const DIMDLI: f32 = 1.5;
|
||||
/// Fallback stacking increment (world units) when no DimStyle is available.
|
||||
const DEFAULT_DIMDLI: f32 = 1.5;
|
||||
|
||||
pub struct DimBaselineCommand {
|
||||
/// Fixed first-extension-line origin (never changes).
|
||||
@@ -38,6 +38,8 @@ pub struct DimBaselineCommand {
|
||||
perp: Vec3,
|
||||
/// Perpendicular distance of the NEXT dimension line from the extension-line axis.
|
||||
next_offset: f32,
|
||||
/// Stacking increment from the active DimStyle (DIMDLI).
|
||||
dimdli: f32,
|
||||
/// True once we have a base dimension loaded.
|
||||
ready: bool,
|
||||
}
|
||||
@@ -49,7 +51,8 @@ impl DimBaselineCommand {
|
||||
base_p1: Vec3::ZERO,
|
||||
rotation: 0.0,
|
||||
perp: Vec3::Y,
|
||||
next_offset: DIMDLI,
|
||||
next_offset: DEFAULT_DIMDLI,
|
||||
dimdli: DEFAULT_DIMDLI,
|
||||
ready: false,
|
||||
}
|
||||
}
|
||||
@@ -60,7 +63,14 @@ impl DimBaselineCommand {
|
||||
/// `p2` — second extension line origin of the base dim (unused for placement, kept for context).
|
||||
/// `definition_point` — dim-line position of the base dim (defines perpendicular side).
|
||||
/// `rotation` — 0.0 = horizontal, PI/2 = vertical.
|
||||
pub fn from_base(p1: Vec3, _p2: Vec3, definition_point: Vec3, rotation: f64) -> Self {
|
||||
/// `dimdli` — DimStyle stacking increment (use [`DEFAULT_DIMDLI`] when no style is active).
|
||||
pub fn from_base(
|
||||
p1: Vec3,
|
||||
_p2: Vec3,
|
||||
definition_point: Vec3,
|
||||
rotation: f64,
|
||||
dimdli: f32,
|
||||
) -> Self {
|
||||
let axis = if rotation.abs() < 0.1 {
|
||||
Vec3::X
|
||||
} else {
|
||||
@@ -68,13 +78,19 @@ impl DimBaselineCommand {
|
||||
};
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let base_offset = (definition_point - p1).dot(perp);
|
||||
let dimdli = if dimdli.abs() < 1e-6 {
|
||||
DEFAULT_DIMDLI
|
||||
} else {
|
||||
dimdli
|
||||
};
|
||||
// Next baseline dim goes one DIMDLI further from the baseline.
|
||||
let next_offset = base_offset + DIMDLI;
|
||||
let next_offset = base_offset + dimdli;
|
||||
Self {
|
||||
base_p1: p1,
|
||||
rotation,
|
||||
perp,
|
||||
next_offset,
|
||||
dimdli,
|
||||
ready: true,
|
||||
}
|
||||
}
|
||||
@@ -113,7 +129,7 @@ impl CadCommand for DimBaselineCommand {
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
|
||||
// Stack the next dim line further out.
|
||||
self.next_offset += DIMDLI;
|
||||
self.next_offset += self.dimdli;
|
||||
|
||||
CmdResult::CommitEntity(EntityType::Dimension(Dimension::Linear(dim)))
|
||||
}
|
||||
|
||||
+112
-18
@@ -146,6 +146,7 @@ pub fn tessellate(
|
||||
// (leader, text, frame, fill) with distinct colors.
|
||||
if let EntityType::Leader(leader) = entity {
|
||||
return tessellate_leader_single(
|
||||
document,
|
||||
handle,
|
||||
leader,
|
||||
selected,
|
||||
@@ -553,7 +554,7 @@ pub fn tessellate_dimension(
|
||||
(a, a)
|
||||
};
|
||||
|
||||
let geom = dimension_geometry(
|
||||
let mut geom = dimension_geometry(
|
||||
dim,
|
||||
&arrow1,
|
||||
&arrow2,
|
||||
@@ -576,6 +577,29 @@ pub fn tessellate_dimension(
|
||||
world_offset,
|
||||
);
|
||||
|
||||
// DIMTMOVE = 1: when the saved text_middle_point sits far from the
|
||||
// dim-line anchor, draw a short leader connecting them. (=0 anchors text
|
||||
// to the dim line — no leader; =2 frees text without a leader.)
|
||||
if let Some(s) = style {
|
||||
if s.dimtmove == 1 {
|
||||
if let Some((anchor, txt)) = dimtmove_leader_endpoints(dim, world_offset) {
|
||||
let gap = dim_txt as f32 * 0.5;
|
||||
if (txt - anchor).length() > gap * 2.0 {
|
||||
add_segment(&mut geom.dim_lines, anchor, txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
// DIMTOFL / DIMTIX / DIMATFIT / DIMUPT control autofit behaviour at
|
||||
// dim *creation*. At render time we honour the saved text and arrow
|
||||
// positions, so reading them here is a no-op — they shape geometry
|
||||
// upstream rather than here.
|
||||
let _ = (s.dimtofl, s.dimtix, s.dimatfit, s.dimupt);
|
||||
// DIMTXTDIRECTION (RTL) needs per-instance text mirroring on the Text
|
||||
// entity, which the current text struct can't carry. Tracked: read
|
||||
// and ignore so the file round-trips on save.
|
||||
let _ = s.dimtxtdirection;
|
||||
}
|
||||
|
||||
// Per-spec colours: DIMCLRD (dim/arrows), DIMCLRE (ext), DIMCLRT (text).
|
||||
// 0=ByBlock and 256=ByLayer fall through to entity_color.
|
||||
let dim_color = if selected {
|
||||
@@ -831,6 +855,47 @@ fn split_ext_lines(points: &[[f32; 3]]) -> (Vec<[f32; 3]>, Vec<[f32; 3]>) {
|
||||
(first, rest)
|
||||
}
|
||||
|
||||
/// Endpoints for the DIMTMOVE=1 leader: (anchor on the dim line, saved
|
||||
/// text_middle_point). Returns None when the dim has no saved text position
|
||||
/// or has no well-defined dim-line midpoint (radius/diameter handled by
|
||||
/// their own leg).
|
||||
fn dimtmove_leader_endpoints(
|
||||
dim: &Dimension,
|
||||
world_offset: [f64; 3],
|
||||
) -> Option<(Vec3, Vec3)> {
|
||||
let base = dim.base();
|
||||
let txt = base.text_middle_point;
|
||||
if txt.x * txt.x + txt.y * txt.y + txt.z * txt.z <= 1e-16 {
|
||||
return None;
|
||||
}
|
||||
let lv = |v| vec3_local(v, world_offset);
|
||||
let anchor = match dim {
|
||||
Dimension::Linear(d) => {
|
||||
let perp = Vec3::new(-(d.rotation.sin() as f32), d.rotation.cos() as f32, 0.0);
|
||||
let first = lv(d.first_point);
|
||||
let second = lv(d.second_point);
|
||||
let def = lv(d.definition_point);
|
||||
let off1 = def.dot(perp) - first.dot(perp);
|
||||
let off2 = def.dot(perp) - second.dot(perp);
|
||||
(first + perp * off1 + second + perp * off2) * 0.5
|
||||
}
|
||||
Dimension::Aligned(d) => {
|
||||
let first = lv(d.first_point);
|
||||
let second = lv(d.second_point);
|
||||
let axis = normalized_or(second - first, Vec3::X);
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let def = lv(d.definition_point);
|
||||
let off1 = def.dot(perp) - first.dot(perp);
|
||||
let off2 = def.dot(perp) - second.dot(perp);
|
||||
(first + perp * off1 + second + perp * off2) * 0.5
|
||||
}
|
||||
Dimension::Radius(d) => lv(d.definition_point),
|
||||
Dimension::Diameter(d) => (lv(d.angle_vertex) + lv(d.definition_point)) * 0.5,
|
||||
_ => return None,
|
||||
};
|
||||
Some((anchor, lv(txt)))
|
||||
}
|
||||
|
||||
/// Build a rectangle of filled triangles sitting under the dim text, used
|
||||
/// when DIMTFILL = 2 (explicit fill colour). The rect width is estimated
|
||||
/// from the formatted text length × character-cell width; an absolutely
|
||||
@@ -1022,6 +1087,7 @@ impl DimGeom {
|
||||
}
|
||||
|
||||
fn tessellate_leader_single(
|
||||
document: &CadDocument,
|
||||
handle: Handle,
|
||||
leader: &Leader,
|
||||
selected: bool,
|
||||
@@ -1066,6 +1132,7 @@ fn tessellate_leader_single(
|
||||
let mut points: Vec<[f32; 3]> = verts.iter().map(|v| p3(v)).collect();
|
||||
let mut tangents: Vec<TangentGeom> = Vec::new();
|
||||
let key_vertices: Vec<[f32; 3]> = verts.iter().map(|v| p3(v)).collect();
|
||||
let mut fill_tris: Vec<[f32; 3]> = Vec::new();
|
||||
|
||||
for i in 0..verts.len().saturating_sub(1) {
|
||||
tangents.push(TangentGeom::Line {
|
||||
@@ -1075,28 +1142,55 @@ fn tessellate_leader_single(
|
||||
}
|
||||
|
||||
if leader.arrow_enabled {
|
||||
// Resolve the active dim style → DIMLDRBLK to pick the arrow shape.
|
||||
// DIMASZ × DIMSCALE drives the size when available; otherwise fall
|
||||
// back to the legacy text-height heuristic.
|
||||
let style = document.dim_styles.iter().find(|s| {
|
||||
s.name.eq_ignore_ascii_case(&leader.dimension_style)
|
||||
|| (leader.dimension_style.trim().is_empty()
|
||||
&& s.name.eq_ignore_ascii_case("Standard"))
|
||||
});
|
||||
let dim_scale = style
|
||||
.map(|s| {
|
||||
if s.dimscale > 1e-6 {
|
||||
s.dimscale
|
||||
} else {
|
||||
anno_scale as f64
|
||||
}
|
||||
})
|
||||
.unwrap_or(anno_scale as f64);
|
||||
let arrow_size = match style {
|
||||
Some(s) => (s.dimasz * dim_scale) as f32,
|
||||
None => (leader.text_height as f32).max(1.0) * 0.8 * anno_scale,
|
||||
};
|
||||
let arrow = match style {
|
||||
Some(s) => arrow_from_block(document, s.dimldrblk, arrow_size.max(0.001)),
|
||||
None => ArrowKind::Triangle {
|
||||
size: arrow_size.max(0.001),
|
||||
filled: true,
|
||||
size_mul: 1.0,
|
||||
},
|
||||
};
|
||||
|
||||
let tip = &verts[0];
|
||||
let next = &verts[1];
|
||||
let dx = (next.x - tip.x) as f32;
|
||||
let dy = (next.y - tip.y) as f32;
|
||||
let len = (dx * dx + dy * dy).sqrt().max(1e-9);
|
||||
let (dx, dy) = (dx / len, dy / len);
|
||||
let sz = (leader.text_height as f32).max(1.0) * 0.8 * anno_scale;
|
||||
let a = std::f32::consts::PI / 6.0;
|
||||
let (s, c) = a.sin_cos();
|
||||
let dir = Vec3::new(dx / len, dy / len, 0.0);
|
||||
let tip_f = p3(tip);
|
||||
points.push(nan);
|
||||
points.push([
|
||||
tip_f[0] + (dx * c - dy * s) * sz,
|
||||
tip_f[1] + (dx * s + dy * c) * sz,
|
||||
tip_f[2],
|
||||
]);
|
||||
points.push(tip_f);
|
||||
points.push([
|
||||
tip_f[0] + (dx * c + dy * s) * sz,
|
||||
tip_f[1] + (-dx * s + dy * c) * sz,
|
||||
tip_f[2],
|
||||
]);
|
||||
let tip_v = Vec3::new(tip_f[0], tip_f[1], tip_f[2]);
|
||||
// Reuse the dim arrow emitter so the leader shape matches the
|
||||
// DIMSTYLE in use (Closed Filled by default, Dot, Tick, …).
|
||||
let mut arrow_pts: Vec<[f32; 3]> = Vec::new();
|
||||
let mut arrow_geom = DimGeom::new();
|
||||
append_arrow(&mut arrow_geom, tip_v, dir, &arrow);
|
||||
if !arrow_geom.dim_lines.is_empty() {
|
||||
arrow_pts.push(nan);
|
||||
arrow_pts.extend(arrow_geom.dim_lines);
|
||||
}
|
||||
points.extend(arrow_pts);
|
||||
fill_tris.extend(arrow_geom.arrow_fill);
|
||||
}
|
||||
|
||||
if leader.hookline_enabled {
|
||||
@@ -1129,7 +1223,7 @@ fn tessellate_leader_single(
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
vp_scissor: None,
|
||||
fill_tris: vec![],
|
||||
fill_tris,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user