Skip to main content

Model

Struct Model 

Source
pub struct Model { /* private fields */ }
Expand description

A document: topology, placements, geometry, and where every entity came from.

Implementations§

Source§

impl Model

Source

pub fn new() -> Self

An empty model, in millimetres.

Source

pub fn with_tolerances(tolerances: Tolerances) -> Self

An empty model at a given unit scale.

A document has a scale, and it is the document’s rather than each call’s: a model authored in metres does not become a model in millimetres because one caller passed the default. Algorithms still take a Tolerances argument (that is deliberate, since a caller may want to work coarser or finer than the document’s own setting for one operation), but the document says what it was built at, so a mismatch is visible rather than assumed away.

Source

pub const fn tolerances(&self) -> Tolerances

The tolerances this document was built at.

Source

pub fn from_parts(parts: ModelParts) -> OgeomResult<Self>

Assemble a model from parts read back from a file.

The one way into a Model that does not go through its builders, and it exists for one reason: a builder mints an identity for every node it makes (docs/DATA_MODEL.md §8). A document rebuilt through the builders is therefore a different document from the one that was written (every EntityId renumbered, every provenance record replaced by a fresh Primitive one), and every reference into it, which is the thing provenance exists to keep alive, is dead. Reading a file has to reproduce the document it describes, identities and all.

This is not a hole in “the builder is the sole mutation path”. Nothing here mutates an existing model; it assembles a new one, and it checks the structural invariants the builders check before handing it back, so a corrupt file is an error, not a model that answers wrongly.

§Errors

OgeomError::Dangling if a node names a child, a datum, a piece of geometry or an identity that is not there; OgeomError::Construction if a node’s data does not match its kind, or a child is of the wrong kind for its parent.

Source

pub fn absorb( &mut self, parts: ModelParts, roots: &[Shape], ) -> OgeomResult<Absorbed>

Absorb another document’s parts into this model.

Model::from_parts lands a document in a fresh model; this lands one in a model that already has things in it: the operation behind bringing a serialized tool body into a live document so a boolean can use it. Everything the parts carry is appended: nodes, datums, geometry, provenance and identities all keep their relative structure, shifted past what the model already holds. The result behaves exactly as if it had been built here, because after the shift it is indistinguishable from having been.

roots are the shapes the source document named, in the unbound state a reader leaves them; they come back bound to this model. The returned Absorbed::entities table says where every source identity landed, which is what a caller holding references against the source document resolves them through.

Three deliberate refusals, each an error rather than a guess:

  • Units. A document authored at another scale is refused, not rescaled; rescaling is a real feature with real decisions in it, and silently absorbing metres into millimetres is a wrong model.
  • Bound handles. Parts whose handles already name an arena did not come from a reader; absorbing them would alias whatever those handles meant elsewhere. Serialization is the one road in.
  • A model that reuses slots. Absorbing appends by offset, which is only sound while each of the target’s arenas hands out fresh slots in order. Model::retain_reachable leaves slots empty and never hands them out again, so a retained model absorbs; an arena waiting to refill a freed slot is refused, and the check is what keeps that refill from becoming aliasing.

The current operation is left alone: absorb mints no identities, it transplants a table, and the absorbed provenance keeps its source OpIds verbatim, meaningful in the source document’s rebuild, kept because renumbering them would orphan the source’s own references.

§Errors

OgeomError::Construction for the three refusals above; OgeomError::Dangling if the parts do not describe themselves: a handle that does not resolve, a derivation from an identity no entry issued, a root naming a node that is not there.

On an error past the up-front gates, the model’s prior contents are untouched and still fully usable: an absorbed subgraph is self-contained (its shifted handles cannot reach below the append line), and no identity is committed until every check has passed. What a failed absorb can leave behind is unreachable appended entries, which cost memory and mean nothing.

Source

pub fn bind(&self, shape: &Shape) -> OgeomResult<Shape>

Bind an unscoped shape to this model.

A handle rebuilt by a reader names no arena, so it resolves nowhere until it is told which document it belongs to. This is how a reader says so, and it verifies the answer, so a file naming a node that is not there is an error rather than a shape that fails mysteriously later.

It will not re-home a shape that already belongs to another model. That is exactly the mistake scoping exists to catch, and quietly relabelling it would hand back a shape that resolves and answers about the wrong entity.

§Errors

OgeomError::Construction if the shape already belongs to a different model; OgeomError::Dangling if it does not resolve here once bound.

Source

