pub struct Model {
pub vertices: OrdMap<VertexId, Vertex>,
pub edges: OrdMap<EdgeId, Edge>,
pub coedges: OrdMap<CoedgeId, Coedge>,
pub faces: OrdMap<FaceId, Face>,
pub shells: OrdMap<ShellId, Shell>,
pub solids: OrdMap<SolidId, Solid>,
pub wires: OrdMap<WireId, Wire>,
/* private fields */
}Expand description
The top-level boundary-representation model.
Vertex, Edge, Coedge, Face, Shell and Solid are the entities
genuinely shared by reference (an edge by its two coedges via opposite,
a vertex by every incident edge, and so on) — these live here in arenas
and are referenced by stable typed IDs. Their geometry (a Curve3 per
Edge, a Curve2 pcurve per Coedge, a NurbSurface per Face) is
owned 1:1 by that entity directly rather than through another arena, since
nothing ever needs to reference it independently. A loop has no entity of
its own: each entry of Face::boundaries just anchors a CoedgeId whose
next/prev cycle traces the whole loop.
Hierarchy (each arrow means “references one or more”):
Solid ──▶ Shell(s) ──▶ Face(s) [+Surface] ──▶ Coedge(s) [+Curve2] ──▶ Edge [+Curve3] ──▶ Vertex
Wire ──▶ Edge(s) [+Curve3], Vertex(es)A Wire holds edges and vertices that bound no face: curves in space.
V - E + F - L = 2 * (Scalar - G), E = 2C for a single-shell solid with genus G,
where V = #vertices, E = #edges, F = #faces, L = #hole loops (i.e.
Σ (face.boundaries.len() - 1), not counting each face’s mandatory outer loop),
Scalar = #shells, and G = genus. Euler’s formula generalizes to multiple
shells and/or genus > 0.
Creation (insert_*), lookup (get_*), and iteration (iterate_*)
methods each live in their own private submodule (model::create,
model::get, model::iterate) — all still just plain inherent Model
methods from the outside.
The arenas are ordered by id, which is the order of creation: every walk
over a model’s entities — validation, a boolean’s classification, the
remesh’s pairing of faces — sees them in the same order on every run.
A HashMap iterates in an order seeded afresh in each process, and a
validation failure that depended on which face pair was checked first
appeared and disappeared between runs.
Fields§
§vertices: OrdMap<VertexId, Vertex>§edges: OrdMap<EdgeId, Edge>§coedges: OrdMap<CoedgeId, Coedge>§faces: OrdMap<FaceId, Face>§shells: OrdMap<ShellId, Shell>§solids: OrdMap<SolidId, Solid>§wires: OrdMap<WireId, Wire>Edges and vertices bounding no face (see Wire).
Implementations§
Source§impl Model
impl Model
Sourcepub fn build_body(&mut self, spec: BodySpec) -> GeopResult<BuiltBody>
pub fn build_body(&mut self, spec: BodySpec) -> GeopResult<BuiltBody>
Adds the body spec describes, once BodySpec::check has found
it consistent — nothing is added otherwise. One without faces is
added as a Wire.
Sourcepub fn body_spec(
&self,
faces: &[FaceId],
solid: bool,
) -> GeopResult<(BodySpec, SpecSources)>
pub fn body_spec( &self, faces: &[FaceId], solid: bool, ) -> GeopResult<(BodySpec, SpecSources)>
faces described as a body of one shell — a solid’s if solid —
with every vertex and edge they use, copied: building it makes a
copy of them, sharing nothing with the originals. Also says where in
the model each entity of the description came from.
Source§impl Model
impl Model
Sourcepub fn assemble_solid(
&mut self,
consumed: &[Body],
keep: &[FaceId],
) -> GeopResult<Option<SolidId>>
pub fn assemble_solid( &mut self, consumed: &[Body], keep: &[FaceId], ) -> GeopResult<Option<SolidId>>
Replace the bodies consumed by one new solid bounded by keep,
which must be faces of those bodies: a shell for each connected set
of them, faces sharing an edge being connected. A boolean that cuts
a bar in two, or leaves a void inside a block, so keeps two shells,
each a closed surface of its own (see Solid). Every other face
the bodies owned is deleted, and so is everything no face reaches any
more.
What a boolean does last, once it has decided which faces survive.
Ok(None) (and no solid) when keep is empty: the result is empty,
which is an answer rather than a failure — and also how a body is
deleted outright.
Deletion is by reachability rather than by tracking what was split or
re-homed on the way here, so no dangling id can be left behind — and
it only ever deletes from what consumed owned: faces of any other
body in the model are none of this operation’s business.
Sourcepub fn assemble_sheet(
&mut self,
consumed: &[Body],
keep: &[FaceId],
) -> GeopResult<Option<ShellId>>
pub fn assemble_sheet( &mut self, consumed: &[Body], keep: &[FaceId], ) -> GeopResult<Option<ShellId>>
Like Model::assemble_solid, but keep becomes a sheet — faces
standing on their own, in one shell whether connected or not —
rather than a solid: what is left of a sheet once it is trimmed.
Sourcepub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()>
pub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()>
Move every shell of from into into, deleting from: one solid of
both bodies. Only valid for bodies that don’t touch — nothing is
intersected, so two overlapping ones would make a solid whose shells
cross; combining those is a boolean union’s job.
Source§impl Model
impl Model
Sourcepub fn merge_edge(
&mut self,
edge_into_id: EdgeId,
edge_deleted_id: EdgeId,
reversed: bool,
) -> GeopResult<()>
pub fn merge_edge( &mut self, edge_into_id: EdgeId, edge_deleted_id: EdgeId, reversed: bool, ) -> GeopResult<()>
Merges edge_deleted_id into edge_into_id: repoints every coedge
tracing the deleted edge to the surviving one, then drops the
now-unreferenced edge. reversed says whether the deleted edge ran
start<->end the other way round relative to the surviving one — if
so, every repointed coedge’s sense is flipped so it keeps tracing
the same physical direction it always did.
TODO: edge_into_id‘s own curve currently stays exactly as it was —
it should instead be widened (unioned) to certainly contain the
deleted edge’s curve too, so the kept edge’s geometry honestly
reflects both original edges’ combined tolerance instead of silently
favoring whichever one happened to survive.
Source§impl Model
impl Model
Sourcepub fn merge_vertex(
&mut self,
vertex_into_id: VertexId,
vertex_deleted_id: VertexId,
) -> GeopResult<()>
pub fn merge_vertex( &mut self, vertex_into_id: VertexId, vertex_deleted_id: VertexId, ) -> GeopResult<()>
Merges vertex_deleted_id into vertex_into_id: unions their points
into vertex_into_id, repoints every edge/coedge/boundary reference
from vertex_deleted_id to vertex_into_id, then drops the now
-unreferenced vertex.
Source§impl Model
impl Model
Sourcepub fn reverse_face(&mut self, face_id: FaceId) -> GeopResult<()>
pub fn reverse_face(&mut self, face_id: FaceId) -> GeopResult<()>
Turn face_id’s material side around, so its normal points the other
way.
Orientation in this kernel lives in the surface’s parametrization —
there is no flag on a face — so flipping it means mirroring the
surface’s u (see geop_core_geometry::nurb_surface::NurbSurface::reverse_u)
and applying the identical mirror to every pcurve drawn on it. The
domain is unchanged by the mirror, so the trim loops still describe
the same region of the same surface; only Su x Sv reverses.
Deliberately touches nothing but this face’s surface and its own pcurves. The 3-D curves, the edges, the vertices and every coedge on the other side of those edges are untouched, which is what lets a boolean flip one operand’s faces while they stay glued to the other’s along shared edges.
Source§impl Model
impl Model
Sourcepub fn splice_edge_into_face(
&mut self,
edge_id: EdgeId,
face_id: FaceId,
max_nodes: usize,
min_subdivision_size: Scalar,
) -> GeopResult<Option<FaceId>>
pub fn splice_edge_into_face( &mut self, edge_id: EdgeId, face_id: FaceId, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<Option<FaceId>>
Splice an already-existing edge_id into face_id’s boundary
structure, as a forward/reversed coedge pair whose pcurves it fits onto
the face’s own surface.
The edge already exists because two faces are meant to share it (the whole point of imprinting an intersection curve), so this rewires loops around the edge it’s handed rather than making one.
What it does depends on how much of the edge’s own topology the face already knows about, which is the only thing that determines what a correct result even looks like:
| edge’s start/end vertex on this face’s boundary | result |
|---|---|
| neither | a new self-contained ring floating inside the face — a hole |
| exactly one | a spur (out along the edge and back) inserted into that vertex’s own loop |
| both, same hole | that hole is divided into two holes |
| both, two different holes | those holes are merged into one |
| both, a hole and the outer loop | the hole is absorbed into the outer loop |
| both, the outer loop | the face is split into two faces |
Splitting the face, and dividing a hole (whose material side becomes
a face of its own), are the cases that create a face; its id is
returned, None otherwise. The face split is the only one that has to
reclassify the old face’s holes — each now lies inside exactly one of
the two halves (see [split_face]). A spur changes no connectivity at
all, being “wire” topology rather than a real trim boundary.
Source§impl Model
impl Model
Sourcepub fn split_edge_at_vertex(
&mut self,
edge_id: EdgeId,
edge_t: Scalar,
vertex_id: VertexId,
max_nodes: usize,
min_subdivision_size: Scalar,
) -> GeopResult<EdgeId>
pub fn split_edge_at_vertex( &mut self, edge_id: EdgeId, edge_t: Scalar, vertex_id: VertexId, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<EdgeId>
Splits edge_id at edge_t into two edges joined at the pre-existing
vertex_id (which must already coincide with edge.curve.evaluate(edge_t)),
and splits every coedge tracing edge_id to match. edge_id itself
keeps the first (start -> vertex) segment; the new edge holding the
second (vertex -> end) segment, in the same direction, is returned.
max_nodes/min_subdivision_size bound the BFS search used to locate
each coedge’s own pcurve parameter for the split (see
curve_could_contain).
Source§impl Model
impl Model
Sourcepub fn transform_body(
&mut self,
body: impl Into<Body>,
map: &dyn Deformation,
) -> GeopResult<()>
pub fn transform_body( &mut self, body: impl Into<Body>, map: &dyn Deformation, ) -> GeopResult<()>
Moves the whole of body by map in place — a rigid motion, a
translation, a mirror, a scaling
(geop_core_math::primitives::Motion) or a deformation about an
axis (geop_core_geometry::deform::AxialMap): every vertex, every
edge’s curve and every face’s surface. The pcurves stay as they are:
every map keeps the surfaces’ parametrization, so each pcurve still
traces the same patch, now moved. Nothing is created or deleted, and
the body stays connected exactly as it was.
A map made of pieces moves each edge and face by the piece it lies in, asking for a point of it (the curve’s middle, a point inside the face) only where its control points straddle two pieces: the body has to be divided first where it crosses from one to the next.
A mirror turns every surface’s normal into the body (see
geop_core_geometry::nurb_surface::NurbSurface::transform), so
it also turns every face around (Model::reverse_face): a mirrored
solid is a valid solid again, its normals pointing out. Each edge is
shared by two faces of the body, both turned, so their coedges stay
opposite.
A translation moves every point by one addition per coordinate; a rotation or a mirror encloses its result honestly, widening every point by what its rounded matrix leaves open.
Source§impl Model
impl Model
Sourcepub fn face_area(&self, face: FaceId) -> GeopResult<(Scalar, bool)>
pub fn face_area(&self, face: FaceId) -> GeopResult<(Scalar, bool)>
The area of the face face, and whether its quadrature converged.
Sourcepub fn solid_area(&self, solid: SolidId) -> GeopResult<(Scalar, bool)>
pub fn solid_area(&self, solid: SolidId) -> GeopResult<(Scalar, bool)>
The area of the boundary of solid: the sum of its faces’ areas,
and whether every face’s quadrature converged.
Sourcepub fn mass_properties(&self, solid: SolidId) -> GeopResult<MassProperties>
pub fn mass_properties(&self, solid: SolidId) -> GeopResult<MassProperties>
The mass properties of solid, of density one: what its shape
alone decides, and a material scales (see
MassProperties::with_density) — so they can be kept for a
shape, whatever it is made of.
Fails for a solid whose faces do not enclose a volume definitely greater than zero — its faces pointing into the material, or its volume not resolved by the quadrature — naming it.
Source§impl Model
impl Model
pub fn insert_vertex(&mut self, v: Vertex) -> VertexId
pub fn insert_edge(&mut self, e: Edge) -> EdgeId
pub fn insert_coedge(&mut self, c: Coedge) -> CoedgeId
pub fn insert_face(&mut self, f: Face) -> FaceId
pub fn insert_shell(&mut self, s: Shell) -> ShellId
pub fn insert_solid(&mut self, s: Solid) -> SolidId
pub fn insert_wire(&mut self, w: Wire) -> WireId
Source§impl Model
impl Model
pub fn get_vertex(&self, id: VertexId) -> GeopResult<&Vertex>
pub fn get_vertex_mut(&mut self, id: VertexId) -> GeopResult<&mut Vertex>
pub fn get_edge(&self, id: EdgeId) -> GeopResult<&Edge>
pub fn get_edge_mut(&mut self, id: EdgeId) -> GeopResult<&mut Edge>
pub fn get_coedge(&self, id: CoedgeId) -> GeopResult<&Coedge>
pub fn get_coedge_mut(&mut self, id: CoedgeId) -> GeopResult<&mut Coedge>
pub fn get_face(&self, id: FaceId) -> GeopResult<&Face>
pub fn get_face_mut(&mut self, id: FaceId) -> GeopResult<&mut Face>
pub fn get_shell(&self, id: ShellId) -> GeopResult<&Shell>
pub fn get_shell_mut(&mut self, id: ShellId) -> GeopResult<&mut Shell>
pub fn get_solid(&self, id: SolidId) -> GeopResult<&Solid>
pub fn get_solid_mut(&mut self, id: SolidId) -> GeopResult<&mut Solid>
Sourcepub fn wire_of_edge(&self, edge: EdgeId) -> Option<WireId>
pub fn wire_of_edge(&self, edge: EdgeId) -> Option<WireId>
The wire edge is an edge of, if any.
pub fn coedge_start_vertex_id(&self, coedge: CoedgeId) -> GeopResult<VertexId>
pub fn coedge_start_vertex(&self, coedge: CoedgeId) -> GeopResult<&Vertex>
pub fn coedge_end_vertex_id(&self, coedge: CoedgeId) -> GeopResult<VertexId>
pub fn coedge_end_vertex(&self, coedge: CoedgeId) -> GeopResult<&Vertex>
Sourcepub fn normal_at_vertex(
&self,
face: FaceId,
vertex: VertexId,
) -> GeopResult<Vector3>
pub fn normal_at_vertex( &self, face: FaceId, vertex: VertexId, ) -> GeopResult<Vector3>
The unit normal of face’s surface at its own vertex vertex: at
the (u, v) its loop gives the vertex — where a coedge of it
starting there starts — not at one searched for by the vertex’s
point. Where the patch’s parametrization degenerates at the vertex —
a SubD patch whose two sides leave its corner along one line — a
whole valley of (u, v) lies within a search’s reach of the point,
and the search runs out of nodes; the loop knows which (u, v) it
is. An error if no coedge of the face starts there.
Source§impl Model
impl Model
Sourcepub fn coedges_of_edge(&self, edge: EdgeId) -> Vec<CoedgeId>
pub fn coedges_of_edge(&self, edge: EdgeId) -> Vec<CoedgeId>
The (unordered) coedges referencing a given edge. An edge shared by a single manifold face pair has exactly two.
Sourcepub fn iterate_loop_coedges(
&self,
anchor: CoedgeId,
) -> impl Iterator<Item = CoedgeId> + '_
pub fn iterate_loop_coedges( &self, anchor: CoedgeId, ) -> impl Iterator<Item = CoedgeId> + '_
Every coedge of the loop anchored at anchor, in traversal order,
following next back around to anchor. Does not itself bound how
many coedges it yields: a corrupted next chain that never returns
to anchor makes this iterate forever, which callers checking that
invariant (see validation::two_way_references) detect with a
.take(n) of their own.
Sourcepub fn face_corners(
&self,
face_id: FaceId,
) -> GeopResult<Vec<Vec<(VertexId, [f64; 3])>>>
pub fn face_corners( &self, face_id: FaceId, ) -> GeopResult<Vec<Vec<(VertexId, [f64; 3])>>>
The corners of face_id’s boundaries, outer loop first, each its
vertex and where it is — what an error shows of a face, to tell which
curves were imprinted into it, by vertices a named model can name
(names_mentioned).
Sourcepub fn iterate_face_coedges(
&self,
face_id: FaceId,
) -> impl Iterator<Item = CoedgeId> + '_
pub fn iterate_face_coedges( &self, face_id: FaceId, ) -> impl Iterator<Item = CoedgeId> + '_
Every coedge of every boundary loop of face_id (its outer loop and
any holes) — a bare Vertex boundary contributes none.
Sourcepub fn face_edges(&self, face_id: FaceId) -> impl Iterator<Item = EdgeId> + '_
pub fn face_edges(&self, face_id: FaceId) -> impl Iterator<Item = EdgeId> + '_
The edge of every edge-backed coedge of face_id’s boundaries, in
loop order, outer loop first: a seam the face runs along twice comes
twice.
Sourcepub fn edges_at_vertex(&self, vertex: VertexId) -> Vec<EdgeId>
pub fn edges_at_vertex(&self, vertex: VertexId) -> Vec<EdgeId>
The edges ending at vertex, sorted by id.
Sourcepub fn faces_at_vertex(&self, vertex: VertexId) -> GeopResult<Vec<FaceId>>
pub fn faces_at_vertex(&self, vertex: VertexId) -> GeopResult<Vec<FaceId>>
The faces whose boundaries run through vertex (along an edge
starting there), sorted by id.
Sourcepub fn faces_of_edge(&self, edge: EdgeId) -> Option<[FaceId; 2]>
pub fn faces_of_edge(&self, edge: EdgeId) -> Option<[FaceId; 2]>
The two faces of edge: the one its forward coedge bounds (on the
edge’s left) first, the one running against it second. None
unless the edge joins two faces.
Sourcepub fn iter_body_vertices(
&self,
body: impl Into<Body>,
) -> GeopResult<impl Iterator<Item = VertexId> + '_>
pub fn iter_body_vertices( &self, body: impl Into<Body>, ) -> GeopResult<impl Iterator<Item = VertexId> + '_>
The distinct vertices referenced by body’s faces. Borrows self
for as long as it lives, so a caller holding one cannot also call a
&mut self method like merge_vertex on the same model: the borrow
checker, not the caller, keeps it from going stale.
Sourcepub fn iter_body_edges(
&self,
body: impl Into<Body>,
) -> GeopResult<impl Iterator<Item = EdgeId> + '_>
pub fn iter_body_edges( &self, body: impl Into<Body>, ) -> GeopResult<impl Iterator<Item = EdgeId> + '_>
The distinct edges referenced by body’s faces, borrowing self
like Self::iter_body_vertices.
Sourcepub fn find_boundary_containing(
&self,
face_id: FaceId,
coedge: CoedgeId,
) -> GeopResult<BoundaryIndex>
pub fn find_boundary_containing( &self, face_id: FaceId, coedge: CoedgeId, ) -> GeopResult<BoundaryIndex>
Which of face_id’s boundaries contains coedge — its outer loop or
one of its holes — found by traversing each Loop boundary’s .next
chain. A face’s boundaries are disjoint loops, so at most one can
contain any given coedge.
Returns BoundaryIndex rather than a bare position, because callers
invariably need to know whether they found the outer loop: joining two
holes, joining a hole to the outer loop, and joining two points of the
outer loop are three different restructurings (see
Model::splice_edge_into_face).
Sourcepub fn remove_boundary(
&mut self,
face_id: FaceId,
index: BoundaryIndex,
) -> GeopResult<()>
pub fn remove_boundary( &mut self, face_id: FaceId, index: BoundaryIndex, ) -> GeopResult<()>
Drop the hole index names from face_id.
Errors on BoundaryIndex::Outer: a face without an outer boundary
is not a face. An operation that genuinely consumes a face’s outer
loop is deleting the face, and must say so rather than leaving one
behind with nothing bounding it.
Sourcepub fn solid_faces(&self, solid_id: SolidId) -> GeopResult<Vec<FaceId>>
pub fn solid_faces(&self, solid_id: SolidId) -> GeopResult<Vec<FaceId>>
Every FaceId across every shell of solid_id.
Sourcepub fn body_faces(&self, body: impl Into<Body>) -> GeopResult<Vec<FaceId>>
pub fn body_faces(&self, body: impl Into<Body>) -> GeopResult<Vec<FaceId>>
Every FaceId of body: across every shell of a solid, or of the
one shell a sheet is.
Sourcepub fn solid_of_face(&self, face_id: FaceId) -> GeopResult<Option<SolidId>>
pub fn solid_of_face(&self, face_id: FaceId) -> GeopResult<Option<SolidId>>
The solid face_id is part of; None for a face of a sheet.
Sourcepub fn body_of_face(&self, face_id: FaceId) -> GeopResult<Body>
pub fn body_of_face(&self, face_id: FaceId) -> GeopResult<Body>
The body face_id is part of: its shell’s solid, or its shell, if
that is a sheet.
Source§impl Model
impl Model
pub fn new() -> Self
Sourcepub fn next_id(&self) -> u64
pub fn next_id(&self) -> u64
The id the next entity it creates gets: what a model written to be
read back as it was keeps with its entities (Model::with_next_id).
Sourcepub fn with_next_id(self, next_id: u64) -> Self
pub fn with_next_id(self, next_id: u64) -> Self
It, creating entities from the id next_id on: a model read back as
it was written, its entities set as they were. next_id is past
every id it holds, as Model::next_id gave it.
Source§impl Model
impl Model
Sourcepub fn wire_chains(&self, wire: WireId) -> GeopResult<Vec<Vec<(EdgeId, Sense)>>>
pub fn wire_chains(&self, wire: WireId) -> GeopResult<Vec<Vec<(EdgeId, Sense)>>>
The edges of the wire wire in chains: each a run of edges, in
order, each starting where the one before ends, with the sense it
runs in along its chain — open between two vertices where the run
does not go on through (an end, or a branch), or closed. Ordered by
the smallest edge id each holds, which follows the order the edges
were made in.