5 Feb 2024
Adding a Systems Browser in Viewer
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.
You can find a live demo and its source below: