28 Feb 2022

Filtering Specific Elements Efficiently by Model Properties API

By Xiaodong Liang

In the workflow of collaboration, it is a common scenario to inspect specific objects of the models only, watch the changes or transfer the data to the collaborators for further analysis. It has been one approach with Model Derivative API of Forge, by downloading all properties and filter them out, or searching by one object Id, or downloading database (sql-lite) to organize/search objects. The other approach is by Forge Viewer, loading the model and calling the API viewer.search(). 

With the releasing of Model Properties API, we have a new efficient way that could filter specific elements more efficiently, when working with BIM360/ACC models.  The API documents mentioned in the other blog are good start to learn the API. This blog tries to summarize some of the knowledges and tricks of filtering elements.

All demo payloads in this blog are based on the indexing of sample models at forge-model-properties.powershell

Property Indexing

To query elements, the first step with Model Properties API is to build indexing with the SVF Property Database and adds SVF2 viewable IDs to the indexing. Once SVF2 translation is completed, the model version can be indexed on demand. After indexing succeeds, the related data will be available:

  • Manifest: the information about model version, seed file, views, databaseId etc.
  • Fields: corresponds to the columns of the properties database of the model. Each field has one unique key (which will be used when building queries). The data also tells the field name, type.
                {
                    "key":"p5eddc473",
                    "category":"__category__",
                    "type":"String",
                    "name":"Category",
                    "uom":null
                }
                {
                    "key":"p03b7a1be",
                    "category":"Constraints",
                    "type":"Double",
                    "name":"Elevation from Level",
                    "uom":"autodesk.unit.unit:feetFractionalInches-1.0.0"
                }
  • Properties: properties of the model objects. Some notes are enclosed in the demo data below.
{
    //basic data (Ids) such as SVF2Id
    // otgId is for compatible with some models which are translated to OTG (an intermediate format before SVF2 was born). 
    //lmvId means the objectId of SVF(1). Since now we have migrated to SVF2, we can use SVF2 in the workflow directly.
    
    "svf2Id": 3963,
    "otgId": 3963,
    "lineageId": "9da40e15",
    "externalId": "f781426a-0a88-4f9c-b975-d65331d5fd84-002163f6",
    "lmvId": 18458,
    "databaseId": "f08e817a",
    
    // properties in a format of field(key): value. 
    // To build interface for end-users in application, we will need to check the meaningful name of the field by key. 
    "props": {
        "p00723fa6": "Main Model",
        "p01bbdcf2": "FIRST FLOOR",
        "p03b7a1be": 1.416666666666674,
        "p5eddc473": "Revit Doors",
       …
     }
    
    //property hash and geometry hash, which can be used to identified if the objects have been changed in model versions
    "propsHash": "e60c0510",
    "geomHash": "1J3RNjTbEdBc7zrVvkatxQ",
    
     //bounding-box data if it is a geometry object
    "bboxMin": {"x": 26.864536257715876, "y": -20.060353907387572, "z": 30.751904914855952},
    "bboxMax": { "x": 27.206442070037482,"y": -19.959561494806948, "z": 31.572199975776666},
    
    //which view the object belongs to.  The manifest will tell the id of the views.
    "views": [ "cf7900d3", "4be0a9fd"]
}

Both data of fields and properties are formatted as compressed line delimited JSON. We can download them directly by Model Properties API. 

Query 

Usually, the workflow may not need to download all properties unless it is necessary. Query will be used more frequently.  Due to indexing, the query will be in high efficiency and the query can be in granular control with sophisticated conditions (SQL AST/binary expression). It could help the project members/collaborators to focus on critical objects/changes only.  The result properties data will be only about those specific objects. 

The query of Model Properties API supports two parameters in the payload:

  • Query: the conditions. They can be combined with AND, OR. The Query Language Reference illustrates all possible conditions. First, ensure the keys exist in available Fields of the model, and input the correct path of the keys. e.g. With the demo data of Properties above, the path of some properties are:
             path of SVF2Id is s.SVF2Id
             path of p01bbdcf2 is s.props.p01bbdcf2
             x of bbxMin is s.bbxMin.x 

