The continued dimension line was positioned with an offset measured from
the base dimension's *first* extension origin, then re-applied to each new
chain point (the previous dim's second origin). When the two extension
origins sat at different perpendicular positions — i.e. the extension
lines had different lengths — that offset placed the new dim line at
`D + (p2 - p1)·perp` instead of the base line's `D`, so the chain drifted
off the base line. Equal-length extension lines have `(p2 - p1)·perp = 0`,
which is why it only misbehaved sometimes.
Store the base dim line's absolute perpendicular coordinate and project
each extension origin onto it, so the whole chain stays collinear
regardless of extension-line lengths. The preview uses the same
projection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DIMALIGNED's drag preview never matched where the dimension landed. The
preview offset the dimension line in the XZ plane while the committed
entity's renderer offsets in the XY plane, and the commit path passed the
straight-line p2→cursor *distance* to `set_offset`, which re-applies it
along the perpendicular — wrong distance, always on the +perp side. Both
now store the cursor as the definition point and project it onto the
XY-plane perpendicular, matching the renderer and DIMLINEAR.
The same XZ-vs-XY plane confusion affected other annotate commands:
- DIMORDINATE worked entirely in the XZ plane (dropping the Y coordinate)
and left `definition_point` at the origin, so the leader kinked through
(0,0,0). Reworked onto the XY plane with a proper orthogonal elbow and a
live preview that matches the placed entity.
- DIMTEDIT / DIMJOGLINE cursor markers were built in the XZ plane, so in
the top-down view they collapsed to a flat line instead of a box /
zigzag. Rebuilt in the XY plane.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Whole command/snap/grip/cursor chain now builds on f64 DVec3 end to end,
so picked points, snaps and grip drags stay precise at UTM-scale
coordinates instead of quantizing to ~0.5 m through f32.
- All ~80 command files migrated: draw (line/circle/arc/ellipse/shapes
in full f64; others via point methods), modify, annotate, insert, view,
inquiry, layers, groups, clipboard, layout.
- Entity apply_grip impls updated for GripApply::{Absolute,Translate}(DVec3).
- Construction sites (EntityTransform/CmdResult/DynAnchor) emit DVec3;
wire-preview boundaries cast to f32 only at the GPU edge.
- Modify/annotate commands keep internal Vec3 geometry where precision is
non-critical, casting at the DVec3 boundary; key draw commands store f64.
Builds clean (no warnings). Visual verification of crosshair/snap/grip at
extreme UTM zoom pending.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Table / PolyfaceMesh / greek-text / mleader-background fills go through
the face3d pipeline; their vertices were single f32 and quantized at UTM
scale. WireModel now carries a fill_tris_low residual (filled by the
tessellate Lines path), Face3DVertex gains position_low (location 3), and
face3d.wgsl reconstructs the eye-relative position from the pair. 3DFACE
quads built from key_vertices use a zero residual (no low source).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Foundation for double-single positions: WireModel now carries an
index-paired f32 residual buffer (`points_low`) next to the existing
`points` high half. Empty means "all-zero residual", so every existing
constructor (interactive draw, previews) is unchanged in behaviour;
tessellation from CAD f64 will fill it in the next steps so vertex
precision survives at UTM-scale coordinates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New annotations now place their text and reading geometry along the
active UCS X axis (identity = world), so they read square to the user's
coordinate system instead of world-horizontal:
- TEXT / MTEXT: rotation defaults to the UCS X angle
(DocumentTab::ucs_rotation_angle, model space only).
- Linear dimensions: measure along the UCS axis the span is nearest, and
bake the UCS X angle as the text rotation so the value reads square
even under a style that forces horizontal text. DIMCONTINUE / DIMBASELINE
derive their axis from the base dim's rotation (any angle), not a
world H/V test.
- LEADER: the MText annotation rotates to the UCS, and the hookline runs
along the leader's horizontal_direction (set to UCS X), used by the
renderer instead of world horizontal.
- MLEADER: context.text_direction = UCS X drives both the text rotation
and, in the renderer, the text side and dogleg/landing direction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- 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.
UI chrome (dropdown carets, undo/redo, checkmarks, close/menu/osnap,
layer/file-menu icons) and ribbon tool icons were drawn as Unicode
glyphs. Desktop fills missing glyphs via system-font fallback, but the
web build bundles only Fira Sans with no fallback, so they rendered as
empty tofu boxes (issue #116).
Add a shared src/ui/icons.rs (SVG-backed, tintable) plus bundled assets,
and switch every glyph site and IconKind::Glyph tool def to SVG.
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>
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>
The pick/typed point is the world XY plane (z = elevation = 0), but
several entity builders still mapped the screen point as if the world
were Y-up (drawing in XZ), reading the constant elevation as the DXF Y.
That collapsed shapes onto a single line — e.g. a revision cloud always
came out as two horizontal lines regardless of the path drawn.
Map the drawing-plane point straight through (x→x, y→y, z→elevation) in:
revcloud, donut, mline, raster image, wipeout (polygonal + rectangular),
table, attribute definition, and tolerance. Previews already used the
correct pass-through, so they matched the cursor; only the committed
geometry was wrong.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworks MLEADER text/leader geometry and grip editing, and aligns LEADER
to MLEADER's conventions (kept as separate entities).
Text & landing (render path):
- Text is side-anchored on the leader-facing edge and reads outward; the
side, alignment and the horizontal landing are recomputed every frame
from the text grip's position relative to the leader, so moving the
arrow or the text re-mirrors the whole layout and the landing stays
attached to the text's near edge.
- Fixes#49: text was centred on the leader endpoint regardless of side.
Grips:
- Text-location grip plus an MTEXT-style triangle wrap-width grip at the
box's far edge; dragging it sets the MText wrap width.
- Multifunction grip menu restored and made functional: leader-line
vertices offer Add Leader / Remove Leader (Add grabs the new arrow so it
follows the cursor — click places, Esc removes it); the text grip offers
Move Independent (text only) and Move with Leader (translates the whole
multileader via a move-all sentinel grip).
Arrows:
- LEADER and MLEADER arrowheads now render at the same size (text height),
matching previews.
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.
Type-as-you-go suggestion popup above the input that filters known
command names by substring (so `LEADER` surfaces both LEADER and
MLEADER). Up / Down navigates the list, Enter dispatches the
highlighted entry, clicking a row dispatches directly. Esc on a
non-empty input clears the buffer instead of escalating to
command-cancel.
The name corpus comes from `inventory::submit!` blocks placed next
to each `CadCommand` impl rather than a hand-maintained static list.
Adding a new interactive command means dropping one `inventory::submit!`
line in its own module file — the autocomplete picks it up at the
next compile. 98 submissions seeded across 60 module files;
`command::all_registered_command_names()` flattens them at runtime.
One-shot dispatch arms in `app/commands.rs` (NEW, OPEN, SAVE, UNDO,
…) don't have a `CadCommand` impl and are therefore absent from
autocomplete by design.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Tessellation and DIMBASELINE pick up the few DimStyle fields that were
still hard-coded or ignored:
- DIMTMOVE = 1 draws a connector segment from the dim-line anchor to the
saved text_middle_point when the text has been moved
- Leader arrows are resolved via the leader's DIMSTYLE → DIMLDRBLK; the
shape (closed-filled / open / dot / tick / …) is drawn through the same
arrow emitter the dim path uses, including fill_tris for filled blocks
- DIMBASELINE picks up DIMDLI from the active DimStyle instead of the
hard-coded 1.5 increment
- DIMTOFL / DIMTIX / DIMATFIT / DIMUPT / DIMTXTDIRECTION are read so the
fields round-trip on save; their effect is at dim creation, not render,
so they're acknowledged with a no-op block
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PolyfaceMesh and PolygonMesh were wireframe-only. They now render with
the same flat-color solid fill as Face3D entities.
- Add fill_tris: Vec<[f32;3]> to TruckEntity and WireModel
- PolyfaceMesh: fan-triangulate each face into fill_tris
- PolygonMesh: triangulate each grid quad into fill_tris
- tessellate.rs Lines branch: apply world_offset to fill_tris
- Face3DGpu::from_wires: collect fill_tris from all wires in addition
to Face3D key_vertices quads
- upload_face3d: pass all_wires so fill_tris are batched into one draw call
Viewport-projected wires now clip at pixel level to their viewport boundary.
- WireModel.vp_scissor: Option<[f32; 4]> — paper-space bbox set on projection
- WireGpu.vp_scissor carries it through to the draw loop
- Pipeline.compute_wire_scissors() converts paper→NDC→pixel each frame
- render() calls set_scissor_rect() per wire, reset after viewport group
- INTEGRATION_GAPS.md: Viewport ⚠️ → ✅, all 51 gaps resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LwPolyline PLINEGEN=0 (DXF default): emit SegmentedLines with NaN
separators between vertex-to-vertex segments; wire_gpu resets
arc-length to 0 after each NaN so linetype pattern restarts per vertex.
PLINEGEN=1: existing Contour path unchanged (continuous pattern).
WireModel gains plinegen field propagated through tessellate pipeline.
Solid3D/Region/Body: call SabReader::read() when parse_sat() returns
None due to binary SAB format (DWG files / newer AutoCAD exports).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add `aabb: [f32; 4]` field to WireModel (world-space 2-D bounding box).
In tessellate_entity() set each wire's AABB from e.as_entity().bounding_box()
via new entity_aabb() helper; Insert sub-entities each get their own AABB.
Preview/interim wires and any entity returning a zero-extent default box get
UNBOUNDED_AABB so they are never pre-rejected.
Replace the chord-sphere heuristic in Snapper::wire_in_range() with a proper
AABB vs snap-circle overlap test. The new check is four scalar comparisons and
correctly handles closed curves (circles, arcs) which the chord-sphere approach
had to special-case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Redesign View ribbon tab with 8 groups matching AutoCAD layout:
Viewport Tools (UCS Icon, ViewCube, Navigation Bar),
Navigate, Model Viewports (Viewport Configuration, Named, Join, Restore),
Visual Style (dropdown), Projection, Preset,
Palettes (Tool Palettes, Properties, Sheet Set Manager),
Interface (File Tabs, Layout Tabs, Tile Horiz/Vert, Cascade)
- Add Extract Data and Link Data tools to Annotate → Tables group
- Fix tab order to Home → Insert → Annotate → View → Manage via build.rs PRIORITY list
- Add 17 new SVG icons for all new commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New StyleComboGroup RibbonItem renders a dropdown combobox for selecting
text style, dimension style, multileader style, or table style. Ribbon
groups in the Annotate tab now match the reference layout: each group
has a large tool on the left and a style combobox with tool rows on the
right.
Each combobox shows the active style name, opens a scrollable item list
on click, and has an optional "Manage…" row that fires the style manager
command (STYLE / DIMSTYLE / MLEADERSTYLE / TABLESTYLE).
Style state is synced from the document on every layer refresh via
sync_ribbon_styles(). RibbonStyleChanged message propagates selections
back to the acadrust document header and the active_mleader_style field.
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>
- ordinate_dim.rs: OrdinateDimCommand picks feature point then leader
endpoint; auto-detects X vs Y ordinate from movement direction
- Ordinate dim added to the Dimensions dropdown in the Annotate ribbon
- ROADMAP.md: DIMORDINATE ✅, Named UCS (already implemented) ✅
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>
DIMALIGNED: 3-click flow (p1 → p2 → dim line position) → DimensionAligned.
Rubber-band preview shows extension lines and dim line offset.
DIMDIAMETER: 3-click flow (center → arc point → text position) → DimensionDiameter.
Previews the diameter chord line.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>