feat(scene): integrate the last Dimension + DimStyle fields
Closes out the file-driven dim render. Remaining entity fields and the last handful of DimStyle knobs are now honoured: - leader_length: extend the radius/diameter dim line past the arrow tip toward the text by this distance - ext_line_rotation: rotate extension lines off perpendicular (DIMEDIT Oblique result) for Linear / Aligned dims - attachment_point: map the 1..9 grid to Text horizontal / vertical alignment so the saved anchor is used - horizontal_direction: use as in-plane text reading direction override - DIMTXSTY by handle: prefer dimtxsty_handle over the name; survives text-style renames - DIMTFAC + DIMTOLJ: tolerance text rendered as a separate Text entity scaled by DIMTFAC and aligned vertically by DIMTOLJ - DIMALTTD + DIMALTTZ: alternate-units tolerance suffix formatted with these decimal-place / zero-suppression knobs - DIMARCSYM / DIMJOGANG / DIMUNIT / normal / line_spacing_factor / insertion_point / block_name / version: read so the file round-trips but no-op in the render path until the matching dim variants / 3D text plane are supported Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0f12c289c8
commit
e59917f798
1 changed files with 270 additions and 56 deletions
|
|
@ -598,7 +598,26 @@ pub fn tessellate_dimension(
|
|||
// entity, which the current text struct can't carry. Tracked: read
|
||||
// and ignore so the file round-trips on save.
|
||||
let _ = s.dimtxtdirection;
|
||||
// DIMARCSYM only applies to arc-length dims; DIMJOGANG only to
|
||||
// jogged-radius dims. We don't ship those Dimension variants yet,
|
||||
// so the values are read for round-trip but not drawn.
|
||||
let _ = (s.dimarcsym, s.dimjogang);
|
||||
// DIMUNIT is the obsolete pre-R2000 linear unit format; DIMLUNIT
|
||||
// supersedes it. Read but not honoured.
|
||||
let _ = s.dimunit;
|
||||
}
|
||||
// Dimension entity fields that the render path doesn't yet use but are
|
||||
// preserved on save:
|
||||
// - base.insertion_point: legacy anchor reference; render uses
|
||||
// text_middle_point + dim-line geometry instead.
|
||||
// - base.block_name: AutoCAD-style "*D..." anonymous block name for
|
||||
// the dim graphics — we re-tessellate so don't need it.
|
||||
// - base.version: DXF format marker (metadata only).
|
||||
let _ = (
|
||||
dim.base().insertion_point,
|
||||
&dim.base().block_name,
|
||||
dim.base().version,
|
||||
);
|
||||
|
||||
// Per-spec colours: DIMCLRD (dim/arrows), DIMCLRE (ext), DIMCLRT (text).
|
||||
// 0=ByBlock and 256=ByLayer fall through to entity_color.
|
||||
|
|
@ -767,7 +786,10 @@ pub fn tessellate_dimension(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(text) = dimension_text_entity(dim, dim_txt, style) {
|
||||
if let Some(text) = dimension_text_entity(dim, dim_txt, style, document) {
|
||||
// Tolerance Text rendered separately so DIMTFAC scales its height
|
||||
// and DIMTOLJ aligns it vertically against the primary text.
|
||||
let tol_entity = dimension_tolerance_entity(dim, style, &text, dim_txt);
|
||||
let mut wire = tessellate(
|
||||
document,
|
||||
handle,
|
||||
|
|
@ -782,8 +804,25 @@ pub fn tessellate_dimension(
|
|||
// let the inner tessellate re-apply anno_scale.
|
||||
1.0,
|
||||
);
|
||||
wire.name = name;
|
||||
wire.name = name.clone();
|
||||
wires.push(wire);
|
||||
|
||||
if let Some(tol_text) = tol_entity {
|
||||
let mut tol_wire = tessellate(
|
||||
document,
|
||||
handle,
|
||||
&EntityType::Text(tol_text),
|
||||
selected,
|
||||
text_color,
|
||||
0.0,
|
||||
[0.0; 8],
|
||||
line_weight_px,
|
||||
world_offset,
|
||||
1.0,
|
||||
);
|
||||
tol_wire.name = name;
|
||||
wires.push(tol_wire);
|
||||
}
|
||||
}
|
||||
|
||||
wires
|
||||
|
|
@ -2049,7 +2088,16 @@ fn dimension_geometry(
|
|||
let def = lv(d.definition_point);
|
||||
let axis = normalized_or(second - first, Vec3::X);
|
||||
append_linear_dimension(
|
||||
&mut g, first, second, def, axis, arrow1, arrow2, params, suppress,
|
||||
&mut g,
|
||||
first,
|
||||
second,
|
||||
def,
|
||||
axis,
|
||||
arrow1,
|
||||
arrow2,
|
||||
params,
|
||||
suppress,
|
||||
d.ext_line_rotation as f32,
|
||||
);
|
||||
}
|
||||
Dimension::Linear(d) => {
|
||||
|
|
@ -2067,6 +2115,7 @@ fn dimension_geometry(
|
|||
arrow2,
|
||||
params,
|
||||
suppress,
|
||||
d.ext_line_rotation as f32,
|
||||
);
|
||||
}
|
||||
Dimension::Radius(d) => {
|
||||
|
|
@ -2074,7 +2123,15 @@ fn dimension_geometry(
|
|||
let point = lv(d.definition_point);
|
||||
let text = dimension_text_position(dim, world_offset);
|
||||
add_segment(&mut g.dim_lines, center, point);
|
||||
add_segment(&mut g.dim_lines, point, text);
|
||||
// Honour leader_length: extend from the arrow tip past it
|
||||
// toward the text by that distance along (text - point).
|
||||
let leader_dir = normalized_or(text - point, Vec3::X);
|
||||
let leader = if d.leader_length.abs() > 1e-9 {
|
||||
point + leader_dir * (d.leader_length as f32)
|
||||
} else {
|
||||
text
|
||||
};
|
||||
add_segment(&mut g.dim_lines, point, leader);
|
||||
append_arrow(&mut g, point, normalized_or(center - point, Vec3::X), arrow1);
|
||||
let radius = (point - center).length();
|
||||
append_center_mark(&mut g, center, params.dimcen, radius);
|
||||
|
|
@ -2085,6 +2142,16 @@ fn dimension_geometry(
|
|||
add_segment(&mut g.dim_lines, p1, p2);
|
||||
append_arrow(&mut g, p1, normalized_or(p2 - p1, Vec3::X), arrow1);
|
||||
append_arrow(&mut g, p2, normalized_or(p1 - p2, Vec3::X), arrow2);
|
||||
// DIMETER leader: continue past p2 toward the text.
|
||||
if d.leader_length.abs() > 1e-9 {
|
||||
let text = dimension_text_position(dim, world_offset);
|
||||
let leader_dir = normalized_or(text - p2, p2 - p1);
|
||||
add_segment(
|
||||
&mut g.dim_lines,
|
||||
p2,
|
||||
p2 + leader_dir * (d.leader_length as f32),
|
||||
);
|
||||
}
|
||||
let radius = (p2 - p1).length() * 0.5;
|
||||
append_center_mark(&mut g, (p1 + p2) * 0.5, params.dimcen, radius);
|
||||
}
|
||||
|
|
@ -2136,6 +2203,7 @@ fn append_linear_dimension(
|
|||
arrow2: &ArrowKind,
|
||||
params: DimLineParams,
|
||||
suppress: SuppressFlags,
|
||||
ext_line_rotation: f32,
|
||||
) {
|
||||
let perp = Vec3::new(-axis.y, axis.x, 0.0);
|
||||
let dim_line_pos = def.dot(perp);
|
||||
|
|
@ -2146,22 +2214,35 @@ fn append_linear_dimension(
|
|||
let sign1 = if offset1 >= 0.0 { 1.0_f32 } else { -1.0 };
|
||||
let sign2 = if offset2 >= 0.0 { 1.0_f32 } else { -1.0 };
|
||||
|
||||
// ext_line_rotation (DIMEDIT "Oblique"): rotate the extension lines by
|
||||
// this angle relative to perpendicular. The ext line still starts at
|
||||
// the def point; only the direction differs.
|
||||
let ext_dir = if ext_line_rotation.abs() > 1e-6 {
|
||||
let c = ext_line_rotation.cos();
|
||||
let s = ext_line_rotation.sin();
|
||||
// Rotate `perp` by ext_line_rotation around Z.
|
||||
Vec3::new(perp.x * c - perp.y * s, perp.x * s + perp.y * c, 0.0)
|
||||
} else {
|
||||
perp
|
||||
};
|
||||
|
||||
// DIMFXLON / DIMFXL: fixed extension-line length from the dim line back
|
||||
// toward (but not past) the definition point. Otherwise grow from the
|
||||
// def point with DIMEXO gap, extending DIMEXE past the dim line.
|
||||
// When oblique, lengths are measured along ext_dir instead of perp.
|
||||
let (ext1_start, ext1_end, ext2_start, ext2_end) = if params.dimfxlon {
|
||||
let fxl = params.dimfxl.max(0.0);
|
||||
let s1 = d1 - perp * (sign1 * fxl);
|
||||
let e1 = d1 + perp * (sign1 * params.dimexe);
|
||||
let s2 = d2 - perp * (sign2 * fxl);
|
||||
let e2 = d2 + perp * (sign2 * params.dimexe);
|
||||
let s1 = d1 - ext_dir * (sign1 * fxl);
|
||||
let e1 = d1 + ext_dir * (sign1 * params.dimexe);
|
||||
let s2 = d2 - ext_dir * (sign2 * fxl);
|
||||
let e2 = d2 + ext_dir * (sign2 * params.dimexe);
|
||||
(s1, e1, s2, e2)
|
||||
} else {
|
||||
(
|
||||
first + perp * (sign1 * params.dimexo),
|
||||
d1 + perp * (sign1 * params.dimexe),
|
||||
second + perp * (sign2 * params.dimexo),
|
||||
d2 + perp * (sign2 * params.dimexe),
|
||||
first + ext_dir * (sign1 * params.dimexo),
|
||||
d1 + ext_dir * (sign1 * params.dimexe),
|
||||
second + ext_dir * (sign2 * params.dimexo),
|
||||
d2 + ext_dir * (sign2 * params.dimexe),
|
||||
)
|
||||
};
|
||||
if !suppress.ext1 {
|
||||
|
|
@ -2465,6 +2546,7 @@ fn dimension_text_entity(
|
|||
dim: &Dimension,
|
||||
text_height: f64,
|
||||
style: Option<&DimStyle>,
|
||||
document: &CadDocument,
|
||||
) -> Option<Text> {
|
||||
let value = dimension_text_value(dim, style)?;
|
||||
// Use f64 position directly to avoid f32 round-trip precision loss at large
|
||||
|
|
@ -2474,11 +2556,16 @@ fn dimension_text_entity(
|
|||
let base = dim.base();
|
||||
|
||||
// DIMTIH/DIMTOH: when set, text is forced horizontal (rotation = 0)
|
||||
// regardless of the dim line angle. Honour explicit base.text_rotation first.
|
||||
// regardless of the dim line angle. Honour explicit base.text_rotation
|
||||
// first, then horizontal_direction override, then DIMTIH/DIMTOH.
|
||||
let dimtih = style.map(|s| s.dimtih).unwrap_or(false);
|
||||
let dimtoh = style.map(|s| s.dimtoh).unwrap_or(false);
|
||||
let rotation = if base.text_rotation.abs() > 1e-9 {
|
||||
base.text_rotation
|
||||
} else if base.horizontal_direction.abs() > 1e-9 {
|
||||
// horizontal_direction is the in-plane reading direction the writer
|
||||
// baked in (used for vertical / oblique dims).
|
||||
base.horizontal_direction
|
||||
} else if dimtih || dimtoh {
|
||||
0.0
|
||||
} else {
|
||||
|
|
@ -2488,21 +2575,68 @@ fn dimension_text_entity(
|
|||
let mut text = Text::with_value(value, pos_f64)
|
||||
.with_height(text_height)
|
||||
.with_rotation(rotation);
|
||||
// Prefer the dim style's text style (DIMTXSTY) over the dim's own style_name
|
||||
// (which is the *dim style* name, not the text style).
|
||||
if let Some(s) = style {
|
||||
if !s.dimtxsty.trim().is_empty() {
|
||||
text.style = s.dimtxsty.clone();
|
||||
} else {
|
||||
text.style = base.style_name.clone();
|
||||
}
|
||||
} else {
|
||||
text.style = base.style_name.clone();
|
||||
}
|
||||
|
||||
// Text style resolution priority:
|
||||
// 1. DIMTXSTY by handle (most reliable; survives rename)
|
||||
// 2. DIMTXSTY by name
|
||||
// 3. dim's own style_name (rare fallback)
|
||||
text.style = style
|
||||
.and_then(|s| {
|
||||
if !s.dimtxsty_handle.is_null() {
|
||||
document
|
||||
.text_styles
|
||||
.iter()
|
||||
.find(|ts| ts.handle == s.dimtxsty_handle)
|
||||
.map(|ts| ts.name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
style
|
||||
.map(|s| s.dimtxsty.clone())
|
||||
.filter(|n| !n.trim().is_empty())
|
||||
})
|
||||
.unwrap_or_else(|| base.style_name.clone());
|
||||
|
||||
// Map AttachmentPointType (1..9 grid) to Text horizontal + vertical
|
||||
// alignments. 1=TopLeft … 9=BottomRight (column-major).
|
||||
let (ha, va) = attachment_to_text_align(base.attachment_point);
|
||||
text.horizontal_alignment = ha;
|
||||
text.vertical_alignment = va;
|
||||
// line_spacing_factor controls multi-line text spacing in MText. Our
|
||||
// synthetic Text is single-line so this is a no-op, but pass through
|
||||
// for completeness.
|
||||
let _ = base.line_spacing_factor;
|
||||
// normal would rotate the dim plane out of XY. The local 2D pipeline
|
||||
// assumes XY, so non-XY normals are read but not applied here.
|
||||
let _ = base.normal;
|
||||
|
||||
text.common = base.common.clone();
|
||||
Some(text)
|
||||
}
|
||||
|
||||
fn attachment_to_text_align(
|
||||
attach: acadrust::entities::dimension::AttachmentPointType,
|
||||
) -> (
|
||||
acadrust::entities::text::TextHorizontalAlignment,
|
||||
acadrust::entities::text::TextVerticalAlignment,
|
||||
) {
|
||||
use acadrust::entities::dimension::AttachmentPointType as A;
|
||||
use acadrust::entities::text::{TextHorizontalAlignment as H, TextVerticalAlignment as V};
|
||||
match attach {
|
||||
A::TopLeft => (H::Left, V::Top),
|
||||
A::TopCenter => (H::Center, V::Top),
|
||||
A::TopRight => (H::Right, V::Top),
|
||||
A::MiddleLeft => (H::Left, V::Middle),
|
||||
A::MiddleCenter => (H::Center, V::Middle),
|
||||
A::MiddleRight => (H::Right, V::Middle),
|
||||
A::BottomLeft => (H::Left, V::Bottom),
|
||||
A::BottomCenter => (H::Center, V::Bottom),
|
||||
A::BottomRight => (H::Right, V::Bottom),
|
||||
}
|
||||
}
|
||||
|
||||
fn dimension_text_natural_rotation(dim: &Dimension) -> f64 {
|
||||
let angle = match dim {
|
||||
Dimension::Linear(d) => d.rotation,
|
||||
|
|
@ -2525,12 +2659,29 @@ fn dimension_text_natural_rotation(dim: &Dimension) -> f64 {
|
|||
}
|
||||
|
||||
fn dimension_text_value(dim: &Dimension, style: Option<&DimStyle>) -> Option<String> {
|
||||
let (main, tol) = dimension_text_parts(dim, style)?;
|
||||
// Tolerance is appended inline for callers (e.g. fill rect width) that
|
||||
// don't render a separate tolerance entity. The visual pipeline that
|
||||
// does emit a separate tolerance text re-derives the parts itself.
|
||||
match tol {
|
||||
Some(t) => Some(format!("{} {}", main, t)),
|
||||
None => Some(main),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns (primary_text, tolerance_suffix). The tolerance is emitted as a
|
||||
/// separate Text entity so DIMTFAC can scale its height and DIMTOLJ can
|
||||
/// align it vertically against the primary value.
|
||||
fn dimension_text_parts(
|
||||
dim: &Dimension,
|
||||
style: Option<&DimStyle>,
|
||||
) -> Option<(String, Option<String>)> {
|
||||
let base = dim.base();
|
||||
let is_angular = matches!(dim, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_));
|
||||
|
||||
// Auto-generated body (the value AutoCAD would emit if the user did not
|
||||
// override it). Built first so user_text "<>" substitution can re-use it.
|
||||
let primary = if is_angular {
|
||||
let primary_raw = if is_angular {
|
||||
format_angular_value(dim.measurement(), style)
|
||||
} else {
|
||||
let v = format_linear_value(dim.measurement(), style);
|
||||
|
|
@ -2541,13 +2692,13 @@ fn dimension_text_value(dim: &Dimension, style: Option<&DimStyle>) -> Option<Str
|
|||
}
|
||||
};
|
||||
|
||||
// Tolerances / limits — applied to the primary value before DIMPOST wraps.
|
||||
let primary = apply_tolerance(primary, dim.measurement(), style, is_angular);
|
||||
// DIMPOST: "<>" template (prefix/suffix wrap).
|
||||
let primary = apply_dimpost(&primary, style);
|
||||
// Build tolerance / limits suffix separately so the caller can render
|
||||
// it as its own Text entity at DIMTFAC × DIMTXT height.
|
||||
let tolerance_suffix = build_tolerance_suffix(dim.measurement(), style, is_angular);
|
||||
let primary = apply_dimpost(&primary_raw, style);
|
||||
|
||||
// Alternate units appended in brackets when DIMALT is on (linear only).
|
||||
let composed = if !is_angular {
|
||||
let primary = if !is_angular {
|
||||
match alternate_units_text(dim.measurement(), style) {
|
||||
Some(alt) => format!("{} [{}]", primary, alt),
|
||||
None => primary,
|
||||
|
|
@ -2562,24 +2713,20 @@ fn dimension_text_value(dim: &Dimension, style: Option<&DimStyle>) -> Option<Str
|
|||
if user_text.is_empty() || user_text.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
return Some(user_text.replace("<>", &composed));
|
||||
return Some((user_text.replace("<>", &primary), tolerance_suffix));
|
||||
}
|
||||
if !base.text.trim().is_empty() {
|
||||
return Some(base.text.replace("<>", &composed));
|
||||
return Some((base.text.replace("<>", &primary), tolerance_suffix));
|
||||
}
|
||||
Some(composed)
|
||||
Some((primary, tolerance_suffix))
|
||||
}
|
||||
|
||||
/// Append tolerance text per DIMTOL / DIMLIM:
|
||||
/// DIMLIM true → replace value with "high/low" limits stacked
|
||||
/// DIMTOL true → "value +tp/-tm" (or "value±t" when tp==tm)
|
||||
fn apply_tolerance(
|
||||
value: String,
|
||||
fn build_tolerance_suffix(
|
||||
measurement: f64,
|
||||
style: Option<&DimStyle>,
|
||||
is_angular: bool,
|
||||
) -> String {
|
||||
let Some(s) = style else { return value };
|
||||
) -> Option<String> {
|
||||
let s = style?;
|
||||
let dimtdec = s.dimtdec.max(0) as usize;
|
||||
let dimtzin = s.dimtzin;
|
||||
let fmt = |v: f64| -> String {
|
||||
|
|
@ -2587,31 +2734,25 @@ fn apply_tolerance(
|
|||
apply_linear_zero_suppression(&raw, dimtzin)
|
||||
};
|
||||
if s.dimlim {
|
||||
// Replace value entirely with high/low limits.
|
||||
let high = measurement + s.dimtp;
|
||||
let low = measurement - s.dimtm;
|
||||
return format!("{}/{}", fmt(high), fmt(low));
|
||||
return Some(format!("{}/{}", fmt(high), fmt(low)));
|
||||
}
|
||||
if s.dimtol {
|
||||
let unit = if is_angular { "°" } else { "" };
|
||||
if (s.dimtp - s.dimtm).abs() < 1e-12 && s.dimtp.abs() > 1e-12 {
|
||||
return format!("{}±{}{}", value, fmt(s.dimtp), unit);
|
||||
return Some(format!("±{}{}", fmt(s.dimtp), unit));
|
||||
}
|
||||
if s.dimtp.abs() > 1e-12 || s.dimtm.abs() > 1e-12 {
|
||||
return format!(
|
||||
"{} +{}{} / -{}{}",
|
||||
value,
|
||||
fmt(s.dimtp),
|
||||
unit,
|
||||
fmt(s.dimtm),
|
||||
unit
|
||||
);
|
||||
return Some(format!("+{}{} / -{}{}", fmt(s.dimtp), unit, fmt(s.dimtm), unit));
|
||||
}
|
||||
}
|
||||
value
|
||||
None
|
||||
}
|
||||
|
||||
/// Build the bracketed alternate-units suffix when DIMALT is enabled.
|
||||
/// When DIMTOL is also on, the bracketed text includes the tolerance
|
||||
/// component formatted with DIMALTTD / DIMALTTZ.
|
||||
fn alternate_units_text(measurement: f64, style: Option<&DimStyle>) -> Option<String> {
|
||||
let s = style?;
|
||||
if !s.dimalt {
|
||||
|
|
@ -2625,17 +2766,90 @@ fn alternate_units_text(measurement: f64, style: Option<&DimStyle>) -> Option<St
|
|||
let raw = format_with_unit(v, s.dimaltu, dec, s.dimfrac);
|
||||
let suppressed = apply_linear_zero_suppression(&raw, s.dimaltz);
|
||||
let sep_swapped = swap_decimal_sep(&suppressed, s.dimdsep);
|
||||
// Alt-unit tolerance suffix using DIMALTTD / DIMALTTZ.
|
||||
let alt_value = if s.dimtol {
|
||||
let alttdec = s.dimalttd.max(0) as usize;
|
||||
let alttzin = s.dimalttz;
|
||||
let fmt = |x: f64| -> String {
|
||||
let raw = format!("{:.*}", alttdec, x * s.dimaltf);
|
||||
swap_decimal_sep(&apply_linear_zero_suppression(&raw, alttzin), s.dimdsep)
|
||||
};
|
||||
if (s.dimtp - s.dimtm).abs() < 1e-12 && s.dimtp.abs() > 1e-12 {
|
||||
format!("{}±{}", sep_swapped, fmt(s.dimtp))
|
||||
} else if s.dimtp.abs() > 1e-12 || s.dimtm.abs() > 1e-12 {
|
||||
format!("{} +{} / -{}", sep_swapped, fmt(s.dimtp), fmt(s.dimtm))
|
||||
} else {
|
||||
sep_swapped
|
||||
}
|
||||
} else if s.dimlim {
|
||||
let alttdec = s.dimalttd.max(0) as usize;
|
||||
let alttzin = s.dimalttz;
|
||||
let fmt = |x: f64| -> String {
|
||||
let raw = format!("{:.*}", alttdec, x * s.dimaltf);
|
||||
swap_decimal_sep(&apply_linear_zero_suppression(&raw, alttzin), s.dimdsep)
|
||||
};
|
||||
format!("{}/{}", fmt(measurement + s.dimtp), fmt(measurement - s.dimtm))
|
||||
} else {
|
||||
sep_swapped
|
||||
};
|
||||
// DIMAPOST wraps the alt value (same "<>" convention as DIMPOST).
|
||||
let wrapped = if s.dimapost.is_empty() {
|
||||
sep_swapped
|
||||
alt_value
|
||||
} else if s.dimapost.contains("<>") {
|
||||
s.dimapost.replace("<>", &sep_swapped)
|
||||
s.dimapost.replace("<>", &alt_value)
|
||||
} else {
|
||||
format!("{}{}", sep_swapped, s.dimapost)
|
||||
format!("{}{}", alt_value, s.dimapost)
|
||||
};
|
||||
Some(wrapped)
|
||||
}
|
||||
|
||||
/// Build the secondary tolerance Text entity at `DIMTXT × DIMTFAC` height,
|
||||
/// positioned to the right of the primary text and vertically aligned per
|
||||
/// `DIMTOLJ` (0=bottom, 1=middle, 2=top). Returns None when DIMTOL/DIMLIM
|
||||
/// produce no tolerance string (e.g. both DIMTP and DIMTM are zero).
|
||||
fn dimension_tolerance_entity(
|
||||
dim: &Dimension,
|
||||
style: Option<&DimStyle>,
|
||||
primary: &Text,
|
||||
primary_height: f64,
|
||||
) -> Option<Text> {
|
||||
let s = style?;
|
||||
let is_angular = matches!(dim, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_));
|
||||
let tol = build_tolerance_suffix(dim.measurement(), style, is_angular)?;
|
||||
let dimtfac = if s.dimtfac.abs() < 1e-12 {
|
||||
1.0
|
||||
} else {
|
||||
s.dimtfac
|
||||
};
|
||||
let tol_height = primary_height * dimtfac;
|
||||
|
||||
// Approximate widths from glyph counts (~0.6 × cell size per char).
|
||||
let primary_w = primary.value.chars().count() as f64 * primary_height * 0.6;
|
||||
let tol_w = tol.chars().count() as f64 * tol_height * 0.6;
|
||||
let gap = primary_height * 0.2;
|
||||
let dx_local = primary_w * 0.5 + tol_w * 0.5 + gap;
|
||||
let dy_local = match s.dimtolj {
|
||||
0 => -primary_height * 0.5 + tol_height * 0.5, // bottom-aligned with primary baseline
|
||||
2 => primary_height * 0.5 - tol_height * 0.5, // top-aligned with primary top
|
||||
_ => 0.0, // centred (default for ±)
|
||||
};
|
||||
let rot = primary.rotation;
|
||||
let (sr, cr) = rot.sin_cos();
|
||||
let pos = Vector3::new(
|
||||
primary.insertion_point.x + dx_local * cr - dy_local * sr,
|
||||
primary.insertion_point.y + dx_local * sr + dy_local * cr,
|
||||
primary.insertion_point.z,
|
||||
);
|
||||
let mut t = Text::with_value(tol, pos)
|
||||
.with_height(tol_height)
|
||||
.with_rotation(rot);
|
||||
t.style = primary.style.clone();
|
||||
t.common = primary.common.clone();
|
||||
t.horizontal_alignment = acadrust::entities::text::TextHorizontalAlignment::Center;
|
||||
t.vertical_alignment = acadrust::entities::text::TextVerticalAlignment::Middle;
|
||||
Some(t)
|
||||
}
|
||||
|
||||
/// Wrap a measured value with the style's DIMPOST prefix/suffix template.
|
||||
/// "<>" inside DIMPOST is replaced by the value; absent "<>" appends.
|
||||
fn apply_dimpost(value: &str, style: Option<&DimStyle>) -> String {
|
||||
|
|
|
|||
Loading…
Reference in a new issue