Removes storm_sewer module and stormsewer crate so this branch is suitable for upstream PR. Plugin host, docs, and CadCommand acquisition hooks remain.
Reports showed structure placement could stall if command-line text entry
didn't register (ribbon-invoked commands, dynamic-input/focus interactions).
Make a canvas click ALWAYS place a structure, using whatever values were typed
so far (defaults for the rest) — so dropping an inlet works regardless of typed
entry. If typing works, enter invert/rim/area/C first, then click to place with
those values. Pipes are pick-first: click START then END structure; the pipe
commits on the second click with the current diameter/n.
Drop the early focus_cmd_input() return in the dispatch arms (the normal
dispatch tail already focuses the command line and records last_cmd).
15 module tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The structure/pipe commands took a viewport interaction first and then asked
for typed values. But a point-pick result (NeedPoint) does not re-focus the
command line, so after the click the typed digits went nowhere and Enter hit
on_enter -> Cancel ("Command cancelled.").
Reorder both commands to the OFFSET-style flow: collect typed values first
(command line focused at dispatch and kept focused through text input), then
take the viewport click last and commit from it.
- PlaceStructure: enter invert/rim/area/C, then click the location.
- PlacePipe: enter diameter/n, then click START and END structures; commit
from the second pick using the click points (connectivity stays exact via
structure handles).
- Dispatch arms focus the command line so the first prompt is typeable.
- Reverts the update.rs inject_picked_entity allow-list change (the pt-based
pipe no longer needs the picked entity).
15 module tests updated and green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the .ssn-file workflow with an interactive one driven by the canvas:
- Structures (SS_INLET / SS_JUNCTION / SS_OUTFALL) prompt invert/rim/area/C on
the command line and commit a CIRCLE tagged with STORMSEWER_STRUCT XDATA
[kind, invert, rim, area, C]. Outfalls skip area/C.
- SS_PIPE picks a START and END structure (entity-pick), snapping its endpoints
to their centers, prompts diameter/n, and stores connectivity (the two
structure handles) in STORMSEWER_PIPE XDATA [diameter, n, from, to].
- SS_ANALYZE / SS_REPORT / SS_PROFILE rebuild the network from the drawn
entities (data.rs::network_from_entities) and analyze it directly; SS_ANALYZE
overlays flow/HGL labels without redrawing the geometry.
Because the data lives on the entities as XDATA, the network round-trips to
DWG/DXF. One additive line in src/app/update.rs adds SS_PIPE to the
inject_picked_entity allow-list so the command can read the picked structure's
center for snapping.
15 module tests cover the XDATA round-trip, analyze-from-drawing, and the
command step machines.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a "Storm Sewer" ribbon tab backed by a new in-repo `stormsewer` engine
crate (vendored under crates/). The engine implements standard public-domain
methods — Rational-method peak-flow accumulation, Manning circular-pipe
hydraulics, time-of-concentration / IDF intensity, and an HGL backwater pass
with junction losses — with 24 unit tests and no external dependencies.
Module (src/modules/storm_sewer/):
- Drafting: SS_INLET / SS_JUNCTION / SS_OUTFALL place structure markers and
SS_PIPE chains pipe runs (interactive CadCommands).
- Analysis: SS_ANALYZE / SS_REPORT / SS_PROFILE open a .ssn network file, run
the engine, draw the plan and HGL long-section, and print a report.
- SVG icons; SS_* commands registered for command-line autocomplete.
Integration is additive: build.rs auto-discovers the module and one dispatch
block is added to src/app/commands.rs. 7 module integration tests.
See src/modules/storm_sewer/INTEGRATION.md for the design and the optional
follow-up (drive analysis from canvas geometry via XDATA).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the Phase 2.2 per-entity memo to the common modify commands, which
still did a full bump_geometry (whole-model re-tess + block rebuild):
- transform_entities (MOVE / ROTATE / SCALE / MIRROR) marks each
transformed handle dirty, then bump_geometry_no_blocks;
- erase_entities drops the deleted handles from the memo and
bump_geometry_no_blocks;
- copy_entities' new handles are natural memo misses, so just
bump_geometry_no_blocks.
All three are top-level operations (including on inserts) that can't change
a block definition, so keeping the block cache is sound. Each now
re-tessellates only the touched entities and reuses the memoized wires for
the rest, matching the grip / line-commit path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.2. wires_for_block_culled re-tessellated every visible entity on
any geometry bump, so a single-entity edit cost a full model re-tess
(~30ms baseline after the block_epoch fix).
Add a per-entity memo (tess_memo: Handle -> Arc<Vec<WireModel>>) on the
culled Model render path. It's guarded by a hash of the tessellation
parameters (tol / view / anno / world_offset / bg / entered viewport); a
mismatch (zoom, layout switch) clears it. The render path now classifies
the visible set against the memo, materializes hits and tessellates only
the misses in parallel, then stores the misses back.
- bump_geometry clears the memo (structural change — safe default);
- incremental edits drop just the changed handle via mark_entity_dirty
and bump with bump_geometry_no_blocks: line commit (new handle is a
natural miss) and grip commit / Esc-cancel (mark the dragged handle).
The hit-test (view_aabb == None), paper and per-viewport paths bypass the
memo so their differing cull parameters don't thrash it. A single-entity
edit now skips the per-entity geometry math for the entire unchanged set;
the Vec assembly + batched-buffer upload stay O(N).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The block-definition tessellation cache (BlockCache::build — every block
defn) was keyed on geometry_epoch, so every edit rebuilt all block defns.
On a block-heavy drawing that turned the ~30ms visible-wire re-tess into a
~400ms spike at line-commit, grip first-move and grip release.
Key the block cache on a separate block_epoch. bump_geometry bumps both
geometry_epoch and block_epoch (safe default — xref, block create,
explode, REFCLOSE, undo, open all keep rebuilding blocks). Operations that
provably can't change a block definition now call bump_geometry_no_blocks,
which bumps only geometry_epoch so the visible wires re-tessellate against
the reused block cache:
- add_entity for a non-Insert/Block entity (line, arc, text, hatch, …) —
scene.add_entity only ever routes to model/paper space, never block
content;
- grip drag hide / commit / Esc-cancel — moving a top-level entity or an
insert never edits a block defn.
Line-commit and grip start/end drop from ~400ms to the ~30ms baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Command-preview / interim / grip-drag wires were appended into the main
wire buffer (all_wires), so every drag frame re-packed and re-uploaded the
entire batched base set — O(N) per move even though only the small overlay
changed.
Split them out: build_primitive keeps `wires` as the cached base only and
passes the overlay as a separate `preview_wires` Arc. The base keeps the
stable tile content id (no per-frame nonce), so its buffer stays resident
and is not re-uploaded during a drag. The overlay uploads to its own small
gpu_preview_wires buffer each frame and draws on top in the wire pass
(same pipeline, same order/depth). The dragged entity still renders
highlighted via its selected-path tessellation.
With the no-re-tess changes, a grip / command drag now costs a single
small overlay upload per move and nothing else.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The grip drag mutates the document live (so grips and the properties panel
track the cursor), which meant Esc left the entity at its dragged position.
Back up the edited entity when the drag starts (the same first-move point
that hides it for the overlay preview) and, on Esc, restore that backup
before un-hiding and re-tessellating — reverting the entity to where it was
when the drag began. A normal release drops the backup and keeps the
dragged geometry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.2 (partial). Dragging an entity's grip called scene.apply_grip on
every mouse move, which bumped geometry and re-tessellated the whole model
per move (plus an O(N) clone of every wire to exclude the edited one from
snapping). On a large drawing that's the 30ms->400ms spike, per frame, for
the duration of the drag.
apply_grip no longer bumps. The drag flow now:
- first move: hide the edited entity from the base tessellation (one
re-tess) and record it in grip_preview_handle;
- each move: apply the grip to the doc (no bump) and show the entity as a
one-entity overlay preview (set_preview_wires) — the base is a cache hit;
- commit on release / Esc: un-hide, drop the preview, one final re-tess.
Because the edited entity is hidden, it's already absent from
hit_test_wires, so snapping runs against the cached set directly — no
per-move clone and no self-snap. Two re-tessellations per drag instead of
one per move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.2 (partial). set_preview_wires / clear_preview_wire /
set_interim_wire called bump_geometry, so every rubber-band frame of a
drawing command invalidated the wire cache and re-tessellated the whole
model — the same 30ms->400ms spike as selection, but while drawing.
Preview / interim wires are an overlay appended to the cached base wire
set in build_primitive; they are not part of the tessellation cache, so
they no longer bump geometry. The base model-tile tessellation stays a
cache hit (static camera, no geometry bump), and the overlay still forces
a GPU wire re-upload through the has_overlay content-id path. Real commits
(commit_entity / transform / copy) bump geometry on their own, so finished
edits and preview-clear still render correctly. Snap/hit-test via
entity_wires_arc also stops re-tessellating per preview frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that selection/hover are decoupled from tessellation, highlighting the
entity under the cursor only refreshes the GPU xray overlay — no
re-tessellation — so a general rollover is cheap. On an idle ViewportMove
(no active command, no drag) hit-test at the cursor and set_hover_highlight
the result; clear it during commands/drags and on ViewportExit so it
doesn't stick over the ribbon. hit_test_wires is the cached entity-wire
Arc, so the per-move cost is just the existing click_hit projection — the
same the command-hover path already pays.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picking an entity called bump_geometry, which invalidated the wire cache
and re-tessellated the entire model just to repaint one entity — 30ms to
400ms on a large drawing (visible as a tess-ms spike on the PERF HUD).
Selection highlight is no longer baked into tessellation. wires_for_block_
culled now feeds tessellate_entity an empty selection set, so wires are
always base-coloured and the wire cache is selection-independent. The
highlight is applied in the GPU xray overlay: prepare filters the cached
wires by the live `selected ∪ hover` handle set, recolours them to
WireModel::SELECTED, and batches them — rebuilt only when a new
selection_generation (or the underlying wire content) changes.
Pure select / deselect / hover paths now call bump_selection instead of
bump_geometry, so a pick refreshes just the overlay — no re-tessellation,
no main wire re-upload (geometry_epoch and wire_content_id are unchanged).
Geometry edits, undo and file-open still bump_geometry and rebuild
everything. Grips/properties refresh through their existing app paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to 3.3. The selection xray pass still built one buffer + one
draw call per selected wire, so select-all over a large drawing issued a
draw per entity. That pass applies neither scissor nor mesh-edge skip, so
all selected wires merge into one order-preserving run via from_run.
Removes the now-dead WireGpu::new / build per-wire constructors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.3. upload_wires built one GPU instance buffer + one draw call per
WireModel — tens of thousands on a large drawing, a major per-frame CPU /
driver cost. Merge maximal runs of consecutive wires that share scissor +
mesh-edge state into one concatenated instance buffer each (WireGpu::
from_run); the existing draw loop then issues a single draw per run. A 2D
model (uniform None scissor, no mesh edges) collapses to one buffer + one
draw.
Runs are consecutive, never globally regrouped, so the sorted wire order
is preserved exactly — depth-biased overlap and alpha blending both
resolve in submission order, so the image is unchanged. from_run
guarantees instance order (index-ordered collect + sequential flatten).
The WireInstance layout, shader, scissor projection and draw loop are
untouched; only buffer packing changed. Selection overlay stays per-wire.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_primitive ran split_face3d_wires every frame — for each wire a
string-parse + document handle lookup + clone, to separate Face3D wires
from the rest — even when a pan had just reused the tessellation. The
split is content-only, so memoize it by the Model-tile wire content id:
a pan-reuse (same id) reuses the split, and the common no-overlay frame
returns the cached `other` Arc with zero clone. Preview/interim frames
clone once to append. Non-tile paths split inline as before.
Completes the cheap-pan path: a pure pan now reuses the CPU
tessellation, the Face3D split, and the GPU wire upload — only the
view_proj uniform and per-frame scissors recompute.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.2 (wire pan path). Wire vertex buffers are world-space, so a
camera move changes only the view_proj uniform (uploaded every frame),
never the wire data — yet upload_wires re-ran on every camera_generation
tick, re-packing vertices and re-writing the GPU buffer on each pan.
Stamp every Model-tile re-tessellation with a monotonic content id
(WIRE_CONTENT_GEN), reused when a pan reuses the tessellation. The
pipeline records the resident buffer's id and skips upload_wires when the
incoming id matches. The gate is pulled out of the (epoch, camera_gen)
block so a preview/interim wire change still uploads even with a static
camera; non-tile paths and overlay frames force a fresh id, preserving
current behaviour. Monotonic id sidesteps the ABA hazard a freed/realloc'd
Arc pointer would carry.
Pairs with the pan tessellation reuse: a pan now skips both the CPU
re-tessellation and the GPU wire upload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.1 (partial — pan). The Model-tile wire cache keyed on the exact
camera hash, so every pan re-tessellated the whole tile even though pan
changes neither the curve tol nor the culled set (beyond a shift). Re-key
on (geometry_epoch, pan_invariant_hash, tessellated_region):
- pan_invariant_hash covers rotation + wpp (tol) but excludes the pan
target, so a pure pan keeps it stable;
- the wires were culled to a 1.25x-margin region, recorded with the entry;
- a hit now also requires the tile's current visible rect to still fit
inside that region, guaranteeing every on-screen wire is present.
Result: panning within the existing margin reuses the tessellation (tess
ms -> ~0 on the PERF HUD) with no missing geometry. Zoom, orbit and edits
change the signature/epoch and rebuild as before; the cull margin is
unchanged so miss cost — and zoom behaviour — is identical. GPU still
re-uploads per camera_generation (diff upload is Phase 3.2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The purge scan tests every entity with is_entity_corrupt, whose
per-vertex finite/extent checks on large polylines dominate. Detection
is pure and read-only, so gather entity refs in one pass and test across
cores with rayon; removal stays serial (remove_entity needs &mut doc).
Cuts the "purge" span in the open-time breakdown on polyline-heavy files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends Phase 2.1. compute_block_aabbs resolved each defn's union AABB
serially via defn_aabb_recursive, which only reads the finished defns map
and re-walks shared nested defns with no memoization — real cost on
block-heavy drawings. Split into a parallel read phase (per-name resolves
fan out over rayon) and a serial write-back phase. BlockCache is Sync, so
sharing &self across the read phase is sound.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5.3 (CPU tessellation slice). Add a `PERF` command that toggles an
overlay on the active viewport showing the cost of the most recent wire
re-tessellation: ms, wire count, geometry epoch. Anchored top-left via
position_canvas_overlay.
Timing is captured at the wire-cache miss paths (model_tile_wires_arc,
paper_sheet_wires_arc) and stored in Cells on Scene, read directly by the
UI view (Scene is single-threaded / Rc-based, so build_primitive and view
share a thread). Reads ~0 ms on a warm cache, so it isolates the exact
work a miss costs — the slice 2.2 / 3.1 / 3.3 each move, making them
PR-to-PR comparable.
GPU upload/draw/GPU-wait spans (need wgpu timestamp queries) remain open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.1 (partial). set_hover_highlight bumped the geometry epoch on
every hover-target change, forcing a full model re-tessellation. The
hover handle is folded into the tessellation highlight set as
selected ∪ {hover}, so hovering an already-selected entity contributes
nothing — the effective set and every resulting WireModel are identical.
Guard the bump on the effective highlight contribution actually changing;
hovering over or between selected entities is now free. The field is
still updated so hit-test / UI state stays current.
The full camera/selection-from-tessellation cache split (3.1 / 2.2) is
left open: highlight colour is baked into WireModel.color across several
tessellation sites, so decoupling it needs running-app verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.1. build_defn stores nested INSERTs as by-name references and
never expands them at build time, so each block defn depends only on the
read-only doc — the builds are independent, not topologically ordered as
the roadmap assumed. Replace the serial loop with a rayon
par_iter().collect(). render_style_for / tessellate already run under
rayon in build_derived_caches, so thread-safety is established.
compute_block_aabbs stays serial (resolves nested refs, cheap post-pass).
Cuts first-frame block tessellation on insert-heavy drawings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5.2. Time each open phase — parse, purge, derived-cache build on
the loader thread, plus xref resolve on the UI thread — and emit a
breakdown line on open completion:
parse 1240ms · purge 80ms · caches 340ms · xref 60ms · total 1820ms
Makes open-time regressions (and the wins from 1.1 / 2.4) visible at a
glance. Timings ride back on DerivedCaches; total is wall time from the
Open click to post-xref.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 4.1. Handle / u64 keys dominate the hot maps (block_defn,
hatch / image / mesh caches, draw-depth map, viewport wire caches);
the default SipHash is wasted work on integer keys. Alias the std
types to rustc_hash::FxHashMap / FxHashSet crate-wide so every map and
set — including the cross-module draw-depth map shared between scene
and the GPU pipelines — uses the faster hasher consistently.
Mechanical: no behavioural change. Constructors moved from ::new() to
::default() (Fx variants carry no RandomState). Build + 13 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1.1: resolve_xrefs now purges corrupt entities inline as it
merges each xref and returns the dropped count, removing the second
full-document purge_corrupt_entities walk in the FileOpened handler.
Every xref-bearing open saves one O(N) entities() pass.
Phase 2.4: split compute_world_offset into offset_prep / offset_centroid
/ world_offset_from_centers; centroid accumulation now rides the existing
cache-handle walk in build_derived_caches. Two O(N) entity walks collapse
into one, with identical MSPACE-membership filtering preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Row exit now clears the preview only when the highlight still points at
that row. Moving between rows can fire on_enter before the previous
row's on_exit, so the unconditional clear wiped a freshly highlighted
row (notably when returning to the first row). Guarding the clear fixes
the highlight not reappearing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a scene hover_highlight handle that renders an entity with the
selection colour without joining the real selection. The cycling list
box sets it on row enter / clears on exit, so hovering a row glows the
matching object in the drawing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the repeat-click cycling with a list box: when a click lands on
two or more overlapping objects (cycling on), a small list of the
candidates opens at the cursor. Picking a row adds that object to the
current selection (accumulate); clicking outside dismisses it. A single
object under the cursor falls through to the normal click.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a click_hits_all hit-test and an SC pill that, when on, makes
repeated clicks at the same spot step through the overlapping objects
there (nearest first) instead of accumulating the selection. The whole
cycling path is gated behind the toggle, so default picking is unchanged
when it is off.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a per-type selection filter to the scene: the FILTER pill opens a
checklist of the entity types in the layout, and unchecking a type
excludes it from interactive picking (click, box and lasso selection all
honour it). The pill lights up while any type is filtered out.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the QP pill is on, a compact floating panel appears at the canvas
top-left on selection, reusing the docked panel's editable section rows.
Toggleable from the status-bar customization menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a transparency_enable uniform read by the wire shader: when the new
TPY pill is off, every line is forced opaque; when on, baked entity
transparency shows through. Toggling is a uniform write, no retessellate
(same mechanism as the lineweight-display toggle). Pill is toggleable
from the status-bar customization menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking the ISO pill now opens a small menu with Isolate Objects, Hide
Objects and End Isolation instead of ending isolation directly; rows
that don't apply (no selection / nothing hidden) are dimmed. The same
actions are added to the viewport right-click menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a hidden-entity set to the scene that the tessellation visibility
test skips, so hidden objects neither render nor hit-test. New commands
ISOLATEOBJECTS (show only the selection), HIDEOBJECTS (hide the
selection) and UNISOLATEOBJECTS (restore). An ISO status pill lights up
while anything is hidden and restores everything on click.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Units pill that shows the drawing's current INSUNITS value and
opens a picker to change it (Unitless, mm, cm, m, km, in, ft, mi, yd).
Toggleable from the status-bar customization menu like the other pills.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a customization menu (far-right handle) that lists every status-bar
pill with a check mark; toggling a row shows or hides that pill, and the
choice persists across sessions in the config dir.
New pills:
- Coordinates: live cursor readout (re-adds the model-space world offset
so it shows true drawing coordinates), updated on every move rather
than only while a command is active.
- Clean Screen: hide the ribbon and side panels for a full canvas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read the model-space annotation scale from the standard CANNOSCALE /
CANNOSCALEVALUE header variables instead of a user-variable hack, and
populate both the status-bar scale picker and the viewport Scale
property purely from the drawing's ACAD_SCALELIST — no built-in
fallback set. Xref-derived scales (`_XREF` suffix, `name|` prefix)
are filtered out so only the host drawing's own scales appear.
Bumps the patched acadrust to the commit that adds CANNOSCALE/
CANNOSCALEVALUE read/write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign the bare .exe before the MSI is built (so the packed copy carries
the signature) and the linked .msi afterwards, via
azure/artifact-signing-action with SHA256 digests and Microsoft's
RFC3161 timestamp server. Credentials come from the AZURE_* repository
secrets.
Also fix the MSI version fallback: workflow_dispatch runs use the
branch name as ref_name, which is not a valid numeric ProductVersion,
so candle failed on any non-tag run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- release.yml: ad-hoc codesign the .app before packaging so a quarantined
download avoids the "app is damaged" Gatekeeper verdict; emit the cask
version + sha256 to the job summary each release for easy tap bumps.
- packaging/homebrew: Homebrew cask + tap/usage notes; install via
--no-quarantine (the actual Gatekeeper bypass for an unsigned app).
- README: split macOS install into Homebrew and manual .dmg paths with
the xattr / Settings "Open Anyway" fallbacks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three polyline issues in the FILLET/CHAMFER commands:
- The polyline preview wire ignored bulge, so a filleted corner previewed
as a straight line instead of the arc. lwpoly_pts now tessellates bulge
segments via BulgeArc (same as the entity render).
- Hovering a polyline edge didn't highlight it: the first-pick hover only
handled Line/Arc. Both commands now highlight the nearest polyline
segment on hover.
- CHAMFER didn't work on polylines at all (Line-only). Added a SecondPoly
step + chamfer_lwpoly_corner: pick two adjacent segments of one polyline
to cut the shared corner back by the two distances with a straight edge.
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>