Generates filled cap geometry along arbitrary world-space cut planes
through a SceneModel, so sliced models read
as solid cross-sections instead of hollow shells.
The sectionCaps module complements
SectionPlane-style clipping: where a
SectionPlane hides everything on the clipped side of a plane,
sectionCapsfills the hole the plane leaves behind. Output is
embedded into a caller-owned target SceneModel
as flat TrianglesPrimitive meshes lying on the cut planes, one
SceneObject per source object that
contributed at least one cap.
Shape
%%{init:{"theme":"dark"}}%%
classDiagram
direction TB
class buildSectionCaps {
+call(params)
}
class canBuildSectionCaps {
+call(sourceModel) boolean
}
class clearSectionCaps {
+call(targetModel)
}
class BuildSectionCapsParams {
+sourceModel : SceneModel
+targetModel : SceneModel
+capPlanes : CapPlane[]
+capColor : Vec3
+idPrefix : string
+layerId : string
+includeObjectIds : string[]
+excludeObjectIds : string[]
+includeObject(obj) boolean
+progressive : ProgressiveSpec
}
class CapPlane {
+dir : Vec3
+dist : number
}
class ProgressiveSpec {
+batchSize : number
+yield() Promise
}
class BuildSectionCapsResult {
+numObjectsWithCaps : number
+numCapMeshes : number
+numUnclosedMeshes : number
}
class SceneModel {
<<scene>>
}
class SectionPlane {
<<viewer>>
+dir : Vec3
+dist : number
}
BuildSectionCapsParams "1" *-- "*" CapPlane : capPlanes
BuildSectionCapsParams o-- SceneModel : sourceModel
BuildSectionCapsParams o-- SceneModel : targetModel
BuildSectionCapsParams o-- ProgressiveSpec : progressive
buildSectionCaps ..> BuildSectionCapsParams : reads
buildSectionCaps ..> BuildSectionCapsResult : returns
canBuildSectionCaps ..> SceneModel : scans
clearSectionCaps ..> SceneModel : destroys
CapPlane <.. SectionPlane : copy dir + dist
%%{init:{"theme":"default"}}%%
classDiagram
direction TB
class buildSectionCaps {
+call(params)
}
class canBuildSectionCaps {
+call(sourceModel) boolean
}
class clearSectionCaps {
+call(targetModel)
}
class BuildSectionCapsParams {
+sourceModel : SceneModel
+targetModel : SceneModel
+capPlanes : CapPlane[]
+capColor : Vec3
+idPrefix : string
+layerId : string
+includeObjectIds : string[]
+excludeObjectIds : string[]
+includeObject(obj) boolean
+progressive : ProgressiveSpec
}
class CapPlane {
+dir : Vec3
+dist : number
}
class ProgressiveSpec {
+batchSize : number
+yield() Promise
}
class BuildSectionCapsResult {
+numObjectsWithCaps : number
+numCapMeshes : number
+numUnclosedMeshes : number
}
class SceneModel {
<<scene>>
}
class SectionPlane {
<<viewer>>
+dir : Vec3
+dist : number
}
BuildSectionCapsParams "1" *-- "*" CapPlane : capPlanes
BuildSectionCapsParams o-- SceneModel : sourceModel
BuildSectionCapsParams o-- SceneModel : targetModel
BuildSectionCapsParams o-- ProgressiveSpec : progressive
buildSectionCaps ..> BuildSectionCapsParams : reads
buildSectionCaps ..> BuildSectionCapsResult : returns
canBuildSectionCaps ..> SceneModel : scans
clearSectionCaps ..> SceneModel : destroys
CapPlane <.. SectionPlane : copy dir + dist
classDiagram
direction TB
class buildSectionCaps {
+call(params)
}
class canBuildSectionCaps {
+call(sourceModel) boolean
}
class clearSectionCaps {
+call(targetModel)
}
class BuildSectionCapsParams {
+sourceModel : SceneModel
+targetModel : SceneModel
+capPlanes : CapPlane[]
+capColor : Vec3
+idPrefix : string
+layerId : string
+includeObjectIds : string[]
+excludeObjectIds : string[]
+includeObject(obj) boolean
+progressive : ProgressiveSpec
}
class CapPlane {
+dir : Vec3
+dist : number
}
class ProgressiveSpec {
+batchSize : number
+yield() Promise
}
class BuildSectionCapsResult {
+numObjectsWithCaps : number
+numCapMeshes : number
+numUnclosedMeshes : number
}
class SceneModel {
<<scene>>
}
class SectionPlane {
<<viewer>>
+dir : Vec3
+dist : number
}
BuildSectionCapsParams "1" *-- "*" CapPlane : capPlanes
BuildSectionCapsParams o-- SceneModel : sourceModel
BuildSectionCapsParams o-- SceneModel : targetModel
BuildSectionCapsParams o-- ProgressiveSpec : progressive
buildSectionCaps ..> BuildSectionCapsParams : reads
buildSectionCaps ..> BuildSectionCapsResult : returns
canBuildSectionCaps ..> SceneModel : scans
clearSectionCaps ..> SceneModel : destroys
CapPlane <.. SectionPlane : copy dir + dist
Features
Watertight cap fills — every cut produces a closed,
triangulated polygon (per source mesh per plane) so the model
reads as a solid cross-section rather than a hollow shell.
Multi-plane caps — caps from intersecting cut planes are
trimmed against each other's kept half-spaces (Sutherland-Hodgman),
so two cuts don't double-cover their shared corner.
Outer / hole topology — loops are classified by signed area
and holes attached to their tightest container, so cross-sections
through hollow members (pipes, walls with openings) render with
their interiors open rather than filled.
Hatch-pattern preservation — the cap's
SceneMaterial inherits the source
mesh material's hatchPattern (when present), so sectioned
masonry, concrete, and metal read with the same engineering
hatch convention as the rest of the model.
Per-mesh colour passthrough — when no capColor is supplied,
each cap inherits its source mesh's effective colour (material
colour first, then mesh-level colour, then a neutral grey
fallback). Reads better on multi-material BIM models than a
single global tint.
SectionPlane-compatible
convention — CapPlane.dir and CapPlane.dist match the
accessors on a viewer-side SectionPlane verbatim, so a cap can
be built directly from any clipping plane the host already drives.
Cross-Scene — source and target
SceneModels may live in different
Scenes.
Object filtering — restrict the cap pass with
includeObjectIds, excludeObjectIds, and/or an arbitrary
includeObject predicate. Right for "cap walls + slabs but not
furniture" or "cap whatever's visible right now."
ViewLayer parking — pass layerId to assign every emitted
SceneObject to a named
ViewLayer; the host can then hide,
show, or style caps collectively.
Progressive emission — yield between batches so very large
models cap progressively without blocking the main thread.
Diagnostic counts — numUnclosedMeshes flags non-watertight
source geometry that produced no cap.
Installation
npminstall@xeokit/sdk
Quick Start
The snippets below assume a Scene with a
source SceneModel already loaded (via any
SDK importer — for example, DotBIMLoader or IFCLoader).
The caller creates the target SceneModel
and passes it in — buildSectionCaps populates it. The plane
convention matches SectionPlane: the
half-space dot(dir, p) + dist > 0 is clipped (discarded) and the
cap's face normal is +dir.
if (!result.ok) { console.error(result.error); targetModel.destroy(); // partial state may exist } else { const { numObjectsWithCaps, numCapMeshes } = result.value; console.log(`${numCapMeshes} caps across ${numObjectsWithCaps} objects`); }
3) Multi-plane caps
Pass several planes to cut along independent axes. Each plane
produces its own cap set per source object, trimmed against the
kept half-space of every other plane so intersecting cuts don't
overlap at their shared corner.
CapPlane mirrors the dir / dist accessors on a
SectionPlane, so the host's existing
clipping plane can be reused verbatim — cap geometry lands on the
same plane the viewer is already clipping against.
idPrefix namespaces every emitted geometry, material, mesh, and
object id ("{idPrefix}__{sourceObjectId}__…"). Pick a unique
prefix per pass when running the extractor more than once into the
same target — e.g. one prefix per cap-plane set, or one per
source model when capping a federation against the same plane.
capColor sets the RGB base colour of every emitted cap
SceneMaterial. Omit capColor to
inherit each cap's tint from its source mesh's effective colour
(material colour first, then mesh-level colour, then a neutral
grey fallback) — useful on multi-material BIM models where a
single global tint would erase material differences. When the
corresponding source mesh's material carries a hatchPattern,
the cap material inherits it — sectioned masonry, concrete, or
metal then renders with the same engineering hatch as the rest
of the model.
// Inherit per-mesh colour from the source material: awaitbuildSectionCaps({ sourceModel, targetModel, capPlanes });
// Or override every cap with a uniform paper-white: awaitbuildSectionCaps({ sourceModel, targetModel, capPlanes, capColor: [0.92, 0.93, 0.95] });
7) Park caps on their own ViewLayer
Pass layerId to assign every emitted SceneObject to a named
ViewLayer. The host can then hide,
show, or restyle the caps collectively without touching the
source model.
Restrict the cap pass with any combination of an id whitelist, an
id blacklist, and an arbitrary predicate. All three conditions
AND together when present.
// Cap only specific objects: awaitbuildSectionCaps({ sourceModel, targetModel, capPlanes, includeObjectIds: ["wall_01", "wall_02", "slab_03"] });
// Cap everything except a transient hidden set (large set -> // prefer Set membership): consthidden = newSet(currentlyHiddenIds); awaitbuildSectionCaps({ sourceModel, targetModel, capPlanes, excludeObjectIds:hidden });
// Predicate filter — "cap whatever's visible in this View": awaitbuildSectionCaps({ sourceModel, targetModel, capPlanes, includeObject: (obj) =>view.objects[obj.id]?.visible === true });
9) Progressive emission
For large source models, pass progressive: true so the function
yields between batches of source-object emissions and the caps
paint into the View as they build. Pass a ProgressiveSpec
for finer control.
The function only reads source geometry and writes to the target,
so the two SceneModels may live in
different Scenes. Useful when capping a
federation loaded in one Scene into a "drawings" Scene that the
host renders on a separate canvas / view.
Point clouds and line-only models produce no caps.
canBuildSectionCaps reports whether the source has any
triangle-bearing geometry the cap builder can usefully consume,
so callers can skip the call instead of running it for nothing.
BuildSectionCapsResult reports per-pass counts. The
numUnclosedMeshes field is a diagnostic — non-zero means the
source had non-watertight meshes whose cap segments didn't stitch
into closed loops. Cap coverage degrades on those meshes; meshes
with stitched loops still produce caps.
if (numUnclosedMeshes > 0) { console.warn(`${numUnclosedMeshes} source meshes were non-watertight`); } }
13) Tearing down
The caller owns the target SceneModel, so teardown is just
targetModel.destroy(). clearSectionCaps is a convenience
wrapper that no-ops if the target is null or already destroyed.
Running buildSectionCaps repeatedly into a freshly-created
target lets the host swap cap sets as the user moves the cut
plane.
xeokit Section Caps
Generates filled cap geometry along arbitrary world-space cut planes through a SceneModel, so sliced models read as solid cross-sections instead of hollow shells.
The
sectionCapsmodule complements SectionPlane-style clipping: where a SectionPlane hides everything on the clipped side of a plane,sectionCapsfills the hole the plane leaves behind. Output is embedded into a caller-owned target SceneModel as flatTrianglesPrimitivemeshes lying on the cut planes, one SceneObject per source object that contributed at least one cap.Shape
Features
hatchPattern(when present), so sectioned masonry, concrete, and metal read with the same engineering hatch convention as the rest of the model.capColoris supplied, each cap inherits its source mesh's effective colour (material colour first, then mesh-level colour, then a neutral grey fallback). Reads better on multi-material BIM models than a single global tint.CapPlane.dirandCapPlane.distmatch the accessors on a viewer-side SectionPlane verbatim, so a cap can be built directly from any clipping plane the host already drives.includeObjectIds,excludeObjectIds, and/or an arbitraryincludeObjectpredicate. Right for "cap walls + slabs but not furniture" or "cap whatever's visible right now."layerIdto assign every emitted SceneObject to a named ViewLayer; the host can then hide, show, or style caps collectively.numUnclosedMeshesflags non-watertight source geometry that produced no cap.Installation
Quick Start
The snippets below assume a Scene with a source SceneModel already loaded (via any SDK importer — for example,
DotBIMLoaderorIFCLoader).1) Import the entry points
2) Cap one source model against one plane
The caller creates the target SceneModel and passes it in —
buildSectionCapspopulates it. The plane convention matches SectionPlane: the half-spacedot(dir, p) + dist > 0is clipped (discarded) and the cap's face normal is+dir.3) Multi-plane caps
Pass several planes to cut along independent axes. Each plane produces its own cap set per source object, trimmed against the kept half-space of every other plane so intersecting cuts don't overlap at their shared corner.
4) Match an existing viewer SectionPlane
CapPlane mirrors the
dir/distaccessors on a SectionPlane, so the host's existing clipping plane can be reused verbatim — cap geometry lands on the same plane the viewer is already clipping against.5) Multiple cap passes into one target model
idPrefixnamespaces every emitted geometry, material, mesh, and object id ("{idPrefix}__{sourceObjectId}__…"). Pick a unique prefix per pass when running the extractor more than once into the same target — e.g. one prefix per cap-plane set, or one per source model when capping a federation against the same plane.6) Colour and hatch pattern
capColorsets the RGB base colour of every emitted cap SceneMaterial. OmitcapColorto inherit each cap's tint from its source mesh's effective colour (material colour first, then mesh-level colour, then a neutral grey fallback) — useful on multi-material BIM models where a single global tint would erase material differences. When the corresponding source mesh's material carries ahatchPattern, the cap material inherits it — sectioned masonry, concrete, or metal then renders with the same engineering hatch as the rest of the model.7) Park caps on their own ViewLayer
Pass
layerIdto assign every emitted SceneObject to a named ViewLayer. The host can then hide, show, or restyle the caps collectively without touching the source model.8) Filter which source objects get capped
Restrict the cap pass with any combination of an id whitelist, an id blacklist, and an arbitrary predicate. All three conditions AND together when present.
9) Progressive emission
For large source models, pass
progressive: trueso the function yields between batches of source-object emissions and the caps paint into the View as they build. Pass a ProgressiveSpec for finer control.10) Source and target in different Scenes
The function only reads source geometry and writes to the target, so the two SceneModels may live in different Scenes. Useful when capping a federation loaded in one Scene into a "drawings" Scene that the host renders on a separate canvas / view.
11) Guard against unprojectable sources
Point clouds and line-only models produce no caps. canBuildSectionCaps reports whether the source has any triangle-bearing geometry the cap builder can usefully consume, so callers can skip the call instead of running it for nothing.
12) Inspecting results
BuildSectionCapsResult reports per-pass counts. The
numUnclosedMeshesfield is a diagnostic — non-zero means the source had non-watertight meshes whose cap segments didn't stitch into closed loops. Cap coverage degrades on those meshes; meshes with stitched loops still produce caps.13) Tearing down
The caller owns the target SceneModel, so teardown is just
targetModel.destroy(). clearSectionCaps is a convenience wrapper that no-ops if the target is null or already destroyed. RunningbuildSectionCapsrepeatedly into a freshly-created target lets the host swap cap sets as the user moves the cut plane.