A 3ft object measuring 35.9999" (float noise) printed "2'-11 1"" because the
architectural and fractional formatters split feet/inches/fraction *before*
rounding. A fraction that rounded up to a full unit (16/16 -> 1/1) reduced to a
bare "1" and leaked out with no carry into the inches or feet.
Round on the fraction grid first using integer ticks, then decompose, so carries
propagate: 35.99" now reads 3'-0" instead of 2'-11 1". Adds regression tests.
Fixes#380
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
The Columns row becomes a No columns / Static / Dynamic picker, with
editable column count / width / gutter rows that are live only when
columns are on. Switching a plain MText to columns seeds a consistent
count / width / gutter (dynamic defaults to auto-height) so the stored
layout isn't half-defined; the rows then refine it.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
Render: annotative-ness is resolved centrally in scene::annotative::is_annotative
(per-object context dictionary, legacy XDATA, or annotative style) so the bake and
the properties panel agree. Text, dimensions, tables and blocks now display at the
current annotation scale in model space. An annotative block scales as one uniform
unit about its insertion point — its internal geometry and attributes are carried by
that scale instead of being scaled individually, fixing the block-attribute size
regression. Tables scale their column/row/margin geometry.
Properties: the handle- and flag-backed rows are now editable pickers — MLEADER
multileader style / text style / arrowhead / leader linetype, General Material, and
Plot style (named plot-style mode only; the color-dependent mode stays read-only,
which is correct). A picked name is resolved back to its handle in the update loop,
where the document is in scope. Annotative Yes/No and the annotation-scale row are
shown per object.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
Render MultiLeader labels from the stored context fields instead of
re-deriving them:
- Draw each leader line with the line's own path type (code 170) rather
than the MultiLeader's overall/style type. A spline-style leader can
carry straight lines; splining a straight two-point run through
Catmull-Rom bowed it, so the leader looked wavy with a dirty elbow.
- Drop the stray landing segment that ran from the dogleg end up to the
text's top-left insertion, leaving just the horizontal dogleg.
- Anchor the horizontal-leader text so its bottom line sits at the
connection point (the box then spans connection.y → text_location.y,
matching the writer's content_base_point + landing_gap geometry).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Completes the f64 migration on the CPU data path: the only f32 left now
is the GPU/render/screen layer, which is architecturally required (wgpu
vertex buffers and iced are f32; f64 vertices would double memory).
- EntityTransform::Rotate.angle_rad and Scale.factor are now f64 (the
transform was already applied in f64 around the DVec3 center, so this
only preserves the typed angle/factor). apply_standard_transform and
apply_standard_scale take f64.
- CmdResult::ExtrudeEntity.height and AlignSelected.{angle_rad,scale}
are f64; the EXTRUDE height parses as f64.
- defaults.rs (last-used construction values: circle radius/diameter,
rotate angle, scale factor, offset/fillet/chamfer distance, array
spacing/counts/angle) moved from Cell<f32> to Cell<f64>. Array counts
round-trip through u32 (exact below 2^53).
- Consumers in circle/array/fillet/offset/rotate/scale/align/solid3d and
dimension.rs rotate_point/scale_point follow. `as f32` is kept only
where a value feeds a WireModel rubber-band/ghost (the preview path is
screen-space f32).
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]>
Two defects in the in-app MTEXT editor, both surfaced once the preview
text became visible:
1. Invisible preview text. After the SDF text migration, tessellate emits
glyphs as SDF quads on `text_verts` and leaves the polyline `points`
empty, but the editor preview draws on a 2D canvas that can't run the
SDF shader — so only the selection box showed. Add a `force_text_strokes`
parameter to tessellate: when set, TEXT/MTEXT run-groups also emit their
glyph outline strokes as points. The preview passes true (real stroke
font, aligned with glyph_boxes); every other caller passes false and
keeps SDF-only text.
2. Enter did not start a new line. mtext_type inserted a literal newline,
but the MTEXT layout only breaks on the `\P` code (a raw 0x0A is a break
only to the caret logic). Normalise every literal newline — from Enter or
a multi-line paste — to `\P`, so the line breaks and the saved value
stays standard.
Closes#308
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Gradient Type (One/Two color), Color 1 and Color 2 editable via a generic field-keyed colour picker path. Background colour editable and stored as HATCHBACKGROUNDCOLOR extended data (group 1071 true colour), rendered in OCS as a solid backdrop drawn behind the pattern. Hide Origin X/Y for gradients (pattern-only concept) and drop the redundant gradient rows from the pattern branch.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Merge duplicate Type into pattern_type; Type/Associative/Island detection as dropdown/toggle; Angle/Scale/Spacing/Origin X-Y editable; Color via picker. Reorder Pattern group, drop redundant Color/Transparency/Layer override and the Misc section. Wire the pattern origin (per-line base_point) into the render (x0/y0 relative to world_origin) so it affects dashed/offset patterns; add a circle origin grip at the hatch centroid (new GripShape::Circle).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Single Geometry section: Start/Center/End points, Major/Minor radius (editable; minor derives ratio), Radius ratio, Start/End angle editable. Major/Minor axis vectors, parameters, Length and Normal kept read-only at the end.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Circle: Diameter/Circumference/Area now editable (each derives radius); Normal X/Y/Z read-only. Arc: reorder to Start/Center/End first; Normal X/Y/Z read-only.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The Current Vertex row is now a ◀ / ▶ stepper (new PropValue::Stepper)
showing "i / N". Stepping it moves the panel's focus through the
polyline's vertices — Vertex X/Y and the per-vertex start/end widths show
and edit the focused vertex instead of always the first — and the focused
vertex's grip is drawn hot (filled) in the drawing so it's visible while
navigating. Focus wraps around and resets to the first vertex when the
selection changes. Adds Area and Length to the LwPolyline Geometry group
(Polyline2D already had them). Applies to LwPolyline and Polyline2D.
The focused vertex is threaded to the per-entity property builder/editor
through a dispatch thread-local (like the curve-tolerance override), so
the PropertyEditable trait signatures are unchanged.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Draw the polyline width bands with the stored width offset to each side
of the centreline rather than half of it.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Dragging the minor-axis grip past the major length re-derived the
perpendicular from the just-swapped major axis on every mouse move, so
the major flipped 90° and the swap re-fired each frame — both axes grew
without bound once the minor reached the major.
Decide which axis the cursor is dragging by projection (not by a perp of
the current major), keep the non-dragged axis fixed, and store the longer
one as the major so the ratio stays <= 1. The swap is now stable across
the circle: the minor holds the old major length while the dragged axis
grows smoothly.
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]>
An in-app-created table (no baked *T### block) synthesised its cell text via
layout_mtext as stroke polylines — the last text path still rendering strokes
while SDF is the default (imported/baked tables already render SDF via the
recursive Text arm).
- tessellate_table: when SDF is on, build each cell's glyph quads from the
run-carrying layout and collect them into one text-carrying wire (per-vertex
cell colour, glyph-bounds AABB); the stroke/fill accumulation is the SDF-off
fallback. Mirrors the MLEADER SDF branch.
- tess.rs synthesized-table path: guard the whole-table AABB clobber on
(!points.is_empty() || !fill_tris.is_empty()) — matching the dim / mleader /
baked-block paths — so the empty SDF-text wire keeps its tight glyph-box AABB
and the pick box hugs the text instead of the whole table. Found by the
adversarial review pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Extend SDF text to the remaining text kinds and make the Text arm decide SDF
per group instead of per entity type.
- Text arm: a stroke group renders as SDF glyph quads when it carries a
GlyphRun (strokes suppressed); a run-less group keeps its strokes. TEXT /
MTEXT / dim / block / mleader are unchanged (all run-groups). A composite
Text object can now mix geometry + text: its run-less groups stay strokes and
its run-groups become SDF. The glyph-bounds AABB is hoisted so both the
bins-empty and bins-non-empty paths stamp it on the text wire.
- Tolerance: refactored to emit the feature-control frame (border, separators,
dividers) as run-less stroke groups and each cell's text as a run-group, so
the frame stays crisp geometry while the values/symbols render as SDF text.
- Complex linetypes: embedded LtSeg::Text renders as SDF glyph quads on an
extra text wire (glyph-bounds AABB) instead of stroke polylines.
- click_hits_all: include SDF text boxes so selection cycling steps through
text too, ranked after real geometry.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Render the MTEXT background (never drawn before — the flags were only shown
read-only in the panel) and make it editable.
- Text arm emits an opaque fill_tris rect behind the glyphs: flag 0x01 uses
the entity background colour, 0x02 uses the canvas colour (a wipeout-style
mask). Box = glyph bounds padded by (background_scale - 1) × text height.
- Text frame (flag 0x10) draws a closed rectangle in the text colour around
the same box.
- Threaded `bg_color` into `tessellate()` (+ all callers and the
MultiLeaderTess trait) so the mask can use the drawing-window colour.
- Properties: "Background mask" is a Off / Fill / Mask dropdown, "Background
color" is its own colour picker (independent of the entity colour — new
PropBgColor* messages; picking a colour switches to Fill mode), and "Text
frame" is a checkbox toggle. Edits re-tessellate so the change shows live.
Renders via the face3d fill path, so the fill shows with FILLMODE on (same as
MLEADER background fill); the frame is a stroke and always shows.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[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]>
OCS maintained a second MTEXT inline-code parser that duplicated
acadrust's `mtext_format::parse_mtext`. Replace it with an adapter
(`adapt_mtext_paragraphs`) that maps acadrust's structured MTextDocument
onto the existing layout types (`MTextLine`/`RunState`); the layout
engine (`layout_mtext`, `mtext_line_count`, and their callers) is
unchanged. Removed the hand-rolled `parse_mtext_paragraphs*` and its
helpers.
Validated during migration by a differential test (adapter vs the old
parser matched byte-for-byte over a 20-case corpus) and confirmed
rendering identically in the app; the differential test is replaced with
adapter-only unit tests. Relies on acadrust's new `MTextScalar` to tell
absolute `\H` from relative `\Hx` (bumped the acadrust pin).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Wires the SDF glyph atlas into the render loop end-to-end for top-level
TEXT entities, gated by the OCS_TEXT_SDF env var so the default path is
unchanged. Verified rendering on a real drawing: glyph quads land exactly
over the existing stroke text (position, size, rotation), confirming the
atlas → shader → pipeline → screen path and the RTE / UV math.
- text::text_run_placement — extracted from to_truck (behaviour-preserved)
so the stroke and SDF-quad paths compute an identical run placement.
- sdf_atlas::text_atlas — process-wide GlyphAtlas (Mutex/OnceLock), shared
by the collector (bakes glyphs) and the GPU upload (reads texels).
- Scene::sdf_text_vertices — collects per-glyph TextVertex for the frame's
TEXT entities via layout_glyph_quads + push_glyph_vertices. Empty unless
OCS_TEXT_SDF is set.
- ViewportData.text_verts + Pipeline text_pipeline/atlas/vbuf, upload_text
(rebuilds the R8 atlas texture when it grows), and a text render pass
drawn over the wires.
Still additive over the stroke text; only top-level TEXT is wired (MTEXT,
attributes, and block-internal text are next), and the collector re-runs
per frame (vertex caching by geometry epoch is a follow-up).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
PolyfaceMesh and SubD Mesh faces were fan-triangulated from vertex 0, which
is only valid for convex faces. A concave face (e.g. an L-shaped / notched
n-gon) fanned into triangles that spill outside the outline — spurious
"triangles that shouldn't be there" that covered neighbouring geometry and
its edges. Replace the fan with an ear-clipping triangulator that projects
the planar face to 2D, respects its winding, and clips only real ears, so
concave faces triangulate to exactly their own area. Convex faces (and the
cylinder-cap n-gons) are unchanged in result. Falls back to a fan for a
degenerate / self-intersecting polygon.
Verified on Annotation-2D-Mesh-Solid-BIM.dwg (concave 5-/6-gon mesh faces)
and by a unit test: a concave L-hexagon triangulates to n-2 triangles whose
total area equals the polygon's, with no overshoot.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A spline stored by fit points (DWG R2013+ scenario 2) has no control points
or knots, so the renderer — which built its curve from control points —
produced nothing and the spline was invisible. When a spline has fewer than
two control points but at least two fit points, interpolate a smooth
Catmull-Rom curve through the fit points and emit it as a dense polyline.
Open ends use reflected phantom points; closed/periodic splines wrap.
Pairs with the acadrust bump (e8e422a) that decodes the R2013+ fit-point
scenario correctly — together they make the fit-point spline in
Annotation-2D-Mesh-Solid-BIM.dwg render.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The paper layout sheet pass reused the full model mesh set, so every
top-level 3D solid piled onto the paper origin — the sheet camera works
in paper coordinates, not model space. Guard `meshes` on
`inst.paper_sheet` the same way wires/hatches/images already are: the
sheet shows only paper-native content; the model's solids render inside
the floating content viewports (model camera + per-viewport scissor).
Also name 3DSOLID/REGION/BODY/SURFACE/MESH/SOLID/MULTILEADER in dxf_name
(they fell through to "ENTITY"), and add a SELHANDLES diagnostic that
reports the active space plus a per-type / per-block selection breakdown.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Thickness (DXF 39) is a General-group property in the palette, but it is
stored per-entity, so it was previously shown (inconsistently) only in the
Geometry section of a few entities. Surface it in General for every entity
type that carries an extrusion thickness (arc, circle, line, lwpolyline,
point, polyface mesh, 2D polyline, shape, 2D solid, text) via
entity_thickness / set_entity_thickness, routed through apply_common_prop.
Removes the now-duplicate Thickness rows (and dead apply arms) from the
arc/circle/shape/solid Geometry sections so it is never shown twice.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Inserting a block piled every attribute at the origin: each AttributeEntity
was built from just tag + value with default geometry. Carry the full
AttributeDefinition through the attreq flow and build attributes with
AttributeEntity::from_definition, then apply the block's insertion transform
so each lands in WCS at its proper position / alignment / rotation.
Attributes are stored in world space, so moving the block must move them
too. The MOVE command already did (acadrust translate_insert); apply_grip
(grip-drag) and the Position X/Y/Z property edit now translate the
attributes by the same world delta so they no longer stay behind.
Rotate/scale/mirror of a block still don't carry the attributes — acadrust's
transform_insert has no attribute pass — tracked separately.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Root cause: the glyph tessellator advanced the pen by advance*width_factor
while the transform already scales the pen position by the width factor, so
a backward (negative width factor) run kept its word order and grew on the
same side of the anchor instead of mirroring across it. Advance in unscaled
glyph units to match text_local_bounds; the transform applies the factor once.
On top of that:
- The renderer honours the entity's own text-generation flags (DXF 71: bit 2
backward / bit 4 upside-down) XOR the text style's flags, and signs the
horizontal anchor by the width factor so Center/Right backward text mirrors
about its anchor.
- MIRRTEXT on: a MIRROR toggles both group-71 bits so the reflected rotation
renders a true glyph mirror (backward-upright for a vertical axis, flipped
for a horizontal one).
- MIRRTEXT off: the text flips left<->right and lands mirror-symmetric to the
source (right-reading) instead of hugging the axis.
- The MIRROR preview re-derives text ghosts to match the commit; mirrored()
now reflects the double-single residual too.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The Text branch only set horizontal_alignment, so TL/BL/MC etc. never
moved the text box vertically. Map the T*/M*/B* codes to Top/Middle/Bottom
and re-sync the alignment point. (#246)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Dragging the minor-axis grip past the major length clamped the stored
ratio to 1.0, pinning the grip at the major radius. Rotate the definition
instead — the minor direction becomes the new major axis — so the shorter
axis stays major and the DXF/DWG ratio stays <= 1.0. (#242)
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]>
Bump acadrust to ef0e231 (region/body history handle + viewport clip
boundary now stored) and populate the rows the builders left blank:
- Solid3D / Body "Show History": derived from the history handle's
presence (there is no separate stored bool — the DWG carries only the
H 350 history link).
- Body "History": the history handle, matching how Solid3D already shows
it.
- Viewport "Clipped": Yes when the clip-boundary handle is non-NULL.
Hatch Origin X/Y and Boundary retention stay blank by design: they are
not part of the persisted AcDbHatch record in DWG or DXF (retention is a
BHATCH command setting), so there is nothing to read.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
A helix loaded from a DWG (AcDbHelix) previously fell through to the
fallback: it did not render, had no grips, could not be moved, and showed
only a Type row. Add src/entities/helix.rs which drives all of these
through the embedded spline (render/grips/transform) and exposes the
generating parameters — base point, turns, turn height, height, base/top
radius, turn slope, twist, constrain — as the Geometry group per the
properties spec. Wire Helix into the to_truck / grips / apply_grip /
geometry_properties / apply_geom_prop / apply_transform dispatch lists.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
The Fit Tolerance and Start/End Tangent X/Y/Z rows were placeholders while
the pinned acadrust lacked those fields. Now that the bump exposes
Spline::fit_tolerance / begin_tangent / end_tangent, populate them.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Pull in this session's acadrust work — the HELIX / ACAD_TABLE read+write,
underlay reader/writer/definition, spline tolerances & tangents, etc. The new
EntityType::Helix variant breaks three exhaustive matches; add its arm to
entity_type_name, the UI-name / DXF-name maps and entity_type_key.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Resolve a dimension's DimStyle from the document and inject its full spec
groups — Lines & Arrows, Text, Fit, Primary Units, Alternate Units,
Tolerances — mirroring the ~80 dimension variables (dimasz, dimclrd, dimtxt,
dimdec, dimtol, dimzin bit flags, …) as read-only rows. The panel already had
doc access for the dim-style picker; extend it to append style_sections(style)
after the dimension's own Misc/Geometry groups.
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]>
Entities inside a block definition that sit on layer "0" must take on the
properties of the layer the block reference is inserted on; every other
layer is "sticky" and keeps its own identity. OpenCADStudio resolved a
block child's ByLayer color/linetype/lineweight against the child's own
layer in all cases, so layer-0 content stayed layer-0 (white) instead of
inheriting the instance layer.
Resolution now follows the standard rule for block children — explicit
wins, then ByBlock → the insert's own style, then layer-0 + ByLayer →
the insert's *layer* style, else the child's own layer. The insert's
layer style threads through the whole block-expansion chain (and chains
correctly through nested inserts), with the child's own transparency
preserved.
Covered across every block-render path:
- wires (line/arc/circle/polyline/text) via the block cache + the
explode fallback + insert attributes
- hatch / solid fills (also fixes the PDF/print export path)
- 3D solid block children (per-instance mesh recolor)
- Dimension / Table baked-block sub-entities
New shared helpers in scene::view::render: InheritStyle, layer_render_style,
lineweight_to_px; render_style_for_block_sub gained the layer-0 branch.
Adds unit tests covering the layer-0, sticky-layer, ByBlock, explicit and
transparency cases.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
- expand_defn: dedup the <1px baseline LOD branch the same way the <5px
greek branch does, so colour-split MTEXT no longer stacks N overlapping
baselines (one per \C segment) over the same OBB.
- sysfont: memoise canonical_family_name process-wide. resolve_font calls
it once per word on the MTEXT measure hot path and Face::resolve re-runs
it right after; the fontdb query + linear family scans were uncached.
- dimension/multileader/table: reword the 6 fill_tris_low FIXMEs. They
cited a stale line and the wrong consumer — these fills render on the
top-level path (panic-safe .get().unwrap_or), not the block cache, so
they cannot trip emit_wire's debug_assert. State the real status:
latent f32-precision debt, not a crash. Also fix the mis-indented
text-fill WireModel in dimension.rs.
- tests: add a host-independent colour-split test (two \C segments in a
block keep ≥2 distinct wire colours — guards the per-wire colour fix)
and a TTF-gated UTM fill test (paired, non-zero fill_tris_low). Note the
arial-style test's LFF-fallback limitation.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
dimension.rs (x2), multileader.rs (x3), table.rs (x1) emit fill_tris
without a corresponding fill_tris_low. These will trip the debug_assert_eq!
added in the preceding commit when the entity appears inside a block
definition. Each site is marked with a FIXME referencing block_cache.rs:1397
and the correct fix pattern (split_ds on each vertex).
resolve_font previously returned &str, passing raw stems like "arial" verbatim to Face::resolve. System font lookup requires the canonical family name ("Arial"), so inline \f/\F overrides from block-nested MTEXT were silently unresolvable and fell through to no geometry.
Change resolve_font to return Cow<'a, str>:
- no override -> Borrowed(base), zero allocation
- LFF built-in -> Borrowed(font), zero allocation
- system font resolved -> Owned(canonical), one allocation
- unresolvable -> Borrowed(base), falls back to style font
All 4 call sites updated to &font_name (&Cow<str> derefs to &str).
Adds two unit tests covering the fallback and TTF-stem-to-family-name paths. Adds integration test verifying block-nested MTEXT produces geometry and correctly separates outline/fill wires.
Brings the save-time *D-block bake into parity with the live render so a
dimension no longer changes when the file is saved and reopened (the reload
draws from the block). Per a bake-vs-live audit:
- text is now the live render's own Text/MText entity (alignment, MText
codes, text style, alignment point) — fixes every label shifting ~half a
glyph on reload
- extension lines carry the DIMEXO start gap + DIMEXE overshoot, and slant
by ext_line_rotation (DIMEDIT Oblique)
- terminators follow the style: oblique tick when DIMTSZ>0, else a closed
filled SOLID arrow (was a generic open stroke)
- angular dims bake the swept arc + tangent terminators, flush extension
lines (matching live exactly)
- radius/diameter bake the leader leg; diameter runs edge-to-edge through
the centre; centre mark honours negative DIMCEN (centre lines)
- per-element colours/lineweights (DIMCLRD/E/T, DIMLWD/E), DIMSE/DIMSD
suppression, DIMDLE tick overshoot
- consistent arc sweep at exactly 180 degrees
Refs #181. Remaining (custom DIMBLK arrow shapes, DIMFXLON, arrow-outside
fit, annotative viewport scale) tracked separately.
Co-Authored-By: Claude Opus 4.8 <[email protected]>