FCS Boolean Operations and CSG

Constructive Solid Geometry (CSG) lets you combine or subtract 3-D volumes. FCS also supports 2-D area islands (holes inside a boundary polygon).

1. Volume Primitives

Before performing CSG you need source volumes.

# vertex-bounded prism (two curve-index ranges: bottom face points, then top)
volume {vo1} prism 1 3          # uses vertices/curves defined elsewhere
volume {vo2} prism 2 4

Optional metadata can be tagged onto any volume:

volume {vo1} prism 1 3 metadata {A:=1}

2. CSG Operations

Operator Meaning
a-b a minus b (subtract)
a+b union of a and b
a*b intersection of a and b
# subtract vo2 from vo1, result stored in vo3
volume {vo3} csg (vo1-vo2)

# union (parentheses group the expression)
volume {voU} csg (vo1+vo2)

# intersection
volume {voI} csg (vo1*vo2)

# chained operations
volume {voR} csg ((vo1+vo2)-vo3)

Source files

3. Debug Mode

debug_boolean writes diagnostic output for the two operands and can be placed before the csg expression to investigate failures:

debug_boolean vo2 vo1
volume {vo3} csg (vo1-vo2)

This prints intersection metadata (face count, vertex count, tolerance used) to the FLI log.

4. Tolerance

The default CSG tolerance is 1e-8. Models with very small features may need vertices to be at least 1e-7 apart to avoid degenerate intersections.

5. 2-D Area Islands (Holes)

An area can contain holes. A curve listed with a minus sign in the boundary declaration is an inner boundary (hole), whose winding must be opposite to the outer boundary.

# outer rectangle boundary: c1 (counter-clockwise)
# inner hole:               c2 (clockwise, i.e. the reverse wind)
area {a1} boundary +c1 -c2

A more complex example with two holes:

area {a1} boundary +c1 -c2 -c3

Orientation convention

Source files

6. Multi-Body Interaction

When volumes share a face (e.g., adjacent rooms), ensure face vertices are identical — do not rely on tolerance-based snapping. Reuse the same vertex references when possible.

7. Known Pitfalls

Pitfall Remedy
CSG result vanishes Check that the volumes actually overlap; run debug_boolean
Island area fails mesh Verify inner-boundary curve is exactly opposite winding
Tolerance errors Ensure shared edges share the same vertex objects
Very thin slivers after subtract Add a small extend margin before subtracting, or merge near-coincident vertices

8. Relevant Test Folders

Folder pattern Coverage
new-Booleans/ Basic CSG subtract + debug
new-UnionIslands/ 2-D island areas
test-Boolean3D-*/ (≈14 folders) 3-D CSG corner cases
bug-Boolean3D-*/ (≈10 folders) Historical CSG bug reproductions

9. Feature-Based Boolean Operations (Preferred Approach)

The low-level csg keyword (sections 1–3) works on raw volumes inside a single file. In practice, multi-file parametric models use a higher-level mechanism instead: Features metadata on gblock.
Features are declared on a gblock and the engine applies the boolean operations automatically when the block geometry is resolved. This approach is far more common in real HiStruct/FCC configurators.

9.1 Core Feature Types

Feature Type Effect Key Fields
BooleanOpeningsFeature Subtracts one or more opening volumes from the gblock's geometry Openings, FilterByLayerNames, AsBpt, MergeResultingSurfaces, StrictAssertCheckVolume, WithVolumeTreshold
BooleanIntersectionFeature Clips (intersects) the gblock's geometry to a boundary volume Boundaries, FilterByLayerNames
AddCompositeFeature Merges (unions) a list of component gblocks into one solid Components

9.2 BooleanOpeningsFeature — Cutting Openings

# Minimal: subtract a cutting-volume gblock from all geometry in gbWall
gblock {gbWall} gclass {gWall} lcs {...} parameters {...} metadata {
   Features := [
      BooleanOpeningsFeature {
         Openings = [gbCuttingVolume]
      }
   ]
}

Full field example (from test-Boolean3DrooferIntersectionMetadata/MainTest.fcs):

gblock {gbSolids} gclass mainBlocks[0] metadata {
   Features := [
      BooleanOpeningsFeature {
         AsBpt                = True,        # boundary-point representation (panels)
         MergeResultingSurfaces = True,
         StrictAssertCheckVolume = True,
         WithVolumeTreshold   = 1.0e-8,
         Openings             = openingBlocks
      }
   ]
}

FilterByLayerNames — target specific layers

