The overall (sheet) viewport was created with its center as (x, 0, z) —
carrying the paper-height midpoint in z with y = 0. AutoCAD/TrueView read a
paper-space viewport center as (x, y), so the sheet view ended up centered at
y = 0, shifting the whole layout half a page down; activating the layout in
AutoCAD could fault on the off-plane viewport. Store the center as (x, y, 0),
matching the convention MVIEW already uses for floating viewports. Verified: a
newly created paper-space viewport no longer faults AutoCAD on layout switch.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Mark commands that are wired and dispatching as done: 3DARRAY, the solid
booleans (UNION/SUBTRACT/INTERSECT), the CONE/WEDGE/TORUS primitives,
LAYISO/LAYUNISO, DIST/ID/LIST inquiry, named-view VIEW, and QUIT. Refresh the
summary totals (done 141 -> 158, missing 91 -> 74).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
File/view/layer/style/undo-redo and similar one-shot commands dispatch a
single action instead of installing an interactive CadCommand, so they had no
module to register from and never appeared in autocomplete. Add a consolidated
CommandRegistration covering all 132 user-typeable one-shots (NEW, OPEN, SAVE,
SAVEAS, PLOT, PURGE, UNDO, REDO, ZOOM, LAYER, XREF, …). Internal dispatch
tokens the user never types (REFEDIT_BEGIN, REFCLOSE_SAVE, REFCLOSE_DISCARD)
stay excluded.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
ARRAY3D/3DARRAY, ATTEDIT/ATE, DDEDIT/ED and WIPEOUT/WO dispatched on literal
matches but had no CommandRegistration entry, so none surfaced in command-line
autocomplete. Add the inventory submit in each command's module, matching the
convention used by the other interactive commands. Audit confirms no remaining
active_cmd dispatch lacks a registration.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
TORIENT dispatched on a literal match but had no CommandRegistration entry,
so it never surfaced in command-line autocomplete. Add the inventory submit
like the other interactive commands.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Fold the Single/Multiple value parsing in TexteditCommand's Mode step into
the shared parse_texteditmode() helper, and report unrecognized input to
the user — the Mode step now stays active and re-prompts with
"Requires Single or Multiple." instead of silently ignoring the value.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Merge PR #130 (KarimJerbi) and resolve review findings:
- Conflict (settings.rs): keep current persisted-settings layout, append
texteditmode field.
- Conflict (command/mod.rs): keep both new CmdResult variants — main's
live-entity ops and the PR's text-edit suspend/resume.
- Bug: invalid TEXTEDITMODE value no longer aborts via the Measurement
arm; it now re-prompts with the error and stays active.
- Dedup: single parse_texteditmode() helper replaces the value parsing
duplicated across commands.rs, settings.rs and textedit.rs.
- Strip trailing whitespace.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
In-place block edit now reads more like a dedicated editor:
- Fade everything except the edited entities toward the background (wires,
block instances and 3D solid meshes) so the block stands out while the
surrounding drawing stays visible for context.
- Don't leave a command active on enter — the user edits the block's geometry
freely (move, grips, draw, erase) and finishes explicitly.
- Show the right-edge vertical toolbar with clickable Save (REFCLOSE_SAVE) and
Discard (REFCLOSE_DISCARD) buttons while a session is active, so the edit
can be ended without typing.
The fade clears on save/discard, restoring full brightness.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Picks up the acadrust transform_arc fix that rotates/reflects an arc's
start/end angles, so editing a rotated or mirrored block in place (and
rotating/scaling a standalone arc) keeps arcs intact.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Double-clicking a block reference (its wire or its solid body) now enters
in-place block edit (REFEDIT), so its geometry can be modified and the change
reflects in every instance on REFCLOSE.
Also drop REFEDIT's "non-uniform scale not supported" restriction: the
session now carries the INSERT's full forward/inverse affine (OCS, rotation
and non-uniform / mirrored scale) and applies it via the entity transform,
instead of a uniform scale + rotate + translate sequence. Mirrored blocks
(e.g. x_scale = -1) are editable now.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The instanced block solid meshes kept the inner solid's handle as their
name, but the highlight tint matches the mesh name against the hovered /
selected handle — which for a block is the parent INSERT. So hovering or
selecting a block whose body is a solid never tinted it. Tag the instanced
meshes with the parent INSERT handle so the orange hover / blue selection
tint applies.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Clicking a block whose body is a solid tried top-level solid meshes first
and block-internal ones only as a fallback, so a stray solid behind the
block always won and the block couldn't be picked. Merge both into one
depth-sorted hit-test (solid_click_hit) keyed by the solid's own handle or
the parent INSERT, so the front-most body wins regardless of kind.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A DXF SOLID renders as a cached solid-fill hatch, but transform_entities and
copy_entities only rebuilt that cache for true HATCH entities — a SOLID fell
through and kept drawing at its old position (or a copy showed nothing).
Rebuild the solid-fill hatch for EntityType::Solid in both paths, matching
the existing grip/add/open handling.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Box/lasso only tested top-level solid meshes, so a block whose visible body
is a solid (its mesh lives in the per-INSERT instanced cache, and it has no
wires to catch) was never picked up by a window/crossing/lasso. Add
block_mesh_box_hit / block_mesh_poly_hit that test each INSERT's instanced
block solid meshes and resolve a hit to the parent INSERT, and wire them
into the selection paths.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A dimension's final graphics are baked into a per-instance *D block, and the
render draws that block's sub-entities directly rather than the definition
points. Move/rotate/scale/mirror updated the definition points but left the
block sub-entities in place, so the dimension stayed drawn where it was.
Apply the same transform to the dimension's *D block sub-entities.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Box, window, crossing and lasso selection only tested wires and hatches, so
3D solids (which render as meshes) could never be selected that way. Add
mesh_box_hit / mesh_poly_hit that project the solid mesh and test it against
the box / lasso (window = all vertices inside, crossing = any vertex inside
or the region sitting inside the solid), and wire them into the selection
paths.
Also re-tessellate solids after a move/rotate/scale/mirror/copy: the entity
moved but its cached mesh didn't, so the solid stayed drawn at its old
position. Rebuild the mesh caches when a transformed/copied handle is an
ACIS volume.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The preview wires are tessellated in the target drawing's offset-relative
frame, but the centroid driving the preview translation was the source
drawing's, so the ghost drifted by the world_offset difference. Shift the
centroid into the target frame to match the committed paste. Zero within one
drawing.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Solids were missing from the entity transform dispatch, so move / rotate /
scale / mirror were silently no-ops on a 3D solid (only its wires shifted)
and a pasted solid stayed at its original location. Add a Transformable impl
for Solid3D/Region/Body/Surface (delegating to acadrust, which now composes
the move into the ACIS body placement) and register them in the dispatcher.
Also make cross-drawing paste land at the cursor: the clipboard centroid is
measured in the source drawing's offset-relative frame, so correct the paste
translation by the source/target world_offset difference (zero within one
drawing). Bumps acadrust for the solid-geometry transform fixes.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Cross-drawing paste recreated layers/linetypes/styles only for the
top-level selection, so objects inside a pasted block landed on layers the
target drawing didn't have. Scan the entities inside captured block
definitions for their dependencies too. Also refresh the layer panel after
a paste so the brought-in layers show in the manager and the dropdown.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
ACIS solids bake their colour into the mesh, so a property-panel or ribbon
colour (or layer) change left the solid showing its old colour — only the
wires, which resolve colour at render time, updated. Run recolor_meshes
(now covering block-definition meshes too) when property targets are
invalidated, and route the ribbon colour change through the same path.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Paste added the entities but never rebuilt the solid mesh caches, which are
only populated by populate_meshes_from_document (file open, undo, a few
commands). A pasted 3D solid — top-level or inside a recreated block
definition — therefore had no mesh and stayed invisible. Rebuild the mesh
caches after the paste so the solids show up.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Bump acadrust to pick up real ACIS body transforms (so moving/copying/
rotating a 3D solid relocates its geometry) and OCS-aware INSERT placement.
- body_transform: read the SAT transform record from its first 13 float
tokens instead of by raw index, which skipped the matrix because the
record leads with a book-keeping pointer — so the body placement was
silently ignored and solids never honored their stored transform.
- Insert grips: the grip lives in world space but insert_point is stored in
the block's OCS, so grip display and grip-drag now round-trip through the
OCS. A block whose extrusion normal isn't +Z no longer shows its grip in
the wrong place or drags along the wrong axes.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add an Invert Selection entry to the selection context menu (below
Select Similar): it replaces the current selection with every other
selectable object in the active layout. Candidates come from the visible
wire set, so objects on off/frozen layers stay excluded.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Copying an INSERT and pasting it into another drawing rendered nothing:
the clipboard snapshotted referenced layers/linetypes/styles but not the
block definition itself, so the pasted reference pointed at a block the
target drawing didn't have.
Capture every block an copied INSERT references — walking nested INSERTs
transitively, skipping model/paper space and xref blocks — and store the
definition's base point plus its owned entities. On paste, recreate any
of those blocks the target drawing lacks via a new define_block_raw
(entities kept in block-local coordinates), before adding the pasted
references. Same-document pastes are unaffected since the blocks already
exist.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Line, Circle and Arc reported their lengths/quantities in the Geometry
section, but polylines (and rectangles, which are closed polylines) only
listed vertex count, closed flag and elevation. Add read-only Length and
Area rows: length is the true path length with arc segments measured by
their arc length, area treats the polyline as closed with a circular
segment correction per bulge.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
JOIN previously only merged collinear lines or co-circular arcs into a
single Line/Arc and rejected everything else as "not co-linear" — so the
segments of a broken polyline, which meet at vertices but run at angles,
could never rejoin. It also flattened endpoints to the first segment's Z,
silently discarding height.
Rewrite the geometry pass to chain segments wherever their endpoints
touch, regardless of angle: a collinear straight run still collapses to
one Line, a planar chain becomes an LwPolyline (arcs carried as bulges),
and a chain with varying Z becomes a Polyline3D, preserving height.
Open/closed is detected from the endpoints; a selection that isn't a
single connected chain, or holds an unsupported entity / tilted arc, is
refused with a clearer message.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The rollover hit-test now sweeps wires, hatches, block-internal hatches
and shaded solid bodies, which is O(N) per cursor frame and was freezing
the crosshair on large drawings. Each idle move now clears the live
highlight and arms a dwell timer; HoverDwellTick runs the pick once the
cursor has been still for HOVER_DWELL_MS (120 ms), using the active
tile's bounds so the deferred pick matches the entity actually under
the cursor in tiled viewports.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A buggy external plugin that panicked anywhere — during load (version,
register, manifest), during ribbon build / dispatch, or inside an
interactive command (prompt, on_point, on_enter, on_object_pick) — took
the host down with it (#134). Run every cdylib entry point under
catch_unwind so the host stays up: the plugin's call is treated as
inert (load fails, dispatch returns "didn't handle it", interactive
hooks resolve to Cancel) and a "Plugin <hook> panicked: …" line lands
in the command line on the next tick. (#145)
Scope is Rust panics across the cdylib boundary, not genuine UB —
real isolation (segfault-proof) still requires out-of-process plugins.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The previous DIMTAD fix made every non-below value use the text-up side,
which broke DIMTAD=2 (Outside) — that should sit on the side farthest from
the measured points. Pass the DIMTAD value through instead of a below flag:
above (1) / JIS (3) / centred (0) use the text-up side, below (4) the
opposite, and outside (2) the away-from-object side. Tests cover above-up
and outside-away. (#144)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Dimension text vertical placement chose the perpendicular side away from the
measured points, so "Above" (DIMTAD 1) flipped to below whenever the
dimension line sat on the opposite side of the geometry. Place it on the side
the text top points to instead — the perpendicular of the dimension line
normalised to read left-to-right — independent of the object. (#144)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The point-in-polygon test in both hatch shaders walked edges
(v0,v1)…(vn-2,vn-1) but never the closing edge vn-1→v0, so an unclosed
boundary miscounted crossings and the fill bled outside the shape. SOLID
entities hit this — their four corners are not repeated — making color-wheel
fills spike past their bounds. Close each sub-loop (at NaN separators and at
the end); already-closed loops get a degenerate closing edge that crosses
nothing, so they're unaffected. (#140)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Ctrl+V was globally bound to the entity paste command, so it never pasted
into an open text editor, and even when routed there iced's clipboard read
is a no-op on the web. Route Ctrl+V to the focused editor and, on the web,
read the system clipboard via the browser's async Clipboard API (the
keypress is a user gesture, so the read is permitted):
- MText editor: insert at the caret (web + native).
- TEXT field: append on the web; native lets the text_input paste itself.
- No editor open: falls through to the entity paste command as before.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The web build has no system-font access, so CAD text in non-Latin scripts
(Cyrillic, Greek, CJK, …) rendered as nothing — build_fallback was a hard
None on wasm (#141). Instead, ship per-script Noto subsets under web/fonts
(one alphabet per file, SIL OFL 1.1) and fetch them lazily over HTTP the
first time a drawing uses that script, then outline glyphs with ttf-parser.
- web_font: Script enum + script_of (char → script font), a per-script
store with lazy fetch, and a pending queue drained by the app loop.
- CJK is split by language (chinese/japanese/korean). Han ideographs share
code points but differ by language, so the shared block is routed by the
document's $DWGCODEPAGE (932→JP, 949→KR, GB/936→CN); kana is always
Japanese, Hangul always Korean. Re-tessellates when the language changes.
- build_fallback (wasm) outlines from the fetched subset; clear_fallback_cache
lets glyphs that missed while a font was in flight reappear on load.
- PollWebFonts subscription (web only) + WebFontLoaded message drive fetches.
- Trunk copy-dir serves web/fonts; nothing is bundled into the native binary
(desktop keeps using system fonts).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The object-snap menu drew each mode's marker as a Unicode geometric glyph
(◻ △ ◯ …). The web build bundles only Fira Sans, which lacks them, so they
rendered as tofu boxes (#138). Add an SVG per snap mode under
assets/icons/osnap, map them via icons::osnap, and draw them with the same
tinted-SVG path the rest of the chrome uses — font-independent on every
platform.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The clap CLI work made boot() read crate::cli, but cli is cfg'd out on
wasm (the web build takes no args), so the wasm target failed to compile.
boot() is only used by the native iced::daemon run(); the web build boots
via boot_web(). Gate boot() with cfg(not(wasm32)) to match.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the ad-hoc `--serve` arg sniff with a clap-parsed CLI:
- positional <file> to open at startup (also the OS file-association path)
- --new, --read-only, --backend, --safe-mode/--no-gpu
- --serve/--port (headless JSON automation, unchanged behaviour)
- --export IN OUT (headless format convert via the automation load/save infra)
- --script FILE (run command lines at startup)
- --log/RUST_LOG, plus --version/--help for free
GUI-only options pass to app::boot through a OnceLock config; read-only
gates the save paths. The web build skips parsing (no args).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A close signal can arrive while the app is backgrounded, leaving the
in-window unsaved-changes prompt unseen behind other windows. Funnel the
prompt opener through gain_focus + a Critical user-attention request so
the window raises (Windows/macOS/X11) or at least flags for attention
(Wayland via xdg_activation). Every unsaved-dialog caller shares this path.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The OCS arbitrary-axis algorithm crossed the normal with Wx=(1,0,0) in the
else branch, which collapses to zero for an X-aligned normal (e.g.
N=(-1,0,0)): the OCS axes became zero and every point mapped to the
origin. The resulting coincident points fed a NaN truck arc whose
parameter_division never converged, hanging the load on "Parsing
entities". Cross with Wz=(0,0,1) per the DXF spec. (#142)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Ribbon tooltips were centered below the button, so the cursor resting on
the button covered the tip text. Anchor them to the right instead, clear
of the cursor. (#143)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
ACIS stores no outer-vs-hole loop flag, so plane_face derives the outer
boundary as the largest-area loop and winds holes opposite. Document why
this is robust: try_attach_plane takes its plane normal from the first
(outer) wire, making the choice self-consistent without relying on the
analytic face normal. No behaviour change.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A planar ACIS face lists its loops outer-first only by convention; a
pierced wall lists its window holes before the wall edge. plane_face
trusted loops[0] as the outer boundary and oriented every other loop
relative to it, so on a face with two or more holes one real hole wound
the same way as the true outer and truck's winding-number trim filled it
in. Select the outer boundary as the largest-area loop (it encloses the
rest) and wind all others opposite. (#123)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Ribbon layer dropdown rows were a single button, so a click on the
visible/freeze/lock icon fell through to the make-active handler instead
of toggling that state. Split the row: each icon is its own toggle
button, only the swatch+label selects the active layer. Toggle handlers
now resync the ribbon so the icon updates while the dropdown stays open.
Layer Manager columns are now sortable: clicking a header sorts by that
column, clicking again flips direction (SVG arrow indicator). Sort is
reapplied on every panel sync so it survives edits, preserves the
selection by name, and keeps the ribbon dropdown order in step.
Fixes#133
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Block-internal entities ignored their own layer state: 2D subs were
only filtered by the invisible flag in the block-defn cache, and 3D
solids render through a separate mesh path that applied no layer filter
at all. Both now skip subs on off/frozen layers, matching top-level
entities. Caches rebuild on every toggle, so visibility updates live.
Fixes#133
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Build pierced planar faces (a wall with a window opening) as truck faces
with inner hole boundaries: collect every face loop (outer + holes), wind
each hole opposite the outer so try_attach_plane cuts it, and let truck
triangulate. Drop the back-face cull on the solid-mesh pipelines so faces
show regardless of the import's winding (the shader already lights
two-sided). Relies on the acadrust SatLoop next_loop fix that makes hole
loops reachable. (#123)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Solids (meshes) now tint on rollover and selection: hover washes orange,
selection washes blue, with selection winning when both apply. Meshes
re-upload their tint when the highlight set changes, and the rollover pick
now also hits shaded solid bodies and hatches (mirroring the click-select
order: wire → hatch → block hatch → solid).
Wires get the same scheme: the selection xray overlay recolours selected
entities blue and the hovered entity orange (previously both blue). Drops
the now-unused highlight_handles union helper and its cache.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds a read-only Handle row at the top of the properties palette so an
entity can be identified by handle (e.g. when cross-referencing a file
dump or reporting a specific object).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
3D solid meshes inside block definitions were drawn once at block-local
coordinates without the INSERT transform, so a block authored in mm and
placed into a metre drawing at a 0.001 INSERT scale rendered ~1000x too
large. Split solid meshes into top-level (layout-owned, offset and drawn
flat) and block-definition (kept block-local) sets, and emit a transformed
instance of each block-def mesh per model-space INSERT, accumulating nested
transforms. Layout blocks are identified from the Layout objects'
block_record handles, since BlockRecord::is_layout() also flags ordinary
blocks in some files. (#123)
Co-Authored-By: Claude Opus 4.8 <[email protected]>