pub fn bind_location(&self, location: &Location) -> OgeomResult<Location>

Bind a bare location to this model’s datum store.

The persistence path’s sibling of Model::bind: a location read from a file names datum handles that are unscoped until the store that holds them exists. Binding checks them too: a chain naming a datum not in this model is an error here rather than wherever it is first resolved.

§Errors

OgeomError::Dangling if the chain names a datum not in this model.

Source

pub const fn begin_operation(&mut self) -> OpId

Begin a new operation, and return its identifier.

Every node created from here on is attributed to it until the next call. The counter is deterministic (the third operation in a rebuild is OpId(3) every time), which is what lets provenance survive a parameter change (docs/DATA_MODEL.md §8).

Source

pub const fn current_operation(&self) -> OpId

The operation nodes are currently attributed to.

Source

pub fn identity_of(&self, shape: &Shape) -> Option<EntityId>

The stable identity of a shape’s node.

Distinct from its arena handle: the handle says where the data is and dies when the shape is rebuilt, while this says what the entity is and survives.

Source

pub fn provenance_of(&self, shape: &Shape) -> Option<&Provenance>

Where a shape’s node came from.

Source

pub const fn provenance(&self) -> &ProvenanceTable

The provenance table.

Source

pub fn forget_provenance(&mut self, ids: impl IntoIterator<Item = EntityId>)

Drop the provenance entries of ids, keeping the ids issued.

What a document reader does with the entities its file left out: no shape the file carries names them and no kept entry derives from them, so they answer None as an id from another document does, and no later entity can take their numbers. Ids without an entry are passed over.

Source

pub fn retain_reachable(&mut self, roots: &[Shape]) -> OgeomResult<()>

Drop everything roots do not reach, in place.

Kept: every node below a root, the curves, pcurves, surfaces and triangulations those nodes name, and the provenance entries of the identities they carry with every entry those derive from. Every handle into what is kept resolves as before and means the same thing, and a lineage query on a kept shape answers as before. A handle into what is dropped fails to resolve, and no later entity takes its slot or its id, so it never answers about something else. Datums are kept whole: a placement a caller holds keeps resolving.

What is dropped stops costing memory, and a clone costs what is kept. For a model that runs many operations and keeps one result: call it with that result once the model has grown well past what the result reaches.

§Errors

OgeomError::Dangling if a root does not resolve in this model; nothing is dropped then.

Source

pub fn roots_of(&self, shape: &Shape) -> Vec<EntityId>

Trace a shape back to the entities it ultimately came from.

How a reference into a rebuilt model is resolved: find what the user originally picked, then find what that became.

Source

pub fn shape_of(&self, id: EntityId) -> Option<Shape>

The shape carrying a given identity, if this document has one.

The inverse of Model::identity_of, and the answer to “I kept a reference and the document has been saved and reloaded since”. A raw Shape cannot survive that: the reloaded document is a new set of arenas and Model::bind refuses a handle from another one, on purpose. An EntityId can, because it names what the entity is rather than where it sits (docs/DATA_MODEL.md §8), and that is the whole reason it exists.

Returns the shape in its default placement and orientation. A caller that wants a particular occurrence explores from here.

Source

pub fn set_derived( &mut self, shape: &Shape, from: &[Shape], role: Role, ) -> OgeomResult<EntityId>

Record that a node was derived from other entities.

Overwrites the Primitive attribution a builder assigns by default. An operation that splits or reshapes existing topology calls this, and what it records is what a later rebuild will match against.

§Errors

OgeomError::Dangling if the shape does not resolve in this model.

Source

pub const fn datums(&self) -> &DatumStore

The placement datums.

Source

pub const fn geometry(&self) -> &GeometryStore

The geometry.

Source

pub const fn geometry_mut(&mut self) -> &mut GeometryStore

Mutable access to the geometry, for adding curves and surfaces.

A surface rewritten in place through it forgets every face’s kept box (Model::face_bounds).

Source

pub fn add_datum(&mut self, transform: Transform) -> DatumId

Intern a transform for use in placements.

Source

pub fn node(&self, shape: &Shape) -> Option<&TShape>

The node behind a shape’s handle.

Source

pub fn node_by_id(&self, id: TShapeId) -> Option<&TShape>

The node behind a handle.

Source

pub fn node_mut(&mut self, shape: &Shape) -> Option<&mut TShape>

Mutable access to the node behind a shape’s handle.

For attaching geometry to an entity that already exists: a pcurve joining an edge to a face it has just come to bound. Structural change still goes through the builders; this reaches the node’s data, which no invariant here constrains on its own.

