From 88669701b2d97bf68053f1edce1ba8d520e58cd6 Mon Sep 17 00:00:00 2001 From: LaurensJN Date: Tue, 17 Aug 2021 11:24:21 +0200 Subject: [PATCH] cityjson: Implement conversion of all LODs instead of only the most detailed --- src/ifccityjson/README.md | 2 +- src/ifccityjson/cityjson2ifc.py | 114 +++++++++++++++--------- src/ifccityjson/example/geometries.json | 4 +- src/ifccityjson/ifccityjson.py | 7 +- 4 files changed, 79 insertions(+), 48 deletions(-) diff --git a/src/ifccityjson/README.md b/src/ifccityjson/README.md index d15fb01142..1f61274082 100644 --- a/src/ifccityjson/README.md +++ b/src/ifccityjson/README.md @@ -35,4 +35,4 @@ The example file that could be used is example/3D_BAG_example.json - [x] Do not use template IFC for new IFC file, but make IFC file from scratch - [x] Create mapping to IFC for all CityJSON object types & semantic surfaces - [ ] Implement conversion of all CitYJSON geometries -- [ ] Implement conversion of all LODs instead of only the most detailed +- [x] Implement conversion of all LODs instead of only the most detailed diff --git a/src/ifccityjson/cityjson2ifc.py b/src/ifccityjson/cityjson2ifc.py index 8ed37c0d96..d2c3ea3b99 100644 --- a/src/ifccityjson/cityjson2ifc.py +++ b/src/ifccityjson/cityjson2ifc.py @@ -1,4 +1,3 @@ - # ifccityjson - Python CityJSON to IFC converter # Copyright (C) 2021 Laurens J.N. Oostwegel # @@ -26,9 +25,9 @@ JSON_TO_IFC = { "Building": ["IfcBuilding"], "BuildingPart": ["IfcBuilding", {"CompositionType": "PARTIAL"}], "BuildingInstallation": ["IfcBuildingElementProxy"], - "Road": ["IfcCivilElement"], # Update for IFC4.3 - "Railway": ["IfcCivilElement"], # Update for IFC4.3 - "TransportSquare": ["IfcCivilElement"], # Update for IFC4.3 + "Road": ["IfcCivilElement"], # Update for IFC4.3 + "Railway": ["IfcCivilElement"], # Update for IFC4.3 + "TransportSquare": ["IfcCivilElement"], # Update for IFC4.3 "TINRelief": ["IfcGeographicElement", {"PredefinedType": "TERRAIN"}], "WaterBody": ["IfcGeographicElement", {"PredefinedType": "USERDEFINED", "ObjectType": "WaterBody"}], # Update for IFC4.3 @@ -66,6 +65,7 @@ JSON_TO_IFC = { "AuxiliaryTrafficArea": ["IfcCivilElement"] # Update for IFC4.3 } + class Cityjson2ifc: def __init__(self): self.city_model = None @@ -74,11 +74,10 @@ class Cityjson2ifc: self.geometry = GeometryIO() self.configuration() - - def configuration(self, file_destination="output.ifc", name_attribute=None): + def configuration(self, file_destination="output.ifc", name_attribute=None, split=True): self.properties["file_destination"] = file_destination self.properties["name_attribute"] = name_attribute - + self.properties["split"] = split def convert(self, city_model): self.city_model = city_model @@ -109,7 +108,8 @@ class Cityjson2ifc: # Meter is assumed as unit for now unit = self.IFC_model.createIfcSIUnit(None, "LENGTHUNIT", None, "METRE") crs = self.IFC_model.create_entity("IfcProjectedCrs", Name=f"epsg:{epsg}") - self.IFC_model.create_entity("IfcMapConversion", self.IFC_representation_context, **self.properties["local_translation"]) + self.IFC_model.create_entity("IfcMapConversion", self.IFC_representation_context, + **self.properties["local_translation"]) def create_new_file(self): self.IFC_model = ifcopenshell.api.run("project.create_file") @@ -118,13 +118,27 @@ class Cityjson2ifc: self.properties["owner_history"] = self.create_owner_history() self.IFC_representation_context = ifcopenshell.api.run("context.add_context", self.IFC_model, **{"context": "Model"}) - self.IFC_representation_sub_context = ifcopenshell.api.run("context.add_context", self.IFC_model, - **{"context": "Model", - "subcontext": "Body", # LODs as subcontext - "target_view": "MODEL_VIEW"}) + + if not self.city_model.has_metadata() or "presentLoDs" not in self.city_model.j["metadata"]: + self.city_model.update_metadata() + self.IFC_representation_sub_contexts = {} + for lod in self.city_model.j["metadata"]["presentLoDs"]: + # TODO in ifcopenshell.api.context.add_context add support for UserDefinedTargetView + # self.IFC_representation_sub_contexts[str(lod)] = ifcopenshell.api.run("context.add_context", self.IFC_model, + # **{"context": "Model", + # "subcontext": "Body", + # "target_view": "USERDEFINED", + # "UserDefinedTargetView":str(lod)}) + self.IFC_representation_sub_contexts[str(lod)] = self.IFC_model.create_entity("IfcGeometricRepresentationSubContext", + **{"ContextType": "Model", + "ContextIdentifier": "Body", + "TargetView": "USERDEFINED", + "ParentContext": self.IFC_representation_context, + "UserDefinedTargetView": "LOD" + str(lod)}) + self.IFC_site = ifcopenshell.api.run("root.create_entity", self.IFC_model, - **{"ifc_class": "IfcSite", - "name": "My Site"}) + **{"ifc_class": "IfcSite", + "name": "My Site"}) def create_owner_history(self): actor = self.IFC_model.createIfcActorRole("ENGINEER", None, None) @@ -137,7 +151,8 @@ class Cityjson2ifc: p_o = self.IFC_model.createIfcPersonAndOrganization(person, organization) application = self.IFC_model.createIfcApplication(organization, "v0.0.x", "ifccityjson", "ifccityjson") timestamp = int(datetime.now().timestamp()) - ownerHistory = self.IFC_model.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, timestamp) + ownerHistory = self.IFC_model.createIfcOwnerHistory(p_o, application, "READWRITE", None, None, None, None, + timestamp) return ownerHistory @@ -168,29 +183,26 @@ class Cityjson2ifc: if len(obj.geometry) == 0: print(f"Warning: Object {obj_id} has no geometry.") - for geom in obj.geometry: - if geom.lod > lod: - geometry = geom - lod = geom.lod - IFC_semantic_surface_children = [] - if geometry and geometry.surfaces: - for surface_id in geometry.surfaces: - IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0] - child_data = {"GlobalId": ifcopenshell.guid.new(), - "Name": IFC_child_class - } - - # CREATE ENTITY - surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id) - if surface_geometry: - child_data["Representation"] = self.create_IFC_representation(surface_geometry, 'brep') - IFC_semantic_surface_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data)) - - elif geometry: - IFC_geometry, shape_representation_type = self.geometry.create_IFC_geometry(self.IFC_model, geometry) + IFC_shape_representations = [] + for geometry in obj.geometry: + lod = geometry.lod + IFC_geometry, shape_representation_type = None, None + # representation = self.create_IFC_LOD_representation(obj, geometry) + if geometry and geometry.surfaces: + IFC_semantic_surface_children = self.create_IFC_semantic_surface_children(geometry, lod) + if geometry: + IFC_geometry, shape_representation_type = self.geometry.create_IFC_geometry(self.IFC_model, + geometry) if IFC_geometry: - data["Representation"] = self.create_IFC_representation(IFC_geometry, shape_representation_type) + IFC_shape_representation = self.create_IFC_shape_representation(IFC_geometry, + shape_representation_type, + lod) + IFC_shape_representations.append(IFC_shape_representation) + + if len(IFC_shape_representations) > 0: + data["Representation"] = self.IFC_model.create_entity("IfcProductDefinitionShape", + Representations=IFC_shape_representations) data["GlobalId"] = ifcopenshell.guid.new() data["Name"] = IFC_name @@ -212,9 +224,9 @@ class Cityjson2ifc: if IFC_semantic_surface_children: self.IFC_model.create_entity("IfcRelAggregates", - **{"GlobalId": ifcopenshell.guid.new(), - "RelatedObjects": IFC_semantic_surface_children, - "RelatingObject": IFC_object}) + **{"GlobalId": ifcopenshell.guid.new(), + "RelatedObjects": IFC_semantic_surface_children, + "RelatingObject": IFC_object}) self.create_property_set(obj.attributes, IFC_object) @@ -225,16 +237,30 @@ class Cityjson2ifc: "RelatingStructure": parent_children['Parent']} ) - def create_IFC_representation(self, IFC_geometry, shape_representation_type): + def create_IFC_semantic_surface_children(self, geometry, lod): + IFC_semantic_surface_children = [] + for surface_id in geometry.surfaces: + IFC_child_class = JSON_TO_IFC[geometry.surfaces[surface_id]["type"]][0] + child_data = {"GlobalId": ifcopenshell.guid.new(), + "Name": IFC_child_class + } + + # CREATE ENTITY + surface_geometry = self.geometry.create_IFC_surface(self.IFC_model, geometry, surface_id) + if surface_geometry: + child_data["Representation"] = self.create_IFC_shape_representation(surface_geometry, 'brep', lod) + IFC_semantic_surface_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data)) + return IFC_semantic_surface_children + + def create_IFC_shape_representation(self, IFC_geometry, shape_representation_type, lod): if not isinstance(IFC_geometry, list): IFC_geometry = [IFC_geometry] shape_representation = self.IFC_model.create_entity("IfcShapeRepresentation", - self.IFC_representation_sub_context, 'Body', shape_representation_type, + self.IFC_representation_sub_contexts[str(lod)], 'Body', + shape_representation_type, IFC_geometry) - product_representation = self.IFC_model.create_entity("IfcProductDefinitionShape", - Representations=[shape_representation]) - return product_representation + return shape_representation def create_property_set(self, CJ_attributes, IFC_entity): IFC_object_properties = [] diff --git a/src/ifccityjson/example/geometries.json b/src/ifccityjson/example/geometries.json index cf5f1c7812..076c8142f2 100644 --- a/src/ifccityjson/example/geometries.json +++ b/src/ifccityjson/example/geometries.json @@ -22,7 +22,7 @@ "geometry": [ { "type": "MultiLineString", - "lod": 1, + "lod": 2.3, "boundaries": [ [ 2, @@ -84,7 +84,7 @@ "geometry": [ { "type": "CompositeSurface", - "lod": 1, + "lod": 1.2, "boundaries": [ [ [ diff --git a/src/ifccityjson/ifccityjson.py b/src/ifccityjson/ifccityjson.py index 791cf5d42b..a0fda53df1 100644 --- a/src/ifccityjson/ifccityjson.py +++ b/src/ifccityjson/ifccityjson.py @@ -16,7 +16,6 @@ # # You should have received a copy of the GNU Lesser General Public License # along with ifccityjson. If not, see . - import argparse from cjio import cityjson from cityjson2ifc import Cityjson2ifc @@ -30,6 +29,11 @@ if __name__ == '__main__': parser.add_argument("-i", "--input", type=str, help="input CityJSON file", required=True) parser.add_argument("-o", "--output", type=str, help="output IFC file. Standard is output.ifc") parser.add_argument("-n", "--name", type=str, help="Attribute containing the name") + parser.add_argument('--split-lod', dest='split', action='store_true', + help="Split the file in multiple LoDs") + parser.add_argument('--no-split-lod', dest='split', action='store_false', + help="Do not split the file in multiple LoDs") + parser.set_defaults(split=True) args = parser.parse_args() city_model = cityjson.load(args.input) @@ -38,6 +42,7 @@ if __name__ == '__main__': data["name_attribute"] = args.name if args.output: data["file_destination"] = args.output + data["split"] = args.split converter = Cityjson2ifc() converter.configuration(**data)