* Add a one-surface fill of closed 3D loops to geom
A closed loop of curves that does not lie in a plane has no exact surface;
the usual answer is one smooth face fitted over it. fill_loop does that:
- the loop's best-fit plane (Newell normal), with a frame chosen from the
loop itself so the result does not depend on where the loop starts, its
direction, or where the part sits
- the loop seen along that normal must be simple and must not fold or run
along the normal; otherwise it is refused ("not supported yet"), never
filled with wrong geometry
- one non-rational bicubic B-spline height field over the loop's box,
fitted by least squares to the loop's samples (The NURBS Book 9.4.1,
9.4.3, eq. 9.62/9.65) with a thin-plate smoothness term
lambda*A*int(h_uu^2 + 2 h_uv^2 + h_vv^2), A one grid cell's area, so the
fit is the same at any scale; only planes are reproduced exactly
- span counts per direction (long narrow loops fit), refined until the
loop is within tolerance, else refused with the deviation reached
bspline: bisected span search, basis functions on a stack array, one
clamping helper shared by curves and surfaces, one binomial helper.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* Patch fills non-planar loops of edges with one smooth face (#250)
surface.patch with edges refused any loop that is not planar ("not
supported yet: patching a loop that is not planar"). It now fills it:
- planar loops still get one exact planar face, but planarity is certified
conservatively: lines by their ends, B-spline/NURBS edges by their control
points, intersection curves by their samples lying on a support plane
within tolerance; a loop that can't be certified is filled instead
- other loops get one face from geom's fill_loop, bounded by the body's own
chained edges, whole (no split): its history is one generated face whose
edges are the body's, and it stitches back into a closed solid
- intersection-curve edges are sampled by a seeded, guarded double
projection shared by the planarity test and the fill (plane_fit)
- the face tolerance is 10x the planarity tolerance (documented: later
operations take the face to contain its edges within it)
- edge picking runs inside the guard; the geom->truck conversion is shared
with the T-spline sheets (spline_conv); the Newell plane fit is shared with
STEP import
Tests: 3- to 8-sided, concave (dart, arrow, plus), flat-corner, two-edge and
imported rims fill as one face and stitch closed (volumes against an
independent integral); finite mesh normals, same result for any pick order
or placement; non-height-field loops and too-fine detail are refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* Test the Patch dialog filling a non-planar loop of picked edges
The Patch dialog's only test used a sketch profile. Picking the four
curved rim edges of a box cut by a sideways cylinder now makes one
surface.patch command whose result is a single smooth face over the rim.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Every panic (in a command, in the kernel, anywhere else) and every unclean
exit (abort, stack overflow, kill) leaves a report in a local crash folder:
message, file:line, a trimmed backtrace, version, commit, system and renderer,
the command and recent commands, and when possible the design and replay.json
that reproduce it (solvecraft-cli crashes replay <id>).
- Cost: nothing until the app installs the hook (tests, CLI and web never
do); then one small journal line per top-level command of the design on
screen, taken back with a truncate when the command changed nothing.
- Unclean exits: each run holds a lock on its journal; a journal nobody holds
at the next start belongs to an app that died. Autosaves restart the
journal from the autosaved design.
- Bounded: a crash's signature (place + message, numbers ignored) is counted,
not rewritten; at most 5 new reports per run, 20 reports, 50 MB, 90 days;
recovered kernel panics are pruned first.
- Agents: diagnostics.crash_reports / crash_report / crash_reports.clear
commands, the MCP crash_reports tool, solvecraft-cli crashes, solvecraft
--crash-log; diagnostics.crash_test (with SOLVECRAFT_CRASH_TEST) checks it.
- People: the Crash Reports window opens after a crash (Help > Crash
Reports…) with a reviewed issue draft per report; nothing is sent.
- Release builds keep line tables for file:line in backtraces.
Tests: child processes that really panic in a command, in the kernel, on a
thread, and abort; dedup counts, limits, hostile folders, journal parsing,
the UI window and draft, the MCP tool. docs/crash-reports.md.
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bherbruck <[email protected]>
* feat(kernel): typed TooLarge refusal and fillet_max search
Blend refusals caused only by the size (larger than the neighbouring faces,
taller than the walls, longer than the next edge, larger than a corner
radius) now return KernelError::TooLarge, displayed as before. fillet_max
finds the largest radius below a request at which the fillet of the edges
works, to three significant digits, by building candidates; it is an explicit
call (up to 16 fillets), not run on every refusal.
A corner edge that doesn't run square to the edge is now reported as such
instead of as a blend larger than the neighbouring faces.
Refs #293
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
* feat: fillet fit option reduces a radius too large for its edges
solid.fillet takes fit: exact (the default, as before: a radius too large
for the geometry around the edges is refused) or reduce: the radius is
reduced to the largest that works for all the picked edges (kernel
fillet_max, three significant digits, every candidate built). The feature
stores the requested radius, so it refits on every evaluation; when it
reduces, the feature's warning says so and the command returns
radius_applied. Pattern copies of the fillet use the same rule. The file
format writes fit only when it is reduce.
The Fillet dialog gets a Radius Fit row (Exact / Reduce to Fit, constant
radius) and, when the preview reduced the radius, an Applied Radius line;
editing the feature keeps the choice.
One radius for all edges for now; per-edge radii and taper follow.
Refs #293
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TWc1smHYwTSVrVc8SNvS44
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: bherbruck <[email protected]>
* Add B-spline basis, knot insertion and NURBS surfaces to geom
Freeform faces need local refinement and gap-free joins between patches
with different knot vectors, which tensor B-spline grids cannot give;
T-splines can, and the first user is an N-sided Boundary Fill of
non-planar loops. This lays the univariate groundwork they rest on, in
the L0 geometry crate so it stays independent of the B-rep kernel:
- Cox-de Boor basis of one function from its local knot vector, on
half-open spans with a left-limit side for the domain end, with
derivatives (NURBS Book eq. 2.5, 2.9)
- knot insertion into a single basis function (Sederberg et al. 2004
section 3) and Bezier extraction of one function by repeated insertion
- Boehm knot insertion for curves (NURBS Book A5.1), de Casteljau
evaluation and sub-segments
- a rational tensor NURBS surface with evaluation, knot insertion and
clamping, and a rational Bezier patch type
All input is checked (finite, monotone knots, positive weights, size
caps) and returns SplineError; nothing indexes unchecked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* Add T-spline surfaces: T-mesh, evaluation, AS check, refinement, extraction
Tensor B-spline faces refine only by whole rows and columns and cannot
join patches with different knot vectors without gaps. T-splines allow
T-junctions in the control net, which fixes both; the first user is an
N-sided Boundary Fill. This adds the maths only (no command, no change
in behaviour):
- T-mesh in index space with knot values per line, validated (outer
rectangle, rectangular faces, no empty lines, size caps); local knot
vectors read off rays from each vertex, rays leaving the mesh repeat
the boundary (Sederberg et al. 2003 section 3)
- rational T-spline evaluation and first derivatives for odd bi-degree,
with a complete (p-1)/2-line frame and the domain inside it
- T-junction extensions and the analysis-suitability check (Scott et al.
2012 section 2)
- local refinement that keeps the surface exactly: requested knot lines
are cut in, then every original blending function is carried as a
record and split (violation 1) or the mesh gains edges (violations 2
and 3) until each record is a blending function of the refined mesh
(Sederberg et al. 2004 section 3); analysis-suitable input stays
analysis-suitable by carrying conflicting T-junctions across faces
- NURBS <-> T-spline conversion for meshes without T-junctions, and
Bezier extraction over the rectangles no blending-function knot line
crosses
Measured: regular T-spline vs NURBS < 1e-12, round trip exact, partition
of unity with T-junctions < 1e-14, refinement deviation <= 1.4e-14 over
40 random refinements, Bezier patches vs surface < 1e-14.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* Convert T-splines to sewn NURBS sheet bodies in the kernel
A T-spline is only useful to the modeller once it is a B-rep face set.
tspline_sheet builds an open shell: a mesh without T-junctions becomes
one clamped NURBS face; otherwise each Bezier element is a face, and
sides are split at the T-points where neighbours' corners land, so all
faces share vertices and edges and the sheet is one oriented shell
(V - E + F = 1). Every truck call runs under guard. No command yet;
Boundary Fill will be the first caller.
Tests: one face for a regular surface, sewn Bezier faces with
T-junctions, tessellated points on the surface to ~1e-9 mm, and hostile
control nets (coincident, 1e300, needle-thin) never panic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Fillets (local, loop, rolling ball, corner, variable, chord), chamfers
(cutters, local corners, loops, two sides), shell, draft, offset faces,
split face/body, delete face, patch/copy/stitch/trim/extend and
transforms record their history over [body] while they build: faces
rebuilt from the body's, blends in place of edges (corner patches in
place of the edges at the corner), shell walls as offsets, edges as
pieces of the body's or where two faces meet. Multi-step operations
compose each step. The push-face offset fallback now has history, and
the bore union composes its narrowed tool.
A reflection reorders a body's edges, so transforms now match edges by
identity (mirrored copies reported the wrong edges).
Refs #136
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
ops::boolean's result carries its history over [a, b] whichever path made
it, recorded by the code that builds the faces:
- truck path (incl. jitter retries): shapeops history mapped through the
order-preserving copies and the cut's turned tool, then through healing
- heal records which input faces each healed face merges and which input
edges each joined or exact edge replaces (HealMap); opt-in, so heal_keep
callers pay nothing
- trivial shortcuts, the void, glue, faces pushed apart (push_faces records
its own history; the slab cut back on a union maps to the operands'
faces in that plane), the nudged retry, by_lumps and join composition
- prism booleans: section segments carry tags (operand cap edge, the wall
beside it, its vertices) through the 2D boolean; walls, caps and edges of
the extruded or sewn result follow from them
- planar and faceted polygon booleans: polygons carry their operand face;
faces join the pieces across edges, edges come from the pieces either side
History gains renumber/over (and consuming forms) for composition;
record gains regions/traced/build helpers. truck-shapeops
*_with_history now return the solid with an Option history. Paths that
cannot report (bore narrowing via offset, inexpressible lineage) leave the
result without history. Boolean results, tolerances and fallback order are
unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
One numbered loop type (crate::numbered) carries each segment's profile
numbers through orientation, snapping, circle re-seaming, re-sampling and
rotation; every builder orients its regions through it once. Builders that
make their own side faces pair each face with its provenance as it is made
(ruled walls by the wire edges each face holds, smooth lofts, coils and path
sweeps by segment), and cap_and_close appends the caps itself. Column
following is kept only for faces truck sweeps internally (rsweep, tsweep
along paths, cones, where an apex vertex stays put). Extrude derives face
sources and history in one topology pass. One attach entry point takes
operands with their edges already indexed.
Geometry, face order, face sources and recorded origins are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
Extrude, revolve (partial and full turn), tapered extrude, loft (incl. to a
point), sweep, path sweep, coil, primitives, lip, emboss and thicken attach
the exact history of the bodies they build: each face's profile segment, cap
or operand face, and each edge's operand edge, recorded by topology identity
inside the building call. Nothing is sampled; a body whose faces do not follow
the expected construction carries no history. Face sources are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
Bodies can now carry the exact history of the operation that made them:
for each result face its origins among the operation's operands (kept,
modified, split, merged, or generated from a profile segment, cap, edge or
offset) and for each edge the faces it separates and the operand edge or
face pair it came from. History is recorded by the operation, never
sampled, and checked against the body when attached.
- history.rs: the vocabulary (moved from provenance.rs), History::check,
identity, substitute (composes an operation with the one that made an
operand, including bodies made from a sketch) and face-source propagation.
- One edge order for a body (body::edge_order), shared by tessellation and
history, so history edge indices are the mesh's edge indices.
- Transforms report an identity history; placed_topology documents the
face/edge order guarantee it relies on.
- provenance.rs keeps the geometric trace for operations that do not yet
report history.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
* test: reproduce #261 (Rectangular pattern of a hole cut scales super-linearly)
* Patterns of many hole cuts scale near-linearly (#261)
Two quadratic costs:
- Meshing a face tested every point against every segment of every boundary
loop, so a plate face with hundreds of holes meshed in quadratic time. The
vendored truck-meshalgo inside test now skips loops whose parameter box is
clear of the point (they wind zero times around it).
- A pattern cut each copy in turn, rebuilding and re-checking an ever more
complex target per copy. Copies whose boxes are clear of each other are now
one tool of many pieces (kernel::union_apart, no intersection work), cut once.
512 holes now take ~7 s instead of not finishing. The test's volume tolerance is
widened from 1e-6 relative to 0.1 % of each hole: the measure meshes the bores
and reads each hole ~0.06 % small, on main too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Hole-pattern test: a budget CI can meet; coarser apart-box meshes (#261)
CI runs every test binary at once and is about ten times slower than a
machine running this test alone (15 tests passed 60 s in the last run), so the
512-hole pattern (about 6 s alone) hit the 60 s timeout there. The budget now
only has to tell linear from the quadratic hang: 300 s timeout, 120 s budget.
union_apart meshes each copy coarsely (2 % of its size, box grown by twice that).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Hole-pattern test checks the scaling, not a wall-clock time (#261)
CI runs every test binary at once: the 512-hole pattern (about 6 s alone, 10 s
CPU single-threaded) took 177 s there, so no fixed time is reliable. The test
now patterns 128 and then 512 holes and requires the second to take under 8x
the first (linear is 4x, measured 3.5-4.2x here; the old quadratic cost was 16x
or more), each with the volume check and a 900 s hang guard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: bherbruck <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Record ownership transfers in the timeline so earlier Join/Cut membership,
world placement, machining data and styles survive component moves, edits,
independent copying, undo and reopening. Preserve pending positions in history
snapshots and keep constructor transfers ordered during feature redefinition.
Retain shallow sheet pockets and exact planar intersection islands through
unfold/refold, with depth-aware flat previews and DXF export. Store explicit
3D wire coordinate spaces and migrate known legacy provenance without moving
saved points or analytic fits.
Infer unique eligible cylindrical Emboss targets, align Revolve manipulators
with actual extent geometry, and discard obsolete preview errors after input
correction. Certify constructed and immutable imported face styles using exact
source metadata while preserving legacy face names. Upgrade designs to format10
with bounded validation and geometry-preserving legacy migration.
Persist typed sketch dimensions and bounded ordered configuration rules in format 8. Use exact occurrence paths and source identity for editing, movement, appearance, and mass. Preserve sheet material regions through cutting, folding, and exact DXF export; retain nested cavities and material islands.
Validation: cargo xtask ci passed, including 368 engine tests, 178 UI tests, 68 UI scenarios, 136 kernel tests, and the 10-crate wasm check. Actual preview workflows and legacy format-7 origin-corner reopening were inspected.
kernel::fillet_variable_points(body, edge, start, [(position, radius)...], smooth): the radius law is a B-spline of the edge parameter (straight pieces, or C1 cubic pieces with Catmull-Rom slopes), and since every section's arc is affine in its radius the blend is exactly a rational B-spline (degree 2 across, the law's degree along) with B-spline contact curves on the faces. The largest radius along the edge decides whether the blend fits. Fillet type variable takes mid: [[position, radius expr], ...] and smooth; middle radii are parameters of the feature (Mid radius) and follow user parameters. Tests: straight pieces against (1 - pi/4) L (a^2 + ab + b^2) / 3 per piece, smooth against the integrated law, either end as start, refusals; engine test with a parameter-driven middle radius.
Refs #9
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
- solvecraft-cli fuzz / fuzz-one: seeded random modelling sessions (boxes, extruded profiles with arcs, revolves; joins and cuts of boxes, cylinders and spheres, fillets, chamfers, shells, holes, patterns) on a grid that makes coincident faces and tangencies common, every step checked (command ok, every body a sound solid via the new Body::validity, volume moving the right way), plus the boolean volume identity on random primitives; one process per seed with a time limit; failures minimised to scripts
- shell/offset: corners where a curved face meets another at an angle are refined onto the moved surfaces (Newton); edges that are neither lines nor arcs are rebuilt through their moved points; one shell per piece; faces that would cross say so (not supported yet) instead of leaving a cavity inside out; flat revolved faces count as planes; the unhealed body is tried when the healed one fails
- cylinders start their seams at an odd angle (grid-aligned seams broke booleans along them)
- partial revolves of profiles touching the axis build no zero-area face on the axis
- booleans on bodies in several pieces retry a piece at a time
- regression tests for each, and a fixed set of seeds in cargo test
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
blend_corner.rs rebuilds the planar faces at the ends of the filleted edges (three planar faces and three straight edges at each such vertex) and keeps the rest. One round at a vertex ends in the third face's exact section of its cylinder; two rounds with the third edge sharp trim each other (rolling ball: the crease from where their contact lines cross to the third edge for equal radii, else to the smaller round's contact line on the face that ends the larger one, which gets the exact section between); three rounds meet a sphere for equal radii, else a rational degree-4 patch through plane sections of the cylinders, the sections turned so that the cylinders' curvatures agree where two meet on a face and the patch fitted tangent to all three (within 0.01 degrees) and to the face at its closed corner.
kernel::fillet falls back to it when the local blends refuse: an edge that ends on an earlier round (any radius) takes the round off (delete_faces) and blends both edges together, so r3 next to r2 and a third edge into a mitred pair work. delete_faces: a gap stretch along several deleted faces keeps the corners between them (two rounds meeting in a mitre come off back to the sharp corner). splitface::fit is shared for the crease.
Tests: volumes against the strips-minus-overlap reference (exact strips, overlap integrated; checked against an independent 1D integral), the sphere-octant formula for three equal rounds, tangency along the patch, closed meshes, refusals without panics.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
New kernel file blend_variable.rs. The hooks into blend.rs are visibility
only: plane_normal, vtx, rebuild_face and unsupported are now pub(crate).
- fillet_variable(body, edge, start, r0, r1) works on the same edge
configuration as the constant fillet (straight edge, planar faces, end
faces square to it). The radius runs linearly from the end nearest
`start`, and every section square to the edge is a circular arc. The
blend is an exact degree (2, 1) rational B-spline: arc control points
move linearly.
- fillet_chord(body, edges, chord): the radius per edge comes from its
face angle (chord = 2 r cos(phi/2)).
Fillet feature: style constant | chord | variable {radius2, start};
FusionFilletEdgesCommand takes type, radius2 and start.
Tests:
- Variable 2 -> 5 on a box edge removes (1 - pi/4) L (r0^2 + r0 r1 + r1^2)/3.
Starting from the other end gives the same solid; equal radii give the
constant fillet; too large is refused.
- Chord 4 on a right angle equals radius 4/sqrt(2).
- Engine: both types by command.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
For solvecraft-params' Split Face feature:
- Sketch tools are chains of Seg2. A single line splits by its plane and a
single arc by its cylinder, exactly (a circular edge on a planar face);
longer chains and splines follow the curve.
- split_faces_with_map gives the source face of each face of the result.
- A listed face the tool misses is an error naming it (split_any keeps
the lenient form for Trim).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Kernel extend(body, edges, distance) moves the picked straight edges of a
planar surface outward in its plane, with neighbours meeting them. Doc
feature SurfaceExtend, engine FusionExtendCommand. Tests: 10x10 extended
by 5 past one edge gives 150, past two gives 225; the engine patch, trim,
extend and thicken gives 240.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Kernel (surfaces):
- Bodies with an open shell are surface bodies (Body::is_surface).
- patch_region: a planar patch of a sketch region. patch_edges: a planar
patch filling a closed loop of a body's edges.
- copy_faces: faces copied out as a surface.
- stitch: sew surfaces along coinciding edges, turning faces to agree; the
result is a solid when it closes.
- trim: cut a surface by a plane or face and keep one side.
- thicken: offset planar and circular-cylindrical faces along their
normals (one side or symmetric) into a solid. Plane copies slide;
cylinder copies scale about the axis, exact for NURBS circles. Free
edges get ruled walls. Faces meeting at a sharp edge are refused.
Doc features Patch, Stitch, Thicken (with operations) and SurfaceTrim;
engine commands FusionSurfacePatchCommand, FusionSurfaceStitchCommand,
FusionSurfaceThickenCommand and FusionSurfaceTrimCommand.
Tests (volumes):
- A 10x10 patch thickened by 2 gives 200, also symmetric.
- Half a cylinder side thickened outward gives half a ring; both halves
inward give the full ring.
- Six faces of a box stitched back give 6000; an open box with its top
patched from the edges, stitched, gives 6000.
- Trim keeps 30 or 70 of 100.
- Engine: patch, trim, thicken gives 140.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
split_body(body, tool) divides every face where the tool meets it and
closes each side with the piece of the tool surface inside the body.
- Patches have outer loops and holes.
- Bands go round a cylinder or another surface of revolution.
- Each side falls apart into its connected parts.
- A body the tool misses comes back whole.
Faces that are B-spline sheets but really planes or circular cylinders
(extruded faces) split as the whole plane or cylinder: the tool is
extended, and the field is exact and cheap.
Tests (volumes): a box split by a plane (2400/3600); a holed plate split
through its hole into two equal halves; a box split by a cylinder's side
into the core (pi*16*30) and the rest; a plane that misses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
split_faces(body, faces, tool) divides the chosen faces where the tool
meets them and leaves the solid unchanged. It handles:
- lines across a face;
- closed lines inside a face (a disc inside a ring);
- lines that run into a hole (the face is divided around it).
Edges the line ends on are cut in every face that uses them. The tool's
zero set is traced on a finely divided triangulation of the face, each
point is placed exactly on the surface by bisection in its parameters,
the ends are placed exactly on the boundary edges, and a cubic is fitted
through the points (a line when they are straight).
Tests: a box top split by a plane (areas 80/120, later booleans still
work); a cylinder side split by a sketch line; a cylinder top split in
ring and disc by a smaller cylinder's face; a plate top split through
its hole into two equal halves; a tool that misses is an error. Volume
and area are unchanged in every case.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
- IGES import (kernel iges_in, from the published specification). The file
is restated as STEP and read by the STEP reader, so an IGES file becomes
the same Import base feature.
- Manifold solid B-reps (186/514/510/508/504/502).
- Trimmed (144), bounded (143) and bare B-spline surfaces, sewn into a
solid by shared edges, with face orientation made consistent.
- Curves: lines, arcs, ellipses, composite and rational B-spline curves.
- Surfaces: planes, analytic surfaces 190-198, B-spline surfaces,
surfaces of revolution and tabulated cylinders.
- Transformations, units, colours, names (406 form 15 or labels).
Open, insert and drop .igs/.iges like STEP.
- IGES export (kernel iges_out): each body as a manifold solid B-rep on the
surfaces a STEP export uses, with name properties and colours.
ExportCommand to .igs.
- OBJ import as mesh bodies: objects/groups, polygons fanned, negative
indices, bad lines reported.
- STL: binary files whose header starts with "solid" and those with a zero
triangle count read as binary; non-finite vertices dropped; facet
normals ignored; ASCII with non-UTF-8 names. Mesh bodies get consistent
winding (stray inverted facets turned over).
- Tests: primitives and features round-trip through IGES (volume, area,
name, colour). Six trimmed patches sew into a closed box. Hostile IGES,
OBJ and STL inputs never panic. STL quirks. The engine exports a design
to IGES and opens it again. All 76 oracle parts round-trip through IGES
with matching volume (local check).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
- Bodies carry a Paint for export (opacity, per-face colours), set by
Document::painted from the appearance model. The paint comes from a
face appearance, a body/component appearance's opacity or a material
look (Glass), and falls back to the paint the body was imported with.
- STEP: faces with colours of their own get a STYLED_ITEM on their
ADVANCED_FACE. See-through looks add SURFACE_STYLE_TRANSPARENT.
Differently coloured pieces are not merged into one face.
- STEP import reads face styles and transparency. The reader tracks the
file face of each kernel face (bands split in two keep it), so imported
face colours land on the right faces; the design shows them through
face_colors.
- 3MF: per-triangle colours (pid/p1 into the base materials, carried
through welding) and the object's alpha (#RRGGBBAA).
- Tests: a blue half-transparent cube with a red top exports, re-imports
with its opacity and the red top face, and keeps it when opened and
exported again. 3MF writes the alpha and the face entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
- Surfaces of revolution follow ISO 10303-42 strictly in reader and writer: the
normal is d/d(angle) x d/d(profile), the opposite of truck's revolution, as
Fusion reads it (fixes Fusion inverting fillet sphere corners and the dimple).
step_orientation_errors flags faces whose loop runs clockwise round their
normal; engine, oracle and kernel round trips assert it.
- Revolved faces with analytic profiles are written as native PLANE,
CYLINDRICAL/CONICAL/SPHERICAL/TOROIDAL surfaces, and the pieces truck splits
a revolution into are merged back into one face per surface (with a seam
edge for full turns), so face counts match other systems.
- A horn torus (tight sweep: tube touching its axis) is written as one rational
B-spline face whose loop passes the pole twice, with truck's zero-length pole
edges collapsed, instead of a degenerate surface of revolution; the reader
starts such figure-eight loops off the pole (Fusion's own part 50 now imports
with the right volume).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Our writer assembles the file: AP242 header and contexts, a product per part
(or per component, with NEXT_ASSEMBLY_USAGE_OCCURRENCE and item-defined
placements per occurrence), named solids, surface colours as styled items,
ADVANCED_FACE for advanced breps. truck now writes only each body's B-rep,
which we copy in; identical geometric entities are written once and shared.
Designs with components export as assemblies (bodies in component frames).
step_validate also rejects unreferenced non-root entities. Tests: the sample
plate and a component assembly export, validate and read back with no
warnings and the same volume, area and face counts; the oracle replay checks
area and faces too (39 rebuildable cases pass). An independent Part 21
parser (step-p21, run outside the repo) accepts our exports.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
Vendored truck-stepio (writer only): an intersection curve's second surface
was written at the first one's id (duplicate ids for every boolean edge),
and revolved surfaces lost their orientation (the modeling-surface sense was
always true; inverted or mirrored revolutions came out inside out). Export
now writes intersection edges as C1 cubic B-splines through exact points,
so files carry plain 3D curves and no redundant surfaces.
Vendored truck-meshalgo: hinted parameter searches that run off a B-spline's
domain are rejected or clamped (meshing their grid never finished).
STEP import: straight revolution profiles are bounded in the meridian plane
and stop at the axis; loops on revolutions start away from the axis; face
extents include edge end points. Edges between pieces of one split surface
draw as seams.
kernel::step_validate checks a STEP file (duplicate ids, dangling
references, dense numbering); the oracle replay now exports every rebuilt
part, validates it and re-imports it with no warnings and the same volume
(all 32 rebuildable cases pass).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL