Skip to main content

Overview

The Element Selection feature provides comprehensive tools for interacting with IFC building elements in the 3D viewer. Select elements with a click, inspect their properties, explore relationships, and control visibility - all through an intuitive interface.
Selection works seamlessly with both standard and streamed IFC models, handling projects of any size.

Selection Methods

Multiple ways to select elements in your BIM model:

Click Selection

Click any element in the 3D viewer to select it and view its properties

Multi-Selection

Hold Ctrl/Cmd while clicking to select multiple elements simultaneously

Focus Selection

Use the Focus button to zoom the camera to your current selection

Chart Selection

Click chart elements to select all items of that type or on that building level

Highlighter System

The selection system is powered by the ThatOpen Components Highlighter:
const highlighter = components.get(OBF.Highlighter);
highlighter.setup({ world });
highlighter.zoomToSelection = true;

Highlighter Features

The highlighter is automatically configured when the world is created, ensuring seamless integration.

Selection Toolbar

The Selection toolbar provides essential visibility and navigation controls:
return BUI.Component.create<BUI.PanelSection>(() => {
  return BUI.html`
    <bim-toolbar-section label="Selection" icon="ph:cursor-fill">
      <bim-button @click=${onShowAll} label="Show All" icon="tabler:eye-filled" tooltip-title="Show All" tooltip-text="Shows all elements in all models."></bim-button>
      <bim-button @click=${onToggleVisibility} label="Toggle Visibility" icon="tabler:square-toggle" tooltip-title="Toggle Visibility" tooltip-text="From the current selection, hides visible elements and shows hidden elements."></bim-button>
      <bim-button @click=${onIsolate} label="Isolate" icon="prime:filter-fill" tooltip-title="Isolate" tooltip-text="Isolates the current selection."></bim-button>
      <bim-button @click=${onFocusSelection} label="Focus" icon="ri:focus-mode" tooltip-title="Focus" tooltip-text="Focus the camera to the current selection."></bim-button>
    </bim-toolbar-section> 
  `;
});

Toolbar Actions

Makes all elements in all loaded models visible, clearing any isolation or hiding.Shortcut: Useful for resetting the view after exploring specific elements.
Inverts the visibility of selected elements - hides visible ones, shows hidden ones.Use Case: Quick way to remove clutter without losing selection context.
Hides all elements except the current selection, focusing attention on specific components.Pro Tip: Perfect for analyzing complex areas without distractions.
Smoothly animates the camera to frame the selected elements optimally.Behavior: Sphere radius is expanded 1.2x for comfortable viewing distance.

Show All Implementation

Restore full model visibility with a single click:
const onShowAll = () => {
  const streamedFragsToShow: FRAGS.FragmentIdMap = {};

  for (const [, fragment] of fragments.list) {
    if (fragment.group?.isStreamed) {
      streamedFragsToShow[fragment.id] = new Set(fragment.ids);
      continue;
    }

    fragment.setVisibility(true);
    const cullers = components.get(OBC.Cullers);
    for (const [, culler] of cullers.list) {
      const culled = culler.colorMeshes.get(fragment.id);
      if (culled) culled.count = fragment.mesh.count;
    }
  }

  if (Object.keys(streamedFragsToShow).length) {
    streamer.setVisibility(true, streamedFragsToShow);
  }
};

Process Flow

1

Fragment Iteration

System iterates through all fragments (geometry chunks) in all loaded models.
2

Streamed vs Static Handling

Distinguishes between streamed (LOD) and static models for appropriate visibility control.
3

Visibility Restoration

Sets visibility to true for all fragments, making everything visible again.
4

Culler Update

Updates the culling system to reflect the full mesh count for proper rendering.

Toggle Visibility

Invert visibility for selected elements:
const onToggleVisibility = () => {
  const selection = highlighter.selection.select;
  if (Object.keys(selection).length === 0) {
    return;
  }
  const meshes = new Set<THREE.InstancedMesh>();
  const streamedFrags: FRAGS.FragmentIdMap = {};

  for (const fragmentID in selection) {
    const fragment = fragments.list.get(fragmentID);
    if (!fragment) continue;

    if (fragment.group?.isStreamed) {
      streamedFrags[fragmentID] = selection[fragmentID];
      continue;
    }

    meshes.add(fragment.mesh);
    const expressIDs = selection[fragmentID];
    for (const id of expressIDs) {
      const isHidden = fragment.hiddenItems.has(id);
      fragment.setVisibility(isHidden, [id]);
    }
  }

  if (meshes.size) {
    cullers.updateInstanced(meshes);
  }

  if (Object.keys(streamedFrags).length) {
    for (const fragmentID in streamedFrags) {
      const fragment = fragments.list.get(fragmentID);
      if (!fragment) continue;
      const ids = streamedFrags[fragmentID];

      for (const id of ids) {
        const isHidden = fragment.hiddenItems.has(id);
        streamer.setVisibility(isHidden, { [fragment.id]: new Set([id]) });
      }
    }
  }
};
The system checks fragment.hiddenItems to determine current visibility state before toggling, ensuring accurate inversion.

