Skip to content

Building Model Updaters

Jakub Ziolkowski edited this page Jul 22, 2026 · 1 revision

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.

The pipeline

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")]
Loading

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

Contracts

Identity — which fragment keeps the component

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.

Construction

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.

Type

The rebuilt component keeps the family of its source: IWallSurfaceWall, IFloorFaceFloor, IRoofSurfaceRoof, IAirSurfaceAir. A face whose reference resolves to nothing — a face created on a cutting plane, for example — becomes a new SurfaceAir.

Spaces

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.

Shared state across shells

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.

Usage

Split a model at an elevation

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);

Slice a model into storeys

BuildingModelFloorUpdater buildingModelFloorUpdater = new(buildingModel)
{
    FloorCount = 3,
    FloorConstruction = floorConstruction
};

bool updated = buildingModelFloorUpdater.Update();

Write your own geometry back

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.

Solvers vs updaters

DiGi.Analytical also ships a solver family (ShellByPlaneSplitSolver, FaceByFacesSplitSolver, …). The difference is what they touch:

  • Solvers work on analytical geometry only. They raise FaceSplit/ShellSplit events letting a handler assign the IUniqueReference of 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.

Known limitations

  • 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).
  • IUniqueReference compared with == is a reference comparison. The equality operators are declared on SerializableReference and do not apply to interface-typed operands, and accessors such as Face.UniqueReference return a fresh clone on every call. Always compare with Equals (null-guarded) or rely on a Dictionary/HashSet, which use Equals/GetHashCode correctly. This bug caused a non-terminating loop in BuildingModelShellUpdater and, at the time of writing, is still present in ShellByPlaneSplitSolver.
  • 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.

Related

Clone this wiki locally