The node may be changed in any way, so the box kept for every face it is part of (Model::face_bounds) is forgotten, and so is what is kept of every shape holding it (Model::kept_read). Every shape holding the node sees the change; an editor starting from a shape it was given copies what other shapes hold first (Model::unshare).

Source

pub fn face_bounds(&self, face: &Shape) -> Option<Aabb>

The box kept for a face: every point of the face, its tolerance included, in the face’s placement. None where face is not a face of this model or no box has been kept for it yet; a kept box is found by ogeom_algo::face_bounds, and by the operations that read face boxes, and is kept until the face, its wires, edges or vertices, or the geometry in place change.

The box is kept in the face’s own frame. Under a placement that turns the face off the axes it is the box of the turned box, which holds the face but stands clear of it.

Source

pub fn kept_face_box( &self, face: &Shape, find: impl FnOnce(&Shape) -> OgeomResult<Aabb>, ) -> OgeomResult<Aabb>

The box kept for a face in its own frame, without its placement or its tolerance; where none is kept, find is asked for it with the face unplaced, and its answer is kept.

find must hold every point of the face it is given, and is what Model::face_bounds reports from then on. Where the face cannot keep a box (the geometry is being rewritten), find’s answer is returned and not kept.

§Errors

OgeomError::Construction if face is not a face of this model; as find reports.

Source

pub fn kept_read<T: Any + Send + Sync>( &self, shape: &Shape, same: impl Fn(&T) -> bool, read: impl FnOnce() -> OgeomResult<(T, bool)>, ) -> OgeomResult<Arc<T>>

What read finds of shape, kept beside the model and handed out again until anything it was read from changes.

A read of type T kept for shape that same accepts is returned as it is; otherwise read is asked, and its answer kept where it says to keep it. read must depend only on what lies below shape (the nodes’ data and children, the geometry and placements they name, the face boxes kept), and same must accept only a read made with the inputs the caller would give read now.

What is kept for a shape is forgotten when a node below it is changed (Model::node_mut, Model::widen growing a tolerance, Model::undo_widened, Model::unshare repointing a child), when a surface or a pcurve is rewritten in place, and when Model::retain_reachable drops the shape. A few shapes keep their reads at once, the oldest forgotten first. Keeping walks every node below the shape once, so it pays for a read that costs more than that walk.

§Errors

As read reports.

Source

pub fn kept_reads<T: Any + Send + Sync>( &self, same: impl Fn(&T) -> bool, ) -> Vec<Arc<T>>

Every read of type T kept now, for any shape, that same accepts, the shape kept latest first: what Model::kept_read would hand back for its shape. Each was read from its shape’s nodes as they stand, so a part of one that depends only on the nodes below a face holds for that face in any shape holding it.

Source

pub fn kind_of(&self, shape: &Shape) -> OgeomResult<ShapeType>

What kind of shape this is.

§Errors

OgeomError::Dangling if the handle does not resolve in this model.

Source

pub fn tolerance_of(&self, shape: &Shape) -> OgeomResult<Option<Tolerance>>

The tolerance a shape carries, if it carries one.

§Errors

As Model::kind_of.

Source

pub fn node_count(&self) -> usize

Number of topology nodes.

Source

pub fn is_empty(&self) -> bool

Whether the model holds no topology.

Source

pub fn nodes(&self) -> impl Iterator<Item = (TShapeId, &TShape)>

Every topology node, with its handle, in arena order.

For writing a document out. Traversal from a root shape reaches only what that root bounds; a document is everything in it.

Source

pub fn identities(&self) -> impl Iterator<Item = (TShapeId, EntityId)>

Every node that has been given an identity, with it.

Source

pub fn add_vertex(&mut self, data: VertexData) -> Shape

Add a vertex.

Source

pub fn add_point(&mut self, point: Point) -> Shape

Add a vertex at point with the minimum tolerance.

Source

pub fn add_edge( &mut self, data: EdgeData, bounds: &[Shape], ) -> OgeomResult<Shape>

Add an edge bounded by the given vertices.

The vertices are the edge’s ends, in order. A closed edge (a full circle) names the same vertex twice rather than once, so that walking its boundary yields a start and an end as every other edge does.

§Errors

OgeomError::Construction if a bound is not a vertex, or if there are more than two; OgeomError::Invariant if a vertex’s tolerance is tighter than the edge’s, breaking the containment rule.

Source

