Set Connectivity CheckR&D · work in progress
Problem
In modular construction, components are grouped into "sets". A set is only valid if its members are actually connected - gaps break downstream assembly and QA. Solibri had no built-in way to verify connectivity inside a set.
Approach
A custom Java rule (Solibri SDK) that, for each set, builds an adjacency graph of its members and flags members that are not connected to the rest. Candidate adjacency is found via bounding-box intersection, then refined.
Parameters
| Set Filter | which components form a set |
| Connectivity tolerance | max gap treated as 'connected' |
| Group by property | report disconnections per property value |
Result
Disconnected members are highlighted in the model. Currently in active R&D: adjacency uses AABB boxes rather than exact geometry, so some cases are missed - the next iteration moves to precise geometric contact.
How the code works
/** Two components "touch" when their real geometry is within tolerance. */
private boolean areTouching(Component a, Component b, double tolerance) {
// Broad phase: the AABB gap is a lower bound on the true distance -
// if even the bounding boxes are too far apart, skip the costly test.
if (bboxDistance(a, b) > tolerance) {
return false;
}
// Narrow phase: precise surface-to-surface distance between the solids.
Optional<Double> distance = a.distance(b);
if (distance.isPresent()) {
return distance.get() <= tolerance;
}
return true; // no geometry - trust the AABB proximity already established
}
- Broad phaseThe bounding-box gap is a mathematical lower bound on the real distance, so far-apart pairs are rejected cheaply before any heavy geometry maths runs.
- Narrow phasea.distance(b) is the true surface-to-surface distance between the two solids. This is what removes the false 'bridges' a large element's bounding box would otherwise create.
- BFS clusteringTouching pairs build an adjacency graph; a breadth-first search groups members into connected clusters. A set with more than one cluster has disconnected parts - every non-largest cluster is flagged.

