Repository navigation
feat(brep): conoid surfaces (radial rulings over a cylinder axis) - #43
Open
Basnederveen wants to merge 10 commits into
Open
Basnederveen wants to merge 10 commits into
Basnederveen wants to merge 10 commits into
Conversation
A crate that depends on OpenGeometry and needs a solid the primitives
don't cover can only build one by writing BRep JSON by hand. The builder
the kernel uses for every primitive and boolean result is crate-private.
- brep::{Builder, Use, boundary, plane_boundary, uv_line} are public:
Builder::{new, vertex, edge, face, face_with_holes, finish_solid}
- face() and face_with_holes() return the new face id
- Builder::brep() gives read access while building
- Builder::set_face_sense() sets a face's sense by id
- the types those signatures need are public: brep::Orientation,
brep::PcurveGeometry (and IntersectionSide), math::Interval::new
- the 14 internal tail calls of face() now end in `?; Ok(())`
tests/public_brep_builder.rs builds a box through the public API only:
valid, volume 24, inside/outside classification, STEP export; a face
sense set by id flips face_normal_at; an edge used by a third loop is
rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
…n axis) A Wrap maps flat space onto a cylinder the way a sheet metal bend wraps a flat sheet: (x, y, z) -> frame.point([z cos(x/r), z sin(x/r), y]). x is arc length on radius r, y runs along the axis, z is the distance from it. CurveGeometry::Wrapped and SurfaceGeometry::Wrapped wrap a line, circle, ellipse or any elementary surface exactly and keep its parameters and charts. A hole through a bend is a wrapped cylinder bounded by wrapped circles; a slanted cut is a wrapped plane bounded by helices. - geometry: point, derivatives (chain rule), normal (cofactor), projection (exact on the surface), conservative bounds for curves and surfaces. - tessellation: grid sizes and chord deviation from the exact second derivatives, sampled with a 1.5x margin; arcs keep the per-turn floor. - query: ray roots on wrapped faces by UV subdivision plus Newton. - STEP: wrapped surfaces as bicubic B-splines over the support's face UV, reaching 10% past the faces (never around a period onto itself) so readers that rebuild trims by intersecting neighbouring surfaces don't trim short; wrapped edges as cubic B-splines; both Hermite fits checked against the exact geometry and charged to the exchange budget. - placement moves wrapped geometry rigidly; scaling, mirroring, booleans, shells, SSI and IFC refuse it with UnsupportedGeometry. Tests: wrapped families in the per-family geometry tests, fitting tests, and a bend sector with a slanted wrapped side that meshes to its exact volume, classifies points on both sides of the wall, exports to STEP within budget, and refuses scaling, mirroring, booleans and shelling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
Face loops now follow the standard B-rep / STEP rule (ISO 10303-42): seen against the face's material normal (surface normal times sense), outer loops run counter-clockwise and holes clockwise, so the face lies to the left of every loop. Most producers already did; the tube builders (cylinder_with_circular_hole, annular_cylinder) wound the cap holes like the outer loops and gave the inner wall the outer wall's loop before setting its sense to Reverse, so their STEP export wrote the hole bounds and the inner wall's bound running the wrong way. Boolean results that keep those faces inherited them. - validate() rejects a face whose loops run against its material normal, measured as signed area in UV (continuous across chart seams and inside an intersection pcurve; two loops bounding a seamless band are judged against each other). Straight pcurves on planes are measured from their ends. - orient_loops() (crate-internal) reverses misoriented loops in place. Builder::finish_solid, the sweep builder and the boolean result sites call it before validate(). - from_json() turns the loops of bodies written with the old winding, so saved bodies keep loading. The check refuses the cone-sphere intersection of generic_closed_loop_handles_periodic_winding: on main it keeps the sphere cap outside the cone (a point inside both operands classifies Outside), and its cone patch's loop runs against the patch's normal. The test now asserts the refusal. Stored fixtures edited by hand because their bodies had the old winding: only the half-edges' from/to/next/prev/sense and the vertices' outgoing_halfedge change, every number is byte-identical (tube bodies, validity builders, five boolean-matrix and six batch-matrix results, the annular-first input). The tube STEP texts change in four ORIENTED_EDGE senses each (the two hole bounds and the inner wall's circles). The tube tessellations list the reversed loops' vertices the other way round; eight positions differ by one ULP from sampling the circles in the other direction, and were recorded on x86_64 Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
SurfaceGeometry::Conoid { frame, c } is the right conoid
S(u, v) = origin + v (cos u x + sin u y) + (c0 + c1 cos u + c2 sin u) z,
with UV = (angle around the axis, distance from it). Its rulings (u fixed)
are radial lines perpendicular to the axis; its curves v = r are the planar
ellipses where the cylinder of radius r meets z = c0 + (c1 x + c2 y) / r.
It describes a cut through a cylindrical shell that keeps every radial line
through the wall whole while one wall face is cut by a plane.
- geometry: exact point, first and second derivatives, normal (S_u x S_v),
conservative bounds; projection starts from the exact angle and radius
(lifted by the UV hint like the other revolved kinds) and refines by Newton
for points off the surface.
- conoid_ellipse(frame, c, radius) returns a constant-radius curve as an exact
CurveGeometry::Ellipse plus its phase (ellipse t = conoid u - phase), so the
edge is a Line2 pcurve on the conoid.
- builder: projected_boundary() gives a boundary use a ProjectedCurve pcurve
(curve id, range, UV hint and rate from sampled projections), so callers
outside the crate can bound e.g. a cylinder face by an elliptic edge; the
builder points projected pcurves at the surface of the face they bound.
- tessellation: grids sized from sampled second derivatives, with the radial
count also following the twist |S_uv|; trimmed conoid faces refine edge by
edge (an axis-aligned grid would leave T-junctions between ellipse edges
sampled differently).
- query: exact ray roots from the quartic (z - c0)^2 (x^2 + y^2) =
(c1 x + c2 y)^2, keeping roots on the surface itself.
- placement: rigid motion and uniform scaling (c scales), mirroring
(c2 changes sign with the reflected chart).
- STEP: fitted as a bicubic B-spline over the faces' UV, like wrapped
surfaces, so the faces' (u, v) pcurves stay valid; charged to the
exchange budget.
- preflight numbers and validation (finite coefficients, orthonormal frame).
- booleans, shells, SSI and IFC refuse it with UnsupportedGeometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
The checker rejected B_SPLINE_SURFACE_WITH_KNOTS, so fitted surfaces could not be re-read. It now evaluates piecewise-Bezier bicubic surfaces (clamped knots, interior multiplicity 3, as the exporter writes them), which lets the pcurve check sample pcurves on fitted faces against their 3D edges. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
…uilder A quarter turn of a shell with radii 2..4 whose one end is a right conoid derived from the plane x - y = k cutting the inner face, the other end a plane, with cylinder faces bounded by the conoid's ellipses (projected pcurves) and two planar sides. It validates as a closed solid with either an exact or a padded conoid trim, meshes to its numerically integrated volume, classifies points on both sides of the conoid, exports to STEP that re-reads with its pcurves checked, round-trips through JSON, moves, scales and mirrors to the right volume, and is refused by booleans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VFZvqx1RmXzPtbL9VXP4H7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new surface kind: the right conoid over a cylinder axis.
SurfaceGeometry::Conoid { frame, c }withuis the angle around the axisframe.z,vthe distance from it. Every ruling is a radial line perpendicular to the axis.Why: a bend (a cylindrical shell) cut "square to the sheet" by a plane, as at a mitered corner, keeps whole radial lines through the thickness while one face's points lie on the plane. That cut face is exactly this conoid, and it has no analytic support today. Its edges at constant radius are planar sections of the cylinders, so they are exact ellipses.
conoid_ellipse(frame, c, radius) -> (CurveGeometry::Ellipse, phase): the curve at a radius as an exact ellipse, with ellipse parametert = u - phase. On the conoid its pcurve isuv_line([phase, radius], [1, 0]); the rulings areuv_line([u, r0], [0, 1]).projected_boundary(builder, surface, edge, from, to, sense, hint): a public helper that builds aProjectedCurvepcurve for an edge on a face (here the ellipse on the bend's cylinder). A caller outside the crate could not build one before, since curve ids are internal. The builder now points a projected pcurve at the surface of the face it bounds.Support across the kernel:
S_u x S_v, UV and interval bounds. Projection starts fromatan2of the local(x, y)and the radial distance (exact for points on the surface), lifted by the hint like the cylinder, and refines off-surface points with Newton.Line2pcurves would no longer hold, and the exporter has no rational B-spline writer. Ellipse edges with a projected pcurve are written as plain 3D ellipses.UnsupportedGeometry: booleans, shells, SSI and IFC export, as for wrapped geometry.Compatibility:
"kind": "Conoid"surface; existing bodies read unchanged.SurfaceGeometrygains a variant, so an exhaustivematchdownstream needs a new arm (two tests in this repo did).Tests
conoid_ellipseis planar and lies on the surface, off-surface projection, and period lifting.tests/conoid_bend.rs(4 tests): a quarter-turn shell, radii 2 to 4, with one end cut by the conoid of the planex - y = kthrough its inner face, built through the public builder with projected pcurves on the cylinders. It validates as a closed solid; its mesh volume matches the Simpson-integrated exact volume within 2e-4 at deflection 1e-3; points classify Inside and Outside; and its STEP export re-reads through the test-support Part 21 checker, which can now read B-spline surfaces and checks the pcurves on the fitted face.cargo fmt --check,cargo clippy --all-targetsandcargo testpass on Windows: 390 crate tests,conoid_bend4, and every other target as on the stacked branches. The only failures are ones that predate this PR: 3 stored-fixture tests incasesand 1 ingolden(they fail onmaintoo), and the 2 IFC oracle tests instep_oracle(one fails onmain, the other with fix(brep): standard loop winding, enforced by validate() #40's winding alone).npm run check:source,check:duplicatesandcheck:cyclespass. Browser runs and the golden comparison were not run (golden files are recorded on macOS).Files
src/brep/geometry/conoid.rs,tests/conoid_bend.rs.brep/builder/boundary.rs(projected_boundary), test-support Part 21 reader.No behaviour changes for existing geometry: the new code paths are only reached by the new surface kind.