diff --git a/src/ifcopenshell-python/ifcopenshell/util/attribute_4_to_2x3.json b/src/ifcopenshell-python/ifcopenshell/util/attribute_4_to_2x3.json new file mode 100644 index 0000000000..5cf025688c --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/attribute_4_to_2x3.json @@ -0,0 +1,251 @@ +{ + "IfcPerformanceHistory": { + "Identification": "LifeCyclePhase" + }, + "IfcProcedure": { + "Identification": "ProcedureID", + "LongDescription": "ProcedureType", + "PredefinedType": "UserDefinedProcedureType" + }, + "IfcTask": { + "Identification": "TaskId", + "LongDescription": "Status", + "Status": "WorkMethod", + "WorkMethod": "IsMilestone", + "IsMilestone": "Priority" + }, + "IfcWorkPlan": { + "Identification": "Identifier", + "PredefinedType": "WorkControlType" + }, + "IfcWorkSchedule": { + "Identification": "Identifier", + "PredefinedType": "WorkControlType" + }, + "IfcSpace": { + "PredefinedType": "InteriorOrExteriorSpace" + }, + "IfcTransportElement": { + "PredefinedType": "OperationType" + }, + "IfcBuildingElementProxy": { + "PredefinedType": "CompositionType" + }, + "IfcRamp": { + "PredefinedType": "ShapeType" + }, + "IfcRelCoversSpaces": { + "RelatingSpace": "RelatedSpace" + }, + "IfcRoof": { + "PredefinedType": "ShapeType" + }, + "IfcStair": { + "PredefinedType": "ShapeType" + }, + "IfcAsset": { + "Identification": "AssetID" + }, + "IfcInventory": { + "PredefinedType": "InventoryType" + }, + "IfcActionRequest": { + "Identification": "RequestID" + }, + "IfcCostSchedule": { + "Identification": "SubmittedBy", + "PredefinedType": "PreparedBy", + "Status": "SubmittedOn", + "SubmittedOn": "Status", + "UpdateDate": "TargetUsers" + }, + "IfcPermit": { + "Identification": "PermitID" + }, + "IfcProjectOrder": { + "Identification": "ID" + }, + "IfcConstructionEquipmentResource": { + "Identification": "ResourceIdentifier", + "LongDescription": "ResourceGroup", + "Usage": "ResourceConsumption", + "BaseCosts": "BaseQuantity" + }, + "IfcConstructionMaterialResource": { + "Identification": "ResourceIdentifier", + "LongDescription": "ResourceGroup", + "Usage": "ResourceConsumption", + "BaseCosts": "BaseQuantity", + "BaseQuantity": "Suppliers", + "PredefinedType": "UsageRatio" + }, + "IfcConstructionProductResource": { + "Identification": "ResourceIdentifier", + "LongDescription": "ResourceGroup", + "Usage": "ResourceConsumption", + "BaseCosts": "BaseQuantity" + }, + "IfcCrewResource": { + "Identification": "ResourceIdentifier", + "LongDescription": "ResourceGroup", + "Usage": "ResourceConsumption", + "BaseCosts": "BaseQuantity" + }, + "IfcLaborResource": { + "Identification": "ResourceIdentifier", + "LongDescription": "ResourceGroup", + "Usage": "ResourceConsumption", + "BaseCosts": "BaseQuantity", + "BaseQuantity": "SkillSet" + }, + "IfcSubContractResource": { + "Identification": "ResourceIdentifier", + "LongDescription": "ResourceGroup", + "Usage": "ResourceConsumption", + "BaseCosts": "BaseQuantity", + "BaseQuantity": "SubContractor", + "PredefinedType": "JobDescription" + }, + "IfcStructuralLinearAction": { + "ProjectedOrTrue": "CausedBy", + "PredefinedType": "ProjectedOrTrue" + }, + "IfcStructuralPlanarAction": { + "ProjectedOrTrue": "CausedBy", + "PredefinedType": "ProjectedOrTrue" + }, + "IfcReinforcingBar": { + "PredefinedType": "BarRole" + }, + "IfcOrganization": { + "Identification": "Id" + }, + "IfcPerson": { + "Identification": "Id" + }, + "IfcApproval": { + "Identifier": "Description", + "Name": "ApprovalDateTime", + "Description": "ApprovalStatus", + "TimeOfApproval": "ApprovalLevel", + "Status": "ApprovalQualifier", + "Level": "Name", + "Qualifier": "Identifier" + }, + "IfcApprovalRelationship": { + "Name": "RelatedApproval", + "Description": "RelatingApproval", + "RelatingApproval": "Description", + "RelatedApprovals": "Name" + }, + "IfcObjective": { + "LogicalAggregator": "ResultValues" + }, + "IfcCostValue": { + "Category": "CostType" + }, + "IfcCurrencyRelationship": { + "Name": "RelatingMonetaryUnit", + "Description": "RelatedMonetaryUnit", + "RelatingMonetaryUnit": "ExchangeRate", + "RelatedMonetaryUnit": "RateDateTime", + "ExchangeRate": "RateSource" + }, + "IfcClassificationReference": { + "Identification": "ItemReference" + }, + "IfcDocumentInformation": { + "Identification": "DocumentId", + "Location": "DocumentReferences" + }, + "IfcDocumentInformationRelationship": { + "Name": "RelatingDocument", + "Description": "RelatedDocuments", + "RelatingDocument": "RelationshipType" + }, + "IfcDocumentReference": { + "Identification": "ItemReference" + }, + "IfcLibraryInformation": { + "Location": "LibraryReference" + }, + "IfcLibraryReference": { + "Identification": "ItemReference" + }, + "IfcMaterialProperties": { + "Name": "Material" + }, + "IfcBlobTexture": { + "Mode": "TextureType", + "Parameter": "RasterFormat", + "RasterFormat": "RasterCode" + }, + "IfcExternallyDefinedHatchStyle": { + "Identification": "ItemReference" + }, + "IfcExternallyDefinedSurfaceStyle": { + "Identification": "ItemReference" + }, + "IfcExternallyDefinedTextFont": { + "Identification": "ItemReference" + }, + "IfcImageTexture": { + "Mode": "TextureType", + "Parameter": "UrlReference" + }, + "IfcPixelTexture": { + "Mode": "TextureType", + "Parameter": "Width", + "Width": "Height", + "Height": "ColourComponents", + "ColourComponents": "Pixel" + }, + "IfcTextureCoordinateGenerator": { + "Maps": "Mode", + "Mode": "Parameter" + }, + "IfcTextureMap": { + "Maps": "TextureMaps" + }, + "IfcAsymmetricIShapeProfileDef": { + "BottomFlangeWidth": "OverallWidth", + "BottomFlangeThickness": "FlangeThickness", + "BottomFlangeFilletRadius": "FilletRadius", + "BottomFlangeEdgeRadius": "CentreOfGravityInY" + }, + "IfcProfileProperties": { + "Name": "ProfileName", + "Description": "ProfileDefinition" + }, + "IfcPropertyDependencyRelationship": { + "Name": "DependingProperty", + "Description": "DependantProperty", + "DependingProperty": "Name", + "DependantProperty": "Description" + }, + "IfcBoundaryEdgeCondition": { + "TranslationalStiffnessByLengthX": "LinearStiffnessByLengthX", + "TranslationalStiffnessByLengthY": "LinearStiffnessByLengthY", + "TranslationalStiffnessByLengthZ": "LinearStiffnessByLengthZ" + }, + "IfcBoundaryFaceCondition": { + "TranslationalStiffnessByAreaX": "LinearStiffnessByAreaX", + "TranslationalStiffnessByAreaY": "LinearStiffnessByAreaY", + "TranslationalStiffnessByAreaZ": "LinearStiffnessByAreaZ" + }, + "IfcBoundaryNodeCondition": { + "TranslationalStiffnessX": "LinearStiffnessX", + "TranslationalStiffnessY": "LinearStiffnessY", + "TranslationalStiffnessZ": "LinearStiffnessZ" + }, + "IfcBoundaryNodeConditionWarping": { + "TranslationalStiffnessX": "LinearStiffnessX", + "TranslationalStiffnessY": "LinearStiffnessY", + "TranslationalStiffnessZ": "LinearStiffnessZ" + }, + "IfcStructuralLoadTemperature": { + "DeltaTConstant": "DeltaT_Constant", + "DeltaTY": "DeltaT_Y", + "DeltaTZ": "DeltaT_Z" + } +} diff --git a/src/ifcopenshell-python/ifcopenshell/util/class_4_to_2x3.json b/src/ifcopenshell-python/ifcopenshell/util/class_4_to_2x3.json new file mode 100644 index 0000000000..250a05b257 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/class_4_to_2x3.json @@ -0,0 +1,213 @@ +{ + "IfcComplexPropertyTemplate": "", + "IfcProjectLibrary": "", + "IfcPropertySetTemplate": "", + "IfcRelAssignsToGroupByFactor": "", + "IfcRelDeclares": "", + "IfcRelDefinesByObject": "", + "IfcRelDefinesByTemplate": "", + "IfcSimplePropertyTemplate": "", + "IfcEvent": "", + "IfcEventType": "", + "IfcProcedureType": "", + "IfcTaskType": "", + "IfcWorkCalendar": "", + "IfcCivilElement": "", + "IfcCivilElementType": "", + "IfcElementAssemblyType": "", + "IfcExternalSpatialElement": "IfcSpace", + "IfcGeographicElement": "", + "IfcGeographicElementType": "", + "IfcOpeningStandardCase": "", + "IfcRelInterferesElements": "", + "IfcRelSpaceBoundary1stLevel": "", + "IfcRelSpaceBoundary2ndLevel": "", + "IfcSpatialZone": "", + "IfcSpatialZoneType": "", + "IfcBeamStandardCase": "", + "IfcBuildingSystem": "", + "IfcChimney": "", + "IfcChimneyType": "", + "IfcColumnStandardCase": "", + "IfcDoorStandardCase": "", + "IfcDoorType": "", + "IfcMemberStandardCase": "", + "IfcPlateStandardCase": "", + "IfcRampType": "", + "IfcRoofType": "", + "IfcShadingDevice": "", + "IfcShadingDeviceType": "", + "IfcSlabElementedCase": "", + "IfcSlabStandardCase": "", + "IfcStairType": "", + "IfcWallElementedCase": "", + "IfcWindowStandardCase": "", + "IfcWindowType": "", + "IfcDistributionCircuit": "", + "IfcDistributionSystem": "", + "IfcBuildingElementPartType": "", + "IfcFurniture": "", + "IfcSystemFurnitureElement": "", + "IfcActuator": "", + "IfcAlarm": "", + "IfcController": "", + "IfcFlowInstrument": "", + "IfcSensor": "", + "IfcUnitaryControlElement": "", + "IfcUnitaryControlElementType": "", + "IfcConstructionEquipmentResourceType": "", + "IfcConstructionMaterialResourceType": "", + "IfcConstructionProductResourceType": "", + "IfcCrewResourceType": "", + "IfcLaborResourceType": "", + "IfcSubContractResourceType": "", + "IfcAudioVisualAppliance": "", + "IfcAudioVisualApplianceType": "", + "IfcCableCarrierFitting": "", + "IfcCableCarrierSegment": "", + "IfcCableFitting": "", + "IfcCableFittingType": "", + "IfcCableSegment": "", + "IfcCommunicationsAppliance": "", + "IfcCommunicationsApplianceType": "", + "IfcElectricAppliance": "", + "IfcElectricDistributionBoard": "", + "IfcElectricDistributionBoardType": "", + "IfcElectricFlowStorageDevice": "", + "IfcElectricGenerator": "", + "IfcElectricMotor": "", + "IfcElectricTimeControl": "", + "IfcJunctionBox": "", + "IfcLamp": "", + "IfcLightFixture": "", + "IfcMotorConnection": "", + "IfcOutlet": "", + "IfcProtectiveDevice": "", + "IfcProtectiveDeviceTrippingUnit": "", + "IfcProtectiveDeviceTrippingUnitType": "", + "IfcSolarDevice": "", + "IfcSolarDeviceType": "", + "IfcSwitchingDevice": "", + "IfcTransformer": "", + "IfcAirTerminal": "", + "IfcAirTerminalBox": "", + "IfcAirToAirHeatRecovery": "", + "IfcBoiler": "", + "IfcBurner": "", + "IfcBurnerType": "", + "IfcChiller": "", + "IfcCoil": "", + "IfcCompressor": "", + "IfcCondenser": "", + "IfcCooledBeam": "", + "IfcCoolingTower": "", + "IfcDamper": "", + "IfcDuctFitting": "", + "IfcDuctSegment": "", + "IfcDuctSilencer": "", + "IfcEngine": "", + "IfcEngineType": "", + "IfcEvaporativeCooler": "", + "IfcEvaporator": "", + "IfcFan": "", + "IfcFilter": "", + "IfcFlowMeter": "", + "IfcHeatExchanger": "", + "IfcHumidifier": "", + "IfcMedicalDevice": "", + "IfcMedicalDeviceType": "", + "IfcPipeFitting": "", + "IfcPipeSegment": "", + "IfcPump": "", + "IfcSpaceHeater": "", + "IfcTank": "", + "IfcTubeBundle": "", + "IfcUnitaryEquipment": "", + "IfcValve": "", + "IfcVibrationIsolator": "", + "IfcFireSuppressionTerminal": "", + "IfcInterceptor": "", + "IfcInterceptorType": "", + "IfcSanitaryTerminal": "", + "IfcStackTerminal": "", + "IfcWasteTerminal": "", + "IfcStructuralCurveAction": "", + "IfcStructuralCurveReaction": "", + "IfcStructuralLoadCase": "", + "IfcStructuralSurfaceAction": "", + "IfcStructuralSurfaceReaction": "", + "IfcFootingType": "", + "IfcPileType": "", + "IfcReinforcingBarType": "", + "IfcReinforcingMeshType": "", + "IfcSurfaceFeature": "", + "IfcTendonAnchorType": "", + "IfcTendonType": "", + "IfcVoidingFeature": "", + "IfcResourceApprovalRelationship": "", + "IfcAppliedValue": "", + "IfcReference": "", + "IfcResourceConstraintRelationship": "", + "IfcEventTime": "", + "IfcLagTime": "", + "IfcRecurrencePattern": "", + "IfcResourceTime": "", + "IfcTaskTime": "", + "IfcTaskTimeRecurring": "", + "IfcTimePeriod": "", + "IfcWorkTime": "", + "IfcExternalReferenceRelationship": "", + "IfcConnectionVolumeGeometry": "", + "IfcAdvancedBrep": "", + "IfcAdvancedBrepWithVoids": "", + "IfcCartesianPointList3D": "", + "IfcExtrudedAreaSolidTapered": "", + "IfcFixedReferenceSweptAreaSolid": "", + "IfcRevolvedAreaSolidTapered": "", + "IfcSweptDiskSolidPolygonal": "", + "IfcTriangulatedFaceSet": "", + "IfcBoundaryCurve": "", + "IfcBSplineCurveWithKnots": "", + "IfcBSplineSurfaceWithKnots": "", + "IfcCompositeCurveOnSurface": "", + "IfcCurveBoundedSurface": "", + "IfcCylindricalSurface": "", + "IfcOuterBoundaryCurve": "", + "IfcPcurve": "", + "IfcRationalBSplineCurveWithKnots": "", + "IfcRationalBSplineSurfaceWithKnots": "", + "IfcReparametrisedCompositeCurveSegment": "", + "IfcMaterialConstituent": "", + "IfcMaterialConstituentSet": "", + "IfcMaterialLayerWithOffsets": "", + "IfcMaterialProfile": "", + "IfcMaterialProfileSet": "", + "IfcMaterialProfileSetUsage": "", + "IfcMaterialProfileSetUsageTapering": "", + "IfcMaterialProfileWithOffsets": "", + "IfcMaterialRelationship": "", + "IfcConversionBasedUnitWithOffset": "", + "IfcVector": "", + "IfcColourRgbList": "", + "IfcIndexedColourMap": "", + "IfcIndexedTriangleTextureMap": "", + "IfcTextureVertexList": "", + "IfcMirroredProfileDef": "", + "IfcTable": "", + "IfcMapConversion": "", + "IfcProjectedCRS": "", + "IfcStructuralLoadConfiguration": "", + "IfcSurfaceReinforcementArea": "", + "IfcAdvancedFace": "", + "IfcTableColumn": "", + "IfcCartesianPointList2D": "", + "IfcIndexedPolyCurve": "", + "IfcIndexedPolygonalFace": "", + "IfcIndexedPolygonalFaceWithVoids": "", + "IfcPolygonalFaceSet": "", + "IfcIntersectionCurve": "", + "IfcSeamCurve": "", + "IfcSphericalSurface": "", + "IfcSurfaceCurve": "", + "IfcToroidalSurface": "" +} diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py new file mode 100644 index 0000000000..a86ab47937 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -0,0 +1,184 @@ +import os +import json +import time +import ifcopenshell + +# This is highly experimental and incomplete, however, it may work for simple datasets. +# In this simple implementation, we only support 2X3<->4 right now + +cwd = os.path.dirname(os.path.realpath(__file__)) + +class Migrator: + def __init__(self): + self.migrated_ids = {} + self.class_4_to_2x3 = json.load(open(os.path.join(cwd, "class_4_to_2x3.json"), "r")) + + # IFC4 classes, and their IFC4 attribute : IFC2X3 attributes + self.attribute_4_to_2x3 = json.load(open(os.path.join(cwd, "attribute_4_to_2x3.json"), "r")) + + self.default_values = { + "ChangeAction": "NOCHANGE", + "CompositionType": "ELEMENT", + "CrossSectionArea": 1, + "DataValue": 0, + "DefinedValues": [0], + "DefiningValues": [0], + "DestabilizingLoad": False, + "Edition": "", + "EndParam": 1.0, + "EnumerationValues": [0], + "GeodeticDatum": "", + "Intent": "", + "IsHeading": False, + "ListValues": [0], + "LongitudinalBarCrossSectionArea": 1, + "LongitudinalBarNominalDiameter": 1, + "LongitudinalBarSpacing": 1, + "Name": "", + "NominalDiameter": 1, + "PredefinedType": "NOTDEFINED", + "RowCells": [0], + "SequenceType": "NOTDEFINED", + "Source": "", + "StartParam": 0.0, + "TransverseBarCrossSectionArea": 1, + "TransverseBarNominalDiameter": 1, + "TransverseBarSpacing": 1, + # Manual additions from experience + "InteriorOrExteriorSpace": "NOTDEFINED", + } + self.default_entities = { + "CurrentValue": None, + "DepreciatedValue": None, + "Jurisdiction": None, + "OriginalValue": None, + "Owner": None, + "OwnerHistory": None, + "Position": None, + "PropertyReference": None, + "RepresentationContexts": None, + "ResponsiblePerson": None, + "ResponsiblePersons": None, + "Rows": None, + "TotalReplacementCost": None, + "UnitsInContext": None, + "User": None, + } + + def migrate(self, element, new_file): + try: + return new_file.by_id(self.migrated_ids[element.id()]) + except: + pass + print("Migrating", element) + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(new_file.schema) + new_element = self.migrate_class(element, new_file) + print("Migrated class from {} to {}".format(element, new_element)) + new_element_schema = schema.declaration_by_name(new_element.is_a()) + if not hasattr(new_element_schema, "all_attributes"): + return element # The element has no attributes, so migration is done + new_element = self.migrate_attributes(element, new_file, new_element, new_element_schema) + self.migrated_ids[element.id()] = new_element.id() + return new_element + + def migrate_class(self, element, new_file): + try: + new_element = new_file.create_entity(element.is_a()) + except: + # The element does not exist in this schema + # Complex migration is not yet supported (e.g. polygonal face set to faceted brep) + if new_file.schema == "IFC2X3": + new_element = new_file.create_entity(self.class_4_to_2x3[element.is_a()]) + elif new_file.schema == "IFC4": + print("Class migration to IFC4 not yet supported for", element) + return new_element + + def migrate_attributes(self, element, new_file, new_element, new_element_schema): + for i, attribute in enumerate(new_element_schema.all_attributes()): + if new_element_schema.derived()[i]: + continue + self.migrate_attribute(attribute, element, new_file, new_element, new_element_schema) + return new_element + + def migrate_attribute(self, attribute, element, new_file, new_element, new_element_schema): + print("Migrating attribute", element, new_element, attribute.name()) + if hasattr(element, attribute.name()): + value = getattr(element, attribute.name()) + print("Attribute names matched", value) + elif new_file.schema == "IFC2X3": + # IFC4 to IFC2X3: We know the IFC2X3 attribute name, but not its IFC4 equivalent + print("Searching for an equivalent", new_element, attribute.name()) + try: + equivalent_map = self.attribute_4_to_2x3[new_element.is_a()] + equivalent = list(equivalent_map.keys())[list(equivalent_map.values()).index(attribute.name())] + if hasattr(element, equivalent): + print("Equivalent found", equivalent) + value = getattr(element, equivalent) + else: + return + except: + print("Unable to find equivalent attribute of {} to migrate from {} to {}".format(attribute.name(), element, new_element)) + return # We tried our best + elif new_file.schema == "IFC4": + # IFC2X3 to IFC4: We know the IFC4 attribute name, but not its IFC2X3 equivalent + print("Searching for an equivalent", element, new_element, attribute.name()) + try: + equivalent = self.attribute_4_to_2x3[new_element.is_a()][attribute.name()] + print("Searching for equivalent", equivalent) + if hasattr(element, equivalent): + value = getattr(element, equivalent) + else: + return + except: + print("Unable to find equivalent attribute of {} to migrate from {} to {}".format(attribute.name(), element, new_element)) + return # We tried our best + + print("Continuing migration of {} to migrate from {} to {}".format(attribute.name(), element, new_element)) + if value is None and not attribute.optional(): + value = self.generate_default_value(attribute, new_file) + if value is None: + print("Failed to generate default value for {} on {}".format(attribute.name(), element)) + elif isinstance(value, ifcopenshell.entity_instance): + value = self.migrate(value, new_file) + elif isinstance(value, (list, tuple)): + if value and isinstance(value[0], ifcopenshell.entity_instance): + new_value = [] + for item in value: + new_value.append(self.migrate(item, new_file)) + value = new_value + setattr(new_element, attribute.name(), value) + + def generate_default_value(self, attribute, new_file): + if attribute.name() in self.default_values: + return self.default_values[attribute.name()] + elif self.default_entities[attribute.name()]: + return self.default_entities[attribute.name()] + elif attribute.name() == "OwnerHistory": + self.default_entities[attribute.name()] = new_file.create_entity("IfcOwnerHistory", **{ + "OwningUser": new_file.create_entity("IfcPersonAndOrganization", **{ + "ThePerson": new_file.create_entity("IfcPerson"), + "TheOrganization": new_file.create_entity("IfcOrganization", **{ + "Name": "IfcOpenShell Migrator" + }) + }), + "OwningApplication": new_file.create_entity("IfcApplication", **{ + "ApplicationDeveloper": new_file.create_entity("IfcOrganization", **{ + "Name": "IfcOpenShell Migrator" + }), + "Version": "Works for me", + "ApplicationFullName": "IfcOpenShell Migrator", + "ApplicationIdentifier": "IfcOpenShell Migrator", + }), + "ChangeAction": "NOCHANGE", + "CreationDate": int(time.time()) + }) + return self.default_entities[attribute.name()] + + +# f = ifcopenshell.open("/home/dion/migrate.ifc") +# g = ifcopenshell.file(schema="IFC4") +# migrator = Migrator() +# for e in f: +# print(e) +# print(migrator.migrate(e, g)) +# g.write("/home/dion/migrate2.ifc")