Commit Graph
653 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 72ec1146df refactor(plugin): extract ocs_plugin_api crate for the stable add-on contract
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]>
2026-06-15 01:36:04 +03:00
Hakan SevenandClaude Opus 4.8 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 <[email protected]>
2026-06-15 01:12:29 +03:00
Hakan SevenandClaude Opus 4.8 036ac7dd86 fix(command): submit typed command on Enter without focusing the input (#99)
Unfocused keyboard input routes through the global subscription: typed
characters reach the command line, but Enter went to CommandFinalize, which
only forwarded the buffer to CommandSubmit while a command was already active.
At startup (before the window grabs focus) typing a command name and pressing
Enter did nothing until the user clicked into the command line. Forward any
non-empty buffer to CommandSubmit regardless of active-command state.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-15 00:35:03 +03:00
Hakan SevenandClaude Opus 4.8 0badf48454 feat(scene): tessellate loaded ACIS solids via the truck B-rep kernel
Rebuild every ACIS face as a truck topological Face and mesh it with truck,
routing loaded 3DSOLID/BODY/REGION/SURFACE through the same kernel the Model
tab uses. plane→try_attach_plane, cone/cylinder/sphere/torus→revolution faces
(truck's Surface has no cone/sphere/torus), spline→BSplineSurface grid sample.
Per-vertex normals are computed analytically outward per surface type and
triangle winding is oriented to match, since truck's own orientation isn't
consistent across independently built faces. `tessellate_solid3d|body|region|
surface` try this first and fall back to the bespoke sampler.

Light the mesh shader with three spread directional lights plus ambient
(abs(dot) per light, two-sided) so faces — including the inner walls of an
open lofted surface — never read as a flat dark mass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-15 00:30:18 +03:00
Hakan SevenandClaude Opus 4.8 9d823363f1 feat(scene): render 3D surface entities (loft/sweep/extrude/etc.)
ACAD_SURFACE entities now load and render. Their NURBS faces are
tessellated by building a truck BSplineSurface from the ACIS `spline-surface`
control net and knot vectors, then sampling its parametric grid — reusing the
same NURBS kernel the Model tab already builds on. Analytic faces
(plane/cone/sphere/torus) continue through the existing tessellator.

- New spline_tess.rs: parse the ACIS `nubs` block (degrees, knots with the
  clamped end-multiplicity fix, control grid) into a truck BSplineSurface and
  mesh it.
- tessellate_surface + Surface wired through the mesh-build, fallback-wire,
  and entity-classification paths.
- Bump acadrust to the commit that adds the Surface entity.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 20:11:44 +03:00
Hakan SevenandClaude Opus 4.8 b173f32d5a fix(scene): tessellate cone and cylinder 3D solids
Cone and cylinder bodies rendered empty because their geometry depends on
circular edges the previous boundary walk couldn't use:

- Sample ellipse/circle boundary edges into multiple points so circular caps
  build a real polygon instead of a single degenerate vertex.
- Recover a cone/cylinder lateral face's height span from the solid's coaxial
  circle rims (plus a true cone's analytic apex) when the face boundary
  collapses to one height.
- Wind cone-side quads and plane-face fans so their face normals point
  outward, keeping flat-shaded and Gouraud lighting consistent.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 15:43:24 +03:00
Hakan SevenandClaude Opus 4.8 a2cb833c83 fix(scene): render raster images at correct position and size (#98)
Match the image shader's uniform layout to the shared 112-byte Uniforms
struct (was 144) and project quad corners via view_proj so texture-quad
draws no longer fail uniform binding validation.

Offset image quad corners by world_offset, matching all other Model-space
geometry, so rasters land in the same local space instead of being shifted
away. Downscale oversized images to the GPU's max texture dimension (8192)
to keep texture creation from failing.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 15:06:06 +03:00
Hakan SevenandClaude Opus 4.8 a819a47c08 fix(image): resolve raster image paths so DWG-embedded images render (#98)
A RasterImage stores its path on the linked ImageDefinition, usually as the
original author's absolute path (e.g. a Windows/VMware share) that doesn't
exist locally, so the image silently failed to load. On open, resolve each
raster image: use the stored path if it exists, else fall back to the same
file name next to the drawing, and write the resolved path onto the entity.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 14:38:54 +03:00
Hakan SevenandClaude Opus 4.8 b7c5103891 feat(ui): show RGB for unnamed palette colours instead of "Index"
Picking a colour from the full palette showed a generic "Index" label.
Add color_display_name: standard names for ACI 1-9 / ByLayer / ByBlock,
"R,G,B" (0-255) for every other indexed or true colour. Used by the shared
colour selector button and the ribbon colour combo.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 14:27:35 +03:00
Hakan SevenandClaude Opus 4.8 f7734b698f feat(ui): use the shared colour selector in properties, ribbon and layers
Replace the bespoke colour pickers with the shared color_selector / color_list
widget so colour selection is identical everywhere:
- left properties panel (selected entities' colour)
- ribbon Home-tab colour dropdown
- layer manager per-layer colour

Each routes its "More…" entry to the standalone Select Color window via a
ColorPickTarget (Properties / Ribbon / Layer). Extracted color_list as the
shared popup content; the colour selector button now fills its container.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 14:22:24 +03:00
Hakan SevenandClaude Opus 4.8 3d050fe1ab feat(ui): move full ACI palette to a separate "Select Color" window (#94)
The colour selector popup now lists only the named colours (ByLayer,
ByBlock, ACI 1-9) plus a "More…" entry. "More…" opens a standalone
"Select Color" window with the full 256-colour grid; picking there applies
to the originating field (dimension/multileader/table style) and closes
the window. Routed via a ColorPickTarget so one window serves every field.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 11:14:14 +03:00
Hakan SevenandClaude Opus 4.8 3a87a18f32 fix(ui): float the colour-selector popup instead of pushing the form (#94)
The colour list/palette was laid out inline, shifting everything below it
down when opened. Wrap it in a small custom widget (Floating) that renders
the button inline and the popup as an iced overlay anchored below it, so it
floats over the UI like a real dropdown. Picking a colour now also closes
the popup (the style editors clear their open-field state on edit).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 11:00:43 +03:00
Hakan SevenandClaude Opus 4.8 96a5a32735 feat(ui): show colour swatches beside names in the colour selector (#94)
Reworked the shared colour selector from a plain text pick_list into a
dropdown button that opens a list where each named colour (ByLayer,
ByBlock, ACI 1-9) shows its swatch next to the name, with the full ACI
palette grid below. The dimension/multileader/table style editors pick
it up unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 10:47:20 +03:00
Hakan SevenandClaude Opus 4.8 8cdb17a6b4 feat(ui): shared colour selector used across all style editors (#94)
Add a reusable color_selector widget (ui/color_select.rs): a main-colour
dropdown plus a "more colours" button that expands the full 255-entry ACI
palette — the same design the properties panel uses. Replace the flat ACI
swatch rows added earlier in the dimension, multileader and table style
editors with it, wired through each editor's existing colour edit message
(the chosen colour is encoded as an ACI string) plus a per-field
palette-open toggle.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 10:42:39 +03:00
Hakan SevenandClaude Opus 4.8 3ccda2f129 feat(style): pick colours from ACI swatches in the style editors (#94)
Dimension, multileader and table style editors took colours as raw ACI
integers in a text box. Replace those with a swatch row (the 9 standard
ACI colours + ByLayer) that highlights the current value, reusing each
editor's existing colour edit message (the chosen index is sent as the
field's string value, so no new plumbing).

Text styles carry no colour field, so none was needed there.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 10:15:14 +03:00
Hakan SevenandClaude Opus 4.8 e263cb2458 fix(dimension): break the dim line behind on-line text so DIMTFILL reads (#94)
2-D fills (the DIMTFILL background) render in a pass before all wires, so
the background could never sit above the dimension line — the line showed
through it. When the text sits on the line, gap the dimension line across
the text box (AutoCAD-style break) so the background reads cleanly over
the line. Text placed above/below the line is unaffected.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 10:09:28 +03:00
Hakan SevenandClaude Opus 4.8 5f6c4029e9 fix(mleader): new multileader inherits the current MLeader style (#94)
A new multileader was built from defaults and ignored the current
MLeaderStyle, so the style editor's settings had no effect on it (the
renderer reads the entity's own fields, not the style). commit_entity_handle
now copies the style's content/path type, text attachment/alignment/angle,
colours, sizes, scale and landing into the new entity (enum types
round-trip through i16 since the entity and style enums are distinct).

Text styles already render correctly and tables already resolve their
style live via table_style_handle, so no change was needed there.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 09:59:12 +03:00
Hakan SevenandClaude Opus 4.8 6aa47c6ead fix(dimension): DIMZIN feet/inch suppression and DIMJUST 3/4 rotation (#94)
- DIMZIN now drives architectural feet/inch zero-suppression (modes 0-3)
  inside format_architectural, threaded through format_with_unit (primary
  and alternate units).
- DIMJUST 3 and 4 (over first / second extension) now rotate the text 90°
  to read along the extension line, not just shift its position.
- DIMTAD 2 (outside) and 3 (JIS) resolve to the away-from-object side,
  which equals "above" for 2-D linear dims; clarified in a comment.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 09:42:11 +03:00
Hakan SevenandClaude Opus 4.8 edacc895f7 fix(dimension): honour DIMTAD below and DIMTFILL background fill (#94)
Two style settings were accepted by the editor but ignored by the
renderer:
- DIMTAD=4 (Below) placed text above the dimension line like DIMTAD=1;
  the text is now offset to the opposite side.
- DIMTFILL=1 (drawing background) drew no fill; it now masks the geometry
  behind the text with the background colour (DIMTFILL=2 already used
  DIMTFILLCLR).

Verified the other enum settings render: DIMLUNIT/DIMFRAC unit formats,
DIMAUNIT/DIMADEC/DIMAZIN angular formats, DIMJUST positions,
DIMATFIT/DIMTMOVE fit, DIMTOLJ tolerance alignment, and the alternate-unit
/ zero-suppression families.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 09:37:01 +03:00
Hakan SevenandClaude Opus 4.8 f93bda1800 feat(dimstyle): use named dropdowns for enum style settings (#94)
The dimension style editor exposed every enum setting (DIMTAD, DIMJUST,
DIMLUNIT, DIMTMOVE, DIMATFIT, DIMTFILL, DIMFRAC, DIMAUNIT, DIMAZIN,
DIMALTU, DIMTOLJ, DIMARCSYM, and the zero-suppression family) as a raw
integer text box, forcing the user to type codes like "1". Replace them
with named pick_list dropdowns via an enum_field helper that maps the
stored code to a label and back. Numeric fields (sizes, colours,
decimals) and obsolete flags (DIMUNIT/DIMFIT) keep their text inputs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 09:28:36 +03:00
Hakan SevenandClaude Opus 4.8 b211ca07a9 feat(dimension): flip arrowheads outside when they don't fit (#94)
When the dimension line is too short for the arrowheads, they are now
placed outside the extension lines (tip on the line, body outward) with a
short stub to sit on, instead of overlapping inside. DIMSOXD suppresses
those outer stubs. Ticks are unaffected (they always fit).

DIMATFIT's exact mode ordering and DIMUPT remain for a later pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 02:13:00 +03:00
Hakan SevenandClaude Opus 4.8 40a91372fd feat(dimension): honour DIMTIX/DIMATFIT text fit; clarify remaining flags (#94)
Centred dimension text that doesn't fit between the extension lines now
slides outside (text-outside placement), unless DIMTIX forces it to stay
inside. DIMTOFL is already satisfied for linear/aligned dims (their dim
line is always drawn between the extension points).

The remaining style flags are documented in place:
- DIMATFIT arrow-outside modes and DIMUPT need an arrow autofit pass.
- DIMTXTDIRECTION (RTL) needs single-line text direction support.
- DIMARCSYM / DIMJOGANG apply only to arc-length / jogged-radius dims,
  which aren't shipped as entity variants.
- DIMUNIT is obsolete (superseded by DIMLUNIT).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 02:03:05 +03:00
Hakan SevenandClaude Opus 4.8 006dbe4631 fix(dimension): make DIMTAD/DIMTVP/DIMJUST drive text placement (#94)
Dimension text position ignored the style: the renderer returned the
saved text_middle_point whenever it was non-zero, which it always is for
app-created dims, so DIMTAD ("text over the dimension line" / ISO) and
friends had no effect — text could only be moved by grips.

Use the new acadrust text_user_positioned flag: when the text wasn't
user-placed, compute the position from the style (DIMTAD/DIMTVP/DIMJUST)
anchored on the dimension line (definition_point); when it was (grip drag
or properties edit, both now set the flag), keep the saved point.
Imported drawings keep their fidelity via the flag round-tripping through
DXF/DWG.

Also fixes linear/aligned text anchoring at the measured midpoint instead
of the actual dimension line.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 01:57:58 +03:00
Hakan SevenandClaude Opus 4.8 68a3efa83d fix(style): new mleader/table use the current style (#92)
Follow-up to the text/dimension fix: MultiLeader and Table were created
at their default style too. Their styles live in the objects dictionary,
so commit_entity_handle now resolves header.current_mleader_style_name /
current_table_style_name to the matching style object's handle and
assigns it, unless the command already set one or no such style exists.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 01:19:00 +03:00
Hakan SevenandClaude Opus 4.8 92e32243eb fix(style): new text/mtext/dimension use the current style (#92)
New annotation entities were created at the entity "Standard" default
instead of the document's current style, so making a style current had
no effect on creation.

- Text / MText: seed the new entity (and the MText editor preview) from
  header.current_text_style_name.
- Dimension: commit_entity_handle fills the new dimension's style_name
  from header.current_dimstyle_name, covering every dimension command
  centrally.

Applied only when the entity is still at its default so an explicitly
styled entity is preserved. MLeader and Table styles need their style
tables modelled first and are left for follow-up.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 01:15:52 +03:00
Hakan SevenandClaude Opus 4.8 29ccfcfc46 fix(command): accept typed points/Enter in keyword steps when DYN is off (#97)
A command step that takes both a point and option keywords (PLINE,
MLINE) returns true from wants_text_input to receive the keywords, but
CommandSubmit then routed the text only to on_text_input and, on a None
(unhandled) result, re-prompted instead of falling through. So with
dynamic input off, typed coordinates and a bare Enter were swallowed —
points couldn't be added by keyboard and the command couldn't be ended.

Fall through to the existing Enter / coordinate handling when
on_text_input doesn't consume the text. Coordinates (including @ and #
prefixes) and Enter now work in these steps with DYN both on and off;
commands that fully handle their text step still return Some and are
unaffected.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 00:59:52 +03:00
Hakan SevenandClaude Opus 4.8 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 <[email protected]>
2026-06-14 00:42:02 +03:00
Hakan SevenandClaude Opus 4.8 bc92590725 fix(stretch): re-tessellate stretched entities so the viewport updates (#95)
STRETCH edited geometry directly via document.get_entity_mut, which the
scene's tessellation cache doesn't observe, so the viewport kept showing
the pre-stretch shape until an unrelated redraw. Mark each stretched
entity dirty and bump geometry on completion, matching MOVE's
transform_entities path.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 00:36:17 +03:00
Hakan SevenandClaude Opus 4.8 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 <[email protected]>
2026-06-14 00:16:06 +03:00
Hakan SevenandClaude Opus 4.8 5848569368 fix(fillet): correct wrap-around corner on closed polyline (#91)
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]>
2026-06-14 00:10:39 +03:00
Hakan SevenandClaude Opus 4.8 ae86ad7582 fix(trim): assign handle to trimmed polyline so it can be trimmed again (#65)
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]>
2026-06-14 00:04:38 +03:00
Hakan SevenandClaude Opus 4.8 57d3e620ea fix(command): keyword/radius input for ribbon-started modify commands (#90)
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]>
2026-06-13 23:58:56 +03:00
Claude d0870745f2 fix(windows): cast COINIT_APARTMENTTHREADED to u32 for windows-sys 0.61.2
windows-sys 0.61.2 changed CoInitializeEx's dwcoinit parameter type
from i32 to u32, causing a mismatched types compile error on Windows.

https://claude.ai/code/session_01F7yMUY48VuGNUF1Q3ma2cZ
2026-06-12 11:47:52 +00:00
Claude 13eef50a00 fix(windows): add DefaultIcon to Applications entry so "Open with" icon shows
Both the MSI and the portable build were missing Applications\OpenCADStudio.exe\
DefaultIcon, causing a blank generic icon in the "Open with" context-menu list
and the "Choose another app" picker.

- packaging/windows/main.wxs: add Applications\OpenCADStudio.exe\DefaultIcon
  (+ FriendlyAppName, shell\open\command, SupportedTypes) to the
  DefaultPrograms component for MSI installs (HKLM).

- src/io/file_association.rs: add DefaultIcon to the Applications\
  OpenCADStudio.exe block written at runtime for portable builds (HKCU).

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 11:42:41 +00:00
Claude 73cb0b7e21 fix(linux): install SVG icon to XDG hicolor theme so "Open with" shows the icon
The .desktop file used a named icon (Icon=io.github.HakanSeven12.OpenCadStudio)
but the icon file was never written to the XDG icon theme directory, so file
managers and the "Open with" picker fell back to a generic blank document icon.

install_icon() now writes assets/logo.svg (embedded at compile time) to
~/.local/share/icons/hicolor/scalable/apps/{APP_ID}.svg alongside the
.desktop file, and runs gtk-update-icon-cache so the change is picked up
immediately. The write is skipped when the on-disk file is already identical.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 11:39:28 +00:00
Hakan SevenandClaude Opus 4.8 cd4d1c2535 fix(layout): focus inline rename field on RENAME
Renaming a layout tab left keyboard focus on the command line, so typed
characters landed there until the user clicked the tab. Give the inline
rename text_input a stable id and return a focus task when the rename
starts so the user types straight into the field. (issue #86)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 14:18:31 +03:00
Hakan SevenandClaude Opus 4.8 dc26c8128c fix(otrack): reset tracking points after each committed point
OTRACK acquired points only ever cleared when a command ended, so a
multi-point command (LINE, etc.) accumulated tracking points across
every input, swamping the user with alignment options. Clear the
acquired points and live alignment vector each time a point is committed
to the active command, at all five point-accept paths. (issue #85)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 14:15:53 +03:00
Hakan SevenandClaude Opus 4.8 9de2760dee fix(selection): keep active selection on empty box/lasso
An empty box or lasso drag called deselect_all, discarding a selection
the user may have built up over several picks. Drop the empty-clear from
the lasso and two-click box paths so a stray empty drag leaves the
selection untouched; Esc remains the deliberate clear. (issue #83)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 14:07:57 +03:00
Claude db607cca1f fix(windows): register HKCU ProgIDs + Capabilities for portable .exe default-app support
For MSI installs the installer already writes HKLM Capabilities +
RegisteredApplications, so the "Set as default" dialog works.  For
portable .exe builds those registry keys are never created, so the
IApplicationAssociationRegistrationUI dialog silently does nothing.

register_handler() now also writes:
- HKCU\Software\Classes\Applications\OpenCADStudio.exe  (Open-with list)
- HKCU\Software\Classes\OpenCADStudio.DWG / .DXF        (ProgIDs + icon + open cmd)
- HKCU\Software\Classes\.dwg|.dxf\OpenWithProgids
- HKCU\Software\Open CAD Studio\Capabilities            (AssociationToasts shown)
- HKCU\Software\RegisteredApplications                  (Windows Default Programs)

This makes the "Yes, set as default" button work correctly for portable
builds on all supported Windows versions.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 10:54:34 +00:00
Claude 5ba7101022 feat(app): silently register as a .dwg/.dxf handler so portable builds show in "Open with"
Installed builds already register a handler (MSI ProgIDs, the Linux
.desktop MimeType, the macOS Info.plist), but the portable .exe and the
AppImage register nothing, so they never appear in the OS "Open with"
list. Add a best-effort, idempotent runtime registration that runs
silently on a detached thread at startup:

  * Windows — writes HKCU\Software\Classes\Applications\OpenCADStudio.exe
    (open command + FriendlyAppName + SupportedTypes .dwg/.dxf), which is
    what lists the exe in "Open with → Choose another app".
  * Linux — writes a user-level .desktop pointing at the running binary
    ($APPIMAGE when set) and refreshes the desktop database. Skipped when
    a system package already provides the desktop entry, so we never
    shadow a distro install.
  * macOS — no-op; .app bundles auto-register with LaunchServices.

Unlike the default-app prompt this changes no defaults and shows no UI;
it only makes the binary discoverable as a handler.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 08:00:18 +00:00
Claude a7d85fe148 feat(app): first-launch prompt to set Open CAD Studio as default for .dwg/.dxf
On the first launch that hasn't answered it yet, the app shows a small
dialog offering to become the default handler for .dwg and .dxf files.
The choice is persisted (default_assoc_prompted) so the prompt never
reappears.

The platform plumbing lives in io/file_association.rs:
  * Windows — opens the OS's own per-app default-programs dialog via
    IApplicationAssociationRegistrationUI::LaunchAdvancedAssociationUI,
    passing the "Open CAD Studio" RegisteredApplications name the MSI
    registers. Modern Windows guards the actual default behind a
    UserChoice hash, so this is the supported consent path.
  * Linux — xdg-mime default against the installed .desktop file, whose
    MimeType entries already cover image/vnd.dwg and image/vnd.dxf.
  * macOS — LSSetDefaultRoleHandlerForContentType binds the DWG/DXF UTIs
    declared in Info.plist to the app bundle id.

The work runs on a dedicated thread (the Windows dialog is modal) with
the result delivered back through a oneshot channel and surfaced in the
command line.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:50:41 +00:00
Hakan SevenandClaude Opus 4.8 71837895b0 feat(ui): right-edge side toolbar; drop the paper-space ribbon tab
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]>
2026-06-12 02:07:26 +03:00
Hakan SevenandClaude Opus 4.8 b47e1bf706 fix(ribbon): load style/layer dropdowns from the document
The Annotate style dropdowns (text / dimension / multileader / table) and
the layer dropdown were hard-coded to "Standard" / "0" and never synced
from the opened document, so they showed placeholder values even with no
file open and stale values after closing one.

- Start the ribbon's style and layer lists empty instead of with a
  placeholder entry.
- Sync the style dropdowns from the document on file open and on tab
  new/switch/close (the layer sync already ran there).
- Guard sync_ribbon_layers / sync_ribbon_styles / sync_ribbon_from_selection
  for the Start (welcome) tab so all three dropdowns stay empty there.
- When a style family is absent (e.g. no table/multileader styles), fall
  back to empty rather than a non-existent "Standard".

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:46:17 +03:00
Hakan SevenandClaude Opus 4.8 2519d64907 fix(select): box/lasso adds to the selection (#83)
Box selection is a two-click window (corner, corner): the first empty
click armed the box AND cleared the selection, and the box completion
cleared again before applying its hits — so a second box dropped
everything the first one picked. Stop clearing on the arming click and
make the completion accumulate like individual picks (#47): a plain box
or lasso adds, Shift+box/lasso removes the enclosed entities, an empty
box/lasso (or Esc) clears.

Fixes #83

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:22:59 +03:00
Hakan SevenandClaude Opus 4.8 610f7f140d fix(trim): trim/extend against polyline boundaries
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]>
2026-06-12 01:06:29 +03:00
Hakan SevenandClaude Opus 4.8 cea84ccb64 fix(edit): correct entity-pick hover previews on offset drawings
The trim/extend hover preview (on_hover_entity) gets the cursor in
offset-relative space but compares it against WCS document entities and
returns WCS preview wires. On a drawing with a non-zero world_offset the
preview highlighted the wrong segment and the wires were shifted off the
geometry by the offset. Pass the world-offset-corrected click to
on_hover_entity and shift its returned wires back to the offset-relative
frame the renderer uses. Point-creation previews keep the local cursor.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 01:00:34 +03:00
Hakan SevenandClaude Opus 4.8 14d047ce9a fix(edit): add world_offset to entity-pick clicks
The pick point handed to commands is in offset-relative (local) space,
matching the camera and point-creation commands. Entity-pick, tangent
and structure-pick handlers (TRIM / EXTEND / FILLET / OFFSET, …) instead
compare the click against WCS document entities, so on a drawing with a
non-zero world_offset (UTM-scale coordinates) the click landed ~1e6
units off the geometry — TRIM cut the wrong side or reported the click
as off the segment. Add world_offset back to the pick (model space only)
before passing it to those handlers, matching what the grip path already
does. Point-creation commands keep the local point unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:53:20 +03:00
Hakan SevenandClaude Opus 4.8 f6b894813e fix(zoom): fit 3D solids in ZOOM EXTENTS (fit_all)
ZOOM EXTENTS routes through fit_all, which built its bounds from the
wire set and returned early when there were none. A drawing of only 3D
solids has no wires, so ZOOM EXTENTS did nothing. Collect the
layout's mesh AABBs alongside the wires, keep going when only meshes
exist, and fold them into the fitted bounds.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:42:50 +03:00
Hakan SevenandClaude Opus 4.8 8eeb155bfe fix(zoom): include 3D solid meshes in model-space extents
ZOOM EXTENTS / auto-fit derived the drawing bounds from wire AABBs and
tessellated key-vertices only. 3D solids (Solid3D/Region/Body) render as
meshes with an empty wire path, so they contributed nothing and a
drawing containing only solids zoomed to nothing. Fold each mesh's
offset-relative XY AABB into the extents on both the wire-cache and
tessellate-fallback paths.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:09:05 +03:00
Hakan SevenandClaude Opus 4.8 5fa93b9cb0 fix(solid3d): stop drawing a placeholder line beside modeler solids
Solid3D / Region / Body have no truck wire conversion, so the wire path
fell through to the generic fallback, which returns a 1-unit placeholder
segment at the origin — a stray little line next to every 3D solid. Give
these types their own fallback that emits only the entity's pre-computed
edge wires (empty for binary SAB) plus an insertion snap, never the
placeholder.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 00:04:55 +03:00