pub struct SpacePlate { /* private fields */ }Expand description
The persistent floor-plate topology. See the module docs.
Implementations§
Source§impl SpacePlate
impl SpacePlate
Sourcepub fn prune_orphans(&mut self) -> usize
pub fn prune_orphans(&mut self) -> usize
Remove all orphaned cruft the wall arrangement leaves behind: dangling spur walls (degree-1 chains), isolated vertices, and redundant collinear degree-2 nodes. Idempotent, and never changes a room’s area (spurs bound no room; collinear dissolve only straightens a node already on its chord). Returns how many topology elements were pruned.
Sourcepub fn remove_edge(
&mut self,
edge: HalfEdgeId,
) -> Result<Vec<FacePatch>, EditError>
pub fn remove_edge( &mut self, edge: HalfEdgeId, ) -> Result<Vec<FacePatch>, EditError>
Remove the wall edge, choosing the right semantics from its two
incident faces, and auto-clean the orphans it leaves:
- room ↔ room → union the two rooms (
merge_faces); - bridge (same face both sides) → delete it, give the cycle it cut off
its own face (
split_bridge_cycles), thenprune_orphans; - outer ↔ outer → delete it +
prune_orphans; - room ↔ outer (a real enclosing wall) →
BordersExterior(don’t open a room).
Source§impl SpacePlate
impl SpacePlate
Sourcepub fn build(segments: &[InputSegment], options: BuildOptions) -> Self
pub fn build(segments: &[InputSegment], options: BuildOptions) -> Self
Build the plate from tagged wall-axis segments.
Pipeline (ported from auto-space-detect.ts, provenance added):
- snap endpoints onto a spatial-hash grid,
- snap dangling endpoints onto nearby edge interiors (T-junctions),
- resolve interior crossings, splitting both hosts,
- dedupe undirected edges,
- build the half-edge graph with angle-sorted vertex fans,
- assign
next/prevby the leftmost-turn rule and walk faces, - flag CW cycles as exterior and drop sub-
min_arearooms.
Sourcepub fn build_from_wall_rects(
rects: &[[[f64; 2]; 4]],
options: BuildOptions,
) -> Self
pub fn build_from_wall_rects( rects: &[[[f64; 2]; 4]], options: BuildOptions, ) -> Self
FACE-BASED build → an editable CENTRELINE plate sitting on the wall axes.
Two stages. (1) DETECT: arrange the wall footprint rectangle edges and keep each bounded face whose centroid lies OUTSIDE every rectangle — i.e. a GAP between walls (not a wall interior or a junction overlap). This locates the rooms accurately, with the room boundary literally on the rendered wall faces (no centroid-derived-centreline drift). (2) LIFT: take each gap room’s wall-AXIS outline (the net gap offset out by ½ thickness, corners re-intersected) and arrange THOSE edges into the returned plate.
The returned plate is therefore a normal centreline plate whose room
outlines ARE the wall axes and whose vertices ARE the displayed nodes — so
every editing op (drag / split / merge / dissolve) acts directly on what the
user sees, with no axis-vs-face offset. Each axis edge carries its wall’s
half-thickness, so net_outline recovers the inner (net) and outer (gross)
faces. Adjacent rooms’ shared wall maps to one shared centreline edge.
Sourcepub fn drag_vertex(
&mut self,
v: VertexId,
x: f64,
y: f64,
) -> Result<Vec<FacePatch>, EditError>
pub fn drag_vertex( &mut self, v: VertexId, x: f64, y: f64, ) -> Result<Vec<FacePatch>, EditError>
Move v to (x, y). Topology is untouched; every incident face is
updated by construction (they all reference this one vertex). Returns
a patch per incident room face — the same call updates a room and
its neighbour across a shared wall.
Sourcepub fn split_face(
&mut self,
face: FaceId,
va: VertexId,
vb: VertexId,
source_element: Option<u32>,
) -> Result<Vec<FacePatch>, EditError>
pub fn split_face( &mut self, face: FaceId, va: VertexId, vb: VertexId, source_element: Option<u32>, ) -> Result<Vec<FacePatch>, EditError>
Subdivide face by inserting a partition edge between two of its
vertices. Returns patches for the kept face and the new face.
The new edge carries source_element (None = a brand-new partition
the user drew, which the bake step materialises as a fresh wall or a
virtual boundary).
Sourcepub fn split_edge(
&mut self,
edge: HalfEdgeId,
x: f64,
y: f64,
) -> Result<VertexId, EditError>
pub fn split_edge( &mut self, edge: HalfEdgeId, x: f64, y: f64, ) -> Result<VertexId, EditError>
Insert a new vertex at (x, y) on the undirected edge edge (and its
twin), subdividing it. No face is created — both incident faces simply
gain a boundary vertex. Returns the new vertex so the caller can use it
as a split_face endpoint. Pass a point on the edge segment to keep
areas unchanged (the caller projects the click onto the edge).
This is what lets the user add a node where there wasn’t a corner, so a partition can start/end mid-wall rather than only at existing vertices.
Sourcepub fn merge_faces(
&mut self,
edge: HalfEdgeId,
) -> Result<Vec<FacePatch>, EditError>
pub fn merge_faces( &mut self, edge: HalfEdgeId, ) -> Result<Vec<FacePatch>, EditError>
Remove the shared edge edge (and its twin), unioning the two rooms
it separated into one. Returns a patch for the surviving face.
Sourcepub fn dissolve_vertex(
&mut self,
v: VertexId,
) -> Result<Vec<FacePatch>, EditError>
pub fn dissolve_vertex( &mut self, v: VertexId, ) -> Result<Vec<FacePatch>, EditError>
Dissolve a degree-2 vertex v, welding its two incident edges into
one straight edge between its neighbours — the inverse of split_edge,
and the “delete this corner / errant node” affordance. Both incident
faces lose a boundary vertex; if v was a real corner the faces change
shape (the edge becomes the straight chord A→B), which is the intended
edit. Returns a patch per incident room face.
Rejects:
- a vertex whose degree isn’t exactly 2 — a wall junction or dangling
tip has no unambiguous edge pair to merge (
VertexNotDissolvable); - a node where the wall thickness changes: the welded edge carries ONE
half_thicknessfor both twins, so a 200 mm wall meeting a 150 mm one end-to-end has no single value to carry (VertexNotDissolvable); - a weld whose two neighbours are already directly joined, which would
make a parallel edge / collapse a triangle to a digon (
DegenerateCut).
Sourcepub fn add_face(
&mut self,
points: &[[f64; 2]],
source_element: Option<u32>,
) -> Result<FacePatch, EditError>
pub fn add_face( &mut self, points: &[[f64; 2]], source_element: Option<u32>, ) -> Result<FacePatch, EditError>
Author a brand-new room from a closed ring of points — the “draw a room” affordance. The polygon becomes its own connected component: a CCW interior room face plus the CW exterior face bounding it. It does NOT merge into existing topology (there’s no arrangement overlay), so drawing over an existing room leaves two independent components — the documented prototype limitation.
points is the ring with no repeated closing vertex; winding is
normalised to CCW. Rejects a ring that is too short, self-intersecting,
or near-zero area (InvalidPolygon). Returns the new room’s patch.
Sourcepub fn neighbor_across(&self, edge: HalfEdgeId) -> Option<FaceId>
pub fn neighbor_across(&self, edge: HalfEdgeId) -> Option<FaceId>
The face on the far side of edge — its twin’s face. O(1). This is
the “who’s my neighbour across this wall” query the UX leans on.
Sourcepub fn rooms(&self) -> impl Iterator<Item = FaceId> + '_
pub fn rooms(&self) -> impl Iterator<Item = FaceId> + '_
Every live interior (room) face. In the FACE-BASED build (wall_rects
non-empty) a bounded face is a room only if it’s a gap between walls — its
centroid lies outside every wall rectangle, so wall interiors and junction
overlaps are excluded.
Sourcepub fn face_outline(&self, face: FaceId) -> Vec<[f64; 2]>
pub fn face_outline(&self, face: FaceId) -> Vec<[f64; 2]>
CCW outline of a face (no repeated closing vertex).
Sourcepub fn net_outline(&self, face: FaceId, inset: bool) -> Vec<[f64; 2]>
pub fn net_outline(&self, face: FaceId, inset: bool) -> Vec<[f64; 2]>
The room’s outline offset to a wall boundary face rather than the
centreline: each boundary edge is moved perpendicular by its own wall’s
half-thickness — inward for the net (inner) face, outward for the gross
(outer) face — then adjacent offset lines are re-intersected for the
corners. Because every half-edge carries its source wall’s thickness,
this needs no fuzzy edge↔wall matching (unlike the TS offsetRoomFootprint).
An edge shared with another room (its twin bounds a room, not the exterior) is pinned to the centreline in outward mode so it can’t push into the neighbour. Falls back to the unchanged centreline outline when no offset applies, a corner is degenerate, or an inset would invert the polygon — so the result is always a sane ring.
Sourcepub fn gap_boundary(&self, face: FaceId, factor: f64) -> Vec<[f64; 2]>
pub fn gap_boundary(&self, face: FaceId, factor: f64) -> Vec<[f64; 2]>
FACE-BASED boundary of a gap room: the gap outline IS the net (inner-face)
area, so this pushes every edge OUTWARD (into the wall, away from the room)
by factor × the source wall's half-thickness, then re-intersects corners.
factor = 0 → net (the gap itself); 1 → the wall axis / centre line
(½ thickness — where the editable node sits, on the wall mid); 2 → the
gross outer face (full thickness). No shared-edge pinning: two rooms across
a wall correctly meet at the mid axis and overlap into it for gross.
Falls back to the un-offset net ring when the offset cannot be built;
try_gap_boundary is the same computation with that case as None.
Sourcepub fn bounding_elements(&self, face: FaceId) -> Vec<(HalfEdgeId, Option<u32>)>
pub fn bounding_elements(&self, face: FaceId) -> Vec<(HalfEdgeId, Option<u32>)>
The bounding half-edges of a face paired with the IFC element each
came from — the raw material for IfcRelSpaceBoundary at bake.
Sourcepub fn set_face_height(
&mut self,
face: FaceId,
floor_z: f64,
ceiling_z: f64,
non_planar_ceiling: bool,
)
pub fn set_face_height( &mut self, face: FaceId, floor_z: f64, ceiling_z: f64, non_planar_ceiling: bool, )
Set the floor / ceiling planes of a face (the vertical dimension that turns a 2D face into a prismatic space at bake).
pub fn vertex_position(&self, v: VertexId) -> Option<[f64; 2]>
Sourcepub fn room_count(&self) -> usize
pub fn room_count(&self) -> usize
Count of live rooms — handy for tests / UI summaries.
Sourcepub fn find_vertex_near(&self, x: f64, y: f64, tol: f64) -> Option<VertexId>
pub fn find_vertex_near(&self, x: f64, y: f64, tol: f64) -> Option<VertexId>
Nearest live vertex to (x, y) within tol, for hit-testing a drag
target from the TS side. Linear scan — a floor plate is small.
Sourcepub fn room_patches(&self) -> Vec<FacePatch>
pub fn room_patches(&self) -> Vec<FacePatch>
Snapshot every live room as a patch (outline + area + simple flag) — for bulk render or seeding a fresh TS mirror after a rebuild.
Trait Implementations§
Source§impl Clone for SpacePlate
impl Clone for SpacePlate
Source§fn clone(&self) -> SpacePlate
fn clone(&self) -> SpacePlate
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl Freeze for SpacePlate
impl RefUnwindSafe for SpacePlate
impl Send for SpacePlate
impl Sync for SpacePlate
impl Unpin for SpacePlate
impl UnwindSafe for SpacePlate
Blanket Implementations§
§impl<U> As for U
impl<U> As for U
§fn as_<T>(self) -> Twhere
T: CastFrom<U>,
fn as_<T>(self) -> Twhere
T: CastFrom<U>,
self to type T. The semantics of numeric casting with the as operator are followed, so <T as As>::as_::<U> can be used in the same way as T as U for numeric conversions. Read moreSource§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Pointable for T
impl<T> Pointable for T
§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read more§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.