Component Count Check
Problem
Assemblies require a specific number of sub-components - fasteners per side, brackets per panel. Manually checking counts across a model is slow and error-prone.
Approach
A custom Java rule that, for each parent component, finds child components whose centroid lies inside the parent and counts them - either as a total or distributed per face (per-side / opposite / all-sides).
Parameters
| Parent / Child Filters | what contains what |
| Check Mode | Total · Per-Side · Opposite · All-Sides |
| Min / Max Count | allowed range |
| Active Faces & Zone | which faces, and the band near each face |
Result
Components with too few or too many children are flagged, with side-specific messages (e.g. "too few components on side +X").
How the code works
// Spatial pre-filter by bounding box, then keep only children whose
// centroid actually falls inside the parent's bounding box.
ComponentFilter spatialFilter = AABBIntersectionFilter
.ofComponentBounds(parent)
.and(childFilter);
Collection<Component> candidates = SMC.getModel().getComponents(spatialFilter);
List<Component> children = new ArrayList<>();
for (Component candidate : candidates) {
if (!candidate.equals(parent) && isCentroidInsideParent(candidate, parent)) {
children.add(candidate);
}
}
- Broad-phase filterAABBIntersectionFilter.ofComponentBounds(parent) asks Solibri only for components near the parent's bounding box, instead of scanning the entire model.
- Centroid testisCentroidInsideParent keeps a child only if its centre lies inside the parent box - more reliable than getIntersections() for embedded parts (e.g. rebar) with zero-thickness contact.
- Then countThe clean child list feeds the four modes - Total, Per-Side, Opposite, All-Sides - each counting children per bounding-box face zone.
Opening Geometry Check
Problem
Openings (voids) in walls and slabs must pass cleanly through their host element. An opening that stops short of the far face ("incomplete") or pokes out beyond it ("protruding") breaks downstream work - MEP penetrations, doors and windows no longer line up. Spotting these by eye across a model is unreliable.
Approach
The rule pairs each opening/void component with its host wall or slab and checks the opening geometry against the host: it verifies the void spans the host's full thickness within a tolerance, and flags openings that don't reach the far face or that protrude. Host and opening components are picked by element-type filters.
Parameters
| Host Components | walls / slabs that carry the openings |
| Opening Components | the void geometry to check |
| Tolerance | max allowed gap / protrusion at the host faces |
Result
Incomplete and protruding openings are highlighted with a precise message and gap - e.g. "Opening '(B) Objekt-2.49' does not reach the far face (X-max) of '(B) Wand-2.6'. Gap: 218 mm."
How the code works
// Thickness axis = the thinnest span of the host's bounding box
// (Z for a slab, X or Y for a wall).
int thicknessAxis = findThinnestAxis(hostBox);
double hostMin = getAxisMin(hostBox, thicknessAxis);
double hostMax = getAxisMax(hostBox, thicknessAxis);
double openMin = getAxisMin(openingBox, thicknessAxis);
double openMax = getAxisMax(openingBox, thicknessAxis);
// A valid opening reaches BOTH faces within tolerance.
boolean reachesNearFace = openMin <= hostMin + tolerance;
boolean reachesFarFace = openMax >= hostMax - tolerance;
if (reachesNearFace && reachesFarFace) {
return null; // full penetration - OK
}
// else: report the gap to the face it falls short of
double shortfallFar = reachesFarFace ? 0 : (hostMax - openMax);
- Spatial pre-filterAABBIntersectionFilter.ofComponentBounds(host) asks Solibri only for openings near the host's bounding box; getIntersections(host) then confirms real geometric overlap before anything is measured - the same broad/narrow pattern as the other rules.
- Thickness axisThe host's thinnest bounding-box axis is treated as its thickness - Z for slabs, X or Y for walls - so the check needs no manual hint about element orientation.
- Two checksFull penetration is just openMin ≤ near face and openMax ≥ far face on that axis. A second pass over all three axes catches the opposite defect - an opening that protrudes beyond the host - reusing the same min/max comparisons.
Penetration Depth Check
Problem
Solibri's stock intersection check (SOL/234) misses penetrating parts with very small volume or footprint - a gap the Solibri community has reported for years. Anchors, rebar and small connectors that don't reach deep enough into their host (or poke too far through it) slip past coordination unnoticed.
Approach
For each enclosing component the rule gathers the penetrating components, confirms a real geometry intersection, and measures along the penetration axis. It reports either the penetration depth (how far the part has entered) or the remaining distance (from the part's tip to the far boundary), then checks that value against an allowed min/max range.
Parameters
| Enclosing Components | the component being penetrated |
| Penetrating Components | the component entering the enclosing one |
| Check Mode | Remaining Distance · Penetration Depth |
| Min / Max Distance | allowed range for the measured value |
Result
Penetrations whose depth (or remaining distance) falls outside the range are flagged with the axis and the measured value - e.g. "Component '(B) Anchor-12' penetrates '(B) Wand-2.6' along Z axis. Penetration depth: 12 mm (minimum required: 30 mm)."
Background Solibri Community - “Components inside component” ↗
How the code works
// Per axis: is the penetrating part partly in, partly out? If so, measure
// how far it entered (depth) and how far its tip is from the far face.
// Case 1 - enters from the min side:
if (penMin < encMin && penMax > encMin && penMax < encMax) {
depth = penMax - encMin; // how far it has entered
remaining = encMax - penMax; // tip -> far boundary
}
// Case 2 - enters from the max side:
else if (penMax > encMax && penMin < encMax && penMin > encMin) {
depth = encMax - penMin;
remaining = penMin - encMin;
}
// Keep the axis with the LARGEST depth, then range-check the chosen value.
double measured = depthMode ? depth : remaining;
if (measured < min - EPSILON || measured > max + EPSILON) { /* report */ }
- Real intersectionBefore measuring, getIntersections(enclosing) confirms the parts actually touch - bounding-box overlap alone gives false positives for shapes like L-profiles whose box reaches into neighbours the geometry never meets.
- Two entry casesA part can enter from either side of the enclosing box; each of the three axes is tested for both, yielding the entered depth and the tip's remaining distance to the far face.
- Depth vs. remainingThe same geometry feeds both modes - Penetration Depth (how far in) and Remaining Distance (how far to go) - and range-checking the chosen value is what catches the tiny penetrations Solibri's stock check overlooks.

Internal Component Distance Check
Problem
Spacing checks between small inner parts - bolts, rebar - only make sense within the same host. Solibri's global distance check (SOL/222) compares parts across the whole model, so two bolts sitting in two different, adjacent slabs get measured against each other even though they belong to separate elements. The spacing has to be evaluated per parent, not globally.
Approach
For each enclosing component (e.g. a slab) the rule gathers the inner components of set A (e.g. bolts) and set B (e.g. rebar) whose bounding boxes fall inside it. For every A it finds the nearest B in the same parent - using the true face-to-face gap between bounding boxes - and flags pairs whose distance falls outside the allowed min/max range.
Parameters
| Enclosing Components | the parent that scopes the check (e.g. slab) |
| Inner Components A | first set of inner parts (e.g. bolts) |
| Inner Components B | second set (e.g. rebar); may equal A |
| Min / Max Distance | allowed spacing range |
Result
Out-of-range pairs are flagged and scoped to their host - e.g. "Inside '(B) Slab-3': distance between '(B) Bolt-7' and '(B) Bolt-9' is 42 mm (minimum required: 60 mm)." Parts in neighbouring slabs are never compared.
How the code works
// Scope the search to THIS enclosing component: only inner parts whose
// bounding box sits inside it (this is what a global check does not do).
ComponentFilter innerA = AABBIntersectionFilter.ofComponentBounds(enclosing).and(filterA);
ComponentFilter innerB = AABBIntersectionFilter.ofComponentBounds(enclosing).and(filterB);
// For each A, keep its nearest B within the same parent:
for (Component a : model.getComponents(innerA)) {
Component nearestB = null;
double nearest = Double.MAX_VALUE;
for (Component b : model.getComponents(innerB)) {
if (b.equals(a) || b.equals(enclosing)) continue;
double d = aabbDistance(a.getBoundingBox(), b.getBoundingBox());
if (d < nearest) { nearest = d; nearestB = b; }
}
// report when the nearest distance is outside [min, max]
if (nearest < min - EPSILON || nearest > max + EPSILON) { /* report */ }
}
- Scoped to the parentBoth inner sets are gathered with AABBIntersectionFilter.ofComponentBounds(enclosing), so only parts inside this enclosing component are considered - that is the whole point versus a model-wide comparison like SOL/222.
- Nearest neighbourFor every A the rule keeps the single closest B; the message names both parts and their host, so a spacing violation points straight at the offending pair.
- Face-to-face gapaabbDistance sums the per-axis gaps between the two bounding boxes (0 when they overlap), giving the real clearance between parts rather than a centre-to-centre value.