Isolate Selection

Focus exclusively on selected elements:
const onIsolate = () => {
  const selection = highlighter.selection.select;
  if (Object.keys(selection).length === 0) return;

  const meshes = new Set<THREE.InstancedMesh>();
  const streamedFragsToHide: FRAGS.FragmentIdMap = {};
  const streamedFragsToShow: FRAGS.FragmentIdMap = {};
  const staticFragsToShow: FRAGS.FragmentIdMap = {};

  // First, hide everything
  for (const [, fragment] of fragments.list) {
    if (fragment.group?.isStreamed) {
      streamedFragsToHide[fragment.id] = new Set(fragment.ids);
      continue;
    }
    fragment.setVisibility(false);
    meshes.add(fragment.mesh);
  }

  // Then, show only selected elements
  for (const fragmentID in selection) {
    const fragment = fragments.list.get(fragmentID);
    if (!fragment) continue;
    
    if (fragment.group?.isStreamed) {
      streamedFragsToShow[fragmentID] = selection[fragmentID];
    } else {
      staticFragsToShow[fragmentID] = selection[fragmentID];
    }
  }

  if (Object.keys(staticFragsToShow).length) {
    hider.set(true, selection);
    cullers.updateInstanced(meshes);
  }

  if (Object.keys(streamedFragsToHide).length || Object.keys(streamedFragsToShow).length) {
    streamer.setVisibility(false, streamedFragsToHide);
    streamer.setVisibility(true, streamedFragsToShow);
  }
};

Isolation Strategy

1

Global Hide

First pass hides all elements in the model, creating a clean slate.
2

Selection Categorization

Selected elements are categorized into streamed and static groups.
3

Selective Show

Only selected elements are made visible using the appropriate method for their type.
4

Render Update

Culling system is updated to reflect the new visibility state for optimal performance.

Focus Selection

Automatically frame selected elements in the viewport:
const onFocusSelection = async () => {
  if (!world) return;
  if (!world.camera.hasCameraControls()) return;

  const bbox = components.get(OBC.BoundingBoxer);
  const fragments = components.get(OBC.FragmentsManager);
  bbox.reset();

  const selected = highlighter.selection.select;
  if (!Object.keys(selected).length) return;

  for (const fragID in selected) {
    const fragment = fragments.list.get(fragID);
    if (!fragment) continue;
    const ids = selected[fragID];
    bbox.addMesh(fragment.mesh, ids);
  }

  const sphere = bbox.getSphere();
  const i = Infinity;
  const mi = -Infinity;
  const { x, y, z } = sphere.center;
  const isInf = sphere.radius === i || x === i || y === i || z === i;
  const isMInf = sphere.radius === mi || x === mi || y === mi || z === mi;
  const isZero = sphere.radius === 0;
  if (isInf || isMInf || isZero) {
    return;
  }

  sphere.radius *= 1.2;
  const camera = world.camera;
  await camera.controls.fitToSphere(sphere, true);
};

Focus Algorithm

The BoundingBoxer component calculates the minimum bounding box containing all selected elements.
The bounding box is converted to a bounding sphere for smooth camera animation.
System validates sphere properties to prevent infinite, zero, or invalid camera positions.
Sphere radius is increased by 20% (1.2x) to provide comfortable viewing distance.
Camera smoothly animates to fit the sphere using fitToSphere with animation enabled.
The Focus function is particularly useful after isolating elements or when selected elements are off-screen.

Property Inspection Panel

When elements are selected, a dedicated panel displays their properties:
return BUI.Component.create<BUI.Panel>(() => {
  return BUI.html`
    <bim-panel>
      <bim-panel-section name="selection" label="Selection Information" icon="solar:document-bold" fixed>
        <div style="display: flex; gap: 0.375rem;">
          <bim-text-input @input=${search} vertical placeholder="Search..." debounce="200"></bim-text-input>
          <bim-button style="flex: 0;" @click=${toggleExpanded} icon="eva:expand-fill"></bim-button>
          <bim-button style="flex: 0;" @click=${() => propsTable.downloadData("ElementData", "tsv")} icon="ph:export-fill" tooltip-title="Export Data" tooltip-text="Export the shown properties to TSV."></bim-button>
        </div>
        ${propsTable}
      </bim-panel-section>
    </bim-panel> 
  `;
});

Panel Features

Search Filter

