From 273f482aee27a674aa346f6c797773803a85330b Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 10 Aug 2026 01:28:25 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20retire=20the=20solid3d=20feature=20?= =?UTF-8?q?=E2=80=94=20the=20web=20build=20gets=20solids=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The feature existed for one reason: truck's mesh and boolean crates reach `vtkio → xz2 → lzma-sys`, a C library that cannot cross-compile to wasm32. So the web build dropped it, and with it the Model tab, solid tessellation and ACIS import — all of which quietly did nothing in the browser. cadkernel is pure Rust and has no C dependency, so none of that holds any more. The feature gated exactly two things by the end, `acis_export` and the save-path sync that calls it, and both are as portable as everything around them. Removing it means the web build makes primitives, runs booleans, draws ACIS solids read from a file, and writes them back out as exact geometry — the same as the desktop build. Verified against all three: native, `--no-default-features`, and an actual `wasm32-unknown-unknown` check rather than an assumption about one. The docs claimed otherwise in four places and are corrected. `tessellation.md` in particular was organised around whether an entity "goes through truck", which is no longer a question anything can be asked — the three paths are now a kernel B-rep mesh, a curve sampled through `entities::curve`, or geometry emitted directly, and the per-entity table is updated to match. `index.html` no longer passes `data-cargo-no-default-features`; there are no features left to turn off. Co-Authored-By: Claude Opus 5 --- .github/workflows/pages.yml | 6 +-- Cargo.toml | 4 -- Trunk.toml | 6 +-- docs/native-vs-web.md | 22 +++++----- docs/tessellation.md | 87 ++++++++++++++++++++----------------- index.html | 7 +-- src/app/update/file.rs | 8 +--- src/scene/convert/mod.rs | 1 - 8 files changed, 68 insertions(+), 73 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 84b209b86..b2402643b 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -50,10 +50,8 @@ jobs: with: tool: wasm-bindgen@${{ steps.wasm-bindgen-version.outputs.version }} - # The rust in index.html sets `data-cargo-no-default-features`, - # so this drops the `solid3d` feature (truck-meshalgo → lzma-sys C deps - # that can't cross-compile to wasm). `--public-url` matches the project - # page sub-path: https://.github.io/OpenCADStudio/. + # `--public-url` matches the project page sub-path: + # https://.github.io/OpenCADStudio/. - name: Build web bundle run: trunk build --release --public-url /OpenCADStudio/ diff --git a/Cargo.toml b/Cargo.toml index f078a1ea8..293d4d7c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,10 +15,6 @@ members = [ [target.'cfg(windows)'.build-dependencies] winresource = "0.1" -[features] -default = ["solid3d"] -solid3d = [] - [dependencies] serde_json = "1" serde = { version = "1", features = ["derive"] } diff --git a/Trunk.toml b/Trunk.toml index 4deb7b037..9a7f9b6d4 100644 --- a/Trunk.toml +++ b/Trunk.toml @@ -8,9 +8,9 @@ [build] target = "index.html" -# Note: the `solid3d` feature is disabled via `data-cargo-no-default-features` -# on the rust in index.html (truck-meshalgo → lzma-sys C deps cannot -# cross-compile to wasm). +# The crate has no Cargo features: the geometry kernel is pure Rust and +# cross-compiles to wasm, so the web build gets the same solid modelling the +# native one does. [[hooks]] stage = "post_build" diff --git a/docs/native-vs-web.md b/docs/native-vs-web.md index 53290168d..3ba1e10a5 100644 --- a/docs/native-vs-web.md +++ b/docs/native-vs-web.md @@ -10,7 +10,7 @@ browser can't provide. This page lists the differences. | Area | Native (desktop) | Web (wasm) | |------|------------------|------------| | Windowing | Multi-window (`iced::daemon`) | Single window (`iced::application`); dialogs are in-canvas modals | -| 3D solid modeling | Yes | **No** — `solid3d` feature off | +| 3D solid modeling | Yes | Yes — the geometry kernel is pure Rust | | Hatch rendering | Yes | **No** — WebGL2 has no vertex-stage storage | | Fonts | Embedded stroke fonts + system TrueType + shaping + fallback | Embedded stroke fonts only | | File open | Native file dialog → path | Browser picker → bytes | @@ -22,15 +22,16 @@ browser can't provide. This page lists the differences. ## Details -### 3D solid modeling — web: disabled -The `solid3d` Cargo feature (default on) gates `truck-meshalgo` and -`truck-shapeops`, which pull `vtkio → xz2 → lzma-sys`, a C library that can't -cross-compile to `wasm32`. The web build compiles with `--no-default-features`, -so on the web: -- The Model tab's solid primitives and boolean operations are no-ops. -- Solid tessellation and ACIS (SAT) import produce no geometry. +### 3D solid modeling — web: the same as native +It used to be off. The modelling kernel was truck, whose mesh and boolean +crates reach `vtkio → xz2 → lzma-sys` — a C library that cannot cross-compile +to `wasm32` — so the web build dropped a `solid3d` feature and the Model tab, +solid tessellation and ACIS import all did nothing there. -2-D CAD is unaffected. +The kernel is now cadkernel, which is pure Rust and has no C dependency at +all, so none of that applies and the feature is gone. The web build makes +primitives, runs booleans, draws ACIS solids read from a file and writes them +back out as exact geometry, exactly as the desktop build does. ### Hatch rendering — web: not drawn The batched hatch pipeline binds a read-only storage buffer in the vertex @@ -97,8 +98,7 @@ folder and load again on the next desktop launch. ## Build & deploy - Native: `cargo build --release --bin OpenCADStudio`. -- Web: `trunk build --release --public-url /OpenCADStudio/` (the rust `` - in `index.html` sets `data-cargo-no-default-features`, dropping `solid3d`). +- Web: `trunk build --release --public-url /OpenCADStudio/`. `.github/workflows/pages.yml` builds and deploys to GitHub Pages on every release. No COOP/COEP headers are needed because the web build is single-threaded. diff --git a/docs/tessellation.md b/docs/tessellation.md index 5640b2f32..8a6815eba 100644 --- a/docs/tessellation.md +++ b/docs/tessellation.md @@ -1,45 +1,50 @@ # Entity tessellation paths -How each acadrust `EntityType` becomes drawable geometry in OpenCADStudio, and -whether it passes through the [truck](https://github.com/ricosjp/truck) modeling -kernel. +How each acadrust `EntityType` becomes drawable geometry in OpenCADStudio. -There are **two distinct meanings of "through truck"** in the codebase, and they -must not be conflated: +There are **three ways** an entity gets there, and they must not be conflated: -1. **truck B-rep meshing** — the entity is rebuilt as a truck `Shell`/`Solid` - and triangulated by `MeshableShape::triangulation()`. This is the only path - that produces filled *mesh* triangles from a kernel. -2. **truck curve topology** — the entity is expressed as a truck `Edge`/`Wire`/ - `Vertex` and sampled into line segments by truck's `ParameterDivision1D`. It - touches truck *topology* but never triangulates a surface; the output is a - polyline. +1. **Kernel B-rep mesh** — the ACIS document is lifted into a cadkernel `Body` + and each face triangulated in its own surface's parameter space. The only + path that produces filled *mesh* triangles from analytic surfaces. +2. **Curve-sampled** — the entity's geometry is read through + `entities::curve::entity_curve`, which is where each type's curve is defined + once, and tessellated to a chord tolerance. Output is a polyline; no surface + is involved. +3. **Direct** — points, segments or triangles emitted straight from the + entity's own fields. -Everything else is **direct**: points/segments/triangles are emitted straight -from the entity's own data without any truck object. +The distinction between (2) and (3) is worth keeping because (2) is a single +definition shared with everything else that needs an entity's geometry — snap +candidates, EXTRUDE and REVOLVE profiles, hatch boundaries, clip outlines. A +circle drawn on screen and a circle handed to the Model tab come from the same +place and cannot disagree. Entry points: `src/scene/convert/tess.rs` (`tessellate_entity`, the per-entity -dispatcher) → `src/scene/convert/tessellate.rs` (`tessellate`, the `TruckObject` -router) and `src/scene/convert/truck_tess.rs` (curve sampling). +dispatcher) → `src/scene/convert/tessellate.rs` (`tessellate`, the +`RenderObject` router), `src/entities/curve.rs` (curve definitions and +sampling) and `src/scene/convert/curve_tol.rs` (the per-frame chord tolerance). ## Summary | Path | Entities | |---|---| -| **truck B-rep mesh** (Shell → `triangulation`, with a direct per-surface fallback) | `Solid3D`, `Region`, `Body`, `Surface` | -| **truck curve topology** (Edge/Wire → `ParameterDivision1D` sampling) | `Line`, `Arc`, `Circle`, `Ellipse`, `Spline`, `LwPolyline`, `Polyline`, `Polyline2D`, `Polyline3D`, `Point` — *non-thick variants only* | -| **direct** (no truck — segments/triangles emitted directly) | everything else | +| **Kernel B-rep mesh** (lift → per-face triangulation, with a direct per-surface fallback) | `Solid3D`, `Region`, `Body`, `Surface` | +| **Curve-sampled** (`entity_curve` → chord-tolerance tessellation) | `Line`, `Arc`, `Circle`, `Ellipse`, `Spline`, `LwPolyline`, `Polyline`, `Polyline2D`, `Polyline3D` — *non-thick variants only* | +| **Direct** (segments/triangles emitted straight) | everything else | > The Model/Design-tab primitives (BOX, SPHERE, CYLINDER, CONE, WEDGE, TORUS, -> PYRAMID, extrude/revolve/loft/sweep) are also true truck B-rep meshes -> (`truck_tess::tessellate_solid`), but they are created as `Solid3D` -> placeholders carrying a truck B-rep, not as their own `EntityType`. +> PYRAMID, EXTRUDE, REVOLVE) are true kernel B-reps too, cached on the Scene as +> `Body` and meshed the same way — but they are created as `Solid3D` +> placeholders carrying that body, not as their own `EntityType`. SWEEP and +> LOFT produce a mesh only, with no B-rep behind it. -The only call site of `shell.triangulation()` for an imported entity is -`acis_to_truck::tessellate_sat_truck`. The four ACIS solids dispatch through -`solid3d_tess::tessellate_acis`, which tries that truck kernel first and falls -back to a bespoke per-surface LOD sampler (`tessellate_sat_lods`) when truck -cannot rebuild a face — so their path is **truck-with-direct-fallback**. +ACIS solids dispatch through `solid3d_tess::tessellate_acis`, which lifts the +document into the kernel first (`acis_kernel::tessellate_sat`) and falls back +to a bespoke per-surface LOD sampler (`tessellate_sat_lods`) when the kernel +cannot express a face — so their path is **kernel-with-direct-fallback**. The +fallback is not silent: a mesh whose faces did not all lift is marked +incomplete, because one missing wall looks exactly like a finished solid. Curved-surface tessellation density is radius-relative (`CURVE_REL_TOL`, a fraction of the surface radius) so a cylinder's facet count matches the @@ -49,40 +54,40 @@ circle/arc wire tessellation instead of exploding on large radii. | Entity | Output | Path | Notes | |---|---|---|---| -| Arc | wire | direct (truck curve topology) | non-thick: truck `Edge` (`builder::circle_arc`) sampled via `ParameterDivision1D`; thick: direct `Lines` | +| Arc | wire | curve-sampled | non-thick: sampled from the arc's own curve; thick: direct `Lines` plus the swept wall | | AttributeDefinition | wire | direct | routes through the Text/MText LFF glyph stroke pipeline | | AttributeEntity | wire | direct | same as AttributeDefinition; values supplied per Insert | | Block | none | n/a | block-definition sentinel; not tessellated (referenced via Insert) | | BlockEnd | none | n/a | block-definition end marker; no output | -| Body | mesh | truck-with-direct-fallback | 3D ACIS body → truck `Shell` → `triangulation`; fallback per-surface sampler | -| Circle | wire | direct (truck curve topology) | non-thick: truck `Wire` of two half-circle edges; thick: direct `Lines` | +| Body | mesh | kernel-with-direct-fallback | 3D ACIS body lifted into a kernel `Body`; fallback per-surface sampler | +| Circle | wire | curve-sampled | non-thick: sampled from the circle's own curve; thick: direct `Lines` | | Dimension | wire | direct | baked-block path recurses on `D###` sub-entities; synthesis path emits lines/arrows/LFF text | -| Ellipse | wire | direct (truck curve topology) | truck `Wire`/`Edge` over a `BSplineCurve`, `ParameterDivision1D` sampled | -| Face3D | both | direct | edge `Lines` + direct fan-triangulated `fill_tris`; no truck B-rep | +| Ellipse | wire | curve-sampled | sampled from the ellipse's own curve | +| Face3D | both | direct | edge `Lines` + direct fan-triangulated `fill_tris`; no B-rep | | Hatch | both | direct | boundary outline not emitted to the wire set (#131 OOM); fill rasterized on GPU | | Insert | wire | direct | expands block children and tessellates each via its own path; XCLIP filter applied | | Leader | wire | direct | leader path + arrowhead + landing, direct `Lines` | -| Line | wire | direct (truck curve topology) | non-thick: truck `Edge` (`builder::line`); thick: direct `Lines` | -| LwPolyline | wire | direct (truck curve topology) | `plinegen` true: truck `Contour` with bulge arcs; else direct `SegmentedLines` | +| Line | wire | curve-sampled | non-thick: its two ends; thick: direct `Lines` plus the swept wall | +| LwPolyline | wire | curve-sampled | `plinegen` true: sampled from its own curve, bulges kept as arcs; else direct `SegmentedLines` | | Mesh | both | direct | SubD mesh: edge `Lines` + direct fan-triangulated `fill_tris` | | MLine | wire | direct | spine + offset lines + caps, direct `Lines` | | MText | wire | direct | wrap-aware multi-line LFF glyph layout; inline formatting codes | | MultiLeader | wire | direct | leader + landing + LFF text + frame + fill | | Ole2Frame | wire | direct | bounding rectangle + diagonal cross | -| Point | wire | direct (truck topology) | truck `Vertex` → cross marker sized by PDSIZE | +| Point | wire | direct | a position → dot or cross marker sized by PDSIZE | | PolyfaceMesh | both | direct | face list: closed-polyline edges + direct fan-triangulated `fill_tris` | | PolygonMesh | both | direct | M×N grid wireframe + direct fan-triangulated `fill_tris` | -| Polyline | wire | direct (truck curve topology) | heavy 3D polyline; truck `Contour` + bulge arcs | -| Polyline2D | wire | direct (truck curve topology) | 2D polyline with bulge; truck `Contour` | -| Polyline3D | wire | direct (truck curve topology) | linear-edge truck `Contour`, no bulge/thickness | +| Polyline | wire | curve-sampled | heavy 3D polyline; sampled from its own curve, bulges kept as arcs | +| Polyline2D | wire | curve-sampled | 2D polyline with bulge; sampled from its own curve | +| Polyline3D | wire | curve-sampled | straight edges, no bulge or thickness | | RasterImage | wire | direct | boundary rectangle / clipping polygon | | Ray | wire | direct | two-point `[base, base + dir×1e6]`, no sampling | -| Region | mesh | truck-with-direct-fallback | 2D planar ACIS body; same truck path as Solid3D | +| Region | mesh | kernel-with-direct-fallback | 2D planar ACIS body; same kernel path as Solid3D | | Seqend | none | n/a | vertex-sequence terminator sentinel; no output | | Shape | wire | direct | small diamond marker at the insertion point | | Solid | wire | direct | 2D SOLID: four quad edges as direct `Lines` | -| Solid3D | mesh | truck-with-direct-fallback | 3DSOLID: parse SAT/SAB → truck `Shell` → `triangulation`; fallback sampler | -| Spline | wire | direct (truck curve topology) | NURBS/BSpline truck `Edge`, `ParameterDivision1D` sampled | +| Solid3D | mesh | kernel-with-direct-fallback | 3DSOLID: parse SAT/SAB → lift into a kernel `Body`; fallback sampler | +| Spline | wire | curve-sampled | NURBS sampled through the kernel's space curve, refined where it bends | | Table | both | direct | cell fills (`fill_tris`) + LFF cell text + grid lines | | Text | wire | direct | LFF-font stroked glyph polylines | | Tolerance | wire | direct | feature-control frame grid + per-cell LFF symbols | diff --git a/index.html b/index.html index 4e48eb498..662a9e108 100644 --- a/index.html +++ b/index.html @@ -39,9 +39,10 @@ } @keyframes ocs-spin { to { transform: rotate(360deg); } } - - + + diff --git a/src/app/update/file.rs b/src/app/update/file.rs index ce60d66b0..08258172d 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -224,9 +224,8 @@ impl OpenCADStudio { /// geometry (EXTRUDE/REVOLVE/SWEEP/LOFT/boolean results) an exact modeler /// body derived from its B-rep, so the written DWG/DXF carries real /// 3-D geometry other CAD apps can open instead of an empty data stream. - /// Curved solids that the exact planar path can't yet express are left - /// untouched (handled by the NURBS path). - #[cfg(feature = "solid3d")] + /// A body holding something the kernel has no ACIS record form for is + /// left untouched rather than written out half-complete. fn sync_solid_models_to_acis(&mut self, i: usize) { use acadrust::EntityType; let scene = &mut self.tabs[i].scene; @@ -256,9 +255,6 @@ impl OpenCADStudio { } } - #[cfg(not(feature = "solid3d"))] - fn sync_solid_models_to_acis(&mut self, _i: usize) {} - /// Snapshot the persisted UI preferences from live state. pub(in crate::app) fn current_settings(&self) -> crate::app::settings::UserSettings { crate::app::settings::UserSettings { diff --git a/src/scene/convert/mod.rs b/src/scene/convert/mod.rs index 4e216f97c..b9acb433d 100644 --- a/src/scene/convert/mod.rs +++ b/src/scene/convert/mod.rs @@ -1,5 +1,4 @@ pub mod acad_to_render; -#[cfg(feature = "solid3d")] pub mod acis_export; pub mod acis_kernel; pub mod dgn_linestyle;