Commit Graph
29 Commits
Author SHA1 Message Date
Felix Riestra db61379bfe Add validated Insert transform scripting coverage 2026-09-19 01:20:20 +02:00
Felix Riestra 11d5614991 Add versioned scripting host model and entity coverage 2026-09-19 01:13:24 +02:00
Felix Riestra ec2271fc9c Add versioned host settings API for plugins 2026-09-18 15:13:06 +02:00
Felix Riestra 93afe91afc Publish built-in document edits to V4 plugins 2026-09-18 12:13:43 +02:00
Sebastian fe8397b3cb remove ocs_doc_api crate and DocApi v2 host integration
- Drop crates/ocs_doc_api from workspace and host dependency.
- Remove src/app/doc_api.rs and its module declaration.
- Strip DocApi v2 undo/dispatch plumbing from HostSession.
- Remove HostApi::doc_api_dispatch default method.
- Remove PluginRequest::DocApiRequest / PluginResponse::DocApiResponse.
- Revert plugin API version from 6 back to 5.
2026-09-11 08:07:30 +02:00
Hakan Seven 3385160493 Merge PR #1028: add document API with atomic edits
Add the typed document facade and plugin IPC adapter. Prepare bulk edits
before mutation, preserve undo and dirty state, keep profile elevations,
validate numeric inputs and session bindings, and generate Python constructors
with typed results.

Reuse existing entity transforms and profile conversion. Compute mass
properties and closed-surface bounds in cadkernel; pin its main revision
6a5e6fc2822f6af6b2611cd605b44437b35158f0. Keep plugin API metadata at version 6.

Validation: 646 native application tests, 597 kernel tests, 12 document API
tests, 91 plugin API tests, 3 Python tests, default/IPC feature builds and
wasm32 check passed.
2026-09-06 21:43:02 +03:00
Sebastian 919128e087 ocs_doc_api: fix branch-review findings (tab auth, bulge center, budget, caps, dead code)
CRITICAL:
- execute_doc_api: reject tab_id != bound tab (confused deputy); test
  doc_api_tab_mismatch_is_rejected.
- bulge_arc_segment: fix negative/CW bulge center (apothem magnitude + sign picks
  side; was double-sign-mirrored); test CW-vs-CCW center offset.
- cold-tess budget: move mass_cache/cold_tess_used to per-tab Scene so the budget
  persists across DocApi dispatches (was reset per request); budget-exhausted ->
  approximate meshes.metrics fallback instead of error.

WARNING:
- Loft: add BULK_ITEM_CAP (was uncapped -> unbounded kernel call from one request).
- CreateMany: pre-validate EntitySpec::Curve (ellipse ratio) in prepare phase ->
  all-or-nothing restored; regression test.
- Partial Ellipse: offset start/end_parameter by rot_z on rotation; regression test.
- resolve_body: add with_body borrow accessor (host overrides to borrow cache in
  place, O(1)); bounds() uses it instead of deep-cloning the B-rep per query.
- add_raster_image: validate file path (non-empty, image extension, reject
  UNC/traversal); regression test.

SUGGESTION:
- deploy: bump API_VERSION 5 -> 6 so DocApi plugins are gated at handshake on old
  hosts (append-only wire + defaulted method keep old V2-V5 plugins loading).
- dead code: wire query_name into apply_queries error labels; host uses crate's
  ObjectId::from_handle/to_handle (drop local duplicates); delete is_null/backend_mut.
- mock drift: test mock now stores curve specs + computes curve bounds (aligns with
  example mock); Spline/Dimension bounds delegate to points_aabb.

623 host lib + 8 crate + 3 consistency + 34 plugin_api green.
2026-09-06 16:03:34 +02:00
Sebastian c74504f30c ocs_doc_api: fix review findings (scale/rotation/all-or-nothing/DoS/bounds/codegen)
CRITICAL:
- transform_entity_geometry: fix s^2 double-scale (axes already carry scale; drop
  the *s pre-multiply so 2D entities scale by s like solids), and handle rotation
  for direction/angle fields (apply_dir for Ray/XLine direction + Ellipse
  major_axis; offset Arc angles; compose Insert rotation).
