Introduction
ogeom is a boundary-representation (B-rep) CAD kernel in Rust. It does not wrap, vendor or link any existing CAD kernel, and it is licensed LGPL-2.1 as a derivative work of Open CASCADE Technology, its reference kernel (provenance).
What it covers
- Parametric curves and surfaces.
- Shared B-rep topology with per-entity tolerances and operation history.
- Booleans, blends and chamfers, offsets and shells, sweeps and lofts.
- Healing and tessellation.
- Product structure (assemblies) and 2D drawings with hidden-line removal.
- Data exchange:
- STEP, read and write, with assemblies, colours, semantic PMI and saved views.
- IGES, read and write.
- The native document format.
- Mesh formats. Meshes can be turned back into solids, with their curved regions recognized.
How this book is kept honest
- Examples are tested. Every code block in this guide comes from a test file that runs in CI, so a broken or false example fails the build. The test suite checks every numeric claim against a closed form or a round trip.
- Completeness is recorded. Scope sets the target: a complete solid-modelling kernel with its data exchange, plus meshes into solids. The capability ledger is committed and machine-checked: each verdict cites symbols and tests the build verifies.
Where things are
- This guide: concepts and workflows, in reading order.
- The API reference:
rustdoc for the
ogeomumbrella crate. Depend on that crate. The crates under it are an implementation detail and will change. - The repository: source, issues, and the governing documents this book includes verbatim.
Getting started
The kernel is not on crates.io yet. Depend on it by git:
[dependencies]
ogeom = { git = "https://github.com/gilbertorconde/ogeom-rs" }
Use the ogeom umbrella crate. It re-exports the whole API as modules
(ogeom::algo, ogeom::boolean, ogeom::topo, ogeom::io, …). The
ogeom-* crates underneath are an implementation detail and their
boundaries will change.
A first solid
A block with a hole through it: two primitives, one boolean, and a volume check.
let mut model = Model::new();
let tol = Tolerances::millimetres();
// A 20×20×10 block, and a Ø8 hole through its middle.
let block = ogeom::algo::make_box(&mut model, Frame::WORLD, (20.0, 20.0, 10.0), tol)
.unwrap()
.shape;
let axis = Frame::new(Point::new(10.0, 10.0, 0.0), Direction::Z, Direction::X, tol).unwrap();
let drill = ogeom::algo::make_cylinder(&mut model, axis, 4.0, 10.0, tol)
.unwrap()
.shape;
let part = ogeom::boolean::cut(&mut model, &block, &drill, tol)
.unwrap()
.shape;
// The result is measured, not assumed: volume against the closed form.
let volume = ogeom::algo::volume_properties(&model, &part, Deflection::default(), tol)
.unwrap()
.mass;
let exact = 20.0 * 20.0 * 10.0 - core::f64::consts::PI * 4.0 * 4.0 * 10.0;
assert!((volume - exact).abs() / exact < 0.01);
The same patterns apply to the whole API:
Modelowns all data. Geometry, topology, tolerances and history live in oneModel. Operations take&mut model. AShapeis a cheap handle into the model; copying it copies no geometry. See the data model.- Every operation takes a
Tolerances. There is no global epsilon.Tolerances::millimetres()is the preset for models in millimetres. See Tolerances. - Every operation returns a
Built.built.shapeis the result. The rest ofBuiltis the history: which input entities generated or were modified into which outputs. Every operation fills it in, so parametric applications can rely on it. - Errors are values. Everything returns
Result. When the kernel cannot produce a correct result, it returns a named refusal instead of bad geometry.
The data model
This chapter is the kernel’s normative data-model document, included
verbatim from
docs/DATA_MODEL.md.
It is written for people implementing against the model. The rest of this
guide refers to it.
The ogeom data model
Status: normative. Everything in crates/ implements this document. A
change here is a design change and must be argued as one (see
CONTRIBUTING.md).
Why this document exists
A B-rep kernel’s public API is a thin layer over its data model. What the model can express limits what the kernel can do. Two examples:
- Flatten a location chain into a 4×4 matrix, and assembly instancing becomes impossible.
- Give an edge one curve instead of a list, and boolean face splitting has nothing to split with.
Neither can be fixed later at the API layer.
The field has converged on the model below over thirty years, because the alternatives do not work. It is described here in its own terms, not as a port of any implementation. Each invariant is:
- cheap now: a few days of design attention;
- effectively impossible to retrofit: adding it later means reworking every algorithm in the kernel;
- load-bearing: its failure mode is written out, so the cost of dropping it is concrete.
In two places the conventional design is wrong and we deliberately differ: stable identity (§8) and predicate abstraction (§9).
1. A shape is a triple
pub struct Shape {
tshape: TShapeId, // arena key: the shared, positionless topology node
location: Location, // where this instance sits
orientation: Orientation, // which way its boundary faces
}
Shape is cheap to copy and is passed by value everywhere. The heavy data
(children, geometry, tolerances) is stored once, in an arena, behind
TShapeId.
This separation is why B-rep scales. The same TShape can appear at many
locations with many orientations without copying any geometry.
Consequence: traversal composes.
- A sub-shape’s effective location is the product of every location from the root down to it.
- Its effective orientation is the composition of every orientation on that path.
An explorer that yields sub-shapes without composing both is wrong. It produces plausible-looking garbage, not a crash.
2. Location is a chain, not a matrix
pub struct Location {
// (datum, power) pairs. Empty == identity.
chain: SmallVec<[(DatumId, i32); 2]>,
// composed transform, computed on demand
cached: OnceCell<Trsf>,
}
A Datum is a reference-counted rigid transform. A Location is a sequence
of (datum, integer power) pairs.
Why not a 4×4 matrix:
- Composition is concatenation. Roughly O(1), no matrix multiply, and no drift from repeated floating-point composition.
- Identity comparison is structural. Two shapes are at “the same place” if their chains are equal. No need to compare 16 floats against a tolerance. This is what lets 10,000 identical bolts in an assembly share one piece of geometry and be recognized as instances of it.
- Inverses are exact. Negate the power instead of inverting a matrix.
The composed Trsf is computed lazily and cached. Nothing outside ogeom-topo
should ever need to read the chain itself.
3. Orientation composes multiplicatively
pub enum Orientation { Forward, Reversed, Internal, External }
| Value | Meaning |
|---|---|
Forward | The material is on the surface’s default side. |
Reversed | The material is on the other side. |
Internal | The boundary lies inside the material (for example, a stiffener edge embedded in a face). |
External | The boundary lies outside the material (reference geometry). |
Composition is a monoid, applied at every level of descent:
compose(Forward, x) = x
compose(Reversed, Forward) = Reversed
compose(Reversed, Reversed) = Forward
compose(Internal, _) = Internal
compose(External, _) = External
An edge’s orientation within a face depends on that face’s orientation within its shell, which depends on the shell’s orientation within the solid. Reversing a solid must not require touching any child.
4. Identity has three levels
There are three distinct equivalences, with three distinct hashers. They are not interchangeable:
| Predicate | Compares | Used for |
|---|---|---|
is_partner | tshape only | “Is this the same underlying topology, anywhere, either way round?” |
is_same | tshape + location | Set membership in most algorithms; the common case |
is_equal (==) | tshape + location + orientation | Exact identity; ordered containers |
Every map and set type names the equivalence it uses and enforces it in its
hasher. A HashMap keyed on is_equal semantics that hashes only the tshape is
a silent correctness bug.
5. Tolerances are per entity, and they only grow
Every vertex, edge and face carries its own tolerance: the radius of the sphere, pipe or slab within which the entity is considered to lie.
Containment rule (an invariant), for entities in a boundary relationship:
tol(vertex) >= tol(edge) >= tol(face)
- Operations may only increase tolerances, never silently decrease them.
- An operation that cannot satisfy the rule has failed and must say so.
This is not a workaround for sloppy code. Exact arithmetic cannot represent the intersection curve of two NURBS surfaces (it is transcendental). A tolerance-carrying topology is the only known way to build a kernel whose models close. Every production kernel works this way. See §9 for what exact predicates can do.
6. An edge carries a list of representations
An edge has a list of representations, not one curve:
pub enum EdgeRepr {
Curve3d { curve: CurveId, location: Location, range: (f64, f64) },
PCurve { curve: Curve2dId, surface: SurfaceId, location: Location },
PCurveClosed { curve: Curve2dId, curve2: Curve2dId, surface: SurfaceId, location: Location },
Polygon3d { polygon: PolygonId, location: Location },
PolygonOnTri { polygon: PolygonOnTriId, triangulation: TriangulationId, location: Location },
}
One edge holds, at the same time:
- a 3D curve;
- one pcurve per adjacent face;
- two pcurves where it is a seam on a closed surface;
- cached discretizations.
Why it must be a list:
- A boolean splits faces in 2D parameter space. Without a pcurve on each face, there is nothing to split with.
- Different surfaces have different parameterizations, so one 2D curve cannot serve two faces.
- A seam edge on a cylinder appears at both
u = 0andu = 2π. One pcurve cannot express that.
The same_parameter flag asserts that all representations agree on the
parameterization: for the same t, curve3d(t) and surface(pcurve(t)) are
the same point within tolerance. The claim can be false. A repair routine
re-establishes it, possibly by increasing the edge’s tolerance.
7. Every operation emits history
pub trait Operation {
fn generated(&self, input: Shape) -> &[Shape]; // new entities made *from* input
fn modified(&self, input: Shape) -> &[Shape]; // what input *became*
fn is_deleted(&self, input: Shape) -> bool; // input has no image in the result
}
History is not optional, not deferred, and not “added when something needs
it”. Every operation in ogeom-algo, ogeom-bool, ogeom-fillet and
ogeom-offset fills these in from the first commit that introduces it.
The reason is downstream. A parametric application records “fillet that edge” and must still find that edge after the model is rebuilt with different dimensions. It finds it again by walking history. This is the topological naming problem. Every application built on a kernel that identifies topology by pointer has had to solve it this way; in one well-known case, it took a decade of work layered on the kernel’s history maps.
Adding history later means revisiting every algorithm. Incomplete history is worse than none: it fails silently and corrupts documents instead of raising an error.
A modification may be marked an exact copy (History::copy, read back with
History::copy_of): the image is a new node standing for the same thing, on
the same surface or curve within the same boundary, its vertices where they
were to within their tolerances. A boolean’s faces the tool never touched, and
a face the sew only moves onto shared edges, are reported so. An application
holding names or meshes for the input carries them across without comparing
geometry. Composition keeps the mark only where every step copied the shape or
left it alone.
A boolean records its arguments’ edges and vertices the same way: one cut short, split or rebuilt is modified into its pieces in the result, one rebuilt whole is copied, and one with nothing left (a corner cut away, a tool’s edge outside a cut) is deleted. One the result holds as it stands, an edge of a face set aside, has no record and traces to itself.
Results stay new nodes: an operation edits its own result in place, and a node shared with an earlier solid would change it too.
A placement (transformed) makes no node, but placement is part of a
shape’s identity, so it records the input and every sub-shape below it as
modified into its occurrence in the moved shape: the same node under the
new placement. These are modifications, not copies: the image stands
elsewhere. A shear or an uneven scale (general_transformed_shape) rebuilds
the shape and records the same set: every container, face, wire, edge and
vertex as modified into its rebuilt twin.
Model::placed and a document’s occurrences are not operations: they name
the input’s own nodes under a further placement and record nothing. A
sub-shape s stands in the placed shape as s.moved(by), with by the
placed shape’s location with the input’s own taken off. A caller composing a
move with other operations’ history uses transformed.
8. Entity identity is stable (deliberate divergence)
The conventional design identifies topology by pointer. Every modelling operation allocates new nodes, so every reference into a previous result is lost. That is the topological naming problem. Every downstream fix tries to reconstruct identity afterwards by walking history maps.
We record identity at creation instead:
pub struct EntityId(u64); // stable for the lifetime of a document
pub enum Provenance {
Primitive { op: OpId, role: PrimitiveRole }, // "the +Z face of box #3"
Derived { op: OpId, from: SmallVec<[EntityId; 2]>, role: DerivedRole },
Imported { file: FileId, external: ExternalRef },
}
An entity’s identity is what produced it, and from what, not where it sits in memory.
- When a boolean splits a face, each fragment knows it came from that face.
- A rebuild with different parameters produces entities with the same provenance, so a reference like “the fillet on this edge” survives.
History (§7) is still required. An embedding application consumes it, and it is the honest answer where provenance alone cannot resolve a reference. But provenance is the primary mechanism, and it only works if it is designed in from the start.
9. Numerics are abstracted; the tolerance model is not negotiable
pub trait Predicates {
fn orient3d(a: Point, b: Point, c: Point, d: Point) -> Sign;
fn insphere(a: Point, b: Point, c: Point, d: Point, e: Point) -> Sign;
// ...
}
Algorithms are written against this trait. Implementations may be fast-filtered floating point, adaptive exact (Shewchuk), or interval-based, and can change without touching any algorithm.
What this does and does not buy:
- Exact predicates solve the polyhedral robustness problem: the orientation of a point against a plane, in-sphere tests for Delaunay.
- They do not solve the CAD problem. The intersection curve of two curved surfaces has no exact representation to be exact about. That is why §5 exists and cannot be traded away.
- Predicates decide exactly what can be decided exactly. Tolerances handle the rest.
Tolerance constants live in ogeom-core. The model’s unit scale is
explicit, not assumed to be millimetres. Kernels often assume millimetres,
and then misbehave silently on models authored in metres or inches.
Tolerances::with_scale accepts a scale only where every linear tolerance
below, and its square, is a normal f64: from about 7.5e-161 to about
6.7e144 millimetres per unit, micrometres to kilometres with room to spare.
| Constant | Value at unit scale | Meaning |
|---|---|---|
CONFUSION | 1e-7 | two points are the same point |
ANGULAR | 1e-12 | two directions are parallel |
INTERSECTION | CONFUSION * 1e-2 | intersection convergence |
APPROXIMATION | CONFUSION * 1e1 | curve/surface fitting target |
P_CONFUSION | CONFUSION * 1e-2 | parametric-space confusion |
10. Geometry is reached through traits
pub trait Curve3d {
fn range(&self) -> (f64, f64);
fn value(&self, u: f64) -> Point;
fn d1(&self, u: f64) -> (Point, Vector);
fn d2(&self, u: f64) -> (Point, Vector, Vector);
fn continuity(&self) -> Continuity;
fn kind(&self) -> CurveKind; // for analytic fast paths
// ...
}
Every intersection, projection, extrema and tessellation algorithm is written
against Curve3d / Curve2d / Surface, never against a concrete type. To a
caller, a face adaptor (surface + location + trimming) and a bare analytic plane
are the same thing.
kind() lets algorithms opt into analytic fast paths (plane/plane
intersection should not go through a marching intersector). The general path
never needs to know what it is looking at.
11. Memory: arenas, not reference counting
Topology lives in typed index arenas (Vec<T> plus a typed u32 key), not
behind Arc or an intrusive reference count.
- No reference cycles to reason about. Intrusive reference counting has no cycle collection, so kernels built on it avoid cycles by convention only.
- Cache-friendly traversal.
- Keys are small,
Copy, and serializable. - It is what makes §8 possible at all.
The cost: a shape only has meaning relative to the arena that owns it. That is the right trade, and the API makes it explicit instead of hiding it.
Append-only arenas, and what relies on it
The arenas hand out slots in order and never refill one. The one removal,
Model::retain_reachable, drops whatever a set of roots does not reach and
leaves those slots empty for good: every kept handle resolves as before, a
dropped one fails to resolve, and the arena stores only what it keeps, so a
clone costs what is kept. Two non-builder paths rely on slots coming in
order.
Model::from_partsassembles a restored document by replaying the file’s insertion order, which reproduces every handle.Model::absorbuses the same engine on a model that already contains data. Another document’s parts are appended, with every handle shifted past what the target already holds, so an absorbed shape is indistinguishable from one built in place. This is how a serialized tool body meets a live one in a boolean.
Absorption:
- preserves the source’s identities under a plain offset (the remap table says where each one landed);
- keeps the source’s provenance verbatim, including source
OpIds, which only have meaning in the source document’s own rebuild; - refuses, by name, a document at a different unit scale. Rescaling is a feature, not a default.
12. Errors are values
Operations return Result<T, OgeomError>. The variants cover the failures a
kernel actually needs: construction, domain, range, dimension mismatch, null
object, not done, numeric failure, invariant violation. They match the
categories applications already handle, so they translate cleanly into any
host’s error model.
There are no exceptions, no setjmp, and no conversion of hardware signals into
throwable objects.
An algorithm that “did not converge” returns that fact. It does not return a null shape and set a flag that the caller may forget to check.
Checklist for a new algorithm
- Written against the geometry traits (§10), not concrete types.
- Populates
generated/modified/is_deleted(§7). - Assigns provenance to every entity it creates (§8).
- Composes location and orientation correctly during traversal (§1, §2, §3).
- Uses the right identity predicate, with a matching hasher (§4).
- Keeps the tolerance containment rule, or fails loudly (§5).
- Keeps edge representations consistent, or clears
same_parameter(§6). - Returns
Result; never a silently invalid shape (§12).
Tolerances
Imported models have gaps, intersections are computed numerically, and points that should coincide rarely match exactly. ogeom handles this with three rules.
1. No global epsilon
Every operation takes a Tolerances argument. The examples in this guide
use Tolerances::millimetres().
confusion()is the distance below which two points count as the same point.- The other thresholds derive from it.
This keeps the units and expected precision visible at each call site.
2. Tolerances are per entity and only grow
Each vertex, edge and face carries its own tolerance: the radius within which its stated geometry is trusted.
- Entities of a clean primitive sit at the baseline.
- Imported or heavily modified models have wider tolerances where the geometry is less certain.
- Operations may widen a tolerance to record real uncertainty. Example:
when
sewmerges two vertices a micron apart, the surviving vertex widens to cover both. - Nothing narrows a tolerance silently, because that would claim precision
nobody measured.
ogeom::heal::reduce_tolerancesnarrows tolerances by re-measuring the geometry.
3. The containment rule
- An edge must lie within the tolerance regions of its faces.
- A vertex must lie within the tolerance regions of its edges.
The validity checker (ogeom::algo::check) accepts a gap within the
stated tolerance and rejects one outside it. It is no stricter than the
builders, so it never rejects what the kernel legitimately builds.
The full semantics (what each entity kind’s tolerance means, and why) are in §5 and §9 of the data model.
Making shapes
Everything here is in ogeom::algo unless stated otherwise. Every
operation returns a Built with the result and its history.
Primitives
make_box, make_cylinder, make_cone, make_sphere, make_torus,
make_wedge and make_half_space each take a Frame (origin and
orientation) plus their dimensions. Degenerate dimensions are refused by
name, not clamped.
Bottom-up construction
Build other shapes in B-rep order, from vertices up:
| Level | Functions |
|---|---|
| Vertex | make_vertex |
| Edge | make_edge (on a curve), make_edge_between (between points) |
| Wire | make_wire (ordered edges), make_wire_unordered (any order), make_polygon (straight sides) |
| Face | make_face, make_face_on (over a surface), make_face_with_pcurves (when the boundary’s surface parametrisation matters) |
| Shell, solid, compound | make_shell, make_solid, make_compound |
Helpers:
sewstitches faces that share boundaries within tolerance into shells.is_wire_closedandis_shell_closedcheck closure before you build the next level.
Sweeps and fitting
make_prismextrudes.make_revolutionrevolves.- The general sweeps live in
ogeom::offset, which holds their shared machinery:make_pipe(along a wire),make_loft(through sections),make_evolved(along a planar profile). See Offsets, shells and features. interpolatefits a curve through points.approximateandapproximate_withinfit a curve near points to a stated tolerance. The fit reports the deviation it achieved.make_textrenders text as wires, for engraving.
History
Every constructor and every operation in later chapters records history:
which inputs generated which outputs, which were modified, and which
is_deleted. Stable references into a rebuilt model (for example “fillet
that edge”) are resolved through this history. §7 of
the data model defines the contract.
Booleans
The four standard booleans are in ogeom::boolean and share one
signature:
let out = ogeom::boolean::fuse(&mut model, &a, &b, tol)?.shape; // union
let out = ogeom::boolean::common(&mut model, &a, &b, tol)?.shape; // intersection
let out = ogeom::boolean::cut(&mut model, &a, &b, tol)?.shape; // difference
let out = ogeom::boolean::section(&mut model, &a, &b, tol)?.shape; // the curves where they meet
The getting-started example is a
cut, checked against its exact volume.
Other operations
| Function | What it does |
|---|---|
cells | Full cellular decomposition of two solids: every region classified against both inputs. The four booleans select from this. Use it when you need a different selection. |
fuse_fuzzy, cut_fuzzy, common_fuzzy | Take an explicit fuzz distance, for inputs whose faces almost coincide (mostly imported geometry). Avoids the sliver faces an exact operation would create along the near-contact. |
make_volume | Builds the solids enclosed by an arbitrary set of faces. |
remove_faces | Defeaturing: deletes a feature’s faces from a solid and closes the gap. Neighbouring faces extend to fill it, or the band is re-intersected where extension cannot close it. |
make_periodic | Prepares shapes for repetition along an axis. |
Guarantees
- Tangent cases work. A tool tangent to a face, even at a parametrisation singularity of that face’s surface, produces the correct section curve. The test suite checks these cases against closed forms (sphere octants, blend corners).
- Unresolvable cases are refused. If two inputs interfere in a way the algorithm cannot resolve correctly, the operation returns a named error instead of a shape that is wrong.
Blends and chamfers
Everything here is in ogeom::fillet.
Rounding one edge
let block = ogeom::algo::make_box(&mut model, Frame::WORLD, (40.0, 30.0, 12.0), T)
.unwrap()
.shape;
// Pick the top edge along y = 0 and round it at radius 2.
let edge = edge_near(&model, &block, Point::new(20.0, 0.0, 12.0));
let rounded = ogeom::fillet::fillet_edge(&mut model, &block, &edge, 2.0, T)
.unwrap()
.shape;
// A fillet removes the square corner and leaves the quarter cylinder:
// ΔV = (1 − π/4)·r²·length, exactly.
let volume = ogeom::algo::volume_properties(&model, &rounded, Deflection::default(), T)
.unwrap()
.mass;
let exact = 40.0 * 30.0 * 12.0 - (1.0 - core::f64::consts::FRAC_PI_4) * 4.0 * 40.0;
assert!((volume - exact).abs() / exact < 0.01);
| Function | Result |
|---|---|
fillet_edge | Constant-radius round. |
fillet_edge_variable | Round with a radius law along the edge. |
chamfer_edge | Symmetric flat bevel. |
chamfer_edge_distances | Asymmetric bevel (two distances). |
chamfer_edge_angle | Bevel from a distance and an angle. |
Fillets are not limited to planes. Between cylinders, cones, spheres, tori and fitted patches, the rolling ball is marched numerically instead of solved in closed form. The blend surface is fitted through its cross-section arcs and merged into the solid the same way.
Several edges at once
Edges filleted or chamfered in separate calls each stop flush against whatever they end on. Passed in one call, they join:
let block = ogeom::algo::make_box(&mut model, Frame::WORLD, (20.0, 20.0, 10.0), T)
.unwrap()
.shape;
let top = [
Point::new(10.0, 0.0, 10.0),
Point::new(20.0, 10.0, 10.0),
Point::new(10.0, 20.0, 10.0),
Point::new(0.0, 10.0, 10.0),
]
.map(|at| edge_near(&model, &block, at));
// Four bevels as one operation mitre where they meet: each corner loses
// the overlap of two prisms, a third of the cube of the distance.
let bevelled = ogeom::fillet::chamfer_edges(&mut model, &block, &top, 1.0, T)
.unwrap()
.shape;
let volume = ogeom::algo::volume_properties(&model, &bevelled, Deflection::default(), T)
.unwrap()
.mass;
assert!((volume - (4000.0 - 4.0 * 10.0 + 4.0 / 3.0)).abs() < 1e-2);
// Three fillets meeting at a corner close it with the ball's own patch,
// an octant of a sphere, instead of leaving the bands' caps standing.
let corner = [
Point::new(10.0, 20.0, 10.0),
Point::new(20.0, 10.0, 10.0),
Point::new(20.0, 20.0, 5.0),
]
.map(|at| edge_near(&model, &block, at));
let rounded = ogeom::fillet::fillet_edges(&mut model, &block, &corner, 2.0, T)
.unwrap()
.shape;
let spheres = ogeom::topo::explore_unique(&model, &rounded, ogeom::topo::ShapeType::Face)
.unwrap()
.iter()
.filter(|face| {
let data = model.node(face).unwrap().data().as_face().unwrap();
matches!(
model.geometry().surface(data.surface),
Some(ogeom::geom::SurfaceGeometry::Sphere(_))
)
})
.count();
assert_eq!(spheres, 1);
fillet_edgestrims neighbouring bands against each other, joins tangent chains without a seam, and fills every vertex where three or more filleted edges meet with a rolling-ball corner patch.round_vertexis that corner tool on its own:- where one ball can touch all the faces, it produces a sphere patch;
- where no single ball can (for example the apex of a rectangular pyramid), it produces the exact envelope of the rolling ball: spheres joined by cylinders.
chamfer_edgesandchamfer_edges_withbevel a set of edges in one operation. Every wedge is built on the solid as it was before the call, so the bevels mitre where they meet.
Blends between faces without a shared edge
blend_facesrolls a constant-radius ball between two faces of one solid that need not share an edge. Planes, and curved faces whose surfaces share a direction or an axis, get an exact cylinder or torus. Curved faces that meet along edges of the solid are blended along those edges, marched where the seat has no closed form. Curved faces that share no edge and no direction or axis (a B-spline face, a cylinder at a slant to a plane) are marched round where their surfaces cross. Where the ball leaves one face, the round ends in a flat cap in the ball’s section.march_blendis the underlying marcher. It traces the contact circle and reports why it stopped (BlendStop). Use it to blend up to an obstruction on purpose.
Tangency checks
A blend must end tangent to the faces it joins. analyse_blend measures
the achieved contact and returns it as BlendContact. Fillets report their
own tangency deviation, and a blend that cannot reach tangency within
tolerance is refused.
Each BlendContact carries the gap (G0), the angle between the normals
(G1) and the step in normal curvature square to the edge (G2), all sampled
at stations along the shared edge. face_curvature_samples gives the
principal curvatures on a grid over one face, for curvature maps and zebra
stripes.
Offsets, shells and features
Everything here is in ogeom::offset.
Offsetting
| Function | What it does |
|---|---|
offset_shape | Moves a solid’s boundary along its normals (outward, or inward with a negative distance) and rebuilds the intersections where offset surfaces collide. |
offset_faces | Offsets only the named faces, and the faces around them follow: pull a wall out, grow a bore, raise a boss. The topology stays the same, and the history maps each face to its result. |
move_faces | Moves the named faces by a translation or rotation, the faces around them following, as offset_faces does. |
make_thick_solid | Shelling: removes the named faces, offsets the rest inward and joins them. Gives a hollow part with an opening. |
offset_sheet | Moves a face or a shell along its normals, trim and joins kept. A face with no parallel surface in its own family (a B-spline) is fitted, and its tolerance holds the measured deviation. |
make_thick_sheet | Thickens a face or an open shell into a solid: the sheet and its offset (or half the thickness each way), closed by side faces along the free edges. Faces meeting at a crease join on the mitre. |
offset_wire | 2D offset of a planar wire. The caller picks the Join style (arcs or intersections). Useful for tool-path-like outlines. |
apply_draft | Tilts faces by a draft angle about a neutral plane, for moulded parts. |
Sweeps
The general sweeps share this module’s machinery:
make_pipeandmake_pipe_skinned: sweep along a single spine edge.make_loftandmake_loft_skinned: loft through profile sections.make_evolved: sweep a profile along a planar spine.make_filling: fill the loop four edges bound with a fitted patch face.make_filling_n: fill a hole bounded by any number of edges with one face bounded by those same edges, meeting each neighbouring face at G0, G1 or G2 and passing through points and curves inside the hole. The gaps, angles and curvature differences it reached come back measured per side.
Features
Feature operations combine a sketch with a solid in one step:
feature_prism: bosses and pockets.feature_revol: revolved bosses and grooves.feature_ribandfeature_slot.
Each is a constrained boolean internally and records history like every other operation.
normal_projection projects a wire onto a shape along the shape’s normals
(for engraving). It returns the Projected curves on the target faces.
split_face (in ogeom::heal) cuts one face along curves that lie on it
(Projection::OnFace) or are dropped onto it along its normals
(AlongNormals) or along a direction (Along). Each curve must cross the
face from boundary to boundary or close inside it. The pieces share the new
edges, and the history maps the face to its pieces.
Healing
Imported geometry is often imperfect: gaps between faces, edges whose 3D
curve and surface curves (pcurves) disagree, one surface split into many
patches. ogeom::heal repairs what it can within tolerance and reports
what it did. Each repair measures what it achieved. A shape that cannot be
repaired within the stated tolerance comes back with a named diagnosis.
| Function | What it does |
|---|---|
sew (in ogeom::algo) | Stitches faces into shells by merging boundaries that coincide within tolerance. Uses each entity’s own tolerance: a vertex merges with what lies inside its tolerance, and the survivor widens to cover what it absorbed. |
repair_same_parameter | Re-fits an edge’s pcurves until they agree with its 3D curve within tolerance. The report gives the achieved deviation per edge. |
unify_same_domain | Merges adjacent faces on the same surface and adjacent edges on the same curve. Undoes the fragmentation that exchange formats cause. |
merge_edges | Joins chains of edges into single edges where the geometry allows. |
reduce_tolerances | Narrows entity tolerances to what the geometry actually measures. This is the only operation that narrows tolerances, and it does so by re-measuring. |
canonical_simplify | Replaces NURBS geometry that is exactly analytic with the analytic form (for example a plane stored as a bicubic patch, or a circle stored as a rational spline). Each match is verified at every sample, and the report includes the worst deviation. A surface that is only almost a cylinder stays a spline. |
reanchor_periodic_rings | Moves the seam of periodic faces so later algorithms see a consistent parametrisation. |
fix_shape | One-call repair for a shape of unknown quality (see below). Returns a FixReport. |
Reshape | The primitive underneath: a recorded substitution of entities that rebuilds everything referencing them. |
fix_shape steps
- Diagnose.
- Put each wire’s edges end to end.
- Collapse edges shorter than their own vertices’ tolerances.
- Fit missing pcurves.
- Sew loose faces.
- Tighten tolerances.
- Widen any vertex tighter than its edges (and any edge tighter than its faces) to restore the containment rule.
- Diagnose again.
The FixReport lists what it did and what the checker still finds. Small
faces and small solids are left in place, because removing them is
defeaturing (see remove_faces in Booleans).
Measurement and checking
Everything here is in ogeom::algo.
Mass properties
volume_properties, surface_properties and linear_properties return
MassProperties: the measure (mass), the centroid, and the inertia
tensor about the centroid.
- Area and volume are integrated on the exact surfaces, trim and all,
whenever every face has pcurves. The result then reports a
deflectionof zero and does not depend on the one passed in. - Otherwise they are computed on a tessellation at the given
Deflection, which the result reports. The answer converges as the deflection gets smaller. Lengths are always measured this way. - An inside-out shell would measure a negative volume. It is reported as an error, not returned as a negative number.
Distances and projections
| Function | Returns |
|---|---|
distance_between_shapes | Minimum distance and the ClosestPair that realises it. |
project_on_curve, project_on_surface, project_on_planar_curve | Parameter and distance of a point’s projection onto the geometry. |
project_edge_onto_plane | An edge projected onto a plane as an exact 2D curve: point, line, circle, ellipse, parabola, hyperbola or B-spline (clamped); any other curve fitted. |
curve_length | Length along a curve. |
parameter_at_length | Parameter at a given arc length. |
points_by_count, points_by_spacing | Points spread along a curve. |
Bounds and classification
shape_bounds,curve_bounds,surface_bounds,vertex_bounds: axis-aligned bounding boxes.oriented_bounds: a tight oriented box (Obb).classify_in_solid: is a point inside, outside or on a solid. The_exactvariants work on the exact geometry.classify_on_face: the same test for a point on a face.
Validity
check verifies the rules the builders promise: edges within their faces’
tolerance, vertices within their edges’, wires closed where they claim to
be. It returns a Diagnosis of named Problems, each with a Severity.
It accepts everything the kernel legitimately builds, so what it flags is
really broken.
check also reports a solid whose boundary passes through itself: a face
folded through itself, or two faces crossing away from the edges they
share. The faces’ meshes find where they might cross, and each crossing is
confirmed on the surfaces before it is named. A face is probed for which
way it faces at two places, and one that reads out of the material at
one and into it at the other is named too.
check_self_intersection asks a different question: it measures the
exact distance between every pair of faces that share no edge or vertex,
and names any pair that touches, not only one that crosses, but it never
looks at a face against its neighbours or itself. It costs more than the
crossing test in check, as does check_tessellation, which compares a
shape’s mesh with its topology. Call them when that is the question.
Tessellation and drawings
Meshing
ogeom::mesh::tessellate triangulates a shape at a given Deflection:
the maximum distance between the mesh and the exact geometry, plus an
angular bound.
- The triangulation is stored on the model. Read it back with
triangulation_ofandpolyline_of. triangulatereturns one welded mesh for a whole shape.simplifydecimates an existing mesh toward aTarget.hatch_facecross-hatches a face, for section fills.
One mesh per face
A viewer that picks and colours individual faces needs one mesh per face. Adjacent face meshes must use the same points along every shared edge, or cracks appear (for example where a narrow face samples its edges more finely than requested).
- Call
edge_chords_foronce per shape to fix the shared edge points. - Call
triangulate_face_withfor each face using those points.
tessellate stores its faces the same way.
Deflection controls accuracy downstream
Everything that consumes the mesh (mass properties, the mesh exchange formats) carries exactly the error you chose here.
Drawings
ogeom::hlr produces 2D drawings by exact hidden-line removal (not a
rendered image).
-
projecttakes shapes and a view direction and returns aDrawingofDrawnCurves. Each curve has:- a
Visibility(visible or hidden); - a
Source: the model edge, silhouette or outline that produced it.
Silhouettes of curved faces are traced on the exact surfaces.
- a
-
sectioncuts a shape with a plane and returns aSectionViewwith the cut face outlines, ready for hatching. -
broken_sectionis the partial-depth variant.
Because each drawn curve has a Source, a dimension attached to a drawn
line can find the model edge it measures after a rebuild, using the same
history as every other operation.
Documents, assemblies and PMI
A Model holds geometry. An ogeom::doc::Document holds the product
information around it: parts, the assembly tree, names, colours and
annotations. The exchange formats read and write documents, because that
is what a STEP file contains.
let mut document = ogeom::doc::Document::over(model);
let bolt = document.add_part("bolt", bolt_shape);
Assemblies
Products form a tree:
- A part is a leaf.
- An assembly places other products as
Instances.
Instances share geometry. Two bolts in an assembly point to one shape node under different location chains, so a thousand fasteners are not a thousand copies.
roots()returns the top-level products.occurrences_of(root)flattens the tree into placedOccurrences. Each has a path string and its placed shape.
Attributes and PMI
Colours and named attributes attach to products and to individual faces.
PMI (the dimensions, geometric tolerances and datums of a manufacturing drawing) is stored in two forms:
- Semantic: a dimension knows which faces it measures, through the same stable references used everywhere else.
- Presentation:
Calloutpolylines, the drawn form.
Both forms, and the distinction between them, survive STEP.
Views and notes
- A
Viewis a named camera plus the callouts it shows. Annotated models use views to organise PMI into readable sheets. - A
Noteis authored text, optionally attached to a product.
Both survive the native format and STEP.
Undo
The document records every structural change as a step. Undo and redo walk
those steps. undo_depth() returns how many steps are available in each
direction.
Exchange
Everything here is in ogeom::io.
- Exact formats (STEP, IGES, native) carry whole documents: the model plus product structure, PMI and views.
- Mesh formats carry tessellations at the deflection you chose.
STEP
Read and write, at document level:
let block = ogeom::algo::make_box(&mut model, Frame::WORLD, (20.0, 10.0, 5.0), T)
.unwrap()
.shape;
// Exchange works on documents: a model plus product structure, colours,
// PMI, views. A bare part is a document with one product.
let mut document = ogeom::doc::Document::over(model);
document.add_part("block", block);
let text = ogeom::io::write_step(&document, T).unwrap();
let import = ogeom::io::read_step(&text, T).unwrap();
// What came back is the same solid, measured.
let back = &import.document;
let root = back.roots()[0];
let occurrence = &back.occurrences_of(root).unwrap()[0];
let volume =
ogeom::algo::volume_properties(back.model(), &occurrence.shape, Deflection::default(), T)
.unwrap()
.mass;
assert!((volume - 1000.0).abs() / 1000.0 < 0.01);
Round trips preserve assemblies with instancing, names, colours, semantic and presentation PMI, datum systems and saved views.
Bodies come back as:
solids;shells, for parts exported as faces instead of a solid. A surface model stays a shell, under its product like any other body.
read_step returns a StepImport. Its report lists by name every
entity the reader met but did not translate, so nothing is dropped
silently.
Boundary curves off their surface
Real exports often have boundary curves that sit off the surfaces they trim.
| Offset | What the reader does |
|---|---|
| Under 1 mm | Heals it: fits the trim, widens the edge’s tolerance to the measured offset, and emits a warning. |
| Over 1 mm | Treats the boundary as not describing that surface. The face is read untrimmed and refuses to mesh. It is listed in report.untrimmed_faces with its file id and face shape, so you can highlight it or pass it to the healer. check also reports these faces as broken. |
report.summary groups the per-edge warnings (often thousands) into one
entry per kind: count, worst measured value and an example id. Use it for
a status bar. warnings keeps the full text.
Progress and cancellation
Scope a Watch around the call to receive each stage. The readers report
solids as (done, total), so a progress bar can be determinate. The
Watch’s canceller stops the work at the next checkpoint:
use ogeom::core::progress::{self, Stage, Watch};
// A watch scopes a long operation: its sink hears each stage as the
// operation reaches it, and its canceller stops the work at the next
// checkpoint. Stages that know their numbers say them: "step: solid"
// arrives as (done, total), which is what a determinate progress bar
// is made of.
let watch = Watch::with_stage_sink(|stage: Stage<'_>| {
if let Some((done, total)) = stage.progress {
// e.g. hand (done, total) to the status bar
assert!(done <= total);
}
});
let stop = watch.canceller(); // send this to the cancel button
let import = progress::watched(&watch, || ogeom::io::read_step(&text, T)).unwrap();
drop(stop);
// The report is the import's honest ledger: entities the reader met
// and did not translate, warnings one line each, and the faces that
// read without a complete trim, by file id, because their boundary
// sat too far from the surface for any honest pcurve. Nothing here:
// this file is clean.
assert!(import.report.untrimmed_faces.is_empty());
IGES
read_iges and write_iges work at document level like STEP. They cover
the core entity set real files use:
- curve and surface entities, including conic arcs of every kind, ruled surfaces, and offset curves and surfaces;
- trimmed surfaces;
- transforms;
- colour;
- the manifold solid B-rep.
IgesReport names anything outside that set. IGES round trips are tested
by volume like STEP, including periodic cases (spheres, tori) where seam
handling is error-prone.
Native format and .brep
native::write_documentandnative::read_documentround-trip the whole document (exact geometry, tolerances, structure, PMI, views, notes) with no loss. Use it between ogeom sessions.brep::writeandbrep::readstore a single shape as text, for model-level interchange.brep::readalso takes the shapes project archives save in that format, which leave out the header line.brep::read_numberedalso returns the file’s own numbering of edges and faces. Use it when an application names subshapes by their number in the file, since reading walks a wire’s edges end to end and so changes their order.
Mesh and drawing formats
| Format | Read | Write |
|---|---|---|
| STL (ascii and binary) | yes | yes |
| glTF / GLB | yes | GLB |
| OBJ | yes | yes |
| PLY | yes | yes |
| VRML (1.0 and 2.0) | yes | yes |
| 3MF (deflated or stored, multi-part) | yes | yes |
| DXF (2D drawings) | yes | yes |
read_3mf returns one placed mesh per build item. It flattens components,
follows multi-part packages from the production extension, scales the
model’s unit to millimetres, keeps a uniform object colour when the file
has one, and warns about anything read with a caveat.
read_vrml returns one placed mesh per shape the scene draws: face sets
and the box, ball, drum and cone primitives, with DEF/USE, transforms,
switches and material colours honoured.
The mesh writers take the tessellation you built, so the error is the deflection you chose. DXF is the output for HLR drawings: visible and hidden polylines.
Meshes to solids
algo::solid_from_mesh turns a mesh from any of these formats into a solid
you can model on:
- It builds topology from the mesh’s own connectivity.
- It merges coplanar triangles into planar faces. An STL cube comes back as six faces.
- It rebuilds regions lying on a cylinder, cone, sphere or torus as that surface. A meshed bore becomes a cylinder again, and a meshed ball one spherical face.
- It rebuilds a smooth region no such surface holds as an extrusion or a
surface of revolution where one fits, and otherwise, with
MeshSolidOptions::patches(on by default), as a fitted B-spline patch. - It reads meshes as other kernels draw them: a drum or cone drawn in strips from rim to rim, with no points between, comes back round.
Its report says where an open mesh is open, and which curved regions could not be rebuilt exactly and stayed faceted.
To correct a conversion, take its two steps apart. algo::MeshRegions::find
returns the regions, each with its triangles, its surface, how far its
vertices stand off it, and its neighbours. Then:
mergejoins two neighbouring regions and refits them as one;splitcuts a region in two along a path of mesh vertices;fitputs a plane, cylinder, cone, sphere or torus on a region, optionally with its axis or radius fixed.
build makes the solid the same way solid_from_mesh does. Each surface
is checked against every vertex of its region, and a step that cannot be
taken returns a RegionRefusal saying why.
let mut regions = MeshRegions::find(&mesh, &MeshSolidOptions::default(), tol)?;
let merged = regions.merge(a, b)?;
regions.fit(merged, SurfaceKind::Cylinder, &FitConstraints {
radius: Some(5.0),
..FitConstraints::default()
})?;
let solid = regions.build(&mut model)?;
Refusals
A plausible wrong answer is worse than an error: a boolean that leaves a sliver of the tool inside, a blend that looks tangent but is not, an import that silently drops a face. ogeom’s rule: when the kernel cannot produce a correct result, it refuses, and the error names the reason.
// A cylinder of zero radius is not a small cylinder; it is a mistake,
// and the kernel says which one instead of producing garbage geometry.
let err = ogeom::algo::make_cylinder(&mut model, Frame::WORLD, 0.0, 10.0, T).unwrap_err();
assert!(err.to_string().contains("cylinder radius"));
Where this applies:
- Degenerate inputs are refused at construction (a zero radius, an empty wire, a face whose boundary does not close). The error names the offending parameter.
- Restricted capabilities refuse outside their limits. Example: the
medial axis supports convex polygonal faces only. Given a reflex corner,
a hole or an arc, it returns an error saying which one it found, instead
of a wrong axis. Every
partialrow in the capability ledger states its restriction, and the code refuses at that same limit. - Exchange readers report what they skip. An unsupported entity appears in the import report by name and number. Everything that was translated can be trusted.
- Repairs report what they achieved. Healing operations return measured deviations. A repair that cannot reach tolerance says so.
For callers: treat every Err as information. The message says which
input, which limit and which capability boundary you hit. The capability
ledger tells you whether that boundary is a known, scoped restriction.
Scope
Included verbatim from
docs/SCOPE.md,
the normative statement of what belongs in this kernel.
Scope
This file says what belongs in the kernel, what does not, and how to decide a case.
The rule
ogeom is a complete solid-modelling kernel with its data exchange, and nothing else. Two areas go beyond what a modelling kernel usually offers: turning meshes into solids (see below), and inspecting and editing scans (see below).
Four areas are in scope:
| Area | What it covers |
|---|---|
| Foundations | Arithmetic, primitives, solvers, tolerances, errors. |
| Geometry and topology | Curves, surfaces, and the b-rep data model. |
| Modelling algorithms | Intersection, booleans, blending, offsets, sweeps, healing, tessellation, hidden-line removal. |
| Data exchange | STEP, IGES, STL, VRML, OBJ, glTF, PLY, 3MF, DXF, and the document structure these formats carry. |
Three areas are permanently out of scope:
| Area | Why it is out |
|---|---|
| Visualization | Rendering, viewers, interactive selection. A kernel is not a renderer. |
| Application frameworks | A generic label-and-attribute document tree. The exchange document is in scope, because the formats carry it. The framework beneath it belongs to the application. |
| Scripting harnesses | Test shells with their own scripting languages. |
Disciplines that are not solid modelling are out of scope. Examples: constraint solving, feature recognition and process planning. These are real disciplines, but they are not this kernel.
The one addition: meshes into solids
ogeom turns a mesh into a solid and recognizes its surfaces
(solid_from_mesh, recognize_points).
What a modelling kernel usually does: build a shape on a mesh (one planar face per triangle, with shared edges) and merge coplanar faces.
What ogeom adds: it finds which regions of triangles lie on a cylinder, a cone, a sphere or a torus, and rebuilds those regions on those surfaces. A smooth region on none of them is rebuilt on a surface of revolution, an extrusion, or a fitted B-spline patch, in that order of preference, before it is left faceted.
Why: the same reason the exchange module exists. Printers, slicers and model sites exchange meshes. A kernel that reads STL, OBJ and 3MF but can only display the result leaves the application to rebuild the geometry itself.
Recognition meets the kernel’s standard, not a heuristic’s:
- every surface is verified against every sample, at a stated tolerance;
- every edge between recognized faces is placed exactly on both surfaces;
- a region that cannot be built this way stays faceted, and is counted;
- every step is a computation the kernel can repeat and measure. No learned model proposes a surface or a topology.
This is what makes recognition a construction the kernel can stand behind.
Neighbouring surfaces may be built from each other where a geometric relation between them holds within the tolerance: a constant-radius fillet is built from its two supports and a radius, so it meets them tangentially by construction, and a corner is solved onto the surfaces that meet there. The relation only places the surfaces. It is not kept, and the result is a plain solid with no feature tree. A helical region (a thread) is told apart from a surface of revolution so it can go to the fitted patch; no helical surface type is added for it.
A fitted patch is held to the same standard. Facets are exact to the mesh
but nothing downstream can work with them: an offset, a shell or a fillet
across a faceted region gives facets back, or fails. A patch verified within
the tolerance (at the triangles’ interiors as well as their corners) is the
better result, and the facets remain the fallback when no patch verifies.
The fitting itself is already in scope (algo.fitting); this only says
where the converter uses it.
The kernel also exposes the steps under the converter: the regions it found, merging and splitting them, fitting a chosen surface type to one, and building the solid from the result. An application can then correct an automatic conversion. The selection and the interface for it belong to the application.
Scans are the second addition, below. Reading design intent back out of topology is still out of scope.
The second addition: scans
ogeom takes point clouds and dense scan meshes as inputs to geometry, not only to conversion.
What is in:
- inspection: fitting surfaces and features to scan points, distances between a scan and a mesh or a shape, registering one onto another, and sections through a scan;
- editing: cutting, filling holes, smoothing, offsetting and decimating a scan mesh, and booleans between meshes;
- reverse engineering: the steps from a scan to a solid, of which turning meshes into solids (above) is the last;
- the spatial indexes all of these stand on: a hierarchy over a mesh’s
triangles (
Bvh) and a tree over a cloud’s points (PointIndex).
Why: the same reason as for meshes. A scan is noisy, incomplete point data, and every question asked of it (how far is this from the model, where is this bore, what is left if this region is cut away) is a geometric computation. The kernel already holds the geometry those questions are asked against. An application left to answer them itself would need a second geometry library beside the kernel.
The same standard holds. Every result is a computation the kernel can repeat and measure: a fit reports its residuals, a distance is the distance to the stated mesh, and a query answers exactly what brute force over the same data would. No learned model proposes a correspondence, a fit or a repair.
What stays out: rendering a scan, and the interface for picking in it. Those belong to visualization, like the rest of selection.
Code that is out of scope: outside/
Some out-of-scope disciplines were built here before this rule existed, and
they work. Instead of deleting working code, it lives in outside/.
outside/is a separate workspace.- The kernel’s
Cargo.tomlexcludes it by name. - So no path dependency can pull it back in unless someone deliberately deletes that exclusion. This makes the rule structural, not just a statement of intent.
outside/README.md explains why each crate there is out of scope.
How to decide a case
- If a capability belongs to one of the four in-scope areas, it is in scope.
- If it belongs to visualization, an application framework or a scripting harness, it is out.
- If it is not solid modelling or data exchange at all, it is out.
docs/capabilities.toml lists the kernel’s capabilities, one per thing a
caller would ask for, each with a verdict and the evidence for it.
docs/CAPABILITIES.md is its rendered form.
What the scope rule does not mean
It does not mean copying how another kernel does it. CONTRIBUTING.md
forbids taking another kernel’s code or reading its implementation source, and
forbids mirroring its class hierarchy, decomposition or file layout. Scope is
about capability, not structure:
- the ledger is keyed on what a caller would ask for;
- a capability we deliberately provide differently is recorded as
divergent, with the reasoning. It is not a gap.
It is not driven by any one application. A capability inside the four areas is in scope whether or not a given application calls it.
How the scope changes
Only by editing this file, with the reasoning written down. Never through a pull request that quietly adds a crate.
Capabilities
The kernel’s capability ledger, included verbatim from
docs/CAPABILITIES.md.
It is generated from the hand-written docs/capabilities.toml, and
tools/check.sh fails if it goes stale, so this chapter is as current as the
commit the book was built from.
Every capability has one of six verdicts, and the gate enforces the evidence each requires:
| Verdict | Means | Must cite |
|---|---|---|
covered | built and tested | symbols that resolve in the built rustdoc, and tests that exist in the tree |
partial | built, with a stated restriction | the restriction in words, plus symbols and tests |
divergent | deliberately done another way | the reasoning |
absent | not built | a plan entry |
n/a | not a kernel capability for us | the reasoning |
unreviewed | not yet reviewed | counted against a ratchet that can only go down |
Rename a cited symbol or delete a cited test, and the build fails.
Capabilities
Generated by tools/capabilities.py generate; do not edit. The input
is docs/capabilities.toml, the only file a person edits. docs/SCOPE.md
defines what belongs in the kernel; this file records where it stands.
One capability per thing a caller would ask for, not per type and not
per module. Each verdict cites the symbols and tests that back it, and
tools/check.sh fails if they drift apart.
Where the kernel stands
| verdict | capabilities |
|---|---|
covered | 100 |
partial | 13 |
divergent | 6 |
119 capabilities. Ratchet: unreviewed_max = 0.
Capabilities
ogeom-algo
- algo.bounds: Guaranteed and tight bounding boxes, axis-aligned and oriented, for curves, surfaces and shapes ·
covered - algo.builders: Building topology by hand: vertices, edges, wires, faces, shells, solids ·
covered - algo.classification: Classifying a point against a solid or a face: in, out, or on, exactly ·
covered - algo.curve-sampling: Sampling curves: by count, by spacing, by deflection, by abscissa ·
covered - algo.fit-outline: An outline of lines, tangent arcs and circles fitted around a 2D silhouette, every point between a play and the play plus twice a tolerance outside it, negative play inside it, optionally about the silhouette with holes and notches narrower than a width filled ·
covered - algo.fit-segments-2d: The fewest lines and tangent or cornered arcs fitted to a 2D polyline, open or closed, every input point within a hard tolerance of its piece ·
covered - algo.fitting: Interpolating and approximating points with B-splines, curves and surfaces, and fitting scattered points over a seed surface’s chart to a tolerance ·
covered - algo.global-properties: Volume, area, mass, centroid and principal moments of shapes ·
covered - algo.history: Every operation emits history: what was generated, modified, deleted ·
covered - algo.medial-axis: The medial axis of a planar region ·
covered - algo.mesh-orientation: Face directions, axes of revolution, principal frames and open rims of a mesh ·
covered - algo.nurbs-conversion: Converting a shape’s geometry to NURBS form ·
covered - algo.patch-network: A network of B-spline patches fitted to a scan in one sparse least-squares system: shared curves one set of poles, curves that are not creases held tangent pole by pole, sides that may be chains of curves meeting at T-junctions, corners of other than four patches held tangent where the tolerance allows, spans raised per strip where a patch misses, optional parameter correction, built into a shell of faces sharing their edges ·
partial- restriction: Patches are four-sided, or three-sided with one side shrunk to a point. A side may be a chain of curves (a T-junction), but the side opposite must cross as many strips, split or as one curve; a curve of a split side may itself be crossed by junctions elsewhere (nested junctions), unless the junctions are carried round a loop. A curve between two patches is held exactly tangent where both its ends are on the border, at corners of four patches or at T-junctions; a curve between two junctions whose knot ratios differ is left near tangent. A corner of other than four patches is held exactly tangent only where every curve into it runs on to the border, to a corner of four patches, to a T-junction (the line across the patch through the junction then held tangent at the corner’s ratio, not
C¹) or to other such corners (an even corner, or a corner whose curves all end at others, as on a ball covered as a cube, settled with the rest where the ratios asked round every cycle of its scales close), and the fit with it meets the tolerance; where it does not, a corner of an odd number of patches, or an even number above four, whose curves each run on to the border or to another such corner has its gluing spread along its curves, every knot inside their strips doubled and the patches onlyC¹across those knots, which meets 0.001 on clean caps of five, six and seven patches. Where the fit near tangent misses too, each held fit that missed is finished from its spans: the points re-taken at their nearest points on it (by the patch across a shared side where a point lies past its own patch’s side) while that comes nearer them and the worst distance grows no worse, the fit refined on after each step with points past their cells’ spread counted as misses, for at most eight steps, the noise read once from the fit at the seed’s parameters; this meets 0.001 on the clean ball covered as a cube and the clean filleted box corner. A clean cap of eight patches at 0.001 still misses (two points on its rim end refinement as strays); there, the curves are fitted near tangent from the points, their angle measured intoworst_tangentand the corner’s angle reported. A fan, a point every patch there is three-sided at, is held exactly tangent along its curves and at the point where that meets the tolerance (fans of three to six patches meet 0.001 clean); a point where a three-sided patch meets four-sided ones is fitted near tangent. Parameter correction otherwise runs only after refinement, at the final spans, and only throughfit_network_chained; the points are those of the region the network covers.
- restriction: Patches are four-sided, or three-sided with one side shrunk to a point. A side may be a chain of curves (a T-junction), but the side opposite must cross as many strips, split or as one curve; a curve of a split side may itself be crossed by junctions elsewhere (nested junctions), unless the junctions are carried round a loop. A curve between two patches is held exactly tangent where both its ends are on the border, at corners of four patches or at T-junctions; a curve between two junctions whose knot ratios differ is left near tangent. A corner of other than four patches is held exactly tangent only where every curve into it runs on to the border, to a corner of four patches, to a T-junction (the line across the patch through the junction then held tangent at the corner’s ratio, not
- algo.place-copy: Copying and transforming shapes, rigidly or generally, sharing what can be shared ·
covered - algo.point-projection: Projecting a point onto a curve or a surface, nearest first, all minima found ·
covered - algo.primitive-fits: Best fits of planes, spheres, cylinders, cones, tori, circles and lines to points, with residuals, a mesh region grown for one from clicks, its rounded rim peeled, a rim circle or a crease line traced along a mesh’s sharp edges from a click, and the line where the faces either side of a rounded edge meet ·
covered - algo.primitives: The primitive solids: box, wedge, cylinder, cone, sphere, torus, half-space, prism, revolution ·
covered - algo.rigid-registration: Rigid registration: closed-form absolute orientation, point-to-plane ICP onto a mesh with free motions, and symmetry planes ·
covered - algo.sewing: Sewing faces into shells along coincident edges, or across a gap asked for, with spatial acceleration ·
covered - algo.shape-distance: Distance, proximity and overlap between whole shapes, and self-intersection ·
covered - algo.signed-distance: Signed distances from many points to a mesh or a shape’s own faces, facing-aware ·
covered - algo.solid-from-mesh: A solid from a triangle mesh: coplanar triangles merged into planar faces, curved regions recognized as cylinders, cones, spheres and tori ·
covered - algo.spatial-acceleration: Generic bounding-volume hierarchies ·
divergent- reasoning: No generic BVH container library: the kernel’s standing indexes are keyed on what a caller asks of a mesh or a cloud (mesh.spatial-index), and the acceleration structures inside its algorithms (sewing’s cell filter, the classifier’s bounds, the distance walk’s pruning) stay internal to them, sized and keyed for their one question. Picking, outside the kernel (outside/crates/ogeom-select), builds on mesh.spatial-index’s triangle hierarchy.
- algo.surface-sheet: Laying a flat sketch onto a curved face: geodesic polar coordinates, curves carried with pcurves ·
partial- restriction:
SurfaceSheetlays a sheet by geodesic polar coordinates (the exponential map at its origin) on any face whose surface has first and second derivatives: a plane, a cylinder and a cone by their exact development, a sphere in closed form, every other surface by integrating its geodesics and their Jacobi field in its chart, the step error held per unit of length. Lengths along lines through the origin are kept;distortionreports the across-ray stretch over the points carried. Carried curves are fitted chart curves whose lift stands within the tolerance asked (a line on a plane or a cylinder is exact), continued along the curve across periodic seams, so a curve going round a pole of the chart is one chart curve winding a whole period. Refused: an origin on a pole of the chart, a sphere’s reach to its origin’s antipode, a point past the reach, on a pole, off a bounded surface’s domain or past a fold of the map; as one chart curve, a curve running through a pole of the chart, whose chart curve jumps there. On a spherechart_piecesandchart_edgescarry such a curve as pieces meeting at the pole, andsplit_facecuts a face along them, closing the region along the pole’s row; on a cone they cut it at the apex alike, except along the sheet’s ray from the origin through the apex, where a straight line has no one way on past a point holding less than a whole turn; through a revolved or spline surface’s collapsed side it is refused by name, a geodesic integrated in its chart never carried past the pole (“crosses a pole of the surface’s chart”).SheetMethod::LeastDistortionlays the conformal flattening of the covered geodesic disc that stretches nothing along its rim, scaled to keep the disc’s area (on a sphere the stereographic projection): solved on a polar mesh by one Poisson problem for the scale and a harmonic extension of the rim, rings doubled until two meshes agree within the resolution asked, and mapped back by a fitted polynomial;flattening_errorestimates the departure from the smooth map, a bound only where each ring doubling at least halves it (measured near four times per doubling on a sphere and a torus, not proved). It is conformal, not area-keeping point by point: a 6 x 4 rectangle at R = 10 with reach 5 covers 25.07 on the sphere, as the scaled stereographic projection does.SheetMethod::AsRigidAsPossiblelays the as-rigid-as-possible flattening of the same disc, settled by local and global rounds from the conformal one on a mesh of 32 rings, and not scaled; its energy is not convex, and the sheet is judged by its measured stretch, not by any distance from a smooth map. Its length ratio against the surface’s metric is measured at the flattened mesh’s vertices and centroids within the reach (not bounded between them) and the sheet is refused past the stretch asked, or where a triangle folds or the rim crosses itself. On a sphere of radius 10 with reach 5 it measures 0.969 to 1.017 in length and 0.970 to 1.034 in area, against the conformal 0.938 to 1.063 in area.
- restriction:
- algo.validity: Diagnosing a shape’s validity, by entity, with named problems ·
covered - algo.volume-difference: The volume between a scan and its solid on a grid: nodes read by exact parity against the scan and against the solid, those in the solid’s mesh band against its true surfaces; holes filled and doubted, differences joined into missing and excess regions with volume, depth, layer or lump, and the shallower than a cell dropped ·
covered - algo.wall-thickness: Wall thickness at many points of a mesh or a shape’s own faces, by rays or by medial balls ·
covered - algo.zone-fits: Form as the standards define it: the minimum-zone flatness, circularity, cylindricity, sphericity and straightness of points, and their maximum-inscribed and minimum-circumscribed circles and cylinders, exact or certified by the Chebyshev conditions ·
covered
ogeom-bool
- bool.booleans: The boolean operations: cut, fuse, common, section and split, exact and fuzzy, with argument checking ·
covered - bool.cells: The cells builder: arbitrary take/remove over the fused arrangement ·
covered - bool.cut-by-mesh-region: Cutting a solid by a break surface marked on a mesh: the break extended over its average plane clear of the solid, holes closed, thickened into a slab and cut away, one side kept, the solid’s faces left on their own surfaces ·
covered - bool.defeaturing: Removing a set of faces from a solid by extending and re-intersecting its neighbours ·
partial- restriction: Inner-loop features (bores, bosses, pockets whose rim is a surviving face’s inner wire) remove in full generality: wire surgery, no re-intersection, the block back to the last bit. Band features (fillets and chamfers along an edge) close for any number of bands in one call, each running straight through with two end faces or wrapping with none, and bands meeting at a corner close together: each recovers its own crease, the corner is where one crease pierces the other’s side, and a wedge’s flush cap named with its band folds into the band’s crease; a tangent chain’s bands close together too (a stadium’s rim rounded in one call), the junction being the foot on either crease of the cross-section edge the two bands share, and a band across a circular crease’s seam read as one run about its own centre rather than its complement; a rebuilt face whose surface has no closed-form pcurves fits them by projection, tolerances widened by the measured offset. A rim blend (a drum’s top, a bore’s mouth, a boss’s seat) takes a whole ring out of its neighbours and closes on the circle they meet along: the neighbours’ surfaces tell that wound from a bore’s, whose faces never meet and whose rings are simply dropped, and a neighbour’s own outer boundary may be the ring, so a drum’s cap grows back to its rim. The wall’s seam reaches the recovered circle, which is where it is cut and what the seam extends to meet. Wires are spliced in the face’s own order, since a seam stands in its wire twice, and a gap that leaves and arrives at one vertex is wound the way the rim it replaces was. A feature of several faces each bordering only one of the neighbours that must meet again (a step along an edge, a notch cut from a corner, a pocket open at an edge) closes by extension: every two neighbours whose surfaces meet across the wound close the stretch of that curve it took, as a new edge between the faces it pierces where they share none there, or by running on an edge they share, cut short or split, to where a third neighbour pierces the curve or to the other half’s end. Neighbours the wound left on one surface facing one way (a slot right across a top, its two halves on one plane) come back as one face through both halves’ surviving edges, and a curve two neighbours meet along that no other face stops (a rebate round a drum’s rim) is taken whole, closing on itself at the wall’s seam: the box and the drum come back exactly. What stays refused by name: a wound whose sides meet in no curve and lie on no one surface, a removal that would leave a face with no boundary and no edge to grow to, and a gap the recovered edges do not bridge.
- bool.glue: Gluing shapes along known-coincident boundaries ·
divergent- reasoning: Subsumed, and recorded as a settled decision in docs/PLAN.md: the boolean’s same-domain unification already skips nothing it needs and unifies what glue would, so a separate glue mode would be a second spelling of the fuse with a faster wrong answer available. MakeConnected’s job (conformal multi-body assembly) falls out of the fuse plus history.
- bool.make-periodic: Making a shape periodic so instances tile without seam duplication ·
covered - bool.make-volume: Making volumes from a soup of faces: the arrangement’s closed cells ·
covered
ogeom-core
- core.progress: Cancellable, staged progress through long operations ·
covered - core.tolerances: The tolerance vocabulary: confusion, angular, intersection, and per-entity widening ·
covered
ogeom-doc
- doc.appearance: Colours, layers, materials and textures, per shape and per sub-shape, with inheritance resolved ·
covered - doc.colour-values: Colour values and their names ·
covered - doc.pmi: Semantic PMI: dimensions, tolerances with datum reference frames and material modifiers, datums, datum targets, and their presentation ·
covered - doc.product-structure: Products, occurrences and instances with placements: the assembly tree ·
covered - doc.properties: Names, user properties, validation properties, lengths and centroids to check a transfer by ·
covered - doc.saved-views: Saved views and standalone note objects in the exchange document ·
covered
ogeom-fillet
- fillet.asymmetric-rounds: Asymmetric rounds: a different distance onto each face, elliptical and conic sections ·
partial- restriction: A straight edge between two planes, convex or concave, rounds to a band meeting one face at one distance and the other at another, its section the ellipse tangent to both there (the circular round’s arc stretched to each distance) or a conic of shape rho: the rational quadratic arc swept along the edge, exact, tangent to both faces along its contact lines. A whole rim where a planar cap meets a coaxial cylinder or cone (a drum’s top, a bore’s mouth, a boss’s foot, a frustum’s top) rounds to that section revolved about the axis, exact, tangent to cap and wall along their contact rings, and an open arc of such a rim to that section revolved over the arc, its ends the circular round’s. A tangent chain of such edges, asked in one blend_edges request naming one face with the same distances (a pad’s top loop, a pocket’s floor loop), closes: each band is capped in its end sections, which the bands either side of a junction share. On a straight edge between planes each distance may vary along the edge by a law (linear, or through points), the band exact: the section’s control points carried along the laws’ cubics, capped square where the edge ends at faces square to it. Any other crease the march seats (a spline’s crease along a wall, a bore’s rim on a drum, open or closed) takes a band fitted to the march’s target through the conic sections station by station: each leg walked its distance along its face’s section in the plane square to the crease, as a marched bevel’s is, and the conic tangent to each face’s section at its contact, through the shoulder where those tangents meet. There, with equal distances, the elliptical section is the round of constant setback, not the constant ball, the dihedral varying. Naming the other face with the distances swapped builds the same solid to the bit, and equal distances with the elliptical section are the circular round by the same call. A straight band’s ends are the circular round’s: square caps at a wall, run out across a face that cuts the corner away past an end, cut off by a face overhanging the corner. In a blend_edges request it stands beside the request’s other rounds and bevels where it meets none of them at a vertex; an asymmetric round of equal distances and the elliptical section meets them as the circular round does. At a convex corner of three planes whose three edges are all in the request, asymmetric rounds close with each other, with rounds and with bevels: each band stopped at the least setback where its section clears the others, and the opening closed by one patch held inside the corner’s planes, tangent to each round’s band, the same solid whichever face each names and in whichever order the request lists them. Refused by name: a crease whose sections do not seat all along it (a straight edge between a flat and a drum whose sections run off the drum); a corner that is not a convex corner of planes, or where an edge is left sharp; a varying distance on a rim, along a chain or at an end not square to its edge; asymmetric rounds meeting at a vertex other than as one tangent chain, or one meeting another blended edge at a vertex; a contact set back past its face’s side or a rim’s contact reaching its axis; and largest_fillet_distances answers the largest common scale of a pair that fits, on a straight edge between planes or a rim.
- fillet.bitangent: Bi-tangent blend construction ·
divergent- reasoning: Subsumed, per the settled decision in docs/PLAN.md: the 2D repertoire carries bi-tangency in the plane and the blend family’s own envelope carries it in space, so a separate bi-tangent constructor would be a third spelling of two existing ones.
- fillet.chamfers: Chamfering edges: symmetric, two-distance, distance-and-angle ·
covered - fillet.corners-2d: Filleting and chamfering the corners of planar wires ·
covered - fillet.edge-blends: Blending edges: constant and variable radius fillets, rolling-ball, marched where no closed form exists ·
partial- restriction: Single edges, tangent chains and full rims blend, constant and variable radius, and the corner where three blends meet is closed. A concave band on a curved edge whose contact leaves its face past the face’s side (a boss’s foot where the plate’s side runs tangent into the boss, the plate’s top narrowing to a cusp there) is cut off at that side’s plane, its fill ending flush with the side; where the solid stands beyond that plane, or no planar side bounds the face there, it is refused by name rather than laid over nothing. Where the request also rounds the plate’s top edge along that side, one tangent chain through the tangent point, the edge’s convex round turns into the foot’s concave one through an S on each side of that point: two patches G1 to the bands, the top, the side and the wall, held inside the solid and the foot’s fill. A cusp rounded on one side of the tangent point only (an arm’s side edge and its arc into a hub) closes the same way with one patch. It builds at radius 1 on a wall of radius 6 (printCAD’s case, and the arm and hub); other radii are refused by name. Also in this restriction: a rim where a flat cap meets a coaxial cylinder or cone (a drum’s top, a frustum’s foot, a tapered bore) rounds to its exact torus band, where the tube stands clear of its axis, and to the fitted band where that is what builds; an arc of such a rim ending on a planar face oblique to it, with no other edge of the request there, runs its torus band on past the end until the ball’s section has left the solid across that face within four radii, as a straight band’s run-out is read (a fill cut back to the face’s plane), where a cap in the meridian there would leave a sliver of the corner. a corner blend applied through the boolean’s tangency at a face’s own vertex. The marched fillet carries the rolling ball to topology on seats no closed form speaks (a fitted seam between two analytic walls, a conic seat re-opened to its full loop), with the blend fitted through the ball’s own arcs and the legs melting on the hosts’ exact surfaces. An open seat runs out: where the crease ends at a wall the band runs on until the ball has left the solid and the cut trims it against the wall, a straight band so across a boss’s cylinder or cone cutting its corner away obliquely (a D-shaped block’s chord, its wall square, drafted or overhanging, its drum upright or drafted) where it runs out alone; where the faces at its end all turn back behind the crease (a re-entrant turn of its wall, however slight), its continuation runs into the material and the band stops at the vertex; where it ends at a split (a seam vertex, or a neighbouring blend’s own rail), the band is capped in the end section’s own plane, and two such blends meet cap to cap along the shared arc, both caps consumed, in either order. Two convex creases of one request that share a face and turn at a vertex meet there: carried on past it each is a straight band of its end section, and two of one radius cross in the plane bisecting their centre lines, exactly where the reflection in it carries one end section onto the other (a flat shared host, walls whose tangent planes at the vertex reflect into each other). There, across a re-entrant turn both run on to that plane and take the corner cell both hold, where the material holds it (a straight band between planes whose end section reaches past that plane, its wall drafted out under the corner as a moulded pocket’s are, is carried on past it and cut back to it); across a convex turn of a curved crease both stop in it where the bands’ curvature keeps their sections there within a marched band’s fit; either way the two are joined across it into one wedge. Where the end sections differ and both creases are straight (a drafted wall beside a square one), across a re-entrant turn each band is carried on with what stands past its wall and the two are intersected in one boolean, so the corner takes what lies above both bands, bounded by their own intersection, where the material holds it all. Across a convex turn of two straight creases, whatever their sections (an acute, square or obtuse outside corner, its walls square or leaning alike or apart), each band runs out as it does alone and the cuts take their union, as they do where one of the two is a drum’s rim beside a straight crease (a D-shaped block’s arc beside its chord, in either order, its drum upright or drafted) and, past where they meet flush, beside a spline crease (a rib’s wall running into a boss’s rim, the rib’s band run out past the vertex as alone). Across a re-entrant turn one of the two may be a drum’s rim beside a straight crease, the material within the drum (a boss fused onto a block whose wall leans either way, the boss upright or overhanging, a cone narrowing to its foot or standing out at it) or outside it (a bore up to a drafted chord): the rim’s torus band is taken from its cap to where its section stands wholly past the other wall, widened by what stands off the drum before the rim’s cap (beside the chord’s own band too, where a chord through the bore’s axis or past it leans off the drum), intersected so, less what stands off the drum past the other wall, and the rest joined back on. A bore whose chord wall overhangs meets as without a junction, where the band carried on past the rim’s cap would reach under the pocket’s floor, and so does a cone standing out at its foot beside a square wall close to the rim’s cap, where that band crosses the cone at a turn the boolean reads as a touch. A loop of junctions closes, its last junction taken and the ring’s wedge sewn to itself (a square pocket’s rim meets at all four corners, and a disc’s pocket up to a chord at both). Crossed junctions stand beside flush ones in a loop or chain of the request’s edges, the flush ones taken first; a band crossed at both ends is cut in two halfway along, each half joined into its own corner and the two joined again where both hold the band’s one section. Inside corners off square in plan and corners whose walls both lean are taken, the open corner past both walls’ planes taken out of the two widenings, and so is every corner of a pocket off square in plan whose walls each lean their own way. Elsewhere (a crossed junction that would leave two joined bands meeting without one, a turn folding back past the solid, a wider convex turn of two curved bands, or of a curved band other than a drum’s rim beside one of another kind) the later seat runs on through the earlier band or the caps stand, as without a junction, and so wherever the meeting does not close. An L-bracket’s end rim blends over its re-entrant band as one tangent chain (line, the band’s own end arc, line), the ball rolling on the concave cylinder between the lines, to the closed form. A fill and a wedge asked together stop at each other, since only blends that round the same way run on through one another’s bands; whichever is asked first takes the corner, and both orders are exact. Cone, sphere and torus hosts march and blend: the chart inverted in closed form, a band’s connector fitted where no closed form lifts the chart segment, a looping seat with a corner at its join steered by a smooth refit of itself, the band’s first station re-solved on the apex column so a rim opened at the host’s own seam leaves no sliver; a full circular rim whose hosts are not a cap and its coaxial wall takes the march too. A fitted host marches: its chart inverts by projection, a closed patch’s seam wraps as a period does, the patch is continued past the face by a few radii so the ball can run out, a seat the boolean split into arcs is closed back through the neighbours the hosts share and marched as the loop it is, the rail loop slid into the host’s own window, and the wedge melts: a straight edge of a converted box, the crease round a converted post split at its seam, a converted cone’s rim, a converted drum’s rim. Under it a marched section the centripetal fit misses is fitted again by chord length, and a loop cut at a closed patch’s seam is closed on it; a melt the boolean still cannot resolve refuses by name. A rim beside a sphere’s pole whose rail passes over the pole blends with the sphere’s leg running from the rail to the pole, the rim cut from it. A bore whose wall toward the pole is thinner than the ball blends the same way. A rail passing the pole closer than one stride of the march, where its chart turns half round faster than the stations follow, is taken on the same sphere charted with its pole on another axis of its frame and its seam through the widest gap between the seat and the faces’ edges: the leg is built there and the solid’s faces on the sphere are rebuilt on that chart, their old seam and pole edges dropped. Where those faces are not the sphere less some disks (or one disk less others) on the turned chart, the rail is refused by name. A seat whose hosts turn tangent at an end of the crease (the seam of two equal drums crossing, at the points where they touch) pinches there: the stations are solved one by one toward the pole, the pole’s collapsed section closes the band, and the wedge has no cap at that end. A tangency inside a crease with none at its ends is refused by name. A straight crease whose faces are planes or drums parallel to it (a wall meeting a drum along a ruling) blends exactly: the section swept straight, its band a drum of the fillet’s radius. A vertex where a curved face meets rounds with the one ball touching its three surfaces, its centre walked to a radius in from each and its compartment bounded by the planes through the centre and each pair of touch points, so each band’s end section is the ball’s own;
fillet_edgescloses such a corner, and the bands one at a time with the corner tool after agree with it to rounding when the ruling’s band goes first, or when the rim arc’s band goes first: the straight band’s section through the rim’s torus runs tangent to the torus’s end meridian into the corner, and ends at that vertex. The straight band first runs out across the drum past that vertex, as it does alone, and takes the corner with it; the ruling’s band and the rim arc’s after it agree in either order. The corner tool rounds any convex planar vertex. Where one ball touches every face (a square or oblique trihedral corner, a square pyramid’s four-edged apex), its block is the polyhedron of the N host planes and the N planes through the ball’s centre square to the edges, where each band’s rim and the ball’s coincide. Where no single ball does (a rectangular pyramid’s apex, an irregular pentagonal one), the region the ball’s centre may occupy has a tip of several vertices joined by ridges, and the corner is the exact envelope of the rolling ball: a sphere at each tip vertex, cut with its own compartment, and the flush fillet of a virtual crease along each ridge, the compartments meeting cap to cap on the planes square to the ridges; a ridge seven microns long is kept as the sliver of cylinder it is. At more than three edges the corner goes first and the flush fillets follow, since bands built before the corner crash into each other at the apex. The ball’s pole stands along a ridge where there is one, its seam turned from the corner, and otherwise the tool is offered on each of the corner’s 2N labellings in turn, the first that closes standing; the boolean closes every labelling of an oblique corner and of a square pyramid’s apex, one solid each time. The flush fillets follow at a sharp apex too, and after a corner whose sphere clears a fourth plane by a few hundredths of a millimetre, where the envelope keeps a sliver of that plane beside the patch and the band meeting the sliver runs a straight end tangent to the sphere’s rim: a line and a circle share no stretch, so the tangency splits neither. The curved-seat corner is closed in either order: throughfillet_edgesa marched blend and a straight one meet at their corners, the later’s run-out walking on under the earlier band, and the boolean decides once per shared edge piece whether it is dust. A sub-piece of one edge lies in several charts, each with its own snap, so deciding once means a sliver that one chart collapses is collapsed in every chart. Where three or more edges of onefillet_edgescall meet at a vertex, the corner tool closes it with the rolling ball’s patch before the bands, which stop flush against it: a box corner’s sphere octant, a pyramid apex’s sphere or spheres and cylinders; a corner the tool does not speak keeps the bands’ caps.blend_facesandfillet_facesround curved faces that share no edge: swept from one section where the surfaces share a direction or an axis, marched where they cross otherwise; a swept round whose crease’s edges end on a planar face oblique to the crease runs on past that end until the ball’s section has left the solid across the face within four radii, cut back to the face’s plane, as a chamfer along an arc of a rim ending so does. Between separate faces a marched ball that touches both all the way round a closed seat (a tube through a floor at a slant, two pipes crossing) gives a band fitted closed round the loop, each face cut along its closed line of contact, across the seam of a closed B-spline face whose chart closes on itself without repeating as across a repeating one. Where both faces let a marched ball go round a closed seat, never at the same place, the round is a run over each stretch both hold it: capped and applied in turn on a solid, and between separate faces a round over each run. A marched line of contact crossing a face more than once (a face letting the ball go at two places round its seat, or a line running out of a face and back) holds the ball over a stretch each time, and the round is a run over each overlap of the two faces’ stretches; withtrimeach face is cut along every stretch and keeps every piece beyond one. Refused by name too: withtrim, a marched line of contact crossing a face’s seam or crossing a face over a stretch the other face does not reach; a line of contact crossing a face more than once on a seat with a closed form; and a round crossing a face between the two (a ball wider than a chamfer between the faces it rounds). The ball touching both faces is the one the round’s arc lies on, and a blend is refused by name where another face of the solid, on any surface, B-splines included, holds a point inside it at any station of the run (a chamfer, a round or a wedge between the faces, over all or part of the run), read over the whole run by halving between stations until the centre’s travel fits under the face’s clearance: the strip goes with the corner only where it lies wholly outside the ball. Between two planes and on the swept path only the ball’s lune counts, the part of it between its centre and its arc, searched cell by cell over its section and the run where a face comes inside the ball: a part thinner than the ball, its far side reaching into the back of the ball only, is rounded, and an older, smaller round between the faces goes with the corner, the ball’s side read inside the lune. On the marched path, where the section turns along the run, the lunes are searched the same way, each cell’s travel between two stations bounded by the centre’s and the contacts’ arcs (chord and twice the height) and the radius times the turn of the contacts’ directions, so a part thinner than its ball rounds there too, and an older round just smaller than its ball goes with the corner.blend_facesandfillet_facesround every corner the surfaces cross in that the ball rolls in (a drum’s flat and its wall, two drums fused), where the corners stand apart by more than their reach or a line parts the polygons holding their sections. Faces the crease or a line of contact meets, and faces holding a capped run’s end, are where the round runs off and are not asked. Between two planes a blend is refused by name too where the ball touching both touches a face’s plane off the face at a station of the run (a face narrower than the ball’s setback). An edge round refuses by name where its faces meet tangent (no corner), where the ball’s contact on a face stands past the face’s far side in the material (a boss on the wall) or all along the edge (a face narrower than the setback, which a ball would roll over onto the face beyond), but for a convex straight edge beside a planar strip (a chamfer, a narrow top) whose far edge is convex too: where the strip lies wholly in the corner the ball touching the planes either side of it takes off, its ends on faces square to the edge or slanted to it, the round is that ball’s and the strip goes with the corner, cut back to slanted ends by their planes; and for a convex rim beside a ring (a coaxial cone or torus: a chamfered or small-rounded boss’s rim) whose far circle is on the wall: where the ring lies wholly in the corner the ball touching the cap and the wall takes off, the round is that ball’s torus band, the ring going with the corner, the edge then refused for its faces meeting tangent rounding too; and for a convex edge on a marched seat beside a strip between it and one other face (a bevelled bore’s rim on a drum), the strip closing on itself or running out at end faces (a bevelled spline crease on a slab): where the ball touching the faces either side of the strip along their crossing keeps every face out of its lune, the strip stands behind both surfaces and no other edge but seams comes within the corner’s reach, the round is that ball’s marched band and the strip goes with the corner. A concave straight edge beside such a strip whose far edge is straight and convex (the seam of two solids fused at a slant) rounds with the ball touching the other face and resting on that far edge, its centre on the ellipse, or the line, where the plane a radius off the face meets the cylinder a radius about the edge, the band fitted through the balls’ arcs to the marched band’s target with its rails on the face’s plane and the edge’s line, where over the whole crease the ball rests on that edge alone, its contact moving one way along it, touches the face within it, comes within its radius of no other face and its sections do not cross (an end face meeting the far edge’s end, touched there by the end ball, is clear of every ball). Where a ball on that edge comes within its radius of the faces beyond it, and the strips beyond the narrow face are planes whose far edges run parallel to the edge over its whole length, ending on planar faces square to it or slanted, the ball may rest on the far edge of a strip beyond (a wall on a staircase with a deep drop past its lower step): the round is the cylinder about the line its centre runs on, and the fill is the prism of one section fused on, the strips it reaches over going into it. Where those far edges are slanted to the edge, the ball may rest on a convex far edge of a strip beyond, its centre on the ellipse about that edge’s line, the round the band through its arcs and the fill the band’s wedge with one face on each strip between, fused on; the walk takes each strip’s far edge as the first it crosses square from the edge before, and a face is cleared only of its part in front of the touched face’s plane and of the planes through the far edge’s line square to the ball’s radius, so a held far edge may end where the end ball touches an end face; past a concave far edge over such strips the ball may come down on the face they run into, the floor going in as the last strip. Past a concave far edge the ball may touch the plane face the strips run into instead (a flat in an inside corner, a staircase coming down on a floor): the round is the cylinder a radius off that face and the touched one, the strips between going into the fill. Where the ball changes what it holds partway, the round is a chain of such bands meeting tangent on the section where the ball holds three things at once, each piece the ball’s holding a face on each side (the cylinder between the planes), a face and an edge of the other face (the ellipse), or a face and a corner of the other face (the circle a radius over the face and a radius from the corner, its band’s arcs all ending on the corner): it comes down on a strip that widens past its setback, narrows onto the other face’s far edge, or rolls round a corner of a triangular strip onto its next edge, under the same conditions on every piece. It is refused by name where the ball comes onto the face beyond a far edge along the crease (it then rounds the corner between that face and the one it touches, another seam’s round), rests on far edges of both faces at once, or leaves either face, and in a request where the edge shares a face or a vertex with another of its edges. A ball whose contact runs off a face along part of an edge, out of the solid, is cut off there as alone, in a request too: where the edge shares a face or a vertex with another of the request, or stands within the two setbacks’ sum of another, its band is checked against theirs on the stretch the face holds, up to the face’s side, and on the face across that side, at the side and, for a convex edge, where the ball’s arc meets that face. It refuses by name where the face it runs off, or, convex, the face across, is not planar with straight sides, where a convex band is cut off by more than that one face, and where those points stand inside another band. A request may give each edge its own radius (fillet_edges_with): two bands of different radii turning at a vertex take the union of their cuts, a tangent chain steps its radius at a junction, and a corner where three or more bands of different radii meet is refused, since no ball rolls round it and the patch between the bands’ ends is not built.
- restriction: Single edges, tangent chains and full rims blend, constant and variable radius, and the corner where three blends meet is closed. A concave band on a curved edge whose contact leaves its face past the face’s side (a boss’s foot where the plate’s side runs tangent into the boss, the plate’s top narrowing to a cusp there) is cut off at that side’s plane, its fill ending flush with the side; where the solid stands beyond that plane, or no planar side bounds the face there, it is refused by name rather than laid over nothing. Where the request also rounds the plate’s top edge along that side, one tangent chain through the tangent point, the edge’s convex round turns into the foot’s concave one through an S on each side of that point: two patches G1 to the bands, the top, the side and the wall, held inside the solid and the foot’s fill. A cusp rounded on one side of the tangent point only (an arm’s side edge and its arc into a hub) closes the same way with one patch. It builds at radius 1 on a wall of radius 6 (printCAD’s case, and the arm and hub); other radii are refused by name. Also in this restriction: a rim where a flat cap meets a coaxial cylinder or cone (a drum’s top, a frustum’s foot, a tapered bore) rounds to its exact torus band, where the tube stands clear of its axis, and to the fitted band where that is what builds; an arc of such a rim ending on a planar face oblique to it, with no other edge of the request there, runs its torus band on past the end until the ball’s section has left the solid across that face within four radii, as a straight band’s run-out is read (a fill cut back to the face’s plane), where a cap in the meridian there would leave a sliver of the corner. a corner blend applied through the boolean’s tangency at a face’s own vertex. The marched fillet carries the rolling ball to topology on seats no closed form speaks (a fitted seam between two analytic walls, a conic seat re-opened to its full loop), with the blend fitted through the ball’s own arcs and the legs melting on the hosts’ exact surfaces. An open seat runs out: where the crease ends at a wall the band runs on until the ball has left the solid and the cut trims it against the wall, a straight band so across a boss’s cylinder or cone cutting its corner away obliquely (a D-shaped block’s chord, its wall square, drafted or overhanging, its drum upright or drafted) where it runs out alone; where the faces at its end all turn back behind the crease (a re-entrant turn of its wall, however slight), its continuation runs into the material and the band stops at the vertex; where it ends at a split (a seam vertex, or a neighbouring blend’s own rail), the band is capped in the end section’s own plane, and two such blends meet cap to cap along the shared arc, both caps consumed, in either order. Two convex creases of one request that share a face and turn at a vertex meet there: carried on past it each is a straight band of its end section, and two of one radius cross in the plane bisecting their centre lines, exactly where the reflection in it carries one end section onto the other (a flat shared host, walls whose tangent planes at the vertex reflect into each other). There, across a re-entrant turn both run on to that plane and take the corner cell both hold, where the material holds it (a straight band between planes whose end section reaches past that plane, its wall drafted out under the corner as a moulded pocket’s are, is carried on past it and cut back to it); across a convex turn of a curved crease both stop in it where the bands’ curvature keeps their sections there within a marched band’s fit; either way the two are joined across it into one wedge. Where the end sections differ and both creases are straight (a drafted wall beside a square one), across a re-entrant turn each band is carried on with what stands past its wall and the two are intersected in one boolean, so the corner takes what lies above both bands, bounded by their own intersection, where the material holds it all. Across a convex turn of two straight creases, whatever their sections (an acute, square or obtuse outside corner, its walls square or leaning alike or apart), each band runs out as it does alone and the cuts take their union, as they do where one of the two is a drum’s rim beside a straight crease (a D-shaped block’s arc beside its chord, in either order, its drum upright or drafted) and, past where they meet flush, beside a spline crease (a rib’s wall running into a boss’s rim, the rib’s band run out past the vertex as alone). Across a re-entrant turn one of the two may be a drum’s rim beside a straight crease, the material within the drum (a boss fused onto a block whose wall leans either way, the boss upright or overhanging, a cone narrowing to its foot or standing out at it) or outside it (a bore up to a drafted chord): the rim’s torus band is taken from its cap to where its section stands wholly past the other wall, widened by what stands off the drum before the rim’s cap (beside the chord’s own band too, where a chord through the bore’s axis or past it leans off the drum), intersected so, less what stands off the drum past the other wall, and the rest joined back on. A bore whose chord wall overhangs meets as without a junction, where the band carried on past the rim’s cap would reach under the pocket’s floor, and so does a cone standing out at its foot beside a square wall close to the rim’s cap, where that band crosses the cone at a turn the boolean reads as a touch. A loop of junctions closes, its last junction taken and the ring’s wedge sewn to itself (a square pocket’s rim meets at all four corners, and a disc’s pocket up to a chord at both). Crossed junctions stand beside flush ones in a loop or chain of the request’s edges, the flush ones taken first; a band crossed at both ends is cut in two halfway along, each half joined into its own corner and the two joined again where both hold the band’s one section. Inside corners off square in plan and corners whose walls both lean are taken, the open corner past both walls’ planes taken out of the two widenings, and so is every corner of a pocket off square in plan whose walls each lean their own way. Elsewhere (a crossed junction that would leave two joined bands meeting without one, a turn folding back past the solid, a wider convex turn of two curved bands, or of a curved band other than a drum’s rim beside one of another kind) the later seat runs on through the earlier band or the caps stand, as without a junction, and so wherever the meeting does not close. An L-bracket’s end rim blends over its re-entrant band as one tangent chain (line, the band’s own end arc, line), the ball rolling on the concave cylinder between the lines, to the closed form. A fill and a wedge asked together stop at each other, since only blends that round the same way run on through one another’s bands; whichever is asked first takes the corner, and both orders are exact. Cone, sphere and torus hosts march and blend: the chart inverted in closed form, a band’s connector fitted where no closed form lifts the chart segment, a looping seat with a corner at its join steered by a smooth refit of itself, the band’s first station re-solved on the apex column so a rim opened at the host’s own seam leaves no sliver; a full circular rim whose hosts are not a cap and its coaxial wall takes the march too. A fitted host marches: its chart inverts by projection, a closed patch’s seam wraps as a period does, the patch is continued past the face by a few radii so the ball can run out, a seat the boolean split into arcs is closed back through the neighbours the hosts share and marched as the loop it is, the rail loop slid into the host’s own window, and the wedge melts: a straight edge of a converted box, the crease round a converted post split at its seam, a converted cone’s rim, a converted drum’s rim. Under it a marched section the centripetal fit misses is fitted again by chord length, and a loop cut at a closed patch’s seam is closed on it; a melt the boolean still cannot resolve refuses by name. A rim beside a sphere’s pole whose rail passes over the pole blends with the sphere’s leg running from the rail to the pole, the rim cut from it. A bore whose wall toward the pole is thinner than the ball blends the same way. A rail passing the pole closer than one stride of the march, where its chart turns half round faster than the stations follow, is taken on the same sphere charted with its pole on another axis of its frame and its seam through the widest gap between the seat and the faces’ edges: the leg is built there and the solid’s faces on the sphere are rebuilt on that chart, their old seam and pole edges dropped. Where those faces are not the sphere less some disks (or one disk less others) on the turned chart, the rail is refused by name. A seat whose hosts turn tangent at an end of the crease (the seam of two equal drums crossing, at the points where they touch) pinches there: the stations are solved one by one toward the pole, the pole’s collapsed section closes the band, and the wedge has no cap at that end. A tangency inside a crease with none at its ends is refused by name. A straight crease whose faces are planes or drums parallel to it (a wall meeting a drum along a ruling) blends exactly: the section swept straight, its band a drum of the fillet’s radius. A vertex where a curved face meets rounds with the one ball touching its three surfaces, its centre walked to a radius in from each and its compartment bounded by the planes through the centre and each pair of touch points, so each band’s end section is the ball’s own;
- fillet.setback-corners: Setback vertex corners: bands stopped a chosen distance along their edges, one patch over the opening ·
partial- restriction: At a convex planar vertex one ball touches every face of (a box corner, a square pyramid’s apex), each band of the vertex’s edges stops at its own setback, cut square to its edge; on each face the trim runs from one band’s rail end to the next on a conic tangent to both rails, and one patch fills the opening of arcs and conics, fitted G1 to every band end and face within the bands’ fit target. A setback equal to the ball’s own is the ball corner. On a solid bounded by planes, fillet_edges and fillet_edges_with close a corner of three rounds no one ball touches (three radii, or convex and concave meeting, as at an L-bracket’s front edges at its re-entrant edge) through the blend engine, the same solid in any order: each strip ends through its faces’ setback corners, skew on its cylinder where they stand apart, and one transfinite patch closes the corner tangent to the three strips’ ends within the bands’ fit target, proved inside the corner’s material, concave rounds’ fill included. Off the engine’s class, fillet_edges_with closes a corner of three bands of different radii the same way as round_vertex_setback, each band stopped its own width past the least setback at which its section clears the others, and short of where any other band reaches its section plane. A patch fitted freely that strays past a plane of the vertex is held inside it where it strayed and fitted again, so the box corner at 1, 3 and 2 closes within the block; where the patch misses the bands’ fit target, or no held net keeps it within the planes, it is refused by name. Where a convex and a concave band of one request meet at a vertex of three planar faces and three straight edges, all rounded (an L-bracket’s front edges at its re-entrant edge), fillet_edges closes it the same way whatever the request’s order, the same solid to the bit: each band its own width past the least setback, and on a face reflex at the vertex the face kept on the side of its conic away from it; coplanar faces a fusion left there count as one. With a convex edge of such a vertex left sharp (the L-bracket’s upright front edge), the opening runs into a point Q on that edge as round_vertex_setback_sharp’s does, the trim on a face reflex at the vertex a cubic swinging round it; both orders give the same solid to the bit. Refused by name there: a concave edge left sharp, a band whose setback reaches its edge’s far end, a cell (twice the widest band behind the vertex) reaching through the solid’s far side, and a patch that no net holds within what the solid and its concave rounds hold near the vertex (out of each concave round’s ball) within the bands’ fit target. round_vertex_setback_sharp closes a box-like corner with two of its three edges rounded and the third left sharp: the sharp edge is cut at its own setback, at a point Q, and the opening runs into Q, where a three-sided patch with its fourth side shrunk to Q, held tangent to both of the sharp edge’s faces exactly up to the held trims’ projection, turns its normal from one face’s to the other’s; one patch G1 within the bands’ fit target closes the rest. blend_edges closes a convex corner of one round, one bevel and one edge left sharp so, in any request order, the sharp edge cut as far out as the farther band and the rest meeting the bevel’s end and the trims from it in position only. Sharp edges at vertices of more than three edges are still refused by name. Refused by name: a corner whose cell holds anything of the solid but the corner (a void, a hole, another feature: a face neither meeting the vertex nor on one of its planes, taken whole or cut through by the cell, or a shell dropped), G2 (a circular band meets its face at G1, so its end arc bends the patch at 1/radius where the face asks none; curvature-continuous band sections come first), a vertex a curved face meets, planes sharing no tangent ball, a curved edge leaving the vertex, and a setback inside the ball’s own or past the edge’s end. fillet_edges_cornered asks setbacks through fillet_edges’ corners: the edges rounded once, each named vertex closed the same way, refused by name where an edge at a named vertex is not rounded or two setbacks along one edge meet. Owed: the patch as a network of quads from the sides’ midpoints to a centre, held tangent exactly rather than to the fit.
- fillet.variable: Variable-radius fillets under a radius law: linear or by points along the chain ·
partial- restriction: The radius follows a law by arc length along the chain (linear, or radii at fractions interpolated C1 without overshoot), the section the arc of the law’s radius in the plane square to the edge. Rounded: a straight edge between two planes under any law, its envelope exact (a cubic B-spline along the edge by the rational quadratic arc across it under a law of points); a closed loop of curved edges between the same two faces, one closed edge or the arcs it is split into end to end (a drum’s rim, a cut drum’s oblique rim), marched with the law’s radius station by station and the band fitted through each station’s arc; an open curved edge whose ball runs out past its ends (a half drum’s rim), the law laid along the edge and held at its end radius past each end, where the ball runs out at that radius, or, where the crease ends on a planar face whose plane holds the ball’s section there (a cut square to it), stops at the vertex capped in that face’s plane; an open chain of arcs of one curve between the same two faces, end to end (a crease split at a vertex), rounded as the one seat over the window its arcs cover; a straight edge along a drum’s ruling (a flat or a wall meeting a drum, or two drums), marched with the law’s radius; and a seat pinched where its hosts turn tangent at an end (two equal drums crossing), each station solved at the law’s radius for its place and the pole’s at the law’s radius there; and a chain of edges of different seats continuing each other end to end, open or closed (the top loop of a rounded-rectangle pad: sides between two planes, corners’ arcs between the top and a drum), each edge under the law over its own stretch of the run, its band stopped in its end sections, and the bands’ wedges joined across each junction’s plane, where one ball touches the same two points from either side, sewn within the marched band’s fit, and applied in one boolean; and a chain of straight edges between planes turning where they meet on one shared planar host (a block’s top loop, an L-block’s), each band carried past a turning vertex at the law’s radius there, as the law holds level past an edge’s end: the constant band of that radius, running out across a convex turn as that band alone does, the cuts taking the union, and carried to the plane bisecting a re-entrant turn, where the two are joined; across a re-entrant turn whose bands’ sections differ (a drafted wall beside a square one), each band follows the law to the vertex and is carried on level past it with what stands past its wall, and the corner takes what lies above both, as the constant corner does; across a convex turn whose bands’ sections differ (a drafted wall’s foot leaning out under the far face), a band that runs out past the vertex is built on past it as one surface, the law held level past the edge’s end as far as the constant band runs out, where that band ends square to its crease; a loop the tangent junctions join round to one convex turn (a pad with one vertical left sharp) is opened at a straight band between two of them, built in two halves meeting flush, so no wedge overlaps itself at the turn; and a chain turning where marched bands meet (a drum’s ruling turning into its rim), met in rounds as
fillet_edgesmeets it, each piece an earlier round leaves under its own window of the law and run on through the earlier band at the law’s radius there. A straight edge between two planes, alone or in a chain joined across tangent junctions or turning on a shared plane (outside the rounds a curved crease’s turn is met in), whose ball’s contact runs off a face beside it (a strip left beside a groove) is read station by station at its law’s radius as the constant round reads its own: a contact on any face of the solid holds, one in the open is cut off there, the band running on over it, and one in the material refuses; in a chain the cut-off band’s contacts are carried past the face’s side and checked against the other bands at every station. A curved edge alone, and a closed loop of arcs of one curve marched as one seat (a drum’s rim beside a ring grooved into its cap), are read so too, the law laid by arc length as the march lays it; and a straight edge alone beside a narrow strip the ball takes off whole at every radius of the law, as the constant round takes it off (its ball off the edge’s faces), the exact envelope over the seat between the faces either side of the strip, while a law whose ball sits on the strip throughout rounds the edge beside it. Refused by name: a law whose ball sits on a narrow strip at some radii and takes it off whole at others, where the constant round changes construction; a linear law changing radius round a closed chain (it jumps where the chain closes), a turn where a marched band meets a straight band between planes (under a law the marched band is fitted, the constant round’s there an exact torus band, and its end meets the straight band in what the boolean does not melt, whichever is built first and however it ends), a loop joined round a convex turn with no straight band between two junctions to open it at, a ball whose contact at any radius of the law runs off a face beside the edge into the material (a step raised beside a strip), or off a face beside an open chain of arcs of one curve or an edge of a chain met in rounds, two bands overlapping across a face they share, any other straight edge whose faces are not both planes (a drum’s ruling inside a chain among them), a law over one arc of a crease that closes through other edges (the whole loop is given instead), and a ball at the law’s largest radius that does not fit the faces.
- restriction: The radius follows a law by arc length along the chain (linear, or radii at fractions interpolated C1 without overshoot), the section the arc of the law’s radius in the plane square to the edge. Rounded: a straight edge between two planes under any law, its envelope exact (a cubic B-spline along the edge by the rational quadratic arc across it under a law of points); a closed loop of curved edges between the same two faces, one closed edge or the arcs it is split into end to end (a drum’s rim, a cut drum’s oblique rim), marched with the law’s radius station by station and the band fitted through each station’s arc; an open curved edge whose ball runs out past its ends (a half drum’s rim), the law laid along the edge and held at its end radius past each end, where the ball runs out at that radius, or, where the crease ends on a planar face whose plane holds the ball’s section there (a cut square to it), stops at the vertex capped in that face’s plane; an open chain of arcs of one curve between the same two faces, end to end (a crease split at a vertex), rounded as the one seat over the window its arcs cover; a straight edge along a drum’s ruling (a flat or a wall meeting a drum, or two drums), marched with the law’s radius; and a seat pinched where its hosts turn tangent at an end (two equal drums crossing), each station solved at the law’s radius for its place and the pole’s at the law’s radius there; and a chain of edges of different seats continuing each other end to end, open or closed (the top loop of a rounded-rectangle pad: sides between two planes, corners’ arcs between the top and a drum), each edge under the law over its own stretch of the run, its band stopped in its end sections, and the bands’ wedges joined across each junction’s plane, where one ball touches the same two points from either side, sewn within the marched band’s fit, and applied in one boolean; and a chain of straight edges between planes turning where they meet on one shared planar host (a block’s top loop, an L-block’s), each band carried past a turning vertex at the law’s radius there, as the law holds level past an edge’s end: the constant band of that radius, running out across a convex turn as that band alone does, the cuts taking the union, and carried to the plane bisecting a re-entrant turn, where the two are joined; across a re-entrant turn whose bands’ sections differ (a drafted wall beside a square one), each band follows the law to the vertex and is carried on level past it with what stands past its wall, and the corner takes what lies above both, as the constant corner does; across a convex turn whose bands’ sections differ (a drafted wall’s foot leaning out under the far face), a band that runs out past the vertex is built on past it as one surface, the law held level past the edge’s end as far as the constant band runs out, where that band ends square to its crease; a loop the tangent junctions join round to one convex turn (a pad with one vertical left sharp) is opened at a straight band between two of them, built in two halves meeting flush, so no wedge overlaps itself at the turn; and a chain turning where marched bands meet (a drum’s ruling turning into its rim), met in rounds as
ogeom-geom
- geom.adaptors: Evaluating topology as geometry: an edge’s curve and a face’s surface, placed ·
covered - geom.conversion: Converting between curve and surface forms: to B-spline, to Bézier segments, degree elevation ·
covered - geom.curves-2d: Parametric plane curves, the pcurve vocabulary ·
covered - geom.curves-3d: Parametric space curves: lines, conics, Bézier, B-spline, trimmed, offset ·
covered - geom.extension: Extending curves, surfaces and faces beyond their domains ·
covered - geom.local-properties: Local properties along curves and across surfaces: tangent, normal, curvature ·
covered - geom.surfaces: Parametric surfaces: planes, quadrics, swept, Bézier, B-spline, trimmed, offset ·
covered
ogeom-heal
- heal.canonical-simplification: Recognizing that exact geometry is secretly analytic: a B-spline that is a cylinder ·
covered - heal.custom-remodelling: Rebuilding a shape’s geometry wholesale: baking transforms, converting representations ·
covered - heal.fix-shape: Fixing broken shapes: wires, faces, shells, solids, free bounds, small features ·
partial- restriction:
fix_shapeis the standalone entry point: diagnose, put a wire’s edges end to end where an order exists, collapse edges shorter than their own vertices’ tolerances, make one of two vertices a wire’s edges meet at within the trim cap, fill a wider gap with a straight edge where it is provably the one lacking (shorter than every edge of each face it is in, no other vertex as near its ends, on each face’s surface within the face’s tolerance, each face checking clean after; any other gap stays reported), give a boundary crossing a pole its degenerate edge, fit missing pcurves, sew loose faces, strip a wire’s tails (an edge out and one straight back along the same curve within their tolerances, on one copy of the chart, held by no other face; a hole of tails alone goes) and cut off its notches (an edge run back along part of the one before it, the longer cut where the shorter turns; check names two-edge tails and notches, not a slit of one edge), remove a hole held by no other face whose every point is within the face’s tolerance of its boundary, on a plane, cylinder, cone, sphere, torus, B-spline patch, or revolution or extrusion of a B-spline (proved cell by cell on the chart drawn to a bound on the surface’s first derivatives over the hole’s box, closed form on the elementary surfaces and from the control net on a spline; the hole’s area within the tolerance times its length is necessary, not sufficient; any other surface keeps its holes), judging and healing each placed copy of a face on its own where it stands (a copy whose tail or hole another face holds at its placement keeps it, a scaled copy keeps its holes), mend a boundary crossing itself once in its chart (two consecutive edges crossing short of their vertex within the trim cap are cut back to the crossing and the vertex moved there; any other crossing of a one-wire face held by a container splits it into its two loops; each face changed checking clean after, anything else counted), bound a wireless face by its domain’s sides (seam and pole edges) on a sphere, torus or patch, or mark it natural where those sides have no closed form (a wireless face on a plane, cylinder or extrusion stays reported: its domain is a working window, not an extent), state the gap between two free edges of a shell’s or a compound’s loose faces that are each other’s only twin within the trim cap (the gap shorter than every edge of either face, no other vertex as near their ends) where sewing a copy shows it joins them, chain free edges into contours and wires (free_bounds), tighten tolerances, widen an edge to how far its pcurves stand off its curve and a vertex to how far its curves end from it (within the trim cap, as the readers record the same slop), orient each run of a shell’s or solid’s faces along their shared edges, turn a solid’s run facing into its material as a whole, restore tolerance containment by widening what is bounded (restore_containment, which the STEP and IGES readers run on every body they build), diagnose again, and report what it did and what remains. Beneath it: reanchoring periodic rings, sewing, validity diagnosis, the reader’s inline heal sequence, and the instructed fixes: fix_face_pcurves fits the trims the reader refused at a caller’s cap, and re-charts a sphere face whose trim runs within 0.05 rad of a pole of its chart without running through it (rechart_clear_of_poles, which the STEP and IGES readers run on every face they read): the same sphere charted about the candidate axis whose poles stand furthest from the trim, the face restated in place, a face no chart clears refused by name; and reanchor_boundaries moves a boundary onto the surface it bounds with the displacement recorded in widened tolerances, both measured on community assemblies with boundaries millimetres off. fix_small_faces collapses a spot face to a point and a strip face to one long side, rebuilding its neighbours so the shell stays closed; remove_small_solids drops debris solids under a volume; solids_from_shells makes solids of closed shells, outer boundaries told from voids by which shell holds which vertex. self_union (in ogeom-bool, opt-in) resolves a solid whose boundary passes through itself into the union of the material it encloses: faces split where they cross, pieces kept where the winding number is zero beyond and positive behind; faces crossing one they share an edge or corner with, or folding through themselves, are refused. A STEP grid of patches reads as one spline surface. Healing here is measured by the imported corpus rather than claimed in general.
- restriction:
- heal.same-parameter: Diagnosing and repairing the same-parameter law between a curve and its pcurves ·
covered - heal.scripted-pipeline: Resource-file-driven sequences of healing operators ·
divergent- reasoning: The job (run a heal sequence on import) exists and is done with a fixed inline sequence in the exchange readers, measured by the corpus. A pipeline scripted from resource files is configuration the applications that need it can build from the same functions; the kernel keeping a config-file interpreter would be an application affordance.
- heal.status-reporting: Statuses, messages and traversal support for the healing pipeline ·
divergent- reasoning: Healing outcomes here are values:
SameParameterReport,StepReport, counts fromreduce_tolerances. These are Rust Results rather than status bitfields read back through a registrator. A message-registration framework would add a second channel for what the return values already say.
- reasoning: Healing outcomes here are values:
- heal.substitution: Recording shape substitutions and applying them across a model ·
covered - heal.tolerances: Reading and tightening the tolerances a shape actually needs ·
covered - heal.upgrade: Upgrading shapes in place: same-domain unification, edge merging, subdivision, splitting along curves ·
covered
ogeom-hlr
- hlr.projection: Projecting a model into a view with visibility classified: hidden line removal, exact and polygonal ·
covered
ogeom-intersect
- intersect.analytic-sections: Closed-form intersections of planes and quadrics ·
covered - intersect.curve-curve: Intersecting two curves, in the plane and in space: crossings, tangencies, overlaps ·
covered - intersect.curve-surface: Intersecting a curve with a surface: piercings and lying segments, with transitions ·
covered - intersect.extrema: Extrema between two curves, a curve and a surface, or two surfaces ·
covered - intersect.surface-surface-march: The general surface/surface intersection: seeding, marching, branch assembly, approximation ·
covered
ogeom-io
- io.3mf-colours-and-roles: 3MF colours from colour groups, base materials and textures, and the roles of objects and volumes ·
partial- restriction: Colour groups, base materials and texture groups read at
each triangle’s corners, textures (PNG or JPEG parts) sampled by the
nearest texel and tiled as the texture says,
noneread asclamp. Composite materials and multi-properties are passed over with a warning. Roles (part, modifier, support blocker, support enforcer, negative volume, or another as written) are read by name from objects’ metadata and from the configuration parts underMetadata/, by triangle range or by component; a role kept in any other form is not seen, and the volume reads as a part. A volume’s triangles stay in its object’s mesh, the volumes saying which they are.
- restriction: Colour groups, base materials and texture groups read at
each triangle’s corners, textures (PNG or JPEG parts) sampled by the
nearest texel and tiled as the texture says,
- io.dxf: DXF drawings, both directions: lines, arcs, circles, ellipses, bulged polylines, splines and text, with units and layers ·
covered - io.iges: IGES, both directions: solids as manifold B-rep, sheets as trimmed surfaces, surface files sewn, units converted ·
partial- restriction: The core entity set reads and writes, and eight round-trip cases hold it to measured volumes: planes, a periodic
cylinder wall with its seam, a doubly periodic torus, a seam-only sphere, a
boolean result, a spline-walled prism through its tabulated cylinders
(122 over 126), a revolved spline profile through its surface of
revolution (120), and inch-unit scaling. The writer writes surfaces of
revolution, extrusions and offsets in their own spellings (120, 122, 140;
an offset as its analytic form where it has one, over a plane or spline
basis otherwise, any other offset as a spline patch), and the reader
reads each back as the same surface; a solid’s whole name goes out and
comes back as its Name property (406 form 15). The reader also takes every axis-aligned conic arc (104: ellipse,
hyperbola, parabola, each as its own curve), ruled surfaces (118) as the
degree-one patch between the two curves’ exact spline forms, and constant
offset curves and offset surfaces (130, 140). A conic whose axes turn is
read in the frame where its cross term vanishes and turned back, exactly;
parametric spline surfaces (114) convert exactly to bicubic B-splines; an
offset curve whose distance varies (linearly with arc length, or as a
function curve’s coordinate) is fitted same-parameter with its base; and a
trim given only in a B-spline surface’s parameters is lifted through the
surface. Model-space annotation (notes, leaders, labels, symbols and the
linear, radial, diametral, angular and ordinate dimensions) reads as PMI
callouts of the lines the file draws, a dimension whose text states a
number carrying that value as a semantic dimension. Subfigure instances
place their definition (308, 320) or base, built once and shared under each
placement: a singular instance (408) or a network instance (420) by its
scale, matrix and translation in the specification’s order, a rectangular
(412) or circular (414) array at each position its DO or DON’T list keeps,
a group (402) as its base placing every member; a network instance
flagged logical (a schematic’s), or scaling its axes unlike, is refused
by name, and connect points are not read; a definition’s solids,
trimmed and untrimmed surfaces, constructive solids and curves are placed
with it; levels (with 406 level lists)
and groups (402) read as the document’s layers. Constructive solids read
as the solids they describe: the block, wedge, cylinder, cone frustum,
sphere, torus and ellipsoid primitives (150 to 168), solids of revolution
and linear extrusion, boolean trees (180) through the kernel’s own
booleans, solid assemblies (184) and solid instances (430). A trim given only
in the parameters of a plane, cylinder, cone, sphere or torus is lifted
through the format’s parameterization, each surface framed by the reference
direction it names; on a tabulated cylinder (fractions of its directrix and
generator) and a surface of revolution whose generatrix is a line, a
spline, a circular arc or an ellipse with its axes along its plane’s (the
generatrix’s own parameter, an arc’s or ellipse’s its angle from the
format’s start angle, and the angle turned). Independent
untrimmed surfaces (parametric spline, ruled, revolved, tabulated,
B-spline, and offsets of those) read as faces bounded by their own extent
and sew with the rest; the writer writes a face with no boundary as the
trimmed surface whose outer boundary is its surface’s. Copious data drawn
as a path (106, forms 11 to 13 and 63) reads as the exact polyline through
its points, a trim taking each segment as an edge; a point set (forms 1
to 3) is no curve and is refused by name. Refused by name: a trim on a
surface of revolution of a hyperbola, parabola, turned ellipse,
composite, parametric-spline or offset generatrix, whose own IGES parameter this reader does not carry; a
drawing’s own annotation stays on the sheet and is counted as skipped.
Independent curves that are geometry
(100, 102, 104, 106, 110, 112, 126, 130) read as wires, a composite
curve’s matrix carrying its pieces and its points passed over, and the
writer writes a free wire or edge as an independent curve. Stand-alone
points (116) that are geometry read as vertices, and the writer writes a
free vertex as an independent point. A curve on a surface (142) builds its
trim from its model-space curve unless its preference flag prefers the
parameter-space curve, which is then lifted, the model-space curve
standing in where that lift fails. A parent’s matrix carries the children
it owns physically, as IGES 5.3, section 3.2.3, table 4 lists them: a
trimmed surface’s surface and boundaries, a subfigure definition’s members,
a drafting entity’s leaders and witness lines, a composite curve’s pieces,
and the curves and surfaces the swept, ruled and offset entities are built
from; a child owned only logically stands where its own matrix puts it. An
independent bounded plane (108, form 1) reads as the face its curve
encloses. External references (416) to a definition or a whole file
resolve, through
read_iges_file, against the files in the deck’s own directory, each file identified by the name its global section gives it and a definition by the symbol its index (402, form 12) lists; a missing file, a misnamed file, an unlisted symbol, a logical reference and a reference to a natively kept definition are refused by name, as is every reference in a deck read from text. A document’s construction geometry is written as independent entities flagged as construction (use flag 06): a point (116), a curve as an edge’s is (a hyperbola or parabola as a conic arc, 104), a surface as a trimmed surface (144) over the exact B-spline patch (128) of its window, an axis as a transformation matrix (124), each named by its label and a Name property; an item with no exact B-spline form is left out and named inwrite_iges_reported’s warnings. The reader reads such entities back into the document’s construction, a composite of several pieces and a surface trimmed by a boundary of its own excepted; a line reads back over its own length from its start, and a surface other than a plane or a B-spline over its patch’s parameters.
- restriction: The core entity set reads and writes, and eight round-trip cases hold it to measured volumes: planes, a periodic
cylinder wall with its seam, a doubly periodic torus, a seam-only sphere, a
boolean result, a spline-walled prism through its tabulated cylinders
(122 over 126), a revolved spline profile through its surface of
revolution (120), and inch-unit scaling. The writer writes surfaces of
revolution, extrusions and offsets in their own spellings (120, 122, 140;
an offset as its analytic form where it has one, over a plane or spline
basis otherwise, any other offset as a spline patch), and the reader
reads each back as the same surface; a solid’s whole name goes out and
comes back as its Name property (406 form 15). The reader also takes every axis-aligned conic arc (104: ellipse,
hyperbola, parabola, each as its own curve), ruled surfaces (118) as the
degree-one patch between the two curves’ exact spline forms, and constant
offset curves and offset surfaces (130, 140). A conic whose axes turn is
read in the frame where its cross term vanishes and turned back, exactly;
parametric spline surfaces (114) convert exactly to bicubic B-splines; an
offset curve whose distance varies (linearly with arc length, or as a
function curve’s coordinate) is fitted same-parameter with its base; and a
trim given only in a B-spline surface’s parameters is lifted through the
surface. Model-space annotation (notes, leaders, labels, symbols and the
linear, radial, diametral, angular and ordinate dimensions) reads as PMI
callouts of the lines the file draws, a dimension whose text states a
number carrying that value as a semantic dimension. Subfigure instances
place their definition (308, 320) or base, built once and shared under each
placement: a singular instance (408) or a network instance (420) by its
scale, matrix and translation in the specification’s order, a rectangular
(412) or circular (414) array at each position its DO or DON’T list keeps,
a group (402) as its base placing every member; a network instance
flagged logical (a schematic’s), or scaling its axes unlike, is refused
by name, and connect points are not read; a definition’s solids,
trimmed and untrimmed surfaces, constructive solids and curves are placed
with it; levels (with 406 level lists)
and groups (402) read as the document’s layers. Constructive solids read
as the solids they describe: the block, wedge, cylinder, cone frustum,
sphere, torus and ellipsoid primitives (150 to 168), solids of revolution
and linear extrusion, boolean trees (180) through the kernel’s own
booleans, solid assemblies (184) and solid instances (430). A trim given only
in the parameters of a plane, cylinder, cone, sphere or torus is lifted
through the format’s parameterization, each surface framed by the reference
direction it names; on a tabulated cylinder (fractions of its directrix and
generator) and a surface of revolution whose generatrix is a line, a
spline, a circular arc or an ellipse with its axes along its plane’s (the
generatrix’s own parameter, an arc’s or ellipse’s its angle from the
format’s start angle, and the angle turned). Independent
untrimmed surfaces (parametric spline, ruled, revolved, tabulated,
B-spline, and offsets of those) read as faces bounded by their own extent
and sew with the rest; the writer writes a face with no boundary as the
trimmed surface whose outer boundary is its surface’s. Copious data drawn
as a path (106, forms 11 to 13 and 63) reads as the exact polyline through
its points, a trim taking each segment as an edge; a point set (forms 1
to 3) is no curve and is refused by name. Refused by name: a trim on a
surface of revolution of a hyperbola, parabola, turned ellipse,
composite, parametric-spline or offset generatrix, whose own IGES parameter this reader does not carry; a
drawing’s own annotation stays on the sheet and is counted as skipped.
Independent curves that are geometry
(100, 102, 104, 106, 110, 112, 126, 130) read as wires, a composite
curve’s matrix carrying its pieces and its points passed over, and the
writer writes a free wire or edge as an independent curve. Stand-alone
points (116) that are geometry read as vertices, and the writer writes a
free vertex as an independent point. A curve on a surface (142) builds its
trim from its model-space curve unless its preference flag prefers the
parameter-space curve, which is then lifted, the model-space curve
standing in where that lift fails. A parent’s matrix carries the children
it owns physically, as IGES 5.3, section 3.2.3, table 4 lists them: a
trimmed surface’s surface and boundaries, a subfigure definition’s members,
a drafting entity’s leaders and witness lines, a composite curve’s pieces,
and the curves and surfaces the swept, ruled and offset entities are built
from; a child owned only logically stands where its own matrix puts it. An
independent bounded plane (108, form 1) reads as the face its curve
encloses. External references (416) to a definition or a whole file
resolve, through
- io.mesh-formats: The mesh exchange formats: STL, OBJ, PLY, glTF/GLB, 3MF, with welding on import ·
covered - io.native-format: The native shape interchange format, versioned, with location and triangulation sets ·
covered - io.obj-materials: OBJ colours: material libraries’ diffuse colours, PNG and JPEG textures sampled at each corner, per-vertex colours ·
covered - io.ply: PLY in text and both binary byte orders: meshes and point clouds, with normals, colours and named scalar properties ·
covered - io.step: STEP, both directions: solids and sheets, construction geometry, assemblies, colours, validation properties, semantic PMI (AP242 datum systems included) and presentation PMI ·
covered - io.text-clouds: XYZ and PTS text point clouds, columns named or read by their count ·
covered - io.vrml: VRML scenes ·
covered
ogeom-math
- math.analytic-carriers: The analytic curve and surface carriers: lines, conics, quadrics ·
covered - math.bspline-basis: The B-spline basis: knots, evaluation, derivatives, insertion, elevation, splitting ·
covered - math.curve-constructors: Constructing curves from constraints: through points, from centre and radius, trimmed arcs ·
covered - math.elementary-evaluation: Evaluating and parameterizing the elementary curves and surfaces in closed form ·
covered - math.equation-solving: Roots and minima of functions and systems: Newton, Brent, bisection, polynomial roots, global minima ·
covered - math.linear-algebra: Dense and sparse linear systems, least squares, eigenvalues ·
divergent- reasoning: Dense linear algebra is nalgebra’s, on purpose: the workspace is generic over RealField so extended-precision scalars can be swapped in (docs/DATA_MODEL.md), and reimplementing SVD/LU under that constraint buys nothing but bugs. What is ours is the sparse matrix and conjugate-gradient path the fitting pipeline uses (ogeom_math::SparseMatrix, ogeom_math::least_squares_cgnr).
- math.primitives: Points, vectors, directions and frames, in the plane and in space ·
covered - math.quadrature: Numerical integration ·
covered - math.tangency-constructions: 2D tangency constructions: circles and lines tangent to points, lines and circles ·
covered - math.transforms: Rigid and general transforms, quaternions, and their interpolation ·
covered
ogeom-mesh
- mesh.boolean: Booleans between closed meshes, and between a mesh and a meshed solid, decided exactly, coplanar faces included, new vertices rounded and flagged ·
covered - mesh.editing: Editing triangulations: half-edges with stable numbers and source vertices, components, rims, face removal and flips, non-manifold input split and reported, welding, decimation verified both ways, in place with source vertex sets carried through collapses and markings of the source mapped to regions ·
covered - mesh.hatching: Hatching faces: iso and free-direction line families clipped to the trim ·
covered - mesh.hole-filling: Filling a rim of an editable mesh: triangulated with the least largest bend then the least area, exactly up to 500 edges and ear-clipped on its plane beyond, refined to the mesh’s spacing, faired curved or flat, closed and manifold about the patch, touching holes parted at their shared point ·
covered - mesh.hollow-thicken: Hollowing a closed mesh to a wall from its exact distance field, the cavity marched on tetrahedra, decimated within a sixteenth of a cell and checked exactly strictly inside; thickening an open mesh by two offset layers walled along each rim ·
covered - mesh.offset: Offsetting an editable mesh by a distance with sharp edges and corners kept by least squares over corner-angle-weighted face normals, moves capped, folds repaired by pushing overshooting vertices onto the offset planes and collapsing folded faces while staying a manifold, and a region stepped behind a closed wall ·
covered - mesh.plane-cut: Cutting an editable mesh by a plane with the section’s exact side test, and capping the cut flat and watertight: discs, rings and nested caps, rim gaps bridged within a distance, the rest reported open ·
covered - mesh.section: Sections of a triangle mesh by a plane or a stack of planes: polylines welded through shared edges, carrying the material side, with exact side tests and non-manifold edges reported ·
covered - mesh.shadow: The shadow of a triangle mesh on a plane, holes kept, and the regions of weighted planar chains, traced with exact integer tests and snap rounding ·
covered - mesh.smoothing-curvature: Feature-preserving smoothing of an editable mesh onto local least-squares quadrics, creases kept by normal angle, moves capped and blended into a region; mean, Gaussian and principal curvatures with their directions at each vertex of a mesh ·
covered - mesh.spatial-index: Spatial indexes over meshes and point clouds: nearest point, ball and ray queries, batched in parallel, equal to brute force ·
covered - mesh.tessellation: Triangulating shapes to a stated deflection by chord and angle alone, with no length cap, deterministically, in parallel ·
covered
ogeom-offset
- offset.draft: Drafting faces about a neutral plane for mould release ·
partial- restriction: Planar faces turn about their neutral line, walls of revolution (cylinders and cones) turn about their neutral circle into exact cones, and extruded walls (a spline profile swept straight) turn ruling by ruling about their neutral crossing’s own tangent and re-fit, with the drafted angle held along the height. Every other face (a raw fitted patch, a wall of revolution about an oblique neutral) is drafted the way a mould-maker drafts: the ruled surface through the face’s crossing with the neutral plane, rulings the pull turned by the angle, read off the face’s own mesh and corrected onto the surface. A face the neutral plane does not reach turns about where its own surface, run on beyond the face, meets the plane: the planes’ line, the neutral circle, the ruling’s closed-form height, a drum’s or cone’s rulings run on to the plane, or a patch’s crossing within its own extent; a patch that stops short of the plane has no extension to carry the hinge and is refused by name. A draft whose turned rulings cross inside the drafted window (a profile curled tighter than the draft’s reach) is refused by name, as is a face the neutral plane crosses in two runs: a region beside both would turn about two hinges, and the face would split where the two turned supports meet, which a draft (the same faces on turned supports) does not do.
- offset.filling: Filling a boundary with a face: the plate surface ·
covered - offset.form-features: The form features: prism, revolution, rib and slot against a base ·
covered - offset.loft: Lofting through sections, ruled or smoothed ·
partial- restriction: The ruled loft takes two closed wire sections (coaxial parallel circles, or polygons of the same corner count whose ruled walls come out planar) or a section and a point: a cone on a circle’s own axis, an exact pyramid over any straight loop. The skinned loft takes N closed sections, planar or not (a planar end is capped by its plane, a wavy end by a patch skinned from the rim to a point inside it), ends at a point on request, loops back on itself, and takes per-section alignment hints. A ruled wall between two segments that are not coplanar is the bilinear patch through its four corners, exact, so polygon sections may be turned against each other.
make_loft_solidcloses the loft sheet into a solid through N closed sections with planar ends, ruled or smooth: sections of different edge counts, lines, arcs and splines mixed, are split to match by arc length, every piece exact, so a ruled square to circle is exact; with guide curves the walls are the guided Gordon sheet, capped by the end sections’ planes, holding every section exactly and every guide to ten confusions. Guided sections of several edges share one edge count and a guide runs through every corner where two edges meet; each edge’s wall is the Gordon patch between the guides through its two corners. The skinned loft holds its sections exactly where they are exact: through two sections it is the ruled loft; through more with matching corners, one strip per edge meeting along seams through the corners, a plane wherever its rows share one; through coaxial circles, the revolution of a meridian through their radii.
- restriction: The ruled loft takes two closed wire sections (coaxial parallel circles, or polygons of the same corner count whose ruled walls come out planar) or a section and a point: a cone on a circle’s own axis, an exact pyramid over any straight loop. The skinned loft takes N closed sections, planar or not (a planar end is capped by its plane, a wavy end by a patch skinned from the rim to a point inside it), ends at a point on request, loops back on itself, and takes per-section alignment hints. A ruled wall between two segments that are not coplanar is the bilinear patch through its four corners, exact, so polygon sections may be turned against each other.
- offset.middle-path: Extracting the middle path of a pipe-like solid ·
covered - offset.projection: Projecting wires normally onto faces, pcurves riding along ·
covered - offset.shell-thicken: Offsetting shapes and sheets, and thickening shells and sheets into solids ·
covered - offset.sweeps: Sweeping profiles along spines: pipes, pipe shells, evolved shapes, the frame laws ·
partial- restriction: Pipes run a circular section along a spine edge, exactly for straight and circular spines and skinned for free-form and helical ones, and along a wire of lines and arcs, open or closed, as the pipe shell of the disc square to its start with its default mitres, wherever that sweeps it exactly: legs meeting tangent are the cylinders and torus segments they sweep, a closed one a ring with no cap, straight legs are mitred on the planes halving their corners, a sharp corner of a wire with arcs, between two lines or turning in the plane of the arcs beside it (a line and an arc, or two arcs), is the union of the legs and their straight run-ons outside the turn, each cut on the plane halving the corner in one half ellipse, the legs meeting inside the turn where each rim point’s two paths cross, and a corner that turns an arc out of its plane, or turns in it so tightly that the paths inside it miss, is mitred in pieces. A wire the pipe shell refuses, or sweeps only as a skin (a free-form edge), is refused by name, as is a leg shorter than its corner’s reach; the skinned pipe takes such a wire to a stated tolerance, as the pipe shell of its disc.
place_on_spinebrings a profile drawn elsewhere onto a spine’s start before a sweep, rigidly about its centroid: turned square to the start tangent by the least rotation, moved so its centroid is the start point, or both. A pipe shell may bring its own profile on, asPipeLaws::contactasks:PipeContact::Contactmoves it along its plane’s normal until its plane runs through the start point (the start’s foot on the plane is the contact point, so an open wire is moved as a face is), andPipeContact::Correctionthen turns it about the start by the least rotation standing it square to the start tangent; a profile with no plane, or whose plane holds the tangent, is refused by name. The evolved sweep runs a profile along a planar spine, exactly, by composition. The pipe shell sweeps an arbitrary planar profile, holes and all, along an open spine wire under a rotation-minimizing or Frenet frame law, and it also sweeps a profile round a closed spine, smooth or faceted, holes and all: the loop’s holonomy paid off, a faceted profile skinned as one C1-closed strip per facet, each hole a void tunnel of its own shell. A sharp-cornered ring mitres exactly when its corners turn in the plane: the wrap is one more mitre, the seam must stand on a corner, and the planar Pappus volumes land to the last digit. The Frenet law rides the loop too (single-valued round it, so it owes no reconciliation), with every corner loop one rail shared by the two strips meeting there. A ring seamed mid-leg butts its two half-legs on the seam’s own ring, and a skew-cornered ring closes on its mitres: the frame is reflected across each mitre plane, the loop’s holonomy spread along the legs as a twist. A corner against a curved leg, on an open spine or a ring, ends both walls on the crossing of their generators, exact where the corner turns in the leg’s plane. A skew corner against a curved leg keeps the reflected frame and mitres in pieces: each side is swept on straight past the corner, trimmed by the mitre plane, and the pieces fused, the difference between the two sections standing as a face of the mitre plane; a closed wire profile sweeps there as the walls of the face it bounds, the end caps taken off. Each spine edge skins its own run, so a spine whose curvature steps at a smooth join (an arc into its tangent line) is followed exactly, and a run whose sections share a plane is that plane. A spine of lines and arcs meeting tangent is swept exactly: each line leg an extrusion of the section, each arc leg its revolution about the arc’s axis, the legs fused, so every wall is the plane, drum, cone, ball or torus its profile edge sweeps. Round sharp corners turning in the planes of the arcs beside them, with the default mitres, any planar face, holes and all (square tubing, a rectangle off the spine, a rounded slot), is swept exactly too: each edge’s walls are those closed forms down each leg, outside a turn run on straight and cut on the plane halving the corner, where the two legs’ run-ons are mirror images whatever the section’s shape, and inside it cut on the curve where each point’s two paths cross, a conic held exactly for a straight edge and a curve fitted within the stated tolerance for a curved one. Where the paths inside a turn miss, or an arc bends round an axis the section reaches, the general sweep takes the corner.make_helical_sweepis the screw motion of a profile in a plane through the axis, cylindrical or tapered, either hand: every point runs its own helix and the walls are fitted through those images in quarter turns.make_pipe_shell_evolvingscales and twists the section linearly with the length run along the spine, andmake_pipe_shell_lawsby laws of its own: linear, the monotone cubic through given values, which keeps between neighbouring values (a scale through positive values stays positive), or a function of the share of the length run (SpineFunction), which gives the start value at the start and is read wherever a section is placed or the skin checked, a scale refused where it reads naught or below; a function law is never taken as standing still, so every run it moves along is skinned. Scaled linearly without a twist down one straight edge it is the ruled loft to the scaled image, exact (a pyramid’s or cone’s frustum, the pyramid or cone at a scale of zero); otherwise each loop is skinned through the placed sections and held to the tolerance against the law, along an open spine with no sharp corner, each hole a solid of its own cut from the outer. Round a smooth closed spine the section twists whole turns, unscaled, the skin closed on itself, or part of a turn where that brings each loop of the section home onto itself (a square a quarter turn, a circle about its centre any angle): a faceted loop’s edges sweep strips open at both ends, each closing on the strip that lands where it began, a smooth loop is read from a point sliding round it as it turns; a scale, or a part turn the section’s symmetry does not close, is refused there by name, as the section would not come home to itself; a twist law of any shape closes there where a linear one ending as it does would. Round sharp corners an evolving face sweeps run by run, mitred, extended or rounded: each run under the laws over its share of the length, from the section it starts with, the stretches where the laws stand still swept exactly; a twist still moving through a mitred corner turns the two legs’ sections inside it against each other, so their skins cut the mitre plane along two curves that cross, and the mitre faces are the regions between them (see the glued mitres below); a skinned run into a curved leg’s extended corner, its wall leaving the extension’s side plane along its start edge as the scale narrows it, joins that extension however shallowly it leaves, so long as it rises past ten confusion distances by the first sample in. The multisection pipe takes faces with holes, as many in every section, each hole swept through the sections and cut from the outer solid. Round sharp corners a face is also swept run by run: the spine cut at its corners, each smooth run swept on its own (a straight run keeping its frame as the prism of the section), the runs mitred on the halving plane, extended or rounded, along open or closed spines and against curved legs. The auxiliary and binormal laws turn sharp corners this way too, their runs meeting a mitre with the sections the law sets; a straight run meeting a skinned one at a mitre takes the skin’s own fit of each ring the two share within the tolerance (a hole or a smooth loop), so both cut the mitre plane alike; two skinned runs meeting at a mitre end on their sections exactly where a ring is one edge with an exact rational form (a circle, a conic, a B-spline), the later run starting on the earlier’s end ring mirrored where the earlier is swept first, so both cut the mitre plane in one curve, and a holed section’s ring that is a circle centred on the spine sweeps there as the exact tube every frame carries it along (a cylinder down a line, a torus round an arc), so a holed face mitres between two skinned runs; a section off the spine, solid or holed, mitres there too, the later run run back from its own skin’s start rings. Where the fuse of two mitred runs cannot reconcile their cuts of the mitre plane (two fits of one curve where the frames mirror across it, as round a symmetric lens, or curves that cross at a shallow angle), the trimmed runs are glued on the plane instead: cuts that run within the two runs’ tolerances of each other all along are sewn as one edge stating the gap, the others split where they cross, and the plane keeps a face where only one run’s region covers it, looking out of that run; a solid that still fails the checks is refused by name rather than returned invalid; a round corner is refused where the law’s next section is not the last turned about the corner, within what the runs are held to (a skinned run’s tolerance), so a holed section’s skinned run turns round ones too. The Frenet frame turns sharp corners extended or rounded the same way, each run’s section set by the run’s own Frenet frame, and is refused along a straight run, which has none. Round a smooth closed spine the auxiliary and binormal laws sweep as the rotation-minimizing frame where their frames are its frames turned by a fixed angle; otherwise their own frames, single-valued round the spine, are skinned round it, closed on themselves and held to the tolerance against the law, holes as void tunnels. Along an open spine those laws sweep a face with holes, each hole a solid of its own cut from the outer. Turned run by run, a closed wire sweeps the walls of the face it bounds, the end caps taken off. Round a closed spine of lines and arcs an open wire sweeps a ring of the exact sheet below, from the middle of the first leg round to there, where it closes on itself, each corner (the one where the spine closes too) mitred or rounded as down an open spine, and under a law as below; where the rotation-minimizing frame comes home turned (a spine out of one plane whose turns do not cancel) the ring would not close and is refused by name, as is a closed spine with any other edge. Down an open spine of lines and arcs under the rotation-minimizing frame an open wire sweeps one exact sheet: each line leg the prism of its section and each arc leg its revolution about the arc’s axis, legs meeting tangent sewn on their common section, mitred on the plane halving each corner (an arc leg run on straight by the prism of its end section) or rounded (the section’s part outside the turn turned about the corner). Inside a corner, mitred or rounded, each leg runs on until it meets the other’s wall: on the halving plane between two lines, and where a corner against an arc turns in the arc’s plane, on the conic a straight profile edge’s two walls cross on, held exactly as a rational quadratic, or for a curved profile edge on a curve fitted through each of its points’ exact crossings, its error stated as its tolerance; each profile edge is cut where it crosses the corner’s axis and swept on its own, and both its walls are split on that one curve. The pieces are sewn and held to a disc whose boundary runs only along its end sections and the paths of the wire’s ends; an extended corner, a leg shorter than its corners’ reach (or a point’s two paths meeting past a leg’s end), a round turning too little to stand off its square end, a section reaching inside a corner that turns an arc out of its plane (its paths down the two legs pass each other without meeting) and a sheet that runs into itself are refused by name. The Frenet (rounded only), binormal and auxiliary laws sweep that sheet where their frames are the rotation-minimizing ones turned by a fixed angle, and are refused by name where they set a sharp corner’s next section otherwise. Down an open spine with a leg that is neither a line nor an arc, or under a law whose frames are not those turned by a fixed angle along a spine with no sharp corner, an open wire sweeps one sheet of fitted skins held to the tolerance, uncapped: each profile edge a strip per run, neighbouring strips sharing their rails and neighbouring runs their rows. Its sharp corners follow the exact sheet’s rules point by point: outside a mitred corner against a curved leg the end row runs on straight to the halving plane as two ruled faces sharing their exact cut, outside a round one it turns about the corner’s axis, and inside either it runs on until its two paths cross; a point inside a corner whose paths pass each other, a turn too small to stand a round or run-on off its square end, and a law that sets a sharp corner’s sections other than the rotation-minimizing frame turned by a fixed angle are refused by name.
- restriction: Pipes run a circular section along a spine edge, exactly for straight and circular spines and skinned for free-form and helical ones, and along a wire of lines and arcs, open or closed, as the pipe shell of the disc square to its start with its default mitres, wherever that sweeps it exactly: legs meeting tangent are the cylinders and torus segments they sweep, a closed one a ring with no cap, straight legs are mitred on the planes halving their corners, a sharp corner of a wire with arcs, between two lines or turning in the plane of the arcs beside it (a line and an arc, or two arcs), is the union of the legs and their straight run-ons outside the turn, each cut on the plane halving the corner in one half ellipse, the legs meeting inside the turn where each rim point’s two paths cross, and a corner that turns an arc out of its plane, or turns in it so tightly that the paths inside it miss, is mitred in pieces. A wire the pipe shell refuses, or sweeps only as a skin (a free-form edge), is refused by name, as is a leg shorter than its corner’s reach; the skinned pipe takes such a wire to a stated tolerance, as the pipe shell of its disc.
- offset.wire-offset: Offsetting planar wires, with the join styles ·
covered
ogeom-topo
- topo.data-model: The B-rep data model: shapes as (node, location, orientation) handles into shared arenas ·
covered - topo.identity: The same / equal / partner identity trichotomy, each with a matching hasher ·
covered - topo.location-chain: Placement as a chain of transforms, so instancing shares geometry ·
covered - topo.multi-representation-edges: Edges and vertices carrying several representations: space curve, pcurves per face, polygons on triangulations ·
covered - topo.traversal: Exploring a shape: filtered descent, unique enumeration, ancestry maps ·
covered - topo.triangulation-store: Triangulations as first-class model data: triangles, polygons on them, parameters ·
covered
Integration and bindings
Included verbatim from
docs/INTEGRATION.md:
what embedding a kernel into a real application demands, and the rules
that keep it possible without letting it drive the design.
Integration
ogeom’s product is its own API. Integration layers for other languages and host applications are downstream and optional. None of them constrains the kernel’s design.
This file has one purpose: to record what embedding a kernel in a real application requires, so that today’s decisions do not rule it out by accident. Nothing here should drive work. It can only veto a design that would make embedding impossible.
The design pressure that matters
We surveyed a large application that embeds a B-rep kernel (about 4,100 call sites). One finding applies beyond that application:
Consumers do not just call a kernel. They extend it. The survey found seven classes that derive from kernel types and override their virtual methods:
- a shape subclass that intercepts every mutating member to keep an element map consistent;
- several operation subclasses;
- custom message and progress sinks.
Two consequences:
- A pure C ABI is never enough for a host that wants to specialise kernel behaviour. A C++ integration layer will need real classes with real virtual dispatch on top of the FFI surface.
- Extension points belong in the kernel’s design, not in the shim. Progress reporting, cancellation, diagnostics, custom tolerance policy and history observation should be traits in the Rust API. If a host has to subclass to reach them, the design is wrong.
The second point is the actionable one. It is kernel work, not integration work.
What the kernel must already do
DATA_MODEL.md covers all of these. Each row gives the consequence of getting
it wrong.
| Requirement | § | Consequence of getting it wrong |
|---|---|---|
Shape is (tshape, location, orientation) and cheap to copy | §1 | Every by-value shape parameter in every host becomes an allocation |
| Location is a chain, not a flat matrix | §2 | Assembly instancing breaks; placement identity can no longer be decided structurally |
| Orientation composes on descent | §3 | Face normals flip inconsistently. This is silent, and catastrophic downstream |
is_same / is_equal / is_partner with matching hashers | §4 | Shape maps use the wrong keys. Silent wrong answers |
| Per-entity tolerances with the containment rule | §5 | Imported geometry cannot be modelled with at all |
| Edges carry a representation list including per-face pcurves | §6 | Boolean face splitting has nothing to split with |
generated / modified / is_deleted on every operation | §7 | Downstream naming breaks silently and corrupts user documents |
| Stable provenance | §8 | References into a rebuilt model cannot be resolved at all |
Result, no exceptions, no signal conversion | §12 | Failures cannot be mapped cleanly into a host’s error model |
Watch the history row most closely: it is the only failure above that is silent. A parametric application records “fillet that edge” and, after a rebuild, finds the edge again by walking history. Incomplete history does not raise an error. It reopens the document with the wrong faces filleted.
Planned integration layers
None of these is scheduled. They are listed so their requirements stay visible.
| Layer | Notes |
|---|---|
C ABI (og-capi) | The base for everything else. Opaque handles, POD structs, explicit ownership. Straightforward once the native API is stable. |
| Python | By far the most valuable binding: it is how most people would try the kernel. Built with PyO3 over the native API, not over the C ABI. |
| C++ | Real classes with virtual methods over the C ABI, for hosts that want to specialise behaviour. |
| WASM | The kernel is pure Rust with no C dependencies, so this is nearly free. Keep it that way: weigh WASM before adding any dependency that could break it. It already runs on wasm32-unknown-unknown, which CI meshes on in Node (tools/ogeom-wasm). That target has no clock and no threads: timings go through ogeom_core::clock::Instant and read zero there unless the web feature reads performance.now(), and parallel stages run on the calling thread. |
Drop-in replacement for another kernel’s headers. This is technically possible: a source-compatible façade that exposes another kernel’s class names and signatures, built into libraries with the names that kernel’s build-system probes expect, so a consumer recompiles without code changes. It is feasible, large, and firmly a downstream project. It is a poor thing to design toward, for two reasons:
- it imports the other kernel’s mistakes wholesale, including the
pointer-identity model that
DATA_MODEL.md§8 exists to avoid; - some things cannot be supported at all. A host API that passes a raw shape pointer to a third-party binding runtime needs binary layout compatibility. That is not a goal, and pursuing it would damage the design.
Rules that protect integration without constraining the kernel
- The native Rust API is designed for Rust. No parameter exists just because a binding might want it.
- Extension points are traits in the native API: progress, cancellation, diagnostics, tolerance policy, history observation.
- No public type is compromised to make it representable in C. The C ABI deals in handles; that is its job.
- No dependency that would break WASM or require a C toolchain, unless an explicit decision is recorded here.
Contributing
Included verbatim from
CONTRIBUTING.md.
The licence rule and the correctness bar are the two sections to
read before anything else.
Contributing to ogeom
Independence
ogeom is an independent implementation. It does not depend on, vendor, link
against, bundle or commit any existing CAD kernel, and nothing in this
repository may pull one in. cargo build needs a Rust toolchain and nothing
else: no C compiler, no system libraries, no submodules. Keep it that way.
Never take another kernel’s code into this one. Not as a dependency, a
vendored subtree, a file, or a fragment pasted into a function. Nothing in
crates/ may be part of someone else’s build.
Do not read another kernel’s implementation source to work on this one.
Not its .cxx, .cpp, .c, .lxx or .gxx files, and not the bodies of
its headers. Do not keep a checkout of one next to this repository. The same
rule binds tools: never give an AI assistant, a script or a search index
another kernel’s implementation source as context for work here, and never
ask one to explain or summarise how another kernel’s code does something.
Do not build a copy of another kernel. A kernel that mirrors another’s class hierarchy, decomposition, call graph and file layout is a translation with different identifiers. The design is worked out here, from the sources below.
Use the field’s vocabulary. A boundary representation is a b-rep, a point
in the plane is a Point2, a curve in a surface’s parameters is a pcurve, a
blend is a fillet. Private jargon would make the kernel harder to read for no
gain. Naming a concept is not naming a dependency.
Name other kernels by their names. The licence is a fact about the
kernel, not a secret: the README’s licence statement and PROVENANCE.md
name Open CASCADE Technology, because ogeom derives from it in the
licensing sense and the LGPL-2.1 licence is that statement. Elsewhere the
field’s vocabulary carries the writing — a b-rep, a pcurve, a fillet —
because product names add no precision to code, tests or docs.
The legal side
- ogeom is LGPL-2.1-only. It is a derivative of Open CASCADE Technology, in
the licensing sense: reference-driven development does that, whatever the
rules below forbid afterwards (see
PROVENANCE.md). LGPL-2.1, the same licence OCCT itself carries, is the honest answer to the derivation. - Renaming identifiers does not stop a work being derived from another. Copyright follows the expression, not the names. That is why the rules above cover reading, not only copying.
- File formats are the safe case. A file format is not copyrightable, and
implementing one from its published description is interoperation. The STEP
and
.brepsupport were both built this way.
Sources
- Published algorithm specifications and papers: Shewchuk on robust predicates, Piegl & Tiller on NURBS, the marching-intersection and surface-surface literature, the published descriptions of boolean pipelines.
- Format standards: ISO 10303 for STEP, and the rest.
- A kernel’s public user documentation, for what an operation is expected to do. Not its source.
- First principles, and your own tests.
If you have contributed to another CAD kernel’s source, say so in your pull request, so we can be careful about which areas you work on.
PROVENANCE.md states how these rules have been applied over the project’s
history.
Scope
A complete solid-modelling kernel and its data exchange, and nothing else. Rendering, application document frameworks and scripting harnesses are out.
docs/SCOPE.md is normative. It states the rule, how to decide a case, and
what the rule does not mean. docs/CAPABILITIES.md records, capability by
capability, where the kernel stands.
Where we deliberately do a job differently from the usual way, the capability
record says divergent and gives the reasoning. That is an answer, not a gap.
Invariants cannot be changed in a pull request
docs/DATA_MODEL.md is normative. A change that breaks one of its invariants
is a design change and must be argued as one, not slipped in. Examples:
flattening the location chain, giving an edge a single curve, adding an
operation that does not emit history, conflating same/equal/partner.
The reason is practical. Each invariant is cheap to keep now and effectively impossible to add back later across all of a kernel’s algorithms. Each one has its concrete failure mode written next to it.
Correctness
Geometry code fails quietly. A boolean that returns a plausible but wrong solid does not throw; it corrupts a document six operations later. So:
- State the property, then test it. Round-trips, composition laws, tolerance
containment, orientation consistency, antisymmetry of predicates. Property
tests over laws are worth more than many examples. See
crates/ogeom-core/tests/properties.rs. - Validate against ground truth you can compute independently. Analytic results for analytic inputs, closed-form volumes and areas, known benchmark datasets. “It looks right in the viewer” does not count.
- There is no external oracle. Comparing against another kernel is not possible, because it would mean vendoring one. Instead, check against closed forms, round trips, invariants (volume, area, validity), and properties over random inputs. The cheapest useful check is usually: build the same result two ways and confirm they agree.
- Never loosen a tolerance to make a test pass without explaining why in the same commit. Numerical tolerances in tests are explicit and justified in a comment.
- Failures are values. An algorithm that did not converge returns that fact. It does not return an empty shape and set a flag.
Practical rules
- Comments describe current behaviour, never the change that produced it.
- No “used to”, “formerly”, “now returns”, “since X landed”.
- No issue, PR or commit references as the reason for a behaviour. Those belong in the commit message.
node tools/lint-comment-rot.mjs --allenforces this intools/check.sh.--pedanticadds an advisory tier. Putlint-comment-rot: ignoreon a line to exempt it where a reference is genuinely needed.- The tracked pre-commit hook runs the lint over the lines a commit adds.
Enable it once per clone with
git config core.hooksPath .githooks.
- Run
./tools/check.shbefore review. It runs formatting, lints, tests and docs, and repeats the test suite so a property test that fails only on some seeds does not slip through. Do not verify by grepping cargo’s output for “ok”: a run with a failing suite still prints “ok” for every suite that passed, so a real failure can hide behind a green-looking summary. - Tests have a budget. Every test in the default tier finishes within
5 s in the
iterateprofile. A test that needs longer (a sweep, a corpus part, a fine mesh) is marked#[ignore = "heavy"]; prefer keeping a small representative case in the default tier beside it.cargo t(an alias forcargo test --profile iterate) is the working loop: the crates you touched, plus the heavy tests of that area by name with-- --include-ignored.- Do not test with
--release: it links every test binary with link-time optimisation and one codegen unit, ten seconds or more each, for the same answers. tools/check.shand CI run both tiers.tools/test-times.shprints the slowest tests of the default tier and fails past 15 s.
- Workspace lints forbid
unsafe, and warn onunwrap/expectand lossy numeric casts in library code. A kernel is arithmetic from end to end, and these are how wrong answers get shipped. A deliberate exception needs an#[allow(..., reason = "...")]and a documented# Panicssection. - New dependencies need a permissive license (
deny.tomlenforces this). They must not require a C toolchain or break WASM. If you think an exception is warranted, raise it explicitly.- Record your decision there either way. The record stops the question being argued again.
- Public items are documented.
missing_docsis a warning, and CI runs with-D warnings.
Skills for coding agents
.claude/skills/<name>/SKILL.md holds the working methods an agent follows
for a kind of task. They cite the rules above; they never weaken one. Read
the skill before the task it covers.
| Skill | Use when |
|---|---|
review | reviewing a branch or PR for wrong geometry, broken invariants and release risk |
systematic-debugging | any bug, failure, wrong shape or refusal, before proposing a fix |
test-audit | writing, changing, reviewing or pruning tests |
performance-optimizer | a measured slowness or a benchmark drift |
ci-iterate | a CI run is red, or a PR has review feedback to address |
comment-cleanup | before committing, when the comment-rot lint fails, or to trim or unify comments |
Releasing
The fifteen library crates under crates/ are published together, at one
version, from [workspace.package]. The three crates under tools/ have
publish = false; they exist only for this repository.
cargo publish --dry-run --workspace # packages, verifies and orders, uploads nothing
cargo publish --workspace # the same, for real
Cargo works out the order from the dependency graph, so one command publishes all fifteen. Know these three points before changing the arrangement:
- Versions move together. Every crate inherits
version.workspace, and the internal requirements in[workspace.dependencies]are pinned to the same number. Bump both, or the workspace stops resolving. Also bump the requirements inoutside/Cargo.toml, because that workspace depends on these crates by version. ogeom-meshdev-depends onogeom-algoby path only.ogeom-algodepends onogeom-mesh, so a version requirement here would be a cycle no registry can resolve. Cargo drops a path-only dev-dependency from the published manifest, so the cycle stays inside this repository. Do not “tidy” it to.workspace = true.- The corpus is not published.
tests/corpus/sits at the repository root, outside every package, and each crate’sexcludelists the suites that read it. A published tarball contains only tests it can run. The repository andtools/check.shstill run all of them.
Provenance
Included verbatim from
PROVENANCE.md:
where the kernel’s code and design came from.
Provenance
This file records where ogeom’s code and design came from, so that users and contributors can judge its licence for themselves. ogeom is LGPL-2.1-only, and it is a derivative work of Open CASCADE Technology (OCCT) in the licensing sense. It contains no code from OCCT’s implementation, translated or otherwise; the audit below is the evidence. What makes it derivative is the reference-driven development recorded here.
CONTRIBUTING.md states the naming rule that goes with this: the licence
and the provenance name OCCT plainly, and the rest of the repository
writes in the field’s vocabulary.
The licence
ogeom is licensed under the GNU Lesser General Public License, version 2.1, and no other version: the same licence OCCT itself carries. The reason is the relationship this file records. The public API vocabulary, the data-model conventions and the default tolerances follow OCCT; the project’s first goal was a kernel that could take OCCT’s place in OCCT-based software; and for one day its implementation source was read. Reference-driven development makes the kernel a derivative work in the licensing sense even where the audit below finds no translated code, and no audit can bound what the AI assistants that wrote most of this code learned from before this project began. LGPL-2.1 states that position honestly, and it keeps the work usable: a library under it may be linked from software under any licence, which is what an application kernel has to allow.
Copies of releases published before the change were distributed under MIT OR Apache-2.0 and remain under those terms; the licence above applies to releases published after it.
The rule, from the first commit
The first commit (31 July 2026) included CONTRIBUTING.md with this rule:
“Do not copy, translate, or transliterate another kernel’s source into
crates/.” The rule has only grown stricter since. Today it also forbids
reading another kernel’s implementation source for work here, and giving it
to any tool, an AI assistant included, as context.
The implementation sources are:
- published algorithms and papers: Piegl & Tiller for NURBS, Shewchuk for robust predicates, Brent’s method, Yuksel’s polynomial roots, and the marching and surface/surface intersection literature;
- format standards: ISO 10303 for STEP, the IGES specification, and the published descriptions of the other formats;
- first principles, checked by tests against results computed independently: closed forms, round trips, invariants and properties over random inputs.
Open CASCADE Technology (OCCT)
OCCT, which is LGPL-2.1, is the kernel ogeom was most often compared with. The relationship is set out here in full.
Why OCCT was a reference. The project’s first goal was a kernel that could be plugged into FreeCAD, which is built on OCCT. To learn what that would take:
- FreeCAD’s use of OCCT’s public API was profiled, from public headers only;
- OCCT’s module and toolkit manifests and its public headers defined a feature-parity scope;
- capabilities were audited against that scope.
That goal has been dropped. ogeom is not meant to replace OCCT in FreeCAD or
in any other program, and the tooling and data built for it have been removed
from the repository. The scope is now stated in ogeom’s own terms
(docs/SCOPE.md), and capabilities are recorded against it
(docs/CAPABILITIES.md).
Public API names. Several public names follow OCCT’s vocabulary
(make_prism, make_pipe_shell, fuse / cut / common, sew,
fix_shape, unify_same_domain, is_same / is_partner / is_equal). They
were chosen so that code written against OCCT would map onto ogeom easily.
They are names only: the implementations behind them are ogeom’s own. Names
that exist only to mirror OCCT may change in a future release.
Data-model conventions. ogeom-topo adopts B-rep conventions that OCCT
users know: a shape as a shared node plus a placement and an orientation, the
eight shape types, the four orientations and how they compose, three levels of
shape identity, placement as a chain of transforms, and several curve
representations on one edge. They were adopted so that models and .brep
files written by OCCT-based software map onto ogeom without loss. The code
that expresses them is independent: arenas with stable keys rather than
reference-counted handles, a placement chain built by appending rather than a
linked list, and an entity identity and provenance model
(docs/DATA_MODEL.md §8) that departs from OCCT’s pointer identity on
purpose.
Default tolerances. The defaults in ogeom-core equal OCCT’s Precision
values: confusion 1e-7, angular 1e-12, and intersection, parametric
confusion and approximation at the same ratios to confusion. The reason is
interoperability. STEP and .brep files written by OCCT-based software state
their vertex and edge tolerances on that basis, and a kernel that reads them
has to agree on when two points are the same, or valid models arrive invalid.
Nothing else in the tolerance code corresponds to OCCT’s: ogeom scales linear
tolerances by the model’s unit, adds a separate degeneracy threshold, and
keeps per-entity tolerances with containment rules.
Vocabulary. The boolean code uses the words “pave”, “interference” and
“same domain”. These words are also used in OCCT’s public Boolean Operations
user guide. In ogeom they name ogeom’s own structures: a Pave is a parameter
with a tolerance and spread model, there are no interference tables, and the
pipeline is arranged differently (face pairs found through a box tree and
intersected in parallel, then each face arranged in its own parameter space).
AI-assisted development. Most of ogeom was written with AI coding assistants, under the same rule. OCCT’s public headers and manifests were used as input for the scope and API-surface work described above. Implementation source was not supplied to these tools, with one exception. On 21 October 2026, agents researching two boolean robustness issues read parts of OCCT’s boolean implementation. One fix written afterwards (PR #388) was compared line by line with the OCCT functions consulted; its methods differ from them. That reading broke the spirit of this project’s rule, and the rule now forbids it explicitly. No one can say what public code the underlying AI models were trained on. That is why the audit below examines the code itself.
The audit
In October 2026, about two dozen of the most algorithm-specific functions and structures were compared side by side with OCCT’s implementation, across mathematics, topology, intersection, booleans and tolerances. None is a translation or a close paraphrase. No OCCT identifiers, naming conventions or comments appear in the code, and no module follows OCCT’s file layout or call graph.
| Area | Finding |
|---|---|
| Mathematics | Published algorithms, written independently. Brent in inverse-quadratic form, Yuksel polynomial roots, de Boor evaluation and knot insertion from Piegl & Tiller, degree elevation by Bézier decomposition. |
| Topology | Shared design conventions (above), independent code. |
| Intersection | Independent. One walker shared by surface/surface intersection, silhouettes and blend marching, with a single spatial step and a sag bound; its own seeding and closed forms. |
| Booleans | Independent decomposition. No counterpart to OCCT’s data structure, pave blocks, common blocks or interference tables. |
| Tolerances | Same default values, for interoperability (above). Nothing else shared. |
Reporting a concern
If a function or file looks like a translation of another kernel’s code, open an issue naming it. It will be compared in public and, if it is derived, either rewritten or recorded here with the rest of the provenance: under the LGPL, a derivation in the open is compliance.