Commit graph

1,323 commits

Author SHA1 Message Date
Hakan Seven
23d9a261b1 chore(release): v0.7.5 2026-07-04 20:18:47 +03:00
Hakan Seven
f8ceddd303 fix(solid): read rational NURBS spline-surface control points with weights
An ACIS spline-surface stored as `nurbs` (rational) carries a weight on each
control point, so the control net is a stream of x, y, z, w tuples. The
parser read plain x, y, z triples, shifting every control point by one float
and scrambling the whole net — the surface, and the solid it bounds,
rendered badly deformed. Non-rational `nubs` surfaces were unaffected, which
is why only some solids (rational lofts/sweeps with varying weights) broke.

Detect `nurbs` vs `nubs`; for rational surfaces read the fourth weight
component and build a truck `NurbsSurface` from homogeneous (xw, yw, zw, w)
control points so the weights actually shape the surface. Both the truck
B-rep path and the bespoke fallback share this parser.

Verified on VillaSavoye_MartinHedin2.dwg solid 0x24A1EE (handle 2400750): the
tessellated surface now stays within the solid's true extent instead of
flying outside it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:15:17 +03:00
Hakan Seven
e515fa3156 feat(spline): draw fit-point splines via Catmull-Rom interpolation
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 <noreply@anthropic.com>
2026-07-04 19:52:56 +03:00
Hakan Seven
e6a961be6c feat(render): draw 3D mesh outline edges black in filled modes
Extends the solid feature-edge behaviour to mesh entities (PolyfaceMesh /
PolygonMesh): their triangulation outline edges now render black when a
filled-with-edges mode is active, and keep the entity colour in the
lines-only modes (wireframe / hidden-line).

A mesh entity emits its face fill and its outline edges as separate
WireModels sharing the entity handle: the fill carries fill_tris plus a
non-empty fill_tris_low (real 3D depth), the edge carries points. The old
is_3d_mesh_edge flag keyed on `!fill_tris.is_empty()`, which tagged the
points-less fill wire rather than the edge wire, so the edges were never
recognised. upload_wires now collects the handles of real 3D fill wires and
flags any points-wire sharing one as the mesh edge (text greek, whose fill
has an empty fill_tris_low, is excluded — matching the face3d test). The
wire draw loop then routes flagged edges through a new black wire pipeline
(fs_black) in filled-with-edges modes.

fs_black lives in wire_indexed.wgsl (the native shader) and wire.wgsl (the
wasm shader).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 18:28:27 +03:00
Hakan Seven
1065bc12b2 feat(render): draw ACIS solids as B-rep feature edges, meshes as tri edges
Solid wireframes rendered every triangle edge of the tessellation, so a
curved solid read as a dense mesh instead of its real outline. Now each ACIS
solid's B-rep face-boundary edges are collected from the SAT `edge` records
(straight edges as segments, ellipse/circle edges sampled along their bounded
arc), body-transformed and split into the double-single pair, and stored on
the MeshLodSet. The block-instance and top-level offset paths transform these
edges alongside the mesh vertices.

The batch builder routes a solid with feature edges into a dedicated LineList
vertex buffer and skips its triangle wireframe; a mesh with no B-rep edges
keeps its triangulation edges. Edge colour follows the display mode: the flat
`fs_edge` shader draws the entity colour in the lines-only modes (wireframe,
hidden-line) and a `fs_edge_black` twin forces black for the edge overlay in
filled render modes.

Verified: the Clinic storefront arch bars now yield ~14-16 clean edge
segments (vs the full triangulation) bounded to the solid's true extent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 17:57:00 +03:00
Hakan Seven
a08cf477c1 fix(render): draw transparent solids without depth writes
Once solids carried a real sub-1 alpha (after the ENC transparency fix),
the single depth-writing mesh pass made a transparent solid occlude the
geometry behind it: it wrote depth at its near surface, so anything farther
failed the LessEqual test and was never drawn — the transparent fill looked
like it erased what was behind it.

The batched mesh builder now routes each solid's triangles into an opaque or
a transparent index stream by its baked colour alpha (< 0.999). The opaque
fills draw first and write depth; the transparent fills draw last through a
new depth-write-disabled pipeline, so they blend over the geometry behind
them instead of culling it. The hidden-line depth prepass still rasterises
both streams so solid surfaces keep occluding hidden edges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 16:27:59 +03:00
Hakan Seven
9f7fdc5f8f fix(solid): tessellate cone-surface faces to their arc, not a full circle
Curved 3D-solid faces backed by an ACIS cone-surface (e.g. the arched
mullion bars of a storefront curtain wall) were swept a full revolution
regardless of the face's actual angular extent, so a bar bounded to an
8-16 degree arc rendered as a full circle of the surface radius — a large
jagged loop instead of a small arch.