Next, compose the conditions. Some formats of the granule condition are:

            operator : path of the key  
                    such as "$notnull" (is not null), "$isnull"(is null) 
            operator : [path of the key, value to compare]  
                    such as "$gt" (greater than), "$eq"(equal to) 
            operator:[path of the key, value1, value2] 
                    such as "$between" (value in a range of value1 and value2)

Normally, all sub conditions are within the array of AND or OR. So, if the parent condition is OR, more results will be queried out than ADN.  Note: the string type value must enclose with single quote mark.

{
    "query": { 
       
        "$and": [  //all conditions are ADN in the array
             {
                //one field is not null
                "$notnull": "s.props.p20d8441e" 
             }, 
             {
                //search those objects whose SVF2Id is between 10 and 100 
                "$between": [ "s.SVF2Id", 10,100]
             },
             {
                //  on String field. compare exact value 
                //note: the string type value must enclose with single quote mark
                "$eq": ["s.props.p39d13156", "'byLayer'"] 
             },
             {
                //  on String field. search by wildcard 
                //note: the string type value must enclose with single quote mark
                "$like": ["s.props.p22bd5046", "'%DUCT%'"] 
             },
             { 
                 // BBOX max.z < 40.00
                 "$lt": ["s.bboxMax.z" , 40.00]
             },
             {
                // one Double field > 10.00
               "$gt": ["s.props.p7e6da1e8" , 10.00 ] 
             } 
             
        ] 
    }
}

All conditions within {} are ADN, so the query above is same to the payload below. Since they are within one {}, no matter the parent condition is ADN, OR, the result data are same.

{
    "query": { 
       //all conditions are ADN in the array
        "$and": [  
              {
                //one field is not null
                "$notnull": "s.props.p20d8441e",   
                //search those objects whose SVF2Id is between 10 and 100 
                "$between": [ "s.SVF2Id", 10,100], 
                //  on String field. compare exact value 
                //note: the string type value must enclose with single quote mark
                "$eq": ["s.props.p39d13156", "'byLayer'"], 
                //  on String field. search by wildcard 
                //note: the string type value must enclose with single quote mark
                "$like": ["s.props.p22bd5046", "'%DUCT%'"], 
                 // BBOX max.z < 40.00
                 "$lt": ["s.bboxMax.z" , 40.00], 
                // one Double field > 10.00
               "$gt": ["s.props.p7e6da1e8" , 10.00 ]
              }
        ] 
    }
}

We can also build nested conditions. e.g. the demo payload sets one top ADN with two conditions. In each sub condition, one single condition and one OR conditions array, nested within the sub condition.

{
    "query": { 
       //all conditions are ADN in the array
        "$and": [  
              {//one sub condition
              
                //one field is not null
                "$isnull": "s.props.p20d8441e", 
                "$or":{// nested conditions

                    //  on String field. compare exact value 
                    //note: the string type value must enclose with single quote mark
                    "$eq": ["s.props.p39d13156", "'byLayer'"], 
                    //  on String field. search by wildcard 
                    //note: the string type value must enclose with single quote mark
                    "$like": ["s.props.p22bd5046", "'%DUCT%'"] 
                 } 
              },
              { //one sub condition

                //search those objects whose SVF2Id is between 10 and 100 
                "$between": [ "s.SVF2Id", 10,100],
                "$or":{//nested conditions
                    // BBOX max.z < 40.00
                    "$lt": ["s.bboxMax.z" , 40.00], 
                    // one Double field > 10.00
                    "$gt": ["s.props.p7e6da1e8" , 10.00 ]

                }
              }

        ] 
    }
}

 

  • Columns this is to transform the built-in columns of database, based on the query result above.  (Note: ensure the keys exist in available Fields of your model)
{
    "query": {  
         "$and": [ 
            {
                "$gt": [{ "$count": "s.views" }, 0] 
            }
        ] 
    },
    
    "columns":{
 

        //based on the query result, transform the columns

        //rename the field to my own param title 
        //
        //in indexed properties of this model:
        //p20d8441e is named with [_RC] 
        //p30db51f9 is named with [_RFN] 
        //p63ed81bb is named with [Assembly Description] 
        "revitCategory": "s.props.p20d8441e",
        "revitFamily": "s.props.p30db51f9",
        "my_desired_column_name": "s.props.p63ed81bb", 

        //build columns with calculations of properties
        //p627b7a9e is named with [width] of the element
        //p849f0aa2 is named with [height] of the element  
        "add_width_with_height": {"$add":["s.props.p627b7a9e","s.props.p849f0aa2"]},
        //p81a19145 is named with [roughness] of the element(duct)
        "double_roughness": {"$mul":["s.props.p81a19145",2]} 
     
    }  
     
}

