Grip markers drew visibly offset from the entity on large-coordinate
(UTM-scale) drawings: every Grippable impl cast the f64 entity coordinates
to f32 BEFORE the world-offset subtraction, and at 1e7 magnitude an f32
holds ~0.9 drawing units of rounding error. The wires subtract the offset
in f64 first (precision-preserving), so the grips landed up to ~1 unit
away from the geometry they belong to.
GripDef.world is now DVec3: producers fill it straight from the f64
entity data, refresh_selected_grips subtracts the world offset in f64,
and only the small offset-local result is cast to f32 for screen-space
projection (grips_to_screen / find_hit_grip) and drag state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the small QCAD CXF stroke-font set with the full LibreCAD LFF font
library (46 fonts) and a new LFF parser/engine in src/scene/lff.rs. The
parser reads the `[hex] char` glyph blocks, tessellates `,A<bulge>` arcs, and
resolves `C<hex>` glyph references. Per LibreCAD's RS_Polyline convention a
vertex's bulge curves the segment ending at that vertex — getting this wrong
turned round letters like O into sideways capsules.
Font name resolution mirrors LibreCAD: match by file stem, map common
AutoCAD/DXF SHX names (txt, isocp, complex, italic, gothic*, gost, …) to the
nearest LFF family, and fall back to `standard`. A missing glyph in the chosen
family falls back to the broad `unicode` font so accented/Cyrillic/Greek text
still renders. Cap height stays 9 units, so the text scale is unchanged.
All text consumers move from cxf:: to lff::; complex linetypes still render
embedded text but no longer draw embedded shapes (LibreCAD ships no shape
file). The CXF module and every *.cxf asset are removed, and the MText / text
style font pickers list the LFF families.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two free helpers in app/commands.rs lived as big match-on-EntityType
blocks: massprop_entity (Circle/Arc/Line/LwPolyline/Ellipse area +
perimeter + centroid, ~140 lines) and entity_text_content /
replace_entity_text (Text/MText/AttributeDefinition/AttributeEntity).
Move each to its own trait in entities/traits.rs and implement
per-entity:
MassPropsCalc — Circle, Arc, Line, LwPolyline, Ellipse
TextContent — Text, MText, AttributeDefinition, AttributeEntity
EntityTypeOps gets dispatch shims that route to the trait via the
existing macro, so callsites just call entity.mass_props() /
entity.text_content() / entity.replace_text(search, rep).
The remaining free helpers in commands.rs (entity_list_details,
entity_extra_info, entity_type_name, build_data_extraction_csv) are
single-command formatting helpers tightly coupled to LIST / EXTRACT /
text-format output. Distributing them as per-entity methods would
inflate every entity file with command-side formatting concerns
without making the command-side any cleaner — left in place.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- MultiLeader: stop double-scaling when ctx.text_height (WCS-baked) is set;
use it as-is and only multiply by scale_factor + anno on the fallback path.
ctx.text_width gets the same treatment.
- Text / Attribute: width_factor and oblique_angle are entity-authoritative.
The previous entity*style multiplication / addition mis-rendered any text
whose TXSTYLE had a non-default width_factor or oblique_angle (RR 0.6,
STYLE2 15°, Romans/ARIEL/A32 0.65-0.8 in the sample file).
- MText: honour TXSTYLE is_backward (negative width_factor mirror) and
is_upside_down (180° rotation). measure_mtext_chars uses positive
magnitude so anchor math stays correct under backward text.
- Tolerance: use tol.dimension_gap when set instead of the hard-coded
0.35 * height heuristic.
- Table: cell text height / font / alignment resolve through
CellContent -> CellStyle -> TableStyle.{title|header|data}_row_style
-> compiled-in defaults. Row classification respects the suppressed
flags. Cell + content rotation applied.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PolyfaceMesh and PolygonMesh were wireframe-only. They now render with
the same flat-color solid fill as Face3D entities.
- Add fill_tris: Vec<[f32;3]> to TruckEntity and WireModel
- PolyfaceMesh: fan-triangulate each face into fill_tris
- PolygonMesh: triangulate each grid quad into fill_tris
- tessellate.rs Lines branch: apply world_offset to fill_tris
- Face3DGpu::from_wires: collect fill_tris from all wires in addition
to Face3D key_vertices quads
- upload_face3d: pass all_wires so fill_tris are batched into one draw call
- fix: polyline arc center sign was inverted (- → +) causing arcs to render on the
wrong side in LwPolyline and Polyline2D
- fix: store glyph strokes in local space with f64 origin so world_offset subtraction
uses f64 precision; prevents blocky text at large UTM coordinates
- fix: Face3D fill darkened to 45% so edge wires are visually distinct from fill
- fix: wire batch vertex buffer chunked to 256 MB GPU limit (was panic on large files)
- fix: gpu_face3d_edges changed from Option<WireGpu> to Vec<WireGpu> to support chunks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spline entities now expose fit_points (or control_points when no fit
points are defined) as key_vertices, enabling Endpoint and Midpoint
snap to reach spline construction points.
TextStyle is_backward flag is applied by negating the width_factor,
producing horizontally mirrored text. is_upside_down rotates the text
180°. Both tessellate_text_ex and text_local_bounds now handle negative
width_factor without clamping it to zero.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Text/MText values now correctly handle DXF special-character sequences:
- %%d → °, %%p → ±, %%c → ⌀, %%nnn → Unicode scalar (resolved in tessellation)
- %%u / %%o toggle underline and overline strokes for TEXT entities
MText decoration codes (\L/\l, \O/\o, \K/\k) were previously consumed
as argument-bearing codes, silently eating the decorated text. Fixed
strip_mtext_codes to keep them as zero-width markers; tessellate_text_ex
now emits actual line strokes (underline, overline, strikethrough).
measure_mtext_chars and word_wrap skip markers so line widths are correct.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>