Nested Object links#
A manual investigation of the complex nested object property links found in the IFC schema. This manual query building exercise functions as a stepping stone towards building an automatic query generator for IFC in EdgeDB.
Note
The intention is that the query builder will in fact work on any EdgeDB schema, not only for IFC.
The rooted IFC element IfcBeam
is used as a source for further investigation.
See the IfcBeam docs for more information
about the IfcBeam.
The ifc file MyBeam.ifc is first uploaded to an EdgeDB instance. See below for the full code employed for both uploading IFC and subsequent queries.
import json
from ifcdb import EdgeIO
from ifcdb.utils import top_dir
def main(ifc_file, refresh_db=False):
db_name = ifc_file.replace(".ifc", "").replace("-", "_")
ifc_path = top_dir() / "files" / ifc_file
with EdgeIO(db_schema_dir=f"temp/{db_name}/dbschema", ifc_schema="IFC4x1", database_name=db_name) as io:
if refresh_db:
io.create_schema_from_ifc_file(ifc_path=ifc_path)
io.setup_database(delete_existing_migrations=True)
io.insert_ifc(ifc_path)
# Queries
for qref in ["q1", "q2", "q3"]:
q1_res = json.loads(io.client.query_json(open(f"{qref}.esdl", "r").read()))
with open(f"{qref}_output.json", "w", encoding="utf-8") as f:
json.dump(q1_res, f, indent=4)
if __name__ == "__main__":
main("MyBeam.ifc", refresh_db=False)
Query 1 -> Basic Select#
The basic select statement to get only the top level of the IfcBeam
class can be written like this:
SELECT IfcBeam {
# Properties pointing to native types (str, float64, int64, etc.)
GlobalId,
Description,
Name,
ObjectType,
Tag,
PredefinedType,
# Links to other objects
OwnerHistory,
ObjectPlacement,
Representation,
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
}
which generates the following output:
[
{
"GlobalId": "3PXsnq_3qHxBd2w2f4ZOUQ",
"Description": "IPE400",
"Name": "MyBeam",
"ObjectType": "Beam",
"Tag": "MyBeam",
"PredefinedType": null,
"OwnerHistory": {
"id": "ebe57210-fced-11ec-97f5-57e3b32f3dcd"
},
"ObjectPlacement": {
"id": "eee839b6-fced-11ec-97f5-73fd14217afc"
},
"Representation": {
"id": "ee4d8ae2-fced-11ec-97f5-7f55f62e5c6b"
},
"_e_type": "default::IfcBeam"
}
]
Query 2 -> Get properties of linked objects#
SELECT IfcBeam {
# Properties pointing to native types (str, float64, int64, etc.)
GlobalId,
Description,
Name,
ObjectType,
Tag,
PredefinedType,
# Links to other objects
OwnerHistory : {
# Properties
LastModifiedDate,
CreationDate,
# Links to other objects
OwningUser,
OwningApplication,
State,
ChangeAction,
LastModifyingUser,
LastModifyingApplication,
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
},
ObjectPlacement : {
# ObjectPlacement points to an abstract superclass "IfcObjectPlacement".
# Therefore we need to consider various subtypes using the [is ..].key
# (to reduce size of this file and since we know that for this scenario ObjectPlacement is pointing to an
# IfcLocalPlacement, we will skip the other subtypes "IfcLinearPlacement" & "IfcGridPlacement"
[is IfcLocalPlacement].PlacementRelTo,
[is IfcLocalPlacement].RelativePlacement,
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
},
Representation : {
# Points to an abstract superclass "IfcProductRepresentation". However only the subtype
# "IfcMaterialDefinitionRepresentation" has any unique properties not associated with its parent class and
# it is not used in this particular test IFC file and is therefore omitted.
# Properties
Name,
Description,
# Links to other objects
Representations,
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
}
}
which returns the following output
[
{
"GlobalId": "3PXsnq_3qHxBd2w2f4ZOUQ",
"Description": "IPE400",
"Name": "MyBeam",
"ObjectType": "Beam",
"Tag": "MyBeam",
"PredefinedType": null,
"OwnerHistory": {
"LastModifiedDate": 1656570506,
"CreationDate": 1656570506,
"OwningUser": {
"id": "e9fde1da-fced-11ec-97f5-6f9f1b461637"
},
"OwningApplication": {
"id": "ea228a44-fced-11ec-97f5-d744ce069446"
},
"State": "READWRITE",
"ChangeAction": null,
"LastModifyingUser": {
"id": "e9fde1da-fced-11ec-97f5-6f9f1b461637"
},
"LastModifyingApplication": {
"id": "ea228a44-fced-11ec-97f5-d744ce069446"
},
"_e_type": "default::IfcOwnerHistory"
},
"ObjectPlacement": {
"PlacementRelTo": {
"id": "ee386f0e-fced-11ec-97f5-b3f35fe72359"
},
"RelativePlacement": {
"id": "eee83704-fced-11ec-97f5-dfb5f1ae7674"
},
"_e_type": "default::IfcLocalPlacement"
},
"Representation": {
"Name": null,
"Description": null,
"Representations": [
{
"id": "ed8f000e-fced-11ec-97f5-57758b6c7591"
},
{
"id": "eda27b02-fced-11ec-97f5-13a248609d5b"
}
],
"_e_type": "default::IfcProductDefinitionShape"
}
}
]
In the outputted results you can notice that there is a lot of elements with "id": ".."
. This is EdgeDB’s way of
letting us know that this property is linked to another object.
At the time of writing, there is no “automatic” way of asking in a single query for all related object’s and their properties without knowing upfront what exactly are the related object type’s and the names of their properties.
As a consequence of this, it is necessary to explicitly define the entire path of every related object and their properties. This will be the final step in this experiment
Query 3 -> The completed query shape#
As a final piece in this exercise, it will be attempted to write the entire query shape to return all related object
properties originating from the IfcBeam
class. In other terms, the number of occurrences of "id": ".."
in the
result should be kept as low as possible.
SELECT IfcBeam {
# Properties pointing to native types (str, float64, int64, etc.)
GlobalId,
Description,
Name,
ObjectType,
Tag,
PredefinedType,
# Links to other objects
OwnerHistory : { # IfcOwnerHistory
# Properties
LastModifiedDate,
CreationDate,
# Links to other objects
OwningUser : { # IfcPersonAndOrganization
ThePerson : { # IfcPerson
Identification,
FamilyName,
GivenName,
MiddleNames,
PrefixTitles,
SuffixTitles,
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
Addresses : { # IfcAddress
Purpose,
Description,
UserDefinedPurpose,
_e_type := .__type__.name
},
_e_type := .__type__.name
},
TheOrganization : { # IfcOrganization
Identification,
Name,
Description,
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
Addresses : { # IfcAddress
Purpose,
Description,
UserDefinedPurpose,
_e_type := .__type__.name
},
_e_type := .__type__.name
},
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
}
},
OwningApplication : { # IfcApplication
ApplicationDeveloper : { # IfcOrganization
Identification,
Name,
Description,
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
Addresses : { # IfcAddress
Purpose,
Description,
UserDefinedPurpose
},
_e_type := .__type__.name
},
Version,
ApplicationFullName,
ApplicationIdentifier,
_e_type := .__type__.name
},
State,
ChangeAction,
LastModifyingUser : { # IfcPersonAndOrganization
ThePerson : { # IfcPerson
Identification,
FamilyName,
GivenName,
MiddleNames,
PrefixTitles,
SuffixTitles,
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
Addresses : { # IfcAddress
Purpose,
Description,
UserDefinedPurpose,
_e_type := .__type__.name
},
_e_type := .__type__.name
},
TheOrganization : { # IfcOrganization
Identification,
Name,
Description,
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
Addresses : { # IfcAddress
Purpose,
Description,
UserDefinedPurpose,
_e_type := .__type__.name
},
_e_type := .__type__.name
},
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
_e_type := .__type__.name
},
LastModifyingApplication : { # IfcApplication
ApplicationDeveloper : { # IfcOrganization
Identification,
Name,
Description,
Roles : { # IfcActorRole
Role,
UserDefinedRole,
Description,
_e_type := .__type__.name
},
Addresses : { # IfcAddress
Purpose,
Description,
UserDefinedPurpose,
_e_type := .__type__.name
},
_e_type := .__type__.name
},
Version,
ApplicationFullName,
ApplicationIdentifier,
_e_type := .__type__.name
},
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
},
ObjectPlacement : { # IfcObjectPlacement
# ObjectPlacement points to an abstract superclass "IfcObjectPlacement".
# Therefore we need to consider various subtypes using the [is ..].key
# (to reduce size of this file and since we know that for this scenario ObjectPlacement is pointing to an
# IfcLocalPlacement, we will skip the other subtypes "IfcLinearPlacement" & "IfcGridPlacement"
[is IfcLocalPlacement].PlacementRelTo : {
id,
_e_type := .__type__.name
},
# PlacementRelTo links to same type of object "IfcObjectPlacement".
# Therefore it is considered not necessary to follow the path further, given that this information would have
# to be collected in a separate step
[is IfcLocalPlacement].RelativePlacement : { # IfcAxis2Placement
# IfcAxis2Placement is a Select object. Which means it can be one of many objects.
# It is however, not known how
[is IfcAxis2Placement3D].Location : { # IfcPoint
[is IfcCartesianPoint].Coordinates
},
[is IfcAxis2Placement3D].RefDirection: { # IfcDirection
DirectionRatios
},
[is IfcAxis2Placement3D].Axis,
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
},
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
},
Representation : { # IfcProductRepresentation
# Points to an abstract superclass "IfcProductRepresentation". However only the subtype
# "IfcMaterialDefinitionRepresentation" has any unique properties not associated with its parent class and
# it is not used in this particular test IFC file and is therefore omitted.
# Properties
Name,
Description,
# Links to other objects
Representations : { # IfcRepresentation
[is IfcShapeRepresentation].RepresentationIdentifier,
[is IfcShapeRepresentation].RepresentationType,
[is IfcShapeRepresentation].ContextOfItems : { # IfcRepresentationContext
ContextIdentifier,
ContextType,
[is IfcGeometricRepresentationContext].CoordinateSpaceDimension,
[is IfcGeometricRepresentationContext].Precision,
[is IfcGeometricRepresentationContext].WorldCoordinateSystem : { # IfcAxis2Placement
# IfcAxis2Placement is a Select object. Which means it can be one of many objects.
# It is however, not known how
[is IfcAxis2Placement3D].Location : { # IfcPoint
[is IfcCartesianPoint].Coordinates
},
[is IfcAxis2Placement3D].RefDirection: { # IfcDirection
DirectionRatios
},
[is IfcAxis2Placement3D].Axis,
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
},
[is IfcGeometricRepresentationContext].TrueNorth : { # IfcDirection
DirectionRatios
},
_e_type := .__type__.name
},
[is IfcShapeRepresentation].Items : { # IfcRepresentationItem
# IfcRepresentationItem is an abstract class that represents > 100 different types of representation
# types. Therefore only the 2 subtypes found in the IFC file is included
[is IfcExtrudedAreaSolid].SweptArea : { # IfcProfileDef
# IfcProfileDef is an abstract superclass. Here only the IfcIShapeProfileDef is included.
[is IfcIShapeProfileDef].ProfileType,
[is IfcIShapeProfileDef].ProfileName,
[is IfcIShapeProfileDef].Position : { # IfcAxis2Placement2D
Location : { # IfcPoint
[is IfcCartesianPoint].Coordinates
},
RefDirection : { # IfcDirection
DirectionRatios
},
},
[is IfcIShapeProfileDef].OverallWidth,
[is IfcIShapeProfileDef].OverallDepth,
[is IfcIShapeProfileDef].WebThickness,
[is IfcIShapeProfileDef].FlangeThickness,
[is IfcIShapeProfileDef].FilletRadius,
[is IfcIShapeProfileDef].FlangeEdgeRadius,
[is IfcIShapeProfileDef].FlangeSlope
},
[is IfcExtrudedAreaSolid].Position : { # IfcAxis2Placement3D
Location : { # IfcPoint
[is IfcCartesianPoint].Coordinates
},
Axis : { # IfcDirection
DirectionRatios
},
RefDirection : { # IfcDirection
DirectionRatios
},
},
[is IfcExtrudedAreaSolid].ExtrudedDirection : { # IfcDirection
DirectionRatios
},
[is IfcExtrudedAreaSolid].Depth,
[is IfcPolyline].Points : { # IfcCartesianPoint
Coordinates
}
}
},
# These are specific EdgeDB attributes to get more information about the object itself
_e_type := .__type__.name
}
}
which returns the following output
[
{
"GlobalId": "3PXsnq_3qHxBd2w2f4ZOUQ",
"Description": "IPE400",
"Name": "MyBeam",
"ObjectType": "Beam",
"Tag": "MyBeam",
"PredefinedType": null,
"OwnerHistory": {
"LastModifiedDate": 1656570506,
"CreationDate": 1656570506,
"OwningUser": {
"ThePerson": {
"Identification": "AdaUser",
"FamilyName": null,
"GivenName": null,
"MiddleNames": null,
"PrefixTitles": null,
"SuffixTitles": null,
"Roles": [],
"Addresses": [],
"_e_type": "default::IfcPerson"
},
"TheOrganization": {
"Identification": "ADA",
"Name": "Assembly For Design and Analysis",
"Description": null,
"Roles": [],
"Addresses": [],
"_e_type": "default::IfcOrganization"
},
"Roles": []
},
"OwningApplication": {
"ApplicationDeveloper": {
"Identification": "ADA",
"Name": "Assembly For Design and Analysis",
"Description": null,
"Roles": [],
"Addresses": [],
"_e_type": "default::IfcOrganization"
},
"Version": "XXX",
"ApplicationFullName": "ADA",
"ApplicationIdentifier": "ADA",
"_e_type": "default::IfcApplication"
},
"State": "READWRITE",
"ChangeAction": null,
"LastModifyingUser": {
"ThePerson": {
"Identification": "AdaUser",
"FamilyName": null,
"GivenName": null,
"MiddleNames": null,
"PrefixTitles": null,
"SuffixTitles": null,
"Roles": [],
"Addresses": [],
"_e_type": "default::IfcPerson"
},
"TheOrganization": {
"Identification": "ADA",
"Name": "Assembly For Design and Analysis",
"Description": null,
"Roles": [],
"Addresses": [],
"_e_type": "default::IfcOrganization"
},
"Roles": [],
"_e_type": "default::IfcPersonAndOrganization"
},
"LastModifyingApplication": {
"ApplicationDeveloper": {
"Identification": "ADA",
"Name": "Assembly For Design and Analysis",
"Description": null,
"Roles": [],
"Addresses": [],
"_e_type": "default::IfcOrganization"
},
"Version": "XXX",
"ApplicationFullName": "ADA",
"ApplicationIdentifier": "ADA",
"_e_type": "default::IfcApplication"
},
"_e_type": "default::IfcOwnerHistory"
},
"ObjectPlacement": {
"PlacementRelTo": {
"id": "ee386f0e-fced-11ec-97f5-b3f35fe72359",
"_e_type": "default::IfcLocalPlacement"
},
"RelativePlacement": {
"Location": null,
"RefDirection": null,
"Axis": null,
"_e_type": "default::IfcAxis2Placement"
},
"_e_type": "default::IfcLocalPlacement"
},
"Representation": {
"Name": null,
"Description": null,
"Representations": [
{
"RepresentationIdentifier": "Body",
"RepresentationType": "SweptSolid",
"ContextOfItems": {
"ContextIdentifier": null,
"ContextType": "Model",
"CoordinateSpaceDimension": 3,
"Precision": 1e-05,
"WorldCoordinateSystem": {
"Location": null,
"RefDirection": null,
"Axis": null,
"_e_type": "default::IfcAxis2Placement"
},
"TrueNorth": {
"DirectionRatios": [
0,
1,
0
]
},
"_e_type": "default::IfcGeometricRepresentationContext"
},
"Items": [
{
"SweptArea": {
"ProfileType": "AREA",
"ProfileName": "IPE400",
"Position": null,
"OverallWidth": 0.18,
"OverallDepth": 0.4,
"WebThickness": 0.0086,
"FlangeThickness": 0.0135,
"FilletRadius": null,
"FlangeEdgeRadius": null,
"FlangeSlope": null
},
"Position": {
"Location": {
"Coordinates": [
0,
0,
0
]
},
"Axis": null,
"RefDirection": null
},
"ExtrudedDirection": {
"DirectionRatios": [
0,
0,
1
]
},
"Depth": 1.5,
"Points": []
}
]
},
{
"RepresentationIdentifier": "Axis",
"RepresentationType": "Curve3D",
"ContextOfItems": {
"ContextIdentifier": null,
"ContextType": "Model",
"CoordinateSpaceDimension": 3,
"Precision": 1e-05,
"WorldCoordinateSystem": {
"Location": null,
"RefDirection": null,
"Axis": null,
"_e_type": "default::IfcAxis2Placement"
},
"TrueNorth": {
"DirectionRatios": [
0,
1,
0
]
},
"_e_type": "default::IfcGeometricRepresentationContext"
},
"Items": [
{
"SweptArea": null,
"Position": null,
"ExtrudedDirection": null,
"Depth": null,
"Points": [
{
"Coordinates": [
0,
0,
0
]
},
{
"Coordinates": [
0,
0,
1.5
]
}
]
}
]
}
],
"_e_type": "default::IfcProductDefinitionShape"
}
}
]
Strategies for reducing the general IFC query shape complexity#
Based on the observed results here are some proposals for further improving the efficiency and ergonomics of the query shapes.
IFC specific query modifications#
The following will work only for IFC and would not apply for general query building using ESDL in general.
Exclude
OwnerHistory
in the general query builder and instead create a custom query for exporting all OwnerHistory related informationMove
ObjectPlacement
query out of the general query builder and to its own custom query in order to resolve World Coordinates based on potentially nestedRelativePlacement
references.
General query modifications#
These options are more general and should apply to all esdl schemas.
Do an initial query to return all nested object links and object types without any non-link-property information. In scenarios with objects being referred to multiple times, this strategy will reduce duplication of result data, and should improve things especially with shapes containing a lot of references to same objects.