0

I'm currently using the Autodesk Forge APIs to access the Autodesk Construction Cloud. I am able to fetch a Revit file, and I’m displaying it on my platform sheet-wise. I’m able to fetch the sheet-wise JSON, which I store in a database.

The JSON contains all the element IDs that are present in the views. However, I’m facing an issue: while I have the JSON data for each element, I’m unable to determine the relationships between the elements (e.g., if an element is part of another element, or how elements are connected within a view or structure).

I’ve explored the Forge Model Derivative API and the BIM 360 API, but I haven't been able to find an appropriate solution.

Details:

I am working with the Forge APIs (Viewer, Model Derivative, etc.)

The JSON returned for each element contains basic information but lacks relational data between elements.

I am trying to discover relationships such as:

Which elements are connected (e.g., electrical components connected to a wall)

Hierarchical relationships (e.g., components grouped under a parent element)

If anyone has faced a similar issue or knows how to retrieve or infer element relationships through the API, your help would be greatly appreciated!

1 Answer 1

0

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;
} 
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.