pub fn add_wire(&mut self, edges: &[Shape]) -> OgeomResult<Shape>

Add a wire from a sequence of edges.

§Errors

OgeomError::Construction if a child is not an edge, or the wire is empty.

Source

pub fn add_face( &mut self, data: FaceData, wires: &[Shape], ) -> OgeomResult<Shape>

Add a face bounded by the given wires.

The first wire is the outer boundary and any others are holes, and the face keeps them in that order whichever way it is later used; see Model::outer_wire.

A face with no wires covers its surface’s whole domain, and is recorded as naturally restricted.

§Errors

OgeomError::Construction if a bound is not a wire; OgeomError::Invariant if the containment rule is broken.

Source

pub fn rechart_face( &mut self, face: &Shape, surface: SurfaceId, wires: &[Shape], ) -> OgeomResult<()>

Restate face in place on surface, bounded by wires: the same face read on another chart of the same points, held by every shape that held it. For a builder that has re-charted a face (a sphere face turned so its poles stand clear of its trim) and whose shells, documents and tables already name the face: none of them has to learn a new one.

The face’s kept triangulation and box are forgotten, as is what is kept of every shape holding it.

§Errors

OgeomError::Construction if face is not a face, a wire is not a wire, or wires is empty; OgeomError::Dangling if face is not in this model.

Source

pub fn add_shell(&mut self, faces: &[Shape]) -> OgeomResult<Shape>

Add a shell from a set of faces.

§Errors

OgeomError::Construction if a child is not a face, or the shell is empty.

Source

pub fn add_solid(&mut self, shells: &[Shape]) -> OgeomResult<Shape>

Add a solid bounded by the given shells.

§Errors

OgeomError::Construction if a child is not a shell, or the solid is empty.

Source

pub fn add_compsolid(&mut self, solids: &[Shape]) -> OgeomResult<Shape>

Add a compsolid from solids sharing faces.

§Errors

OgeomError::Construction if a child is not a solid, or it is empty.

Source

pub fn add_compound(&mut self, shapes: &[Shape]) -> OgeomResult<Shape>

Add a compound of arbitrary shapes.

The one container with no type constraint; that is what a compound is for. It may be empty, since an empty result is a legitimate answer from a boolean and needs somewhere to live.

§Errors

OgeomError::Dangling if a child does not resolve in this model.

Source

pub fn children_of(&self, shape: &Shape) -> OgeomResult<Vec<Shape>>

The direct children of a shape, with this shape’s placement and orientation composed onto each.

The reason traversal is correct by default rather than by discipline: a child’s placement in the world is its parent’s composed with its own, and its orientation is its parent’s composed with its own. Returning raw children would leave every caller to remember both, and the failure is silent: face normals that flip inconsistently, sub-shapes drawn at the origin.

§Errors

OgeomError::Dangling if the shape does not resolve in this model.

Source

pub fn ordered_children_of(&self, shape: &Shape) -> OgeomResult<Vec<Shape>>

A shape’s children in traversal order.

The same shapes as Model::children_of, but with the list reversed when the parent is reversed. Order carries meaning for a wire (its edges run head to tail), and reversing a wire has to reverse the walk as well as each edge, or consecutive edges stop sharing a vertex and the boundary comes apart. For a shell or a solid the order means nothing and the reversal is invisible.

Model::children_of stays the raw accessor: it returns what is stored, which is what a rebuild or a comparison wants.

A reversed face’s wires come back reversed too, so its holes are listed before its outer wire. A face stores its outer wire first, and that is the only place the order means anything: code that wants the outer wire asks Model::outer_wire, and code that wants every wire walked under the face’s sense, outer first, asks Model::children_of, whose wires carry the face’s orientation in stored order.

§Errors

OgeomError::Dangling if the shape does not resolve in this model.

Source

pub fn outer_wire(&self, face: &Shape) -> OgeomResult<Option<Shape>>

A face’s outer wire, carrying the face’s placement and orientation, or None for a face with no wires (one covering its whole surface).

The outer wire is the first one the face stores, whichever way the face is used. Model::ordered_children_of on a reversed face lists the holes first, so its first wire is not this one.

§Errors

OgeomError::Dangling if the face does not resolve in this model; OgeomError::Construction if the shape is not a face.

Source

pub fn widen(&mut self, shape: &Shape, to: Tolerance) -> OgeomResult<()>

Widen a shape’s tolerance, and every sub-shape’s with it.

