Files
jyviko 81dd685a84 Mesh export path: every face by constrained Delaunay with equilateral seeds and shaping, beside the display mesher (#15446)
* solidcore-tess: snapping touching edges walks them in edge order

snap_touches gathered the nodes to snap per edge in a std HashMap and
snapped the edges in its iteration order, which SipHash seeds per
process. Each edge's snap is independent and the faces to redo are
sorted, so only which error came back, when two edges cannot take
their nodes, could differ from run to run. A BTreeMap walks them in
edge order: the same error every run. No mesh changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the mesh path, every face by constrained Delaunay with lattice seeds and shaping

solidcore_tess::mesh(brep, &MeshSpec, size, size_at) is a second entry
point beside plan, not for display: for mesh bodies, mesh export (STL,
OBJ, 3MF, PLY) and analysis meshers, slow by design. It brings back the
general method of 1fba791e81 for every face, planar ones included, which
the display has since cut back for speed (d84c710353, then the
structured layouts and flat faces with no size):

- every face goes through constrained Delaunay in its (u, v) scaled to
  millimetres, seeded with the equilateral lattice of its side, seeds
  within half a side of the boundary left out; no grid, Coons, pieces,
  cut grid or flat pass;
- a planar face's side is the spec's (default a fifth of the body's
  extent), a curved face's the smaller of that and its curvature's, and
  the caller's size function (Vec3 -> mm) lowers it where it asks for
  less: edges are sampled to it (a line's even division halved there)
  and sides longer than 1.5 times it are split;
- sag and side-length refinement and Ruppert shaping to spec.min_angle
  (default 25 degrees, at most 30, spade's limit) run in turn until
  neither adds a point, one bound of 200 000 points per face; a bound
  hit names the face in PlanStats::over_tolerance with the new
  Cap::Shape, and a lattice too large to seed is reported the same way;
- the sag refinement and flips after it, and the shared edge nodes, are
  the display's; nothing is filled leniently, and a face that cannot be
  meshed is an error naming it.

TessPlan::welded joins a plan's faces at their shared boundary nodes
(bit for bit the same in every face), numbered in face order, with each
edge's nodes as vertex ids and a face id per triangle.

The display path is unchanged: plan threads Purpose::Display through
plan_at, prepare, plan_or_fill and plan_face, triangulate is split into
its boundary, fill and collection steps in the same order, and bisect
and sample_edge keep their signatures over sized variants.

Tests (mesher/mesh_tests.rs; solidcore-ops as a dev-dependency to model
the bodies): the plate a ball is sunk into (#14915: display leaves
0.853 degree needles on the top face) keeps 20 degrees there, folds
nowhere and is closed; a 20 x 10 x 5 box at side 2 keeps 25 degrees and
no side over 3; a cylinder wall keeps 10 degrees (printed); a size
function halving the side in a ball gives shorter sides and more
triangles there; one thread and eight give the same plan; a spec out of
range is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the mesh path's tests import the record, EvalSurface and PlanStats

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the mesh path tests' imports in order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the mesh path splits encroached boundary sides on their edges

CI on the mesh path: the 20 x 10 x 5 box kept 23.79 degrees at the
y = 10 side (triangle (2,10,0) (4,10,0) (1.732,9,0)) and the plate a
ball sinks into 18.28 degrees on its top face. Shaping ran with
keep_constraint_edges, so a skinny triangle whose circumcentre
encroached a boundary side was left as it was: Ruppert's angle bound
rests on splitting such sides (Ruppert 1995; Shewchuk 2002).

A boundary side is an edge's segment, which the faces either side
share, so a split is not kept in the face. cdt::mesh may now split
sides (Sides::Split): the first shaping pass that does ends the fill
and returns the sides it split with the fractions along them where
it put points. plan_at finds each side as a segment of one of the
face's edges (its two end nodes), puts a node at that fraction of the
segment's curve parameters (edges::insert_at), reads the boundary of
every face of the edge again and plans those faces once more, in edge
order, until no face splits a side. A side on no edge (a pole's
collapsed side, a seam cut's line) takes no node: a face that splits
only such sides is shaped with them whole (Sides::Kept). After 16
rounds the faces left are shaped with their sides whole and named
with Cap::Shape (Sides::Exhausted). Both faces of an edge always mesh
from its one set of nodes, so the mesh stays closed.

plan_face and plan_or_fill return Planned (a face's plan, or the
sides it split); the display path plans every face once as before,
with its sides kept, and nothing in it changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a node two faces put on one edge in a round is one node

CI at 5c1a53ef7c: the plate a ball sinks into had a triangle of
1.3e-13 degrees on its top face. When both faces of an edge split the
same side in one round, each gives the fraction along it in its own
(u, v) metric, and the two parameters on the edge differ by their
rounding: insert_at kept both, two nodes a rounding apart, and the
faces meshed a triangle between them.

insert_at now leaves out a parameter within 1e-6 of its segment's
parameter length from the segment's ends or from a node put in before
it. Test: two faces split one side at 1/2 and at 1/2 + 1e-13 the other
way round; the edge takes one node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the mesh path shapes every face by its triangles in 3D

FieldCraft's trial at e728f90e5 (min angle 25): a sphere 1.10 degrees
at its pole (display 4.94), a bent tube 0.06 (display 9.54), blends
and cylinder sides 0.08; 30 was worse. Ruppert ran in (u, v) scaled by
the surface's mean speeds, which is the surface only where the speeds
are constant: towards a pole, round a torus or across a fillet the
triangles it made square in (u, v) are needles in 3D, and the
display's flips toward the normals after it undid the rest.

The mesh path now shapes by Delaunay refinement on the surface (Chew,
"Guaranteed-quality mesh generation for curved surfaces", 1993;
Ruppert 1995), in mesher/shape.rs, on the face's triangulation in
(u, v):
- seeds: the equilateral lattice at the surface's fastest speeds (no
  wider than the side in 3D anywhere), thinned in 3D so no seed lies
  within 0.8 of the side of a boundary node or a seed before it;
- constrained Delaunay of the boundary and seeds, refined for sag and
  side length as before (spade's Ruppert no longer runs on this path);
- sides flipped by the surface's Delaunay rule (the 3D angles facing a
  side add up to less than half a turn), where both triangles keep
  their (u, v) winding;
- a triangle sharper than the angle in 3D, a side longer than 1.5
  times the side asked, or off the surface gets a point at its 3D
  circumcentre, taken to (u, v) by the triangle's own map from its
  plane, then flips round it;
- a point that would encroach on a boundary side (its diametral ball,
  or past it) is not added: the side goes on its edge as before;
- a point within half a side of a pole is not added (the parameters
  collapse there), nor one in a triangle below the area floor; a
  corner of the boundary sharper than the angle stays;
- the display's sag flips are skipped on this path.

PlanStats::unshaped names each face left short of its shape with its
smallest 3D angle and what limited it (ShapeLimit: points, seeds,
rounds, a side on no edge kept whole, pole, floor, corner), apart
from over_tolerance, which keeps naming the faces whose sag
refinement stopped and whether they are within the chord. Cap::Shape
is gone. TessPlan::welded's doc names the refused faces it returns.

Tests (fail before, FieldCraft's numbers): a sphere, a torus 20 / 5,
the rounded box fixture and a cylinder wall keep 20 degrees in 3D
everywhere, closed; the plate a ball sinks into keeps 20 degrees on
every face, the ball's pole included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the edge-split test imports the planner's edges, sizes and split sides

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* solidcore-tess: the mesh path fans each pole's cap from the pole

CI at cf282493fd: the sphere was refused ("triangulation does not
follow the boundary") and the plate's ball kept 0.003 degrees at its
pole (unshaped [Pole, Rounds, Floor]). In (u, v) a pole is a side, its
copies one point in 3D: a triangle the triangulation laid on both
copies and a seam node has no area and is left out, which leaves the
seam's first side with no triangle; and the triangles toward the side
crowd and fold, which the guard on points near a pole only stopped
refining.

Each pole of the surface on a face's boundary (its run of copies, as
poles::Face::pole tells them, within the face's reach) now has its cap
meshed on its own: a ring from the boundary node before the pole to
the one after it, straight in (u, v), divided evenly by the side asked
at the pole and at least once per quarter turn round it, and the fan
from the pole to it (the first copy to the ring, the copies to the
ring's last inner node, which leaves triangles on both copies the
planner drops, the last copy to the last segment), so the cap's
triangles are near equilateral and its boundary sides each have one.
The rest of the face is triangulated inside the rings, then shaped
in 3D with the fans, only the face's own boundary sides kept; the pole
side carries no node. The pole guard and ShapeLimit::Pole are gone. A
pole whose neighbour on its loop is a pole too is an error naming the
face.

Tests: the sphere keeps 20 degrees with nothing unshaped; the plate a
ball sinks into names no unshaped ball face.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the mesh path keeps its pole fans and bounds every shaping loop

CI at 8360117419: the sphere and the plate a ball sinks into ran past
3000 s. The pole fans went into the 3D shaping like any triangles, and
their sag was judged at middles in (u, v): a fan's corner at the pole
is a copy whose free parameter is a neighbour's, so the middle of a
fan triangle in (u, v) lies off it on the surface and every fan
triangle, and every triangle split from one, sagged. Each point then
walked up to four times the triangles and flipped up to eight times
them, so the 200 000 points it may add never came.

- The fans are kept as they are: never flipped or refined. The rings
  are kept like the boundary; a ring side a point would encroach on is
  split in the face at its middle in (u, v), the fan triangle on it
  halved at the pole, while the halves keep the spec's angle; past
  that the point is not added (ShapeLimit::SidesOnNoEdge).
- Every loop of the shaping is bounded: the points added (its budget,
  ShapeLimit::Points), 8 flips per triangle in the first pass and 256
  after each point, and 4096 steps per walk (past them the point is
  not added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the shaping's ring sides are a set, its side key taken by value

Clippy (-D warnings) at 74120b5244: a map of zero-sized values for the
ring sides, and an 8-byte key passed by reference. The ring sides are
a BTreeSet; kept takes the key by value. No behaviour changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a triangle a ring side keeps short stays given up

A triangle whose shaping would split a ring side it may not (the fan's
halves would be sharper than the angle) was marked stuck only until a
neighbour changed it, then tried again. It now stays given up by its
nodes for the rest of the shaping, so no point is spent on it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the mesh path's angle tests name the sharpest triangle's corners

A failing angle assertion on the mesh path now prints the corners of
its sharpest triangle, so CI's log shows where on the face it lies
(a pole's fan, beside its ring, or elsewhere).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a pole's fan owns its seam sides; no point outside it splits them

CI at dbd071e06c: the sphere's sliver lay 0.45 mm from its pole, inside
where the cap should be (its ring through the seam node beside the
pole, about 0.89 mm out). A point outside the cap could encroach on the
fan's own boundary side (the pole to the seam node beside it: its
diametral ball reaches past the ring), which was then split on the
seam edge. The next round's ring passed through the new node, half as
far from the pole, and so on each round: the cap shrank toward the
pole and the shaping worked in the band where the parameters squeeze.
The sides of a fan triangle are no longer candidates for encroachment,
so the ring stays where the edge's sampling put it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: the shaping chooses each point in 3D, never a sliver from a node

CI at dbd071e06c: the slivers were pairs of nodes 1.6e-6 and 6.8e-5 mm
apart. The point for a triangle was its 3D circumcentre's barycentric
coordinates applied to its corners' (u, v), then evaluated there: where
the parameters stretch (toward a pole) that lands far from the 3D
circumcentre, near a node, and the next triangle's point nearer still.

- The point is the 3D circumcentre put on the surface at its closest
  point (the kernel's plan::uv::project, from the barycentric
  parameters, unwrapped to them); (u, v) only locates it.
- A point nearer a node of the triangle it lands in, or of that
  triangle's neighbours, than a quarter of the longest side of the
  triangle it is for is not added: a circumcentre of the surface's
  Delaunay triangulation is a circumradius, at least half that side,
  from every node, so a nearer one is not its circumcentre, and adding
  it makes a sliver. The triangle stays given up, named with the new
  ShapeLimit::Crowded. This bound, not the point budget, ends the
  refinement there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a pole's ring keeps the chord tolerance, at most 29 tolerances out

CI at e95d9a9c74: the slivers' corners lay on the pole rings (the
sphere's at 0.446 mm from the pole, the ball's at 1.734 mm), node pairs
3e-8 and 1.6e-4 mm apart. The ring was divided by the side asked and
by quarter turns, not by the chord tolerance: on the sphere its four
chords sag 0.13 mm from the surface against a tolerance of 0.01, on
the ball 0.51 against 0.0197. The triangles on the ring could never
come within the tolerance; refinement kept adding their middles,
nearer the ring each time, until the point budget ran out (Points),
leaving node pairs a rounding apart (Floor).

- The ring is divided evenly into chords that keep the tolerance as an
  edge's do (checked at their quarter points and middles), no longer
  than the side asked, and at least one per quarter turn round the
  pole.
- A chord turning the fan's angle at the pole sags r (1 - cos(t / 2))
  from a ring of radius r, so for the fan to keep 30 degrees the ring
  lies no farther than tol / (1 - cos 15 degrees), about 29
  tolerances (nor than the side asked). Where the boundary nodes
  beside the pole lie farther (by half again), the face asks first for
  nodes at that reach on the sides to the pole, on their edges, and is
  planned again with the ring through them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a ring split obeys the shaping's rules, its fan's corner checked to be the pole

Every way the shaping adds a point now keeps the same rules. A ring
side split in the face is refused where its new node lies nearer a
node of its two triangles than a quarter of the longest side of the
triangle that asked (ShapeLimit::Crowded), where the fan's corner
opposite it is not one of the cap's pole copies (the planner now passes
them, rather than the corner being taken as whatever node is
opposite), where the halves of the fan's angle at the pole would be
sharper than the spec's angle, or once the side's line was halved
twice (each ring side carries its count). A triangle refused stays
given up, named with why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a pole's ring side encroaches by its lens, and its off-centre takes the point

CI at 016a3a9626: the sphere and the ball keep 21.9 and 22.4 degrees
beside their rings (SidesOnNoEdge). A ring side turns 30 degrees round
its pole, so it cannot be halved in the face and keep 25; Ruppert's
bound needs every side a point encroaches on to be split, and a point
inside a ring side's diametral ball was refused, leaving the triangle
on the side short.

Both published remedies, together:
- A ring side encroaches by its lens (Shewchuk, "Delaunay refinement
  mesh generation", 1997, section 3.4): only points that see it at
  more than half a turn less twice the spec's angle. A circumcentre
  lies on the side's bisector, and outside the lens it makes the
  triangle on the side keep the angle at both ends, so it is added.
  Boundary sides (an edge's, splittable on it) keep the diametral ball.
- A point that does fall in a ring side's lens (or past the side) is
  moved to the side's off-centre (Ungor, "Off-centers", 2004): on its
  bisector toward the face, at the height where the side is seen at
  half a turn less twice the angle and a thousandth: the triangle it
  makes on the side keeps the angle, and it lies outside the lens, so
  it encroaches on nothing it is for. It is put on the surface at its
  closest point and passes the same tests as any point (encroachment,
  the crowding bound, the budget); otherwise the triangle stays given
  up, as before.

Termination: every point added is a circumcentre or an off-centre
outside every lens and diametral ball, at least a quarter of the
asking triangle's longest side from the nodes round it (Crowded), so
the insertion radius is bounded below as in Ruppert's and Ungor's
proofs; the point budget stays the backstop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a pole's ring is divided by the spec's angle first, its radius from the tolerance

CI at ff8301a7ac: nothing limited either body, and both kept 22.41
degrees: the fans' angle at the pole, a ring of 16 chords (the
tolerance's division at 30 degrees, then a quarter more where a chord
still sagged, the ring lying up to half again farther out than planned).

The ring's segments now come from the angle: as many as keep the fan's
angle at the pole at least the spec's with 2 percent to spare
(floor(turn / (1.02 min_angle)), 14 for a full turn at 25 degrees), and
at least one per quarter turn; then fewer while any neighbour pair's
angle at the pole in 3D falls short (the nodes lean away from the pole
on a curved surface). The ring's radius follows: tol / (1 - cos(turn /
2n)), nor more than the side asked; where the boundary nodes beside the
pole lie farther, the edges take nodes at 0.9 of it first (the plan is
made again), so its chords keep the tolerance with room. The fan's
base angles are (180 - turn / n) / 2, 77 degrees and more. A seam's
two nodes are the ring's ends and count among its segments. Only where
the nodes could not be moved (sides kept whole) is the ring divided
more finely to keep the tolerance, and the face is then named short.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

* solidcore-tess: a pole's ring is even round the pole in 3D, not in its parameter

CI at 83e819468f: both bodies' sharpest triangle was a fan triangle
whose ring nodes turned 19.7 degrees at the pole, about azimuth 180,
the others wider. The ring was divided evenly in its (u, v) line, and
the spheres are rational splines, whose parameter runs unevenly round
each arc: even in the parameter is uneven round the pole.

The ring's nodes are now placed by their share of its whole turn round
the pole in 3D (sampled along it, then bisected between the samples),
the seam's node one point at both ends, and every neighbour pair,
across the seam too, is checked in 3D.

Test: a ring on a rational spline sphere (four quarter arcs round, two
up), a full turn at 25 degrees: 14 segments, each pair, the one across
the seam included, 25 degrees or more at the pole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M2MmcDrkcWhQeHS2K8

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 15:31:14 -05:00

488 lines
11 KiB
TOML
Vendored

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "cfg-if"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]]
name = "crc32fast"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
[[package]]
name = "diagnostics"
version = "0.1.0"
dependencies = [
"miniz_oxide",
"serde_json",
"solidcore",
"solidcore-convert",
"solidcore-tess",
]
[[package]]
name = "either"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "flate2"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"crc32fast",
"miniz_oxide",
"zlib-rs",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "goldens"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"solidcore",
"solidcore-record",
"solidcore-ssi",
"ssi",
"step-export",
"toml",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "miniz_oxide"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "nurbs-grid-mesh"
version = "0.1.0"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "robust"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "simd-adler32"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
[[package]]
name = "smallvec"
version = "1.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8"
[[package]]
name = "solidcore"
version = "1.0.1"
dependencies = [
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "solidcore-convert"
version = "0.1.0"
dependencies = [
"flate2",
"serde_json",
"solidcore",
"solidcore-record",
]
[[package]]
name = "solidcore-ops"
version = "0.1.0"
dependencies = [
"flate2",
"serde",
"serde_json",
"solidcore",
"solidcore-convert",
"solidcore-record",
]
[[package]]
name = "solidcore-record"
version = "0.1.0"
dependencies = [
"flate2",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "solidcore-ssi"
version = "0.1.0"
dependencies = [
"solidcore",
"ssi",
]
[[package]]
name = "solidcore-step"
version = "0.1.0"
dependencies = [
"solidcore",
"solidcore-convert",
"solidcore-ops",
"solidcore-record",
]
[[package]]
name = "solidcore-tess"
version = "0.1.0"
dependencies = [
"flate2",
"rayon",
"serde",
"serde_json",
"solidcore",
"solidcore-convert",
"solidcore-ops",
"solidcore-record",
"spade",
"thiserror",
]
[[package]]
name = "spade"
version = "2.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9699399fd9349b00b184f5635b074f9ec93afffef30c853f8c875b32c0f8c7fa"
dependencies = [
"hashbrown 0.16.1",
"num-traits",
"robust",
"smallvec",
]
[[package]]
name = "ssi"
version = "0.1.0"
[[package]]
name = "step-export"
version = "0.1.0"
dependencies = [
"solidcore",
"solidcore-record",
]
[[package]]
name = "syn"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
[[package]]
name = "xtask"
version = "0.1.0"
[[package]]
name = "zlib-rs"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"