Commit graph cad-editor/src/app/commands.rs
Author SHA1 Message Date
Hakan Seven
96ec672d5b feat(points): point style commands + DDPTYPE dialog, viewport-aware size
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 <noreply@anthropic.com>
2026-06-17 11:13:12 +03:00
Hakan Seven
17f7924e77 feat(ui): OCS Web button, prefilled feedback, drop Start About button
- Add an "OCS Web" button (bright ribbon-blue, desktop only) to the Start
  page that opens the web build at hakanseven12.github.io/OpenCADStudio.
- Send Feedback now opens the GitHub issue with the body pre-filled:
  app version + platform (OS/arch on desktop, browser user-agent on web),
  via sys::platform_info + sys::percent_encode.
- Remove the redundant About button from the Start page (ABOUT command /
  menu still opens it).

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 01:34:13 +03:00
Hakan Seven
93d136c5db fix: Donate button (Start tab gate + web window.open)
- Add DONATE to the Start-tab command allowlist; it was blocked there
  with "No drawing open" while REPORT/CHANGELOG/ABOUT were allowed, so
  the Donate button did nothing on the welcome screen.
- Implement sys::open_url on wasm via web_sys window.open(_blank) — it
  was a no-op stub, so Donate, Release Notes and Send Feedback opened
  nothing on the web. The click is a user gesture, so the pop-up blocker
  allows it.