cone_faces (the truck B-rep path) now derives the angular span from the
face's boundary loop and rsweeps only that arc; the bespoke fallback
tess_cone_face no longer forces a full revolution when a thin face
collapses to a single height. Arc extraction uses a seam-robust
largest-gap method so faces straddling +/-pi (the crown of an arch) are
not mistaken for full circles.

Verified against Clinic_Architectural.dwg: the seven arch-bar solids drop
from a ~12.9 (2R) bbox to their true ~1-2 unit extent, and a file-wide
check of 155 cone-bearing solids shows none ballooned and none collapsed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 16:11:24 +03:00
Hakan Seven
a8c368aef9 chore(deps): bump acadrust to 4f0599a (ENC transparency field-order + opacity fix)
Fixes entity transparency dropping to opaque on DWG read (e.g. the 85%
area-highlight solids in Clinic_Architectural.dwg). The ENC codec read/wrote
the transparency BL before the rgb BL (real order is rgb then transparency)
and stored the on-disk opacity byte without inverting to the internal
transparency-amount representation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 14:48:15 +03:00
Hakan Seven
c94e50a8e1 chore(deps): bump acadrust (AcDs pre-table blob pool — HVAC 2446/2446 vs reference)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 07:22:31 +03:00
Hakan Seven
a360da25fb chore(deps): bump acadrust to edfe3bd (AcDs record-table data-offset blob pairing)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 07:00:24 +03:00
Hakan Seven
d6ad3100fe chore(deps): bump acadrust to f65a7e4 (AcDs record-table blob pairing)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 05:49:45 +03:00
Hakan Seven
33f1150fad chore(release): v0.7.4 2026-07-04 01:46:33 +03:00
Hakan Seven
b3ead4f691 chore(deps): bump acadrust to 063da6f (AcDs SAB blob handle-pairing fix)
Fixes BIM 3D solids rendering at wrong positions — the AcDs SAB blob→solid
pairing now follows handle order instead of object-stream order, so every
solid gets its own geometry and body transform. Corrects the regression that
broke 3D solids in 0.7.3 (they were correct in 0.7.2) and places wall/floor
solids that previously piled near the origin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 01:42:31 +03:00
Hakan Seven
e78e137664 fix(paper): keep model 3D solids off the paper-space sheet
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 <noreply@anthropic.com>
2026-07-03 23:29:18 +03:00
Hakan Seven
313a0eacfd chore(deps): bump acadrust to 6d5c47b (issue 225 — DWG opens in strict readers)
Pulls the acadrust fixes that make a saved DWG containing 3D solids open
cleanly in a strict conformance reader: the AcDsPrototype datastore section is
now built correctly (segment-header ds_version, datidx, and the handle-sorted
search index) so 3D solids keep their geometry instead of being dropped, and a
dim-style/block handle collision on read is repaired so layouts round-trip.
Validated end-to-end on two real 107- and 273-solid files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:17:04 +03:00
Hakan Seven
c3e86d08cc feat(snap): OTRACK ortho lock + edge-extension tracking (#218, #219)
#218: with Ortho on and a base point the axis is now a hard lock — a lone
tracking ray can't pull the cursor off it. Only a tracking ray crossing
the ortho axis locks (the corner-finding case); otherwise the ortho
constraint applies as the fallback, so Ortho reliably locks when OTRACK
is also on.

#219: an acquired corner now contributes an alignment ray along its own
edge direction(s) (captured once at acquisition), so OTRACK follows a
segment's extension, not just the ortho/polar axes. Acquisition is also
captured on leave — a perfectly still cursor emits no move events, so
"pause on a corner, then drag" now reliably acquires it. The acquired
marker projects through the active pane's camera + rect (canvas offset
included) instead of vanishing, and the active alignment path is drawn
as a full-length dashed guide through the corner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 20:34:20 +03:00
Hakan Seven
0322128f48 fix(snap): extension guides stay visible at extended intersections (#247, #259)
Extension osnap now offers a ray off every polyline vertex, not just the
first and last, and snaps to where two segments' extension lines would
cross. That crossing is emitted as an Intersection so it outranks the
per-segment extension feet on their own lines. When it wins, both
contributing extensions are re-detected — off-ray distance measured in
screen space so the tolerance holds at UTM coordinates — and the overlay
draws a dashed guide from each endpoint through the crossing, so both
extension paths remain visible instead of vanishing when caught.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:46:08 +03:00
Hakan Seven
f4fffa1359 chore(release): v0.7.3
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:55:43 +03:00
Hakan Seven
726984e3a5 feat(properties): show Thickness in the General group
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 <noreply@anthropic.com>
2026-07-03 16:51:05 +03:00
Hakan Seven
ab0a4f790c fix(insert): place block attributes by their ATTDEF geometry, follow the block (#255)
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 <noreply@anthropic.com>
2026-07-03 16:47:39 +03:00
Hakan Seven
818638d3aa feat(ui): bulk layer on/off, lock, freeze, transparency + Ctrl+A (#236)
The Layer Manager already applied colour/linetype/lineweight in bulk;
extend that to the rest:

- Visibility, lock and freeze toggles now act on every selected layer, not
  just the clicked row. The clicked row's new state becomes the target for
  all. Transparency edits apply to the whole selection too.
- Clicking a linetype/lineweight combo on a row that is already part of a
  multi-selection no longer collapses the selection (the whole row is one
  mouse_area whose plain-click select would otherwise reset it), so those
  edits stay bulk.
- Ctrl/Cmd+A selects every layer row while the Layer Manager is open, and
  all drawing objects otherwise.

Two now-unused Scene layer-toggle helpers are removed (the handlers set the
flags directly and bump once).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:06:10 +03:00
Hakan Seven
5e4e7c1020 fix(commands): ADDSELECTED keeps CLAYER, adopts dimension style (#239)
Two follow-ups to ADDSELECTED:

- It no longer permanently changes the current layer/colour/linetype/
  lineweight. The template's properties are captured as the drawing
  defaults only while the launched draw command runs; a snapshot taken in
  cmd_add_selected is restored the moment that command ends (central hook
  in apply_cmd_result) or is interrupted by another command.
- Dimensions are supported: add_selected_verb maps each DimensionType to
  its DIM* command, and the template's dimension style is adopted as the
  current DIMSTYLE (restored afterwards). commit_entity_handle now stamps
  every new dimension with the current DIMSTYLE — the DIM commands left the
  default "Standard" on the entity, so a normal dimension also picks up
  the active style now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 16:05:54 +03:00
Hakan Seven
f9b6834ad9 feat(export): exact ACIS geometry for planar solids on save
OCS models 3-D solids with truck, but extrude/boolean results carried no ACIS
body, so other CAD apps dropped them on open. Add an exact planar exporter and
run it at save time.

- scene::convert::acis_export::planar_solid_to_sat walks a truck Solid whose
  faces are all planes bounded by straight edges into a vertex/face-ring mesh
  and builds an exact ACIS body via acadrust's build_planar_body. Curved
  faces/edges return None (left for the NURBS path). A signed-volume check
  flips the winding if the shell comes out inside-out.
- OpenCADStudio::sync_truck_solids_to_acis fills in ACIS geometry for every
  cached truck solid that still has none, just before a Save / Save-As, so the
  written DWG/DXF carries real modeler geometry.
- Bump acadrust to 9142a50 (build_planar_body + classic-ACIS AcDs roundtrip fix).

Booleans (already cached as truck solids) now export exact geometry. Extruded
polygons will too once the EXTRUDE command caches its truck solid (a separate
change kept out of this commit because command_driver.rs holds unrelated
in-progress edits).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:43:37 +03:00
Hakan Seven
79a5ac6fed fix(scene): correct MIRROR of TEXT under MIRRTEXT (commit + preview)
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 <noreply@anthropic.com>
2026-07-03 12:42:55 +03:00
Hakan Seven
a24232842c feat(commands): prompt for a system-variable value on the next line
Typing a bare system-variable name (MIRRTEXT, FILLMODE, LTSCALE, …) now
echoes the current value and prompts "Enter new value for <NAME> <cur>:",
capturing the next command-line entry as the new value — Enter keeps the
current one, Escape cancels. The single-line "MIRRTEXT 1" form still works.
Mirrors the awaiting_vports pattern: a pending_setvar field on the app,
consumed at the top of on_command_submit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 12:42:55 +03:00
Hakan Seven
24f7329468 fix(input): numpad decimal key always types a decimal point
On German/European layouts the numpad decimal key emits a comma, which the
coordinate parser rejects. Match the physical NumpadDecimal / NumpadComma
key and emit '.' regardless of the OS layout. (#201)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:52:35 +03:00
Hakan Seven
7bab7c7b8d feat(ui): replace app-menu logo with a quick-access file toolbar
Remove the OCS-logo button and the dropdown menu it opened; the top strip
now shows direct New / Open / Save / Save As / Print buttons ahead of the
undo/redo controls. Drop the HELP and OPTIONS commands (HELP dumped text to
the command line, OPTIONS aliased the drafting-settings popup).

The app-menu overlay is gone, so its rendering code, MENU_ITEMS, four dead
messages, and the LOGO_RED / GEAR / HELP assets are removed. The recents
list it held is the only survivor — moved into OpenCADStudio.recent_files
via a new app/recent.rs, backing the Start page's Recent Documents panel.

Quick-access icons are redrawn in a cleaner style (file-plus, folder-open,
device-floppy, file-export, printer) and tinted light so they read on the
dark strip; FOLDER / DOC keep their old glyphs for the file browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:51:49 +03:00
Hakan Seven
2bc5c2a686 fix(commands): JUSTIFYTEXT sets vertical alignment for single-line TEXT
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 <noreply@anthropic.com>
2026-07-03 09:51:34 +03:00
Hakan Seven
3d761bdcd3 fix(scene): honor MIRRTEXT on the MIRROR copy path
transform_entities kept text/mtext/shape right-reading when MIRRTEXT is
off, but the default MIRROR-with-copy path did not, so copied text mirrored
backwards. Extract the capture/restore into shared helpers and apply them
in copy_entities too. (#243)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:51:34 +03:00
Hakan Seven
c95c2dda6c fix(entities): swap ELLIPSE axes when minor grip passes major length
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 <noreply@anthropic.com>
2026-07-03 09:51:34 +03:00
Hakan Seven
123d4e9083 feat(properties): make read-only values selectable text instead of click-copy
Replace the click-to-instantly-copy read-only cell with a non-editable but
selectable text field: the user selects the value (drag / double-click /
Ctrl+A) and copies with Ctrl+C, the normal expected gesture. The field
carries the full un-truncated value, styled flat (no input box or focus
ring) so it still reads as read-only but with a visible selection colour.
Keystrokes route to Noop, so it can be selected/copied but never edited.

Removes the PropCopyValue message + handler and the mouse_area copy hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 08:14:37 +03:00
Hakan Seven
c59a584d14 feat(properties): copy read-only property values to the clipboard
A read-only value cell is now click-to-copy: clicking writes the full,
un-elided value to the system clipboard (copy cursor on hover). One
change in render_ro_row, so it covers every read-only row for every
entity. Adds Message::PropCopyValue routed through iced:📋:write.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 08:06:00 +03:00
Hakan Seven
4d2471cafd feat(properties): audit editable vs read-only across all entities
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>
2026-07-03 08:06:00 +03:00
Hakan Seven
35468004e6 test(core): ignore ribbon_groups microbenchmark by default
The once_lock_eliminates_allocation_after_first_call test from PR #257
is a timing microbenchmark that Box::leaks ~400k strings by design to
defeat allocator reuse. Mark it #[ignore] so it stays out of the default
suite; cache correctness is already covered by the pointer-identity test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 07:48:31 +03:00
Hakan Seven
f11fc7dcb4 Merge PR #257: cache ribbon_groups() with OnceLock
perf(core): return &[RibbonGroup] instead of rebuilding the tree each
call. Function-local OnceLock per module + From<&T> IPC conversions +
borrow-based ribbon widget rendering.

Co-authored-by: Karim Jerbi <k4rim.jerbi@gmail.com>
2026-07-03 07:47:43 +03:00
Hakan Seven
b4e3902d64 fix(properties): stop block Attributes section rendering twice
A block reference's Attributes group was built in two places — the Insert
entity's own properties() builder and an older panel injection left from
the issue-192 attribute-editing work — so it appeared twice in the panel.
Both produced identical AttrText rows (get_value() just returns &value).
Drop the panel injection; the entity builder is the single source and
also covers multi-select. Attribute edits still route by tag, unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 07:21:21 +03:00
Hakan Seven
b5bee92335 chore(deps): bump acadrust to e06182f (AcDs 3DSOLID fix, issue 225)
Pulls the acadrust fix that links R2013+ 3DSOLID/REGION/BODY geometry to its
AcDs SAB blob (writes the has_ds_data flag, corrects the AcDsPrototype header,
and pairs blobs in object-stream order). DWG files saved with 3D solids now
reopen with their geometry intact in AutoCAD/BricsCAD instead of being dropped
as an empty data stream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 07:04:07 +03:00
Hakan Seven
1523eda00f feat(properties): fill solid-history + viewport clipped rows
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 <noreply@anthropic.com>
2026-07-03 00:17:58 +03:00
Hakan Seven
f50ad0dde4 feat(properties): fill placeholder rows from acadrust fields + doc lookups
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>
2026-07-03 00:05:58 +03:00
Hakan Seven
0a9c27e39d feat(entities): render + property builder for loaded Helix entities
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 <noreply@anthropic.com>
2026-07-03 00:05:58 +03:00
Hakan Seven
4398341632 chore(properties): remove dead helpers left by property-builder rewrite
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>
2026-07-02 23:34:01 +03:00
Hakan Seven
1043a3d561 feat(properties): fill spline Fit Tolerance + tangent rows from acadrust
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 <noreply@anthropic.com>
2026-07-02 23:34:01 +03:00
Hakan Seven
147255aa40 build(deps): bump acadrust to 486c0a9; handle the Helix entity
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 <noreply@anthropic.com>
2026-07-02 23:28:37 +03:00
Hakan Seven
41f4a062dc feat(properties): resolve Hyperlink (XDATA) and custom Material name
Fill the two placeholder rows added earlier. Read the General group's
Hyperlink from the entity's XDATA (the "PE_URL" application record's first
string). When an entity carries an explicit material (material_flags == 3),
resolve its material_handle to the Material object's name and show it in the
3D Visualization group instead of "Custom".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:25:02 +03:00
Hakan Seven
1f2d543755 feat(properties): dimension style-derived property groups
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 <noreply@anthropic.com>
2026-07-02 23:17:54 +03:00
Hakan Seven
f4c60a39a9 feat(properties): add 3D Visualization group + General Plot style/Hyperlink
Per the COMMON section of PROPERTIES.md. Insert a "3D Visualization" group
(Material, Shadow display) after General for every graphical object, and add
Plot style + Hyperlink rows to General. Material / plot style / shadow are
sourced from the entity's flags (ByLayer / ByBlock / custom); resolving a
custom material or plot-style handle to its name, and reading the hyperlink
from XDATA, are follow-ups that need document access.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:07:50 +03:00
Hakan Seven
b9cebab951 feat(properties): conform entity property groups to PROPERTIES.md
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>
2026-07-02 23:03:18 +03:00
Karim Jerbi
73b05e1dec perf(core): cache ribbon_groups() with OnceLock, avoid per-frame tree allocations
Change CadModule::ribbon_groups() from -> Vec<RibbonGroup> to -> &[RibbonGroup],
backed by a per-module OnceLock cache. The ribbon group tree is static data
(&'static str, Copy enums) that was being fully reconstructed — Vecs, String
clones, enum discriminants — on every call, including all three per-frame
call sites (view(), dropdown_overlay(), style_combo_overlay()).

- 7 built-in modules + 2 plugin templates cache via function-local OnceLock.
  Safe because each is a unit struct with exactly one instance per process
  (plugins run in isolated child processes; see PluginProcess::spawn()).
- SharedCadModule avoids the static pattern entirely, storing groups in an
  instance field (owned.rs).
- IPC runner converts &RibbonGroup -> OwnedRibbonGroup directly via new
  From<&T> impls, avoiding an intermediate clone.
- render_small/render_large and the two make_tool_row closures now borrow
  (&RibbonItem, &[ToolDef]) instead of taking ownership, so the view loop
  doesn't need to clone items to satisfy the old by-value signatures.
- Various match-ergonomics deref fixes (*id, *default, *icon, etc.) from
  the &RibbonItem pattern change.
2026-07-02 21:00:57 +01:00
Hakan Seven
b2b2bfb246 refactor(properties): entity property groups return Vec<PropSection>
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>
2026-07-02 22:46:19 +03:00
Hakan Seven
8164e14a19 fix(plugin): auto-register unknown layers so added entities survive DWG save (#252)
An entity added via a plugin's AddEntity with a common.layer name that no
LAYER command ever created kept its layer in-session, but the DWG writer
resolves an unknown layer name to a NULL handle, so on reopen the entity
collapsed to layer 0.

Add a shared Scene::ensure_layer(name) that registers a missing layer with a
real handle (allocate_handle, per #67) and call it from add_entity and
update_entity. This covers the plugin AddEntity/UpdateEntity IPC and every
internal add path without a new IPC request. Existing/empty/"0" layers are
no-ops, so normal draw commands are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:36:16 +03:00