When a gblock contains many sub-solids in different layers (wall panels, flashings, plinth, etc.), you can restrict which layers are cut using FilterByLayerNames. This is the standard pattern in building facades:

gblock {gbBuilding} gclass {gBuilding} lcs {...} parameters {...} metadata {
   Features := [
      BooleanOpeningsFeature {
         Openings = [
            dOpeningFront.GetGeometryFromLayerName("LayerCuttingVolume"),
            dOpeningBack.GetGeometryFromLayerName("LayerCuttingVolume"),
            dOpeningLeft.GetGeometryFromLayerName("LayerCuttingVolume"),
            dOpeningRight.GetGeometryFromLayerName("LayerCuttingVolume")
         ],
         FilterByLayerNames = [ "LayerWallPanel", "LayerWallPanel1",
                                "LayerWallPanel2", "LayerFlashings", "LayerPlinth" ]
      }
   ]
}

GetGeometryFromLayerName("LayerCuttingVolume") extracts only the solids tagged in that layer from a distribution. This separates the cutting-volume geometry from the visual geometry of the opening component.

Multiple independent features per gblock

Each feature in the array is applied independently, so you can target different layer sets with different cutting volumes in the same gblock:

gblock {gbWallBack} gclass {gWall} lcs {...} parameters {...} metadata {
   Features := [
      FeatureCuttingRoof(BackWall.isAttic),   # pre-computed lambda: cuts roof slope
      FeatureCuttingCorner,                    # pre-computed variable: cuts corner flashing
      FeatureCuttingOpeningBack                # pre-computed variable: cuts door/window openings
   ]
}

9.3 Reusable Feature Variables

It is idiomatic to define feature objects as named variables and reuse them across multiple gblocks:

# Defined once near the top of the configurator file
FeatureCuttingSkyLightRidge := BooleanOpeningsFeature {
   Openings           = [gbCuttingVolumeSkyLight],
   FilterByLayerNames = ["LayerRoofPanel", "LayerFlashings"]
}

FeatureCuttingCorner := BooleanOpeningsFeature {
   Openings           = [gbCuttingVolumeRoof],
   FilterByLayerNames = ["LayerFlashingPanelCorner"]
}

# Applied to multiple gblocks
gblock {gbRoofLeft}  gclass {gRoof} lcs {...} metadata { Features := [FeatureCuttingSkyLightRidge] }
gblock {gbRoofRight} gclass {gRoof} lcs {...} metadata { Features := [FeatureCuttingSkyLightRidge] }
gblock {gbRidgeFlash} gclass {gFlashRidge} lcs {...} metadata { Features := [FeatureCuttingSkyLightRidge] }

9.4 Conditional Features

Features can be empty-list conditional so no cutting happens when not needed:

# Ternary: apply cutting feature only when skylight is configured
gblock {gbRoofPanelsLeft} gclass {gRoofPanels} lcs {...} metadata {
   Features := (Building.isSkyLight) ? ([BooleanOpeningsFeature{ Openings := [gbCuttingVolumeSkyLight] }]) : ([])
}

Or using a lambda to select between two pre-built feature objects:

FeatureCuttingRoof := isAttic => (
   (isAttic) ? (FeatureCuttingAttic)
             : (FeatureCuttingSlope)
)

gblock {gbWallRight} gclass {gWall} lcs {...} metadata {
   Features := [(LeftWall.isAttic) ? (FeatureCuttingAttic) : (FeatureCuttingSlope), FeatureCuttingOpeningLeft]
}

9.5 BooleanIntersectionFeature — Clipping to a Boundary

BooleanIntersectionFeature retains only the part of the gblock geometry that is inside the boundary volumes. Used for clipping a structural segment to the extent of a building zone:

FeatureIntersectionAll := BooleanIntersectionFeature {
   Boundaries := boundaryBlocks
}

# With layer filter (only clip specific sub-solids)
FeatureIntersectionLayers := BooleanIntersectionFeature {
   Boundaries          := boundaryBlocks,
   FilterByLayerNames  = cuttedLayers
}

# Conditionally include
FeaturesIntersection := (boundaryBlocks.Count > 0) ? ([FeatureIntersectionAll]) : ([])

9.6 Reusable Worker-Class Pattern

For large configurators, the features + gblock wiring is factored into a dedicated helper class (PlaneSegmentBooleanWorker):

# PlaneSegmentBooleanWorker.fcs (parameterized inputs)
segment    := {}        # the gclass to cut
AllFeatures := []       # array of features to apply
segmentLcs := GCS

