- Introduction
- Import
- Export
Introduction
This guide is for developers who are interested in customizing the CET Abstract IFC interfaces for importing and exporting IFC 2x3 files. You should have a basic understanding of the IFC standard, and some entity types required. A good reference is IFC documentation.
Overview
The API v2 for the IFC Interface revolves around several use cases:
- Import: Replace IFC Objects with native snappers – such as replacing a generic IFC Furniture into an actual custom worksurface snapper.
- Export: Append additional properties – such as Parts and Dimensions using Property Sets and Quantity Sets.
- Export: Customize exported object types – such as properly classifying HVAC as a Duct, instead of the generic FurnishingElement.
- Export: Logical grouping of objects – such as objects that contain a set of objects.
- Export: Customizing the shape representation – such as using Solids instead of generic meshes.
The IFC Base code is stored in cm.abstract.ifc with 2 sub-packages, cm.abstract.ifc.import and cm.abstract.ifc.export, that handles import and export respectively. There is also a pset directory that contains a list of property and quantity set data schema XMLs.
IFC Class Structure
The CET IFC Object structure starts with the base class IfcExpressObject
, which has 3 subclasses, namely IfcExpressDefinedType
, IfcExpressEntity
and IfcExpressSelectType
.
IfcExpressObject -- cm.abstract.ifc |---IfcExpressDefinedType | |---+ |---IfcExpressEntity | |---+ |---IfcExpressSelectType | |---+
IfcExpressDefinedType
serves as the base class for different IFC Types like IfcBoolean, IfcLengthMeasure, etc., while IfcExpressEntity
serves as the base class for IFC Entities such as IfcProductRepresentation, IfcProperty, etc.
On the other hand, IfcExpressSelectType
serves as the base class for IFC selector classes that accept values from a predefined domain of classes. For example, a user can assign IfcAxis2Placement2D, or IfcAxis2Placement3D to IfcAxis2Placement, depending on the context is 2D or 3D.
Please note that IFC-related classes that are only defined in CET/CM start with the prefix 'Ifc', while IFC classes that originate from the IFC schema start with the prefix 'Exp'. For example, the CET class for IfcProductRepresentation is ExpIfcProductRepresentation
.
Import
Customizing Imported Products
By default, IfcObject in an IFC drawing is imported as an IfcSymbol
in CET, which is a generic snapper that stores IFC graphics and metadata.
However, it is possible to parse an IfcObject and retrieve metadata from it, and replace the IfcObject with a snapper from your extension/customization using a product replacement hook. The example below registers a hook that import IfcFlowSegment as MechaStraightSnapper
in the extension.cm file of the HVAC extension. A simplified version of the function that replaces an IfcFlowSegment with a MechaStraightSnapper
is also given in the example below.
// PipeExtension class public void start(ExtensionEnv env) { ifcRegisterProductReplacementHook("CetIfcHvacImport", function hvacProductReplacementHook); super(env); } // PipeExtension class public void stop(ExtensionEnv env) { ifcUnregisterProductReplacementHook("CetIfcHvacImport"); super(env); } public Snapper[] hvacProductReplacementHook(IfcProjectTreeNode productNode) { if (?ExpIfcFlowSegment flowSegment = productNode.owner) { if (getFlowSegmentType(flowSegment) in ExpIfcDuctSegmentType) { Snapper[] snappers(); MechaStraightSnapper mechaStraightSnapper(); // TODO: Update the dimensions and other properties of the Snapper. snappers << mechaStraightSnapper; return snappers; } } return null; }
Customizing Imported Metadata
In IFC, each IfcObject has the ‘Name’ and ‘Description’ attributes, which can be assigned to the relevant fields of a snapper. In addition, an IfcObject can have properties data defined using IfcPropertySet, which are associated with the IFCObject using IfcRelDefinedByProperties. Hence, it is possible to retrieve relevant properties, and assign the values to the corresponding fields of a snapper.
Property Set
IFC provides a list of Property Set Definition (PSD) schema, which describes properties that are specific for certain IfcObject classes. For example, the definition ‘Pset_DuctFittingTypeCommon’ is applicable to the IFC entity IfcDuctFittingType, and contains property fields such as ‘NominalHeight’, ‘NominalDiameterOrWidth’, ‘Length, etc., to better describe a duct.
The property set definitions are defined in the form of .xml files. The .xml files are stored in the folder ‘cm/abstract/ifc/pset/ifc2x3/’.
The relevant properties from the property sets can be retrived and the values assigned to the corresponding fields of the relevant snapper. In the example below, the property ‘Length’ is retrived from the ‘Pset_DuctSegmentTypeCommon’ property set of an IfcflowSegment and then the value is assigned to the field ‘length’ of a MechaStraightSnapper.
public Snapper[] hvacProductReplacementHook(IfcProjectTreeNode productNode) {
if (?ExpIfcFlowSegment flowSegment = productNode.owner) { if (getFlowSegmentType(flowSegment) in ExpIfcDuctSegmentType) { Snapper[] snappers(); MechaStraightSnapper mechaStraightSnapper(); // Get the length from the property set. if (IfcPropertiesObject propObject = productNode.getData("Pset_DuctSegmentTypeCommon")) { if (?Double length = propObject.get("Length")) { mechaStraightSnapper.length = length.v; } } snappers << mechaStraightSnapper; return snappers; } } return null;
}
Custom Property Set Definitions
In addition to the property sets that are defined in the Property Set Definition (PSD) schema, it is also possible to import custom property sets that are not defined in the PSD schema. To do so, the custom property sets in CET need to be defined, as shown below.
public const str ifcKeyPset_CETData = "Pset_CETData"; public const str ifcKeyPset_CETHvacData = "Pset_CETHvacData"; private const str{} cetDefinedSchemas = { ifcKeyPset_CETData, ifcKeyPset_CETHvacData }; public class Pset_CETDataDefs extends IfcPropertiesDefs { public constructor() { this.name = ifcKeyPset_CETData; this.schemaVersion = ifcFileSchema.ifc2x3; this.put(IfcPsetPropertySingleValueDef(k="PartList", pType=Str, expIfcType=ExpIfcLabel)); } } public class Pset_CETHvacDataDefs extends Pset_CETDataDefs { public constructor() { super(); this.name = ifcKeyPset_CETHvacData; this.put(IfcPsetPropertySingleValueDef(k="IsOpenEnded", pType=Bool, expIfcType=ExpIfcBoolean)); } } private IfcPropertiesDefs createCetDefinedSchemas(str key) { if (key == ifcKeyPset_CETData) { return Pset_CETDataDefs(); } else if (key == ifcKeyPset_CETHvacData) { return Pset_CETHvacDataDefs(); } return null;
}
Then, relevant properties from the custom property sets can be retrived and the values assigned to the corresponding fields of the relevant snapper. In the following example, the property ‘IsOpenEnded’ is retrived from the custom property set ‘Pset_CETHvacData’ and the value assigned to the field ‘openEnded’ of MechaStraightSnapper.
public Snapper[] hvacProductReplacementHook(IfcProjectTreeNode productNode) { if (?ExpIfcFlowSegment flowSegment = productNode.owner) { if (getFlowSegmentType(flowSegment) in ExpIfcDuctSegmentType) { Snapper[] snappers(); MechaStraightSnapper mechaStraightSnapper(); // Get the property from the property set. if (IfcPropertiesObject propObject = productNode.getData(ifcKeyPset_CETHvacData)) { if (?Bool isOpenEnded = propObject.get("IsOpenEnded")) { mechaStraightSnapper.openEnded = isOpenEnded.v; } } snappers << mechaStraightSnapper; return snappers; } } return null;
}
Customizing Object Aggregations
In IFC, it is possible to define a composition or decomposition relationship between IfcObjectDefinition using IfcRelAggregates, where the main object is defined as an aggregation of the parts. For example, a roof can be defined as an aggregation of roof elements like roof slabs, rafters, etc.
While processing certain IfcObject, we might want to get some properties or data from the disaggregated child IfcObject. For this purpose, the following syntax can be used to get the relevant child IfcOject.
IfcProjectTreeNode[] childNodes = ifcProjectTreeNode.get(isDecomposedBy); for (childNode in childNodes) { if (?ExpIfcProduct ifcProduct = childNode.owner) { //TODO: process child nodes. } }
Export
Customizing Export
When CET exports a drawing as IFC, a factory class for each snapper in space is invoked to generate the required IFC entities.
The basic structure of IfcExportFactory is shown below:
IfcExportFactory -- cm.abstract.ifc.export |---IfcExportCoreObjectFactory | |---IfcExportContainedVesselFactory -- cm.abstract.ifc | |---IfcExportSnapperFactory -- cm.abstract.ifc.export | | |---+
IFC Base comes with a generic factory (IfcExportSnapperFactory) out of the box that handles graphics and basic parts data export. To customize the export, IfcExportSnapperFactory may be subclassed with its own logic.
Customizing Factory Hook Registration
After subclassing IfcExportSnapperFactory, the new factory needs to be registered into the export factory hook in order to associate the snapper with the corresponding export factory. Use the function 'registerIfcExportFactoryHook' to register a new hook for a new export factory subclass. It is recommended to register the hook in the 'start' method of an extension, and unregister it when the extension is turned off.
// PipeExtension class public void start(ExtensionEnv env) { registerIfcExportFactoryHook("CetIfcHvacExp", function cetHvacExportHook); super(env); } public void registerIfcExportFactoryHook(str key, function(Snapper, IfcExportEnvG2):IfcExportFactory hook) { if (developMode) pln("[IFC] Registered IFC Export Factory Hook: ", #key); _hooks.put(key, hook); } public IfcExportFactory cetHvacExportHook(Snapper s, IfcExportEnvG2 env) { if (s as MechaStraightSnapper) return HvacSegmentIfcExportFactory(s, env); else if (s in MechaJunction) return HvacFittingIfcExportFactory(s, env); return null; }
Customizing Property Exports
In IFC, an IfcObject can have properties data defined using IfcPropertySet, and they are associated using IfcRelDefinedByProperties. As such, one possible use case with the API is to export metadata of a snapper (such as dimensions, name, parts, manufacturer data, etc.) as an IfcProperty.
Property Set
IfcPropertiesObject works similar to PropObj, where IfcPropertiesDefs provides the definition of the data to be exported, while IfcPropertiesData stores the actual data.
In this example, an IfcPropertiesData (a subclass of IfcPropertiesObject) is created that employs the IFC PSD schema ‘Pset_FlowSegmentDuctSegment’ and assigns a value to the ‘Length’ property field. Finally, IfcExportCoreObjectFactory.getPropertySetDatas() is overridden.
// HvacSegmentIfcExportFactory class
public IfcPropertiesObject[] getPropertySetDatas() { IfcPropertiesObject[] ps = super(); if (!ps) init ps();
IfcPropertiesData distributionFlowElementCommonPset(getPsetDefinition(ifcKeyPset_DistributionFlowElementCommon)); distributionFlowElementCommonPset."Reference" = snapper.id.toS; ps << distributionFlowElementCommonPset; if (IfcPropertiesData pset = IfcPropertiesData(getPsetDefinition("Pset_FlowSegmentDuctSegment"))) { pset."Length" = length; ps << pset; } return ps; }
Quantity Set
In addition to IfcPropertySet, properties can also be exported using IfcElementQuantity, which describes the physical quantities of an element, by overriding the corresponding method IfcExportCoreObjectFactory.getQuantitySetDatas().
// IfcExportWallFactory class public IfcPropertiesObject[] getQuantitySetDatas() {
IfcPropertiesObject[] props = super(); if (!props) init props(); box b = snapper.?localBound(); IfcPropertiesData qs(Qto_WallBaseQuantitiesDefs()); qs."Width" = b.w; qs."Height" = b.h; props << qs; return props;
}
Custom Property Set Definitions
It is possible to define our own IfcPropertiesDefs in CET. In this example, a new set of property definitions for HVAC is defined.
public class Pset_CETDataDefs extends IfcPropertiesDefs { public constructor() { this.name = ifcKeyPset_CETData; this.schemaVersion = ifcFileSchema.ifc2x3; this.put(IfcPsetPropertySingleValueDef(k="PartList", pType=Str, expIfcType=ExpIfcLabel)); } } public class Pset_CETHvacDataDefs extends Pset_CETDataDefs { public constructor() { super(); this.put(IfcPsetPropertySingleValueDef(k="IsOpenEnded", pType=Bool, expIfcType=ExpIfcBoolean)); } }
The IfcPropertiesDefs can then be used to construct IfcPropertiesData, as shown below. For properties that are only defined in CET, IfcExportSnapperFactory.getCetDatas() should be overridden in order to customize the CET data that should be exported.
// Hvacsegmentifcexportfactory class public IfcPropertiesObject getCetDatas() { IfcPropertiesDefs cetDataDef = getPsetDefinition(ifcKeyPset_CETHvacData); IfcPropertiesData cetData(cetDataDef); cetData."PartList" = (getPartListValue()); if (snapper as MechaStraightSnapper) cetData."IsOpenEnded" = snapper.openEnded; return cetData; }
Customizing Exported IFC Object Type
By default, snappers are exported as IfcFurnishingElement. However, it is possible to export snappers as another IfcProduct by overriding the method IfcExportCoreObjectFactory.makeProduct(). For example, the code below customizes the exporting of a straight HVAC as an IfcFlowSegment.
// HvacSegmentIfcExportFactory class public ExpIfcProduct makeProduct() { return ExpIfcFlowSegment(); }
By default, snappers are exported as IfcFurnishingElementType. However, it is possible to export snappers as another IfcTypeObject by overriding the method IfcExportFactory.getObjectTyping(). For example, the code below customizes the exporting of a straight HVAC as IfcDuctSegmentType.
// HvacSegmentIfcExportFactory class public ExpIfcTypeObject getObjectTyping() { if (!objectType) { ExpIfcDuctSegmentType typeObject(ifcGuid(), env.ownerHistory, expIfcDuctSegmentTypeEnum.notdefined); typeObject.name = ExpIfcLabel(getName()); typeObject.description = ExpIfcText(getDescription()); typeObject.predefinedType = ductSegmentTypeEnum(); objectType = typeObject; } return objectType; }
Customizing Object Aggregations
In IFC, it is possible to define a composition or decomposition relationship between IfcObjectDefinition using IfcRelAggregates where the main object is defined as an aggregation of the parts. For example, a roof can be defined as an aggregation of roof elements such as roof slabs, rafters, etc.
In CET, the aggregation relationship is defined in IfcExportAggregateInfo, where IfcExportAggregateInfo.parent stores the parent IfcObject, while IfcExportAggregateInfo.children stores the decomposed children IfcObject. The code below show an example of constructing IfcExportAggregateInfo for stairs, and how to register the IFC export aggregate hook.
package IfcExportAggregateInfo[] generateStairGroups(IfcExportEnvG2 env) {
if (!env.space) return null; IfcExportAggregateInfo[] aggInfos(); StairLandingSnapperFilter filter(); Snapper{} visited(); for (z in env.space.snappers) { // Start collecting groups from non railings snappers if (filter.accepts(z) and z !in visited and z !in Railings) { Snapper{} connected = z.allConnected(includeSelf=true); Snapper{} group = filter.allAccepted(connected); // Only whitelisted snappers can merge up with railings for (Railings r in group) { Connector c = r.getConnectedConn(); if (c and c.snapper) { if (!filter.accepts(c.snapper)) { group.remove(r); } } } visited += group; ExpIfcStair stair(ifcGuid, env.ownerHistory, expIfcStairTypeEnum.notdefined); aggInfos << IfcExportAggregateInfo(stair, group); } } return aggInfos; }
registerIfcExportAggregateHook(function generateStairGroups);
Customizing Exported Graphics (Shape Representations)
The generic export factory is also responsible for converting the 3D graphics for snappers in CET into IfcRepresentation. Primitive3D is converted into meshes and exported as IfcFaceBasedSurfaceModel. CmSym is exported by converting SymComponent to accurate IFC graphical representation.
Customizing the 3D graphics that should be exported for a particular snapper can be done by overriding the method IfcExportCoreObjectFactory.getRepresentations(), as shown below. In this example, the 3D graphics that should be exported for a straight rectangular HVAC is customized, where the graphics are built using extrusions.
// HvacSegmentIfcExportFactory class public ExpIfcRepresentation[] getRepresentations() { if (snapper as MechaStraightSnapper) { ExpIfcAxis2Placement2D profilePos(ifcCartesianPoint(point2D(0, 0)), ifcDirection(point2D(1, 0))); ExpIfcAxis2Placement3D repPlacement(ifcCartesianPoint(point(0, 0, 0)), ifcDirection(point(1, 0, 0)), ifcDirection(point(0, -1, 0))); ExpIfcRectangleProfileDef profileDef(expIfcProfileTypeEnum.area, profilePos, ExpIfcPositiveLengthMeasure(snapper.diameter), ExpIfcPositiveLengthMeasure(snapper.height)); ExpIfcExtrudedAreaSolid repItem(sweptArea=profileDef, position=repPlacement, ifcDirection(point(0, 0, 1)), depth=ExpIfcPositiveLengthMeasure(snapper.length)); ExpIfcShapeRepresentation shapeRep(null, ExpIfcLabel("Body"), ExpIfcLabel("SweptSolid"), [ExpIfcRepresentationItem: repItem]); return [ExpIfcRepresentation : shapeRep]; } return super(); }
Customizing CmSym Exported Graphics
By default, CET supports a list of standard CmSym to IfcRepresentation conversions:
- SymExtrusion exports as IfcSweptAreaSolid.
- SymBrep and SymBSplineSurface export as IfcFacetedBrep.
- SymBox and SymCylinder export as IfcExtrudedAreaSolid.
- Otherwise, CmSym will be exported as meshes to IfcFaceBasedSurfaceModel.
Customizing CmSym exported graphics as other IfcShapeRepresentation can be done by subclassing SymComponentToIfcRepConverter and implement your own logic in getIfcRepresentationItems. For example, SymBox is exported to IfcExtrudedAreaSolid:
/** * Get the IFC representation item. */ public void getIfcRepresentationItems(ExpIfcRepresentationItem[] expIfcRepItems) { if (symComponent as SymBox) { box b = symComponent.b; ExpIfcAxis2Placement2D pos2D(ifcCartesianPoint((b.center.point2D, b.p0.z)), ifcDirection((1, 0))); ExpIfcRectangleProfileDef profileDef(expIfcProfileTypeEnum.area, pos2D, ExpIfcPositiveLengthMeasure(b.w), ExpIfcPositiveLengthMeasure(b.d)); orientation o(); Transform t(point(), o); if (Transform symT = symNodeItEnv.transform) t = t + symT; ExpIfcAxis2Placement3D pos = ifcAxis2Placement3D(t.pos, t.rot); ExpIfcDirection extrudedDirection = ifcDirection((0, 0, 1)); ExpIfcPositiveLengthMeasure depth = ExpIfcPositiveLengthMeasure(b.h); expIfcRepItems << ExpIfcExtrudedAreaSolid(profileDef, pos, extrudedDirection, depth); } }
Comments
0 comments
Please sign in to leave a comment.