- ensure_transformable: check layer-lock + resolve solid bodies (not just type),
  restoring TransformMany all-or-nothing; regression test with a locked layer.

WARNING:
- volume/centroid: memoize per (handle, geometry_epoch) + cap cold-cache
  tessellation budget (query-batch DoS).
- entity_to_profile_curves: fail Unsupported on polyline bulge (no silent
  chord-substituted sweep profile).
- Ellipse: validate minor_axis_ratio in (0,1] so bounds never under-bound.
- RasterImage bounds: bracket all four corners (rotated/flipped images).

SUGGESTION:
- binding_schema.json handles derived from spec families (includes ArcCurve/
  Ellipse/Spline/Ray/XLine).
- build.rs header: enums are hand-maintained (not 'GENERATED'); ops_gen/query_gen
  headers corrected to match.
- Dedupe divergence mesh_volume_centroid into the crate's geom module; host +
  both mocks delegate.

Tests: +3 regression tests (scale-once, rotation, locked-layer all-or-nothing)
-> 604 host lib green; 8 crate unit tests green.
2026-09-06 08:36:06 +02:00
Sebastian e41086b058 Add ocs_doc_api: object-oriented CAD entity API (v2)
New crate crates/ocs_doc_api: object-centric facade (DocApi -> Document ->
collections -> typed handles), transport-agnostic wire layer (DocApiEnvelope =
single op | read-only query batch), per-op autocommit (no write transactions),
bulk ops with upfront-validation all-or-nothing, structured ApiError, and
build-time codegen (spec/entities.toml -> ops/query enums, api_reference.md,
binding_schema.json).

ocs_plugin_api: append-only PluginRequest::DocApiRequest / PluginResponse::
DocApiResponse (serde_bytes payload) + defaulted HostApi::doc_api_dispatch.

Host: DocApiBackend impl for HostSession (src/app/doc_api.rs) mapping to scene
primitives (solid_models lift-on-miss, register_prepared_solid_model, meshes
metrics), entity conversions (doc_api_convert.rs), HostSession accessors +
doc_api_dispatch override.

Tests: 592 host lib (incl. 7 DocApi integration tests: create/boolean/query,
unknown-id, transform-many all-or-nothing, query-batch cap, 2D/geometric/DWG
roundtrips), 8 crate unit tests. Examples + README/ARCHITECTURE/IMPLEMENTATION docs.
2026-09-06 00:15:27 +02:00
Sebastian 09ba67bfd1 feat(plugins): expose document path to plugins via HostApi
OpenCADStudio/plugins#901 (comment)

- Add HostApi::document_path(tab_id) returning Option<PathBuf>.
- Add PluginRequest::DocumentPath and PluginResponse::DocumentPath for
  out-of-process plugins.
- Implement the request in the IPC server and in both V3/V4 plugin-side
  HostApi proxies.
- Implement HostSession::document_path by looking up the tab by id and
  returning its current_path.
