mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-07 20:41:50 +00:00
e4844753cb9a836cda170d2a416412b3ca875ff1
15 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b1326e48d6 |
brep: an edge on a line, circle or ellipse carries the NURBS form SMLib holds it as
The edge mirror of a face's NURBS form: Edge::nurbs keeps the NURBS SMLib holds a conic as, and the edge's interval and its pcurves' parameter are its. Edge::parameters is the curve every evaluation at a parameter uses; curve says what the edge is. Nothing writes the form yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b6a3b31ff9 |
brep: a face on a cylinder, cone, sphere or torus carries the NURBS form SMLib holds it as
Every SMLib analytic surface is a B-spline surface, and its trims are in that NURBS's parameters. Face::nurbs keeps that NURBS: the face's domain and pcurves are its (u, v), and `surface` says what the face is. Face::parameters is the surface every (u, v) evaluation uses. The SMLib conversion reads and writes it (fork FACE_NURBS_FORM, SMLIB_REV c4483f6); the STEP writer writes the analytic surface and the face's edges without pcurves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt |
||
|
|
667cd33560 |
solidcraft-smlib: pcurves on B-spline faces are SMLib's own UV trims
On a B-spline face the export now takes each edgeuse's UV trim exactly as
SMLib holds it (EDGEUSE_NURBS_*): the face's parameters are SMLib's, so the
trim is the pcurve, reversed exactly (knots t -> a + b - t) where the
edgeuse runs against its edge, its knots mapped onto the edge record's
parameter where that is an affine image of SMLib's (identity for spline
edges, as_line's map for NURBS lines, the C side's map for lines), and its
end knots set to the edge interval where they differ by rounding (within
the schema's 1e-9 slack, else an error). Changed: export.rs `curves`
(per-edge TrimMap), new `trims`, `mapped`, `Face::given`,
`Edge::trim_map`, the face loop in `native_ordered`, `pcurves`,
`outer_area`.
Analytic faces and circle/ellipse edges keep brep::pcurve: SMLib keeps an
analytic face's trims on its internal rational B-spline (SmCylinder.h:
"the map from one to the other is not linear") and a circle's on its NURBS
parameter, neither an exact B-spline in the record's angle parameters.
brep::pcurve::EdgeUse gains `given`, a producer's exact pcurve taken as it
is (structure and interval checked, moved by whole periods with its loop,
never refitted). Existing construction sites (STEP reader, tests, truck
adapter) pass `given: None`; the existing tests change only for the new
field.
Built, clippy-clean and tested on
|
||
|
|
19cf3d94ac |
brep: orientation, not position, tells a face's outer loop
truck lists some planar faces' hole loops before the outer loop, so the rule that loops[0] is the outer loop never held for truck's records; moving it in the adapter would renumber those faces' edges, which history and names stand on. The outer loop is the one that runs counter-clockwise about the natural normal (what check_geometry checks); the order is the producer's, and SMLib's import puts the outer loop first, as SMLib needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
204b650d9f |
brep: check_geometry refuses loops that enclose no area or turn the wrong way
check_geometry compared pcurve ends with edge points only, so a loop whose pcurves enclose nothing passed whenever its uses lift onto the same 3D points: SMLib's hole-rim fillet with both seam uses on u = 0, or a whole sphere whose pole-to-pole seam has both uses on one line (the continuity check skips the pole). Such a face has no surface and meshes to nothing. Each edge loop's signed (u, v) area is now taken over 16 points of each use's pcurve: a loop within 1e-9 of the square of its extent of zero is refused (Problem::Empty), and each face must have exactly one counter-clockwise loop about the natural normal, the rest clockwise (Problem::Turn). Outer and inner are told apart by area rather than position, because the truck adapter writes a face's wires in truck's order, which puts a hole first on some planar faces of the sample designs (the model cache test caught it). Fixtures: the pole-to-pole seam on one line, and a sheet whose plane axes are swapped so its loop runs clockwise; a seamed sphere passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
27e996c44d |
brep: splines closed on themselves are periodic for pcurves and tessellation
A spline surface whose first and last control rows coincide in a direction (points and weights, knots clamped there) closes on itself, but the display evaluator's domain gave NURBS no period. The pcurve routine therefore never moved a seam's second use by a period: SMLib's fillet round a hole's rim (a rational spline closed in u) got both seam pcurves on u = 0, its outer loop enclosed no (u, v) area, the decoded face tessellated to nothing and the body measured 6839.73 mm3 against SMLib's 6989.00. tess::uv::closed detects the closure from the record, within 1e-9 of the control net's extent; the domain then has period = the active knot interval's width, and the evaluator wraps a parameter outside that interval back inside it, so pcurves, projection, sizing and the display plan all treat the direction as periodic. The record stays as it is. The display plan hands both evaluators wrapped parameters (the GPU does not wrap). Tests: a rational torus band, a NURBS cylinder and a NURBS dome with a pole, each bounded by a seam used twice, get seam pcurves one period apart; a record of the band passes check_geometry and its seam uses on one line are refused; SMLib's hole-rim fillet decodes with every face meshed and SMLib's volume within 1e-3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
8f31c088f4 |
brep: a budget of 4,000,000 items, pcurves included
Schema 4 counts every pcurve's control points as items, which schema 3 had none of, so a body that fitted the old 1,000,000 no longer does (a STEP plate of 50k faces). The binary envelope's own caps on bytes, values and entries per collection still bound a body. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
30f5e617cf |
brep: document check_geometry in the validation section
What it checks, against which tolerance, its bounded cost, that it is a spot check rather than a certificate, and that the truck adapter runs it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
c06c3cb2cd |
brep: cones narrowing along z, and a linear duplicate-shell check
SMLib makes narrowing cones with a negative half angle; schema 4 now takes them as they are (r(v) = r0 + v tan a already covers them), with the natural v range below the apex. The check that no region lists a shell twice was quadratic per region (about 1e9 comparisons at the caps); it now remembers the last region that listed each shell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
95b2295160 |
kernel: the truck adapter writes pcurves through brep::pcurve
The adapter now saves each edge's curve record and interval when the edge first appears and computes every face's pcurves from those records and the surface record it writes, with the shared plain-record routine; the truck-face version in native/pcurve.rs is deleted. Loops go in as the record keeps them (relative to the surface), so a reversed face no longer has its wires reversed and back again. The native tests are ported (adapter::face_alone gives one face's record for them). The cut body's intersection pcurves now fit at 1e-6 mm too: the routine follows the converged intersection, and truck's own points are within its 1e-6 mm of it, which the lift check allows for on intersection edges. docs/native-brep.md says where pcurves come from and what a surface intersection's point at t is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
02abecd52c |
brep: docs for schema 4, and a pcurve lift test
docs/native-brep.md describes schema 4: the records with their SMLib and STEP names, regions and shells, the loop convention, every edge's uses, the canonical order (a reversed face walked backwards), each curve and surface kind's parameterisation, pcurve rules, validation, history, the SCBREP05 envelope and what the truck adapter writes and refuses. A test lifts every pcurve of a box, a filleted box with a bore and a sphere onto its face and checks it is its own edge at both ends and the middle; with a reversed face's pcurves left in truck's oriented order it fails by 30 mm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT |
||
|
|
93a2922c5e |
Display plans and the model cache for large STEP assemblies (#2814) (#4021)
* STEP import: mesh band faces that wrap around a closed surface correctly Fixes two related bugs that caused SolidCraft to fail to mesh a face wrapping all the way around a closed, non-periodic surface (a revolved ridge band, or a tube-shaped B-spline surface): a missing face, a folded/self-overlapping triangulation, or a hole in the rendered body. 1. Ring edges that run slightly off their surface. STEP files write a surface and the curves bounding it to the file's own declared uncertainty, so a ring edge can legitimately sit a few micrometres off the surface it bounds. The code finding where a ring edge crosses the planned split line compared against the edge's raw (off-surface) point using a hardcoded tolerance far stricter than the file's own precision, rejecting a valid cut. Now matches against the point on the surface directly below the ring crossing, respecting the file's declared tolerance. 2. Closed B-spline surfaces, their slits and their slivers. truck only treats the rotation of a revolved surface as periodic; a face on a B-spline surface whose control net closes on itself (a tube) wasn't recognized as wrapping around at all. This produced a folded face, a hole (a boundary loop landing a full period away from where it should cut), and one near-zero-width sliver face costing the entire body its tessellation plan. Adds periodicity detection for closed B-spline surfaces, period- aware hole-loop alignment, dropping of zero-width slit boundaries, and isolating a too-thin-to-triangulate face so it no longer takes the rest of the body down with it. The GPU shader's periodic wrap mirrors the CPU math so the two don't drift apart. Regression tests: band_faces_split_when_ring_edges_run_off_their_surface, band_face_with_a_zero_width_slit_is_still_meshed, a_sliver_face_too_thin_to_triangulate_does_not_cost_the_body_its_plan. Ablation-tested: disabling either mechanism in (2) reproduces its target test's original failure message while leaving the other test unaffected. * STEP import: mesh the band's split faces, not just split them jyviko asked whether a band wall missing from the interactive app's render was a shading issue or a genuinely missing face. It was missing: the ring- edge split fix made the band's topology correct, but plan_face still produced zero triangles for the two new sub-faces. Two causes, both in plan_face (crates/kernel/src/tess/mod.rs): 1. The tolerance for how far an imported boundary may lie off its surface was tied to the requested mesh's chord tolerance instead of to the body's own size. A finer display tolerance could then reject a boundary a coarser one had just accepted, so the face would drop out exactly when zooming in on it. 2. Near a sharp corner of this flat-panelled band, several boundary points that are legitimately a few micrometres apart (within the STEP file's own declared precision) all projected onto the same surface point. The boundary loop then repeated a point in (u, v), which a triangulator can't follow, so the face's plan failed outright. One face without a plan costs the whole body its plan, and the fallback mesher can't handle a closed B-spline surface either, leaving both halves of the split band with no triangles at all: present in the topology, invisible in every renderer, and absent from the body's volume. Fix: scale the off-surface tolerance to the body's own extent (new Rules::extent), and collapse a run of boundary nodes that land on the same surface point to one (drop_collapsed) before triangulating. Regression test additions to band_faces_split_when_ring_edges_run_off_their_surface: the body's volume now matches the analytic pentagon prism (was a third of it - only the top cap - before this fix); display_plan leaves no face without triangles from coarse to fine tolerance; and the actual tolerance and facet angle the viewport draws with gives the split band its full lateral area on both halves. Confirmed on review_band_big.step, the fixture behind the review screenshots: volume's relative error against the analytic pentagon prism drops from 1.4e-4 to 4.1e-6, and the band wall now occludes the floor grid behind it in a snapshot the same way the top and bottom caps always did. Full kernel suite: 739 tests, 0 failed. * STEP import: hold an imported boundary to the file's own tolerance, not the part's size jyviko: the off-surface acceptance added in the last commit (rules.extent * OFF_SURFACE) scaled with the part, so a 2 m part silently accepted a boundary 2 mm off its surface while a 20 mm copy of the same part accepted only 0.02 mm. A STEP file's own declared uncertainty does not grow with the part, so the bound shouldn't either. Body gains boundary_tol: Option<f64>, Some(file's declared tolerance) for a body read from STEP (crate::step_in::Units::tol, already used by the ring-split fix in split.rs), None for anything this kernel built. It's set before the import's own unmeshed-face check runs, and carried over by the two face-split operations that re-cut an imported surface (splitface.rs, outline.rs), since the file's uncertainty still describes those boundaries afterward. plan_face's accept now reads: the file's own tolerance when the body declares one, or the old chord-tolerance/extent bound otherwise. The old bound's tol * 10.0 term had to go too, not just the extent term: at a 2 m part's typical display tolerance that term alone is ~20 mm, nearly ten times the extent bound jyviko objected to, so keeping it would have left the exact 2 m/2 mm example still silently accepted. Two new tests in step_in/tests.rs: - band_faces_split_when_a_scaled_copy_runs_off_its_surface: the same band fixture at a realistic 2 m scale with the same absolute slop and declared uncertainty as the small one, meshes the same way at every chord tolerance from coarse to fine. - off_surface_budget_follows_the_file_not_the_part_size: the actual regression. The same off-surface error is accepted or refused the same way whether the part is 20 mm or 2 m; a 2 mm error against a 1 µm declared uncertainty is refused at both sizes now (accepted at 2 m under the old bound, refused at 20 mm - that asymmetry was the bug). Checked at both a coarse and a fine display tolerance. Known gap, not fixed here: a STEP-imported body saved into a .solidcraft design and reopened loses boundary_tol (native.rs's decoder has no field to read it from) and falls back to the old extent-based bound on reload. Today's design format has no concept of this value at all; persisting it is a real format change, out of scope for this fix. Full kernel suite: 776 tests, 0 failed. clippy -D warnings clean. * Display plans for STEP threads and pointed ends (#3847) Three faces cost a threaded or pointed STEP part its whole display plan, so it was drawn from the reference mesh: cracked, faceted, and never refined up close. - A loop through a pole (a cone's apex on the boundary) seemed to wind around the surface: the jump at the apex, unwrapped to the nearest copy, was arbitrary. The loop is turned to end where it leaves the pole, so the step that closes it is an ordinary one (`seam::close_through_pole`). - Projection onto a spline with hundreds of spans (a helical flank) restarted from a 16 x 16 grid and settled on another turn; it now starts from two points per span, capped. - Sag refinement no longer inserts a point exactly on a boundary side, which split the side and failed the face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tess: a refused face's edges are divided finer and it is planned again (#2814) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * tess: sliver faces plan empty; a face no method plans is filled flat from its nodes (#2814) A face whose boundary runs out and back along itself (two edges between the same vertices, a rounding apart, as STEP files write slivers) encloses nothing and is planned with no triangles. A face still refused after its edges are divided finer (a cusp, a sliver whose sides sag across each other, a small cap at a pole) is filled flat from its shared boundary nodes, so the rest of the body keeps its display plan instead of falling back to the reference mesh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * display mesh tests: a rim the chord tolerance leaves a line is divided again (#2814) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * portable B-rep: up to 8192 control points along a spline direction (#2814) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * solidcraft-cli: --debug logs what the engine reports to stderr Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * STEP import: a spline written over a tiny parameter range is read over one (#2814) Some writers parameterize splines over a few millionths (a humanoid robot's edges span 5.4e-6). The kernel tells parameters apart only to 1e-6, so over such a range it evaluated the curve's end wrongly, and trimming an edge to its vertices dropped a control point: the bodies could not be stored exactly and the whole model cache was left out of the design. The knots are now stretched to span one; the curve is the same and edges are trimmed by their vertices, so nothing read from the file refers to the old parameters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * model cache: a body keeps its file's declared accuracy (#2814) A body restored from the cache dropped the accuracy the file it was read from declares, so the planner held its boundaries to the kernel's own bound and a reopened design drew differently from the import. It is stored with the body's runtime values, and only when there is one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * tess: a boundary within a hundredth of its body lies on its surface, at every quality (#2814) #1721 held a body read from a file to the accuracy the file declares. A writer declares what it aims for, not a bound its edges keep: a 251-body robot assembly declaring 0.01 µm has boundaries 10 to 30 µm off their surfaces, and 95 of its bodies lost their display plans. The bound is a hundredth of the body again, what Normal quality always allowed, and now the same at every quality, so zooming in still never drops a face (#1721's point). A file declaring more raises it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * STEP import: keep faces whose edge vertices sit slightly off their curves Some exporters write an edge's vertex beside the end of its curve, further off than the accuracy the file declares (a surface export declares 0.01 mm and puts a vertex 0.19 mm off two spline ends). The reader refused such edges and left the face, and with single-face bodies the body, out. A vertex may now sit up to the file's tolerance or 1/1000 of the model's size (the diagonal around its VERTEX_POINTs), whichever is larger, never more than the existing 1 mm ceiling. Spline and open-conic edges have the end control point moved onto the vertex, so every such edge meets its vertices exactly. The file's declared accuracy still governs closed-curve, seam and circle checks. * tess, io: review clean-ups (#2814) - A sliver face is one whose loops are all slit: drop_slits finds it in the retry after a failed plan, so the pre-check on every face (folded) is gone. - The flat fill uses cdt::triangulate (a lenient fill that leaves out sides crossing one already kept) and its flood-fill inside test, and the plane from geom::Plane. - Refinement only builds its indexes when a face was refused, takes the faces to plan again from edge_faces, and re-samples edges on the pool; Rules::finer for the finer rules. - One PackageWriter::fits for the model cache, the preview and the thumbnail. - The band split's ring tolerance lives in split.rs; comments say what the off-surface bound is now. - The CLI logger prints Level::as_str(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM --------- Co-authored-by: David Lackovic <lackovicdavid1234@gmail.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Ray <raykholo@users.noreply.github.com> |
||
|
|
d0afc31632 |
Save the evaluated model with every design: exact, stored once, scaled caps (#3053) (#3093)
* Model cache written by default: exact bodies, stored once, scaled caps (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * bench-open: say why bodies were not kept * Exact planes and revolutions; budgets for large bodies (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Native B-rep: rebuild shells as the kernel made them (closed and seam edges) (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: the model cache and the exact native B-rep (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * bench-open: time a save without the cache too * Model cache entries are Deflate-compressed (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Model cache: one that does not fit the design file is left out, never a failed save (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
913583bfee |
Show a bounded native-cache preview before authoritative reconstruction (#463)
Refs #303. file.save / file.save_as can store a warm native geometry cache (cache: true, off by default) keyed to the document hash, app version and adapter revision. Opening shows a bounded preview from it until the rebuilt session replaces it. Any cache failure counts as a miss. |
||
|
|
9f88537927 |
Define a kernel-neutral native B-rep format and guarded adapter (#461)
A SolidCraft-owned portable B-rep schema (geometry schema 2 with analytic curves and surfaces, explicit frames and ranges, fitted intersection curves) and a versioned body envelope (SCBREP03) with its own metadata records, a bounded binary codec, a validator with resource budgets, and an adapter that refuses what it can't convert exactly. Pinned by a box-and-fillet golden fixture. Not yet written by application saves. Refs #303. |