1737 Commits
Author SHA1 Message Date
Hakan Seven 6fdd090176 fix(ui): restore start actions and web copy
Route About info through browser clipboard on WASM.
Let Start-page links reach the existing command allowlist.

Refs #707
Refs #738
2026-08-11 13:15:09 +03:00
Hakan Seven 70f65270aa refactor(properties): remove detail sections 2026-08-11 12:49:28 +03:00
Hakan Seven de6f6c933b fix(acis): use kernel angle policy 2026-08-11 12:49:20 +03:00
Hakan Seven c00ee3d924 fix(acis): use source fit tolerance 2026-08-11 10:17:22 +03:00
gianlucafiore efc881d5bc Merge remote-tracking branch 'upstream/main' into feature/plotstyle-color-picker 2026-08-10 20:00:13 -03:00
gianlucafiore 97c904796e fix(plot): preserve inherited ACI for block plotting 2026-08-10 19:57:24 -03:00
Karim Jerbi f3d8904fc7 fix(alias): only advance the migration marker after the merge persists
Advancing ocad.pgp.version (or the web counterpart) could succeed while the
merged alias table write failed, so a later launch skipped migration against a
stale file and permanently lost the new aliases. Gate the version write on the
alias-table write (native fs::write / web set_item) succeeding.
2026-08-10 23:49:43 +01:00
Karim Jerbi 7013aa8cbb fix(alias): migrate new default aliases into existing profiles
Existing installs only got newly-added default aliases when storage was
absent; load_aliases parsed an already-present ocad.pgp or web alias table
verbatim, so R/RA/RE/REA remained unknown to profiles seeded before version 2.

Introduce a versioned default merge: track the default-table version each
profile has seen (wordless ocad.pgp.version marker / localStorage key) and
fold in only the aliases introduced after that version, never overwriting
user-defined mappings or resurrecting deliberately removed aliases. On first
migration the merged table and the advanced marker are persisted so later
launches skip the already-delivered aliases.
2026-08-10 23:39:46 +01:00
Karim Jerbi de1c1798a0 fix(scene): cfg-gate test-only refresh_pending_any to silence dead-code warning 2026-08-10 23:26:27 +01:00
Karim Jerbi 319710f36b test(commands): alias R routes like REDRAW 2026-08-10 23:26:27 +01:00
Karim Jerbi d2e18ead38 feat(aliases): R/RA/RE/REA for REDRAW/REGEN; register verbs 2026-08-10 23:26:26 +01:00
Karim Jerbi 692bb3babf feat(commands): REDRAW/REDRAWALL/REGEN/REGENALL 2026-08-10 23:26:26 +01:00
Karim Jerbi 7ace7fef9e feat(render): per-viewport REDRAW force_rasterize through builders to prepare gate 2026-08-10 23:26:26 +01:00
Karim Jerbi df103575b9 feat(scene): REDRAW refresh request resolved to per-viewport instance ids 2026-08-10 23:26:26 +01:00
gianlucafiore e2e4bfdcd4 feat(plotstyle): improve CTB editor workflow 2026-08-10 19:09:40 -03:00
gianlucafiore d22d0a272c fix(linetype): preserve patterns for basic curve entities 2026-08-10 15:10:42 -03:00
gianlucafiore 1cd1b89250 Merge pull request #729 from gianlucafiore/fix/705-grip-dynamic-input
Add dynamic input and precision snapping to 2D grip editing
2026-08-10 13:30:27 -03:00
gianlucafiore 86d7226df6 feat(grips): add dynamic input and precision snapping 2026-08-10 13:26:35 -03:00
Hakan Seven c943dfe391 fix(render): draw spatial curves and widths 2026-08-10 16:30:43 +03:00
Hakan Seven ac8183ec91 refactor(cad): use direct stack dependencies 2026-08-10 15:03:31 +03:00
Hakan Seven 96be8214f9 feat(tess): use angular sampling
Route curve, hatch, sweep, loft, surface, edge, and isoline tessellation through the shared kernel policy.
2026-08-10 12:12:16 +03:00
gianlucafiore f607422c9d Merge pull request #718 from gianlucafiore/feature/cad-color-picker
Add CAD indexed color picker with recent colors
2026-08-10 06:08:13 -03:00
Hakan Seven dc998fe46b refactor(tess): move solid sampling to kernel
Remove local face, edge, spline, sweep, loft, and silhouette
samplers. Meshes and overlays share the kernel tolerance and edge
schedule.
2026-08-10 10:18:53 +03:00
gianlucafiore 4697e8cc33 feat(color): add CAD indexed color picker 2026-08-09 23:40:55 -03:00
Hakan SevenandClaude Opus 5 60f5bca0d4 fix(tess): sample a solid's faces as finely as the edges drawn over them
The shaded surface of a curved solid was visibly coarser than its own
wireframe: the wire ran along the true rim while the facets cut inside it.

