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-07-07 18:22:08 +03:00
|
|
|
use crate::scene::convert::acad_to_truck::{GlyphRun, TextStroke, TruckEntity, TruckObject};
|
2026-06-16 11:33:46 +03:00
|
|
|
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-08-02 22:09:25 +03:00
|
|
|
use crate::t;
|
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 ───────────────────────────────────────────────────────────
|
|
|
|
|
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
/// The font a compartment's symbols come from.
|
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
|
|
|
///
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
/// The source names it in its own escapes, and it is registered under that name
|
|
|
|
|
/// like any other font — so nothing here special-cases it. A letter in this font
|
|
|
|
|
/// IS a symbol; it holds no Latin text.
|
|
|
|
|
const SYMBOL_FONT: &str = "gdt";
|
|
|
|
|
/// The font a compartment's ordinary text comes from.
|
|
|
|
|
const TEXT_FONT: &str = "txt";
|
|
|
|
|
|
|
|
|
|
/// One stretch of a compartment drawn in a single font.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
|
struct Run {
|
|
|
|
|
text: String,
|
|
|
|
|
font: &'static str,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A compartment: the runs it is built from, in order.
|
|
|
|
|
type Cell = Vec<Run>;
|
|
|
|
|
|
|
|
|
|
/// The characters of `cell`, whichever font each came from. For messages and
|
|
|
|
|
/// tests — never for measuring, since the runs are in different fonts.
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
fn cell_text(cell: &Cell) -> String {
|
|
|
|
|
cell.iter().map(|r| r.text.as_str()).collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parse a tolerance text string into rows of compartments.
|
|
|
|
|
///
|
|
|
|
|
/// Example: `{\Fgdt;p}%%v0.5%%vA%%vB%%v%%v` + newline + `{\Fgdt;j}%%v0.1%%vA`
|
|
|
|
|
/// - row separator → see below; `%%v` → compartment separator within a row
|
|
|
|
|
/// - `{\Fgdt;X}` → character X, drawn from the font that escape names
|
|
|
|
|
///
|
|
|
|
|
/// The row break reaches us in four encodings and all four are live:
|
|
|
|
|
/// a DWG carries a raw newline; the text-DXF reader rewrites the on-disk `^J`
|
|
|
|
|
/// to a newline before we ever see it; the binary-DXF reader does not, so `^J`
|
|
|
|
|
/// can still arrive literally; and our own DXF writer re-emits embedded
|
|
|
|
|
/// newlines as `\P`, because a line-based format cannot carry a raw newline
|
|
|
|
|
/// inside a string value. Splitting on `^J` alone — as this did — therefore
|
|
|
|
|
/// never fired on the two mainstream paths, and every row collapsed into one.
|
|
|
|
|
fn parse_gdt_rows(raw: &str) -> Vec<Vec<Cell>> {
|
|
|
|
|
let norm = raw
|
|
|
|
|
.replace("^J", "\n")
|
|
|
|
|
.replace("\\P", "\n")
|
|
|
|
|
.replace("\r\n", "\n")
|
|
|
|
|
.replace('\r', "\n");
|
|
|
|
|
|
|
|
|
|
norm.split('\n')
|
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
|
|
|
.filter(|row| !row.trim().is_empty())
|
|
|
|
|
.map(|row| {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
let mut cells: Vec<Cell> = row.split("%%v").map(|c| parse_cell(c.trim())).collect();
|
|
|
|
|
// Trailing empties are unused compartments, not blank boxes: a row
|
|
|
|
|
// written with spare datum slots ends in `%%v%%v` yet draws only the
|
|
|
|
|
// compartments it filled. Interior empties stay — there the position
|
|
|
|
|
// itself carries meaning (datum A, _, C).
|
|
|
|
|
while cells.last().is_some_and(|c| c.is_empty()) {
|
|
|
|
|
cells.pop();
|
|
|
|
|
}
|
|
|
|
|
cells
|
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
|
|
|
})
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
.filter(|cells: &Vec<Cell>| !cells.is_empty())
|
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
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
/// Split a compartment into font runs, resolving `{\Fgdt;X}` switches and
|
|
|
|
|
/// dropping every other inline format code.
|
|
|
|
|
///
|
|
|
|
|
/// The escape names its font and carries the character to draw in it; both are
|
|
|
|
|
/// kept as-is, so the symbol is looked up by the ordinary font machinery rather
|
|
|
|
|
/// than translated into some other character first.
|
|
|
|
|
fn parse_cell(s: &str) -> Cell {
|
|
|
|
|
let mut runs: Cell = Vec::new();
|
2026-07-15 22:55:51 +03:00
|
|
|
let push = |ch: char, font: &'static str, runs: &mut Cell| match runs.last_mut() {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
Some(r) if r.font == font => r.text.push(ch),
|
|
|
|
|
_ => runs.push(Run {
|
|
|
|
|
text: ch.to_string(),
|
|
|
|
|
font,
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
|
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 chars = s.chars().peekable();
|
|
|
|
|
while let Some(ch) = chars.next() {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
if ch != '{' {
|
|
|
|
|
push(ch, TEXT_FONT, &mut runs);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Collect to the closing brace.
|
|
|
|
|
let mut inner = String::new();
|
|
|
|
|
let mut depth = 1usize;
|
|
|
|
|
for c in chars.by_ref() {
|
|
|
|
|
match c {
|
|
|
|
|
'{' => {
|
|
|
|
|
depth += 1;
|
|
|
|
|
inner.push(c);
|
|
|
|
|
}
|
|
|
|
|
'}' => {
|
|
|
|
|
depth -= 1;
|
|
|
|
|
if depth == 0 {
|
|
|
|
|
break;
|
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
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
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
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
_ => 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
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
}
|
|
|
|
|
if let Some(sym) = symbol_font_switch(&inner) {
|
|
|
|
|
push(sym, SYMBOL_FONT, &mut runs);
|
|
|
|
|
}
|
|
|
|
|
// Any other format code contributes nothing.
|
|
|
|
|
}
|
|
|
|
|
runs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The character selected by a `\F<symbol font>;X` switch, or `None` when the
|
|
|
|
|
/// braces hold some other format code.
|
|
|
|
|
///
|
|
|
|
|
/// The font name may carry an extension and a run of `|`-separated parameters
|
|
|
|
|
/// (`\Fgdt.shx|b0|i0|c134|p6;j`), so match the name and skip to the `;` rather
|
|
|
|
|
/// than demanding the bare `\Fgdt;` form — the parameterised spelling is legal
|
|
|
|
|
/// and would otherwise drop the symbol silently.
|
|
|
|
|
fn symbol_font_switch(inner: &str) -> Option<char> {
|
|
|
|
|
let rest = inner
|
|
|
|
|
.strip_prefix("\\F")
|
|
|
|
|
.or_else(|| inner.strip_prefix("\\f"))?;
|
|
|
|
|
let (name, tail) = rest.split_once(';')?;
|
|
|
|
|
let name = name.split('|').next()?.trim();
|
|
|
|
|
let stem = name.strip_suffix(".shx").unwrap_or(name);
|
|
|
|
|
if !stem.eq_ignore_ascii_case("gdt") {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
tail.chars().next()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Per-entity overrides of individual dimension-style variables, carried as
|
|
|
|
|
/// extended data under the `DSTYLE` application.
|
|
|
|
|
///
|
|
|
|
|
/// An entity may keep its style yet override single variables on itself, as
|
|
|
|
|
/// `(variable group code, value)` pairs. Reading the style but ignoring these
|
|
|
|
|
/// draws the frame at the style's size rather than its own — which is how this
|
|
|
|
|
/// one, overriding its text height to a fraction of the style's, came out far
|
|
|
|
|
/// too large.
|
|
|
|
|
///
|
|
|
|
|
/// Only the character height is read — it is the frame's single geometric
|
|
|
|
|
/// input (see `tessellate_tolerance`); every other variable is left to the style.
|
|
|
|
|
fn dstyle_overrides(tol: &Tolerance) -> Option<f64> {
|
|
|
|
|
// The only variable the frame is built from.
|
|
|
|
|
const DIMTXT: i16 = 140;
|
|
|
|
|
|
|
|
|
|
use acadrust::xdata::XDataValue as V;
|
|
|
|
|
|
|
|
|
|
// The record belongs to the shared "ACAD" application and names itself in
|
|
|
|
|
// its FIRST STRING VALUE. There is no record called "DSTYLE" — asking for
|
|
|
|
|
// one finds nothing and leaves every override silently unread.
|
|
|
|
|
let rec = tol.common.extended_data.get_record("ACAD")?;
|
|
|
|
|
let mut vals = rec.values.iter();
|
|
|
|
|
match vals.next() {
|
|
|
|
|
Some(V::String(s)) if s == "DSTYLE" => {}
|
|
|
|
|
_ => return None,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut txt = None;
|
|
|
|
|
let mut pending: Option<i16> = None;
|
|
|
|
|
for v in vals {
|
|
|
|
|
match v {
|
|
|
|
|
V::Integer16(code) => pending = Some(*code),
|
|
|
|
|
V::Real(value) | V::Distance(value) => {
|
|
|
|
|
if pending.take() == Some(DIMTXT) {
|
|
|
|
|
txt = Some(*value);
|
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
|
|
|
}
|
|
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// Braces and anything else just delimit; a non-numeric value also
|
|
|
|
|
// ends the pair we were waiting on.
|
|
|
|
|
_ => pending = 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
|
|
|
}
|
|
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
txt
|
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
|
|
|
}
|
|
|
|
|
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
/// The tolerance's dimension style — by handle first, then by name.
|
|
|
|
|
///
|
|
|
|
|
/// The order matters and is not interchangeable: a DWG records the style's
|
|
|
|
|
/// handle and leaves the name at its default, while a DXF records the name and
|
|
|
|
|
/// leaves the handle empty. Matching on the name alone — the shape used
|
|
|
|
|
/// elsewhere for entities that only ever carry one — would silently resolve
|
|
|
|
|
/// every DWG-read tolerance to "Standard" and pick the wrong metrics.
|
|
|
|
|
fn resolve_dim_style<'a>(
|
|
|
|
|
tol: &Tolerance,
|
|
|
|
|
doc: &'a acadrust::CadDocument,
|
|
|
|
|
) -> Option<&'a acadrust::tables::DimStyle> {
|
|
|
|
|
if let Some(h) = tol.dimension_style_handle {
|
|
|
|
|
if !h.is_null() {
|
|
|
|
|
if let Some(s) = doc.dim_styles.iter().find(|s| s.handle == h) {
|
|
|
|
|
return Some(s);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let name = tol.dimension_style_name.trim();
|
|
|
|
|
if name.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
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
doc.dim_styles
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|s| s.name.eq_ignore_ascii_case(name))
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Feature-control frame builder ─────────────────────────────────────────────
|
|
|
|
|
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
/// The pen advance of `text` in `font` at `height`.
|
|
|
|
|
///
|
|
|
|
|
/// This is where the pen LANDS, so it carries the font's letter spacing past
|
|
|
|
|
/// the final glyph — which is what puts the gap between one run and the next.
|
|
|
|
|
fn run_advance(text: &str, font: &str, height: f32) -> f32 {
|
|
|
|
|
crate::entities::text_support::text_local_bounds(font, text, height, 1.0, 0.0)
|
|
|
|
|
.map(|b| b.advance)
|
|
|
|
|
.unwrap_or(0.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The font's letter spacing at `height`, in world units.
|
|
|
|
|
///
|
|
|
|
|
/// Glyph geometry is authored against a 9-unit cap height, so a font's spacing
|
|
|
|
|
/// scales with the character height like everything else.
|
|
|
|
|
fn letter_spacing(font: &str, height: f32) -> f32 {
|
|
|
|
|
crate::scene::text::font_face::Face::resolve(font).letter_spacing() * height / 9.0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// How wide a compartment's content actually draws.
|
|
|
|
|
///
|
|
|
|
|
/// Not the pen advance: that stops one letter-space PAST the last glyph, since
|
|
|
|
|
/// its job is to place whatever comes next. Summing run advances therefore
|
|
|
|
|
/// over-measures by exactly one spacing — the gaps between runs are real, the
|
|
|
|
|
/// one hanging off the end is not — and every compartment came out that much
|
|
|
|
|
/// too wide.
|
|
|
|
|
fn content_width(cell: &Cell, height: f32) -> f32 {
|
|
|
|
|
let Some(last) = cell.last() else {
|
|
|
|
|
return 0.0;
|
|
|
|
|
};
|
|
|
|
|
let pen: f32 = cell
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|r| run_advance(&r.text, r.font, height))
|
|
|
|
|
.sum();
|
|
|
|
|
(pen - letter_spacing(last.font, height)).max(0.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// One text run of a feature-control frame, ready to become a `TextStroke`
|
|
|
|
|
/// with a `GlyphRun` so the run can render as SDF glyph quads (or, when
|
2026-07-07 18:22:08 +03:00
|
|
|
/// SDF is off, from `strokes`). `origin` is relative to the tolerance insertion
|
|
|
|
|
/// point (already rotated); `strokes` are the glyph polylines rotated about the
|
|
|
|
|
/// origin (no origin translation — the wire-builder adds the origin).
|
|
|
|
|
struct TolCell {
|
|
|
|
|
text: String,
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
font: &'static str,
|
2026-07-07 18:22:08 +03:00
|
|
|
origin: [f32; 2],
|
|
|
|
|
strokes: Vec<Vec<[f32; 2]>>,
|
|
|
|
|
height: f32,
|
|
|
|
|
rotation: f32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tessellate a Tolerance entity's feature-control frame.
|
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
|
|
|
///
|
2026-07-07 18:22:08 +03:00
|
|
|
/// Returns (`box_strokes`, `cells`):
|
|
|
|
|
/// - `box_strokes` — the outer border, row separators and column dividers as
|
|
|
|
|
/// 2-D polylines (rotated; run-less, so they always render as strokes)
|
|
|
|
|
/// - `cells` — one [`TolCell`] per non-empty cell, carrying its text + a
|
|
|
|
|
/// `GlyphRun` so the cell renders as SDF text (frame stays geometry).
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
fn tessellate_tolerance(
|
|
|
|
|
tol: &Tolerance,
|
|
|
|
|
doc: &acadrust::CadDocument,
|
|
|
|
|
) -> (Vec<Vec<[f32; 2]>>, Vec<TolCell>) {
|
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 tol.text.is_empty() {
|
2026-07-07 18:22:08 +03:00
|
|
|
return (vec![], vec![]);
|
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 rows = parse_gdt_rows(&tol.text);
|
|
|
|
|
if rows.is_empty() {
|
2026-07-07 18:22:08 +03:00
|
|
|
return (vec![], vec![]);
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Metrics ──────────────────────────────────────────────────────────
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// Text height and gap come from the dimension style whenever one resolves.
|
|
|
|
|
// The entity's own fields are not trustworthy: a DWG carries only the
|
|
|
|
|
// style's handle and leaves these at their constructed defaults (0.18 /
|
|
|
|
|
// 0.09), which are small but non-zero — so a "> 0" test happily uses them
|
|
|
|
|
// and the frame draws about ten times too small. Falling back to them only
|
|
|
|
|
// when no style resolves keeps DXF-read entities working.
|
|
|
|
|
let style = resolve_dim_style(tol, doc);
|
|
|
|
|
let scale = style
|
|
|
|
|
.map(|s| if s.dimscale > 1e-6 { s.dimscale } else { 1.0 })
|
|
|
|
|
.unwrap_or(1.0);
|
|
|
|
|
// The entity's own overrides win over its style; the style wins over the
|
|
|
|
|
// entity's constructed defaults.
|
|
|
|
|
let h = dstyle_overrides(tol)
|
|
|
|
|
.or(style.map(|s| s.dimtxt))
|
|
|
|
|
.map(|v| v * scale)
|
|
|
|
|
.unwrap_or(tol.text_height) as f32;
|
|
|
|
|
let h = if h > 1e-6 { h } else { 2.5_f32 };
|
|
|
|
|
|
|
|
|
|
// A compartment is a PROPORTION of the character height: it spans -h..+h
|
|
|
|
|
// about the row's centreline, so it is exactly 2h tall and the h/2 margin
|
|
|
|
|
// falls out of that rather than being an input.
|
|
|
|
|
//
|
|
|
|
|
// The gap variable is deliberately NOT read. Its scope is the dimension
|
|
|
|
|
// line's break and the rectangle around a basic dimension — a different
|
|
|
|
|
// entity's geometry, where it genuinely does apply twice per axis. Carrying
|
|
|
|
|
// that shape over to this frame drew it at ~4x the character height. Even
|
|
|
|
|
// fed a self-consistent style it can only ever produce 1.5h, never 2h, so
|
|
|
|
|
// the formula was wrong in kind, not merely mis-fed.
|
|
|
|
|
let cell_h = 2.0 * h;
|
|
|
|
|
let pad = h * 0.5;
|
|
|
|
|
let min_cell_w = 2.0 * h;
|
|
|
|
|
|
|
|
|
|
// Cell widths per row, not a shared column grid: a row holding one
|
|
|
|
|
// compartment is only as wide as that compartment, so a lone datum below a
|
|
|
|
|
// four-compartment row draws a small box rather than inheriting the row
|
|
|
|
|
// above it. Width comes from the real pen advance — a character count times
|
|
|
|
|
// an average width mis-measures every symbol, and counting UTF-8 bytes
|
|
|
|
|
// (`len()`) would make each one two or three cells wide.
|
|
|
|
|
// A compartment's runs come from different fonts, so each is measured in its
|
|
|
|
|
// own before they are summed.
|
|
|
|
|
let cell_width = |cell: &Cell| -> f32 { (content_width(cell, h) + 2.0 * pad).max(min_cell_w) };
|
|
|
|
|
let row_widths: Vec<Vec<f32>> = rows
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|row| row.iter().map(|c| cell_width(c)).collect())
|
|
|
|
|
.collect();
|
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
|
|
|
|
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
|
|
|
|
2026-07-07 18:22:08 +03:00
|
|
|
let mut box_out: Vec<Vec<[f32; 2]>> = Vec::new();
|
|
|
|
|
let mut cells: Vec<TolCell> = Vec::new();
|
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
|
|
|
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// The insertion point sits on the CENTRELINE OF THE FIRST ROW, and rows
|
|
|
|
|
// stack downward from there: row `i` spans `-h - 2h*i` .. `+h - 2h*i`. So
|
|
|
|
|
// row 0 straddles y = 0 and the frame grows downward only.
|
|
|
|
|
//
|
|
|
|
|
// Anchoring the frame's bottom-left at the insertion point instead — as
|
|
|
|
|
// this did — offsets the whole frame upward by its own height, and by an
|
|
|
|
|
// amount that changes with the number of rows, so a two-row frame and a
|
|
|
|
|
// three-row one would sit at different heights from the same point.
|
|
|
|
|
let row_bottom = |ri: usize| -h - cell_h * ri as f32;
|
|
|
|
|
|
|
|
|
|
// ── Per-row border ────────────────────────────────────────────────────
|
|
|
|
|
// One box per row, each left-aligned at x = 0 and only as wide as its own
|
|
|
|
|
// compartments. Adjacent rows share the edge between them, so this draws
|
|
|
|
|
// the row separators too.
|
|
|
|
|
for (ri, widths) in row_widths.iter().enumerate() {
|
|
|
|
|
let rw: f32 = widths.iter().sum();
|
|
|
|
|
let y0 = row_bottom(ri);
|
|
|
|
|
let y1 = y0 + cell_h;
|
|
|
|
|
box_out.push(vec![
|
|
|
|
|
rot(0.0, y0),
|
|
|
|
|
rot(rw, y0),
|
|
|
|
|
rot(rw, y1),
|
|
|
|
|
rot(0.0, y1),
|
|
|
|
|
rot(0.0, y0),
|
|
|
|
|
]);
|
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
|
|
|
}
|
|
|
|
|
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// ── Compartment dividers, within each row ─────────────────────────────
|
|
|
|
|
for (ri, widths) in row_widths.iter().enumerate() {
|
|
|
|
|
let y0 = row_bottom(ri);
|
|
|
|
|
let y1 = y0 + cell_h;
|
|
|
|
|
let mut x_cursor = 0.0_f32;
|
|
|
|
|
for w in widths.iter().take(widths.len().saturating_sub(1)) {
|
|
|
|
|
x_cursor += w;
|
|
|
|
|
box_out.push(vec![rot(x_cursor, y0), rot(x_cursor, y1)]);
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Text content per cell ─────────────────────────────────────────────
|
2026-07-07 18:22:08 +03:00
|
|
|
// Each cell becomes a TolCell: its origin is the (rotated) cell position
|
|
|
|
|
// relative to the insertion point; its `strokes` are the glyph polylines
|
|
|
|
|
// rotated about that origin (used only when SDF is off). The GlyphRun the
|
|
|
|
|
// caller attaches lets the cell render as SDF text.
|
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 (ri, row) in rows.iter().enumerate() {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// The compartment is 2h tall and the character h, so an h/2 margin
|
|
|
|
|
// centres the text on the row's centreline.
|
|
|
|
|
let row_y = row_bottom(ri) + pad;
|
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 cell_x = 0.0_f32;
|
|
|
|
|
for (ci, cell) in row.iter().enumerate() {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
let cw = row_widths[ri][ci];
|
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() {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// Centre the compartment's whole content, then lay its runs out
|
|
|
|
|
// left to right — each in its own font, each advancing the pen
|
|
|
|
|
// by what that font actually measures.
|
|
|
|
|
let mut run_x = cell_x + (cw - content_width(cell, h)) * 0.5;
|
|
|
|
|
for run in cell {
|
|
|
|
|
let (text, font) = (run.text.clone(), run.font);
|
|
|
|
|
let (local_strokes, _) =
|
|
|
|
|
lff::tessellate_text_ex([0.0, 0.0], h, 0.0, 1.0, 0.0, font, &text);
|
|
|
|
|
// Glyph polylines rotated about the run's origin (no origin
|
|
|
|
|
// translation — the wire-builder adds `origin`).
|
|
|
|
|
let strokes: Vec<Vec<[f32; 2]>> = local_strokes
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|pl| pl.into_iter().map(|[px, py]| rot(px, py)).collect())
|
|
|
|
|
.filter(|pl: &Vec<[f32; 2]>| !pl.is_empty())
|
|
|
|
|
.collect();
|
|
|
|
|
let advance = run_advance(&text, font, h);
|
|
|
|
|
cells.push(TolCell {
|
|
|
|
|
text,
|
|
|
|
|
font,
|
|
|
|
|
origin: rot(run_x, row_y),
|
|
|
|
|
strokes,
|
|
|
|
|
height: h,
|
|
|
|
|
rotation: angle,
|
|
|
|
|
});
|
|
|
|
|
run_x += advance;
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
cell_x += cw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-07 18:22:08 +03:00
|
|
|
(box_out, cells)
|
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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── TruckConvertible ──────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-04-02 19:06:20 +03:00
|
|
|
impl TruckConvertible for Tolerance {
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
2026-04-02 19:06:20 +03:00
|
|
|
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.
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
let (box_strokes, cells) = tessellate_tolerance(self, document);
|
2026-07-07 18:22:08 +03:00
|
|
|
let ins = [self.insertion_point.x, self.insertion_point.y];
|
2026-04-02 19:06:20 +03:00
|
|
|
|
2026-07-07 18:22:08 +03:00
|
|
|
// Frame geometry first (run-less → always strokes; also the anchor
|
|
|
|
|
// group so its origin = the insertion point), then one run-group per
|
|
|
|
|
// text cell so the cell text renders as SDF glyphs (or strokes when
|
|
|
|
|
// SDF is off).
|
|
|
|
|
let mut groups: Vec<TextStroke> = Vec::with_capacity(1 + cells.len());
|
|
|
|
|
if !box_strokes.is_empty() {
|
|
|
|
|
groups.push(TextStroke {
|
|
|
|
|
strokes: box_strokes,
|
|
|
|
|
origin: ins,
|
2026-06-11 00:04:37 +03:00
|
|
|
color: None,
|
2026-06-25 18:17:01 +01:00
|
|
|
fill_tris: vec![],
|
2026-07-07 00:53:19 +03:00
|
|
|
run: None,
|
2026-07-07 18:22:08 +03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for cell in cells {
|
|
|
|
|
groups.push(TextStroke {
|
|
|
|
|
strokes: cell.strokes,
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
origin: [
|
|
|
|
|
ins[0] + cell.origin[0] as f64,
|
|
|
|
|
ins[1] + cell.origin[1] as f64,
|
|
|
|
|
],
|
2026-07-07 18:22:08 +03:00
|
|
|
color: None,
|
|
|
|
|
fill_tris: vec![],
|
|
|
|
|
run: Some(GlyphRun {
|
|
|
|
|
text: cell.text,
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
font: cell.font.to_string(),
|
2026-07-07 18:22:08 +03:00
|
|
|
height: cell.height,
|
|
|
|
|
rotation: cell.rotation,
|
|
|
|
|
width_factor: 1.0,
|
|
|
|
|
oblique: 0.0,
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
// `tracking` scales the font's own letter spacing, so 0
|
|
|
|
|
// collapses the gap between glyphs and the characters run
|
|
|
|
|
// together. Every other text-bearing entity passes 1.0, and
|
|
|
|
|
// so do both the stroke path (`tessellate_text_ex`) and the
|
|
|
|
|
// width measurement above — leaving this at 0 made the cells
|
|
|
|
|
// measure wider than the text they drew.
|
|
|
|
|
tracking: 1.0,
|
feat(mtext): structured editor with selection, copy, and visible bold
Rewrite the in-app MTEXT editor to run on acadrust's structured
MTextDocument model end-to-end: load via parse_mtext, edit a flat Cell
model, serialize via to_mtext_string. Retires the hand-rolled format-code
string handling, the parallel visible_spans parser, the composed_value
prefix, and the Edit-raw-code tab.
Selection and copy:
- double-click selects the word, triple-click selects all (click-counting)
- Ctrl+A selects all text
- Ctrl+C copies the selected text to the clipboard
Formatting:
- per-selection Bold/Italic/Underline/Overline/Strike/Case/Alignment
- Bold renders as a visibly thicker SDF pen, keyed (family, char, bold)
through the whole text pipeline; the resolved style font name is stamped
so the \f...|b1; code survives serialization
- per-selection font and color changes
- editor color dropdown reuses the Properties panel color selector
Global toolbar defaults (font/color/oblique/width/tracking) fold onto the
document as real span properties instead of a leading code prefix; a
trailing empty paragraph is preserved so Enter at the end keeps its line.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 20:18:50 +03:00
|
|
|
bold: false,
|
2026-07-07 18:22:08 +03:00
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if groups.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(TruckEntity {
|
fix(pick): hit-test lineweight width and thickness walls
Selection tested a flat 8 px radius around each wire's centreline and
nothing else, so two things the user can see were not pickable.
A wire renders as a band `line_weight_px` wide, so pick now widens to that
rendered half-width via `pick_tolerance_px`, mirroring the wire shader's
`select(0.5, half_width, lwdisplay_enable)`. Behaviour is unchanged for
every standard weight (the widest, 2.11 mm, renders 7.97 px half-width —
just under the 8 px threshold); this keeps the two sides from drifting
apart if the display boost in `lineweight_to_px` ever changes.
Entities extruded by a DXF thickness draw their wall as four edges with
nothing in between, so the interior was a hole the cursor fell through.
WireModel gains `pick_tris`: triangles hit-testing treats as solid and the
renderer never uploads. Kept apart from `fill_tris` because that channel
reaches the GPU, and a wall that rendered shaded would change how every
thickness drawing looks. Ranked below `fill_tris` — those are drawn, these
are not, so where they overlap the drawn face is what the user means.
Walls are dropped when a wire is projected into a floating viewport:
`points` are reprojected and clipped into paper coords there, so cloned
model-space triangles would hit-test at a model-scale offset. Extruded
entities stay selectable by their edges, which do get projected.
The wall loop runs on every hover that misses everything else, so it
rejects on the wire's AABB first — an extruded circle is ~128 triangles
and a drawing full of thickness would otherwise project every wall on
each mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 14:58:55 +03:00
|
|
|
pick_tris: Vec::new(),
|
2026-07-07 18:22:08 +03:00
|
|
|
object: TruckObject::Text(groups),
|
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 {
|
2026-07-02 22:46:19 +03:00
|
|
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
vec![
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
title: t!("Text").into_owned(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
props: vec![
|
2026-08-02 22:09:25 +03:00
|
|
|
ro(t!("Text style").as_ref(), "tol_text_style", String::new()),
|
|
|
|
|
edit(t!("Text height").as_ref(), "tol_text_height", self.text_height),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
title: t!("Geometry").into_owned(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
props: vec![
|
2026-08-02 22:09:25 +03:00
|
|
|
edit(t!("Position X").as_ref(), "tol_ix", self.insertion_point.x),
|
|
|
|
|
edit(t!("Position Y").as_ref(), "tol_iy", self.insertion_point.y),
|
|
|
|
|
edit(t!("Position Z").as_ref(), "tol_iz", self.insertion_point.z),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
PropSection {
|
2026-08-02 22:09:25 +03:00
|
|
|
title: t!("Misc").into_owned(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
props: vec![
|
|
|
|
|
ro(
|
2026-08-02 22:09:25 +03:00
|
|
|
t!("Dimension style").as_ref(),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
"tol_dim_style",
|
|
|
|
|
if self.dimension_style_name.is_empty() {
|
|
|
|
|
"(default)".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
self.dimension_style_name.clone()
|
|
|
|
|
},
|
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
|
|
|
),
|
2026-08-02 22:09:25 +03:00
|
|
|
edit(t!("Direction X").as_ref(), "tol_dir_x", self.direction.x),
|
|
|
|
|
edit(t!("Direction Y").as_ref(), "tol_dir_y", self.direction.y),
|
|
|
|
|
edit(t!("Direction Z").as_ref(), "tol_dir_z", self.direction.z),
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
]
|
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,
|
feat(properties): conform entity property groups to PROPERTIES.md
Rewrite each entity's property builder to the per-entity group/row spec:
the correct sections (Geometry, Misc, Text, Pattern, Lines & Arrows, Leaders,
Block, …) with the spec's rows and real computed values — Diameter,
Circumference, Area, Delta X/Y/Z, Angle, Total angle, Arc length, Radius ratio,
etc. — plus apply routing for the editable rows.
Covers ~26 entities (Line, Circle, Arc, Point, Ellipse, Spline, polylines,
Ray/XLine, Insert, Text, MText, Hatch, Leader, MultiLeader, Tolerance, Solid,
Solid3D/Region/Body, meshes, MLine, Raster/Wipeout, Underlay, Table, Viewport,
Attribute, OLE). The 3D Visualization/Material group and the full Dimension
spec are deferred (they need document access for material/dimstyle name
resolution). Rows whose data is not in the pinned acadrust revision are shown
as empty placeholders, to be filled when the dependency is bumped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:03:18 +03:00
|
|
|
"tol_dir_x" => self.direction.x = v,
|
|
|
|
|
"tol_dir_y" => self.direction.y = v,
|
|
|
|
|
"tol_dir_z" => self.direction.z = 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,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
fix(tolerance): draw the feature control frame properly
A tolerance drew one row of ASCII soup — "RUN | ANGtol(M) | ..." — about
a tenth of its proper size. Six defects, each independent.
Rows were split on the two-character string "^J". A DWG stores a raw
newline, and the text-DXF reader rewrites the on-disk ^J to one before we
see it, so that split never fired on either mainstream path and every row
collapsed into the first. It now normalises all four spellings the break
arrives in — including the \P our own DXF writer emits, which made
open-DWG → save-DXF → reopen asymmetric.
Symbols were replaced with ASCII labels from a table that was itself
wrong: it read r as circular runout, n as angularity, p as position. The
font's own encoding says concentricity, diameter, projected tolerance
zone, and the benchmark's pre-exploded reference agrees. That table was
an inversion of acadrust's, which is self-contradictory — it binds n to
two different meanings. Do not re-derive from it.
The frame is a proportion of the character height: a compartment spans
-h..+h about its row's centreline, so it is 2h tall and the margin falls
out of that. It was built as height + 2*DIMGAP instead, which is a
different entity's geometry — DIMGAP sizes the dimension line's break and
the rectangle around a basic dimension, where it genuinely applies twice
per axis. Even fed a self-consistent style that can only ever produce
1.5h, so it was wrong in kind rather than mis-fed.
Height came from the entity's own field, which a DWG never sets — the
reader carries only the style's handle, leaving a small non-zero default
that a "> 0" test happily used. It now resolves the style by handle first
and name second: a DWG records the handle and defaults the name, a DXF
the reverse, so matching on the name alone would have picked the wrong
style for every DWG-read tolerance. An entity may also override single
style variables on itself, and this one halves its character height that
way; ignoring that drew the frame several times too large.
Rows stacked upward, every row inherited the widest row's columns, and
one full-width border boxed them all — none of which showed while the
parse only ever produced a single row. The insertion point sits on the
first row's centreline; anchoring the frame's bottom-left there offset it
by its own height, and by an amount that changed with the row count.
Cell text ran together because the glyph run asked for no letter spacing
while the width measurement assumed the font's own, so the characters
crowded inside correctly-sized boxes. Compartments are also measured by
what they draw now: the pen advance stops one letter-space past the last
glyph, and counting that made every one of them too wide.
Symbols come from a bundled stroke font, restored from this repository's
own history: it shipped QCAD's gdt.cxf until the text engine moved from
CXF to LFF, which removed every .cxf and left nothing in their place —
the ASCII labels date from exactly there. Converted to LFF, keyed by the
letter its \Fgdt;X escape carries, and registered under that name like
any other font, so a switch to it resolves through the ordinary lookup:
an MTEXT carrying the escape, or a text style named gdt.shx, reaches it
too, with no code of their own. The geometry is public domain by explicit
waiver; provenance is in the file's header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 22:22:07 +03:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
/// The benchmark's own string, byte for byte, in the form a DWG carries it
|
|
|
|
|
/// (a raw newline). This one assertion is the whole reported bug: it used to
|
|
|
|
|
/// collapse to a single row of ASCII soup.
|
|
|
|
|
const BENCH: &str =
|
|
|
|
|
"{\\Fgdt;r}%%v{\\Fgdt;n}tol{\\Fgdt;m}%%v{\\Fgdt;n}tol{\\Fgdt;s}%%v1{\\Fgdt;m}%%v%%v\n2{\\Fgdt;p}\nA";
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn the_benchmark_frame_parses_to_four_one_one() {
|
|
|
|
|
let rows = parse_gdt_rows(BENCH);
|
|
|
|
|
// Rendered as "<font>:<text>" per run so a wrong font fails loudly.
|
|
|
|
|
let shown: Vec<Vec<String>> = rows
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|row| {
|
|
|
|
|
row.iter()
|
|
|
|
|
.map(|cell| {
|
|
|
|
|
cell.iter()
|
|
|
|
|
.map(|r| format!("{}:{}", r.font, r.text))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join("|")
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
shown,
|
|
|
|
|
vec![
|
|
|
|
|
vec![
|
|
|
|
|
"gdt:r".to_string(),
|
|
|
|
|
"gdt:n|txt:tol|gdt:m".to_string(),
|
|
|
|
|
"gdt:n|txt:tol|gdt:s".to_string(),
|
|
|
|
|
"txt:1|gdt:m".to_string(),
|
|
|
|
|
],
|
|
|
|
|
vec!["txt:2|gdt:p".to_string()],
|
|
|
|
|
vec!["txt:A".to_string()],
|
|
|
|
|
],
|
|
|
|
|
"the reference draws [◎][⌀tolⓂ][⌀tolⓈ][1Ⓜ] / [2Ⓟ] / [A], each symbol \
|
|
|
|
|
from the font its escape names"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A row break reaches us in four encodings depending on the format and who
|
|
|
|
|
/// wrote it. All four must land on the same frame.
|
|
|
|
|
#[test]
|
|
|
|
|
fn every_row_break_encoding_gives_the_same_rows() {
|
|
|
|
|
let want = parse_gdt_rows(BENCH);
|
|
|
|
|
assert_eq!(want.len(), 3, "guard: the baseline itself must be 3 rows");
|
|
|
|
|
for (label, variant) in [
|
|
|
|
|
("^J", BENCH.replace('\n', "^J")),
|
|
|
|
|
("\\P", BENCH.replace('\n', "\\P")),
|
|
|
|
|
("CRLF", BENCH.replace('\n', "\r\n")),
|
|
|
|
|
("CR", BENCH.replace('\n', "\r")),
|
|
|
|
|
] {
|
|
|
|
|
assert_eq!(parse_gdt_rows(&variant), want, "{label} parsed differently");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Spare compartments at the end of a row are unused slots, not empty boxes
|
|
|
|
|
/// — the benchmark's first row ends `%%v%%v` yet draws four.
|
|
|
|
|
#[test]
|
|
|
|
|
fn trailing_empty_compartments_are_dropped_interior_ones_kept() {
|
|
|
|
|
let shown = |raw: &str| -> Vec<Vec<String>> {
|
|
|
|
|
parse_gdt_rows(raw)
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|row| row.iter().map(cell_text).collect())
|
|
|
|
|
.collect()
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(shown("a%%vb%%v%%v"), vec![vec!["a", "b"]]);
|
|
|
|
|
assert_eq!(shown("a%%v%%vc"), vec![vec!["a", "", "c"]]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The font switch legally carries an extension and parameters; the bare
|
|
|
|
|
/// form is only the common spelling.
|
|
|
|
|
#[test]
|
|
|
|
|
fn a_parameterised_font_switch_still_yields_its_symbol() {
|
|
|
|
|
assert_eq!(symbol_font_switch("\\Fgdt;j"), Some('j'));
|
|
|
|
|
assert_eq!(symbol_font_switch("\\Fgdt.shx|b0|i0|c134|p6;j"), Some('j'));
|
|
|
|
|
assert_eq!(symbol_font_switch("\\FGDT;j"), Some('j'));
|
|
|
|
|
// Not the symbol font — leave it to the generic format-code stripper.
|
|
|
|
|
assert_eq!(symbol_font_switch("\\Farial;j"), None);
|
|
|
|
|
assert_eq!(symbol_font_switch("\\C1"), None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The frame must take its size from the dimension style. A DWG carries
|
|
|
|
|
/// only the style's handle and leaves the entity's own height at a small
|
|
|
|
|
/// non-zero default, so trusting that field drew the frame ~10x too small.
|
|
|
|
|
#[test]
|
|
|
|
|
fn the_frame_is_sized_from_the_dimension_style_not_the_entity_default() {
|
|
|
|
|
use acadrust::tables::DimStyle;
|
|
|
|
|
let mut doc = acadrust::CadDocument::new();
|
|
|
|
|
let handle = acadrust::Handle::from(0x27_u64);
|
|
|
|
|
let mut style = DimStyle::new("ISO-25");
|
|
|
|
|
style.handle = handle;
|
|
|
|
|
style.dimtxt = 2.5;
|
|
|
|
|
style.dimgap = 0.625;
|
|
|
|
|
style.dimscale = 1.0;
|
|
|
|
|
doc.dim_styles.add_or_replace(style);
|
|
|
|
|
|
|
|
|
|
let mut tol = Tolerance::new();
|
|
|
|
|
tol.text = "A".into();
|
|
|
|
|
// What a DWG gives us: the handle set, the metrics left at defaults.
|
|
|
|
|
tol.dimension_style_handle = Some(handle);
|
|
|
|
|
assert!(
|
|
|
|
|
tol.text_height < 1.0,
|
|
|
|
|
"guard: the default height must stay small for this test to mean anything"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let (boxes, _) = tessellate_tolerance(&tol, &doc);
|
|
|
|
|
// 2 x dimtxt. The entity's own default (0.18) would give 0.36.
|
|
|
|
|
let height = frame_height(&boxes);
|
|
|
|
|
assert!(
|
|
|
|
|
(height - 5.0).abs() < 1e-3,
|
|
|
|
|
"frame is {height} tall; 2 x the style's 2.5 character height is 5.0"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An entity that overrides a style variable on itself wins over the style.
|
|
|
|
|
/// The benchmark's tolerance keeps ISO-25 but overrides its text height to
|
|
|
|
|
/// 0.42; reading the style alone drew the frame several times too large.
|
|
|
|
|
#[test]
|
|
|
|
|
fn an_entity_override_beats_its_dimension_style() {
|
|
|
|
|
use acadrust::tables::DimStyle;
|
|
|
|
|
use acadrust::xdata::XDataValue;
|
|
|
|
|
|
|
|
|
|
let mut doc = acadrust::CadDocument::new();
|
|
|
|
|
let handle = acadrust::Handle::from(0x27_u64);
|
|
|
|
|
let mut style = DimStyle::new("ISO-25");
|
|
|
|
|
style.handle = handle;
|
|
|
|
|
style.dimtxt = 2.5;
|
|
|
|
|
style.dimgap = 0.625;
|
|
|
|
|
style.dimscale = 1.0;
|
|
|
|
|
doc.dim_styles.add_or_replace(style);
|
|
|
|
|
|
|
|
|
|
let mut tol = Tolerance::new();
|
|
|
|
|
tol.text = "A".into();
|
|
|
|
|
tol.dimension_style_handle = Some(handle);
|
|
|
|
|
|
|
|
|
|
// Without the override: 2 x the style's 2.5 character height.
|
|
|
|
|
let (boxes, _) = tessellate_tolerance(&tol, &doc);
|
|
|
|
|
assert!(
|
|
|
|
|
(frame_height(&boxes) - 5.0).abs() < 1e-3,
|
|
|
|
|
"guard: style-sized, got {}",
|
|
|
|
|
frame_height(&boxes)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// The entity's own overrides, shaped exactly as the file carries them:
|
|
|
|
|
// application "ACAD", naming itself "DSTYLE" in its first string value,
|
|
|
|
|
// then (variable code, value) pairs. Variable 140 = text height.
|
|
|
|
|
let mut rec = acadrust::xdata::ExtendedDataRecord::new("ACAD");
|
|
|
|
|
rec.add_value(XDataValue::String("DSTYLE".into()));
|
|
|
|
|
rec.add_value(XDataValue::ControlString("{".into()));
|
|
|
|
|
rec.add_value(XDataValue::Integer16(140));
|
|
|
|
|
rec.add_value(XDataValue::Real(0.42));
|
|
|
|
|
rec.add_value(XDataValue::ControlString("}".into()));
|
|
|
|
|
tol.common.extended_data.add_record(rec);
|
|
|
|
|
|
|
|
|
|
let (boxes, _) = tessellate_tolerance(&tol, &doc);
|
|
|
|
|
// 2 x 0.42 — the frame follows the entity's own character height.
|
|
|
|
|
assert!(
|
|
|
|
|
(frame_height(&boxes) - 0.84).abs() < 1e-3,
|
|
|
|
|
"override ignored: frame is {} tall, expected 0.84",
|
|
|
|
|
frame_height(&boxes)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn frame_height(boxes: &[Vec<[f32; 2]>]) -> f32 {
|
|
|
|
|
let ys: Vec<f32> = boxes.iter().flatten().map(|p| p[1]).collect();
|
|
|
|
|
ys.iter().cloned().fold(f32::MIN, f32::max) - ys.iter().cloned().fold(f32::MAX, f32::min)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Rows read downward and start at the left edge.
|
|
|
|
|
#[test]
|
|
|
|
|
fn rows_stack_downward_and_left_align() {
|
|
|
|
|
let doc = acadrust::CadDocument::new();
|
|
|
|
|
let mut tol = Tolerance::new();
|
|
|
|
|
tol.text = BENCH.to_string();
|
|
|
|
|
let (boxes, _) = tessellate_tolerance(&tol, &doc);
|
|
|
|
|
|
|
|
|
|
// One closed rect per row (plus dividers); every rect starts at x = 0.
|
|
|
|
|
let rects: Vec<&Vec<[f32; 2]>> = boxes.iter().filter(|b| b.len() == 5).collect();
|
|
|
|
|
assert_eq!(rects.len(), 3, "one border per row");
|
|
|
|
|
for r in &rects {
|
|
|
|
|
let x0 = r.iter().map(|p| p[0]).fold(f32::MAX, f32::min);
|
|
|
|
|
assert!(x0.abs() < 1e-3, "row border not left-aligned: x0={x0}");
|
|
|
|
|
}
|
|
|
|
|
// The insertion point is the first row's centreline: row 0 straddles
|
|
|
|
|
// y = 0 and the frame only ever grows downward from it, so the anchor
|
|
|
|
|
// does not drift with the number of rows.
|
|
|
|
|
let h = tol.text_height as f32;
|
|
|
|
|
let top = rects
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|r| r.iter())
|
|
|
|
|
.map(|p| p[1])
|
|
|
|
|
.fold(f32::MIN, f32::max);
|
|
|
|
|
let bottom = rects
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|r| r.iter())
|
|
|
|
|
.map(|p| p[1])
|
|
|
|
|
.fold(f32::MAX, f32::min);
|
|
|
|
|
assert!(
|
|
|
|
|
(top - h).abs() < 1e-4,
|
|
|
|
|
"frame should reach +h ({h}) above the insertion point, reaches {top}"
|
|
|
|
|
);
|
|
|
|
|
// 3 rows of 2h, the first centred on 0 -> the last bottom is -5h.
|
|
|
|
|
assert!(
|
|
|
|
|
(bottom - (-5.0 * h)).abs() < 1e-4,
|
|
|
|
|
"three rows should reach -5h ({}) below, reach {bottom}",
|
|
|
|
|
-5.0 * h
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Row 0 (4 compartments) is the widest AND the topmost.
|
|
|
|
|
let top = |r: &Vec<[f32; 2]>| r.iter().map(|p| p[1]).fold(f32::MIN, f32::max);
|
|
|
|
|
let wide = |r: &Vec<[f32; 2]>| r.iter().map(|p| p[0]).fold(f32::MIN, f32::max);
|
|
|
|
|
assert!(
|
|
|
|
|
top(rects[0]) > top(rects[1]) && top(rects[1]) > top(rects[2]),
|
|
|
|
|
"rows must stack downward, row 0 on top"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
wide(rects[0]) > wide(rects[1]),
|
|
|
|
|
"the lone datum row must not inherit the four-compartment row's width"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The cell is measured with the font's normal letter spacing, so it must be
|
|
|
|
|
/// DRAWN with it too. They diverged once — the measurement assumed normal
|
|
|
|
|
/// spacing while the glyph run asked for none — and the characters ran
|
|
|
|
|
/// together inside correctly-sized boxes.
|
|
|
|
|
#[test]
|
|
|
|
|
fn cells_are_drawn_with_the_spacing_they_were_measured_with() {
|
|
|
|
|
use acadrust::EntityType;
|
|
|
|
|
let doc = acadrust::CadDocument::new();
|
|
|
|
|
let mut tol = Tolerance::new();
|
|
|
|
|
tol.text = "ABC".into();
|
|
|
|
|
|
|
|
|
|
let Some(TruckEntity {
|
|
|
|
|
object: TruckObject::Text(groups),
|
|
|
|
|
..
|
|
|
|
|
}) = tol.to_truck(&doc)
|
|
|
|
|
else {
|
|
|
|
|
panic!("tolerance produced no text");
|
|
|
|
|
};
|
|
|
|
|
// The frame is the run-less group; every cell carries a run.
|
|
|
|
|
let runs: Vec<_> = groups.iter().filter_map(|g| g.run.as_ref()).collect();
|
|
|
|
|
assert!(!runs.is_empty(), "no cell runs emitted");
|
|
|
|
|
for r in &runs {
|
|
|
|
|
assert_eq!(
|
|
|
|
|
r.tracking, 1.0,
|
|
|
|
|
"cell text must use the font's normal letter spacing, like every \
|
|
|
|
|
other entity and like the width measurement"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The symbol font is reached by NAME, through the same lookup every other
|
|
|
|
|
/// font goes through — so a switch to it works wherever inline codes do, not
|
|
|
|
|
/// only inside a tolerance. An MTEXT carrying `{\\Fgdt;j}`, or a text style
|
|
|
|
|
/// literally named "gdt.shx", lands on it too, with no code of their own.
|
|
|
|
|
#[test]
|
|
|
|
|
fn the_symbol_font_is_reached_by_name_like_any_other() {
|
|
|
|
|
use crate::scene::text::font_face::Face;
|
|
|
|
|
for name in ["gdt", "GDT", "gdt.shx"] {
|
|
|
|
|
assert!(
|
|
|
|
|
crate::scene::text::lff::is_builtin(name),
|
|
|
|
|
"{name:?} must resolve to the bundled symbol font"
|
|
|
|
|
);
|
|
|
|
|
let face = Face::resolve(name);
|
|
|
|
|
let Face::Lff(font) = &face else {
|
|
|
|
|
panic!("{name:?} resolved to a system outline, not the stroke font");
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(
|
|
|
|
|
font.name.to_ascii_lowercase(),
|
|
|
|
|
"gdt",
|
|
|
|
|
"{name:?} landed on {}",
|
|
|
|
|
font.name
|
|
|
|
|
);
|
|
|
|
|
// Every character the escape can carry draws from it.
|
|
|
|
|
for c in 'a'..='z' {
|
|
|
|
|
assert!(font.glyph(c).is_some(), "{name:?} has no glyph for {c:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// It holds symbols, not letters: the glyph for 'm' is the circled M, and
|
|
|
|
|
/// must not be the letter m the text fonts draw.
|
|
|
|
|
#[test]
|
|
|
|
|
fn a_letter_in_the_symbol_font_is_a_symbol() {
|
|
|
|
|
use crate::scene::text::font_face::Face;
|
|
|
|
|
let gdt = Face::resolve(SYMBOL_FONT);
|
|
|
|
|
let txt = Face::resolve(TEXT_FONT);
|
|
|
|
|
let mut differ = 0;
|
|
|
|
|
for c in 'a'..='z' {
|
|
|
|
|
let (g, x) = (gdt.glyph(c), txt.glyph(c));
|
|
|
|
|
if let (Some(g), Some(x)) = (g, x) {
|
|
|
|
|
if g.strokes.len() != x.strokes.len() || (g.advance - x.advance).abs() > 1e-3 {
|
|
|
|
|
differ += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
assert!(
|
|
|
|
|
differ >= 20,
|
|
|
|
|
"only {differ} of 26 differ from the text font — the symbol font is \
|
|
|
|
|
drawing letters"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A compartment is measured by what it draws, not by where the pen lands.
|
|
|
|
|
/// The pen stops one letter-space past the last glyph — that gap belongs to
|
|
|
|
|
/// whatever comes next, and counting it made every compartment that much
|
|
|
|
|
/// too wide.
|
|
|
|
|
#[test]
|
|
|
|
|
fn compartments_are_measured_by_their_ink_not_the_trailing_pen_gap() {
|
|
|
|
|
// Cap height, so glyph units and world units line up.
|
|
|
|
|
let h = 9.0_f32;
|
|
|
|
|
for src in ["{\\Fgdt;r}", "{\\Fgdt;n}tol{\\Fgdt;m}", "1{\\Fgdt;m}", "A"] {
|
|
|
|
|
let cell = parse_cell(src);
|
|
|
|
|
let pen: f32 = cell.iter().map(|r| run_advance(&r.text, r.font, h)).sum();
|
|
|
|
|
let ink: f32 = cell
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|r| {
|
|
|
|
|
crate::entities::text_support::text_local_bounds(r.font, &r.text, h, 1.0, 0.0)
|
|
|
|
|
.map(|b| b.ink_max[0] - b.ink_min[0])
|
|
|
|
|
.unwrap_or(0.0)
|
|
|
|
|
})
|
|
|
|
|
.sum();
|
|
|
|
|
let got = content_width(&cell, h);
|
|
|
|
|
|
|
|
|
|
// Exactly one trailing gap comes off — the gaps BETWEEN runs are
|
|
|
|
|
// real spacing and must stay.
|
|
|
|
|
let spacing = letter_spacing(cell.last().unwrap().font, h);
|
|
|
|
|
assert!(
|
|
|
|
|
(got - (pen - spacing)).abs() < 1e-3,
|
|
|
|
|
"{src:?}: content {got} should be pen {pen} less one {spacing} gap"
|
|
|
|
|
);
|
|
|
|
|
let expected = ink + (cell.len() as f32 - 1.0) * spacing;
|
|
|
|
|
assert!(
|
|
|
|
|
(got - expected).abs() < 1e-3,
|
|
|
|
|
"{src:?}: content {got} should be ink {ink} plus {} inter-run gaps",
|
|
|
|
|
cell.len() - 1
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|