Commands that construct geometry with implicit axis directions ignored
the UCS, so a rectangle drawn under a rotated UCS came out world-aligned.
Give commands the UCS and build square to it:
- CadCommand::set_ucs(Mat4) (UCS→wire affine, default no-op); the host
pushes the current UCS via App::push_ucs_to_cmd before each point /
preview / text dispatch (DocumentTab::ucs_wire_affine, identity outside
model space). New axis-aligned commands just override set_ucs.
- RECT (two-corner) and RECT_CEN (center) place their derived corners on
the UCS axes (ucs_box_corners / ucs_box_around_center).
- ARRAYRECT row/column offsets run along the UCS axes.
RECT_ROT, POLYGON, and ELLIPSE already take their orientation from picked
points, so they need no change.
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]>
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]>
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]>
Remove accidental dead code (unused imports, the superseded paste tool()
helper, a never-read TORIENT handles field, a duplicate let binding, an
unneeded mut) and mark intentional-but-unwired scaffolding with
allow(dead_code): the web-only save_to_bytes, the plugin loadable() probe,
the planned PICK_HIGHLIGHT colours, the DynFieldSpec label, and the layout
ribbon helpers. Native build is now warning-clean apart from the upstream
nom/quick-xml future-incompat notices.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The polyline was only committed to the document on finish, so its partial
geometry produced no snap points — you could not snap to vertices already
placed. Publish it as a real LwPolyline at the second vertex and replace
its geometry in place as later vertices land, so it lives in the document
(and the snapper) throughout the draw.
New CmdResult::CommitLiveEntity / UpdateLiveEntity{finish} plus a
set_live_handle() hook let a command own an entity mid-run. The create
pushes a single undo snapshot; in-place updates push none, so the whole
object reverts as one unit. Updates preserve the entity's handle and
layer (a fresh built entity carries a NULL handle that would desync it
from the document map key and drop it from rendering). (#119)
Co-Authored-By: Claude Opus 4.8 <[email protected]>
- Adds TEXTEDIT command to seamlessly edit annotation objects in-place.
- Matches industry-standard CAD tools' prompt behavior and "Current settings" banner logic.
- Implements the TEXTEDITMODE system variable (0/1) for Multiple vs Single editing modes.
- Enhances command lifecycle with CmdResult::SuspendForTextEdit and post_editor_closed hooks, allowing commands to cleanly pause and resume after external UI editors close.
Turn the ribbon Clipboard "Paste" button into a dropdown:
- Paste — existing pick-a-point paste.
- Paste to Original Coordinates — drops the clipboard at its source
coordinates with no prompt.
- Paste as Block — wraps the clipboard contents in a new block definition
and starts an interactive insert (prompts for the drop point, with the
geometry rubber-banding under the cursor).
Also fixes two bugs the new commands exposed:
- entities_centroid summed the NaN separator points wire models carry,
so the paste base point came out NaN; non-finite points are now
skipped. A NaN base previously froze block tessellation outright.
- PASTEORIG added clipboard entities with their original handles
(duplicates on same-document paste); it now goes through
add_entity_clone and merges referenced records like the other pastes.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
OpenCADStudio no longer bundles plugins — every add-on is an external
cdylib loaded from the plugins folder or installed via the marketplace.
- Remove the demo plugin and the in-tree ocs_example_plugin crate.
- Remove the inventory-based built-in registration path (PluginRegistration,
all_plugins, installed_manifests); registry now serves core ribbon tabs
plus loaded external plugins, and try_dispatch routes only to externals.
- Plugin Manager drops the 'compiled into this build' section; the
enable/disable toggle moves onto loaded external cards.
- Prune the now-dead HostSession/DocumentTab helpers and re-exports.
Part of the #100 extensibility epic (phase 2).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Plugins targeted the host's concrete HostSession type. Add a HostApi
trait in ocs_plugin_api behind an optional `host` feature (the only thing
that pulls acadrust, so the core crate stays dependency-free). HostSession
implements it and BuiltinPlugin::dispatch now takes `&mut dyn HostApi`, so
an out-of-tree add-on compiles against the contract crate alone. Per-tab
plugin state is reached through object-safe plugin_state* helpers.
Completes the phase-1 host-surface extraction in the #100 epic.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Points could only render as a 1px dot — no way to change PDMODE/PDSIZE.
- PDMODE / PDSIZE commands set the header value and rebuild the glyphs.
- DDPTYPE opens a Point Style dialog: a 5x4 grid of rendered glyphs
(shape x enclosure), a size field, and relative/absolute radios.
- Relative (<=0) PDSIZE now sizes from the current world-per-pixel so the
marker stays a roughly constant on-screen size across zoom, rebuilding
on zoom via the existing camera-generation cache key. Absolute (>0)
scales with the world as before. Switching to absolute with a zero size
seeds a positive value from the on-screen size so it stays representable.
Part of the #100 extensibility epic (point style surfaced in #106).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add-ons had no way to request a file picker — only the host's own Open
dialog. PluginFileDialog lets a plugin tool ask the host to open a native
picker; on selection the host dispatches "<command> <path>" back to the
plugin with original case preserved (bypassing the command-line
upper-casing that mangles case-sensitive paths on Linux/macOS). The demo
plugin gains an Import tool exercising it.
Part of the #100 extensibility epic (surfaced in #106).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
Match the `*_model` naming of the sibling render models (wire_model,
hatch_model, mesh_model, image_model). Also realign the related Scene
API for consistency: model_solids → solid_models, add_model_solid →
add_solid_model, register_model_solid → register_solid_model.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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 <[email protected]>
The travel direction of a path array now follows where the user clicks to
select the path: the array starts from the nearest end, so clicking near
either end picks the direction. A stored arc is always CCW regardless of how
it was drawn, so without this the array could run opposite to expectation.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Path arrays along arc segments came out mirrored: the LwPolyline bulge
sampler placed the arc centre on the wrong side of the chord and ignored
the polyline normal. Sample bulge arcs through the canonical BulgeArc and
map OCS->WCS, matching the renderer. Sample standalone Arc/Circle paths via
their OCS basis too. Also align each copy to the path tangent (the first
item keeps its orientation), so items follow a curve instead of staying
upright.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
STRETCH now shows a live ghost where vertices inside the crossing window
follow the cursor and the rest stay anchored, instead of only a rubber-band
line. Add WireModel::stretched and pass the selection's wires into the
command.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
SCALE now scales a live ghost by the cursor distance from the base point
right after the base is picked, instead of requiring a separate reference
pick before any preview appears. Type a factor for an exact scale, Enter
for the last factor, or R for the reference-length workflow (preserved).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
MIRROR now asks "Erase source objects? [Yes/No] <No>" after the second
mirror-line point. No (default) keeps the original and adds a mirrored
copy; Yes flips the original in place. Make the BatchCopy command-line
message label-aware and singular/plural so MIRROR reports "1 copy".
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Make angle display, sign and the polar guide reference part of the
common infrastructure instead of per-command logic:
- DynSpec.ref_point now drives the polar guide's reference direction, so
the arc and value box can sweep from a command-defined reference (not
just +X). LINE etc. keep +X by leaving it None.
- `dyn_display_angle_deg` is the single rule for how an angle reads: the
unsigned magnitude of the short angle, so clockwise (cursor on the CW
side of the reference) shows positive. Used by the generic value box,
ROTATE and ARC.
- A typed angle committed via on_text_input is re-signed by the cursor's
side relative to the reference (`dyn_sign_angle_text`), so commands
receive an already-signed value and don't each implement it.
ROTATE: drop its own sweep arc (the polar guide draws it now, anchored at
the centre and swept from the reference); the rotation commits in the
dragged direction whether picked, typed or accepted with Enter.
ARC SCA/CSA: reference the start angle so the arc, sign and readout are
all relative to it.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
- ARC: per-step DynSpec for the typed-value modes — included-angle (SCA,
CSA), chord length (SCL, CSL) and radius (SER) show their box and route
the typed value through on_text_input via a new dyn_commit_as_text()
hook; the angle box previews the live span. Point/3P/SCE steps stay on
the legacy polar path.
- ARC + ellipse-arc: derive the previewed sweep direction from the swept
angle with a ~10° tolerance and advance the reference only on a clear
move, so small cursor jitter no longer flips the arc. Ellipse uses the
parametric angle so a flat ellipse isn't sticky.
- Ellipse arc: the start-angle step now shows a line from the centre to
the cursor (it only picks the start angle) instead of a misleading arc.
- Ellipse arc: use the same minor-axis direction (Z x major) as the
rendered entity so the committed arc matches the preview instead of
mirroring across the major axis.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Extend the DynSpec coverage and add the guides those commands need:
- Rectangle: two-corner enters width/height as unsigned magnitudes (the
sign follows the cursor side, like the angle entry), drawn with the
rectangle as the guide; rotated rectangle's height is a single distance
measured square to the base edge (dimension line offset off the edge
with extension lines); centre rectangle shows half-width/half-height.
- Polygon: inscribed/circumscribed vertex step shows radius + rotation.
- Ellipse: minor-axis step (all three modes) measures the half-length
perpendicular to the major axis and draws that semi-axis from the
centre; angle steps anchor their arc at the centre.
Adds DynGuide::Perp / PerpDim (perpendicular measure to a reference line)
and DynSpec::ref_point, plus Width/Height role handling (unsigned display,
cursor-signed commit) and diameter value scaling.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Introduce a per-command, per-step dynamic-input description so each step
shows the value boxes and guide geometry that make sense for it, instead
of a one-size-fits-all distance/angle pair.
- command: add DynSpec{anchor, fields(role+label), guide}, DynRole
(X/Y/Z/Distance/Angle/Radius/Diameter/Width/Height/Factor/Count with
label + value scaling), DynGuide(None/Polar/AxisDelta/Radius/RectSides)
and DynAnchor. New dyn_spec() trait method defaults to None so commands
on the legacy dyn_field() path are unchanged.
- overlay: redraw the dynamic-input layer by guide — polar reference line
+ angle arc, single radius line, dotted axis legs, or a cursor row —
and place each box by its role. Angle reads as the unsigned magnitude
of the short arc; a typed polar angle takes the sign of the cursor side.
- app: route fields/guide/anchor through the tab; resolve points from the
step anchor and de-scale typed values by role (typed diameter -> radius).
- circle: CR shows a radius value on one line to the cursor; CD shows the
diameter (twice the cursor radius). No angle arc on radius steps.
- pline/mline: keep polar dynamic input on vertex steps that also accept
keyword letters via point_step_accepts_keywords().
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Move the dependency-free, semver-versioned half of the plugin contract into a
standalone workspace crate `crates/ocs_plugin_api`:
- manifest types: PluginManifest, ApiVersion, API_VERSION
- ribbon vocabulary: CadModule trait + ToolDef/RibbonGroup/RibbonItem/IconKind/
ModuleEvent/StyleKey
The host re-exports them from `crate::plugin::manifest` and `crate::modules`, so
every existing call site is unchanged. The acadrust-typed runtime surface
(HostSession) stays in the host binary; lifting it behind a HostApi trait in the
same crate is the remaining phase-1b step.
Part of #100. Docs updated in docs/plugin-architecture.md.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The last corner of a closed polyline joins its first (0) and last (n-1)
segments at vertex 0, but the corner fillet/chamfer used corner_idx
high%n (= n-1) and treated seg low as the "before" segment, so the
shared vertex and the before/after order were both wrong — producing a
fillet/chamfer at the wrong place. Detect the wrap pair and map
before_seg=n-1, after_seg=0, corner_idx=0. Applies to FILLET and
CHAMFER.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
TrimCommand::on_entity_replaced never matched LwPolyline, so a trimmed
(closed or open) polyline kept a NULL handle in the command's entity
snapshot. A second pick on the same polyline then found nothing, so it
could not be trimmed more than once without restarting the command.
ExtendCommand already handled this case.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Starting FILLET/CHAMFER from the ribbon left the command-line field
unfocused, so its Enter arrived as CommandFinalize and discarded a
typed option keyword ("R"/"D"), cancelling the command. Several
related dynamic-input issues surfaced from the same flow:
- CommandFinalize now forwards a non-empty command-line buffer through
CommandSubmit, so a typed keyword is handled the same whether the
command was started from the ribbon or the command line.
- sync_dyn_fields shows no coordinate box during an object-pick step
(needs_entity_pick / needs_structure_point_pick) — there is no point
to enter there, so typed keywords reach the command line cleanly.
- apply_cmd_result rebuilds the dynamic-input fields on NeedPoint, so a
step change (object pick -> radius entry) shows the matching box
immediately and typed digits land in it instead of the command line.
- Object-pick steps now draw a prompt-only hint pill at the cursor
(no input box).
- FILLET/CHAMFER remember the first pick across a mid-selection radius
or distance entry, resuming the second pick instead of restarting
selection.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Switching to a paper layout used to hijack the ribbon with a contextual
"Layout" tab. Replace that with a reusable right-edge vertical toolbar:
a single centred column of icon buttons floated over the canvas, built
from a flat ToolDef list and dispatching through the existing
RibbonToolClick path (ui::side_toolbar). Paper space now shows Viewport /
Page Setup / Export PDF there and leaves the ribbon on the user's tab;
the contextual layout tab and its three auto-switch hooks are removed.
The toolbar is generic infrastructure for future context action sets.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
build_geos only turned Line/Arc/Circle/Ray/XLine/Ellipse/Spline into
cutting-edge geometry, so a polyline contributed no boundary and a line
could not be trimmed (or extended) against it — even though trimming the
polyline itself against lines already worked. Explode each polyline
(LwPolyline / Polyline / Polyline2D / Polyline3D) into its Line + Arc
segments and add them as boundaries, tagged with the polyline's handle so
the click-target self-exclusion still holds. Factored the per-entity Geo
build into geo_from_entity and exposed explode_polyline_segments.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
TRIM had no LwPolyline arm in its pick dispatch, so clicking a polyline
segment did nothing (only EXTEND handled polylines). Add trim_lwpolyline:
parameterise the polyline by segment+u, find boundary crossings per
segment, and remove the span containing the click between the adjacent
cuts. A closed polyline needs >=2 cuts and yields the surviving open
arc; an open one yields the surviving piece(s). Fully-surviving segment
bulges are kept; the partial ends become straight. Hover now previews
the trim too.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Removes storm_sewer module and stormsewer crate so this branch is suitable for upstream PR. Plugin host, docs, and CadCommand acquisition hooks remain.
Text-input steps (OFFSET distance, POLYGON sides, radii, counts) were
swallowed by the dynamic-input overlay: typed digits landed in the field
buffer and Enter committed a point pick, so the value never reached
on_text_input (issue #61). Now each text step shows one scalar box whose
value routes to on_text_input on commit; non-numeric prompts (names,
keywords) keep going to the command line so mixed text like "Bolt12"
isn't split.
OFFSET reworked: object first, then through-mode by default — the offset
follows the cursor at the perpendicular distance to the object. Typing a
number locks the magnitude (cursor then only picks the side); a value
typed and clicked without Enter is applied before the point pick.
The dynamic-input overlay and the command line now show the active
step's prompt and hold it until the step completes.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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) <[email protected]>
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) <[email protected]>
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) <[email protected]>
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) <[email protected]>
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 <[email protected]>
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 <[email protected]>
The trim/extend preview wire for an LwPolyline placed each vertex at
[x, elevation, y] (the old Y-up swap), so the highlighted boundary was
drawn in the wrong plane. Use [x, y, elevation] to match the entity
render and the rest of the now-XY commands.
(The remaining axis swaps live in the 3D revolve / truck B-rep pipeline,
which is a separate, internally-consistent Y-up subsystem.)
Co-Authored-By: Claude Opus 4.8 <[email protected]>