2026-04-02 19:06:20 +03:00
|
|
|
|
use acadrust::entities::Tolerance;
|
|
|
|
|
|
|
|
|
|
|
|
use crate::command::EntityTransform;
|
|
|
|
|
|
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
|
|
|
|
|
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
2026-06-16 11:33:46 +03:00
|
|
|
|
use crate::scene::convert::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
|
|
|
|
|
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
|
|
|
|
|
use crate::scene::model::wire_model::SnapHint;
|
|
|
|
|
|
use crate::scene::text::lff;
|
|
|
|
|
|
use crate::scene::view::transform;
|
2026-04-02 19:06:20 +03:00
|
|
|
|
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
// ── GDT text parser ───────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// Parse a DXF tolerance text string into rows of cell strings.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// DXF format: `{\Fgdt;p}%%v0.5%%vA%%vB%%v%%v^J{\Fgdt;j}%%v0.1%%vA%%v%%v%%v`
|
|
|
|
|
|
/// - `^J` → row separator
|
|
|
|
|
|
/// - `%%v` → cell separator within a row
|
|
|
|
|
|
/// - `{\Fgdt;X}` → GDT symbol X (mapped to a text label)
|
|
|
|
|
|
fn parse_gdt_rows(raw: &str) -> Vec<Vec<String>> {
|
|
|
|
|
|
raw.split("^J")
|
|
|
|
|
|
.filter(|row| !row.trim().is_empty())
|
|
|
|
|
|
.map(|row| {
|
|
|
|
|
|
row.split("%%v")
|
|
|
|
|
|
.map(|cell| substitute_gdt_codes(cell.trim()))
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Replace `{\Fgdt;X}` sequences with a short ASCII label and strip other
|
|
|
|
|
|
/// MTEXT-style format codes `{\...}`.
|
|
|
|
|
|
fn substitute_gdt_codes(s: &str) -> String {
|
|
|
|
|
|
let mut out = String::new();
|
|
|
|
|
|
let mut chars = s.chars().peekable();
|
|
|
|
|
|
while let Some(ch) = chars.next() {
|
|
|
|
|
|
if ch == '{' {
|
|
|
|
|
|
// Collect to closing '}'
|
|
|
|
|
|
let mut inner = String::new();
|
|
|
|
|
|
let mut depth = 1usize;
|
|
|
|
|
|
for c in chars.by_ref() {
|
|
|
|
|
|
match c {
|
2026-05-12 10:36:15 +03:00
|
|
|
|
'{' => {
|
|
|
|
|
|
depth += 1;
|
|
|
|
|
|
inner.push(c);
|
|
|
|
|
|
}
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
'}' => {
|
|
|
|
|
|
depth -= 1;
|
2026-05-12 10:36:15 +03:00
|
|
|
|
if depth == 0 {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
inner.push(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
inner.push(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Is it a GDT font switch?
|
|
|
|
|
|
if let Some(rest) = inner.strip_prefix("\\Fgdt;") {
|
|
|
|
|
|
// rest is the symbol letter(s)
|
|
|
|
|
|
if let Some(sym_char) = rest.chars().next() {
|
|
|
|
|
|
out.push_str(gdt_char_to_ascii(sym_char));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// other format codes: skip
|
|
|
|
|
|
} else {
|
|
|
|
|
|
out.push(ch);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
out
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Map a GDT font character to a short ASCII approximation.
|
|
|
|
|
|
fn gdt_char_to_ascii(c: char) -> &'static str {
|
|
|
|
|
|
match c {
|
2026-05-12 10:36:15 +03:00
|
|
|
|
'a' => "SRT", // Straightness
|
|
|
|
|
|
'b' => "FLT", // Flatness
|
|
|
|
|
|
'c' => "FLT", // Flatness
|
|
|
|
|
|
'd' => "PSF", // Profile of Surface
|
|
|
|
|
|
'e' => "CYL", // Cylindricity
|
|
|
|
|
|
'f' => "PRL", // Profile of Line
|
|
|
|
|
|
'g' => "CIR", // Circularity
|
|
|
|
|
|
'h' => "PAR", // Parallelism
|
|
|
|
|
|
'i' => "SYM", // Symmetry
|
|
|
|
|
|
'j' => "PRP", // Perpendicularity
|
|
|
|
|
|
'k' => "PLN", // Profile of Line
|
|
|
|
|
|
'l' => "(L)", // LMC
|
|
|
|
|
|
'm' => "(M)", // MMC / Diameter
|
|
|
|
|
|
'n' => "ANG", // Angularity
|
|
|
|
|
|
'o' => "(o)", // at maximum material boundary
|
|
|
|
|
|
'p' => "POS", // Position
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
'q' => "(q)",
|
2026-05-12 10:36:15 +03:00
|
|
|
|
'r' => "RUN", // Circular Runout
|
|
|
|
|
|
's' => "(S)", // RFS / Regardless of Feature Size
|
|
|
|
|
|
't' => "TRN", // Total Runout
|
|
|
|
|
|
'u' => "CON", // Concentricity
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
'v' => "(v)",
|
|
|
|
|
|
'w' => "(w)",
|
|
|
|
|
|
_ => "?",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Feature-control frame builder ─────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// Tessellate a Tolerance entity into CXF-style polyline output.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Returns (`box_lines`, `text_strokes`) where:
|
|
|
|
|
|
/// - `box_lines` — 3-D line segments forming the outer border and dividers
|
|
|
|
|
|
/// - `text_strokes` — 2-D polylines from the CXF tessellator
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Since TruckObject has either Lines (3-D) or Text (2-D CXF), we render the
|
|
|
|
|
|
/// box frame via a separate Lines path (added by the caller) and the text cells
|
|
|
|
|
|
/// via the Text path. For simplicity this function packs everything into a
|
|
|
|
|
|
/// single Vec<Vec<[f32;2]>> by projecting the box lines to 2-D and using NaN
|
|
|
|
|
|
/// polylines as separators — exactly what the Text wire-builder does.
|
|
|
|
|
|
fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
|
|
|
|
|
|
if tol.text.is_empty() {
|
|
|
|
|
|
return vec![];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let rows = parse_gdt_rows(&tol.text);
|
|
|
|
|
|
if rows.is_empty() {
|
|
|
|
|
|
return vec![];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Metrics ──────────────────────────────────────────────────────────
|
2026-05-12 10:36:15 +03:00
|
|
|
|
let h = if tol.text_height > 1e-6 {
|
|
|
|
|
|
tol.text_height as f32
|
|
|
|
|
|
} else {
|
|
|
|
|
|
2.5_f32
|
|
|
|
|
|
};
|
2026-05-21 12:38:03 +03:00
|
|
|
|
// DIMGAP — stored on the entity (resolved from the dim style at creation).
|
|
|
|
|
|
// Fall back to AutoCAD's 0.35 × height convention only when missing.
|
|
|
|
|
|
let gap = if tol.dimension_gap > 1e-6 {
|
|
|
|
|
|
tol.dimension_gap as f32
|
|
|
|
|
|
} else {
|
|
|
|
|
|
(h * 0.35).max(0.1)
|
|
|
|
|
|
};
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
let cell_h = h + 2.0 * gap;
|
|
|
|
|
|
let char_w = h * 0.65;
|
|
|
|
|
|
let min_cell_w = h * 1.4;
|
|
|
|
|
|
|
|
|
|
|
|
// Column widths: max across all rows
|
|
|
|
|
|
let max_cols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
|
|
|
|
|
|
let mut col_widths: Vec<f32> = vec![0.0_f32; max_cols];
|
|
|
|
|
|
for row in &rows {
|
|
|
|
|
|
for (ci, cell) in row.iter().enumerate() {
|
|
|
|
|
|
let w = (cell.len() as f32 * char_w).max(min_cell_w);
|
|
|
|
|
|
if ci < col_widths.len() {
|
|
|
|
|
|
col_widths[ci] = col_widths[ci].max(w);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
let total_w: f32 = col_widths.iter().sum();
|
|
|
|
|
|
let total_h = cell_h * rows.len() as f32;
|
|
|
|
|
|
|
2026-04-29 20:58:44 +03:00
|
|
|
|
// ── Transform helpers (local space — translation applied in tessellate.rs) ──
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
let angle = (tol.direction.y as f32).atan2(tol.direction.x as f32);
|
|
|
|
|
|
let (sa, ca) = angle.sin_cos();
|
|
|
|
|
|
|
2026-04-29 20:58:44 +03:00
|
|
|
|
// Rotate only; origin is kept as f64 and applied later with full precision.
|
2026-05-12 10:36:15 +03:00
|
|
|
|
let rot = |x: f32, y: f32| -> [f32; 2] { [x * ca - y * sa, x * sa + y * ca] };
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
|
|
|
|
|
|
let mut out: Vec<Vec<[f32; 2]>> = Vec::new();
|
|
|
|
|
|
|
|
|
|
|
|
// ── Outer border ──────────────────────────────────────────────────────
|
|
|
|
|
|
out.push(vec![
|
|
|
|
|
|
rot(0.0, 0.0),
|
|
|
|
|
|
rot(total_w, 0.0),
|
|
|
|
|
|
rot(total_w, total_h),
|
|
|
|
|
|
rot(0.0, total_h),
|
|
|
|
|
|
rot(0.0, 0.0),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// ── Row separators ─────────────────────────────────────────────────────
|
|
|
|
|
|
for ri in 1..rows.len() {
|
|
|
|
|
|
let y = cell_h * ri as f32;
|
|
|
|
|
|
out.push(vec![rot(0.0, y), rot(total_w, y)]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Column dividers ────────────────────────────────────────────────────
|
|
|
|
|
|
let mut x_cursor = 0.0_f32;
|
|
|
|
|
|
for ci in 0..col_widths.len().saturating_sub(1) {
|
|
|
|
|
|
x_cursor += col_widths[ci];
|
|
|
|
|
|
for ri in 0..rows.len() {
|
|
|
|
|
|
if ci + 1 < rows[ri].len() {
|
|
|
|
|
|
let y0 = cell_h * ri as f32;
|
|
|
|
|
|
let y1 = y0 + cell_h;
|
|
|
|
|
|
out.push(vec![rot(x_cursor, y0), rot(x_cursor, y1)]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Text content per cell ─────────────────────────────────────────────
|
|
|
|
|
|
for (ri, row) in rows.iter().enumerate() {
|
|
|
|
|
|
let row_y = cell_h * ri as f32 + gap;
|
|
|
|
|
|
let mut cell_x = 0.0_f32;
|
|
|
|
|
|
for (ci, cell) in row.iter().enumerate() {
|
2026-05-12 10:36:15 +03:00
|
|
|
|
let cw = if ci < col_widths.len() {
|
|
|
|
|
|
col_widths[ci]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
0.0
|
|
|
|
|
|
};
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
if !cell.is_empty() {
|
|
|
|
|
|
let text_w = cell.len() as f32 * char_w;
|
|
|
|
|
|
let tx = cell_x + (cw - text_w) * 0.5;
|
|
|
|
|
|
// Tessellate text in local frame then transform
|
2026-05-12 10:36:15 +03:00
|
|
|
|
let local_strokes =
|
feat(fonts): switch the text engine from CXF to LibreCAD LFF fonts
Replace the small QCAD CXF stroke-font set with the full LibreCAD LFF font
library (46 fonts) and a new LFF parser/engine in src/scene/lff.rs. The
parser reads the `[hex] char` glyph blocks, tessellates `,A<bulge>` arcs, and
resolves `C<hex>` glyph references. Per LibreCAD's RS_Polyline convention a
vertex's bulge curves the segment ending at that vertex — getting this wrong
turned round letters like O into sideways capsules.
Font name resolution mirrors LibreCAD: match by file stem, map common
AutoCAD/DXF SHX names (txt, isocp, complex, italic, gothic*, gost, …) to the
nearest LFF family, and fall back to `standard`. A missing glyph in the chosen
family falls back to the broad `unicode` font so accented/Cyrillic/Greek text
still renders. Cap height stays 9 units, so the text scale is unchanged.
All text consumers move from cxf:: to lff::; complex linetypes still render
embedded text but no longer draw embedded shapes (LibreCAD ships no shape
file). The CXF module and every *.cxf asset are removed, and the MText / text
style font pickers list the LFF families.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 03:31:20 +03:00
|
|
|
|
lff::tessellate_text_ex([0.0, 0.0], h, 0.0, 1.0, 0.0, "txt", cell);
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
for polyline in local_strokes {
|
|
|
|
|
|
let transformed: Vec<[f32; 2]> = polyline
|
|
|
|
|
|
.into_iter()
|
|
|
|
|
|
.map(|[px, py]| rot(px + tx, py + row_y))
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
if !transformed.is_empty() {
|
|
|
|
|
|
out.push(transformed);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
cell_x += cw;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
out
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── TruckConvertible ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-02 19:06:20 +03:00
|
|
|
|
impl TruckConvertible for Tolerance {
|
|
|
|
|
|
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
|
|
|
|
|
if self.text.is_empty() {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
|
refactor(snap): producers emit f64 snap points (world_offset removal P3)
TruckEntity.snap_pts is now DVec3 and every entity producer builds snap
candidates straight from the acadrust f64 coordinates instead of casting
to f32 first (text, mtext, arc, circle, ellipse, point, shape, solid,
mline, mtext, attribute, tolerance, table, underlay, ole2frame, mesh,
multileader, dimension, block-cache). offset_snap_pts stays f64
throughout. Still offset-relative (inert) — but snap precision is no
longer capped at ~0.5 m by an early f32 cast, which fixes a latent
UTM-scale snap error and sets up the absolute-coordinate switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-23 22:10:29 +03:00
|
|
|
|
let snap_pt = glam::DVec3::new(
|
|
|
|
|
|
self.insertion_point.x,
|
|
|
|
|
|
self.insertion_point.y,
|
|
|
|
|
|
self.insertion_point.z,
|
2026-04-02 19:06:20 +03:00
|
|
|
|
);
|
|
|
|
|
|
|
2026-04-29 20:58:44 +03:00
|
|
|
|
// Build the feature-control frame in local space; origin stored as f64.
|
|
|
|
|
|
let strokes = tessellate_tolerance(self);
|
|
|
|
|
|
let origin = [self.insertion_point.x, self.insertion_point.y];
|
2026-04-02 19:06:20 +03:00
|
|
|
|
|
|
|
|
|
|
Some(TruckEntity {
|
2026-06-11 00:04:37 +03:00
|
|
|
|
object: TruckObject::Text(vec![TextStroke {
|
|
|
|
|
|
strokes,
|
|
|
|
|
|
origin,
|
|
|
|
|
|
color: None,
|
|
|
|
|
|
}]),
|
2026-04-02 19:06:20 +03:00
|
|
|
|
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
|
|
|
|
|
tangent_geoms: vec![],
|
|
|
|
|
|
key_vertices: vec![],
|
2026-05-06 23:24:26 +03:00
|
|
|
|
fill_tris: vec![],
|
2026-04-02 19:06:20 +03:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
// ── Grippable ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-02 19:06:20 +03:00
|
|
|
|
impl Grippable for Tolerance {
|
|
|
|
|
|
fn grips(&self) -> Vec<GripDef> {
|
|
|
|
|
|
vec![square_grip(
|
|
|
|
|
|
0,
|
2026-06-11 00:04:37 +03:00
|
|
|
|
glam::DVec3::new(
|
|
|
|
|
|
self.insertion_point.x,
|
|
|
|
|
|
self.insertion_point.y,
|
|
|
|
|
|
self.insertion_point.z,
|
2026-04-02 19:06:20 +03:00
|
|
|
|
),
|
|
|
|
|
|
)]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
|
|
|
|
|
if grip_id == 0 {
|
|
|
|
|
|
match apply {
|
|
|
|
|
|
GripApply::Translate(d) => {
|
|
|
|
|
|
self.insertion_point.x += d.x as f64;
|
|
|
|
|
|
self.insertion_point.y += d.y as f64;
|
|
|
|
|
|
self.insertion_point.z += d.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
GripApply::Absolute(p) => {
|
|
|
|
|
|
self.insertion_point.x = p.x as f64;
|
|
|
|
|
|
self.insertion_point.y = p.y as f64;
|
|
|
|
|
|
self.insertion_point.z = p.z as f64;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
// ── PropertyEditable ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-02 19:06:20 +03:00
|
|
|
|
impl PropertyEditable for Tolerance {
|
|
|
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
|
|
|
|
|
PropSection {
|
|
|
|
|
|
title: "Geometry".into(),
|
|
|
|
|
|
props: vec![
|
|
|
|
|
|
ro("Text", "tol_text", self.text.clone()),
|
|
|
|
|
|
edit("Insert X", "tol_ix", self.insertion_point.x),
|
|
|
|
|
|
edit("Insert Y", "tol_iy", self.insertion_point.y),
|
|
|
|
|
|
edit("Insert Z", "tol_iz", self.insertion_point.z),
|
feat(entities): surface remaining 2D + Region fields in properties
Closes out the 2D-entity audit (and the Region partner that ships next
to Solid3D / Body). Round-trip-only metadata fields plus a few editable
ones gain Properties-panel rows so the values the parser populates are
visible and don't get silently dropped on save:
- Hatch: seed_points count, pixel_size, normal
- Polyline2D: smooth_surface, default_start_width, default_end_width,
thickness, normal (with editors for the widths/thickness)
- Region: uid, silhouettes (shared file with Solid3D / Body)
- Underlay: clip_inverted
- RasterImage: class_version, definition_handle, definition_reactor_handle
- Wipeout: class_version, clip_mode, definition_handle,
definition_reactor_handle
- Leader: annotation_handle, override_color
- Tolerance: dimension_style_name, dimension_style_handle, dimension_gap,
text_height (editable), direction, normal
- MLine: style_handle, style_element_count, justification picker
- Ole2Frame: version, source_application, binary_data size, dwg_mode,
dwg_trailing_byte, is_paper_space
- Viewport: snap_base/spacing/angle, twist_angle, front/back clip Z,
circle_sides, grid_flags, grid_major, base_ucs_handle, ucs_ortho_type,
background_handle, shade_plot_handle, visual_style_handle,
default_lighting, default_lighting_type, ambient_color (with editors
for the snap / clip / circle_sides values)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:28:16 +03:00
|
|
|
|
ro(
|
|
|
|
|
|
"Dim Style",
|
|
|
|
|
|
"tol_dim_style",
|
|
|
|
|
|
if self.dimension_style_name.is_empty() {
|
|
|
|
|
|
"(default)".to_string()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
self.dimension_style_name.clone()
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
ro(
|
|
|
|
|
|
"Dim Style Handle",
|
|
|
|
|
|
"tol_dim_style_handle",
|
|
|
|
|
|
match self.dimension_style_handle {
|
|
|
|
|
|
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
|
|
|
|
|
_ => "(none)".to_string(),
|
|
|
|
|
|
},
|
|
|
|
|
|
),
|
|
|
|
|
|
edit("Text Height", "tol_text_height", self.text_height),
|
|
|
|
|
|
edit("Dim Gap", "tol_dim_gap", self.dimension_gap),
|
|
|
|
|
|
ro(
|
|
|
|
|
|
"Direction",
|
|
|
|
|
|
"tol_direction",
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{:.3}, {:.3}, {:.3}",
|
|
|
|
|
|
self.direction.x, self.direction.y, self.direction.z
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
ro(
|
|
|
|
|
|
"Normal",
|
|
|
|
|
|
"tol_normal",
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"{:.3}, {:.3}, {:.3}",
|
|
|
|
|
|
self.normal.x, self.normal.y, self.normal.z
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
2026-04-02 19:06:20 +03:00
|
|
|
|
],
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
2026-05-12 10:36:15 +03:00
|
|
|
|
let Ok(v) = value.trim().parse::<f64>() else {
|
|
|
|
|
|
return;
|
|
|
|
|
|
};
|
2026-04-02 19:06:20 +03:00
|
|
|
|
match field {
|
|
|
|
|
|
"tol_ix" => self.insertion_point.x = v,
|
|
|
|
|
|
"tol_iy" => self.insertion_point.y = v,
|
|
|
|
|
|
"tol_iz" => self.insertion_point.z = v,
|
feat(entities): surface remaining 2D + Region fields in properties
Closes out the 2D-entity audit (and the Region partner that ships next
to Solid3D / Body). Round-trip-only metadata fields plus a few editable
ones gain Properties-panel rows so the values the parser populates are
visible and don't get silently dropped on save:
- Hatch: seed_points count, pixel_size, normal
- Polyline2D: smooth_surface, default_start_width, default_end_width,
thickness, normal (with editors for the widths/thickness)
- Region: uid, silhouettes (shared file with Solid3D / Body)
- Underlay: clip_inverted
- RasterImage: class_version, definition_handle, definition_reactor_handle
- Wipeout: class_version, clip_mode, definition_handle,
definition_reactor_handle
- Leader: annotation_handle, override_color
- Tolerance: dimension_style_name, dimension_style_handle, dimension_gap,
text_height (editable), direction, normal
- MLine: style_handle, style_element_count, justification picker
- Ole2Frame: version, source_application, binary_data size, dwg_mode,
dwg_trailing_byte, is_paper_space
- Viewport: snap_base/spacing/angle, twist_angle, front/back clip Z,
circle_sides, grid_flags, grid_major, base_ucs_handle, ucs_ortho_type,
background_handle, shade_plot_handle, visual_style_handle,
default_lighting, default_lighting_type, ambient_color (with editors
for the snap / clip / circle_sides values)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 21:28:16 +03:00
|
|
|
|
"tol_text_height" if v > 0.0 => self.text_height = v,
|
|
|
|
|
|
"tol_dim_gap" => self.dimension_gap = v,
|
2026-04-02 19:06:20 +03:00
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: Tolerance GD&T feature control frame render
Replace raw text dump with proper boxed feature-control frame:
- parse_gdt_rows(): splits %%v cells and ^J row separators
- substitute_gdt_codes(): strips {\Fgdt;X} patterns, maps symbol chars
to 3-letter ASCII labels (POS, PRP, PAR, SYM, CIR, CYL, …)
- tessellate_tolerance(): outer rectangle, row separators, column
dividers, and CXF-tessellated cell text — all as Vec<Vec<[f32;2]>>
(TruckObject::Text polyline path)
- Column widths computed per cell content; text centred in each cell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 23:23:06 +03:00
|
|
|
|
// ── Transformable ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-04-02 19:06:20 +03:00
|
|
|
|
impl Transformable for Tolerance {
|
|
|
|
|
|
fn apply_transform(&mut self, t: &EntityTransform) {
|
|
|
|
|
|
transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
|
|
|
|
|
transform::reflect_xy_point(
|
|
|
|
|
|
&mut entity.insertion_point.x,
|
|
|
|
|
|
&mut entity.insertion_point.y,
|
|
|
|
|
|
p1,
|
|
|
|
|
|
p2,
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|