diff --git a/src/ifccityjson/README.md b/src/ifccityjson/README.md index 95183145ed..d15fb01142 100644 --- a/src/ifccityjson/README.md +++ b/src/ifccityjson/README.md @@ -19,8 +19,8 @@ The example file that could be used is example/3D_BAG_example.json python ifccityjson.py -i example/3DBAG_example.json -o example/3DBAG_example.ifc -n identificatie ## Implemented geometries -- [ ] "MultiPoint" -- [ ] "MultiLineString" +- [x] "MultiPoint" +- [x] "MultiLineString" - [x] "MultiSurface" - [x] "CompositeSurface" - [x] "Solid": exterior shell @@ -34,5 +34,5 @@ The example file that could be used is example/3D_BAG_example.json - [x] Implement georeferencing - [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 geometries -- [ ] Implement conversion of all LODs instead of online the most detailed +- [ ] Implement conversion of all CitYJSON geometries +- [ ] Implement conversion of all LODs instead of only the most detailed diff --git a/src/ifccityjson/cityjson2ifc.py b/src/ifccityjson/cityjson2ifc.py index 0916fd5bb1..986d203913 100644 --- a/src/ifccityjson/cityjson2ifc.py +++ b/src/ifccityjson/cityjson2ifc.py @@ -106,15 +106,14 @@ class Cityjson2ifc: def create_new_file(self): self.IFC_model = ifcopenshell.api.run("project.create_file") self.IFC_project = ifcopenshell.api.run("root.create_entity", self.IFC_model, **{"ifc_class": "IfcProject"}) + ifcopenshell.api.run("unit.assign_unit", self.IFC_model, length={"is_metric": True, "raw": "METERS"}) self.properties["owner_history"] = self.create_owner_history() - ifcopenshell.api.run("unit.assign_unit", self.IFC_model) 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"}) - self.IFC_site = ifcopenshell.api.run("root.create_entity", self.IFC_model, **{"ifc_class": "IfcSite", "name": "My Site"}) @@ -150,7 +149,7 @@ class Cityjson2ifc: data.update(mapping[1]) # attributes - IFC_name = None + IFC_name = obj_id if "name_attribute" in self.properties and self.properties["name_attribute"] in obj.attributes: IFC_name = obj.attributes[self.properties["name_attribute"]] @@ -175,25 +174,26 @@ class Cityjson2ifc: 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) + child_data["Representation"] = self.create_IFC_representation(surface_geometry, 'brep') IFC_children.append(self.IFC_model.create_entity(IFC_child_class, **child_data)) else: - IFC_geometry = self.geometry.create_IFC_geometry(self.IFC_model, 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) + data["Representation"] = self.create_IFC_representation(IFC_geometry, shape_representation_type) data["GlobalId"] = ifcopenshell.guid.new() data["Name"] = IFC_name IFC_object = self.IFC_model.create_entity(IFC_class, **data) # Define aggregation - self.IFC_model.create_entity("IfcRelAggregates", + self.IFC_model.create_entity("IfcRelContainedInSpatialStructure", **{"GlobalId": ifcopenshell.guid.new(), - "RelatedObjects": [IFC_object], - "RelatingObject": self.IFC_site} + "RelatedElements": [IFC_object], + "RelatingStructure": self.IFC_site} ) if IFC_children: self.IFC_model.create_entity("IfcRelAggregates", @@ -203,10 +203,13 @@ class Cityjson2ifc: self.create_property_set(obj.attributes, IFC_object) - def create_IFC_representation(self, IFC_geometry): + def create_IFC_representation(self, IFC_geometry, shape_representation_type): + if not isinstance(IFC_geometry, list): + IFC_geometry = [IFC_geometry] + shape_representation = self.IFC_model.create_entity("IfcShapeRepresentation", - self.IFC_representation_sub_context, 'Body', 'Brep', - [IFC_geometry]) + self.IFC_representation_sub_context, 'Body', shape_representation_type, + IFC_geometry) product_representation = self.IFC_model.create_entity("IfcProductDefinitionShape", Representations=[shape_representation]) return product_representation diff --git a/src/ifccityjson/example/geometries.json b/src/ifccityjson/example/geometries.json new file mode 100644 index 0000000000..cf5f1c7812 --- /dev/null +++ b/src/ifccityjson/example/geometries.json @@ -0,0 +1,485 @@ +{ + "CityObjects": { + "multipoint": { + "geometry": [ + { + "type": "MultiPoint", + "lod": 1, + "boundaries": [ + 2, + 3, + 5, + 7 + ] + } + ], + "attributes": { + "function": "Being a MultiPoint geometry" + }, + "type": "GenericCityObject" + }, + "multiline": { + "geometry": [ + { + "type": "MultiLineString", + "lod": 1, + "boundaries": [ + [ + 2, + 3, + 9 + ], + [ + 10, + 8, + 10 + ] + ] + } + ], + "attributes": { + "function": "Being a MultiLineString" + }, + "type": "GenericCityObject" + }, + "multisurface": { + "geometry": [ + { + "type": "MultiSurface", + "lod": 1, + "boundaries": [ + [ + [ + 0, + 3, + 2, + 1 + ] + ], + [ + [ + 4, + 5, + 6, + 7 + ] + ], + [ + [ + 0, + 1, + 5, + 4 + ] + ] + ] + } + ], + "attributes": { + "function": "Being a MultiSurface without holes" + }, + "type": "GenericCityObject" + }, + "CompositeSurface": { + "geometry": [ + { + "type": "CompositeSurface", + "lod": 1, + "boundaries": [ + [ + [ + 0, + 3, + 2, + 1 + ] + ], + [ + [ + 4, + 5, + 6, + 7 + ] + ], + [ + [ + 0, + 1, + 5, + 4 + ] + ] + ] + } + ], + "attributes": { + "function": "Being a CompositeSurface without holes" + }, + "type": "GenericCityObject" + }, + "cube": { + "geometry": [ + { + "boundaries": [ + [ + [ + [ + 0, + 1, + 2, + 3 + ] + ], + [ + [ + 7, + 4, + 0, + 3 + ] + ], + [ + [ + 4, + 5, + 1, + 0 + ] + ], + [ + [ + 5, + 6, + 2, + 1 + ] + ], + [ + [ + 3, + 2, + 6, + 7 + ] + ], + [ + [ + 6, + 5, + 4, + 7 + ] + ] + ] + ], + "lod": 1, + "type": "Solid" + } + ], + "attributes": { + "function": "being a Solid geometry cube" + }, + "type": "GenericCityObject" + }, + "multicube": { + "geometry": [ + { + "boundaries": [[ + [ + [ + [ + 0, + 1, + 2, + 3 + ] + ], + [ + [ + 7, + 4, + 0, + 3 + ] + ], + [ + [ + 4, + 5, + 1, + 0 + ] + ], + [ + [ + 5, + 6, + 2, + 1 + ] + ], + [ + [ + 3, + 2, + 6, + 7 + ] + ], + [ + [ + 6, + 5, + 4, + 7 + ] + ] + ] + ],[ + [ + [ + [ + 0, + 1, + 2, + 3 + ] + ], + [ + [ + 7, + 4, + 0, + 3 + ] + ], + [ + [ + 4, + 5, + 1, + 0 + ] + ], + [ + [ + 5, + 6, + 2, + 1 + ] + ], + [ + [ + 3, + 2, + 6, + 7 + ] + ], + [ + [ + 6, + 5, + 4, + 7 + ] + ] + ] + ]], + "lod": 1, + "type": "MultiSolid" + } + ], + "attributes": { + "function": "being a MultiSolid geometry cube that is twice the same geometry" + }, + "type": "GenericCityObject" + }, + "compositecube": { + "geometry": [ + { + "boundaries": [[ + [ + [ + [ + 0, + 1, + 2, + 3 + ] + ], + [ + [ + 7, + 4, + 0, + 3 + ] + ], + [ + [ + 4, + 5, + 1, + 0 + ] + ], + [ + [ + 5, + 6, + 2, + 1 + ] + ], + [ + [ + 3, + 2, + 6, + 7 + ] + ], + [ + [ + 6, + 5, + 4, + 7 + ] + ] + ] + ],[ + [ + [ + [ + 0, + 1, + 2, + 3 + ] + ], + [ + [ + 7, + 4, + 0, + 3 + ] + ], + [ + [ + 4, + 5, + 1, + 0 + ] + ], + [ + [ + 5, + 6, + 2, + 1 + ] + ], + [ + [ + 3, + 2, + 6, + 7 + ] + ], + [ + [ + 6, + 5, + 4, + 7 + ] + ] + ] + ]], + "lod": 1, + "type": "CompositeSolid" + } + ], + "attributes": { + "function": "being a CompositeSolid geometry cube that is twice the same geometry" + }, + "type": "GenericCityObject" + } + }, + "type": "CityJSON", + "version": "1.0", + "vertices": [ + [ + 1.0, + 0.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.0, + 0.0, + 1.0 + ], + [ + 0.0, + -1.0, + 1.0 + ], + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0 + ], + [ + -1.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.0, + 0.0 + ], + [ + 5.0, + 30, + 2.0 + ], + [ + 6.0, + -1.0, + 2.0 + ], + [ + 8.0, + 7.0, + -1.0 + ] + ], + "metadata": { + "geographicalExtent": [ + -1.0, + -1.0, + 0.0, + 1.0, + 1.0, + 1.0 + ] + } +} \ No newline at end of file diff --git a/src/ifccityjson/geometry.py b/src/ifccityjson/geometry.py index 87cebb392c..4706dde237 100644 --- a/src/ifccityjson/geometry.py +++ b/src/ifccityjson/geometry.py @@ -35,16 +35,50 @@ class GeometryIO: IFC_cartesian_point = IFC_model.create_entity("IfcCartesianPoint", IFC_vertex) self.vertices[tuple(coord)] = IFC_cartesian_point + # See for CityJSON geometries: + # https://www.cityjson.org/dev/geom-arrays/ + # https://www.cityjson.org/specs/1.0.3/#geometry-objects def create_IFC_geometry(self, IFC_model, geometry): - if geometry.type == "Solid": - return self.create_IFC_closed_shell(IFC_model, geometry) - elif geometry.type in ["CompositeSolid", "MultiSolid"]: - return self.create_IFC_composite_closed_shell(IFC_model, geometry) + IFC_Geometry = None + geometry_type = 'brep' + if geometry.type in ["MultiPoint"]: + IFC_geometry = self.create_IFC_cartesian_point_list3D(IFC_model, geometry) + geometry_type = 'PointCloud' + elif geometry.type in ["MultiLineString"]: + IFC_geometry = self.create_IFC_composite_curve(IFC_model, geometry) + geometry_type = 'Curve3D' elif geometry.type in ["CompositeSurface", "MultiSurface"]: - return self.create_IFC_surface(IFC_model, geometry) + IFC_geometry = self.create_IFC_surface(IFC_model, geometry) + elif geometry.type == "Solid": + IFC_geometry = self.create_IFC_closed_shell(IFC_model, geometry) + elif geometry.type in ["CompositeSolid", "MultiSolid"]: + IFC_geometry = self.create_IFC_composite_closed_shell(IFC_model, geometry) + elif geometry.type in ["GeometryInstance"]: + warnings.warn("GeometryInstance is not supported.") + return None, None else: - warnings.warn("Types other than solids are not yet supported") - return + warnings.warn("Custom CityJSON geometries are not supported.") + return None, None + return IFC_geometry, geometry_type + + def create_IFC_cartesian_point_list3D(self, IFC_model, geometry): + # https://www.cityjson.org/dev/geom-arrays/ + # https://standards.buildingsmart.org/IFC/DEV/IFC4_2/FINAL/HTML/schema/ifcgeometricmodelresource/lexical/ifccartesianpointlist3d.htm + IFC_geometry = IFC_model.create_entity("IfcCartesianPointList3D", geometry.boundaries) + return IFC_geometry + + def create_IFC_composite_curve(self, IFC_model, geometry): + # https://www.cityjson.org/dev/geom-arrays/ + # https://standards.buildingsmart.org/IFC/DEV/IFC4_2/FINAL/HTML/schema/ifcgeometryresource/lexical/ifccompositecurve.htm + # https://standards.buildingsmart.org/IFC/DEV/IFC4_2/FINAL/HTML/schema/ifcgeometryresource/lexical/ifccompositecurvesegment.htm + IFC_geometry = [] + for line in geometry.boundaries: + vertices = [] + for vertex in line: + vertices.append(self.vertices[tuple(vertex)]) + polyline = IFC_model.create_entity("IfcPolyLine", vertices) + IFC_geometry.append(polyline) + return IFC_geometry def create_IFC_composite_closed_shell(self, IFC_model, geometry): shells = [] diff --git a/src/ifccityjson/ifccityjson.py b/src/ifccityjson/ifccityjson.py index c8cb23d691..791cf5d42b 100644 --- a/src/ifccityjson/ifccityjson.py +++ b/src/ifccityjson/ifccityjson.py @@ -25,6 +25,7 @@ from cityjson2ifc import Cityjson2ifc if __name__ == '__main__': # Example: # python ifccityjson.py -i example/3DBAG_example.json -o example/output.ifc -n identificatie + # python ifccityjson.py -i example/geometries.json -o example/geometry_output.ifc parser = argparse.ArgumentParser(description="") 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")