gblock {gbSegment} gclass (segment) lcs (segmentLcs) metadata {
   Features := AllFeatures
}

Calling side:

AllFeatures := FeaturesIntersection + FeaturesOpenings

cuttedSegment := PlaneSegmentBooleanWorker {
   segment,
   AllFeatures,
   segmentLcs
}
gblock {gbSegment} gclass (cuttedSegment)

The + operator concatenates feature arrays, enabling composable feature pipelines.

9.7 Low-Level FeatureSolver API

Rarely used directly; useful for debug-dump files or procedural scripts:

features = [
   BooleanIntersectionFeature { Boundaries := [...] },
   BooleanOpeningsFeature     { Openings   := [...] }
]
result = Fcs.Geometry.Features.FeatureSolver(cmp1, features)

Volume metadata can also declare features (seen in dump files):

meta00 := { Features = [FCS.Library.Geometry.BooleanIntersectionFeature, FCS.Library.Geometry.BooleanOpeningsFeature] }

9.8 When to Use Each Approach

Scenario Approach
Simple script, single file, raw geometry testing volume csg (a-b)
Parametric gblock component, subtract a door/window opening from a wall panel BooleanOpeningsFeature on the wall gblock
Clip a building segment to a zone boundary BooleanIntersectionFeature on the segment gblock
Different opening types (panels vs flashings) need separate cuts Multiple BooleanOpeningsFeature entries with different FilterByLayerNames
Opening volume lives alongside visual geometry in a distribution dOpening.GetGeometryFromLayerName("LayerCuttingVolume")
Cut is conditional on a parameter (skylight on/off) Ternary Features := (cond)?([feat]):([])

9.9 Relevant Source Files

File Pattern demonstrated
TestData/test-Boolean3DrooferIntersectionMetadata/MainTest.fcs BooleanOpeningsFeature with all boolean control fields
TestData/bug-Boolean3D_20240913/BooleanSolid.fcs BooleanOpeningsFeature minimal
TestData/bug-NoCut/Configurator_SheatedBuildings_Geometry/BuildingDuoPitchClearSpan.fcs Named feature variables, multiple features per gblock, conditional lambda
TestData/benchmark-HBC-2/.../PlaneSegmentBoolean.fcs BooleanIntersectionFeature + BooleanOpeningsFeature combined, conditional arrays
TestData/bug-HbcCycRef/_Common/BuildingTools/PlaneSegmentBooleanWorker.fcs Worker-class pattern
LabComponents/.../Sheated_Building_DuoPitch.fcs GetGeometryFromLayerName, FilterByLayerNames on distribution openings
LabComponents/.../Unihal_Buildings_Geometry_SimpleHall.fcs Conditional ternary feature on each gblock
TestData/bug-Boolean3D_20230119/Demo_sub.fcs AddCompositeFeature{Components=[...]}

10. Quick Reference

# --- CSG (single-file, raw volumes) ---
volume {result} csg (body1 - body2)   # subtraction
volume {result} csg (body1 + body2)   # union
volume {result} csg (body1 * body2)   # intersection
debug_boolean body2 body1

# --- Island area ---
area {aResult} boundary +cOuter -cHole1 -cHole2

# --- Feature: subtract opening from gblock (all layers) ---
gblock {gb} gclass {gc} lcs {...} metadata {
   Features := [BooleanOpeningsFeature{ Openings = [gbCuttingVolume] }]
}

# --- Feature: subtract opening, specific layers only ---
gblock {gb} gclass {gc} lcs {...} metadata {
   Features := [
      BooleanOpeningsFeature {
         Openings           = [dOpening.GetGeometryFromLayerName("LayerCuttingVolume")],
         FilterByLayerNames = ["LayerWallPanel", "LayerFlashings"]
      }
   ]
}

# --- Feature: clip to boundary ---
gblock {gb} gclass {gc} lcs {...} metadata {
   Features := [BooleanIntersectionFeature{ Boundaries := [gbBoundary] }]
}

# --- Feature: conditional ---
gblock {gb} gclass {gc} lcs {...} metadata {
   Features := (isOn) ? ([BooleanOpeningsFeature{Openings=[gbCut]}]) : ([])
}

# --- Feature: multiple features ---
gblock {gb} gclass {gc} lcs {...} metadata {
   Features := [featureA, featureB, featureC]
}

# --- Combined array via + operator ---
AllFeatures := FeaturesIntersection + FeaturesOpenings