The properties data will be like:

{
    "revitCategory": "Walls",
    "revitFamily": "Curtain Wall",
    "my_desired_column_name": "Curtain Walls",
    "add_width_with_height": 91916666666666490349344662390649318695e-36,
    "double_roughness": null
}
{
    "revitCategory": "Ducts",
    "revitFamily": "Round Duct",
    "add_width_with_height": null,
    "double_roughness": 6.0E-4
}

Sometimes, we may only want to know the stats of the objects, such as the values range (min, max) of specific property, summary of specific property etc, we could use the expressions: $sum, $max, $min etc.

 "columns":{

        //based on the query result, transform the columns 
 
        //get values range of one built-in fields
        // 
        //p1962a707 is named with [Frame Depth] 
        //s.bboxMin.x is the x of min point of bounding-box
        //s.bboxMax.z is the z of max point of bounding-box

        "sum_of_Frame_Depth": {"$sum": "s.props.p1962a707"},
        "min_of_Frame_Depth": {"$min": "s.props.p1962a707"}, 
        "max_of_Frame_Depth": {"$max": "s.props.p1962a707"},
        "min_x_of_bbox_max": {"$min": "s.bboxMin.x"},
        "max_z_of_bbox_min": {"$max": "s.bboxMax.z"}
    } 

In such case, the output will be one json object only:

{
     "sum_of_Frame_Depth":596666666666666655e-16, 
     "min_of_Frame_Depth":3333333333333333e-16,
     "max_of_Frame_Depth":0.375e0,
     "min_x_of_bbox_max":-4787388752534602e-14,
     "max_z_of_bbox_min":3695505825805663e-14
}

As said, the columns are based on the result of query. If we want to get information of all properties, we could specify the payload like below. It will return max value and min value of one specific property (Double) of all objects in the models.

{
    "query": { 
        "$and": [ 
            //get all objects
            true
        ] 
    },
    "columns":{

        //based on the query result, transform the columns 
       //in this case, all objects in the model

        //get values range of one built-in field
        //
        //p1962a707 is named with [Frame Depth] 
        "max_of_Frame_Depth": {"$max": "s.props.p1962a707"},
        "min_of_Frame_Depth": {"$min": "s.props.p1962a707"} 
    } 
}

In the PowerShell utility for testing Model Properties API, we keep adding more examples of queries. Welcome to contribute your demo queries. 

The Indexing can be also combined with Query, if the keys of related fields have been known. Model Property API will process in one call. If no indexing, then the API will build indexing and return the specific objects data with the conditions of query in the custom format ( if columns are specified) . One example from PowerShell test demo query: Determine the stats for Flange cut length.

POST /construction/index/v2/projects/<project_id>/indexes:batch-status

{
    "versions": [
        {
            "versionUrn": "urn:adsk.wipprod:fs.file:vf.OGB3DgCdTQq--zcEsvlm6A?version=1",
            "query": 
            {
                "$and": [
                    { "$gt": ["s.props.p7eba89fd", 0] },
                    { "$like": ["s.props.p153cb174", "''SMBH-W-Wide Flange%''"] }
                ]
            },
            "columns":
            {
                "sum": { "$sum": "s.props.p7eba89fd" },
                "min": { "$min": "s.props.p7eba89fd" },
                "max": { "$max": "s.props.p7eba89fd" },
                "avg": { "$avg": "s.props.p7eba89fd" }
            }
        }
    ]
}

In the other blog, we will introduce the sample that filters elements and load partial objects to Forge Viewer.

 

Related Article