Two things were wrong, and only one was the tolerance.

`facet_res` is a resolution multiplier — the same one `scale_lod` divides the
fallback sampler's chord fraction by — and the kernel path was using it as a
sag in world units. At the default that asks for a whole unit of departure, so
nothing smaller than a metre subdivided at all and a face was only ever as
round as its own boundary. It is a fraction of each surface's own radius now,
which carries no assumption about the drawing's units.

And it is the *edges'* fraction, shared from `EDGE_CHORD_FRAC` rather than
copied. Those edges are drawn over these faces; sampling the two differently
is what put the wire across a facet instead of along its corners. `facet_res`
is deliberately not applied either, since the edges it must line up with are
built once at highest detail and never scaled.

A torus is measured by its tube rather than its ring — the tube is the tighter
bend, and sampling to the ring leaves the section a hexagon.

The primitive-volume test claimed a mesh can never read over its true size.
That holds for a convex solid, where every chord lies inside; a torus is not
one, and across the inside of its tube the chords fall outside the material.
It passed before only because the sampling was too coarse to show it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 03:17:12 +03:00
Hakan SevenandClaude Opus 5 afb4fd4733 fix(tess): sample a curve against its own size, not an absolute length
Round things came out with too few sides in drawings measured in metres, and
zooming in did not help.

The chord tolerance was floored at five thousandths of a world unit. That is
a fine sampling for a part measured in millimetres and a coarse one for a
building measured in metres: a ten-centimetre pipe got about ten sides and a
five-centimetre bolt about seven. The floor was also what stopped the frame
asking for better, so closing the camera on one changed nothing.

A chord height is an absolute length, so any fixed value carries an assumption
about the drawing's units. Bounded against the curve's own radius instead,
which carries none. The two ends are really segment counts: a circle gets at
least about fifty sides and never more than about a hundred and sixty,
wherever the camera is and whatever the drawing is measured in. Between them
the frame still decides, so a curve fills in as the camera closes on it.

Straight runs keep the frame's own tolerance — there is no curvature to
sample, and two ends are all a line has.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 02:44:52 +03:00
Hakan SevenandClaude Opus 5 35a2714025 fix(camera): keep the eye's coordinates out of the view basis
A drawing placed at UTM shimmered as the camera moved.

The relative-to-eye path exists so the large eye translation never enters an
f32 matrix, and it zeroed the translation column to make sure. But the basis
was built first, by `look_at(eye, target)` with both positions cast to f32 —
and at (639792, 4517057) that grid is half a metre across in y. Rounding both
ends and then subtracting them left a direction that changed as the camera
moved, so the whole view basis jittered. Zeroing the translation afterwards
came too late to help: the damage was in the rotation.

It never needed the positions. The eye is `target + rotation·Z·distance`, so
the direction between them is the rotation's own Z — a unit vector carrying no
coordinate at all.

Two tests. Where the camera stands must not reach the matrix, which is the
property being restored; and at the origin, where the old construction had no
large coordinates to lose, the two must agree exactly — which is what says
this kept the same basis rather than turning the view round.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 02:36:28 +03:00
Hakan SevenandClaude Opus 5 7c9366e362 fix(acis): read a lifted body at a tolerance a real file can meet
Faces were being dropped for sitting a nanometre off their own plane.

An edge is shared by two faces and in a real file cannot lie exactly on both:
the two surfaces were fitted separately and written to finite precision. Asked
for exactness, the kernel decides the edge is not on its plane, declines to
project it, and the face goes undrawn — silently, since a face that cannot be
expressed is reported as absent rather than as an error.

A micrometre in a drawing measured in metres recovers twenty-six walls of one
building. Loosening further buys almost nothing — a hundredth of this finds
one more face in sixty thousand — and past that it would start accepting
geometry that really is wrong, so it stops here.

With the tube fix that came with the kernel bump, VILLA_NEU_IFC3.dwg now
meshes 59015 of its 59033 faces, from 58922.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 02:23:58 +03:00
Hakan SevenandClaude Opus 5 5a2aa13b74 fix(acis): fill a planar face's holes in the fallback sampler too
A face with a hole came out with the hole solid and the material around it
missing. Handle 176 of Annotation-2D-Mesh-Solid-BIM.dwg is one.

`tess_plane_face` read `face.first_loop()` and fan-triangulated it, on the
grounds that holes were the kernel path's job. Two things make that wrong.
Nothing says the outer loop is listed first, so when a hole came first the
hole got filled and the boundary was ignored. And the kernel path does not
always win: when its mesh is incomplete, `tessellate_acis` runs both and keeps
whichever produced more triangles — which is not a measure of being right.

