Sync current main and companion codec changes before preserving unsupported frame rows, rendering in the entity plane, and sharing final layout metrics with placement preview.
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 <[email protected]>
A text style with a non-zero height fixes the size of everything drawn in
it — the CAD that writes the file skips the height prompt for such a
style, and shows the height without letting it be changed. OCS offered
the field either way, so a drawing could be edited into disagreeing with
its own style.
The height row is now read-only when the style fixes it, for TEXT, MTEXT
and both attribute kinds. `num_prop` already draws a numeric row either
way, so the value still reads the same, just grey.
The panel builders are handed an entity, not the document it came from,
and widening that trait would touch every entity type. The lookup rides
beside the unit context instead, seeded from the same place, since it
answers the same kind of question: something the drawing settles that a
row needs in order to render itself.
Co-Authored-By: Claude Opus 5 <[email protected]>
Every angle row went through the linear formatter, so switching LUNITS
to Architectural showed a block's Rotation as feet-and-inches — and the
string wouldn't parse back, wedging the field until it was cleared and
retyped. Only the decimal/fractional LUNITS survived by accident.
New edit_angle row helper formats angles with format_angle
(AUNITS/AUPREC: decimal degrees, DMS, grads or radians), applied to all
17 angle rows (rotations, obliquing, arc start/end, hatch pattern
angle, multileader text rotation). parse_angle_deg converts every
AUNITS display form back to degrees ("30°"/"30d", DMS 30°15'20",
33.33g, 0.52r) and parse_f64 falls back to it, so the displayed value
round-trips through an edit.
Closes#297
Co-Authored-By: Claude Fable 5 <[email protected]>
An INSERT attribute left with an empty value drew a "[]" placeholder: the
SingleLine tessellation fell back to `[value]` whenever every line was
empty, but that branch only fires once the value is already empty, so it
always produced bare brackets (and never carried the tag it was meant to
preview). On a heavily scaled block instance the unscaled brackets ballooned
across the sheet.
Render nothing for an empty value instead, matching AutoCAD, where a blank
attribute shows no text. An attribute definition previews its tag as a
non-empty value, so it never reached this branch and is unaffected.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Two things an MTEXT can ask for that the layout ignored, both found on the
same drawing (budweiser2018.dwg handles 8782 and 2609).
Columns: `column_data` was read but only ever reached the properties panel,
so a three-column MTEXT ran as one stacked block. Each line now carries the
column it belongs to, the row counter restarts per column, and wrapping
measures against the column rather than the block — measuring against the
block would let a line run across the gutter and over its neighbour.
Paragraph alignment already worked, which is what made the bug read as
"columns are there but misplaced": `\pxqr;` pushed col3 to the right edge
either way.
Only explicit `\N` breaks move to the next column. A column that fills up
does not spill into the next on its own — `heights` / `auto_height` are not
read, so a column runs as long as its content.
Stacking: `\S1/2;` was flattened to the literal "1/2", which is why the
line then overflowed its 7.26-wide box and dropped `°C ª` onto a second
row. The halves now share one horizontal slot, drawn at a shrunk height
with the numerator lifted, so the slot is as wide as the wider half. `^`
stacks without a rule; `#` stays inline `num/den`, which is what a slanted
fraction reads as and needs no second baseline.
The fraction rule rides a run-less stroke group: a group carrying a
`GlyphRun` renders as SDF quads with its strokes suppressed, a run-less one
keeps them, and the rule is plain geometry rather than a glyph.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
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 <[email protected]>
SDF is the only text renderer now — the stroke path and its LOD ladder are
gone, and the remaining text kinds render as SDF glyph quads too.
- ATTDEF / ATTRIB: carry a GlyphRun so single-line attribute text renders as
SDF (multi-line already did via layout_mtext). Tolerance / complex-linetype
text already SDF.
- Remove `sdf_text_enabled()` (the OCS_TEXT_SDF flag / kill switch) and take
the SDF path unconditionally everywhere: tessellate Text arm (per-group by
GlyphRun presence), hit_test fallbacks, render gather, complex_lt, table,
mleader.
- Delete the text LOD ladder: tess.rs baseline/greek block and the
block_cache greek/baseline path (emit_greeked_text / emit_text_baseline),
plus the now-dead helpers text_baseline_points / text_greek_obb_tris /
text_obb_corners_native / mtext_line_count and the LocalWire
text_height_local / text_obb_local fields. All text is crisp at every zoom;
text entities are exempt from the sub-pixel stub cull.
- Fix a latent bug: block-internal SDF text was culled because its LocalWire
aabb_local was [0,0,0,0] (no points) — fold the glyph-quad extent into
aabb_local and exempt text_verts-carrying wires from the sub-pixel cull.
- Clear the pre-existing dead-code warnings (TextPlacement.elevation, TabKind,
MTextRunKind::Tab).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Route single-line attribute text through the same acad_text_encode path
TEXT uses (acadrust parse_plain_text, re-encoded for the stroke
tessellator). Multi-line attributes already flow through layout_mtext
(acadrust). No visible change — attribute text with %% codes rendered
correctly before via the local tokenizer; this shares the one parser.
TOLERANCE cells and complex-linetype text still use the local tokenizer
(specialized paths that rarely carry %% codes).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Two related steps for the text pipeline:
- TEXT parsing now goes through acadrust's parse_plain_text (like MTEXT
uses parse_mtext). text::acad_text_encode parses the value's %% codes —
including %%u/%%o underline/overline and %%nnn — and re-encodes to the
stroke tessellator's \L/\O + resolved-Unicode grammar, so OCS no longer
tokenizes TEXT %% codes itself. (ATTRIB/TOLERANCE/DIMENSION still use the
local tokenizer for now.) Bumps the acadrust pin to the commit that
completes the TEXT/MTEXT control-code set.
- SDF text (OCS_TEXT_SDF) now covers MTEXT and every text-producing
entity, not just top-level TEXT. TextStroke carries a GlyphRun (layout
params); the collector walks convert()'s TruckObject::Text runs and
applies annotation scale exactly as tessellate does for strokes.
layout_glyph_quads shares lff::tokenize_run so %% specials resolve and
\L/\O decorations are skipped (decoration LINES in SDF are still TODO).
Verified: stroke rendering unchanged with the flag off; SDF glyphs render
correctly for TEXT and MTEXT with OCS_TEXT_SDF=1. 154 lib tests green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Text elements previously rendered as hollow wireframe outlines and sometimes
fell back to the wrong system font due to case-sensitive exact matching. Also,
the viewport background grid drew on top of solid entities.
Case-insensitive and partial-name fallback matching (e.g., mapping "arialn"
to "Arial Narrow") is now added to `sysfont::canonical_family_name`, ensuring
text resolves to the correct installed system font.
TrueType font contours are now triangulated into solid fills (`fill_tris`)
using `lyon_tessellation` in `ttf_glyph.rs`. To prevent the GPU from misclassifying
this 2D text as a 3D mesh (which broke flat shading), text tessellation splits
the output into separate outline and fill `WireModel`s.
The background grid is extracted from `SelectionCanvas` into `GridCanvas` and
moved beneath the 3D viewport to ensure proper layering. Finally, `DepthBiasState`
is enabled on all face/fill pipelines to apply a polygon offset, perfectly
resolving Z-fighting between coplanar wireframe lines and solid faces.
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 <[email protected]>
Single-line TEXT and ATTRIB/ATTDEF cast the insertion/alignment point to
f32 before computing the stroke origin. At large coordinates (UTM ~4.5M)
the f32 ulp is ~0.5, snapping text baselines onto a coarse grid so
adjacent rows collapse onto each other and overlap. Keep the anchor in
f64 throughout.
Horizontal alignment now uses the pen advance box [0, advance] instead of
the inked min_x/max_x, so leading/trailing spaces keep their width and
space-aligned columns no longer shift. text_local_bounds returns
TextLocalBounds { ink_min, ink_max, advance }.
TABLE laid out its whole grid in an f32 world frame; switch to a local
f32 frame plus an f64 base added at the widening step, fixing cell-text
baseline snap and the i32 overflow in the border-dedup keys.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
The rendered MText preview is now interactive: drag across glyphs to select
a character range and apply toolbar formatting (bold, italic, underline,
overline, strike, case, alignment) to exactly those characters.
The text layout gains an opt-in pass that emits one world-space box per
visible character carrying its reading-order index; the editor maps those
boxes into the preview frame, hit-tests pointer drags against them, and
highlights the selection. Formatting splices the matching inline codes into
the raw value at the selected byte range (mapped via a code-aware visible-
character scanner). With no preview selection the Edit-box path is unchanged.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
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 <[email protected]>
- 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 <[email protected]>
AttributeDefinition / AttributeEntity were rendering as left-aligned
single-line text regardless of their stored alignment, generation flags,
or multiline settings. Bring both to parity with Text plus expose every
remaining field through the Properties panel:
- horizontal_alignment / vertical_alignment with bounds-based anchor
using alignment_point, mirroring Text's logic
- text_generation_flags bit 2 (backward) flips width_factor sign; bit 4
(upside-down) adds π to the rotation; combined with the TextStyle
flags via XOR (double-mirror cancels)
- multi-line splitting on \n / \P with line_count surfaced as info
- mtext_flag, is_multiline, line_count, lock_position, field_length,
AttributeFlags bits (invisible / constant / verify / preset /
annotative) surfaced as properties
- AttributeEntity.attdef_handle shown as the definition reference
- flags.constant marks the default-value field read-only
- flags.locked_position / lock_position blocks the position grip
Shape entity now applies rotation, relative_x_scale, and oblique_angle
to its placeholder diamond marker, and surfaces shape_number,
style_handle, thickness, and normal in the Properties panel.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
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 <[email protected]>
- raster_image.rs: RasterImage renders as border rectangle + X diagonals
(pixel placeholder); Wipeout renders border or polygon clip boundary;
both support insertion_point grip, brightness/contrast/fade properties,
clipping toggle, and mirror transform (u/v vectors reflected)
- attribute.rs: AttributeDefinition renders default_value (or [TAG] if
empty) as font-tessellated text; AttributeEntity renders the live value;
both support insertion grip, position/height/rotation properties,
and transform operations
- mod.rs + traits.rs: wire up all four new entity types through the full
dispatch chain (to_truck, grips, geometry_properties, apply_geom_prop,
apply_grip, apply_transform)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>