ACAD_SURFACE entities now load and render. Their NURBS faces are
tessellated by building a truck BSplineSurface from the ACIS `spline-surface`
control net and knot vectors, then sampling its parametric grid — reusing the
same NURBS kernel the Model tab already builds on. Analytic faces
(plane/cone/sphere/torus) continue through the existing tessellator.
- New spline_tess.rs: parse the ACIS `nubs` block (degrees, knots with the
clamped end-multiplicity fix, control grid) into a truck BSplineSurface and
mesh it.
- tessellate_surface + Surface wired through the mesh-build, fallback-wire,
and entity-classification paths.
- Bump acadrust to the commit that adds the Surface entity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Solid3D / Region / Body have no truck wire conversion, so the wire path
fell through to the generic fallback, which returns a 1-unit placeholder
segment at the origin — a stray little line next to every 3D solid. Give
these types their own fallback that emits only the entity's pre-computed
edge wires (empty for binary SAB) plus an insertion snap, never the
placeholder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The same three-arm `match entity { Solid3D | Region | Body }` block
appeared at five callsites — twice in tessellate.rs (point_of_reference,
fallback wires) and three times in scene/mod.rs (mesh tess dispatch,
apply_grip pre/post position).
Add three accessors in entities/solid3d.rs:
point_of_reference(e) → Option<&Vector3>
fallback_wires(e) → Option<&[Wire]>
tessellate_volume(e, color, facet_res) → Option<MeshLodSet>
Callsites shrink from 5–8 lines to one call. The apply_grip path
collapses two parallel pre/post matches plus the entity-list `matches!`
guard.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The trait isn't deprecating anything — it carries the path entities
take when they're not covered by the truck topology pipeline
(Viewport, Insert, Hatch outline, Ole2Frame). "Fallback" names the
purpose; "Legacy" implied retirement that isn't actually on the
roadmap.
Same rename for LegacyGeometry → FallbackGeometry,
legacy_geometry() → fallback_geometry().
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each entity's tessellation now lives alongside its definition in
entities/*.rs, attached to the entity type via a local trait:
LegacyTess — Viewport / Insert / Hatch / Ole2Frame
(fallback path for entities not on the truck pipeline)
LeaderTess — Leader
MultiLeaderTess — MultiLeader
DimensionTess — Dimension (carries its dim-only helpers too:
~1700 lines of dim_geometry, dim_text_*, format_*,
tolerance, suppression, etc.)
scene/tessellate.rs keeps only the cross-entity dispatcher plus the
shared dim machinery reused by leader/multileader/dimension
(ArrowKind, DimGeom, append_arrow, arrow_from_block, add_segment,
add_polyline, normalized_or, push_tri, color_or_inherit, entity_z,
offset_snap_pts). Cross-entity arc helpers + aci_to_rgba moved to
scene/tess_util.rs.
scene/tessellate.rs shrinks from ~3300 to 809 lines. Compile clean,
all 9 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lift the MText parser + renderer into a reusable `layout_mtext` helper in
`text_support` and re-point the remaining text-bearing entities at it, so
every inline format code (`\f`, `\C`/`\c`, `\H`, `\W`, `\Q`, `\T`, `\A`,
`\p…`, decorations, stacked fractions, paragraph indents, tab stops, …)
reaches the stroke output uniformly.
Per-entity changes:
- TABLE cell ([entities/table.rs]) — anchor + alignment math now feeds
`layout_mtext`; `\C7;Red`-style codes no longer render as literal
glyph noise.
- MLEADER MText content (both entity path in [entities/multileader.rs]
and the LOD-aware path in [scene/tessellate.rs]) — drops the
`strip_mtext_codes` + per-line `tessellate_text_ex` chain; the LOD 0
baseline / LOD 1 greek rect / LOD 2 stroke ladder and the text-frame /
bg-fill rectangle now read line widths from the layout.
- ATTRIB / ATTDEF — when `mtext_flag` is MultiLine / ConstantMultiLine
the value goes through the MText pipeline (SingleLine stays on the
Text-style anchor maths because the DXF spec disallows codes there).
- DIMENSION text_override — `dimension_text_entity` emits a synthetic
MText (instead of Text) whenever the value contains MText escapes;
the LOD ladder via `tessellate_entity_dim_text` handles both kinds.
Infrastructure:
- `MTextVAnchor` gains `MiddleOfTopLine`, `MiddleOfBottomLine`,
`BottomOfTopLine` so MLEADER's 9 attachment variants all map cleanly.
- `MTextLayout` returns `line_widths` / `line_height` / `v_offset` so
callers that draw chrome (frames, fills, baselines, greek rects)
share the same numbers as the rendered strokes.
- `mtext_line_count` re-routed through the new parser so LOD row counts
and rendered row counts can never disagree.
- Legacy helpers (`strip_mtext_codes`, `split_mtext_lines`,
`measure_mtext_chars`, `word_wrap`, `cxf::measure_text`, the
`v_offset_for_attachment` MLEADER offset table, `MTextLine::plain_text`)
deleted — all callers migrated.
Audit (TEXT_FORMAT_CODE_INTEGRATION.md) removed; its plan is implemented
here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Moves the Dimension and Table baked-block paths up into
`tessellate_entity` so sub-Text/MText recurse through the same LOD
ladder (baseline / greek / full) the top-level entities already use,
and routes the synth-dim text path through `tessellate_entity` as well
so files that omit `block_name` (e.g. typical DXFs) also get LOD.
Extends the text-height LOD branch from Text/MText to
AttributeDefinition, AttributeEntity, and Tolerance — the OBB helpers
gained the same coverage so the baseline/greek rect lines up.
When a sub-entity inside a baked dim/table block has ByBlock colour,
the recursive call's resolved colour is replaced with the parent's so
dimension text no longer renders in the raw ByBlock fallback hue.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AutoCAD bakes a per-instance block with the final, displayed geometry
into Dimension and Table entities (typically *D### / *T###, but custom
names also occur — sample-file-H uses DIMBLOCK*-4NP). The block's text
already encodes DIMTXT * DIMSCALE (or the table's scale factor), so
synthesising the entity from the dim/table style on top of that
double-applies the scale.
When the referenced block exists with content, render it directly:
- Dimension: dim.base.block_name -> block_record
- Table: block_record_handle -> block_record
The synthesis fallback stays in place for files where the block is
missing or empty.
Fixes sample-file-H.dwg dimensions rendering at 60000 instead of 200
(DIMTXT=199.196 * DIMSCALE=304.8).
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>
MultiLeader and Table were ignoring most of the styling and content
fields that the parser populated. Bring both up to file-driven render:
MultiLeader
- line_color: leader stroke / arrow colour with ByBlock/ByLayer fallback
- line_type_handle: dashed leader via line_types lookup by handle
- line_weight (ml.line_weight): per-leader pixel width override
- arrowhead_handle: arrow shape resolved through the dim arrow emitter
(Closed Filled / Open / Dot / Origin / Box / Datum / Tick / None)
- extend_leader_to_text: continue the leader past the landing to the
text insertion point
- text_angle_type: Horizontal forces 0, Optimized clamps to a readable
half-plane, ParallelToLastLeaderLine keeps the stored direction
- text_direction_negative: adds π to the rotation
- text_alignment: drives the horizontal anchor when more specific than
the context's text_attachment_point
- text_attachment_direction + text_top_attachment + text_bottom_attachment:
Vertical mode uses the top / bottom attachment values chosen from the
first root's direction.y sign
- has_block_contents + block_content_handle + block_content_location +
block_rotation + block_scale + block_content_color: synthesise an
Insert and explode it through the standard tessellator so block-
content MultiLeaders render their referenced block
- style_handle, arrowhead_handle, line_type_handle, text_style_handle,
block_content_handle, property_override_flags, text_align_in_ipe,
block_scale, block_rotation, extend_leader_to_text,
text_direction_negative, text_top_attachment, text_bottom_attachment
surfaced in the Properties panel
Table
- Per-cell borders: drop the always-grid pass, walk the cells and
emit each border honouring its `invisible` flag with dedup so shared
edges aren't doubled
- table_style_handle, block_record_handle, data_version, value_flags,
override_flag / override_border_* flags, break_options /
break_flow_direction / break_spacing, normal surfaced in the
Properties panel
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes out the file-driven dim render. Remaining entity fields and the
last handful of DimStyle knobs are now honoured:
- leader_length: extend the radius/diameter dim line past the arrow tip
toward the text by this distance
- ext_line_rotation: rotate extension lines off perpendicular (DIMEDIT
Oblique result) for Linear / Aligned dims
- attachment_point: map the 1..9 grid to Text horizontal / vertical
alignment so the saved anchor is used
- horizontal_direction: use as in-plane text reading direction override
- DIMTXSTY by handle: prefer dimtxsty_handle over the name; survives
text-style renames
- DIMTFAC + DIMTOLJ: tolerance text rendered as a separate Text entity
scaled by DIMTFAC and aligned vertically by DIMTOLJ
- DIMALTTD + DIMALTTZ: alternate-units tolerance suffix formatted with
these decimal-place / zero-suppression knobs
- DIMARCSYM / DIMJOGANG / DIMUNIT / normal / line_spacing_factor /
insertion_point / block_name / version: read so the file round-trips
but no-op in the render path until the matching dim variants / 3D
text plane are supported
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tessellation and DIMBASELINE pick up the few DimStyle fields that were
still hard-coded or ignored:
- DIMTMOVE = 1 draws a connector segment from the dim-line anchor to the
saved text_middle_point when the text has been moved
- Leader arrows are resolved via the leader's DIMSTYLE → DIMLDRBLK; the
shape (closed-filled / open / dot / tick / …) is drawn through the same
arrow emitter the dim path uses, including fill_tris for filled blocks
- DIMBASELINE picks up DIMDLI from the active DimStyle instead of the
hard-coded 1.5 increment
- DIMTOFL / DIMTIX / DIMATFIT / DIMUPT / DIMTXTDIRECTION are read so the
fields round-trip on save; their effect is at dim creation, not render,
so they're acknowledged with a no-op block
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drives more of the dim render off the parsed DimStyle so files round-trip
visually identical to AutoCAD instead of using hard-coded fallbacks:
- text format dispatch via DIMLUNIT (Sci / Decimal / Engineering /
Architectural / Fractional / Windows) + DIMFRAC fraction denominator
- alternate units appended in brackets per DIMALT / DIMALTF / DIMALTD /
DIMALTU / DIMALTRND / DIMAPOST / DIMALTZ
- tolerance / limits text per DIMTOL / DIMLIM / DIMTP / DIMTM / DIMTDEC /
DIMTZIN; "value ± t", "+tp / -tm", or stacked "high/low"
- center mark for radius/diameter from DIMCEN (positive = "+", negative =
"+" plus radial strokes)
- DIMDLE dim-line overshoot at tick endpoints
- DIMJUST horizontal text slide along the dim axis
- DIMTVP perpendicular text offset multiplier when DIMTAD=0
- DIMFXL/DIMFXLON fixed extension-line length override
- DIMTFILL/DIMTFILLCLR text background fill rectangle
- DIMLTEX/DIMLTEX1/DIMLTEX2 linetype handles resolved per-wire; ext1 and
ext2 split into separate wires when their patterns differ
- DIMSOXD read; left as a no-op until autofit is added
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dimension tessellation was hardcoding arrow shape (open-V line pair) and
text format ("{:.4}"), ignoring almost every DimStyle field the parser
populated. Drives the wire/text geometry from the resolved DimStyle:
- text: DIMDEC, DIMZIN, DIMLFAC, DIMRND, DIMDSEP, DIMPOST, DIMADEC,
DIMAZIN, DIMAUNIT (decimal/DMS/grad/rad), DIMTXSTY
- arrows: DIMBLK/DIMBLK1/DIMBLK2/DIMSAH resolution against block_records,
supporting ClosedFilled (default), Closed/Closed-Blank, Small, Open
(30/90), Dot (small/blank), Origin, Oblique/ArchTick, Box, Datum, None;
DIMTSZ overrides to oblique ticks
- colours: DIMCLRD / DIMCLRE / DIMCLRT split into three wires (ext, dim,
text); BYBLOCK/BYLAYER fall through to entity colour
- weights: DIMLWD / DIMLWE drive line_weight_px per wire
- suppress: DIMSE1/DIMSE2 (ext) and DIMSD1+DIMSD2 (dim) honoured
- text placement: DIMTAD vertical offset using DIMGAP + text height when
text_middle_point is unset; DIMTIH/DIMTOH force horizontal text
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The shared `arc_segments` helper now takes (radius, span, chord_tol) so
each call site can pick its own tolerance:
* Hatch fill polygon (built at load / on edit, never re-tessellated)
uses `fill_chord_tol(r) = max(0.001*r, 1µm)` — fixed high quality so
zoom-in stays sharp without rebuilding the boundary.
* Hatch wire outline (rebuilt every frame inside the render scope) uses
`wire_chord_tol(r) = min(scene_override, fill_chord_tol(r))` —
reads the per-frame `truck_tess::set_curve_tol_override` (the Scene
already sets it to `world_per_pixel × 0.5` for Phase 3.2 wires) so
far-out boundary arcs collapse to a handful of segments at low zoom
while staying at least as sharp as the underlying fill at high zoom.
Same arc_segments switch applied to the legacy 16-segments-per-circle
formula in `hatch_model_from_dxf`'s polyline-bulge tessellation, so
fill boundaries with bulged polylines also gain the proper chord-error
target.
Also expose `truck_tess::current_curve_tol` (pub(crate)) and add
`active_curve_tol()` returning Option so the new wire path can
distinguish "scene override active" from "load-time, no zoom info".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The hatch fill polygon (`hatch_model_from_dxf`) handled `BoundaryEdge::
Polyline` vertex bulges correctly — sampling arcs between v0 → v1 when
v0.z (bulge) was non-zero. The wire outline path
(`legacy_geometry::Hatch`) just pushed (x, y) for every vertex and
ignored v.z, so any bulged boundary segment drew as a straight chord
even though the fill underneath was correctly curved.
Mirror the bulge-arc tessellation into the wire path, reusing the
shared `arc_segments` helper from this module. Polyline edges now emit
the arc start vertex + sampled arc points + the trailing endpoint, with
a NaN separator added between distinct edges so connected boundaries
don't draw spurious chords from one edge's last point to the next
edge's first.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
af1e655's wrap-aware `arc_signed_span` was correct per a strict reading
of the DXF "is_counterclockwise" spec but caused CW boundary arcs on
real files to sweep the long way around the circle, flipping arc
direction and filling huge unintended regions.
Restore the legacy `(TAU - sa, TAU - ea)` flip semantics. The adaptive
`arc_segments` count fix from the same commit is kept — the visible
faceting issue on large-radius arcs was orthogonal to direction and the
revert affects only the span computation.
The wrap-through-2π edge case the previous commit tried to address is
back to being a known limitation. A proper fix needs to know how each
upstream writer (AutoCAD, BricsCAD, ODA, etc.) actually encodes wrap
arcs and isn't possible without sample files exercising the case.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Hatch boundary CircularArc / EllipticArc tessellation had two bugs that
showed up as polygonal-looking arc edges and, on certain arcs, edges
that drew the long way around the seam:
1. Fixed 32 segments per full circle gave ~5 mm chord-height error at
r = 1 m, visibly polygonal at typical hatch radii. Replace with an
adaptive count targeting ~0.1% chord error (≈ 5° max step), floor 8,
cap 256.
2. The `(TAU - sa, TAU - ea)` flip for CW arcs was direction-correct
for non-wrap inputs but silently went the long way around whenever
the short path crossed the 0 / 2π seam (e.g. a CCW arc from 3π/2 to
π/2 should sweep east through 0; the old code swept west through π).
Replace with `arc_signed_span` which derives a signed span that wraps
through 2π in the correct direction.
Shared helpers (`arc_signed_span`, `arc_segments`) live in
`scene/tessellate.rs` and feed both the hatch fill boundary builder
(`scene/mod.rs::hatch_model_from_dxf`) and the legacy wire fallback
(`scene/tessellate.rs::legacy_geometry`). Same fix applied to the
EllipticArc paths in both. Dropped the unused module-level `TAU`
constant the old code carried.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* helpers::wcs_to_ucs — UCS inverse transform with zero call sites.
* scene::Scene::compute_and_set_world_offset — never called; the load
path uses the free function `compute_world_offset` directly.
* tessellate::tessellate_mesh — never called; mesh tessellation goes
through `solid3d_tess`, not the truck Shell/Solid path.
* truck_tess::tess_to_mesh_model — orphaned with tessellate_mesh's
removal.
* MeshModel::WHITE / MeshModel::SELECTED — only used by the removed
tessellate_mesh.
* xattach::Step::FilePath variant — never constructed; XATTACH always
enters through `with_path` after the file picker. Replaced the Step
enum + prefilled_path field with flat `path` / `block_name` fields
and dropped the unreachable text-input flow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Greek rects rendered through fill_tris went through the face3d 0.45
dim path; the pre-boost (color × 1/0.45) compensated only until clamp
hit at 1.0, leaving bright user colors washed out. The earlier outline
workaround dodged the dim but lost the filled box visual that AutoCAD
uses for "text lives here".
- face3d pipeline now skips the 0.45 dim when `wire.points` is
empty. PolyfaceMesh / PolygonMesh keep their dim (both points and
fill_tris populated); greek and MultiLeader background-fill emit
`points: vec![]` so they render at the literal color.
- All three greek paths (top-level, block-defn, MultiLeader) emit a
2-triangle filled rect instead of an outline / boosted fill.
- Greek rects are clamped to single-line height so 5-line MText
doesn't blow the box up to the full text-block height. MultiLeader
emits one rect per line — keeps the per-row visual hint.
- BatchEntry/StyleKey gain an `is_fill_only` discriminator so greek
batches never collide with regular wire batches; otherwise the
finalized WireModel could carry both points and fill_tris and
defeat the pipeline-side empty-points check.
Xref fade still applies to the resolved color via the existing
`fade_toward_bg` path, so xref greek stays faded as expected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
MultiLeader's text strokes go through cxf::tessellate_text_ex per-glyph
regardless of zoom — same waste the top-level Text/MText path used to
have. Mirror the 1/5 px ladder here:
h_px < 1 → top-line baseline (skipped if its own length is < 2 px)
1 ≤ h < 5 → greek rect at the tight text bbox, color pre-boosted for
the face3d 0.45 fill_tris dim
h_px ≥ 5 → full per-glyph stroke (existing path, untouched)
Frame and background-fill rects keep rendering at every LOD — only the
glyph payload is collapsed.
Plain Leader entities don't bake their own text (annotation lives in a
separate linked MText), so they already inherit the top-level Text/MText
ladder; no change there.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entity converters were casting WCS coordinates to f32 before the renderer
subtracted world_offset, which destroyed sub-unit precision on drawings
sitting at large coordinates (typical UTM-style CAD files). The multileader
case made it visible — glyphs warped under rotation — but every entity
that emits TruckObject::Lines/SegmentedLines/key_vertices/fill_tris was
affected.
Type changes (in src/scene/acad_to_truck.rs):
- TruckObject::Lines / SegmentedLines: Vec<[f32; 3]> → Vec<[f64; 3]>
- TruckEntity::key_vertices / fill_tris: f32 → f64
All ~17 producer files updated to keep WCS coordinates in f64 right up to
the TruckEntity boundary. The consumer in scene/tessellate.rs now performs
`(x - ox) as f32` (f64 subtract, then cast) for points, key_vertices, and
fill_tris — preserving precision near the camera origin even when the
drawing sits at million-unit coordinates.
catmull_rom_pts (multileader spline) is now f64; the scene path widens its
offset-relative f32 ctrl points to f64 around the call and narrows back at
the output.
Notes:
- Table layout still uses glam::Vec3 (f32) internally; widened to f64 only
at the TruckEntity boundary for type compatibility. Separate cleanup to
switch its internal math to f64 is tracked.
- snap_pts and tangent_geoms remain f32 — they are UI feedback (snap
candidates, tangent hints) and the precision is sufficient there.
Multileader text was emitted as a single literal stroke pass with no MText
code handling, no rotation, no attachment, no line breaks, no per-piece
color and bad f32 precision under world_offset. Two separate render paths
both had the same bug, and one was masking the fix in the other.
This rewrite, applied to both paths:
- Strips MText format codes (`{\fArial Black|b0|i0|c162|p34;...}`) so the
rendered text is the actual content, not the markup.
- Honors text_rotation (preferring text_direction so transforms survive),
text_attachment_point (Left/Center/Right horizontal anchor), and
text_left_attachment for vertical alignment via a shared
v_offset_for_attachment helper.
- Splits on \P/\n/\N and word-wraps to text_width.
- Applies line_spacing_factor with the same 5/3 × height convention as
MText.
- Resolves the text style via text_style_handle (fallback STANDARD).
- Applies ml.scale_factor everywhere (arrow size, dogleg length, text
height, text width). anno_scale is now only applied when
enable_annotation_scale is set.
- Splits the scene-level output into separate WireModels so leader,
text, frame and background fill each carry their own colour
(ctx.text_color, ml.text_frame, ctx.background_fill_color).
- Mirrors text_direction, base_direction and root.direction in
apply_transform so a mirrored multileader's text follows the geometry
instead of staying at its original rotation.
- Computes the text origin and frame corners in offset-relative f32
space — drawings sit at large absolute coordinates and casting to f32
before subtracting world_offset was wrecking the rotated sub-glyph
precision.
Use reflection (TAU - sa, TAU - ea) for CW arcs and span.abs() for
segment count, consistent with the CircularArc pattern in both
hatch boundary (mod.rs) and tessellate (tessellate.rs).
Co-Authored-By: Claude Sonnet 4.6 <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
- Hatch pattern_scale multiplied by annotation_scale in model space so
pattern density stays consistent when CANNOSCALE changes
- Annotative check now uses AcAnnoPO xdata (R2007+ marker) instead of
relying solely on group-code-293 flag; falls back to is_annotative
for files with no xdata (old DXF backward compat)
- acadrust patched to local path for is_annotative field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scale popup dropdown replaces cycle behavior; paper-space viewport scale
editable when a viewport is active/selected
- CANNOSCALE persisted via DXF $USERR1 (read on open, write on save)
- PSLTSCALE: linetype dash lengths scaled by viewport anno_scale so dashes
appear uniform physical size in paper space
- MultiLeader: arrow size, dogleg length, and text height scale with
anno_scale; leader line paths (world coords) stay fixed
- Leader: arrow size and hookline length scale with anno_scale; also
corrects world_offset subtraction that was previously deferred to the
TruckObject::Lines path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Viewport-projected wires now clip at pixel level to their viewport boundary.
- WireModel.vp_scissor: Option<[f32; 4]> — paper-space bbox set on projection
- WireGpu.vp_scissor carries it through to the draw loop
- Pipeline.compute_wire_scissors() converts paper→NDC→pixel each frame
- render() calls set_scissor_rect() per wire, reset after viewport group
- INTEGRATION_GAPS.md: Viewport ⚠️ → ✅, all 51 gaps resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LwPolyline PLINEGEN=0 (DXF default): emit SegmentedLines with NaN
separators between vertex-to-vertex segments; wire_gpu resets
arc-length to 0 after each NaN so linetype pattern restarts per vertex.
PLINEGEN=1: existing Contour path unchanged (continuous pattern).
WireModel gains plinegen field propagated through tessellate pipeline.
Solid3D/Region/Body: call SabReader::read() when parse_sat() returns
None due to binary SAB format (DWG files / newer AutoCAD exports).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dimension measurement origin points (first_point, second_point,
definition_point, angle_vertex, etc.) are now emitted as Node snap
hints so OSNAP Node mode can locate them precisely without relying on
the tessellated geometry wire alone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hatch boundaries with arc or ellipse edges were silently dropped from
the wire outline, leaving gaps in the hatch contour. CircularArc and
EllipticArc boundary edges are now tessellated as polyline segments and
their centers are emitted as Center snap hints.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Three root causes caused all geometry and UCS arrows to disappear:
1. TruckObject::Lines and TruckObject::Text variants returned world-space
f32 coordinates without applying world_offset, so fit_all() computed
bounds spanning from local space to UTM world space (~4 M m), placing
the camera impossibly far from all visible geometry.
2. Ray/XLine entities with unnormalized direction vectors (common in some
DXF exporters) produced astronomically large far-point coordinates
(up to 1e28) that overwhelmed the camera fit even after the offset fix.
Fixed by normalizing the direction vector before multiplying by
DISPLAY_EXTENT.
3. Origin-stuck corrupted entities (coordinates ≈ 0 in world space become
≈ -world_offset in local space) pulled the fit_all() bounds far from
the actual drawing. Fixed by computing local_extent_max from EXTMIN/EXTMAX
(10× safety margin) and skipping points outside that range in fit_all().
Additional hardening: wire_gpu.rs now skips ±inf segments (not just NaN),
and snap_pts/key_vertices in all TruckObject variants are offset-corrected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Subtracts the DXF header extents centroid (world_offset) from all coordinates
in f64 BEFORE casting to f32. This eliminates the ~0.5 m ULP error that occurs
at 4,000,000 m UTM coordinates and caused geometry to disappear on zoom-out.
- Scene: adds world_offset field + compute_and_set_world_offset() method
- tessellate.rs: threads world_offset through all f64→f32 conversions
(legacy_geometry, dimension_geometry, solid_wire_fallback, multileader)
- truck_tess.rs: to_local() helper already in place from prior session
- mod.rs: tessellate_entity() and entity_aabb() apply world_offset
- update.rs: compute_and_set_world_offset() called after doc load
- cmd_result.rs / solid3d_cmds.rs: pass correct offset to truck tessellators
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extension lines for linear and aligned dimensions now start at
DIMEXO × DIMSCALE distance from the definition point (instead of at the
point itself) and extend DIMEXE × DIMSCALE units past the dimension
line, matching AutoCAD rendering. Both values fall back to 0 when the
dimstyle is not found.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Arrow size for all dimension types (linear, aligned, radius, diameter,
angular) is now read from the associated DimStyle's dimasz field scaled
by dimscale, instead of being hardcoded at 0.12. Falls back to 0.12
when the style isn't found in the document's dim_styles table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
INSERT entities now expose their insertion point as a SnapHint::Insertion
pre-baked snap candidate, enabling OSNAP Insertion to locate block
reference origins. The cross marker is also positioned at the actual
insertion point instead of the origin.
Hatch entities now populate key_vertices from Polyline and Line boundary
edges (with correct elevation Z), enabling Endpoint and Midpoint snapping
to hatch boundary geometry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
acadrust's DXF reader does not parse group code 53 (text rotation),
so DimensionBase.text_rotation is always zero for DXF-loaded files.
Fall back to the natural axis-aligned rotation computed from geometry:
Linear dimensions use d.rotation (radians), Aligned dimensions use
atan2 of the second-first vector. Normalize to (-π/2, π/2] so text
never appears upside-down. Explicit non-zero overrides stored by the
DWG reader are preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
append_linear_dimension() applied the same perp offset to both d1 and d2,
but only d2 is guaranteed to be at the definition_point level; d1 must be
projected independently onto the dimension line, otherwise non-collinear
first/second points produce a parallelogram instead of a rectangle.
Fix: compute dim_line_pos = def.dot(perp) and project each endpoint
separately so the dimension line is always parallel to axis.
Additionally, the acadrust DXF reader stores LinearDimension.rotation from
group code 50 (degrees) without converting to radians, while DWG and our
own annotation commands store it in radians. Add a post-load fixup in
load_file() that converts the rotation to radians for DXF files so
tessellation can call cos/sin uniformly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `aabb: [f32; 4]` field to WireModel (world-space 2-D bounding box).
In tessellate_entity() set each wire's AABB from e.as_entity().bounding_box()
via new entity_aabb() helper; Insert sub-entities each get their own AABB.
Preview/interim wires and any entity returning a zero-extent default box get
UNBOUNDED_AABB so they are never pre-rejected.
Replace the chord-sphere heuristic in Snapper::wire_in_range() with a proper
AABB vs snap-circle overlap test. The new check is four scalar comparisons and
correctly handles closed curves (circles, arcs) which the chord-sphere approach
had to special-case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- When SAT tessellation yields no mesh (binary SAB or unsupported ACIS
geometry), render the pre-computed edge-wire polylines stored in
Solid3D / Region / Body entities as a visible line fallback.
- Ole2Frame entities now render a bounding rectangle with diagonal X
instead of an invisible stub — marks embedded OLE objects in the
viewport.
Closes ROADMAP 2.13 (partial), 3.2 (Ole2Frame).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>