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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
#249 — plugin XDATA written via write_record survived only in memory: the
acadrust DWG writer dropped ExtendedData::records on save. Bump acadrust to
e88a9a6 (records now encode to EED and decode back on read) and fix the host
side that fed it:
- ensure_app_id allocates a real APPID handle; a null handle serializes as
0 and the EED reference can't resolve, so the XDATA vanished on reopen.
- write_record / remove_record drop stale raw_dwg_eed for the target app so
an edit made after a save/reopen wins over the pre-edit bytes.
#250 — out-of-process plugins got a throwaway document_mut() snapshot, so
edits to existing entities were silently discarded and deletion wasn't
expressible at all. Add the missing mutation surface:
- UpdateEntity / RemoveEntity IPC requests + HostApi::update_entity /
remove_entity (default in-process impls, RPC overrides on the client that
invalidate the stale document cache).
- Scene::update_entity replaces the entity in place, preserving its handle
and owning block, and reseeds only its derived caches; remove reuses the
cache-coherent erase_entities (which also honours layer locks).
- document_mut() is documented as a local-only snapshot out-of-process.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
XREFs are merged into the document on the UI thread AFTER the background
worker already built the mesh caches, so those caches contained none of
the xref'd geometry. The wire pass rebuilds from the document each frame
(bump_geometry covers it), but 3D-solid meshes are only produced by
populate_meshes_from_document, which never re-ran after the merge — so
xref'd solids (walls, floors, roofs) were loaded but never tessellated
and never drawn. Re-run it once a xref actually resolves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The batch split at a fixed 6M-vertex cap that assumed 40 B/vertex, but
MeshVertex grew to 52 B when the double-single `position_low` field was
added — so a full chunk was 6M x 52 = 312 MB, past wgpu's 256 MB
max_buffer_size, and Device::create_buffer panicked on large models.
Derive the caps from device.limits().max_buffer_size and the real vertex
size, bound both the vertex buffer and the (fatter) wire-index buffer,
and split any single mesh too large for one chunk into triangle-soup
sub-chunks so no batch buffer can exceed the device limit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The browser can't call the Patreon API directly (CORS, and the token
would be exposed in the bundle), so the web build fetches a
pre-generated supporters.json served on the same origin. The Pages
workflow generates it server-side with the token (CI secret) after the
trunk build. Native keeps its live API fetch.
serde_json moves to the shared dependencies so the wasm build can parse
the list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The orthographic near/far span was ±1000x the camera distance, so
zooming out ballooned the depth range and collapsed z-buffer precision:
coincident solids, meshes and wires then flipped draw order (meshes drew
in front of solids only when zoomed out, correct order when zoomed in).
Size the depth half-range from the drawing's extent instead — the bbox
diagonal on fit_to_bounds, the view height on a restored saved view —
and hold it constant as the camera zooms, so precision no longer depends
on distance. Falls back to the old distance-scaled range when unset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PolyfaceMesh / PolygonMesh / SubDMesh fills and 3DFACE quads were
classified as 2D fills and given the draw-order z-bias, so they were
pulled toward the camera and drew over solids. The tessellator emits a
mesh's edges and its fill as separate WireModels (points-only vs
fill-only), so the old `!points.is_empty()` discriminator misrouted the
fill. Classify a fill as a 3D surface by its double-single low residual
(`fill_tris_low`) instead, and drop the draw-order bias from 3DFACE
quads. 2D annotation fills (text greek, MultiLeader / dimension
backgrounds) leave `fill_tris_low` empty and keep ordering by rank.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a right-hand rail on the Start page listing active paying patrons
(name + pledge amount, highest first) with a "Support on Patreon" button.
The list is fetched once at boot from the Patreon API in the background;
free followers, $0 tiers, declined and former patrons are excluded.
The creator access token is read at build time from OCS_PATREON_TOKEN
(option_env!), so it never lives in source or git — the release workflow
passes it from a repo secret, and build.rs re-bakes when it changes.
Without a token the rail just shows the support button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show two clickable video thumbnails side by side on the Start page; a
click opens the video in the system browser. Refactor the card into a
shared builder and bundle the second thumbnail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pull in acadrust 4ae2ceb, which fixes two R2013+ (AC1027) DWG writer
bugs that made OCS-saved files unreadable in AutoCAD/TrueView/BricsCAD:
- #182: R2013 header string stream was unlocatable because the file
wrote maintenance_version 0 (omitting the R2010+ extra RL).
- #225: 3DSOLID/REGION/BODY omitted the R2013+ revision block, corrupting
the entity stream and dropping 3D solids on load.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pull in acadrust ad0471e, which corrects the entity ENC color decode
for book colors. R2007 files whose hatches carry a book color (e.g.
the santeen.dwg from issue 46) desynced on read, dropping all 270
SOLID hatch boundaries; they now load and render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rework 3D view navigation:
- Turntable / lock-horizon orbit: horizontal drag yaws about world +Z,
vertical drag pitches about the horizontal camera-right axis, so the
view never banks. Pitch is pole-clamped but the straight-down default
view can still be tilted out of.
- Orbit centre: the selection's centre when something is selected,
otherwise the point under the cursor — no longer only the file centre.
The camera revolves rigidly about the pivot, so it stays put on screen
and the view doesn't jump.
- Mouse scheme: Zoom = wheel, Pan = MMB, Rotate = Shift+MMB. Right-drag
no longer orbits (right-click stays context menu / Enter). Floating
viewports orbit with Shift+MMB too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The window/crossing selection anchor was stored only in screen pixels, so
zooming or panning mid-drag left it frozen while the drawing moved under
it — the rectangle then covered the wrong world area and selected too much
or too little.
Store the anchor's world point and re-project it to the screen anchor
whenever the model-space view zooms (on_viewport_scroll) or pans
(on_viewport_move), so the rectangle tracks the drawing. The moving corner
already follows the cursor. (MSPACE floating-viewport box-select would
need the viewport camera and is left as-is.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Properties panel color picker is a custom bool-driven dropdown, while
Layer/Linetype/Lineweight are combo_boxes. Combo_boxes close each other on
blur, but nothing closed the color picker — so opening the Layer dropdown
while the color picker was open left both showing and overlapping.
Give every panel combo_box an on_open handler that collapses the color
picker, so at most one panel dropdown is open at a time.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Extension object snap worked but drew no tracking path, so it was
easy to miss — users had to keep the cursor exactly on the invisible
extension line without any visual cue.
When an Extension snap is active, draw a dashed guide line from the
endpoint it extends from, through the snap point and a little beyond,
and orient the three-dot marker along that direction. SnapResult carries
the endpoint screen position (filled once the winning snap is known);
the overlay renders the guide.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Color/linetype/lineweight/layer are baked into the cached wire geometry
at tessellation time, so a property edit only repaints if the handler
bumps the geometry epoch. The color path did; several siblings only set
tabs.dirty (a save flag) and never re-tessellated, leaving the viewport
showing the stale look.
Route every handle-based edit through invalidate_property_targets
(mark dirty + recolor meshes + bump geometry), and full-bump the
name-based LAYER COLOR change:
- ribbon layer / linetype / lineweight dropdowns applied to a selection
- CHPROP (color/linetype/ltscale/transparency/layer)
- LAYMATCH (MatchEntityLayer)
- LAYER COLOR <name> <aci>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Take the first selected entity as a template, adopt its general
properties (layer, colour, linetype, lineweight, linetype scale) as the
current defaults, then start the matching draw command so the object
drawn next inherits them.
Supports Point, Line, Circle, Arc, Ellipse, all polylines (PLINE), Text,
MText, Spline, Hatch, Solid, Ray and XLine; other types report that
creation is unsupported. Registered for command-line autocomplete.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The command-line history dropdown was display-only, so users had to
screenshot and OCR the log to report output. Make it copyable:
- Render the whole backlog in one read-only text_editor so a single
mouse drag selects across lines and Ctrl+C copies the span.
- Add Copy (whole log to clipboard) and Clear buttons to the dropdown.
- Gate the Ctrl+C/X/V accelerators on event Status::Ignored so a focused
text widget's copy/cut/paste wins instead of firing COPYCLIP/CUTCLIP;
the drawing's clipboard shortcuts still work when it has focus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Layer Manager only allowed one layer at a time. Now:
- Ctrl/Cmd-click toggles a layer in the selection; Shift-click selects
the range from the anchor; a plain click selects just one.
- Colour, linetype and lineweight changes apply to every selected layer.
- Delete removes all selected layers at once (reusing the #237 non-empty
warning, now pluralised); layer "0" and the current layer are skipped.
Selection is stored as row indices but re-resolved by name across sorts
and layer-table rebuilds, so it never points at the wrong rows. Modifier
state is tracked via a new SetModifiers message (shift + ctrl/cmd).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deleting a layer only dropped the layer record, leaving its objects in the
file still tagged with the (now-gone) layer name. Now:
- Empty layer → deleted straight away.
- Non-empty layer → a warning modal ("Layer X is not empty — N objects;
deleting also removes them. Continue?") with Delete Objects / Cancel.
Confirming erases every object on the layer, then removes the layer, as
one undo step. The layer record is dropped before the erase so the
locked-layer guard can't keep its objects.
- Layer "0" and the current layer can't be deleted (reported on the
command line) instead of silently leaving a broken state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A normal run dumped the whole host↔runner trace (spawn, handshake, every
per-command dispatch and request/response) to the terminal. Gate that
behind OCS_PLUGIN_VERBOSE and, in normal runs, print just one line per
plugin: `Loaded plugin: <name> (<id> <version>)`.
The chatty host-side `[plugin]` lines now go through a `vlog!` macro that
only fires when OCS_PLUGIN_VERBOSE is set. Runner-side logs were already
suppressed (the runner is spawned with stderr = null); the remaining
`[plugin]` lines in the IPC client are genuine error messages, left as-is.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Double-clicking a locked object no longer opens its text / attribute /
in-place block editor.
- Clicking a locked object during a command's object-gather no longer
counts as completing the selection (don't set selection_just_completed
on the locked path).
- copy_entities skips locked entities.
- The attribute editor's Apply refuses to write when the block's layer was
locked while the dialog was open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Locked-layer objects were fully editable. Now they stay visible and
snappable but are otherwise protected:
- Not selectable: the five interactive pick paths (box, lasso, single
click, cycling, box-release) plus SELECTALL and QSELECT skip locked
entities.
- Not modifiable (defense-in-depth even if a handle slips through):
transform_entities, erase_entities, apply_grip and the Properties
commit all skip locked entities.
- Faded: entities on a locked layer render dimmed toward the background
(render_style + wire tessellation); toggling lock re-tessellates.
- Snap still works: locked entities are intentionally kept in the wire
set (visibility_ok untouched), so object snap keeps finding them.
- Feedback: a small padlock is drawn by the crosshair when hovering a
locked object, and clicking one prints "Object is on locked layer …".
New Scene::is_layer_locked / locked_layer_name helpers centralise the
check (mirroring layer_hidden).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pulls the acadrust translate fix: MOVE now carries a block's attributes
and the whole MultiLeader (text/base/block-content anchors + break
points), not just the insert point / leader line. No OCS code change —
the transform path already calls translate and re-tessellates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The selection-filter popup now has the same Select All / Clear All header
as the OSNAP popup: Select All clears every exclusion (all types
selectable), Clear All excludes every present type. Each button disables
when it would be a no-op.
Also remove the "Object Snap Settings…" footer from the OSNAP popup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bottom status bar was hard to read: size-10 text labels in a 26px
strip. Make it legible and consistent with the drawing tab bar:
- Height 26 → 30 to match the document tab bar; the command-line input
pill matches too, so the three strips line up.
- Replace the drafting-aid toggle labels (ORTHO/POLAR/OSNAP/OTRACK/DYN/
LWT and the OCS pills ISO/QP/FILTER/SC/CLEAN/TPY) with 17px SVG icons;
the name stays in each pill's tooltip. Twelve new icons under
assets/icons/status, drawn in the existing 24×24 stroke style.
- Vertically centre every element via center_y, and bump all pill text to
size 12 with uniform padding plus larger chrome icons (menu, scroll
arrows, osnap dropdown) so nothing reads smaller than the toggle icons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
its value below.
- Text Options: the selected attribute's Text Style, Justification,
Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
Lineweight (the colour list also surfaces a non-standard current
colour so it displays and round-trips).
Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each row in the attribute editor now shows the attribute's prompt — the
text defined on the block's ATTDEF — falling back to the tag when the
block defines no prompt. The prompt is read from the block definition,
since attribute instances carry only tag + value.
Bumps the acadrust pin to pull the matching reader fix: its DWG object
reader was discarding the ATTDEF prompt, so every prompt came back empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Double-clicking a block reference that has attributes, or running ATTEDIT
on one, now opens an editor dialog listing every attribute (tag + editable
value) with OK / Cancel. OK writes the values back to the block, with undo
and a repaint; Cancel discards. A block with no attributes still enters
in-place block edit (REFEDIT) on double-click as before. (#192)
ATTEDIT opens the dialog directly when a suitable block is already
selected; otherwise it runs the pick command and the dialog opens once a
block is chosen. This replaces the earlier command-line, per-attribute
prompt flow, which is removed along with its __ATTEDIT__ sentinel path and
the now-unused attedit_set_attrs trait hook; the ATTEDIT command is reduced
to a plain block picker.
The dialog is a tab-scoped in-canvas modal (Plan B): its working copy holds
a document-local handle, so closing that tab or switching away dismisses the
editor rather than risk applying edits to another tab's document.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selecting an INSERT with attributes now adds an editable "Attributes"
section to the Properties panel — each tag is a row whose value can be
changed inline (Enter commits, undo works, the attribute text repaints).
Previously attribute values could only be reached through ATTEDIT, which
didn't let them be changed (#192).
Attribute tags are runtime strings, so they can't ride the geometry
edit path whose field key is `&'static str`; a dedicated PropValue::
AttrText variant plus PropAttrInput/PropAttrCommit messages carry the
tag. Values are stored verbatim (no expression evaluation, since
attribute text is free-form).
Also guard the panel's in-progress edit buffer: it now only carries
across a rebuild when the selection is unchanged (a commit-triggered
refresh), so a typed-but-uncommitted value can't display or commit onto
a different entity — e.g. two title blocks sharing a REV1 tag. This
closes a latent stale-buffer leak that affected geometry fields too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ribbon layer dropdown grew unbounded so a long layer list ran off the
bottom of the screen with no way to scroll. Cap the panel height and make
the list scrollable; short lists still shrink to fit.
While a dropdown was open, the 3D viewport beneath it kept tracking the
cursor over the panel's empty areas, and the OS cursor vanished there. In
iced 0.14 mouse_area/opaque only capture button presses, never CursorMoved,
so a higher stack layer can't swallow motion — gate it state-side instead:
- ignore viewport motion in on_viewport_move while ribbon.open_dropdown is set
- suppress the crosshair overlay's Hidden cursor (and crosshair draw) so the
normal OS cursor shows over an open dropdown
- route all ribbon dropdown backdrops through a shared dropdown_backdrop()
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PURGE removed unused layers/styles/linetypes from the document but never
refreshed the layer panel + ribbon caches, so the removed definitions
stayed visible in the UI (issue 228). It also never purged blocks.
- call refresh_layer_panel() after a purge so panel/dropdowns update
- purge unused block definitions (no INSERT/Dimension reference), skipping
anonymous, layout, and xref records; drop their member entities too
- add PURGE BLOCKS sub-arg; bare PURGE / PURGE ALL now include blocks
- report a per-type breakdown in the result message
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Layer color/linetype/lineweight and MATCHPROP edits set the
document dirty flag but never bumped geometry_epoch. The wire
caches key on that epoch and bake a ByLayer entity's resolved
color at tessellation time, so they returned stale wires until
a new entity forced a rebuild. Bump geometry on those four
handlers so the edit repaints immediately.
Also remove REGEN/REGENALL/REDRAW/REDRWALL: the GPU raster
pipeline keeps the display continuously in sync, so on-demand
regen/redraw is a no-op concept in OCS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opening / navigating a heavy 3D DWG (e.g. a BIM model with ~10k ACIS solids)
was effectively frozen. Several independent costs, all addressed here:
- Face3D edge/fill buffers were re-uploaded on every camera move (keyed on
camera_generation). They are world-space and selection-independent, so gate
them on (geometry_epoch, fill_mode) instead — a pan no longer re-walks every
wire.
- Solid meshes were drawn one buffer + one draw call per solid (~10k draws/
frame), which strangled the GPU front end. Concatenate every solid's LOD0
into a few large buffers (split only to stay under the 256 MB buffer cap) and
draw the whole set in a handful of calls. Built once per geometry epoch, so
selection / hover never re-pack it.
- Selection / hover highlight is now a small tinted overlay of just the picked
solids, drawn last with an Always-depth pipeline so it shows on top even when
the solid is occluded — instead of re-tinting and re-uploading all meshes.
- Hover / pick hit-testing scanned every triangle of every solid each move
(seconds on a heavy model). Add a 3D-AABB screen-rect broad phase
(MeshLodSet now carries z_aabb) and reuse the renderer's cached expanded mesh
set, so picking is O(solids) cheap projections plus the few solids actually
under the cursor.
- Block-internal hatch hit-testing exploded every INSERT on every hover.
Cache the result per geometry epoch and skip blocks that contain no hatch.
- Curved-surface (cone/sphere/torus) tessellation tolerance is now radius-
relative (CURVE_REL_TOL) so a cylinder's facet count is size-independent and
matches the circle/arc wire tessellation, instead of exploding on large radii.
Also adds docs/tessellation.md mapping every EntityType to its tessellation
path (truck B-rep vs truck curve topology vs direct).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pulls the quadratic→linear AcDs blob-extraction fix in acadrust. 3D-heavy
DWGs (and master files that xref them) that took 28s+ to parse — minutes
for a multi-discipline set — now open in well under a second each.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bare BACKGROUND now opens an interactive colour prompt (via
ValuePromptCommand, same idiom as PDMODE/LTSCALE) instead of just
printing usage, so the command works both step-by-step and as a
one-shot `BACKGROUND <colour>`. Options reordered to
Default/Black/DarkGray/Gray/LightGray/White; DEFAULT restores the app
default, now unified to rgb(33,40,48) across startup, load and reset.
Also drop the unreachable OPTIONS arm in dispatch_display.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entities inside a block definition that sit on layer "0" must take on the
properties of the layer the block reference is inserted on; every other
layer is "sticky" and keeps its own identity. OpenCADStudio resolved a
block child's ByLayer color/linetype/lineweight against the child's own
layer in all cases, so layer-0 content stayed layer-0 (white) instead of
inheriting the instance layer.
Resolution now follows the standard rule for block children — explicit
wins, then ByBlock → the insert's own style, then layer-0 + ByLayer →
the insert's *layer* style, else the child's own layer. The insert's
layer style threads through the whole block-expansion chain (and chains
correctly through nested inserts), with the child's own transparency
preserved.
Covered across every block-render path:
- wires (line/arc/circle/polyline/text) via the block cache + the
explode fallback + insert attributes
- hatch / solid fills (also fixes the PDF/print export path)
- 3D solid block children (per-instance mesh recolor)
- Dimension / Table baked-block sub-entities
New shared helpers in scene::view::render: InheritStyle, layer_render_style,
lineweight_to_px; render_style_for_block_sub gained the layer-0 branch.
Adds unit tests covering the layer-0, sticky-layer, ByBlock, explicit and
transparency cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modify commands invoked with nothing pre-selected (ERASE, MOVE, COPY,
ROTATE, SCALE, MIRROR, ARRAY*, STRETCH, EXPLODE, GROUP, BLOCK, the
clipboard cuts, LAYOFF/FRZ/LCK/ULK, ...) ran the command on the FIRST
picked object and terminated. You could not build up a selection.
SelectObjectsCommand now keeps gathering: each pick accumulates into the
set (Shift removes), the prompt shows the running count, and the command
fires only when the user presses Enter or right-clicks. Reuses the
existing JOIN gather pattern, so no viewport/host changes are needed.
LAYMCUR acts on a single object's layer, so it keeps the immediate
first-pick behaviour via the new SelectObjectsCommand::instant().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preview box rendered a hardcoded `text("AaBbCc 0123")` that ignored
every edited property. Replace it with a canvas that tessellates the
sample string through the same stroke path the text entities use
(`lff::tessellate_text_ex`), so font, width factor, oblique angle and the
backward/upside-down flags are reflected live as fields change. Effective
font follows the entity rule (TrueType name wins, else stroke font file);
the result is fit to the preview box with Y flipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>