Unfortunately, there is no direct data to tell the relationship of the "electrical components connected to a wall" case, as Revit data is simplified after processing with the Model Derivative API.
However, you could try to do a collision test to check which wall and electrical component have been contacted. I don't have an out-of-the-box example, but please refer to this sample of "find objects in a room" here to make it support your requirements:
Here is the sample for Revit Group case. It will build a group list and show which element belongs to it.
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
'use strict';
(function () {
class AdnRevitGroupTool extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
}
userFunction(pdb, viewableId) {
//console.log(pdb, viewableId);
let _nameAttrId = pdb.getAttrName();
let _internalViewableInAttrId = pdb.getAttrViewableIn();
let _internalGroupRefAttrId = -1;
// Iterate over all attributes and find the index to the one we are interested in
pdb.enumAttributes(function (i, attrDef, attrRaw) {
let category = attrDef.category;
let name = attrDef.name;
if (name === 'Group' && category === '__internalref__') {
_internalGroupRefAttrId = i;
return true; // to stop iterating over the remaining attributes.
}
});
//console.log( _internalGroupRefAttrId );
// Early return is the model doesn't contain data for "Group".
if (_internalGroupRefAttrId === -1)
return null;
let _internalMemberRefAttrId = -1;
// Iterate over all attributes and find the index to the one we are interested in
pdb.enumAttributes(function (i, attrDef, attrRaw) {
let category = attrDef.category;
let name = attrDef.name;
if (name === 'Member' && category === '__internalref__') {
_internalMemberRefAttrId = i;
return true; // to stop iterating over the remaining attributes.
}
});
//console.log( _internalMemberRefAttrId );
// Early return is the model doesn't contain data for "Member".
if (_internalMemberRefAttrId === -1)
return null;
let _categoryAttrId = -1;
// Iterate over all attributes and find the index to the one we are interested in
pdb.enumAttributes(function (i, attrDef, attrRaw) {
let category = attrDef.category;
let name = attrDef.name;
if (name === 'Category' && category === '__category__') {
_categoryAttrId = i;
return true; // to stop iterating over the remaining attributes.
}
});
//console.log( _categoryAttrId );
// Early return is the model doesn't contain data for "Member".
if (_categoryAttrId === -1)
return null;
const groups = [];
// Now iterate over all parts to find all groups
pdb.enumObjects(function (dbId) {
let isGroup = false;
// For each part, iterate over their properties.
pdb.enumObjectProperties(dbId, function (attrId, valId) {
// Only process 'Caegory' property.
// The word "Property" and "Attribute" are used interchangeably.
if (attrId === _categoryAttrId) {
const value = pdb.getAttrValue(attrId, valId);
if (value === 'Revit Group') {
isGroup = true;
// Stop iterating over additional properties when "Category: Revit Group" is found.
return true;
}
}
});
if (!isGroup) return;
const children = [];
let groupName = '';
// For each part, iterate over their properties.
pdb.enumObjectProperties(dbId, function (attrId, valId) {
// Only process 'Member' property.
// The word "Property" and "Attribute" are used interchangeably.
if (attrId === _internalMemberRefAttrId) {
const value = pdb.getAttrValue(attrId, valId);
const props = pdb.getObjectProperties(value);
//console.log(value, pdb.getObjectProperties(value));
if (props.name.includes('Room Tag'))
return;
pdb.enumObjectProperties(value, function (childAttrId, childAttrValId) {
if (childAttrId === _internalViewableInAttrId) {
const childAttrVal = pdb.getAttrValue(childAttrId, childAttrValId);
//console.log(value, childAttrVal);
if ((childAttrVal == viewableId) && (!children.includes(value))) {
children.push(value);
return true;
}
}
});
}
if (attrId === _nameAttrId) {
const value = pdb.getAttrValue(attrId, valId);
const groupExtId = pdb.getIdAt(dbId);
if (groupExtId) {
const result = groupExtId.split('-');
const rvtElementId = parseInt(result[result.length - 1], 16);
groupName = `${value} [${rvtElementId}]`;
} else {
groupName = value;
}
}
});
if (children.length > 0) {
groups.push({
dbId,
name: groupName,
children
});
}
});
return groups;
}
async build() {
try {
const fucString = this.userFunction.toString();
const userFunction = `function ${fucString}`;
const model = this.viewer.model;
const viewableId = model.getDocumentNode().data['viewableID'];
return await model.getPropertyDb().executeUserFunction(userFunction, viewableId);
} catch (ex) {
console.error(ex);
return null;
}
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.AdnRevitGroupTool', AdnRevitGroupTool);
class AdnRevitGroupPanel extends Autodesk.Viewing.UI.DockingPanel {
constructor(viewer, title, options) {
options = options || {};
// Height adjustment for scroll container, offset to height of the title bar and footer by default.
if (!options.heightAdjustment)
options.heightAdjustment = 70;
if (!options.marginTop)
options.marginTop = 0;
super(viewer.container, viewer.container.id + 'AdnRevitGroupPanel', title, options);
this.container.classList.add('adn-docking-panel');
this.container.classList.add('adn-rvt-group-panel');
this.createScrollContainer(options);
this.viewer = viewer;
this.options = options;
this.uiCreated = false;
// Pre-load extension
viewer.loadExtension('Autodesk.ADN.AdnRevitGroupTool');
this.addVisibilityListener((show) => {
if (!show) return;
if (!this.uiCreated)
this.createUI();
});
this.onShowAll = this.onShowAll.bind(this);
}
onShowAll() {
$(this.treeContainer).jstree().deselect_all(true);
}
get tool() {
return this.viewer.getExtension('Autodesk.ADN.AdnRevitGroupTool');
}
async getGroupData() {
try {
return await this.tool?.build();
} catch (ex) {
console.error(ex);
return null;
}
}
genGuid(format = 'xxxxxxxxxx') {
let d = new Date().getTime();
return format.replace(
/[xy]/g,
function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
});
}
getNodeName(dbId) {
return new Promise((resolve, reject) => {
this.viewer.getProperties(
dbId,
result => {
let name = result.name;
const externalId = result.externalId;
if (externalId) {
const result = externalId.split('-');
const rvtElementId = parseInt(result[result.length - 1], 16);
name = `${name} [${rvtElementId}]`;
}
resolve(name);
},
error => reject(error)
);
});
}
async requestContent() {
const data = await this.getGroupData();
if (!data) return;
const nodes = [];
for (let i = 0; i < data.length; i++) {
const group = data[i];
if (!group || group.children.length <= 0) continue;
const node = {
id: this.genGuid(),
dbId: group.dbId,
type: 'groups',
text: group.name,
children: await Promise.all(group.children.map(async (childId) => {
const childName = await this.getNodeName(childId);
return {
id: this.genGuid(),
dbId: childId,
type: 'members',
text: childName
};
}))
};
//node.children = node.children.filter(child => !child.name.includes('Room Tag'));
nodes.push(node);
}
// if (nodes.length > 0) {
// nodes.sort((a, b) => {
// return (a.text > b.text) ? 1 : -1;
// });
// nodes[0].state = { 'opened': true };
// }
$(this.treeContainer)
.jstree({
core: {
data: nodes,
multiple: false,
themes: {
icons: false,
dots: false,
name: 'default-dark'
}
},
sort: function (a, b) {
const a1 = this.get_node(a);
const b1 = this.get_node(b);
return (a1.text > b1.text) ? 1 : -1;
},
types: {
groups: {},
members: {}
},
plugins: ['types', 'sort', 'wholerow'],
})
.on('hover_node.jstree', async (e, data) => {
//console.log(data);
if (data.node.type === 'groups') {
const children = data.node.children;
children.forEach(child => {
let node = data.instance.get_node(child);
this.viewer.impl.highlightObjectNode(this.viewer.model, node.original.dbId, true, false);
});
} else {
this.viewer.impl.highlightObjectNode(this.viewer.model, data.node.original.dbId, true, false);
}
})
.on('dehover_node.jstree', async (e, data) => {
if (data.node.type === 'groups') {
const children = data.node.children;
children.forEach(child => {
let node = data.instance.get_node(child);
this.viewer.impl.highlightObjectNode(this.viewer.model, node.original.dbId, false);
});
} else {
this.viewer.impl.highlightObjectNode(this.viewer.model, data.node.original.dbId, false);
}
const selSet = this.viewer.getSelection();
if (selSet.length > 0) {
this.viewer.clearSelection();
this.viewer.impl.clearOverlay('selection');
this.viewer.select(selSet);
}
})
.on('changed.jstree', async (e, data) => {
// console.log(e, data);
if (!data.node || !data.node.type) {
return;
}
this.viewer.clearSelection();
this.viewer.impl.clearOverlay('selection');
if (data.action === 'select_node') {
let dbIds = null;
if (data.node.type === 'groups') {
const children = data.node.children;
dbIds = children.map(child => {
let node = data.instance.get_node(child);
return node.original.dbId;
});
this.viewer.select(data.node.original.dbId);
} else {
dbIds = [data.node.original.dbId];
this.viewer.select(dbIds);
}
this.viewer.fitToView(dbIds);
this.viewer.isolate(dbIds);
}
});
this.resizeToContent();
}
async createUI() {
if (this.uiCreated) return;
this.uiCreated = true;
const div = document.createElement('div');
const treeDiv = document.createElement('div');
div.appendChild(treeDiv);
this.treeContainer = treeDiv;
this.scrollContainer.appendChild(div);
if (this.viewer.model.isLoadDone()) {
await this.requestContent();
} else {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
() => this.requestContent(),
{ once: true }
);
}
this.viewer.addEventListener(
Autodesk.Viewing.SHOW_ALL_EVENT,
this.onShowAll
)
}
uninitialize() {
if (this.tool)
this.viewer.unloadExtension('Autodesk.ADN.AdnRevitGroupTool');
this.viewer.removeEventListener(
Autodesk.Viewing.SHOW_ALL_EVENT,
this.onShowAll
);
super.uninitialize();
}
}
class AdnRevitGroupPanelExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.panel = null;
this.createUI = this.createUI.bind(this);
this.onToolbarCreated = this.onToolbarCreated.bind(this);
this.onGeometriesLoaded = this.onGeometriesLoaded.bind(this);
}
onToolbarCreated() {
this.createUI();
}
onGeometriesLoaded() {
this.viewer.getExtension('Autodesk.ViewCubeUi')?.setViewCube('front/top/right');
}
createUI() {
const viewer = this.viewer;
const rvtGroupPanel = new AdnRevitGroupPanel(viewer, 'Revit Groups');
viewer.addPanel(rvtGroupPanel);
this.panel = rvtGroupPanel;
const rvtGroupButton = new Autodesk.Viewing.UI.Button('toolbar-adnRevitGroupTool');
rvtGroupButton.setToolTip('Revit Group');
rvtGroupButton.setIcon('adsk-icon-documentModels');
rvtGroupButton.onClick = function () {
rvtGroupPanel.setVisible(!rvtGroupPanel.isVisible());
};
const subToolbar = new Autodesk.Viewing.UI.ControlGroup('toolbar-adn-tools');
subToolbar.addControl(rvtGroupButton);
subToolbar.adnRvtGroupButton = rvtGroupButton;
this.subToolbar = subToolbar;
viewer.toolbar.addControl(this.subToolbar);
rvtGroupPanel.addVisibilityListener(function (visible) {
if (visible)
viewer.onPanelVisible(rvtGroupPanel, viewer);
rvtGroupButton.setState(visible ? Autodesk.Viewing.UI.Button.State.ACTIVE : Autodesk.Viewing.UI.Button.State.INACTIVE);
});
}
load() {
if (this.viewer.toolbar) {
// Toolbar is already available, create the UI
this.createUI();
}
if ((!this.viewer.model) || (!this.viewer.model.isLoadDone())) {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
this.onGeometriesLoaded,
{ once: true });
} else {
this.onGeometriesLoaded();
}
return true;
}
unload() {
if (this.panel) {
this.panel.uninitialize();
delete this.panel;
this.panel = null;
}
if (this.subToolbar) {
this.viewer.toolbar.removeControl(this.subToolbar);
delete this.subToolbar;
this.subToolbar = null;
}
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.RevitGroupPanel', AdnRevitGroupPanelExtension);
})();
Here is the full sample: https://github.com/yiskang/au-2021-forge-lightning-talk-sample/blob/main/server/www/js/revit-group.js
Additionally, here is an example for showing Revit system hierarchy. You can find the full sample here: https://github.com/autodesk-platform-services/aps-viewer-revit-systems-extension
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;
}