The same shape was being read out of an entity's fields in three places
and the three had drifted. An arc's tessellation cut it at 20 segments
per radian, TRIM's sampling at 16, and its snap points came from a
fourth expression again; a spline was sampled at a fixed 96 points
however long it was; TRIM read an arc's centre as world coordinates
when the entity stores it in its OCS, so an arc carrying the (0, 0, −1)
normal a MIRROR leaves behind acted as a boundary on the wrong side of
the drawing.
entity_curve returns a PlanarCurve: the shape, plus the plane its
coordinates are read in. The plane comes from the entity's OCS — its
extrusion normal through the DXF arbitrary-axis algorithm — not from the
UCS. A UCS is the working plane a user drew in and an entity does not
remember it: draw a circle in a UCS turned 30° about Z and the stored
normal is still +Z, which is why the header keeps the UCS separately.
The algorithm itself stays in transform.rs, where the format is
understood and where #142's degenerate case was fixed once.
Entities that store world coordinates and may not be planar at all —
LINE, SPLINE — are checked rather than assumed. A spline whose points
wander in space returns None instead of being flattened onto XY, which
would move it.
entity_curve_xy folds the plane away for the editing commands, which
work in plan view and need every boundary in one frame before two of
them can be intersected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The codec declared nalgebra and never used it — its Vector2 and Vector3
are plain structs of its own. It was pulling eleven crates into every
build here for nothing.
Also picks up cadkernel's Vec2, which replaces the vector arithmetic the
kernel had been writing out by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`line_seg_ts`, `arc_seg_ts`, `ellipse_seg_ts` and `spline_seg_ts` each
walked every boundary kind and solved that pair by hand — four nested
matches over the same seven cases. They are now four descriptions of the
target as a `Curve` handed to one `cut_params`, which asks the kernel.
Their signatures are unchanged, so the twenty call sites are untouched.
Three things follow from it.
Boundary splines and target splines stop being sampled. A cut on a spline
now lands where the boundary actually crosses rather than where a fixed
sampling said it did, because the kernel subdivides and converges to a
tolerance instead.
Rays and infinite lines as boundaries are handled by the kernel's extent
rather than by a case here — a ray rejecting what lies behind its origin
is the kernel's job now.
`extend_spline` shoots a real ray. It used to build a line a million units
long and cut against that, which both invents a limit and, at survey
coordinates, is comparable to the coordinates themselves. The ray's
direction is a unit vector, so what comes back is the distance to the
boundary. That also fixes the hit position, which multiplied by the
million-unit extent a second time and landed the extended end far past
where it was supposed to.
`cut_params` clamps to 0..=1 only for a bounded target; a ray has to keep
the parameter it comes back with, since the distance is the answer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two faults, reported together.
Fit-point splines could not be edited at all. A SPLINE is stored either as
a control polygon the curve approaches or as fit points it must pass
through with optional end tangents, and `spline_to_nurbs` only read the
first. A fit-point spline has no control points, so every command that
touched one — TRIM, BREAK, LENGTHEN, OFFSET — got nothing back and did
nothing. Two fit points with start and end tangents, which is what a
drawing stores for the simplest curve, was the clearest case. Fit points
now interpolate into an exact curve through cadkernel, so they are as
editable as any other spline.
Trimming deleted the whole spline when the surviving piece reached the
curve's own start. `trim_spline` cut away everything before the interval
and everything after it, and a cut at either end is a no-op that reports
nothing to do — which the end case handled and the start case propagated
as failure, dropping the piece. That is why one side of the cutting line
worked and the other emptied the drawing.
Elevation is read from whichever point list the spline actually has. A
fit-point spline has no control points, so it was falling to z = 0 on the
first edit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spline path ran on truck's BSplineCurve<Point3>, which is not
rational. Weights were dropped on the way in and cleared on every split,
so a NURBS circle — which is how most circles arrive once they have been
through a modeller — was evaluated as a parabola and stayed one after a
break. cadkernel's NurbsCurve carries the weights and applies them in
homogeneous coordinates, and `spline_cut` splits by raising the cut to
full knot multiplicity, so both halves are exact and both stay rational.
`spline_ops` keeps the knot-domain call shapes the commands were written
against — `spline_range` replaces reaching for `range_tuple`, `spline_cut`
replaces `cut` plus the rebuild — so TRIM, BREAK, LENGTHEN and OFFSET
changed only where they named those.
Knot vectors for a rebuilt control polygon come from the kernel too, so
the clamped uniform vector is defined once rather than assumed in two
places.
truck is now absent from the whole spline path. It remains for 3D solids.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`compute_fillet` keeps the entity work — resolving which side of each line
the pick kept, and trimming the lines back — and hands the geometry out.
The kernel takes the corner as an apex plus the two keep directions, which
is what the side resolution here already produces, and returns the tangent
points, centre and the endpoints in fill-the-corner order.
`line_circle_ts` and `circle_circle_pts` were further copies of maths the
kernel already had; the second now calls `circle_circle_points`, added for
callers that want the crossings as points rather than as angles to cut at.
`arc_span` and `clamp_angle_to_arc` stay put on purpose. This file's
`arc_span` treats any span under 1e-6 as a full turn, where the kernel's
only does so at zero or below. That difference is real for a degenerate
arc, and picking a winner is a separate question from moving the code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The offset core moves out: normalising the source, the cavalier_contours
call, the sharp-corner join fixup and the conversion back. What stays is
entity work — reading an LwPolyline in, writing offset LwPolylines out,
and the per-type offsets for lines, arcs, circles, ellipses and splines,
which are analytic and never needed the polyline machinery.
`BulgeArc` moves with it, since the offset preprocessing splits over-half-
turn arcs and cannot work without it. `entities::common` now re-exports it
from the kernel, so the twelve modules already reaching for
`entities::common::BulgeArc` are untouched.
`norm_rad` was a fourth copy of angle normalisation, after the three
removed from trim, fillet and explode. It now aliases the kernel's.
cavalier_contours leaves this crate's manifest: nothing here calls it any
more, and it arrives through the kernel's `offset` feature instead, which
acadifc forwards.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two faults met on the same command, both of them older than the move to
cadkernel and both hidden until ellipse previews started rendering
truthfully.
The survivor came back in two pieces. `trim_ellipse` handed its cuts to
`trim_intervals`, which is the open-curve routine: it anchors on bounds at
0.0 and 1.0, so a closed ellipse gained invented cuts at the parameter
seam and the surviving arc arrived split either side of it. Worse, a click
in the wrapping piece landed in one of those two halves, so the wrong span
disappeared. A closed ellipse now takes the cyclic route circles already
used — find the cut-to-cut gap holding the click, keep everything else,
and let end < start signal the wrap.
The cut span was also short. That came from `line_ellipse`, fixed upstream
in cadkernel 46e76cc: it returned the geometric angle at the centre rather
than the ellipse parameter, which are the same thing only on a circle. On
anything eccentric the cuts sat tens of degrees off where the line
actually crossed, so the removed piece was consistently too small. The
lock bump brings that fix in.
Elliptical arcs, lines, rays, xlines and open splines keep using
`trim_intervals`; nothing about the open-curve path changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TRIM, FILLET and EXPLODE each carried their own copy of the same plane
maths. The copies had already drifted apart: `ll` differed only in
parameter names between trim.rs and fillet.rs, `arc_pts` only in which
angle helper it called, and `norm` meant angle normalisation in trim.rs
and explode.rs while meaning vector normalisation in two other files.
The implementations now live in cadkernel's geom2d. A new local `geom`
module adapts the call shapes — the kernel takes `[f64; 2]` points and
returns `[f64; 3]`, while these commands pass loose scalars and feed
`WireModel::points`, which is `[f32; 3]`. Narrowing stays on this side
because only the caller knows whether it is a preview wire, which leaves
`points_low` empty and accepts the loss, or resident geometry, which
splits the f64 into a high/low pair instead.
Call sites are untouched: the kernel functions are imported under the old
local names, so the signatures they were written against still hold.
This also fixes ELLIPSE trim and extend previews. `ellipse_pts` wrote its
components as [x, z, y] while `arc_pts` — reached from the same match in
`entity_pts`, feeding the same `[f32; 3]` — wrote [x, y, z], and
`WireModel::points` is documented as world-space positions. Ellipse
previews were collapsing onto y = 0 and displacing along Z. The kernel's
`ellipse_arc` orders them correctly, so delegating corrects it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The DWG/DXF/ACIS codec now lives in its own crate and acadifc re-exports
it alongside the geometry kernel, so this is the single dependency:
OpenCADStudio -> acadifc -> { cadcodec, cadkernel }
acadifc is aliased to `acadrust`, which leaves the 2569 `use acadrust::…`
paths across 253 files untouched — the alias is a manifest-level rename,
not a source change.
The old `[patch.crates-io]` entry goes away with it: the dependency is a
git revision now rather than a crates.io version being redirected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two places still showed the old mark after the logo was redrawn.
The window icon was built by drawing the letterforms stroke by stroke in
code, background colour and all, which made it the one icon in the
application not coming from assets/logo.svg. It now rasterises that file,
fitted to the square without distorting whatever aspect the logo has. It
returns nothing when the logo cannot be rendered, so the window falls back
to the platform default instead of to something invented here -- which is
how it ended up stale in the first place.
The icon installed into the icon theme was only ever written from inside
the desktop-entry write, behind that write's own "nothing changed" early
return. The entry's text does not change when the logo does, so once it
had been written the icon was never refreshed again. The two change for
different reasons; the icon is now checked first and on its own terms. It
already compares bytes, so an unchanged logo still costs only a read.
Worth knowing on Wayland: the compositor ignores the window icon entirely
and takes the dock icon from the desktop entry, so there the installed
theme file is the only one that matters -- and it refreshes when the file
associations are next set up, not at launch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The merged fix added the rotation update on this side, matching only
`EntityTransform::Rotate`. That left `Affine` -- which several entity
paths and the UCS world transform emit -- still dropping the angle, and
it sat against the contract this crate already relies on: MIRROR is
delegated precisely because acadrust's entity-aware paths "handle
direction flags, stored-angle conventions and bulges themselves", and
`mirror_text` did exactly that for the same field.
Moved upstream (acadrust 9ecaffc), where one helper carries the angle for
every entity that keeps one. That covers rotation, affine and reflection
in a single place, and closes the same gap for SHAPE and the ATTRIB pair,
which a rotation left facing the old way too -- attributes noticeably so,
since INSERT already turned itself while its attributes did not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A drawing whose bodies are built only of planes opened without error but
drew nothing, while the solids still selected. Pre-7.0 SAT writes strings
as a length and the characters, so one may contain the record
terminator; ODA's builder emits `8 STEP Id#` inside a dxid-attrib. The
parser split there, every later record answered to the wrong index, and
faces resolved their surface pointer to a coedge or a vertex, so nothing
meshed.
Fixed upstream in acadrust 41149d7. All 22 solids in the reported drawing
now parse to exactly the record count their header declares, and all 22
mesh.
Closes#670
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scope revolution spans to face boundaries and preserve pierced cylinders and rational spline lofts so valid bodies no longer produce stretched or missing shells.
Pin Iced 0.15 so dialogs can measure content before distributing the resolved frame across child panels. Keep the web plugin notice compact and non-resizable.\n\nCloses #582
Use the operating system trust store and Windows proxy settings while keeping TLS verification enabled. Add retryable registry errors with copyable diagnostics.\n\nRefs #563
Keep storage fast paths on capable adapters and use packed or texture fallbacks only when device limits require them.\n\nCompact mesh-edge varyings to avoid WebGL shader rejection.