5 Feb 2024

Adding a Systems Browser in Viewer

systems browser

Introduction

This blog post shares a Viewer extension that builds a tree view of the systems available in the rendered design.

It works based on the system-related properties from a model, and the idea is to build a DockingPanel on top of the Viewer that hosts the nodes.

We can have nodes created referring from Systems to instances of elements, so when you click them, the correlated elements get highlighted in the scene.

The approach

To achieve that we're going to leverage Petr's Custom Tree Views blog and Viewer methods shared below.

Preparing Systems Data

The first thing we need to do is prepare the Sytems Data as soon as our design gets loaded. That's achieved with the snippet below.

async getSystemsData(model) {
  // First we grab all MEP Systems for classifications
  let systems = await getSystems(model);
  let SYSTEMS_DATA = {
    name: 'Systems',
    path: 'systems',
    dbIds: [],
    entries: []
  };

  // Here we grab all the leaf nodes
  const leafNodeDbIds = await this.findLeafNodes(model);
  // Here we retrieve system-related properties for the leaf nodes
  let leafNodeResults = await this.getBulkPropertiesAsync(model, leafNodeDbIds, { propFilter: [SYSTEM_TYPE_PROPERTY, SYSTEM_NAME_PROPERTY, SYSTEM_CIRCUIT_NUMBER_PROPERTY, 'name', 'Category'] });
  leafNodeResults = leafNodeResults.filter(e => e.properties.length >= 2);

  for (const system of systems) {
    //For each MEP systems we retrieve its properties
    let systemName = system.name;
    let familyNameProp = system.properties.find(p => p.attributeName == REVIT_FAMILY_NAME_PROPERTY);
    // Here we transform system name to correct classifications
    let systemClassificationName = getSystemClassification(familyNameProp?.displayValue);

    //And then we start populating the SYSTEMS_DATA object
    let currentSystemClassification = SYSTEMS_DATA.entries.find(s => s.name == systemClassificationName);
    if (!currentSystemClassification) {
      SYSTEMS_DATA.entries.push({ name: systemClassificationName, path: `systems/${systemClassificationName}`, dbIds: [], entries: [] });
      currentSystemClassification = SYSTEMS_DATA.entries.find(s => s.name == systemClassificationName);
    }

    let currentSystem = null;
    if (systemClassificationName == 'Electrical') {
      currentSystem = currentSystemClassification;
    } else {
      currentSystem = currentSystemClassification.entries.find(s => s.name == systemName);
      if (!currentSystem) {
        currentSystemClassification.entries.push({ name: systemName, path: `systems/${systemClassificationName}/${systemName}`, dbIds: [], entries: [] });
        currentSystem = currentSystemClassification.entries.find(s => s.name == systemName);
      }
    }

    // Here we grab all MEP System types and their system-related properties
    let systemTypeDbIds = system.properties.filter(p => p.attributeName == CHILD_PROPERTY).map(p => p.displayValue);
    let systemTypeResults = await this.getBulkPropertiesAsync(model, systemTypeDbIds, { propFilter: [SYSTEM_TYPE_PROPERTY, SYSTEM_NAME_PROPERTY, SYSTEM_CIRCUIT_NUMBER_PROPERTY, 'name'] });

    for (let systemTypeResult of systemTypeResults) {
      //For system type we retrieve properties for the leaf nodes
      let systemTypeTypeProp = systemTypeResult.properties.find(p => p.attributeName == SYSTEM_TYPE_PROPERTY);
      let systemTypeNameProp = systemTypeResult.properties.find(p => p.attributeName == SYSTEM_NAME_PROPERTY);
      let circuitNumberProp = systemTypeResult.properties.find(p => p.attributeName == SYSTEM_CIRCUIT_NUMBER_PROPERTY);

      let systemTypeName = systemTypeNameProp?.displayValue;
      let systemTypeEntryPath = `systems/${systemClassificationName}/${systemName}/${systemTypeName}`;
      if (systemClassificationName == 'Electrical') {
        systemTypeName = systemTypeTypeProp?.displayValue;
        systemTypeEntryPath = `systems/${systemClassificationName}/${systemTypeName}`;
      }

      let currentSystemType = currentSystem.entries.find(st => st.name == systemTypeName);
      if (!currentSystemType) {
        currentSystem.entries.push({ name: systemTypeName, path: systemTypeEntryPath, dbIds: [], entries: [] });
        currentSystemType = currentSystem.entries.find(st => st.name == systemTypeName);
      }

      // Here we retrieve system-end elements by their system type value from the leaf nodes
      let endElementResults = null;
      let prevCurrentSystemType = null;
      if (systemClassificationName == 'Electrical') {
        let circuitNumberVal = circuitNumberProp?.displayValue;
        let currentCircuitNumber = currentSystemType.entries.find(st => st.name == circuitNumberVal);
        if (!currentCircuitNumber) {
          currentSystemType.entries.push({ name: circuitNumberVal, path: `${systemTypeEntryPath}/${circuitNumberVal}`, dbIds: [], entries: [] });
          currentCircuitNumber = currentSystemType.entries.find(st => st.name == circuitNumberVal);
        }

        prevCurrentSystemType = currentSystemType;
        currentSystemType = currentCircuitNumber;
        let endElementSearchTerm = SYSTEM_CIRCUIT_NUMBER_PROPERTY;
        endElementResults = leafNodeResults.filter(e =>
          (e.properties.find(prop => prop.attributeName == endElementSearchTerm && prop.displayValue == currentSystemType.name) != null)
        );
      } else {
        let endElementSearchTerm = SYSTEM_NAME_PROPERTY;
        endElementResults = leafNodeResults.filter(e =>
          (e.properties.find(prop => prop.attributeName == endElementSearchTerm && prop.displayValue.split(',').some(s => s == currentSystemType.name)) != null)
        );
      }

      for (let endElement of endElementResults) {
        // Each system-end element we put it into correct system type
        let endElementName = endElement.name;
        let currentEndElement = currentSystemType.entries.find(st => st.name == endElementName);
        if (!currentEndElement) {
          currentSystemType.entries.push({ name: endElementName, path: `${currentSystemType}/${endElementName}`, dbIds: [endElement.dbId], entries: [] });
          currentEndElement = currentSystemType.entries.find(st => st.name == endElementName);
        }
        currentSystemType.dbIds.push(endElement.dbId);
        prevCurrentSystemType?.dbIds.push(endElement.dbId);
        currentSystem.dbIds.push(endElement.dbId);
        currentSystemClassification.dbIds.push(endElement.dbId);
      }

      // Remove unused system types for electrical system as Revit does
      if (currentSystemType.entries.length <= 0 && prevCurrentSystemType != null) {
        let idx = prevCurrentSystemType.entries.indexOf(currentSystemType);
        if (idx != -1)
          prevCurrentSystemType.entries.splice(idx, 1);
      }
    }
  }

  return SYSTEMS_DATA;
}
  • In this case, each node contains an array of dbIds that we can use to isolate its elements

Reacting to node click

We're also adding a reaction when a node gets clicked, so all the related dbIds get isolated.

thumbnail

You can find a live demo and its source below:

LIVE DEMO

SOURCE CODE

Related Article