Release Notes
v0.10.1
Repairs; no API breaks. orient_faces_consistently and
ensure_positive_orientation now return whether the surface is
orientable: every reversal is decided against the input winding and
applied after the walk, so one call settles every orientable
manifold-edge component, and a non-orientable component is left
untouched rather than half-repaired. An integral coordinate type votes
by face count, its lattice not holding a squared area.
euler_characteristic counts boundary edges correctly — a disk is 1,
an uncapped tube 0. fit_similarity_alignment measures the source
spread in the same space as its covariance, so a scaled frame no longer
skews the recovered scale. The VTK wrappers delegate orientation to the
core and surface its verdict.
v0.10.0 — one engine
The engine
Every boolean runs on the CSG engine. tf::make_boolean builds the same
N-form arrangement that powers tf::make_csg_graph and evaluates the
operation as a boolean expression over it. One arrangement answers any number
of boolean expressions, domain reads, and curve extractions; there is one
pipeline, one triangulator, one classification tier.
The arrangement is two tiers. The local arrangement is the identity tier: polygon intersections are classified exactly, and every crossing, landing, and coincidence names one created identity on an exact integer lattice — a point's identity is a canonical name, not a coordinate. Coplanar faces, within a mesh and across meshes, pool onto one geometric plane, so one plane is one identity and coincident walls cannot disagree. The plane arrangement is the triangulation tier: each plane carrier triangulates against its own constraint set, and a statement that cannot stand as given is resolved — split at its crossings, welded at its coincidences — in recovery waves that repeat until nothing new is stated. Classification — components, fences, radial fans, domains — reads the finished arrangement, and every product (boolean meshes, domain cells, the outer shell, intersection curves) is a read.
Coincidence is resolved at the root. When quantization collapses geometry — duplicate vertices, sub-epsilon edges, folded slivers, vertices landing on the intersection curve — the arrangement resolves each such coincidence to a single created identity, and the surrounding faces re-emit conformed to it. Raw arrangements are watertight by construction at these sites.
tf::triangulated is the same engine with the face as the carrier. Every
face triangulates on its own boundary, a shared edge is one identity in both
faces, and a self-crossing or folded face is resolved — split at its own
crossings, its coincident vertices welded — never refused silently or
dropped. A resolved face's positions are unchanged; only its names are.
Refusals are a surface, not a silence. tf::return_refused on
tf::triangulated, tf::embedded_isocurves, and tf::make_isobands
returns the input faces a build declined, and failed() on a built graph
names the carriers still refusing after recovery. Nothing is dropped without
a name, and the surface costs zero when nothing refuses.
Tolerance is a statement about the input
A tolerance is the pitch the input's planes are quantized to; it never widens a predicate. One converter is built over the union of the input forms; every face's plane — direction and offset — is rounded onto a grid of that pitch, so two faces whose planes differ by less than it share one plane. Every original vertex is then placed, at most the tolerance away, onto the quantized planes its own faces stand on — the meet of three where it is a corner, a line of two where it lies on a crease, its own tangent plane where the surface is smooth. The result is the exact arrangement of that moved mesh: every predicate below the placement runs exact, and a weld is an identity, never a proximity. A tolerance of zero is the identity — nothing moves, no placement table is built, and the result is byte-identical to the exact arrangement of the input as given.
A wall doubled at less than the pitch becomes one wall; one doubled at more stays two. A vertex a weld retires has no output point of its own — the index map reports it absent. Plane identity is quantized-name equality, so results from calls with different converter domains are not identity-comparable; a session that needs one perturbation across calls pins one converter.
Provenance selection
One arrangement, any read. tf::csg::selection names which forms' faces
reach the output, orthogonally to the boolean algebra:
auto graph = tf::make_csg_graph(forms); // A=0, B=1, C=2
auto e = (tf::csg::op(0) | tf::csg::op(1)) - tf::csg::op(2);
auto solid = tf::make_csg_mesh(graph, e); // as always
auto partA = tf::make_csg_mesh(graph, tf::csg::selection(0, e)); // A's walls of it
auto inA = tf::make_csg_mesh(graph, tf::csg::inside(2, tf::csg::op(0))); // C inside A
auto embed = tf::make_csg_mesh(graph, tf::csg::selection({0})); // A cut by all, no boolean
The per-form parts partition the result face-for-face and re-weld into the
solid. tf::csg::inside reads the other side of the same question — the
named forms' surface lying INSIDE the expression's region, both sides of a
piece in it, kept in the form's stored winding, which is how a sheet's
surface within a solid is asked for. tf::make_csg_domains takes the same
selection — each cell's walls by contributor — in its boundary kind only.
Every existing expression call is unchanged.
The module map (breaking)
The cut module was named for the verb its first consumer needed. Its ground
is split by mechanism, and there is no compatibility shim: the old names are
gone, not deprecated.
<trueform/arrangement.hpp>— mesh, polygon, and segment arrangements: the arrangement of a set of forms and its reads.<trueform/iso.hpp>— the scalar-field tier:embedded_isocurves,make_isobands,make_isocontours,scalar_field_intersections.<trueform/csg.hpp>— booleans, the CSG graph, expressions, selections, domains, and the outer shell.
| Old | New |
|---|---|
<trueform/cut.hpp> | <trueform/arrangement.hpp> |
<trueform/cut/...> | <trueform/arrangement/...> |
tf::cut:: | tf::arrangement:: |
<trueform/intersect/make_isocurves.hpp> | <trueform/iso.hpp> |
<trueform/intersect/scalar_field_intersections.hpp> | <trueform/iso.hpp> |
tf::cut::iso:: | tf::iso:: |
tf::make_isocontours, tf::make_isobands, tf::embedded_isocurves and
tf::scalar_field_intersections keep their names; only their header and the
machinery namespace below them changed. See Iso.
tf::make_boolean keeps its name but not its umbrella. A boolean is the
two-operand case of the CSG arrangement, so the entry lives in
<trueform/csg.hpp> — not <trueform/arrangement.hpp>. See
CSG.
Removed entry points
| Removed | Use instead |
|---|---|
tf::embedded_intersection_curves | tf::make_mesh_arrangements |
tf::embedded_self_intersection_curves | tf::make_polygon_arrangements |
tf::intersections_between_polygons | tf::polygon_intersections |
tf::intersections_within_polygons | tf::polygon_intersections |
tf::triangulated_faces | tf::triangulated |
tf::triangulated_faces returned corners alone, against a point table it did
not return. That was sound only while a triangulation could name no point the
input did not carry, and it no longer can: a face whose loop crosses itself is
resolved, and the identity its crossing stands on is a corner the caller's own
array has no row for. tf::triangulated returns the corners with the table
they index. Code that needs the connectivity without the mesh reads the
arrangement mesh tier (<trueform/arrangement/mesh/mesh_triangulation.hpp>)
directly — it states the triangles, the minted identities and the refusal
surface as one product.
tf::polygon_intersections is not exported by <trueform/intersect.hpp>;
include <trueform/intersect/polygon_intersections.hpp> directly. Arity plus
tf::intersect_config decide between- and within-form records, so the two
alias types have no successor names.
Retired triangulators
The constrained Delaunay family is the library's one triangulation engine.
| Removed | Use instead |
|---|---|
tf::ear_cutter | tf::constrained_delaunay_triangulator, or tf::triangulated for a whole mesh |
tf::delaunay_triangulator | tf::unconstrained_delaunay_triangulator, or tf::make_cdt(points) |
tf::delaunay_flipper | — no replacement; it existed only for the batch triangulator |
tf::constrained_delaunay_triangulator::build and its siblings take a
trailing tf::cdt_region_mode
that states what region_labels() mean.
The exact lattice
The lattice integer is read off the coordinate type or named by the caller —
it is never a hardcoded default. Every entry point (tf::make_cdt, the
arrangements, the booleans, the iso cut) resolves it for you; direct class
instantiations are affected:
tf::constrained_delaunay_triangulatorandtf::unconstrained_delaunay_triangulatordefault theirIntfrom the coordinate type (float →int32, double →int64). A direct double-precision instantiation that leaned on the old fixed-int32default now computes on theint64lattice.- The planar-graph classes —
tf::planar_graph_regions,tf::face_hole_relations,tf::face_split_by_edges,tf::hole_patcher,tf::planar_embedding— carry no coordinate type, soInthas no default:tf::planar_embedding<int>does not compile; writetf::planar_embedding<int, tf::exact::int32>(or the lattice your coordinates call for).
API changes
make_boolean(a, b, op[, sheets][, config])— operands listed insheetsbound no volume and cut as oriented separators. An open operand is a volume unless declared a sheet; declare it a sheet for the previous open-mesh behavior.- Booleans take
tf::arrangement_config(anintersect_config, atriangulation_type, or both);boolean_configdoes not exist. Multi-nesting is always on. make_boolean(..., tf::return_index_map)returnstf::stitch_index_map— a producer-agnostic carrier consumed bytf::stitch_*.- A swapped difference does not reverse winding.
intersect_config::toleranceis the distance an input vertex may move to reach the lattice — a statement about the input, exact below it. A band on the predicates' comparisons does not exist.tf::intersections_within_segments::build(segments)takes nointersect_config: the segment tier is exact, like every tier.make_intersection_curvesdefaults toprimitives.triangulatedresolves rather than refuses: a self-crossing face is split at its crossings, coincident vertices weld into one identity at the same coordinate — a resolved face need not name every vertex it was given; positions are unchanged, only names. Region fill islabels != 0.fit_similarity_alignmentrecovers the scale independent of the point count; it wasntimes too small (#21).
Bindings
The Python and TypeScript layers follow the same map in the same release, so all three languages speak the same module names. See the Python release notes and the TypeScript release notes.
