- Custom recursive resolver ensures Leader entities trace back to their editable Text/MText annotations.
- Bypasses overly strict baseline field checks so TEXTEDIT can natively target and launch the inline editor on Leaders.
- Adds TEXTEDIT command to seamlessly edit annotation objects in-place.
- Matches industry-standard CAD tools' prompt behavior and "Current settings" banner logic.
- Implements the TEXTEDITMODE system variable (0/1) for Multiple vs Single editing modes.
- Enhances command lifecycle with CmdResult::SuspendForTextEdit and post_editor_closed hooks, allowing commands to cleanly pause and resume after external UI editors close.
Turn the ribbon Clipboard "Paste" button into a dropdown:
- Paste — existing pick-a-point paste.
- Paste to Original Coordinates — drops the clipboard at its source
coordinates with no prompt.
- Paste as Block — wraps the clipboard contents in a new block definition
and starts an interactive insert (prompts for the drop point, with the
geometry rubber-banding under the cursor).
Also fixes two bugs the new commands exposed:
- entities_centroid summed the NaN separator points wire models carry,
so the paste base point came out NaN; non-finite points are now
skipped. A NaN base previously froze block tessellation outright.
- PASTEORIG added clipboard entities with their original handles
(duplicates on same-document paste); it now goes through
add_entity_clone and merges referenced records like the other pastes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`document.add_entity` only assigns the top-level handle, leaving inline
sub-entities that carry their own — INSERT attributes and 3D-polyline
vertices — with the source's handles. Copying such an entity therefore
produced duplicate handles in the document, which corrupts the saved DWG
so other CAD apps (QCAD, DWG TrueView) refuse to open it.
Add `Scene::add_entity_clone` / `reset_clone_subhandles`, which allocate
fresh handles for the entity and every inline sub-entity, and route all
duplication paths through it: COPY/ARRAY/MIRROR-copy (via copy_entities)
and clipboard paste. (#129)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy/paste between drawings dropped the entities' layer (and linetype /
text + dim style): the clipboard held only the entities, which reference
those records by name, so pasting into a drawing without a matching
record left the entity dangling on a non-existent layer. (#129)
Snapshot the referenced table records from the source drawing at copy
time (ClipboardDeps::capture) and, on paste, recreate any the target
drawing lacks with a fresh handle (merge_clipboard_deps). A record that
already exists by name in the target is left untouched — the target's
own definition wins; only missing ones are imported from the source.
Same-document pastes are unaffected (every record already exists).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Home tab was already titled "Draw"; rename the module to match:
folder modules/home → modules/draw, HomeModule → DrawModule, id "home"
→ "draw". The inner draw/ command folder is kept (modules::draw::draw).
Update build.rs PRIORITY ("home" → "draw") so the tab returns to the
front of the ribbon; registry.rs regenerates accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the `*_model` naming of the sibling render models (wire_model,
hatch_model, mesh_model, image_model). Also realign the related Scene
API for consistency: model_solids → solid_models, add_model_solid →
add_solid_model, register_model_solid → register_solid_model.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.
Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MIRROR now asks "Erase source objects? [Yes/No] <No>" after the second
mirror-line point. No (default) keeps the original and adds a mirrored
copy; Yes flips the original in place. Make the BatchCopy command-line
message label-aware and singular/plural so MIRROR reports "1 copy".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
STRETCH edited geometry directly via document.get_entity_mut, which the
scene's tessellation cache doesn't observe, so the viewport kept showing
the pre-stretch shape until an unrelated redraw. Mark each stretched
entity dirty and bump geometry on completion, matching MOVE's
transform_entities path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Starting FILLET/CHAMFER from the ribbon left the command-line field
unfocused, so its Enter arrived as CommandFinalize and discarded a
typed option keyword ("R"/"D"), cancelling the command. Several
related dynamic-input issues surfaced from the same flow:
- CommandFinalize now forwards a non-empty command-line buffer through
CommandSubmit, so a typed keyword is handled the same whether the
command was started from the ribbon or the command line.
- sync_dyn_fields shows no coordinate box during an object-pick step
(needs_entity_pick / needs_structure_point_pick) — there is no point
to enter there, so typed keywords reach the command line cleanly.
- apply_cmd_result rebuilds the dynamic-input fields on NeedPoint, so a
step change (object pick -> radius entry) shows the matching box
immediately and typed digits land in it instead of the command line.
- Object-pick steps now draw a prompt-only hint pill at the cursor
(no input box).
- FILLET/CHAMFER remember the first pick across a mid-selection radius
or distance entry, resuming the second pick instead of restarting
selection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The truck B-rep pipeline is Z-up (solids extrude/raise along Z, profile
wires convert pass-through), but two spots still assumed a Y-up world:
- REVOLVE swapped the axis/origin to (x, z, y), so the revolve axis sat
in the XZ plane while the profile sat in XY → wrong solid. Use the
axis/origin straight through.
- OBJ import kept Y-up coordinates as-is on the (now Z-up) viewport, so
imported meshes lay on their side. Rotate +90° about X on import
(positions + normals): (x, y, z) → (x, -z, y).
EXTRUDE (sweep along +Z) and the box/cylinder primitives were already
Z-up and unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The drawing plane is world XY, but break, join, rectangular array, the
stretch/window move, the spline preview wire and several doc comments
still used the stale "Y-up XZ" mapping (world Z = DXF Y). That made
those commands operate against a constant-elevation axis: arc/circle/
line/spline break and join picked the wrong points, rectangular array
stepped rows along the Z (elevation) axis, window-stretch tested the
wrong window bound, and the spline wire drew in the wrong plane.
Map every drawing-plane coordinate through .y (elevation on .z),
matching the entity builders, the inquiry commands and the central
transform. Stale Y-up comments updated to avoid future confusion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the Home tab to "Draw" and add a Model tab beside it with two
groups:
- Model: BOX / CYLINDER / CONE / SPHERE / WEDGE / TORUS, placed with a
footprint + height. Each is built as a real ACIS Solid3D (acadrust
primitive builders) plus a truck B-rep, tessellated into the shaded
mesh pipeline, with edge wires for picking + wireframe.
- Design: UNION / SUBTRACT / INTERSECT via truck-shapeops.
A session-only Scene.model_solids cache holds each solid's truck B-rep
so the boolean tools can combine and chain them. Supersedes the old
command-line BOX/SPHERE/CYLINDER (insert::solid3d_cmds keeps EXTRUDE/
REVOLVE/SWEEP/LOFT).
Known limitations: booleans need solids created this session; curved
ACIS primitives render in-session but not after reload (the SAT
tessellator handles only planar caps); geometry assumes world_offset 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route every text-bearing entity to an in-place editor instead of the
command line: a plain text box for single-line text (Text, attributes,
dimension override, tolerance) and the rich MText editor for MText /
MultiLeader. A Leader resolves to the entity it annotates.
- TEXT places a plain text box at the click; double-click edits in place.
- DDEDIT and double-click on any text entity open the matching editor.
- MLEADER places the leader then opens the MText editor on its content.
- LEADER follows the classic flow: points, Enter, then a linked empty
MText annotation opens in the editor; the leader's annotation_handle
links the pair so they edit/erase as a unit.
Adds shared field read/write helpers and a begin_text_edit router, plus
CommitAndEditText / CommitManyAndEditText results that commit an entity
and open its editor (commit_entity now returns the new handle).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Added SVG icons for text formatting options (bold, italic, underline, overline, strike, uppercase, lowercase).
- Introduced a new `MTextEditorState` struct to manage the state of the MText editor.
- Implemented functionality to open the MText editor from the command line and edit existing MText entities.
- Added message handling for various MText editor actions (formatting, height, style, font, justification, alignment, line spacing).
- Created a dedicated preview area for MText rendering within the editor.
- Updated the command structure to support opening the MText editor and handling text input.
- Refactored existing MText command logic to integrate with the new editor.
Two behaviour changes that bring the command line in line with
established CAD workflow:
1. **Always-on keyboard capture.** The input was blurred whenever no
command was running, so typed letters went nowhere until the user
clicked into the field. Replace the blur with a re-focus on every
command lifecycle transition (cmd_result.rs) and on viewport / tab
/ ribbon clicks (update.rs), focus the input at startup, and add
a global key subscription that catches any unhandled character key
or backspace and routes it into the buffer via two new messages
(CommandAppendChar / CommandBackspace) — so the command line keeps
accumulating keystrokes even if focus parks somewhere not
focusable. Removes the unused blur_cmd_input helper.
2. **Space submits.** Typing a space at the end of the input now
submits the buffer the same way Enter does. Only exception: an
active command that's currently collecting free-form prose
(TEXT / MTEXT / DDEDIT body, ATTDEF prompt / default value,
INSERT attribute value, ATTEDIT cell). Those advertise the new
`CadCommand::wants_text_with_spaces()` flag so the Space character
reaches the buffer as a literal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Joining the OpenAEC Foundation (open-aec.com). Crate/struct become
OpenCADStudio, user-facing strings use the spaced "Open CAD Studio"
form, repo URLs point to HakanSeven12/open-cad-studio. Logo (window
icon + assets/logo.svg) redrawn with OCS initials. Drop examples/
binaries — unused.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ACIS SAT entities (Solid3D / Region / Body) were tessellated at one
fixed sampling density (CIRC_SEGS=48, GRID_U=32, GRID_V=16) regardless
of how far they were from the camera. Distant solids paid the full
triangle bill for fragments that mostly resolved to sub-pixel size.
Generate three pre-built LODs per entity at load time:
* LOD 0 (HIGH) — 48/32/16 segments. Use above 200 px projected diag.
* LOD 1 (MID) — 24/16/8. Use 50–200 px.
* LOD 2 (LOW) — 12/8/4. Use below 50 px.
Tessellation cost is paid 3× on load (cheap — typical files have few
solids). Render picks per-frame via `compute_mesh_lod` from each mesh's
`world_aabb` projected diagonal. When a level is missing (e.g. a solid
entity that only emits LOD 0), the selector walks down to the nearest
available slot.
API ripples:
* New `LodConfig` (HIGH/MID/LOW) drives `tess_sat`, `tess_cone_face`,
`tess_sphere_face`, `tess_torus_face`.
* `tessellate_solid3d / _region / _body` now return `MeshLodSet`.
* `MeshLodSet { lods: Vec<MeshModel>, world_aabb }` + `from_single`
constructor so interactive truck-based commands (BOX/CYLINDER/etc.)
that only generate one tessellation stay one-line.
* GPU side gets `MeshLodGpu { lods: Vec<MeshGpu>, world_aabb }` and
a `mesh_lod_levels` per-frame index vector.
* STL/STEP export pulls slot 0 explicitly so the on-disk geometry
isn't downgraded by the view-dependent ladder.
Background-thread lazy generation (per the roadmap) is deferred — the
synchronous eager build is fine for typical CAD files. Switch to a
queue + background tessellator when files with hundreds of solids
become a load-time bottleneck.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Subtracts the DXF header extents centroid (world_offset) from all coordinates
in f64 BEFORE casting to f32. This eliminates the ~0.5 m ULP error that occurs
at 4,000,000 m UTM coordinates and caused geometry to disappear on zoom-out.
- Scene: adds world_offset field + compute_and_set_world_offset() method
- tessellate.rs: threads world_offset through all f64→f32 conversions
(legacy_geometry, dimension_geometry, solid_wire_fallback, multileader)
- truck_tess.rs: to_local() helper already in place from prior session
- mod.rs: tessellate_entity() and entity_aabb() apply world_offset
- update.rs: compute_and_set_world_offset() called after doc load
- cmd_result.rs / solid3d_cmds.rs: pass correct offset to truck tessellators
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New dimension commands: QDIM, DIMEDIT, DIMTEDIT, DIMBREAK, DIMSPACE,
DIMJOGLINE with full command workflows and SVG icons.
New multileader commands: MLEADERADD, MLEADERREMOVE, MLEADERALIGN,
MLEADERCOLLECT with entity injection pattern for in-place editing.
Updated annotate ribbon: all tools exposed across Text, Dimensions,
Leaders, Tables, Markup, and Annotation Scaling groups. Added STYLE,
FIND, DIMSTYLE shortcut buttons.
Added `inject_picked_entity` to the CadCommand trait for commands that
need a cloned copy of the picked entity to modify (DIMTEDIT, MLEADERADD,
MLEADERREMOVE). Sentinel-based dispatch in cmd_result.rs handles
DIMBREAK, DIMSPACE, DIMJOGLINE, MLEADERALIGN, and MLEADERCOLLECT.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Picks 2+ profile entities (closed or open wires), builds ruled shells
between consecutive pairs via builder::try_wire_homotopy, caps closed
ends with planar faces, and tessellates the result into a Solid3D mesh.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Picks a profile (closed or open 2D entity) then a path entity
(Line or LwPolyline). For Line paths uses tsweep along the line
vector; for Polyline paths uses start→end as translation direction.
Closed profiles produce a capped Solid; open profiles a Shell.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BOX, SPHERE, and CYLINDER build truck topology solids and tessellate
them into MeshModels stored alongside a Solid3D placeholder entity so
the GPU mesh pipeline renders them immediately.
EXTRUDE picks any closed 2D profile (Circle, LwPolyline, etc.),
attaches a planar face via try_attach_plane, and translational-sweeps
it (tsweep Face → Solid) along Z by the given height.
REVOLVE picks a profile, two axis points, and an angle (default 360°),
then rotational-sweeps the wire (rsweep) around the axis.
Three new CmdResult variants drive the operations:
CommitSolid3D, ExtrudeEntity, RevolveEntity.
Scene::layer_color() added to retrieve the active layer's RGBA color.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When inserting a block that contains AttributeDefinition entities,
the INSERT command now enters an attribute-filling phase: each attdef
is prompted tag-by-tag (showing its prompt text and default value).
Pressing Enter accepts the default. The completed AttributeEntity list
is embedded in the Insert before committing.
Blocks without attdefs continue to insert immediately as before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- attedit.rs: AtteditCommand picks an INSERT entity then iterates through
each attribute tag prompting for a new value (Enter keeps existing)
- attedit_pending_handle() / attedit_set_attrs() trait methods inject
attribute data into the command after entity-pick (avoids document access
in the command itself)
- cmd_result.rs: NeedPoint handler injects attr data; ReplaceEntity detects
the __ATTEDIT__ sentinel and calls apply_attedit() on the document
- apply_attedit() encodes tag/value pairs as __ATTEDIT__tag\x01val\x02...
- ROADMAP.md: ATTEDIT marked ✅
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- splinedit.rs: SplineditCommand picks a spline (entity-pick phase) then
accepts CLOSE/OPEN/REVERSE/EXIT sub-commands via text input
- apply_spline_op() mutates the spline in the document directly, with knot
vector regeneration via generate_clamped_knots()
- Sentinel XLine trick: ReplaceEntity with a magic __SPLINEDIT_* layer name
is intercepted in cmd_result.rs before erasing the original entity
- Control-point dragging already works via the existing grip editing system
- ROADMAP.md: SPLINEDIT marked ✅
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced the simplified MOVE-based STRETCH with a real crossing-window
implementation. Workflow: pick two crossing-window corners, then base+target
points. Only vertices/endpoints inside the window are translated:
- Line: moves start/end independently
- LwPolyline/Polyline: moves individual vertices inside window
- Arc/Circle/Ellipse: moves whole entity if center is inside
- Insert/Text/MText: moves if insertion point is inside
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add HATCHEDIT (HE alias) command. Works on pre-selected hatch or via
entity pick. Accepts text options: P <pattern>, S <scale>, A <angle>.
Enter applies changes; rebuilds hatch entity and GPU model via add_hatch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add DDEDIT (ED alias) command that edits Text/MText/Attribute entities
in-place via the command line. Double-clicking a text entity in model space
now auto-launches DDEDIT for that entity. Supports Text, MText,
AttributeDefinition, and AttributeEntity types.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add VP freeze/thaw toggle columns to the layer panel when in a paper
space layout. Each viewport gets its own column (VP 1, VP 2, …) showing
a freeze icon per layer. Clicking toggles frozen_layers on the Viewport
entity and syncs back to the panel.
- scene: viewport_list() → [(handle, label, frozen_handles)]
- LayerPanel: VpCol + vp_frozen per layer, sync_with_viewports()
- Message::LayerToggleVpFreeze(layer_idx, vp_col_idx)
- Sync VP columns on layout switch, VPLAYER cmd, undo/redo, layer ops
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Entity pick → text input subcommands:
C / CLOSE → set closed flag
O / OPEN → clear closed flag
W <value> → set uniform width on LwPolyline vertices
X / EXIT → cancel
Command stays active after each op (CmdResult::PeditOp) for multiple edits.
Supports LwPolyline and Polyline2D.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Options via text input after entity pick:
DE <delta> — extend by delta (negative trims)
TO <total> — set absolute total length / arc length
P <percent> — scale by percentage
<number> — plain number treated as DE delta
Closest end to pick point is modified. Uses CmdResult::LengthenEntity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Selection-gathering mode: select objects, press Enter to apply.
- Lines: checks collinearity + no-gap, merges into single line span
- Arcs: checks same center/radius + contiguous angles, merges to Arc or Circle
- Uses CmdResult::JoinEntities; geometry computed in cmd_result from document
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3-step interactive flow:
1. Entity pick (first break point recorded at click location)
2. Optional first point refinement
3. Second break point → CmdResult::BreakEntity applied in cmd_result
Geometry:
- Line: project p1/p2 onto line parameter space → two line fragments
- Arc: project p1/p2 to arc angles → trim CCW from p1 to p2
- Circle: same as Arc, converts Circle → Arc
- LwPolyline: nearest-vertex split → two polyline fragments
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add three classic CAD measurement commands:
- DIST (DI): two-point pick → distance, angle, delta XYZ
- ID: one-point pick → coordinates
- AREA: multi-point polygon pick (Enter to close) → area + perimeter
New CmdResult::Measurement(String) variant ends active command and
prints result to command line without modifying the document.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Enforce status.locked in pan_active_viewport and zoom_active_viewport:
locked viewports now reject pan/zoom inputs entirely
- Add perspective, render_mode, shade_plot_mode, hide_plot, ucs_icon_visible
fields to the viewport property panel with appropriate pickers/toggles
- Add VPORTS command: lists all user viewports in the current layout
(id, size, position, scale, on/locked state) in the command line
- Add VPLAYER command: per-viewport layer freeze/thaw from the command line
(F <layer> to freeze, T <layer> to thaw, Enter to exit)
- Add CmdResult::VpLayerUpdate variant and its handler in apply_cmd_result
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Deactivate ribbon tool highlight when command ends (issue #1)
- Reset last_point on new command dispatch to fix polar snap offset (issue #2)
- FILLET/CHAMFER: disable text input during entity pick so clicks reach viewport (issue #3)
- Dropdown icon buttons now use RibbonToolClick so they highlight blue when active
- FILLET/CHAMFER: AutoCAD-style sub-step UX — typing R/D + Enter prompts for value
- Fix FILLET arc preview plane: arc was rendering in XZ instead of XY
- Bump version to 0.1.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>