2026-08-29 11:09:31 +02:00
Hakan Seven f247d15f6e feat(plugin): harden V4 host integration
Preserve legacy wire indices and route concurrent V4 responses and background requests by correlation and stable tab ID.
2026-08-15 20:17:43 +03:00
Sebastian 29c41a4041 Snapshot: V4 plugin API and host integration 2026-08-12 21:01:39 +02:00
Hakan Seven 0be9c41773 fix(layers): protect locked entities
Keep locked-layer objects selectable for inspection while suppressing grips, property edits, command mutations, and stale editor writes.
2026-08-12 12:46:41 +03:00
Hakan Seven 3e6693b93f feat(locale): localize full interface 2026-08-02 22:09:25 +03:00
Hakan SevenandClaude Opus 4.8 8164e14a19 fix(plugin): auto-register unknown layers so added entities survive DWG save (#252)
An entity added via a plugin's AddEntity with a common.layer name that no
LAYER command ever created kept its layer in-session, but the DWG writer
resolves an unknown layer name to a NULL handle, so on reopen the entity
collapsed to layer 0.

Add a shared Scene::ensure_layer(name) that registers a missing layer with a
real handle (allocate_handle, per #67) and call it from add_entity and
update_entity. This covers the plugin AddEntity/UpdateEntity IPC and every
internal add path without a new IPC request. Existing/empty/"0" layers are
no-ops, so normal draw commands are unaffected.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-02 20:36:16 +03:00
Hakan SevenandClaude Opus 4.8 f9f7138370 feat(plugin): persist XDATA to DWG and let plugins modify/delete entities (#249, #250)
#249 — plugin XDATA written via write_record survived only in memory: the
acadrust DWG writer dropped ExtendedData::records on save. Bump acadrust to
e88a9a6 (records now encode to EED and decode back on read) and fix the host
side that fed it:
  - ensure_app_id allocates a real APPID handle; a null handle serializes as
    0 and the EED reference can't resolve, so the XDATA vanished on reopen.
  - write_record / remove_record drop stale raw_dwg_eed for the target app so
    an edit made after a save/reopen wins over the pre-edit bytes.

#250 — out-of-process plugins got a throwaway document_mut() snapshot, so
edits to existing entities were silently discarded and deletion wasn't
expressible at all. Add the missing mutation surface:
  - UpdateEntity / RemoveEntity IPC requests + HostApi::update_entity /
    remove_entity (default in-process impls, RPC overrides on the client that
    invalidate the stale document cache).
  - Scene::update_entity replaces the entity in place, preserving its handle
    and owning block, and reseeds only its derived caches; remove reuses the
    cache-coherent erase_entities (which also honours layer locks).
  - document_mut() is documented as a local-only snapshot out-of-process.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-02 15:39:26 +03:00
Hakan SevenandClaude Opus 4.8 b4093724f2 fix(plugin): f64 pick coords + harden out-of-process lifecycle
Follow-ups after merging the out-of-process plugin isolation work:

- Convert the out-of-process interactive adapter's `on_point` /
  `on_entity_pick` to `glam::DVec3`, matching the `CadCommand` trait,
  which moved to f64 pick coordinates. The branch predated that change
  and no longer compiled against the trait.
- Contain plugin constructor panics in `export_plugin!`: the constructor
  runs across the C ABI boundary in `ocs_plugin_register`, where an
  unwinding panic is undefined behavior. Catch it and return null, which
  the loader already treats as a failed registration.
- Reap killed runner processes. The spawn-timeout and disconnect paths
  called `child.kill()` without `wait()`, leaving a zombie on Unix until
  the host exited. Route both — and `shutdown()` — through a shared
  `reap()` that kills and waits on a detached thread.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 00:16:08 +03:00
Sebastian d268170790 Fixing plugin_host.rs to double precision 2026-06-25 00:02:17 +03:00
Sebastian d0003534a0 Fix 5: Allow backward-compatibility and add plugin-template-v2 2026-06-25 00:02:17 +03:00
Sebastian dcfaed0182 Initial commit 2026-06-25 00:01:36 +03:00
Hakan SevenandClaude Opus 4.8 6667042372 fix(xclip): precise clip + carry clip across cross-drawing paste
Clipped blocks were broken at UTM and lost their clip when pasted into
another drawing.

- Clip geometry: world_clip_polygon_f64 keeps the boundary in absolute f64;
  clip_wires now clips in a frame relative to the boundary's first vertex
  (small coords → exact f32 Sutherland–Hodgman) and re-splits the result
  into the double-single high/low pair the relative-to-eye renderer needs,
  so the clipped block draws correctly and no longer poisons ZOOM Extents.
- ZOOM Extents (fit_all): the outlier reject compared absolute UTM
  coordinates against a centre-RELATIVE span (local_extent_max), rejecting
  the whole drawing → no-op. Made the reject distance-from-median-centre.
- Cross-drawing paste: clipboard now snapshots each copied entity's whole
  xdictionary object graph (ClipboardDeps.ext_objects) and recreates it on
  paste with fresh handles + remapped references, re-pointing the pasted
  entity's xdictionary at the new root. Wired into PASTECLIP and PASTEORIG.
  Fixed handle allocation (allocate_handle, not the non-advancing
  next_handle) so the dictionary chain doesn't collapse onto one handle.
- Updated hit_test / plugin_host tests for the eye-relative + DVec3 signatures.

Known gaps: PASTEBLOCK does not yet carry the clip (clip would need to live
inside the new wrapper block definition); hatch-clip path still f32.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-24 09:33:55 +03:00
Hakan SevenandClaude Opus 4.8 4f7a98a2ad feat(f64): command layer compiles on DVec3
Whole command/snap/grip/cursor chain now builds on f64 DVec3 end to end,
so picked points, snaps and grip drags stay precise at UTM-scale
coordinates instead of quantizing to ~0.5 m through f32.

- All ~80 command files migrated: draw (line/circle/arc/ellipse/shapes
  in full f64; others via point methods), modify, annotate, insert, view,
  inquiry, layers, groups, clipboard, layout.
- Entity apply_grip impls updated for GripApply::{Absolute,Translate}(DVec3).
- Construction sites (EntityTransform/CmdResult/DynAnchor) emit DVec3;
  wire-preview boundaries cast to f32 only at the GPU edge.
- Modify/annotate commands keep internal Vec3 geometry where precision is
  non-critical, casting at the DVec3 boundary; key draw commands store f64.

Builds clean (no warnings). Visual verification of crosshair/snap/grip at
extreme UTM zoom pending.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-24 03:51:50 +03:00
Hakan SevenandClaude Opus 4.8 f860fb39c2 feat(plugin): wrap external-plugin calls in a panic guard
A buggy external plugin that panicked anywhere — during load (version,
register, manifest), during ribbon build / dispatch, or inside an
interactive command (prompt, on_point, on_enter, on_object_pick) — took
the host down with it (#134). Run every cdylib entry point under
catch_unwind so the host stays up: the plugin's call is treated as
inert (load fails, dispatch returns "didn't handle it", interactive
hooks resolve to Cancel) and a "Plugin <hook> panicked: …" line lands
in the command line on the next tick. (#145)

Scope is Rust panics across the cdylib boundary, not genuine UB —
real isolation (segfault-proof) still requires out-of-process plugins.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 20:17:37 +03:00
Hakan SevenandClaude Opus 4.8 707c143212 feat(plugin): object-pick acquisition for interactive commands
Extend InteractiveCommand with needs_object_pick / on_object_pick so a
plugin tool can prompt the user to pick an existing entity (handle + point)
rather than a free point — the structure-pick path Storm Sewer's SS_PIPE
needs to connect existing structures. The host adapter delegates to the
internal entity-pick flow; over --serve the pick is fed as a hex handle.
Stays API v2 (no released plugin implements InteractiveCommand yet).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 01:43:53 +03:00
Hakan SevenandClaude Opus 4.8 931eb908a9 feat(plugin): InteractiveCommand hook for click-to-place (API v2)
Plugins could dispatch commands but not register interactive (click-to-
place) tools — the gap mf4633 flagged on #100 for Storm Sewer's SS_INLET
/ SS_PIPE. Add an InteractiveCommand trait + CommandStep to ocs_plugin_api
and HostApi::start_interactive; a host adapter bridges it to the internal
CadCommand, so a plugin tool drives the host's point-collection flow.

Hybrid by construction: the same command works by clicking in the viewport
AND by feeding coordinates over --serve (run "CMD x,y x,y"). Adding a
HostApi method changes the contract vtable, so API_VERSION bumps to 2 —
v1 plugin binaries are now refused at load.

Part of the #100 extensibility epic.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 21:43:35 +03:00
Hakan SevenandClaude Opus 4.8 eb53e445ab refactor(plugin): drop built-in plugins, external-only
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]>
2026-06-17 15:50:52 +03:00
Hakan SevenandClaude Opus 4.8 5ccf5cfe36 feat(plugin): lift host surface behind a HostApi trait (phase 1b)
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]>
2026-06-17 11:31:05 +03:00
Hakan SevenandClaude Opus 4.8 a3b8acb59e feat(plugin): XDATA read/write/remove helpers on HostSession
Plugins persisted domain data via the raw acadrust::xdata API with
boilerplate and no APPID registration. Add read_record / write_record /
remove_record keyed by entity handle; write_record replaces an existing
record for the same app and registers the application in the APPID table
so the data round-trips through DWG/DXF for other CAD apps.

Part of the #100 extensibility epic (surfaced in #106).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 10:28:12 +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