The cascade is the point. The containment rule is transitive: a face’s edges must be no tighter than the face, and those edges’ vertices no tighter than the edges. Widening only one level leaves the rule broken two levels down, where nothing will notice until a containment test quietly answers about geometry that does not meet.

Tolerances only ever grow, so this is the sanctioned repair: raise what bounds, never lower what is bounded.

The nodes are widened where they stand, held by other shapes as well or not (Model::note_held): a boolean widens a vertex its rebuilt edges end on though a face it set aside, and so the operand, holds it too. Only a tolerance grows: every shape holding the node keeps its geometry and its pcurves, and a looser bound still bounds what it bounded, so each stays valid. An editor starting from a shape it was given copies what is held first (Model::unshare), and widens no other shape.

§Errors

OgeomError::Dangling if the shape, or anything below it, does not resolve in this model.

Source

pub fn note_widened(&mut self) -> WidenMark

Start noting the tolerances Model::widen grows and edits through Model::node_mut may grow, so an attempt that fails can put them back with Model::undo_widened. Where a journal is open already, it goes on and the mark is where this attempt began.

Source

pub fn undo_widened(&mut self, mark: WidenMark)

Put back every tolerance noted since mark, and close the journal where mark opened it.

Source

pub fn keep_widened(&mut self, mark: WidenMark)

Keep the tolerances noted since mark as they now stand, and close the journal where mark opened it.

Source

pub fn note_held(&mut self, result: &Shape, since: usize) -> OgeomResult<()>

Record that result holds nodes made before the model held since nodes: what an operation passed through into its result from the shapes it was given, which those shapes still hold.

The walk goes down from result through the nodes made since, and marks each older node where it first meets one; what lies below a marked node is held with it. It costs what the operation made. Model::unshare reads the marks.

§Errors

OgeomError::Dangling if the shape, or anything below it, does not resolve in this model.

Source

pub fn unshare( &mut self, root: &Shape, ) -> OgeomResult<Vec<(TShapeId, TShapeId)>>

Make every node below root the root’s alone, so that an edit in place below it changes no other shape.

Each node an operation passed through into root from the shapes it was given (Model::note_held), and everything below it, is copied: the same kind, data and identity, and the same kept face box. The nodes root holds alone that held one now hold its copy, at the same placement and orientation. What is held is found from root down; a root that is itself held is left as it stands, with everything below it.

Returns each node copied, with its copy, in the order copied.

§Errors

OgeomError::Dangling if the shape, or anything below it, does not resolve in this model.

Source

pub fn unshare_each(&mut self, shapes: &[Shape]) -> OgeomResult<Unshared>

As Model::unshare for several shapes at once, for an operation that takes shapes and returns what it makes of them, and leaves the shapes it was given as they are.

Nothing given is edited: each node held below a shape, and each node on the way down to one, the shape itself included, is copied once for them all, so a shape with anything held below it comes back as a copy. What a copy shares with the shape it was copied from is noted as held by both (Model::note_held).

§Errors

As Model::unshare.

Source

pub fn check_tolerances(&self, root: &Shape) -> OgeomResult<()>

Verify the containment rule across a whole shape tree.

docs/DATA_MODEL.md §5. Walks parent to child and checks that whatever bounds is no tighter than what it bounds.

§Errors

OgeomError::Invariant at the first violation, naming the two shape types involved.

Source

pub fn same_position( &self, a: &Shape, b: &Shape, tol: Tolerances, ) -> OgeomResult<bool>

Whether two shapes coincide in position, comparing composed transforms.

§Errors

As Location::composed.

Source

pub fn placed(&mut self, shape: &Shape, transform: Transform) -> Shape

A shape placed by an additional transform.

Interns the transform and composes it onto the shape’s placement, so the underlying node (and all its geometry) is shared rather than copied. Placing ten thousand instances of a part costs ten thousand short chains and one copy of the geometry.

Records no history: nothing is built, and a sub-shape s of the input stands in the result as s.moved(by), where by is the result’s location with the input’s own taken off, result.location().then(&shape.location().inverted()). A caller that traces names across the move, or composes it with an operation’s history, places with ogeom_algo::transformed, which makes the same placement and records every sub-shape into its occurrence.

Trait Implementations§

Source§

impl Clone for Model

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Model

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Model

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl !Freeze for Model

§

impl RefUnwindSafe for Model

§

impl Send for Model

§

impl Sync for Model

§

impl Unpin for Model

§

impl UnsafeUnpin for Model

§

impl UnwindSafe for Model

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.