Every loop is read now, flattened into the plane's own frame, and triangulated
with the widest ring bounding and the rest cutting into it — the same rule the
kernel uses, and by area rather than by listing order.

This is interim. The fallback should not exist at all: the kernel already
lifts these bodies with no loss and meshes ten of handle 176's fourteen faces.
The four it drops are planar rectangles whose boundary projects with one
edge's `v` mirrored, so the ring self-crosses and ear clipping declines it —
a `pcurve` fault, not a missing feature. Fixing that makes the mesh complete,
and a complete mesh means the fallback never runs and can go.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 01:56:57 +03:00
Hakan SevenandClaude Opus 5 c668b2f665 fix(acis): place a lifted solid where its body transform says
Solids in a BIM export collapsed onto the origin — every one the right shape,
all in the same place.

ACIS keeps a body's geometry in its own local frame and records where it sits
in a separate `transform` record. Both paths that drew ACIS before this
applied it; the kernel path I put in their place did not, so every solid drew
at its local origin. A file authored in absolute coordinates carries no
transform and looked fine, which is why it took a placed one to show.

The mesh now goes through `finalize_mesh`, which already applied the placement
for the fallback sampler and already split positions into the coarse and fine
pair. That fixes the fault and deletes the hand-rolled copy of the splitting
next to it.

The convention is the part worth testing and the part that invites a mistake:
ACIS treats points as row vectors, `p' = scale·(p·M) + T`, so the stored 3×3
is indexed transposed from a column-vector multiply and the translation is not
scaled. Getting the first wrong mirrors a solid instead of moving it; the
second puts it at twice its offset, which reads as a plausible position and is
the wrong one. Both are covered.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 01:41:11 +03:00
Hakan SevenandClaude Opus 5 273f482aee refactor: retire the solid3d feature — the web build gets solids too
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 <[email protected]>
2026-08-10 01:28:25 +03:00
Hakan SevenandClaude Opus 5 f14ef54528 fix(build): restore the no-default-features build, and drop lzma-sys
Two things the truck removal broke without saying so, both found by auditing
what is left in the manifest rather than by anything failing.

The cfg gate on `acis_export` ended up on `acad_to_render` instead, when that
module was put back after being deleted by mistake. `acad_to_render` is what
every entity converts through, so gating it left the wasm build — which is
what `--no-default-features` stands in for — unable to resolve it from thirty
places. It built at the commit before the migration and builds again now.

And deleting TRUCK_CHORD_FRAC took the constant but left its doc comment and
its `#[cfg]` behind, so the attribute landed on the next constant down and
gated a boundary-sampling tolerance that has nothing to do with solids.

lzma-sys was the last dependency in the manifest with nothing using it: no
code references it and nothing else in the tree pulls it in. The `solid3d`
feature it hung off still gates four places, so the feature stays.

Everything else declared is used. `getrandom` and `iced_widget` look unused
by grep and are not — the first is declared to turn on the feature the wasm
target needs from a crate half the tree pulls in, and the second is a
`[patch]` entry rather than a dependency at all.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 01:22:08 +03:00
Hakan SevenandClaude Opus 5 8c7ebdc9f1 refactor: drop truck, and evaluate every curve through the kernel
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.

NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.

The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.

SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.

Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.

The two `automation` test failures are unchanged from before this and are not
caused by it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 01:08:50 +03:00
Hakan SevenandClaude Opus 5 1f642bb40a feat(model): build and read solids with the geometry kernel
Every 3-D solid OCS makes or opens now goes through cadkernel, which holds
analytic surfaces — a cylinder is a cylinder record, not a spline that looks
like one. That is what lets a solid built here be written back out as exact
ACIS instead of a triangulation, and a facetted export is a one-way door: the
next application to open the file would have no way back to the circle.

The Model tab's seven primitives, the Design-group booleans, 3DROTATE,
3DMIRROR, 3DALIGN, SLICE, SECTION, POLYSOLID and PYRAMID all come from the
kernel now, and `scene.solid_models` holds a `Body`. A mirrored solid needed
its own care before; the kernel reverses the loops on the way through, so it
comes back the right way out rather than lighting black.

EXTRUDE and REVOLVE sweep the profile `entities::curve` already defines, so a
circle, an arc-bulged polyline and a closed spline arrive as the same thing.
A circle extrudes into four cylinder patches rather than a run of chords, and
a profile turned about an axis becomes a cone, a sphere or a torus. A spline
profile has no analytic side wall and is refused rather than approximated.

