Render Hider hierarchy based on IFC spatial / assembly structure instead of IFC entity type

Summary

The core issue is a mismatch between the default grouping strategy of a visualization component’s Hider panel (grouping by IFC entity type) and the user’s requirement to navigate and control visibility by IFC spatial/decomposition hierarchy (Project → Site → Building → Storey → Assembly). There is typically no built-in toggle for this; the solution requires manually traversing the IFC graph to build a hierarchical tree data structure and mapping it to the UI.

Root Cause

  1. Default Indexing: Many BIM visualization libraries default to indexing geometry by IfcEntity type (e.g., IfcWall, IfcPlate) for broad categorization and filtering.
  2. Lack of Spatial Context: The default Hider logic often operates on a flat list of types or a shallow grouping, ignoring the IfcRelAggregates and IfcRelContainedInSpatialStructure relationships that define the BIM hierarchy.
  3. Separation of Data and UI: The library provides the raw data (IFC elements and their relationships) but does not automatically generate a nested TreeView structure for the UI; the application logic must construct this.

Why This Happens in Real Systems

In BIM software development, performance and simplicity often dictate initial design choices.

  • Performance: Grouping by a string property (Entity Type) is O(N) and extremely fast. Traversing the IFC owner history and spatial tree to build a hierarchy is computationally heavier (graph traversal).
  • Scope: “Hider” panels are often implemented as simple filters. Sophisticated BIM Tree navigation (like BIM Vision) is a distinct feature that requires maintaining a state tree, distinct from the geometry viewer.
  • IFC Complexity: The IFC standard allows for complex ownership and decomposition structures (e.g., an element belonging to a storey but also nested inside an assembly). A generic “one-size-fits-all” tree is hard to implement without opinionated logic.

Real-World Impact

  • User Disorientation: Users (BIM Managers, Coordinators) rely on the spatial hierarchy to locate elements. Flattening this into types makes finding a specific plate in a specific room difficult.
  • Loss of Assembly Logic: If you are manufacturing or pre-fabricating, IfcElementAssembly is crucial. Grouping by type breaks the relationship between the assembly and its constituent parts.
  • Manual Filtering Friction: Instead of toggling a single assembly, the user must manually select dozens of individual plates/walls because they are scattered across the UI list.

Example or Code

To replicate the BIM Vision structure, you must query the IFC graph to find the root IfcProject, then recursively traverse spatial elements (IfcSite, IfcBuilding, IfcBuildingStorey) and finally aggregate IfcElementAssembly and its parts.

Here is the TypeScript logic to build this hierarchy from a loaded model:

import { IfcAPI, IFCPROJECT, IFCBUILDING, IFCBUILDINGSTOREY, IFCELEMENTASSEMBLY, IFCRELCONTAINEDINSPATIALSTRUCTURE, IFCRELDECOMPOSES } from 'web-ifc';

async function buildSpatialHierarchy(ifcApi: IfcAPI, modelID: number) {
    // 1. Get the Project Root
    const projects = await ifcApi.getAllItemsOfType(modelID, IFCPROJECT, false);
    if (projects.length === 0) return [];

    const projectNode = { expressID: projects[0], name: "Project", children: [] };

    // 2. Recursive Traversal for Spatial Structure (Site -> Building -> Storey)
    async function getSpatialChildren(parentID: number) {
        // Find relations where this parent is the 'RelatingObject'
        const rels = await ifcApi.getProperty(modelID, {
            expressID: parentID,
            groupName: 5, // IsDefinedBy or Decomposes usually
            verbose: true
        });

        let children = [];

        // Filter for spatial decomposition
        for (const rel of rels) {
            if (rel.type === IFCRELCONTAINEDINSPATIALSTRUCTURE || rel.type === IFCRELDECOMPOSES) {
                // Get the related objects
                const relatedIDs = rel.RelatedObjects || []; 
                for (const id of relatedIDs) {
                    const info = await ifcApi.getLineWidth(modelID, id); // Placeholder to get basic props
                    // In reality, fetch Name, LongName, etc. via getProperties
                    const node = { 
                        expressID: id, 
                        name: `Element ${id}`, // Replace with actual property fetch
                        children: await getSpatialChildren(id) // Recursive
                    };

                    // 3. Handle Assemblies specifically if needed
                    const type = await ifcApi.getIfcType(modelID, id);
                    if (type === IFCELEMENTASSEMBLY) {
                        // Fetch parts of this assembly
                        node.children.push(...await getAssemblyParts(id));
                    }

                    children.push(node);
                }
            }
        }
        return children;
    }

    // Helper to get parts of an assembly
    async function getAssemblyParts(assemblyID: number) {
        // Logic to find IfcRelAggregates where RelatingObject is assemblyID
        // Return array of parts
        return []; 
    }

    projectNode.children = await getSpatialChildren(projectNode.expressID);
    return projectNode;
}

How Senior Engineers Fix It

Seniors approach this as a Data Transformation problem, not just a UI configuration issue.

  1. Decouple the Tree: Do not rely on the viewer’s native “Hider” to generate the list. Create a separate data structure (a Tree Model) that represents the hierarchy.
  2. Index by GlobalID: Build a lookup map (Map<string, any>) where keys are ExpressIDs and values are UI nodes.
  3. Recursive Fetch: Use the IFC API to fetch IfcRelContainedInSpatialStructure and IfcRelDecomposes. This traverses the “Is Defined By” and “Decomposes” relationships.
  4. Link UI to Viewer:
    • When a user clicks a “Storey” node, the code looks up all ExpressIDs contained in that storey and calls Hider.show(expressIDs).
    • When a user clicks an “Assembly”, the code identifies the assembly’s parts and toggles them.
  5. Caching: Perform this hierarchy calculation once on model load and cache it. It is expensive to do on every click.

Why Juniors Miss It

  • “It’s just a setting”: Juniors often assume that since BIM Vision does it, the underlying component (ThatOpenComponents) must have a flag like hierarchyMode: 'spatial'. They search for a property that doesn’t exist.
  • Underestimating IFC Graph: They look for a simple “Parent” property on an element. In IFC, parentage is defined via relationships (Entities in IfcRel...). Traversing this graph requires understanding IFC object definitions, not just viewing geometry.
  • Tight Coupling: Juniors often try to write the logic directly inside the UI event handler (e.g., inside onClick). This leads to performance issues. Seniors know to pre-process the data structure before rendering the UI.