Commit Graph
833 Commits
Author SHA1 Message Date
Hakan Seven 5b9fffdfba fix(hatch): clip fill with kernel mesh
Resolve boundary topology before rendering so corner-aligned pattern rows cannot flip shader parity. Chunk indexed geometry to device buffer limits and retain the texture compatibility transport.

Refs #489
2026-08-13 00:29:03 +03:00
Hakan Seven 448869ac35 perf(view): share viewport render caches 2026-08-12 20:38:18 +03:00
Hakan Seven 0df2356243 fix(view): correct 3D mesh interaction
Sync projection controls from the active camera, preserve the UCS icon in perspective views, and use visible or selected 3D geometry for orbit pivots. Dense mesh vertex grips remain hidden.
2026-08-12 19:49:52 +03:00
Hakan Seven 5e8221f8f1 perf(render): keep solid meshes resident
Keep all solid geometry GPU-resident during navigation. Use vertex-rate instancing, compact untextured vertices, vertex-cache ordering, and cached silhouette sources without culling geometry.
2026-08-12 16:02:10 +03:00
Hakan Seven 15ab027d70 fix(scene): preserve viewport settings
Reuse full VPort records during camera and tile synchronization so
navigation cannot replace file-defined display state with defaults.

Honor image, ground, IBL, and sun-backed sky parameters in
background rendering.
2026-08-12 13:11:12 +03: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 ca331fff48 fix(render): scale solid tessellation
Derive one drawing-space chordal tolerance so large curves stay smooth while small solids avoid excessive detail.
2026-08-12 12:46:17 +03:00
Hakan Seven 7b4af0aa75 fix(render): share repeated block meshes
Keep placement transforms and bounds separate from immutable mesh data to prevent large drawings from exhausting memory or stalling selection.
2026-08-12 11:01:53 +03:00
gianlucafiore ff61164d8b fix: preserve current layer for hatches and DWG loading 2026-08-12 00:30:21 -03:00
gianlucafiore b8b66163d6 Merge pull request #745 from gianlucafiore/fix/dwgunits-annotation-scales
Fix viewport and annotation scales for drawing units
2026-08-11 22:48:27 -03:00
gianlucafiore cc7894cd92 fix: honor drawing units in annotation and viewport scales 2026-08-11 22:41:26 -03:00
Hakan Seven 4fd9b98da4 perf(render): instance block geometry
Share source buffers across block placements and render nested content with GPU instance transforms.\n\nChunk silhouettes and compact mesh material data so large drawings stay under device buffer limits and remain responsive.
2026-08-12 02:33:10 +03:00
Hakan Seven c4dfa7567d fix(model): correct primitive creation and grips 2026-08-12 00:03:27 +03:00
Hakan Seven 59ca0f693f feat(model): edit primitive history properties 2026-08-11 20:14:51 +03:00
Hakan Seven 4ac10187c2 fix(model): preserve solid history lifecycle 2026-08-11 20:10:23 +03:00
Hakan Seven d2993fd054 feat(model): add parametric solid grips 2026-08-11 19:59:24 +03:00
Hakan Seven e37b77e426 feat(model): persist solid creation history 2026-08-11 19:52:17 +03:00
Hakan Seven dc6dc2256d fix(model): sync solid render interaction 2026-08-11 19:39:01 +03:00
Hakan Seven a1f64d7663 fix(render): restore solid surface rendering
Keep viewport styles from disabling selected shaded modes.\n\nPack mesh shader inputs to stay within WebGL limits.
2026-08-11 15:44:40 +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 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 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 d22d0a272c fix(linetype): preserve patterns for basic curve entities 2026-08-10 15:10:42 -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
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
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 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 SevenandClaude Opus 5 9babf355b1 refactor(fillet): find the tangent arc in the kernel
FILLET enumerated its own candidates twice, once for a line against an
arc and once for two arcs, each with its own quadratic, its own four
sign combinations and its own conventions about which root meant what.
Both are the same construction: a circle of the given radius tangent to
a curve has its centre on one of that curve's offsets, so the fillet
centre is where an offset of one crosses an offset of the other.

That now comes from the kernel, which does it for any pair of straight
and circular curves and agrees with the corner formula where both apply.
What stays here is the part that genuinely belongs to the command —
which candidate the two picks meant, whether the touch lands on the
drawn part of an arc rather than elsewhere on its circle, and which side
of each entity to keep.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 01:37:11 +03:00
Hakan SevenandClaude Opus 5 f9a0d73dc6 refactor(hatch): take the boundary arrangement from the kernel
624 lines become 70. What went is the planar arrangement itself —
splitting segments at their crossings, welding coincident endpoints and
tracing the bounded faces of the resulting graph — which is not a
drawing-specific problem. It is the same one a B-rep boolean solves in a
face's parameter space once the intersection curves are projected into
it, so solving it in the kernel means the boolean does not arrive with a
second copy.

What stays is reading the wires, which is where the drawing's own
conventions are: NaNs separating the runs inside one WireModel, and the
double-single coordinate pair that has to be recombined before anything
is measured.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 01:30:32 +03:00
Hakan SevenandClaude Opus 5 b36e85e46e refactor(snap): take an entity's snap points from its curve
Each entity built its own, and what they offered had drifted apart from
what the geometry actually has:

- An arc offered no quadrants at all. It now offers the ones its sweep
  covers, and no more.
- A spline offered its control points as endpoints. For a control-point
  spline those are not on the curve, so the cursor landed in empty space
  beside the geometry. It now offers its two real ends and its midpoint,
  with the fit points — which are on the curve — still on offer.
- An elliptical arc had no midpoint and no ends.

A new SnapHint::Endpoint carries an end that cannot go in key_vertices.
Consecutive entries there are taken to be joined by a straight segment
and their midpoint offered, which is right for a line or a polyline and
wrong for anything curved: an arc's two ends would also produce a snap
out on the chord. Splines lose their key_vertices for the same reason —
the chord between two fit points of a curvy spline does not run along
it.

A polyline's midpoints and arc-segment centres stay out of snap_pts
deliberately. The engine already derives a chain's midpoints from its
vertices, and a wire carrying a centre is treated as round elsewhere,
which a polyline with one bulge in it is not.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-09 00:43:44 +03:00
Hakan Seven 1738f9a79b feat(cursor): add crosshair options
Add persistent cursor sizing, pick aperture, color, pointer mode, isometric drafting planes, and SNAPANG-aligned grid behavior.\n\nRefs #413
2026-08-08 21:32:44 +03:00
Hakan Seven adb8ced8b5 feat(view): complete ZOOM options
Add command-line navigation modes and persistent wheel controls.\n\nRefs #412
2026-08-08 20:37:53 +03:00
Hakan Seven 65b882dd76 fix(hatch): harden boundary extraction
Use a sweep broad phase, preserve f64 coordinates, and split collinear overlaps so large drawings remain responsive and precise.
2026-08-08 18:59:56 +03:00
Hakan Seven 598de3e47a Merge pull request #703 from gianlucafiore/feature/699-hatch-pick-boundary
Add internal-point hatch boundary detection
2026-08-08 18:36:46 +03:00
gianlucafiore 7d5818d5e0 Add internal-point hatch boundary detection 2026-08-08 09:09:43 -03:00