ACIS goes both ways through the codec's own bridge: `lift` to read and
`append` to write. That drops the 679-line truck conversion and the planar-
only exporter, and the tessellator keeps its bespoke sampler as a fallback for
whatever the kernel cannot yet express — reported rather than passed off as
whole, since a mesh missing a wall looks finished.

truck-shapeops is no longer used and is gone from the manifest. truck-modeling
stays for now: fourteen files use it as a 3-D NURBS evaluator, which the
kernel has no answer for yet, and SWEEP and LOFT have no kernel operation
behind them either.

The two `automation` test failures are unchanged from before this and are not
caused by it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 00:32:34 +03:00
Hakan Seven 8016b79ddf fix(snap): align extension tracking
Require OTRACK crossings to include the active Extension ray and resolve Shift constraints consistently across preview, grips, and clicks.\n\nRefs #716
2026-08-09 23:55:06 +03:00
gianlucafiore af7613f1d8 Merge remote-tracking branch 'upstream/main' into fix/716-perpendicular-tracking 2026-08-09 11:43:08 -03:00
gianlucafiore 080a82306e Fix perpendicular and OTRACK snap interactions 2026-08-09 11:37:21 -03:00
Hakan Seven f57c258ada fix(snap): correct active intersection snapping 2026-08-09 13:27:51 +03:00
Hakan Seven 6a39b82a68 Merge pull request #712 from gianlucafiore/fix/704-active-intersection-snap
Fix active intersection snapping
2026-08-09 13:17:31 +03:00
Hakan Seven d80ee709ac fix(otrack): clear and localize tracking hint 2026-08-09 13:04:55 +03:00
Hakan Seven ae24f985f0 Merge pull request #714 from gianlucafiore/feature/tracking-reference-tooltip
Show active tracking reference tooltip
2026-08-09 12:57:50 +03:00
Hakan SevenandClaude Opus 5 1a37562a28 chore: take the stack with its solid layer and ACIS bridge
The kernel gained a B-rep layer — topology, analytic surfaces, surface
intersection, face splitting, point classification, booleans — and
acadifc gained the bridge that lifts an ACIS document into it and lowers
it back, provenance-driven so an untouched body saves byte for byte.

The reachability test is a compile-time one on purpose. A dependency
chain that stops resolving is caught where it breaks rather than the
next time somebody reaches for the far end of it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 11:26:57 +03:00
Hakan Seven 2f60dd0f59 refactor(i18n): group Fluent attributes 2026-08-09 11:12:47 +03:00
Hakan SevenandClaude Opus 5 f3c2e061e0 refactor(trim): find an extension's boundary with the kernel
EXTEND asked where a line reaches by matching over every Geo variant and
running its own line-line, line-circle and line-ellipse test — twice
over, once for a LINE and once for a polyline's terminal segment, about
two hundred and sixty lines between them. A boundary spline was walked
as sixty-four chords there rather than intersected, so an extend landed
on the chord nearest the spline instead of on the spline.

Both are one call now: shoot the segment's own infinite line and take
the nearest crossing on the side being extended. That is `cut_params`,
which every other cut in the file already goes through.

With those two gone, the sampled copy carried beside each boundary
spline had no remaining reader, so a spline boundary is now only ever
the exact NURBS. The line-ellipse adapter lost its last caller with it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 10:02:23 +03:00
Hakan SevenandClaude Opus 5 ca85856f52 refactor(hatch): clip the pattern against the boundary in the kernel
The pattern's lines were clipped by sorting their crossings with the
boundary and taking every other span. That is right where a line cuts a
boundary cleanly and wrong wherever it grazes one: a line through a
corner crosses twice in effectively one place, and from there every span
is inverted — the fill lands in the holes and the solid comes out empty.
A 1e-5 dedup on the crossings hid some of it and could not fix the rest,
because which crossings to merge is not the question.

The kernel divides the line by its crossings and then asks each span
whether its middle is inside, which costs a containment test apiece and
cannot get out of step. It also accepts a curved boundary rather than
only a polygon, which this call site does not need yet and a section
fill will.

What stays is the PAT frame — the family angle, spacing, origin offset,
the k range and the dash walk — which is a format convention rather than
geometry.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 09:59:19 +03:00
Hakan Seven 52799786f4 feat(i18n): complete locale catalogs 2026-08-09 09:44:54 +03:00
gianlucafiore a8c9e3dd0c Merge branch 'feature/tracking-reference-tooltip' 2026-08-08 23:23:06 -03:00
gianlucafiore a06e8a7a69 Show active tracking reference tooltip 2026-08-08 23:09:06 -03:00
gianlucafiore 17b4eafa3b Merge upstream/main into fix/704-active-intersection-snap 2026-08-08 22:13:54 -03:00
gianlucafiore 15727bbffe Fix active intersection snapping 2026-08-08 21:20:40 -03:00