Native + web build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:27:33 +03:00
Hakan Seven
1ab892c635
Merge pull request #105 from KarimJerbi/rectang-suppress-ortho
Temporarily suppress Ortho mode during RECTANG commands
2026-06-16 18:17:18 +03:00
Hakan Seven
b2e20c95fe feat(wasm): compile the library for wasm32 (issue #45 groundwork)
First step toward a web build: the library now compiles for
wasm32-unknown-unknown with --no-default-features.

- Add a `solid3d` feature (default on) gating truck-meshalgo /
  truck-shapeops / lzma-sys. These pull vtkio → xz2 → lzma-sys (C),
  which cannot cross-compile to wasm. Without the feature the 3-D
  solid path (tessellation, booleans, ACIS import) becomes no-ops;
  2-D CAD is unaffected. Native builds keep the feature by default.
- Move native-only crates (ureq, open) to non-wasm target deps and
  add cfg shims: update-check is a no-op on wasm, and a new `sys`
  module provides open_url + handle_path (rfd FileHandle → PathBuf;
  synthesizes a name-based path on the web).

Native build + all 75 tests unchanged. Browser runtime wiring
(entry point, trunk, real file I/O) is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:01:19 +03:00
Karim Jerbi
a431e02378 Temporarily suppress Ortho mode during RECTANG commands 2026-06-16 09:53:32 +01:00
Hakan Seven
3666c7fa69 refactor(modules): rename home module to draw, restore first-tab order
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>
2026-06-16 11:49:14 +03:00
Hakan Seven
e2be8197b4 feat(stretch): live ghost preview while dragging the new point
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 <noreply@anthropic.com>
2026-06-15 23:43:07 +03:00
Hakan Seven
ec865a95aa feat(plugin): add a Plugin Manager window listing installed add-ons
Read-only inventory of the add-ons compiled into the build — name, version,
id, API level, description, command prefixes — built from the extracted
`ocs_plugin_api` manifest. Opened with the `PLUGINS` / `PLUGINMANAGER` command
or the Start-page "Plugins" button.

Adds `installed_manifests()` to the plugin registry and wires the window the
same way as the existing About / Shortcuts windows.

Part of #100 (phase-1 plugin manager UI stub).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 01:46:09 +03:00
Hakan Seven
3522637f65 fix(start): enable Send Feedback / Release Notes on the start tab
The start (welcome) tab blocks any command that isn't NEW/OPEN/EXIT/QUIT,
so the Contribute and Release Notes buttons did nothing but print
"No drawing open". REPORT and CHANGELOG (and ABOUT) only open a URL or a
dialog and need no drawing, so allow them through.

Also rename the "Contribute" button to "Send Feedback" (#44) and update
the REPORT status line to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 01:12:29 +03:00
Hakan Seven
e141fb6df3 fix(command): block drawing commands on the Start tab (#96)
The Start (welcome) tab has no drawing, so commands like LINE dispatched
from the command line or ribbon ran as silent no-ops. Allow only the
commands that make sense there (NEW / OPEN / EXIT / QUIT; OPEN_RECENT is
handled earlier) and inform the user otherwise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:42:02 +03:00
Hakan Seven
8d6e21ef5f fix(layers): sync header CLAYER when making a layer current (#93)
Making a layer current via the layer manager (LayerSetCurrent) or
LAYMCUR updated only the per-tab active_layer, not the document
header's current_layer_name/handle. The no-selection ribbon refresh
reads the header layer, so after the change an Esc (deselect ->
refresh_properties) snapped the active-layer dropdown back to the
stale header value ("0") even though new entities still used the
correct active layer. Mirror the change into the header in both paths,
matching the ribbon dropdown's own set-current path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 00:16:06 +03:00
Hakan Seven
6a70e99dd5
Merge pull request #80 from mf4633/feature/plugin-host
Add plugin host (Phase 1)
2026-06-10 00:37:37 +03:00
Michael Flynn
e46701456f Add plugin host architecture and Storm Sewer add-on package
Generic plugin runtime (src/plugin/, HostSession, per-tab plugin_state, command router). QGIS-style add-on layout with plugin.toml and single PluginRegistration. Storm Sewer refactored as reference consumer with dispatch.rs, catchments, LandXML, headless tests (43 passing). Core update.rs decoupled from storm_sewer via CadCommand object-pick hooks. Docs: plugin-architecture.md, plugin-template, PR and issue-78 reply drafts.
2026-06-09 13:41:21 -04:00
Hakan Seven
6520195220 fix(layers): allocate real handles for new layers (#67)
New layers were added to the table with a NULL handle, and the first
patch used the non-advancing `next_handle()` getter, so two new layers
(or a layer and the next allocated object) shared one handle. The DWG
format is handle-based, so the collision dropped the layer on save;
DXF is name-based and survived, which is why a rename appeared to fix
it.

Allocate a unique advancing handle via `allocate_handle()` in LAYER NEW
(panel + command line) and on rename when the source handle is invalid.
Rename now also clones the whole record so colour/linetype/lineweight
are kept, not just the flags.

Adds DWG/DXF round-trip tests covering one and multiple new layers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:37:42 +03:00
Hakan Seven
4b878b88c4 feat(input): scalar entry via dynamic input
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 <noreply@anthropic.com>
2026-06-09 18:32:03 +03:00
Michael Flynn
1199895f0e Storm Sewer: make placement click-driven so it always works
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>
2026-06-09 09:06:18 -04:00
Michael Flynn
057de504b2 Storm Sewer: fix prompt flow — collect typed values before the click
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>
2026-06-09 08:42:47 -04:00
Michael Flynn
1e68219b43 Storm Sewer: interactive in-app network — snapping, tracked objects, analyze the drawing
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>
2026-06-09 07:29:14 -04:00
Michael Flynn
9b44eb6400 Add Storm Sewer module: storm-drain network analysis (Rational + Manning + HGL)
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>
2026-06-08 17:16:30 -04:00
Hakan Seven
9b112ce12c feat(scene): PERF frame-budget HUD for wire re-tessellation cost
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>
2026-06-05 01:07:17 +03:00
Hakan Seven
70702d063f perf(hash): swap std HashMap/HashSet for rustc-hash FxHash
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>
2026-06-05 00:35:53 +03:00
Hakan Seven
3098523b8d perf(io,scene): drop redundant purge walk, fuse world-offset scan
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>
2026-06-05 00:32:39 +03:00
Hakan Seven
e0e7897952 feat(statusbar): isolate / hide objects with status pill
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>
2026-06-04 22:51:23 +03:00
Hakan Seven
a584e772ba fix(textstyle): open Text Style manager from the ribbon Manage button
The bare STYLE command (sent by the Annotate-tab text-style combo's
"Manage…" button) fell into the LIST arm and only printed the style
list instead of opening the manager. Route no-arg STYLE / TEXTSTYLE to
the dialog, matching DIMSTYLE / TABLESTYLE / MLEADERSTYLE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:52:11 +03:00
Hakan Seven
e0c5db9e0c feat(mleaderstyle): add Multileader Style editor window
New dedicated style-manager window for MultiLeaderStyle objects (the
existing mlstyle window edits MLineStyle, not multileader). Opened via
the MLEADERSTYLE command / ribbon manager entry.

Editable across Leader Format / Structure / Content sections:
- path/content/text-angle/text-alignment enums (cycle buttons)
- line & text color (ACI), arrowhead & break-gap sizes
- landing distance/gap, max points, segment angles, scale factor
- default text, text height
- enable landing/dogleg, text frame, text-always-left, annotative
New/Delete/Set-Current style management.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:25:14 +03:00
Hakan Seven
7bf9ce31ae feat(model): 3D solid modelling tab — primitives + boolean ops
Rename the Home tab to "Draw" and add a Model tab beside it with two
groups:

- Model: BOX / CYLINDER / CONE / SPHERE / WEDGE / TORUS, placed with a
  footprint + height. Each is built as a real ACIS Solid3D (acadrust
  primitive builders) plus a truck B-rep, tessellated into the shaded
  mesh pipeline, with edge wires for picking + wireframe.
- Design: UNION / SUBTRACT / INTERSECT via truck-shapeops.

A session-only Scene.model_solids cache holds each solid's truck B-rep
so the boolean tools can combine and chain them. Supersedes the old
command-line BOX/SPHERE/CYLINDER (insert::solid3d_cmds keeps EXTRUDE/
REVOLVE/SWEEP/LOFT).

Known limitations: booleans need solids created this session; curved
ACIS primitives render in-session but not after reload (the SAT
tessellator handles only planar caps); geometry assumes world_offset 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:27:06 +03:00
Hakan Seven
4acb820e01 fix(ribbon): dismiss stuck style dropdown on viewport click
The annotation style combo renders its panel inline with no backdrop to
catch outside clicks, so it stayed open after clicking into the viewport
or starting another tool. Close any open ribbon dropdown on viewport
press (left/right/middle) and on command dispatch.

Fixes #50

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 14:57:50 +03:00
Hakan Seven
2627d06224 feat(text): in-place editors for all text entities
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>
2026-05-31 14:44:18 +03:00
Hakan Seven
09e7e90aae feat(draworder): cross-type draw order + context menu
DRAWORDER now affects rendering immediately (bump geometry epoch so the
sort cache rebuilds) and front/back anchor to the block's full effective
sort range instead of only existing table entries, so bring-to-front
actually lands on top. The render fallback sorts table-absent entities by
their own handle.

Draw order now applies across entity types, not just within a type. A
per-entity draw-order rank (SortEntitiesTable override or own handle,
ranked per block) is encoded as a small signed clip-z bias in the wire,
hatch, image, and 3DFACE pipelines. The shared LessEqual depth buffer then
orders fills, lines, rasters and 2D solids against each other regardless of
pass order — without touching model coordinates. 3D meshes keep real
geometric depth; wipeouts stay always-on-top.

Adds a Draw Order submenu to the viewport context menu (Bring to Front,
Send to Back, Bring Above/Send Under Object). The above/under items run an
interactive reference-object pick, then reorder the captured selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 21:29:01 +03:00
Hakan Seven
92f368f275 fix(layer): refresh viewport after LAYOFF/LAYFRZ/LAYON/LAYTHW/LAYLCK… (#42)
The eight layer-state commands (LAYOFF, LAYFRZ, LAYLCK, LAYULK, LAYON,
LAYTHW, LAYISO, LAYUNISO) mutated `document.layers` directly through
`dl.turn_off()` / `dl.freeze()` / `dl.lock()` / … and then called
`refresh_layer_panel()` — but never bumped the scene's geometry epoch.
The 3D viewport caches its uploaded geometry keyed by that epoch, so
visibility / freeze changes only took effect once something else
triggered a re-render. Pressing Esc was the workaround because the Esc
handler calls `deselect_all()`, which internally bumps geometry.

The Layer Properties panel toggles already did this (their handlers in
`update.rs` call `scene.bump_geometry()` after each freeze / visibility
toggle), which is why the panel worked and only the command path
exhibited the lag.

Add `scene.bump_geometry()` after the layer mutation loop in each of
the eight command branches so the viewport repaints immediately. Lock /
unlock don't strictly affect rendering today but are bumped for
consistency — selection / grip code may grow lock-aware later.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 23:17:58 +03:00
Hakan Seven
9db206f310 feat(select): Select Similar + Quick Select panel (#28)
Two ways to extend the current selection by entity type + layer:

* `Select Similar` (right-click → Select Similar, or `SELECTSIMILAR` /
  `SELSIM`): for every (type, layer) pair in the current selection, add
  every entity in the active layout that matches the pair. The seed
  selection stays selected; matches already selected are not
  double-counted.
* `Quick Select` (right-click → Quick Select…, or `QSELECT` / `QS`):
  opens a centred floating panel with Object-type and Layer pick_lists
  (populated only with values that actually exist in the active layout)
  and an "Append to current selection" checkbox. `(Any)` in either slot
  skips that filter. Apply replaces the selection (or extends it with
  Append on) and reports the match count on the command line. Cancel,
  Esc, and outside-click all dismiss without applying. When opened with
  a selection present, the panel pre-fills both filters from the first
  selected entity so QSelect doubles as an editable Select Similar.

Scoped to entities owned by the active layout's block-record so a paper
layout's QSELECT never reaches into model-space entities (and vice
versa). Entity-type names live in a single `entity_type_name` helper in
`entities::traits` to keep the UI strings and the filter keys aligned.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:30:56 +03:00
Hakan Seven
89fcb21b46 fix(layer): propagate LAY* command changes to panel + dropdown (#39)
LAYOFF / LAYFRZ / LAYLCK / LAYON / LAYTHW / LAYULK / LAYISO /
LAYUNISO mutated the document's layer table (\`turn_off\` /
\`freeze\` / \`lock\` / …) and then called \`sync_ribbon_layers\`,
which feeds the ribbon dropdown from \`self.tabs[i].layers.layers\`
— the \`LayerPanel\` cache. That cache was never refreshed from
the document for these commands, so the dropdown stayed wrong and
the Layers Properties Manager kept showing the old visibility /
lock / freeze icons.

Add \`OpenCADStudio::refresh_layer_panel\` that runs
\`LayerPanel::sync_with_viewports\` from the live document before
calling \`sync_ribbon_layers\`. Replace every direct
\`sync_ribbon_layers\` in \`commands.rs\` (all of them sit inside
LAY* arms) with the new helper.

Closes #39
2026-05-29 01:30:02 +03:00
Hakan Seven
8f02e1ca07 fix(dyn-input): re-sync fields immediately after point commits (#35)
`sync_dyn_fields` only ran during `ViewportMove`, so a typed-coord
or click-driven point commit left the previous iteration's field
shape in place until the user nudged the mouse. After the first
LINE click that meant the stale `[X, Y]` (built when there was no
base) kept serving the next point's input as cartesian even though
the command-default for a "Point with base" is now polar
`[Distance, Angle]`. The original #35 fix preserved a
`,`-reshaped cartesian set on purpose, but the same machinery now
also held onto a never-explicitly-chosen `[X, Y]` carried over from
"no base" → "has base".

Track that intent explicitly with `dyn_user_reshaped` (set in
`dyn_comma_advance`, cleared on point commit and command start)
and only treat a non-matching shape as acceptable while that flag
is on. Re-run `sync_dyn_fields` from every point-commit path
(command-line typed coord, mouse click, dynamic-input locked
commit) so the freshly updated `has_base` state immediately picks
the right default and the user sees polar fields without having
to move the mouse first.
2026-05-29 00:57:37 +03:00
Hakan Seven
e27ab79b08 fix(menu): wire EXIT / QUIT commands to the close path (#41)
The OCS main menu's "Exit Open CAD Studio" button dispatched
`Message::Command("EXIT")` but `dispatch_command` had no arm for it,
so the click was swallowed. Route `EXIT` and `QUIT` through
`Message::WindowCloseRequested` so the unsaved-changes dialog runs
first and the app then exits via the same path as the OS-window close.

Closes #41

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:55:08 +03:00
Hakan Seven
8a510c563c fix(render): adapt mesh + hatch load colours to background
`adapt_to_bg` (pure black → white on dark bg, pure white → black on
light bg) was only running through `render_style`, which the wire and
per-frame hatch paths already use. The mesh population paths and the
file-load hatch path went straight through `aci_to_rgba`, so:

- ACIS solids / regions / bodies appeared in their raw ACI colour
  even when that colour was pure white on a white paper bg.
- The initial hatch upload used the unadapted colour for the first
  frame after load (`synced_hatch_models` then re-ran adaptation per
  frame, so the issue cleared after one redraw — but visible).

Now every mesh / hatch creation site funnels through
`render_style_for` + `adapt_to_bg`:

- `build_derived_caches` (load): uses the default model bg as the
  adaptation target.
- `populate_meshes_from_document`: uses `self.render_style` so the
  per-frame paper / model bg selection applies.
- New `Scene::recolor_meshes` rewrites every cached mesh's colour
  field through `render_style`; called from `BACKGROUND` (after the
  user changes bg) and `set_current_layout` (Model ↔ paper bg
  switch) so ACIS mesh colour tracks the live bg without re-
  tessellating ACIS geometry.

Wipeout fills stay bg-coloured by design (that's their job). Raster
images skip adaptation — pixel data is not entity colour.
2026-05-28 21:15:28 +03:00
Hakan Seven
e3c962bdd1 fix(viewport): adapt entity colours to visible bg
`model_wires_for_viewport` was tessellating with `self.bg_color` (the
model bg), so model content rendered inside a paper-layout viewport
adapted to a dark bg even though the transparent shader now reveals
the PaperCanvas sheet behind it — white wires stayed white on a light
sheet. Pass the paper bg in a paper layout.

Wire cache also needed an explicit invalidation when the bg or the
active layout changes; otherwise the cached, already-adapted wires
were returned against the new bg unchanged.

- BACKGROUND command bumps `geometry_epoch` after both branches
- new `Scene::set_current_layout` setter bumps on actual change; layout
  switch + history restore use it
2026-05-28 18:28:18 +03:00
Hakan Seven
6fe25e4b5f feat(viewport): interactive VPORTS model tiled presets
Model space'de VPORTS artık iki aşamalı: bare VPORTS bir yapılandırma
istiyor (SIngle/2H/2V/4), sonraki komut-satırı girdisi düzeni kuruyor
(boş giriş = SINGLE). set_model_tile_layout normalized rect listesiyle
tiled düzeni değiştiriyor:
- SINGLE: tek tam-ekran
- 2H: üst/alt
- 2V: sol/sağ
- 4: 2x2

awaiting_vports app flag'i prompt sonrası girdiyi yakalıyor. Paper-space
VPORTS davranışı değişmedi.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:52:55 +03:00
Hakan Seven
fda0f2dbb9 refactor(ui): remove the viewport navigation bar
Sağ kenardaki dikey orbit/pan/zoom/zoom-extents araç çubuğu tümüyle
kaldırıldı: viewport render'ı, overlay::nav_toolbar fonksiyonu,
show_navbar state + ToggleNavbar mesajı/handler'ı, NAVBAR komutu, View
şeridindeki "Navigation Bar" aracı ve navbar modülü/ikonu. Aynı
gezinme işlemleri komutlar (3DORBIT, PAN, ZOOM*) ve ViewCube üzerinden
erişilebilir durumda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 17:49:23 +03:00
Hakan Seven
874226bd43 feat(input): relative coordinate entry (@/#) and DYN default — #26
Koordinat girişi artık göreli/mutlak ayrımını destekliyor:
- `@x,y[,z]` → son girilen noktaya göreli (UCS eksenlerinde döndürülür)
- `#x,y[,z]` → her zaman mutlak (DYN durumundan bağımsız)
- ön ek yok → DYN açıksa göreli, kapalıysa mutlak
İlk noktada referans olmadığından göreli giriş mutlağa düşer. Çözülen
WCS noktası last_point olarak saklanıyor, böylece ardışık göreli
girişler zincirleniyor.

parse_coord artık (Vec3, CoordKind) döndürüyor; relative offset için
ucs_rotate_vec eklendi (origin translation olmadan UCS rotasyonu).
UCS ORIGIN komutu prefix'i yok sayıp mutlak kalıyor.

Ayrıca ViewCube snap refactor'undan kalan kullanılmayan kod temizlendi:
angle_close, active_viewport_yaw_pitch, CubeRegion::snap_angles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 11:40:28 +03:00
Hakan Seven
5da32ff202 refactor(entities): collocate ui_name + dxf_name into entities/names.rs
Two near-identical match-on-EntityType tables lived far apart:
scene/properties.rs::entity_type_name returned mixed-case UI labels;
app/commands.rs::entity_type_name returned uppercase DXF record names.
Both enumerated ~30 variants; adding a new entity meant remembering to
touch two files in two crates' worth of source layout.

Move both into a single entities/names.rs as `ui_name()` and
`dxf_name()`. Adjacent so a new variant gets both labels added at once;
function names make the intended audience explicit at the callsite.

scene/properties.rs and app/commands.rs each lose their local match
plus one re-export, gaining a `crate::entities::names::*` call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:18:47 +03:00
Hakan Seven
e78321190a refactor(commands): per-entity MassProps + TextContent traits
Two free helpers in app/commands.rs lived as big match-on-EntityType
blocks: massprop_entity (Circle/Arc/Line/LwPolyline/Ellipse area +
perimeter + centroid, ~140 lines) and entity_text_content /
replace_entity_text (Text/MText/AttributeDefinition/AttributeEntity).

Move each to its own trait in entities/traits.rs and implement
per-entity:

  MassPropsCalc  — Circle, Arc, Line, LwPolyline, Ellipse
  TextContent    — Text, MText, AttributeDefinition, AttributeEntity

EntityTypeOps gets dispatch shims that route to the trait via the
existing macro, so callsites just call entity.mass_props() /
entity.text_content() / entity.replace_text(search, rep).

The remaining free helpers in commands.rs (entity_list_details,
entity_extra_info, entity_type_name, build_data_extraction_csv) are
single-command formatting helpers tightly coupled to LIST / EXTRACT /
text-format output. Distributing them as per-entity methods would
inflate every entity file with command-side formatting concerns
without making the command-side any cleaner — left in place.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:30:34 +03:00
Hakan Seven
ecdb0cd0a0 chore(release): bump to 0.4.0
Fix repo URLs to canonical HakanSeven12/OpenCADStudio casing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 17:32:29 +03:00
Hakan Seven
ef1f9a08d6 chore: rename H7CAD to Open CAD Studio
Joining the OpenAEC Foundation (open-aec.com). Crate/struct become
OpenCADStudio, user-facing strings use the spaced "Open CAD Studio"
form, repo URLs point to HakanSeven12/open-cad-studio. Logo (window
icon + assets/logo.svg) redrawn with OCS initials. Drop examples/
binaries — unused.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 17:16:15 +03:00
Hakan
e9488f99dd perf(wire): move LWDISPLAY toggle from CPU rebake to wire shader uniform
LWDISPLAY (Show Lineweight) flipping the toggle no longer triggers a full
retessellate. `render_style_for` always bakes the entity's resolved (layer-
inherited) lineweight, and the wire shader reads `Uniforms.lwdisplay_enable`
to either honour the per-vertex half_width or collapse the quad to a 1 px line.

Side effect: PDF export now uses the real lineweight regardless of LWDISPLAY,
matching AutoCAD's plot semantics (LWDISPLAY only affects screen render).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 09:45:24 +03:00
Hakan Seven
fb3dd58d09 fix(polyline): correct wide-polyline fill band arc center + add LWDISPLAY toggle
Wide LWPolyline/Polyline2D fill bands now sit on the centerline:
- polyline_segment_fill arc center matched explode.rs / acadrust convention
  (signed d_perp = r·(1-b²)/(1+b²), handles major arcs correctly)
- per-vertex hw0/hw1 taper applied along arc; tess density 24/circle
- synced_hatch_models subtracts hatch_offset from band boundary so it lives
  in the same offset-rel space as the wire tessellation

LWDISPLAY:
- new command `LWDISPLAY [ON|OFF]` toggles header.lineweight_display
- LWT status-bar pill (between ORTHO and POLAR) drives the same flag
- bump_geometry() on change so the viewport re-tess immediately

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 07:15:26 +03:00
Hakan Seven
4cca6363ef feat(app): surface round-trip HeaderVariables via DWGPROPS + USERI/USERR
The "round-trip metadata" fields (timestamps, GUIDs, file identity,
user-script scalars) were parsed and re-emitted on save but never
exposed anywhere, so users couldn't confirm what the file actually
carried. Add two command-line commands that read / edit them:

- DWGPROPS / DWGPROP dumps the header to the command-line output:
  create / update / total-edit / user-elapsed timestamps,
  fingerprint_guid, version_guid, code_page, menu_name, hyperlink_base,
  project_name, stylesheet, required_versions, measurement (with a
  human label), proxy_graphics, tree_depth, plus the five USERI / USERR
  slots and the user_timer flag.
- USERI <1-5> <int> / USERR <1-5> <real> writes one of the five
  document-scoped scalar slots and marks the tab dirty so the value
  rides the next save. Lets users park drawing-scoped numbers in the
  same place AutoCAD scripts have always used them, even though we
  don't ship a LISP / DIESEL runtime.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 05:48:07 +03:00
Hakan Seven
4adbb20c5d feat(scene): honour the last DimStyle fields end-to-end
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>
2026-05-19 13:37:37 +03:00
Hakan Seven
66243e53ca feat: correct viewport scale calculation with correct dimension and text scaling 2026-05-19 00:26:08 +03:00
Hakan Seven
412fb857ba chore: drop legacy comments and the last unused dead_code allows
* io/xref.rs — replaced the 7-line "BlockRecord::is_loaded turned out to
  be unreliable" rationale with a one-liner. The reverted experiment
  lives in git history (e88a946, 0996b75).
* refedit::RefEditSession.insert_handle — set at construction but never
  read; the "kept for future REFCLOSE DISCARD" TODO can come back via
  git history when that feature lands.
* scene::mod — dropped the placeholder MeshModel re-export; nothing
  outside `mesh_model` consumes the bare name and MeshLodSet is the
  pipeline-facing type now.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 02:04:41 +03:00