Share source buffers across block placements and render nested content with GPU instance transforms.\n\nChunk silhouettes and compact mesh material data so large drawings stay under device buffer limits and remain responsive.
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Companion to the acadrust DXF-fidelity sweep (bump to 379ce12):
- Legacy LEADER hookline: underline the annotation using the leader's own
stored text width (DXF code 41) when the MTEXT has no computed extents,
so the landing spans the text instead of a short stub.
- SHAPE: resolve the glyph by name when the shape number is 0 (DXF SHAPE
carries only the name), and search shape-file text styles when the entity
has no direct style reference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three defects made a leader's arrowhead render as one stray line far
longer than the leader itself, at UTM coordinates:
- The arrowhead was drawn even when it is larger than the first leader
segment it sits on. A DIMASZ sized for a bigger plot (here 6 units on a
~1-4 unit pointer) painted a stroke longer than the whole leader.
AutoCAD omits the arrowhead when it does not fit the first segment, so
gate the arrow on `arrow_size <= first_segment_len`.
- Leader geometry was built as absolute f32. At UTM coordinates (Y ~ 4.5M,
f32 ulp ~ 0.5) the oblique arrow tick's small cross-slope quantised
away, collapsing the 45-degree tick into a flat horizontal line. Build
the path and arrow in absolute f64 — the arrow around a local origin so
append_arrow's f32 math stays precise — then RTE-split into the double
single (points_low / fill_tris_low) at the end.
- The `_Oblique` / `_Archtick` arrowhead block mapped to ArrowKind::Tick
with the full DIMASZ, but the tick emitter draws `size` to either side
(half-length semantics, matching DIMTSZ). Pass half so an oblique
arrowhead sizes to DIMASZ like every other arrowhead, not double.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DWG stores no "hookline exists" flag (only the direction bit), so every
DWG-loaded leader kept hookline_enabled=false and drew no hook. Derive
it: a text-annotated leader hooks when its final segment leaves the
horizontal by more than 15°, shared by the render and snap/grip paths.
Hook length comes from the dim style like the arrowhead (DIMASZ ×
DIMSCALE), and with DIMTAD "text above" it extends under the annotation
to the far edge of the MTEXT's laid-out extents — newly exposed by
acadrust (lock bumped); the old text-height heuristic stays as the
no-style fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Xref merge now inserts entities in the source block's chain order and
carries SortEntitiesTable objects across (remapped to merged handles).
draw_depth_map values become [depth, half] so exploded block/xref fills
and wide-polyline band wires both compose child depth as
insert_depth + in-block rank x half, recursing into nested inserts with
the same divisor - bands and fills from one block interleave by the
block's internal order instead of stacking at the insert's single depth.
Band batches split per rank via WireModel.depth_override; thin wires
stay merged. The wire arena falls back to a full rebuild when a slab
carries mixed depths so refreshes don't flatten band overrides.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A wide polyline with per-vertex varying widths rendered at its widest edge
(uniform-width ones were already exact). Draw the true taper instead.
`WireModel.taper_widths` carries a per-point full band width. The wire
instance (native slim + web fat) gains per-endpoint `world_hw_a/b`, and both
wire shaders interpolate `mix(hw_a, hw_b, which_end)` so each segment's band
narrows/widens linearly — depth-tested, and the linetype dash still applies.
Alignment (widths must match the tessellated points) is solved by building
both together: a new `TruckObject::TaperedLines(points, widths)`, filled by
`common::tapered_band_points` (straight = endpoints, arc = 16 samples with the
width lerped along it). to_truck routes a varying-width polyline here —
LwPolyline before the PLINEGEN split, Polyline2D at its return — while a
uniform-width one keeps the constant-band Contour path unchanged. `world_width`
stays the widest edge as the PDF-stroke + zoom-floor fallback.
Blocks fall back to the constant-widest band (`LocalWire` carries no
taper_widths).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A wide polyline's flat band was drawn by the hatch pipeline: a solid fill
with only draw-order depth. It ignored the entity linetype (the width
"crushed" the dash pattern) and showed through 3-D geometry in a tilted view.
Draw the band from the polyline's own centre-line wire instead. A new
`WireModel.world_width` (drawing units) makes the wire vertex shader expand
the quad by `world_half_width / world_per_pixel` (clamped to a hairline), so
the band is sized in world units and tracks zoom. Because it is now a wire:
- the GPU dash shader dashes the band by arc-length — the linetype applies;
- it is depth-tested at the polyline elevation — no draw-order see-through;
- no CPU band triangles or per-dash geometry are generated.
`world_half_width` rides in `WireConst` (native, replacing a pad) and the web
`WireInstance` (loc 14); both wire shaders branch on it. The tessellator sets
`world_width` from the LwPolyline width on the Contour / SegmentedLines arms,
and the model-space fill loop no longer emits a hatch band for any LwPolyline.
Pick still uses the band polygon (`pick_tris`).
Blocks keep the hatch band: `LocalWire` carries no `world_width` (finalize
zeroes it), so a scaled insert still scales its band correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rectangular paper viewports now go through the same GPU stencil clip as
non-rectangular ones: viewport_clip_boundary_ndc returns the four rect
corners (a full-target mask) instead of nothing, so the render path has
no rect/non-rect special case — every content viewport carries a clip
boundary and draws with stencil reference 0xFF.
With a single clip path, the old per-wire / -image / -wipeout scissor
mechanism is fully dead and removed: compute_*_scissors, the
*_pixel_scissors buffers, the project_scissor helper, the per-pass
set_scissor_rect blocks, the vp_scissor field threaded through
WireModel / ImageModel / HatchModel and their GPU mirrors (WireGpu /
ImageGpu / WipeoutGpu / HatchWebGpu), from_run's scissor parameter, the
scissor grouping in build_wire_buffers, and the is_arena_eligible gate.
Net -245 lines across 54 files.
Rectangular viewports still clip exactly via their own render rectangle;
the stencil mask is a redundant full-target no-op there, kept only so
the clip code path stays uniform. XCLIP keeps its CPU wire clip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A SOLID's filled quad and a dimension/MultiLeader text background are flat 2-D
overlays that should draw in wireframe and shaded alike, but face3d_gpu
classified fills by `!fill_tris_low.is_empty()` — and a 2-D fill at UTM scale
carries a double-single low residual for precision just like a real 3-D mesh
face. So the arrowhead fill and the "5;8" cyan text background were routed to
the 3-D buffer and only appeared in shaded mode, vanishing back to nothing on
return to wireframe.
WireModel now carries an explicit `fill_is_3d`: true only for a real surface
(PolyfaceMesh / PolygonMesh face, set in the tessellator), false for every flat
overlay fill. face3d_gpu classifies on the flag, so 2-D fills stay in the
always-visible buffer while keeping their low residual (and UTM precision).
Verified on budweiser: the `_BoxFilled` arrow SOLID and the dim '5;8' MTEXT
background both tessellate with fill_is_3d = false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
MLINE dashed elements were fully CPU-expanded (apply_along) — one wire per
dash — to keep the parallel lines in phase. Move pure-dash elements onto the
GPU dash shader and end-align every element to the multiline's centre-line so
they share one interior phase.
- wire_distances now accumulates arc-length from the double-single (high+low)
points, so the dash phase stays precise at UTM coordinates. f32-high-only
deltas quantised ~0.1 there and drifted the parallel lines apart — the
original reason MLINE dashes were CPU-side.
- New WireModel.dash_align_end carries a shared begin/end dash length. The
shader uses it as align_end for every parallel element (the interior phase
depends on align_end, not the wire's own length) while align_total stays the
element's own length, so each still ends on a dash at its own endpoint.
- MLINE tessellation derives that length once from the centre-line, routes
pure dash/space/dot elements to the GPU pattern and text-bearing elements to
apply_along.
- apply_along gained an "A"-type mode (shared reference length): a solid
begin/end dash and a phased interior, so its dashes — and embedded text —
line up with the GPU-dashed sibling elements instead of tiling from start.
Pure-dash MLINEs now cost one WireModel + pattern per element instead of N
segments; single (non-MLINE) entities keep the from-start tiling unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DET pipe walls now carry the stroke pattern (dash_from_start), which
tiles from each wall's own start vertex and skips the A-type end
alignment that was force-lighting a solid dash under the end symbol.
Two rendering fixes on top:
- Dedup the host polyline's vertices before offsetting the walls. Some
DGN pipe polylines carry trailing duplicate end vertices; a zero-length
segment gave offset_xy a degenerate normal, leaving that vertex
un-offset and folding the wall down to the centre line. The two walls'
fold segments met at the centre and drew a spurious line linking them.
- Re-key the symbol (end circle / half-ellipse) wires to the host handle
so the whole pipe picks and selects as one entity instead of the
symbols acting as separate phantom entities.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Dim line color" row on Leader and Dimension entities was
read-only: an earlier attempt wired it to Leader.override_color, which
acadrust never serialises, so the pick was lost on save. Store it
instead as a standard ACAD_DSTYLE per-object dimension-style override
(DXF code 176, an ACI index) through the existing dim_override codec,
so it round-trips through both DWG and DXF like the other dim
overrides. RGB picks collapse to the nearest ACI, matching the rest of
the dim-colour stack (dimension styles are index-only through the file
layer). The renderer prefers the override over the style's DIMCLRD.
The write branch is guarded to leaders and dimensions so a mixed
selection cannot stamp the override onto other entity types.
Bumps acadrust to c9fe982, which carries parsed XDATA onto dimensions
on DXF read (it was dropping common.extended_data), so the dimension
override persists on DXF save as well as DWG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A legacy leader's Lines & Arrows / Text / Fit rows are now editable
per-object overrides of its dimension style, stored in the standard
ACAD_DSTYLE XDATA record (new src/entities/dim_override.rs codec). Arrow
block, arrow size, dim-line lineweight, text offset, vertical text
position and overall scale each prefer an override over the style; the
renderer honours arrow size / block / overall scale and the dim-line
lineweight, so an edited leader redraws at its new arrow and weight.
XDATA edits go through a new dispatch::set_entity_xdata, which registers
the application in the APPID table (the DWG writer drops records for an
unregistered app) and drops that app's stale verbatim EED block (which
otherwise wins over the structured record on a DWG save), so edits and
clears round-trip. The hyperlink editor moves onto this path too.
Dim-line colour stays read-only: it would live in the leader's
override_color, which the file layer doesn't serialise, so making it
editable would silently lose the pick on save. Picking "Closed filled"
writes an explicit null-handle arrow override rather than clearing, so
it sticks even when the style's arrow differs; an unparseable numeric
entry is ignored instead of wiping the stored override.
Note: the DXF entity writer only emits XDATA for hatches, so these
overrides (and hyperlinks) currently persist on DWG save but not DXF —
an acadrust-side gap to close separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the entity Properties panels to the standard CAD layout and
web-verified behaviour:
- Text: combined Justify (horizontal x vertical) dropdown; Position and
Text-alignment rows become editable/grayed by justification; drop the
Annotative-scale row.
- MText: combined Justify; editable Direction, Line space distance
(back-solves the factor) and Line space style; Defined width/height
gated on column mode.
- MLeader: regrouped to match the palette - drop the Leader Structure
group (fold landing rows into Leaders), gate Text vs Block on content
type, full 9-value attachment label set, resolve handle-backed rows
(style, text style, arrowhead, linetype) to names.
- Leader: Lines & Arrows / Text / Fit are resolved from the dimension
style (arrowhead, arrow size, dim-line lineweight/colour, text gap,
vertical text pos, overall scale); Current Vertex navigator; combined
Type; Geometry group after General; no 3D-Visualisation group.
- General: rename "LT Scale" to "Linetype scale"; show ByLayer
transparency by name. Add shared num_prop helper for conditionally
editable numeric rows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rearchitect SDF text from a document-wide collector to per-entity
production during tessellation, so each entity owns its glyph quads and
composite/nested text is covered for free (no re-explosion, no per-frame
document walk).
- WireModel gains `text_verts`: glyph quads ride on the wire, so they are
cached by the tess memo, cloned on hit, and transformed by the block
expand loop exactly like `points`.
- Text arm (tessellate.rs) builds the quads when SDF is on and suppresses
the strokes; render gathers text_verts from the viewport wire set
(`gather_text_verts`, cached on wire content id) — the old collector is
removed. Covers model, dimension, table, block-internal and paper-sheet
text uniformly.
- MLEADER text (lays out via layout_mtext, not the Text arm) gets its own
SDF branch driven by the per-run GlyphRun.
- Pick box comes from the rendered glyph quads (true text extent), not
entity_aabb which mis-places the box for MTEXT; composite paths no longer
clobber the empty text wire's tight AABB (dim pick box hugs the text).
- Selection / hover highlight via a text-highlight overlay (upload_text_
highlight, keyed on selection_generation) mirroring the selected-wire
xray overlay; base glyphs stay neutral so a deselect leaves no stale tint.
- Debug: env OCS_TEXT_BOX draws each run's glyph-bounds rectangle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reclassify every property row to match standard palette behaviour: a row
is editable only when its value maps directly to a stored field AND its
apply_geom_prop handler is wired; computed/derived/count/handle/
cross-object values stay read-only.
Newly editable (all apply-wired): attribute tag/prompt; ellipse major
radius; hatch Double; lwpolyline closed/linetype-gen; mline style;
multileader annotative/landing/background-fill; polyline 2D+3D vertex
coords/segment widths/linetype-gen; shape style; spline control/fit
point coords, weight, fit tolerance, tangents; table break-option flags;
text alignment point, upside-down, backward; underlay display flags.
Corrections: spline Degree -> read-only (editing desynced the knot
vector); removed a duplicate leader "Arrowhead" row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bump acadrust to a46cb8b (spline knot parameterization + mtext line-space
style now stored) and populate rows the builders left blank:
Entity-local: spline Knot Parameterization + closed-spline Area (shoelace),
mtext Line space style, hatch Spacing (pattern-line offset), leader Text
offset (annotation offset), region Area + Perimeter (wire-loop approx).
Doc-dependent (resolved in app/properties.rs where the document is
reachable): insert Block Unit + Unit factor (block record units vs
INSUNITS), underlay Name + Path (definition object), leader Text style /
vertical placement / overall scale and tolerance Text style (dimension
style), multileader Max points + segment angles (mleader style).
Rows needing an annotation-scale / sheet-set subsystem or an internal
PDF/DWF layer parser are intentionally left blank.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The spec-conforming property builders dropped the rows that used these
helpers, leaving them unreferenced: hookline_dir_str (leader),
length_and_area (lwpolyline), render_mode_label / STD_VIEWS /
viewport_view_label (viewport). No behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Groundwork for making the Properties panel conform to the per-entity group/row
spec (PROPERTIES.md). The panel already renders a Vec of sections, but each
entity could only express a single geometry section.
Change PropertyEditable::geometry_properties (and the EntityTypeOps enum
dispatch) to return Vec<PropSection>, update the two impl macros, and flatten
in properties_sectioned so the panel shows [General] + the entity's groups.
Every entity builder is wrapped to return its existing section as a one-element
Vec — pure mechanical change, no rows or titles altered, panel output identical.
Enables Phase 2: rewriting each entity's groups/rows to match the spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the files where ox/oy/oz came solely from the now-zero offset
destructure (tessellate, block_cache, dimension, leader, text_support),
remove the `let [ox,oy,oz] = [0,0,0]` binding and the `- ox` arithmetic so
the coordinate math reads as the plain absolute value it now is.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that geometry reaches the GPU/CPU as absolute coordinates via the
double-single relative-to-eye path, world_offset is always [0,0,0]. Strip
the parameter that was threaded through the whole tessellation / block-
expansion / fallback / camera-decode chain and the ExpandCtx field that
carried it — ~340 references across 20 files, all subtracting zero.
- truck_tess: to_local / to_local_low / tessellate_* drop the offset arg;
to_local is now a pure double-single split.
- tessellate: offset_to_ds → points_to_ds (pure DS split); tessellate(),
tessellate_entity(), fallback_geometry(), solid_wire_fallback(),
entity_aabb(), expand_insert(), expand_block_meshes(), the dimension
helpers (vec3_local, dimension_snap_pts, …), text_support, leader,
multileader, image_model, xclip and camera_from_view all lose the param.
- block_cache: ExpandCtx loses its world_offset field.
- Remove the now-dead offset_snap_pts and the unused set_grid_snap.
Behaviour is unchanged (the offset was zero everywhere); the compiler
verifies every call site.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WireModel/LocalWire/BatchEntry key_vertices are now Vec<[f64; 3]>;
tessellation keeps them in f64 (no f32 cast at offset subtraction),
producers (dimension, leader, multileader) build them from the acadrust
f64 source, the block-cache transform keeps f64, and the face3d Face3D
quad path splits them into the double-single (high, low) pair. Consumers
(snap endpoint/midpoint, xclip) read via f32 at the leaf. Still
offset-relative (inert); endpoint/midpoint snap is no longer f32-capped.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Table / PolyfaceMesh / greek-text / mleader-background fills go through
the face3d pipeline; their vertices were single f32 and quantized at UTM
scale. WireModel now carries a fill_tris_low residual (filled by the
tessellate Lines path), Face3DVertex gains position_low (location 3), and
face3d.wgsl reconstructs the eye-relative position from the pair. 3DFACE
quads built from key_vertices use a zero residual (no low source).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Foundation for double-single positions: WireModel now carries an
index-paired f32 residual buffer (`points_low`) next to the existing
`points` high half. Empty means "all-zero residual", so every existing
constructor (interactive draw, previews) is unchanged in behaviour;
tessellation from CAD f64 will fill it in the next steps so vertex
precision survives at UTM-scale coordinates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New annotations now place their text and reading geometry along the
active UCS X axis (identity = world), so they read square to the user's
coordinate system instead of world-horizontal:
- TEXT / MTEXT: rotation defaults to the UCS X angle
(DocumentTab::ucs_rotation_angle, model space only).
- Linear dimensions: measure along the UCS axis the span is nearest, and
bake the UCS X angle as the text rotation so the value reads square
even under a style that forces horizontal text. DIMCONTINUE / DIMBASELINE
derive their axis from the base dim's rotation (any angle), not a
world H/V test.
- LEADER: the MText annotation rotates to the UCS, and the hookline runs
along the leader's horizontal_direction (set to UCS X), used by the
renderer instead of world horizontal.
- MLEADER: context.text_direction = UCS X drives both the text rotation
and, in the renderer, the text side and dogleg/landing direction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.
Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Reworks MLEADER text/leader geometry and grip editing, and aligns LEADER
to MLEADER's conventions (kept as separate entities).
Text & landing (render path):
- Text is side-anchored on the leader-facing edge and reads outward; the
side, alignment and the horizontal landing are recomputed every frame
from the text grip's position relative to the leader, so moving the
arrow or the text re-mirrors the whole layout and the landing stays
attached to the text's near edge.
- Fixes#49: text was centred on the leader endpoint regardless of side.
Grips:
- Text-location grip plus an MTEXT-style triangle wrap-width grip at the
box's far edge; dragging it sets the MText wrap width.
- Multifunction grip menu restored and made functional: leader-line
vertices offer Add Leader / Remove Leader (Add grabs the new arrow so it
follows the cursor — click places, Esc removes it); the text grip offers
Move Independent (text only) and Move with Leader (translates the whole
multileader via a move-all sentinel grip).
Arrows:
- LEADER and MLEADER arrowheads now render at the same size (text height),
matching previews.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1 of bringing the grip vocabulary in line with the standard
CAD convention: drop the per-position-type shape encoding (Diamond
for translate, Circle for parameter) and use a plain square for
every endpoint / vertex / centre grip. The behavioural midpoint vs
vertex distinction stays — it lives on `GripDef::is_midpoint` and
drives `Translate` vs `Absolute` in the grip-edit dispatcher — only
the marker shape collapses.
- `GripShape` keeps `Square`, `Rectangle`, `Triangle`. Diamond /
Circle are gone.
- `GripDef` gains `dir: Option<[f32; 2]>` — a world-XY direction
vector consumed by `Rectangle` to orient the box along its
segment.
- `entities::common` exposes `square_grip`, `center_grip` (same
square marker, flagged as translate), `rectangle_grip(id, world,
dir)` for oriented mid-segment handles, and a kept-for-Phase-2
`triangle_grip`.
- `overlay::GripMarker` / `grips_to_screen[_paper]` thread `dir`
through to the canvas painter; the Rectangle path rotates the
box around the grip centre using the world-XY direction (flipping
the sin for screen-Y).
Every existing `diamond_grip(id, w)` call becomes `center_grip(id,
w)` — the visual change is square-replacing-diamond at line
midpoints / arc midpoints / circle centres / ellipse centres /
viewport / hatch loop centroids / dimension grips / image corners /
multileader / ray / underlay / ole2frame / solid3d.
`LwPolyline` grows a stretch handle on every segment (straight or
arc), drawn as the new oriented rectangle. The grip-edit path
translates both segment endpoints for straight segments and
adjusts the arc bulge from the new midpoint for arc segments.
Every entity file ended with the same three-trait wrapper block:
impl Grippable for T {
fn grips(&self) -> Vec<GripDef> { grips(self) }
fn apply_grip(&mut self, ...) { apply_grip(self, ...) }
}
impl PropertyEditable for T { ... }
impl Transformable for T { ... }
— 23 lines of pure delegation around five same-named free functions.
Replace with `crate::impl_entity_basics!(T);` (a macro in
entities/traits.rs that expands to the three trait impls).
Applied where the file already follows the free-fn delegation
convention (Arc, Circle, Ellipse, Insert, Leader, Line, LwPolyline,
MultiLeader, Ole2Frame, Point, Spline, Viewport). Files that inlined
the trait method bodies directly (Ray, XLine, Shape, Solid, Tolerance,
MLine, Mesh, Solid3D/Region/Body, Table, RasterImage, Wipeout,
Underlay, Hatch, Polyline, Polyline2D, Polyline3D, Dimension) left
alone — they already skip the indirection. Text/MText/Attribute use
the with-text-style flavour and will move once that variant is needed
beyond the existing two manual impls.
A second macro `impl_entity_basics_with_text_styles!` is included for
the text-like flavour but no entity uses it yet.
Net: -204 lines.
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>
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.
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