mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
release
446 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dc38349cbf |
Layer split stage 1: the B-rep record, native body file and Vec/Plane come from solidcore-record (#11496)
* Kernel: the B-rep record and the native body file come from solidcore-record SolidCraft's portable B-rep record (`solidcraft_geom::brep`: brep.rs, topology, history, envelope, part, curve2) and the native body file (`solidcraft_kernel::native`: the tagged binary codec and the container) move to solidcore's `solidcore-record` crate: geometry as data, with structural validation and no maths (solidcore's docs/layers.md, stage 1). Every caller uses `solidcore_record::brep` and `solidcore_record::native`; the old copies are deleted. What computes from coordinates stays with the kernel, in geom's `brep`: - `brep::check`: the record's geometric checks (orthonormal frames, non-degenerate planes, unit revolution axes, non-singular placements). `check::validate` and `check::validate_built` are the record's validation and these together, and every caller of `Brep::validate` uses them, so what is accepted is unchanged; - `brep::label`: a surface's natural ranges (a cone's apex), `curve_interval`, `edge_curve_interval` and `form_turns`; - `brep::pcurve::eval`: a pcurve's point; - `geometry::check`, `placement::moved` and `split_edges` become functions on the record. The kernel's native module keeps the conversion between a body and its saved record (metadata.rs) and checks the decoded record's geometry. `native/analytic.rs`, which no module declared, is deleted. solidcore-record is pinned to its rev on solidcore's `layer-record` branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT * Geometry: Vec2, Vec3, Plane and the profile types come from solidcore-record The plain data of vectors and points, sketch planes and planar profiles (`Seg2`, `Loop2`, `Region2`) moves to `solidcore-record`, with its constants and component-wise operators only (owner decision, solidcore layer split). Their geometry stays in geom as traits: `Vec2Geom`, `Vec3Geom`, `PlaneGeom`, `Seg2Geom`, `Loop2Geom`, `Region2Geom`, `Seg2ArcFit`, `Loop2ArcFit` and `Region2ArcFit`, in scope with `use solidcraft_geom::prelude::*`. Every caller names the types from `solidcore_record`; geom no longer defines or re-exports them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT * Kernel: pin solidcore-record to solidcore main f5e8e9a29 solidcore-record is merged on solidcore main (tangency-labs/solidcore#2519); its crate there is identical to the rev pinned before but for its clippy configuration. The lock is regenerated against the merged rev. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9c65a30265 |
Kernel rig: export each case's kernel operations as a corpus for solidcore's rig
`cargo xtask kernel-cases [--ladder] --export DIR` runs the rig with the kernel's operations traced (`smlib::trace`) and writes one entry per case: the operation, or chain of operations, that made the bodies the case ends with; each operand that came in as a record, as that record; the parameters, with every pick by points also given as the topology index it picked; the fix set; the case's expected values and the rig's result. A case no kernel operation alone accounts for is written too, marked not expressible with its reasons. DIR/manifest.json counts them per area. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
55a5f86e2e |
Kernel test: the 851 middle point's y written at f64's shortest digits (clippy excessive_precision)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
47c685d471 |
rustfmt on the pin's changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
c7c28515ce |
Kernel test: the tabbed plate's rim and corner arc take off the rim's closed form
The rim alone was pinned to 7.958674274526857, the marched end rib's value; its closed form, the spandrel (1 - pi/4) r^2 revolved about the disc's axis outside |x| < 4, is 7.958672911836132, which the rim's End (fillet_blend_corner) builds to 5e-8. With the corner arc's Pappus volume the two take off 8.117808752; solidcore 8ddce9016 removes 8.117808800. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
ac3e6e0105 |
A panic solidcore's guard catches reaches the engine as EngineError::Internal
smlib's run() catches a solidcore panic and replaces the kernel (kept: the app does not die natively), returning Error::Internal; the kernel turned it into KernelError::Failed and the engine into Other, so hostile_params_never_panic and the fuzzer no longer saw kernel panics. Now the kernel's wrapper errors go through KernelError::smlib, which keeps Internal; a body the wrapper could not take because of a panic is Internal too. A feature whose evaluation hit it records the result as internal (not cached: the kernel was replaced), and the command that made it, or any command whose kernel call returns it, fails with EngineError::Internal under its own id. Crash reports mark a panic inside a solidcore call recovered (kernel::guarded), and the crash test's kernel mode panics inside a solidcore call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
440fdaed13 |
Kernel work runs on threads with solidcore's 8 MiB stack
solidcore's algorithms recurse (subdivision, intersection, filleting) and its README asks for 8 MiB, a native main thread's stack; Rust starts other threads with 2 MiB, and only the web build set more. kernel::KERNEL_STACK is 8 MiB and kernel::kernel_thread a thread builder with it. The kernel's parallel map runs on a pool of its own with that stack (on a worker of a pool already, as in map_with_local_pools, it stays on that pool as before), the one-thread mesh pools and map_on_threads' helpers have it, and so do the engine's interference worker and the evaluator's plan-mesh thread. A test recurses 5 MiB deep on each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
ae45cc6623 |
smlib and kernel: solidcore's refusal reasons carried through as typed errors
A shell, offset or thicken solidcore's one construction refuses because a face's offset folds (SolidError::ShellRefused, ShellRefusal::Fold) is Error::WallFolds with the largest wall it names; the kernel says "the wall is thicker than a face curves, so the face's offset would fold; the largest wall that works is 10 mm". A blend too large carries solidcore's NoRoomReason as Error::TooLarge's RoomReason, worded in its message before the largest size that fits: a collapsed offset (solidcore#2431) says the ball is as wide as the face it rolls on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
a3f06a4f94 |
Tests: a wall that folds names the largest wall that works, a collapsed offset says why
A thicken of solidcore#2350's parabolic sheet past its apex's radius of curvature is refused by solidcore's one construction (ShellRefused, Fold, naming 10); the wrapper passed it on as "solidcore: ..." with no words of its own. A round at the radius of the round it rolls on beside the 851 part's arc wall is refused as a collapsed offset (NoRoomReason::OffsetCollapses, solidcore#2431); the message named the size but not why. Both fail until the reasons are carried through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ |
||
|
|
8a730c6273 |
Fillets on solidcore: the largest size that fits is still the one the search builds; the kernel's names the edge
solidcore's fit is the largest size less a margin (34.99 where the L bracket's bend builds 35, 4.29 where #3435's ring builds 4.3), so the feature's search keeps naming the size, and solidcore's is the answer only where the search finds none. The refusal keeps its words, "larger than the faces beside its edges allow", with the edge after them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz |
||
|
|
881899bee1 |
Fillets on solidcore: a blend too large names the largest size that fits and its edge, from the kernel; the crossing-rails mixed corner is not supported (solidcore#2256)
solidcore sizes a blend it refuses as too large (the session's fit, SolidError::FilletRefused under fillet_gn_section_generator). The smlib crate gives it as Error::TooLarge with the size, what it sizes and a point halfway along its edge (an equal chamfer's ball radius turned back into its distance); the kernel words it as the fillet feature's search does (largest_fit_note), and the feature runs no search when the refusal names it. The 3x2 mixed corner whose rails cross, which solidcore refuses (fillet_refuse_mixed_corner_crossing_rails), is not supported yet, citing solidcore#2256. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz |
||
|
|
c5ccb9201d |
Wave 43/45: smlib: rustfmt after the C++ kernel's removal (and 1 more)
2 commits, squashed; the originals stay reachable from branch jyviko/merge-main-develop-solidcore (#11360). - |
||
|
|
28e983e191 |
Wave 39/45: diagnostics.kernel takes a typed contract, as main's other diagnostics commands do
1 commits, squashed; the originals stay reachable from branch jyviko/merge-main-develop-solidcore (#11360).
-
|
||
|
|
ea772a0438 |
Merge develop into main: SolidCraft on the SMLib kernel (v1.1.0-alpha) (#11351)
* UI: rim and wall picking tests for a circle of arcs that is one wall A circle drawn as two half arcs now extrudes to one full-turn wall with its seam edge, and each rim is one closed edge (tangency-labs/smlib#67). The tests keep their intent on that body: a rim clicked once is one item, measured and highlighted whole and picked from either side; a wall picked either side of its seam is the same surface, lit whole, and toggles off from the other side; every face is its own patch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fillets: a later round at a corner where earlier rounds meet blends them again with it The owner's ruling of 2026-10-30: a later fillet on an edge at a vertex where earlier rounds already meet in a mitre makes the corner rounding all of them at once makes, one smooth patch, the mitre gone. A body made by constant-radius rounds keeps what they were made on and which edges they rounded at which radius; a later round reaching such a corner blends the earlier rounds' edges (from the earliest of those meeting there on) with the new edges in one SMLib blend of the body before them. The result's history is put over the body the later round was given: an earlier round's face is that face trimmed anew (its name kept), the corner patch and the new rounds are the later fillet's. A picked mitre seam has no round of its own: it is refused unless a new edge of its radius meets it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: a later r3 round at an r2 mitre, through the Fillet feature, makes the all-at-once corner Two box edges rounded r2 in one fillet, then the upright edge at their mitre r3 in a second fillet (tangent chain on, so the seam comes with it): 10 faces, 7928.743041 mm³ as fillet_radii of the box gives, the r2 rounds' names kept, the r3 round and the corner patch the second fillet's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Kernel: a body's record of rounds goes with its history, and can be read back The rounds that made a body are over the body its history is over: a new history (an operation carrying a step's result back to its own operands) leaves the record out, so a later round never reads one history against another body. Body::rounds gives the record and Body::with_rounds_of takes one back, checked (edges of the body before them, a fillet's radii, the body's history over that body), for a saved model to keep it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Model cache: a body made by rounds keeps them, so a later round blends them the same after opening A design gave another body when opened from its model cache: the rounds a body was made of lived only in memory, so a later round at their mitre was built sequentially (or refused, as too large) after opening and blended with them when evaluated anew. The saved model now records, per kernel body made by rounds, the body they were made on (a place in the bodies, stored once though no state holds it) and the edges with their radii; reading it attaches them back down each chain, and a thinned model keeps those bodies with the bodies made from them. Model cache format 7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: a corner patch of rounds 2/3/4 takes no more than the strips leave in its block three_rounds_at_a_corner bounded the patch's extra cut at 3 mm³ beyond the strips, a figure with no derivation; SMLib's all-at-once body at radii 2, 3, 4 takes 3.099 (V 7878.579574 against strips 7881.678). That body is the strips' outside the block its patch lies in: its rounds are the strips' cylinders ending at a's section y = rc, b's x = 20 − rc and c's rails where they meet a's and b's (the patch's four corners, now asserted), so the patch takes at most what the strips leave in that block (11.86 at 2/2/3, 32.69 at 2/3/4). The lower bound is that block; the upper one, at most the strips' volume, stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: the io crate's hand-built saved models list their rounds, as format 7 records do Model cache format 7 ( |
||
|
|
28e26209f3 |
Assembly Move: drag allowed joint freedoms (#6679) (#6904)
* feat: add joint-aware assembly Move handles and live motion readouts * test: improve render job failure diagnostics * fix: preserve assembly Move compatibility and viewport theme colors * ci: skip default label sync for fork PRs * fix: keep Windows CI checks portable after main rebase * test: cover RTL assembly strings and stable refraction job * i18n: cover assembly Move in Vietnamese * i18n: cover assembly Move in French * fix: complete CGA palette for assembly motion handles * fix: complete Assembly Move palettes and Turkish strings * fix: retain assembly move cache answers and toolbar help * style: format merged assembly Move navigation guard * fix: align Assembly Move preferences and tests with main * Fix offline update fixture synchronization and clipped prism intersections Wait for all v5 manifest requests before deploying v6. Intersect hole-free prism sections over their common height, retaining the established B-rep path for newly clipped perforated sections. Preserve the failing property seed and add closed-form and cap-boundary regressions. |
||
|
|
7734ba2300 |
Offset Face: split a periodic face off its seam first, as booleans already do (#6226) (#6227)
* claim: #6226 * Offset Face: split a face off its seam and try again, as booleans do (#6226) A hole's wall arrives from a STEP file as one whole turn bounded by one wire that runs along its seam twice, where SolidCraft builds the same wall as two halves. Offset Face heals the body before it moves anything, and healing such a face fails inside truck with `This wire is not simple`, so the panic guard turned the whole operation into a kernel internal error. Since importing is how a part with a hole usually arrives, Offset Face did not work on imported parts with holes at all. `seam_faces` already answers this for booleans: split every such face in two along a plane through its axis, run the operation, and compose the history back over the body as it was given. This gives the offset path the same retry. - `seam_faces::one_body` is `boolean`'s answer for an operation that takes one body, with `needed_one` beside `needed`. - `offset_faces` now does its healing, its face picking and its offset inside one closure, so the retry runs the whole of it on the split body; the first attempt is guarded, so the failure is an error the retry can act on rather than an unwind. The faces are picked per body, because the pieces are numbered differently once a face is split — the points do not move, so they pick the same surface either way. The regression test is in `seam_faces_tests.rs`, beside the boolean ones: a 20 x 20 x 10 block with a 6 mm hole, read back from STEP, whose top moves 2 mm and lands on the same volume as moving the built body's top, with its history intact. Checked against `main` before and after: the three kernel tests that fail here (`blend::…seam_twice`, `step_in::…parallel_body_preparation`, `tests::a_box_cut_round_a_small_hole…`) fail the same way without this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: CarlosKeese <dudukeese@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
743accca7a |
Triangulation: split a crossing boundary segment ourselves, never in spade's assert (#4031) (#5409)
* claim: #4031 * Triangulation: split a crossing boundary segment ourselves, never in spade's assert (#4031) A trimmed face's boundary segment that crosses a constraint already in the triangulation went to spade's add_constraint_and_split, which asserts (cdt.rs:978, `!previous_region.is_empty() || from == final_vertex`) when a split lands so near another vertex or constraint that the piece up to it is blocked. The kernel guard caught it, but the whole operation failed and a crash report was written; a STEP body imported empty or not at all (#4598, also #4293, #4478, #5068, #5229). PolyBoundary::add_constraints now adds every segment that can be added, then splits each blocked one itself: a vertex at each crossing, inserted from the crossed edge's own vertex, and the pieces between added where they can be. A segment with crowded or very many crossings, or a face with more than four blocked segments (a rim that runs back and forth across itself), leaves them out, as truck did before #1653, so the face meshes slightly off instead of not at all. Pinched faces (#1648) still split where they cross. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
30b01e8d79 | A spherical shell revolves into a solid with a void, and cuts and joins with hollow tools work (#451) (#4182) | ||
|
|
f422bb1263 |
Intersection edges divide at 1e-6 when asked for less, instead of panicking (#4623) (#4650)
A join failed with the kernel's internal error "tolerance must be no less than 1e-6" at vendor/truck-geometry/src/decorators/intersection_curve.rs:172: IntersectionCurve::parameter_division asserted truck's minimum tolerance, and the guard turned the panic into a failed operation. A tolerance below truck's TOLERANCE, or one that is not a number, now divides at TOLERANCE: the curve's points are only found to within it anyway. The test divides the intersection edges of a hole cut through a B-spline face at 5e-7, 1e-12, 0, -1 and NaN: each gives the division at 1e-6. Without the fix it panics with the reported message at the reported line. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
edf57cec29 |
Stand up Y-up STEP and IGES files from SolidWorks automatically (#5191)
* claim: #5150 * Stand it up: don't offer it for SolidCraft's own STEP files (#5150) STEP replies from file.insert_step and doc.open now carry the file's originating system, and the window skips the stand-up offer when it is exactly SolidCraft, as SolidCraft's STEP export writes it. * Stand up Y-up STEP and IGES files from SolidWorks automatically The STEP header (originating system, preprocessor version, FILE_DESCRIPTION) and the IGES global section's native system ID say which program wrote a file. A SolidWorks header means Y up: the import's component is stood up as it opens, as one undo step, and the message offers Undo. SolidCraft's own files are left alone; an unknown exporter keeps the offer. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Ray <raykholo@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
b0048a6104 |
Mesh bodies: measure their size once, when made (#4662) (#4663)
`Body::size` walked a mesh body's vertices on every call, and it is asked for every frame. A mesh body's triangles never change after `mesh_body` or `mesh_transformed` makes it, so both now measure it then (`TriMesh::span`) and `size` returns it like a B-rep body's. `ModelBody::mesh` returns a mesh it already has before working out the display tolerance. Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
688124937d |
Chamfer: a cutter that would reach into a nearby wall is not used (#4539)
* Chamfer: a cutter that would reach into a nearby wall is not used A straight chamfer's cutter reaches several times the chamfer's size out from its edge, and further on a larger part. On a pin standing in a socket, that far side lands inside the socket's walls, and the cut took bites out of them. A cutter whose far side is inside the part is now refused, as one whose ends are, and the chamfer is built locally instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Robustness goldens: seed 9 re-recorded for the chamfer cutter check A chamfer in seed 9 was refused because its result changed more of the part than a chamfer can. With its cutter refused, it now builds locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2fdc105d87 |
IGES import: separate lumps of one part are separate solids (#3928) (#3972)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
e94900933d |
IGES import: close small gaps between trim pieces (#3845) (#3846)
Consecutive pieces of a trim loop can miss each other by micrometres to a tenth of a millimetre, past the file's resolution, so the loop did not close and the face was dropped. Faces are now read first and sewn after: each small gap's ends, and the other piece ends at them, move to one vertex. Gaps over 0.1 mm or not small next to their pieces stay open; the sewing tolerance stays the file's. The stated accuracy covers how far a vertex moved, so the STEP reader keeps those edges. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
79a843c963 |
Transform: refuse a matrix that is not an affine placement (#3900)
transform_matrix took any finite 4x4 matrix. A perspective entry in the last row, or a singular linear part, was built into faces that mesh to nonsense: a revolved ring placed with such a matrix measured hundreds of millions of mm3, and a boolean on it ran for over half an hour before failing with "no result". Matrices come from design files and command parameters, so they are refused with KernelError::Invalid instead. |
||
|
|
1bcc3602b4 |
STEP export: a merged cylinder or cone face's loop starts on the side its angle grows from (#3394) (#3557)
* claim: #3394 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * STEP export: a merged cylinder or cone face's loop starts on the side its angle grows from (#3394) A whole-turn cylinder or cone face is written as one loop: ring, seam, ring, seam reversed. Readers lay the ring that follows the seam's first use out from the surface's angle 0, so that ring must turn the way the angle grows (anticlockwise about the written axis). Three of the four tapered legs in #3394 came out the other way and other readers dropped their sides. When the second ring turns against the angle, the loop now starts with it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> |
||
|
|
63cb48a4ee |
Measure: a body whose meshes leave a gap keeps its volume wherever it is (#2069)
* Measure: a body whose meshes leave a gap keeps its volume wherever it is
A part ring cut by solid.combine (two prisms through its ends, a notch
through its chamfer) measured 992.03 mm3 at the origin, 999.17 moved
100 mm and 1063.48 moved 1000 mm; it encloses 993.92. The reference
mesher leaves a crack where the notch meets the chamfer's cone: the
volume beside it is left out, and the divergence-theorem sums, taken
about the world origin, close the crack by a cone to the origin, so the
error grows with the body's distance from it.
A closed surface's area vectors sum to zero. When the picked meshes'
sum could move the volume by more than 1e-5 within the body's size, and
the display plan (which shares every edge's nodes between its faces) is
closed, the body is measured on the plan, about its box centre. Every
other body measures exactly as before.
Tests: the cut ring at the origin and moved by up to 10000 mm, against a
grid quadrature of the cut; a revolved ring of cones and cylinders moved
100/1000/10000 mm along each axis (1e-6); a cylinder and a frustum far
from the origin against hand values. Robustness seed 64 moves by 1.4e-6
and is recorded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Measure: a gap the display plan can't close is measured about the box's centre
When the meshes leave a gap and the display plan isn't closed either, the
sums used to fall back to the origin-based volume, which drifts with the
body's distance from the origin (7 % at 1000 mm for the part ring). The
per-face sums are now taken about the fine mesh's box centre, which closes
the gap by a cone to a point inside the body; over a closed mesh nothing
changes. The gap decision moves into gap_closed, and a unit test with a
cube left open by hand covers closed vs open meshes, the closed plan, and
the fallback with an open plan or none.
Seeds 14, 20, 40, 55 and 61 end with such a body and are recorded with
ROBUSTNESS_GOLDENS_BLESS (volumes move by -0.51 %, +0.16 %, +1.9e-4,
+9.0e-5 and -1.5e-5; steps, outcomes and counts unchanged).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Record robustness seed 86 after measuring bodies whose meshes leave a gap
Seed 86 was added on main by #2387 after this branch's goldens were recorded. Its last
body leaves a gap in its meshes, so the measure change here moves its volume by -0.27 %
and its area by +0.13 %; steps, outcome and counts are unchanged. Seed 86 passes
unchanged on main
|
||
|
|
316b49cd84 |
Extrude from a whole face rounded by fillets: join, and cuts whose walls only touch (#2040) (#2041)
* Extrude: a join from a whole face rounded by fillets sweeps the face itself (#2040) A face's sketch fits an edge that is no line or arc (the quarter-ellipses where fillets meet a slanted face) as splines a hair off the body's edges, so a join from the face touched the body along a face it did not match and failed with "boolean Union: no result" after minutes. When the profile is a whole planar face of the one target, the join now sweeps the body's own face, which shares its edges and is sewn on along it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Extrude: a cut into a whole face keeps the walls that cut and moves the others off (#2040) A cut from a whole planar face sweeps walls from each of its edges. Where the face beside an edge turns away from the sweep (n_N . -n_P >= 0) the wall only touches the body along that edge, or lies in the face beside it, and the boolean failed on it with "no result". The cut now keeps the walls that go into the body, exactly (the body's own edges), runs the outline round the others well outside the body, and starts a little out from the face. A cut whose walls all only touch takes a slab beyond its depth plane. Not covered: a cutting wall tangent to the face beside a touching one (a round's wall meeting an end face, as in the issue's part); the boolean still fails there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Extrude: leave a whole face outlined by lines and arcs to the profile's own tool Only a face whose outline the projection had to fit needs the body's own face as the tool. A square drawn over a box's top already meets the body exactly, and the swept face lost the names a sketch on the new top attaches to (stacked_face_tests, #489). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Run the whole cut tool from a face under the kernel guard (#2040) Review: `builder::vertex`, `builder::line`, `Face::try_new` and `builder::translated` ran outside `crate::guard`; only the sweep was guarded. `cut_tool` now runs all of it under one guard. Adds an end-to-end test of a cut into a face the body stands out past below it (the block's top strip between its rounds): it takes only what lies over the face. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: the slab cut of whole_face_tool, on a spline prism (#2040) A prism on a closed spline standing on a wider plate, cut 3 into its whole top face: the slab takes the face's area times the depth and leaves the plate alone. A test-only counter asserts the slab was the tool, so the test cannot pass through the profile's own tool. Shortens the name of the rounded block's top-face cut test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Kernel: the face's outline as a Vec now that polyline returns a closed chain (#2040) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
26c0c19ef7 |
Preserve imported part names and IGES assembly structure (#1609)
Build on #1426's import components and nested STEP assembly instances. Replace placeholder solid labels with meaningful part or instance names. Translate IGES groups and subfigure instances through the existing STEP component path, preserving nesting and placements and sewing surfaces within each group. Bound assembly walks and reuse work buffers. Related to: - tangency-labs/solidcraft#1386 - tangency-labs/solidcraft#1426 Co-authored-by: Codex <noreply@openai.com> |
||
|
|
2f43ad5c5b |
Loft: a circle lofted to a non-star-shaped profile is split once round (#2570) (#3670)
* claim: #2570 * Loft: a circle lofted to a non-star-shaped profile is split once round (#2570) A circle section was split at the lead section's vertex directions, kept in the lead's order and forced to increase by whole turns. A non-star-shaped lead (a C, a comb) turns back on itself, so the circle wound round many times and closed with a negative arc: self-crossing walls and a huge mesh (which froze the web build while meshing it). The circle is now split exactly once round: at the lead's directions when they go round once in order (clockwise on a plane facing the other way), otherwise at the lead's perimeter fractions, evenly as the fallback. Every arc is finite and positive and they total one turn; the unbounded loop is gone. Section preparation is split out as loft_section_loops so the resampled sections can be checked. |
||
|
|
6a8604c55d |
Fix lofts through sections turning past 90 degrees (#2847) (#3452)
* claim: #2847 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * loft: keep sections turned past 90 degrees facing the same way (#2847) The loft oriented each section by the sign of its normal against the first section's, so on planes turning about an axis every section beyond 90 degrees was reversed and the side faces folded. Follow the facing neighbour to neighbour instead. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
d2188fb7ab |
IGES import: a B-spline curve over a tiny knot span keeps its faces (#3885) (#3886)
A curve written over a knot span of 1e-7 or less was refused on the STEP side
("knot vector consists single value"), and both faces it bounds were lost. Its
knots are moved and scaled onto (0, 1), which leaves its shape as it is.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
438abbcf82 |
STEP conformance sweep: the cones to a point read right now (#258) (#3901)
cone_point and cone_point_vertex join the sweep now that #3871 reads a cone bounded only by its base circle up to its point. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
94bd8719aa |
STEP import: a void reads as a cavity whichever way its shell flag points (#3730) (#3872)
A BREP_WITH_VOIDS void's faces face into the cavity, so it encloses volume of the opposite sign to the outer shell. Its ORIENTED_CLOSED_SHELL flag alone doesn't tell: SOLIDWORKS writes .F. over a shell already facing into the cavity, SolidCraft .T., and flipping by the flag added the cavity's volume. A void enclosing volume of the outer shell's sign is now turned inside out. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fc3dedfd5c |
STEP import: regression sweep over the step-conformance parts of known shape (#3880)
* STEP import: regression sweep over the step-conformance parts of known shape Fifteen generated parts from step-conformance (CC0): plain, NURBS, seamed, placed, inch and far-away cylinders, domes, a grooved shaft, a plate with 100 holes, a 3.6 m disc, a 0.5 mm pin, a B-spline half-torus and a washer. Each reads back with its exact volume, its box and its face count. The half-torus on a TOROIDAL_SURFACE and the cones to a point are left out until they read right. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * STEP conformance sweep: the pin's volume as FRAC_PI_8 (clippy) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5638259737 |
STEP import: a cone bounded only by its base circle reaches its point (#258) (#3871)
A cone face written with no vertex at its point has the base circle as its only loop (or that circle and a VERTEX_LOOP at the point, which has no edges). Its surface was spanned over the circle alone, a sliver, so a solid cone came in as a flat disc. Such a face is now spanned to the apex, and its circle is joined to the apex by a seam line out and back, as a cone with a seam is written. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
090119a5c5 |
Boolean: a small hole in a large body's face stays a hole when the face is divided (#2579) (#2580)
* Boolean: a small hole in a large body's face stays a hole when the face is divided
A boolean divides faces at a tolerance that follows the larger operand (a few
millimetres on a part a few metres long). A small hole's rim, two half circles
when read from STEP, then became two chords: its polygon in the face's
parameters had no area, and the sign that tells a hole from a face piece came
out at random. The hole became a face of its own, inside out, and the face it
was cut in stayed whole. A box cut round such a hole gave a piece without it,
and the shared part of that piece and a pin in the hole came out negative
("malformed") or the boolean failed.
divide_face now divides a loop whose polygon is flat again, finer (down to a
hundredth of the loop's own size), before reading its sign. Only the polygon
that places the loop changes; loops that have an area are untouched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Kernel: the long-plate hole test in tests.rs; a flat loop's size from its box
Review: the test moves into the kernel's test file (no file of its own),
and finer_if_flat takes the loop's size from the diagonal of the box round
its sample points instead of the largest distance between any two.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aac7mP9rthK3WsPpPRvCsF
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
||
|
|
5ba8c815a6 |
IGES import: trim curves with reversed pieces no longer drop their faces (#3358) (#3359)
* claim: #3358 * IGES import: trim curves with reversed pieces no longer drop their faces A curve on a surface (142) gives each trim loop twice: B in the surface's parameter space and C in model space, running the same way. Some exporters write C as a composite curve (102) whose pieces are in order but some of them end to start. The reader passed those pieces on as they were, so the loop did not close and the STEP reader left the face out ("face boundary is not a closed loop"). The pieces of C are now turned to run head to tail, starting at S(B(start)), the point where the parameter-space curve begins on the surface. That keeps the loop's sense from the file, so holes still run against the outer loop. A piece whose ends are one vertex at the sewing tolerance keeps its direction. On a 9,276-face trimmed-surface car model, 7,887 faces now import instead of 3,882. |
||
|
|
687caad24c |
IGES import: a revolved arc is a cap, not a whole sphere (#3806) (#3813)
A surface of revolution (120) wrote its arc generatrix as a whole STEP circle, which turns into a whole sphere twice over when the arc is centred on the axis; the trim could not pick the cap out of it and the face meshed as the full ball. An arc in a plane through the axis now comes in as a sphere or torus, any other arc as an exact rational B-spline of just the arc. |
||
|
|
932d8d6b4f |
Tessellation: trimmed faces of display meshes stay on their surfaces (#1653)
* Tessellation: trimmed faces of display meshes stay on their surfaces Display meshes triangulate a trimmed face in parameters scaled by the surface's speed along each, so a strip stretched along one parameter is not fanned across, and every boundary segment stays a constraint: one that crosses another is split where they meet. Boolean meshes keep truck's way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * Tessellation: one boundary rule for every mesh; drop the #1648 retry A boundary segment that crosses one already added is split where they meet in Boolean meshes too, which keeps a pinched face (#1648) whole without the snap-and-retry. Faces whose segments all go in mesh as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGeCa5jWWdroMWvXxN3LLM * Robustness goldens: seeds 1, 23, 55, 64 and 86 re-recorded on current main for #1653 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT * Thread preview test: wait for the stale fit error to clear before the next preview After switching back from M6x1.3 to M8x1.3, wait_feature_preview stopped at the M6 error still on screen when the new preview started a few frames later (seen on Linux CI with #1653's meshing). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT * Robustness goldens: the slow seeds 14, 20 and 40 re-recorded for #1653 from the merge queue Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e98e065584 |
Open saved designs from the model they were saved with (#3053) (#3102)
* WIP: adopt the saved model on open (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * WIP: tests * WIP: bench-open opens as Open does * docs: opening from the saved model * Tests: an edit after opening evaluates from the feature it changes * clippy * Read SOLIDCRAFT_NO_MODEL_CACHE through the env helper * Open: no picture from the model when the model itself opens; cancel test evaluates * Saved model: bodies passed through found without writing their record; states list runs of bodies * Adopted results go into the evaluation cache: an undone edit, or the marker rolled forward, takes them again * Model cache size guard: the saved model's writer --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c64a611818 |
Boolean: fail an unverifiable intersection instead of keeping what it should trim (#3244) (#3344)
* claim: #3244 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fail an unverifiable intersection instead of keeping what it should trim (#3244) When every route of the general boolean fails, it falls back on the sampled answer (one body whole, or nothing) with a "not verified ... that part is ignored" warning, even when the bodies' facets cross well past their faceting error. For an intersection that keeps exactly the material it was asked to trim: teeth standing 0.27 mm out of a trimming cylinder came back untrimmed, and `solidcraft-cli run` exited 0 with a part 16.918 mm in radius. An intersection whose sampled answer is contradicted by facets crossing by more than twice the depth the warning reports (6 x the check mesh tolerance, three times the faceting error of two meshes) now fails, and the model is kept. Cuts and unions keep the fallback: shelling a body in two pieces relies on it (a cavity tool grazing the other piece), with the same depth and ignored volume as this case. Fixes #3244 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Leave an empty unverified intersection as it was (#3244) Only a doubted intersection that keeps one body whole is refused. An empty one ("the bodies are apart") stays a result reported unverified: the clearance and interference checks rely on it, and main's new a_pin_through_a_d_bore_of_its_size_still_overlaps_the_flat (#2714) expects it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
47108fa063 |
Open: show a design as its bodies are meshed (#3053) (#3178)
* Open: show the design as its bodies are meshed, off the UI thread (#3053) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Open: hold screenshots until the design is meshed; profile lines * Tests: time-bounded opens * Open: a Loading parts n/N note while bodies are meshed * Open: frame the design from outlines while it goes into the window * Outline bounds: vertices and edges only; test * Frame watch: the window thread's frame times by open phase (SOLIDCRAFT_PROFILE_FRAMES) * Open: the picture grows in mesh order and stays once complete; refreshes pace themselves; named frame steps; watchdog test * Open: outlines measured on the file worker and kept per body * Open: draw the Home picture under the opened file's own key once meshed * Format the open wait --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ca9ebb44db |
Sheet metal: refuse a flat pattern that no longer matches the body (#3324) (#3325)
* claim: #3324 * Sheet metal: refuse a flat pattern that no longer matches the body A feature that is not sheet metal can change a sheet body's material without its sheet model knowing: a mirror combined into it, an extrude joined onto it, a fillet or chamfer on a corner. The flat pattern, the DXF and the bend table then wrote the sheet as it was before, a part other than the one in the design, with no warning. The evaluator now notes, per sheet, the first such feature: one that changes the body's shape while its sheet model stays the same. That feature warns, and sheet.flat_pattern, sheet.export_dxf (also with per_body) and sheet.export_bend_table refuse with "not supported yet", naming it. A sheet metal feature, or machining the sheet keeps as cut-outs, rebuilds the body from the model: it clears the note and warns that the change is not kept. Moving the body, splitting its faces and cosmetic threads leave the flat pattern as it is. * Sheet metal: a modelled thread is refused in the flat pattern too Only a cosmetic thread leaves a sheet body's material as it is. A modelled thread cuts the thread profile into the hole, which the flat pattern does not have, and a later sheet metal feature dropped it without a word. It now marks the sheet like a fillet does: it warns, the flat pattern, DXF and bend table refuse naming it, and the feature that rebuilds the body says the thread is not kept. |
||
|
|
e44dd4ff9e |
Loft: refuse consecutive sections that lie in one plane (#3412)
A loft through two sketches on the same plane built a flat body of zero volume and reported success, so the mistake surfaced later, if at all (a measure of 0, or a boolean that did nothing). `loft_wires` now checks each pair of consecutive sections: parallel planes with the second section's centroid in the first's plane are refused with "loft sections i and i+1 lie in the same plane (no volume between them)". The tolerance scales with the section's distance from its plane's origin. Test: two circles on XY; the loft fails with that message and leaves no body. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5b637600c |
Chamfer: one pick follows a straight edge's curved tangent chain at any part size (#2672) (#3295)
* claim: #2672 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Chamfer: one pick follows a straight edge's curved tangent chain at any size (#2672) chamfer_tool built a single-edge cutter for a straight edge even when the edge runs on tangentially into an arc (a slot side into its end). The cutter's overshoot (max(2*distance, 1% of the part size)) is probed for material beyond the edge ends; on a small part the probe hit the arc's material and the cutter was refused, so the blend followed the whole chain. Past a bounding-box diagonal of about 200 mm the overshoot cleared the arc, the cutter was accepted, and only the straight edge was chamfered, silently. A straight edge whose smooth chain contains a curved edge no longer gets a cutter; it takes the chain-following blend, as at small sizes and as a fillet does. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
c83446c6c9 | Fix issue 3120, import of STEP file not correct (#3208) | ||
|
|
349a6c6fc4 |
perf: a copy placed for showing doesn't turn its revolved faces again (#3044) (#3104)
Moving an occurrence places every body under it again, and each placed copy searched every revolved face for where it sits to turn it away from its seam, although its source had been turned already. On the Prusa Core One assembly that search was most of the 12.7 s a move of the top occurrence took. A body now records that its revolved faces are turned, and world placement (`place_matrix`) skips the search for such a body. Modelling moves (pattern copies, hole tools) keep `transform_matrix`, which still turns them in the copy's own frame: where a face wraps all the way round the turn it finds depends on those coordinates, and later booleans depend on it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f6cf8ceaad |
perf: an instanced STEP part's first instance shares its display mesh (#2814) (#3047)
Later instances of a part showed a mesh shared through the first instance, but the first instance meshed its own, so every repeated part was meshed twice. The first instance of a part placed more than once now shows the shared mesh too; a part placed once keeps its own (no second copy). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |