The previous commit's Apple Event handler only ever catches an open
sent to an already-running process. A cold double-click (app not
running yet) still failed: every registration point tried inside the
real binary — before app::run(), on the Subscription's first poll,
even the literal first statement of main() — reliably caught a warm
open but never the original cold-launch event. A raw
AEInstallEventHandler in a separate, minimal, non-AppKit process (no
winit/iced, just a manual CFRunLoopRunInMode pump) still missed it
too, ruling out "the real binary's heavy dependency graph loading
before main() wins the race" as the explanation.
What does reliably catch a cold launch, verified against real
Finder-equivalent opens: a from-scratch NSApplication with its own
delegate implementing application:openURLs: — in a process where we
own setDelegate: from the start, with no contention over the single
delegate slot winit already claims. That has to be a separate process
from the real GUI binary (only one -[NSApplication run] call can own a
process), so src/bin/ocs_launcher.rs is now the .app's actual
CFBundleExecutable: a small, dependency-light binary (no iced, wgpu,
or cadkernel) whose only job is to catch the open and relay it.
Relaying by replacing this process's image (exec, same PID) or by
spawning a child and exiting turned out to both be dead ends: Launch
Services tracks "the running instance of this bundle" by the
Application Serial Number it handed out to whichever process it
actually launched — this launcher, not whatever it execs into or
spawns. Once the launcher's own ASN stopped pointing at a live,
listening process, every later open Finder tried to deliver silently
went nowhere (measured with lsappinfo against a real second "Open
With", not assumed). So the launcher now stays running for the app's
session — invisible, via NSApplicationActivationPolicyAccessory, since
it owns no window — and relays every open (cold or warm) over the same
channel a second CLI invocation already uses: io::single_instance.
That relay only worked once single_instance's rendezvous key stopped
being computed from the running executable's own path. That was fine
when one binary always did the job, but the launcher and the real
binary are different executables and independently computed different
ports, so they could never find each other. Anchor the key on the
enclosing .app bundle instead, shared by both.
packaging/build_macos_signed.sh and .github/workflows/release.yml now
build ocs_launcher and place it at Contents/MacOS/OpenCADStudio (the
CFBundleExecutable Info.plist already names), with the real GUI binary
renamed to Contents/MacOS/OpenCADStudio-App alongside it.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Double-click / "Open With" never puts the file on argv — Finder always
delivers it as a kAEOpenDocuments Apple Event instead, which nothing in
this app listened for, so it just showed "cannot open files in this
format" even though the file association itself was registered
correctly.
The standard fix, implementing application:openURLs: on this app's own
NSApplicationDelegate, isn't available here: winit already installs
its own delegate to own window/lifecycle events, NSApplication has
exactly one delegate slot, and winit's class doesn't implement the
open-document method.
This bypasses the delegate protocol and registers directly with the
lower-level Apple Event Manager (AEInstallEventHandler) instead — the
same mechanism AppKit's delegate forwarding is itself built on, and
independent of who owns the delegate. Registration happens on the
iced Subscription's first poll rather than in main() before app::run()
— registering earlier gets silently overwritten by winit's own
NSApplication startup, confirmed by testing against a real `open -a`
rather than assumed from Apple's docs.
This handles a file opened while the app is already running. A cold
launch (app not running yet) needs a different mechanism, in the
following commit.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
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.
Keep adaptive sampling in both text shaders with compact sampling loops. Validate shader derivatives and existing text rendering, including GPU pipeline creation.
Closes#964
Delete the thin re-export file; src/app/doc_api.rs now uses ocs_doc_api::convert
directly (use ocs_doc_api::convert + convert::* call sites). Remove the
mod doc_api_convert registration from src/app/mod.rs.
The conversion logic already lives in the crate (src/convert.rs) from the prior
move; the shim was redundant. 623 host lib + 38 doc_api tests green.
Move the 503-line host-independent entity-conversion module (acadrust EntityType
<-> DTO conversions, transform_entity_geometry, entity_to_profile_curves,
bulge_arc_segment, entity_bounds, entity_kind_name, points_aabb) from
src/app/doc_api_convert.rs into crates/ocs_doc_api/src/convert.rs (host feature).
The host file is now a thin re-export (pub use ocs_doc_api::convert::*) so all
existing crate::app::doc_api_convert::* paths keep working unchanged. Zero
coupling risk (the module only touches acadrust + crate DTOs).
623 host lib + 8 crate + 3 consistency green.
Replace the mermaid flowchart (hard to read raw) with a clean ASCII/text diagram
tracing the same flow from the single source of truth (spec/entities.toml +
layouts.json) through codegen, facade, wire, transport, executor, and the host
backend/Scene, plus a layer-by-layer legend.
- layouts.json: append the 6 missing ops (CreateDimensionRadius/Diameter/Angular,
CreateAttributeDefinition, CreateTable, CreateDimensionAngular2Ln); regenerate
binding_schema.json + api_reference.md wire vocabulary (all 28 ops).
- Helix bounds: bound the evaluated spline control polygon (was a degenerate
single-start-point AABB).
- points_aabb: empty -> Geometry::Empty('entity has no points to bound'), not
UnknownId (the entity exists; don't misclassify as stale/deleted).
- mass_properties: accuracy-first fine tessellation + graceful degradation to the
approximate meshes.metrics value when the cold-tess budget is exhausted (large
distinct-solid batches no longer hard-fail).
- IMPLEMENTATION.md: Volume/Centroid rows + accuracy paragraph + test count (618)
consistent with the final design.
- 618 host lib + 8 crate + 3 consistency green.
- mass_properties: query path now uses fine mesh_body(0.1, 1e-4) for near-analytic
volume/centroid; the render-mesh metrics cache (coarse display LOD) no longer
shadows query accuracy. Memoized per (handle, epoch); cold-tess budget kept.
- IMPLEMENTATION.md: accuracy item done.
- Test accurate_volume_centroid_sphere_and_cube (sphere within 0.5% of 4/3*pi*r^3,
centroid at centre, cube exact) -> 617 host lib green.
Capture the rendered canvas during the clean frame instead of requesting synchronous GPU buffer readback. Preserve embedded previews and allow saving when canvas readback is denied. Add a browser save and reopen regression check.
Fixes#1021
The loft/attributes/viewport mock methods were appended after the impl block's
closing brace, orphaning add_vertex..finalize_op and leaving a duplicate brace.
Move the impl close to the true end in both tests/mock_backend.rs and
examples/common/mod.rs. Crate tests (8) + examples compile green again.
Add tests/spec_wire_consistency.rs: (a) every op/query name in spec/entities.toml
maps to an existing Operation/Query variant; (b) both enums are append-only
relative to a recorded variant-order baseline (bincode discriminant stability).
Adds toml to dev-dependencies. 3 tests green.
Add Arc/Ellipse/Spline/Ray/XLine arms to the test + example mock add_curve
kind matching and the example curve_bounds, so the crate's unit tests and
examples compile against the Phase 2 curve families.