SOLIBRI SDK · JAVA

Custom Solibri rules

Check rules Solibri does not ship - built with the Solibri Java SDK (Maven).

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 Filterwhich components form a set
Connectivity tolerancemax gap treated as 'connected'
Group by propertyreport 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.
Set Connectivity Check

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 Filterswhat contains what
Check ModeTotal · Per-Side · Opposite · All-Sides
Min / Max Countallowed range
Active Faces & Zonewhich 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 Componentswalls / slabs that carry the openings
Opening Componentsthe void geometry to check
Tolerancemax 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 Componentsthe component being penetrated
Penetrating Componentsthe component entering the enclosing one
Check ModeRemaining Distance · Penetration Depth
Min / Max Distanceallowed 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.
Penetration Depth Check

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 Componentsthe parent that scopes the check (e.g. slab)
Inner Components Afirst set of inner parts (e.g. bolts)
Inner Components Bsecond set (e.g. rebar); may equal A
Min / Max Distanceallowed 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.
Internal Component Distance CheckInternal Component Distance Check