Real-time property search with 200ms debounce for smooth filtering

Expand/Collapse

Toggle between compact and expanded views of property hierarchies

Export Data

Download property data as TSV (Tab-Separated Values) for external analysis

Properties Table

The properties table is powered by ThatOpen UI components:
const [propsTable, updatePropsTable] = CUI.tables.elementProperties({
  components,
  fragmentIdMap: {},
});

propsTable.preserveStructureOnFilter = true;
fragments.onFragmentsDisposed.add(() => updatePropsTable());

highlighter.events.select.onHighlight.add((fragmentIdMap) => {
  if (!viewportGrid) return;
  viewportGrid.layout = "second";
  propsTable.expanded = false;
  updatePropsTable({ fragmentIdMap });
});

Table Behavior

1

Dynamic Creation

The table is created using the elementProperties helper from ThatOpen UI-OBC.
2

Structure Preservation

Property hierarchy is maintained even when filtering (preserveStructureOnFilter: true).
3

Selection Integration

Table automatically updates when the highlighter selection changes.
4

Layout Switching

Viewport switches to “second” layout to show the properties panel alongside the 3D view.
Real-time search filtering for quick property lookup:
const search = (e: Event) => {
  const input = e.target as BUI.TextInput;
  propsTable.queryString = input.value;
};
Search has a 200ms debounce to prevent excessive filtering during typing, improving performance.

Expand/Collapse Control

const toggleExpanded = () => {
  propsTable.expanded = !propsTable.expanded;
};
Use expanded view to see deeply nested property sets and relationships. Use collapsed view for quick overview.

Clear Selection

When selection is cleared, the panel automatically hides:
highlighter.events.select.onClear.add(() => {
  updatePropsTable({ fragmentIdMap: {} });
  if (!viewportGrid) return;
  viewportGrid.layout = "main";
});

Layout Management

Template: "leftPanel viewport" 1fr / 29rem 1frLeft panel and viewport without properties panel.

Streamed vs Static Models

The selection system intelligently handles both model types:
Standard IFC LoadingAll geometry is loaded into memory. Selection uses fragment.setVisibility() and direct mesh manipulation.Best For: Models under 50MB with moderate complexity.
Progressive LOD LoadingGeometry is loaded on-demand. Selection uses streamer.setVisibility() with fragment ID maps.Best For: Large models over 100MB, complex buildings with many elements.
The system automatically detects model type using fragment.group?.isStreamed and applies the appropriate visibility control method.

Performance Optimizations

Culling Integration

Visibility changes trigger culling updates for optimal rendering:
if (meshes.size) {
  cullers.updateInstanced(meshes);
}
Culling updates are batched - only meshes affected by visibility changes are processed, minimizing overhead.

Fragment ID Maps

The system uses efficient fragment ID maps for selection:
const streamedFrags: FRAGS.FragmentIdMap = {};
streamedFrags[fragmentID] = selection[fragmentID];
Structure: { [fragmentId: string]: Set<number> }
  • Key: Fragment ID (geometry chunk identifier)
  • Value: Set of Express IDs (individual element IDs)

Best Practices

1

Single Click for Quick Inspection

Click any element to instantly see its properties without losing context.
2

Multi-Select for Comparison

Ctrl/Cmd-click multiple elements to compare properties side-by-side.
3

Use Isolate for Complex Areas

In dense models, isolate elements to focus on specific components without distraction.
4

Search for Specific Properties

Use the search function to quickly find elements by property name or value.
5

Export for Documentation

Export property data to TSV for inclusion in reports or further analysis.

Keyboard Shortcuts

While not explicitly defined in the code, standard browser interactions work:

Ctrl/Cmd + Click

Add element to selection (multi-select)

Click Empty Space

Clear selection and hide properties panel

Scroll in Panel

Navigate through element properties

Type in Search

Filter properties in real-time

Element Property Types

The properties table displays comprehensive IFC data:

Basic Attributes

Name, GlobalId, Type, Description, Object Type

Geometric Properties

Dimensions, coordinates, placement, representation

Material Associations

Materials, layers, material properties

Relationships

Spatial containment, aggregations, connections, definitions

Property Sets

Custom properties, quantities, classifications

Type Properties

Shared properties from element type definitions

Common Use Cases

Select elements to verify properties match specifications. Export data for compliance reporting.
Isolate intersecting elements to understand spatial conflicts. Focus camera for detailed inspection.
Select walls, slabs, or beams to confirm material assignments and specifications.
Multi-select elements by type, export properties to calculate quantities and costs.
Inspect element relationships and spatial containment to ensure model integrity.
Combine selection with the AI Assistant to ask questions about specific elements: “What material is used in the selected wall?”

Build docs developers (and LLMs) love