-
Notifications
You must be signed in to change notification settings - Fork 0
Building Model Updaters
The updaters of DiGi.Analytical.Building are the bridge between analytical geometry and the analytical model. Geometry operations (splitting, normalisation, boolean operations) work on Shell and Face objects taken out of a BuildingModel; the updaters write the modified geometry back, rebuilding the components and spaces while preserving identity, construction and topology.
Everything on this page lives in DiGi.Analytical.Building/Classes/Updater/. Member-by-member signatures are in the generated DiGi.Analytical.Building.Classes page.
flowchart TD
FLOOR["BuildingModelFloorUpdater"] -->|one call per storey elevation| SPLIT["BuildingModel.TrySplit(elevation, ...)"]
OWN["Own geometry work<br/>GetShells, split, modify"] --> SHELLS
SPLIT --> SHELLS["BuildingModelShellsUpdater"]
SHELLS -->|one per shell| SHELL["BuildingModelShellUpdater"]
SHELL -->|one per face| FACE["BuildingModelFaceUpdater"]
FACE --> MODEL[("BuildingModel<br/>components, spaces, relations")]
A shell taken from BuildingModel.GetShell/GetShells already carries everything the updaters need: the shell holds a GuidReference of its space and every face holds a GuidReference of the component it was built from. Geometry code may split, merge or reorder those faces — as long as the references survive, the updaters can put the model back together.
| Class | Works on | Key inputs | Result |
|---|---|---|---|
BuildingModelUpdater |
— |
Value (the model, modified in place) |
abstract base; Update() returns false instead of throwing |
BuildingModelFaceUpdater |
one Face
|
Face, Guid
|
one component created or rebuilt |
BuildingModelShellUpdater |
one Shell
|
Shell, Tolerance, the three shared caches |
one space with all its components |
BuildingModelShellsUpdater |
many Shells |
Shells, Tolerance
|
all spaces, with components shared between them handled once |
BuildingModelFloorUpdater |
whole model |
FloorCount, FloorConstruction, MinHeight
|
model sliced into storeys of equal height |
A face resolves its source component through its UniqueReference. When one component was split into several faces:
- the first face processed for that reference rebuilds the component under its own identifier, so it replaces the original in the model (and keeps its relations);
- every further face becomes an additional component created under a new identifier, of the same type and with the same construction.
BuildingModelFaceUpdater.Guid is what selects between the two: null inherits, a value creates. BuildingModelShellUpdater sets it automatically from its fragment cache.
The construction of the source component is read (GetWallConstruction, GetRoofConstruction, GetFloorConstruction) and re-assigned to every component created from it, so all fragments of a split wall share the wall construction of the original wall.
The rebuilt component keeps the family of its source: IWall → SurfaceWall, IFloor → FaceFloor, IRoof → SurfaceRoof, IAir → SurfaceAir. A face whose reference resolves to nothing — a face created on a cutting plane, for example — becomes a new SurfaceAir.
The shell reference selects the space. When it resolves to a space of the model, that space is reused; when it does not, a new Space is created at the internal point of the shell. Component-to-space assignments are then re-derived from the shells, not accumulated: an assignment to a space that is part of the same update is replaced, an assignment to a space outside the update is preserved. That is what keeps a neighbouring, untouched space attached to a wall that was split from the other side.
BuildingModelShellUpdater exposes three properties that BuildingModelShellsUpdater fills in and shares between all its shells:
| Property | Purpose |
|---|---|
UpdatedComponents |
fragment cache, keyed by source reference and by the internal point of the face. A component bounding two spaces is created once and reused from the second shell instead of being duplicated. Also decides which fragment inherits the source identifier. |
UpdatedComponentSpaces |
spaces already assigned to a component during this update, so a component bounding two spaces keeps both assignments. |
ProcessedSpaceGuids |
identifiers of the spaces rebuilt by this update. Must be complete before the first shell is processed — BuildingModelShellsUpdater seeds it from all its shells, which is what makes the outcome independent of shell order. |
Updating shells one by one with your own BuildingModelShellUpdater instances therefore only works correctly if you share these three objects between them; using BuildingModelShellsUpdater does it for you.
The common case does not need the updaters directly — Modify.TrySplit drives them:
BuildingModel buildingModel = ...;
FloorConstruction floorConstruction = new();
// Every space crossed by z = 5 becomes a lower and an upper space,
// bounding components are rebuilt, a floor is created on the cutting plane
bool split = buildingModel.TrySplit(5, minHeight: 1, floorConstruction: floorConstruction);BuildingModelFloorUpdater buildingModelFloorUpdater = new(buildingModel)
{
FloorCount = 3,
FloorConstruction = floorConstruction
};
bool updated = buildingModelFloorUpdater.Update();When the geometry was produced outside the model — by a solver, a boolean operation, an import — feed the shells in directly:
List<Shell>? shells = buildingModel.GetShells<ISpace>();
// ... modify the shells, keeping the references of the shells and of their faces ...
BuildingModelShellsUpdater buildingModelShellsUpdater = new(buildingModel)
{
Shells = shells,
Tolerance = Core.Constants.Tolerance.Distance
};
if (buildingModelShellsUpdater.Update(out List<ISpace>? spaces))
{
// spaces holds the space of every updated shell
}Faces you add yourself get their reference from what you want to happen: keep the reference of an existing component to rebuild it, set the reference of another component to reuse that one, leave it null to create a new air component.
DiGi.Analytical also ships a solver family (ShellByPlaneSplitSolver, FaceByFacesSplitSolver, …). The difference is what they touch:
-
Solvers work on analytical geometry only. They raise
FaceSplit/ShellSplitevents letting a handler assign theIUniqueReferenceof each result, and they never modify a building model. - Updaters take geometry that already carries references and write it into a building model.
The two compose: solve first, then update. BuildingModelFloorUpdater and Modify.TrySplit are ready-made compositions of exactly that shape.
-
Openings are not re-hosted. Windows and doors hosted by a component that is split stay assigned to the fragment that inherits the identifier of the original component; they are not moved to the fragment that geometrically contains them. Re-hosting needs a public counterpart of the currently private
BuildingModel.Assign(IComponent, IOpening). -
IUniqueReferencecompared with==is a reference comparison. The equality operators are declared onSerializableReferenceand do not apply to interface-typed operands, and accessors such asFace.UniqueReferencereturn a fresh clone on every call. Always compare withEquals(null-guarded) or rely on aDictionary/HashSet, which useEquals/GetHashCodecorrectly. This bug caused a non-terminating loop inBuildingModelShellUpdaterand, at the time of writing, is still present inShellByPlaneSplitSolver. -
A shell needs at least four faces.
Polyhedron<T>silently discards shorter face lists, so a degenerate part of a split simply disappears instead of producing an invalid space. -
Components with a non-polygonal geometry are skipped when shells are built (
GetShells), so they are neither split nor rebuilt.
-
DiGi.Analytical.Building.Classes — generated API reference for the updaters and
BuildingModel. -
DiGi.Analytical.Classes —
Shell,Faceand the split solvers they are used with.