# Bonsai - OpenBIM Blender Add-on # Copyright (C) 2021 Dion Moult # # This file is part of Bonsai. # # Bonsai is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Bonsai is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . # This can be packaged with `pyinstaller --onefile --hidden-import numpy --collect-all ifcopenshell --clean obj2ifc.py` import argparse import pywavefront # pyright: ignore[reportMissingImports] import ifcopenshell import ifcopenshell.api import ifcopenshell.api.aggregate import ifcopenshell.api.context import ifcopenshell.api.geometry import ifcopenshell.api.project import ifcopenshell.api.root import ifcopenshell.api.spatial import ifcopenshell.api.unit import ifcopenshell.api.owner.settings import ifcopenshell.guid from pathlib import Path import numpy as np class Obj2Ifc: def __init__(self, paths, outfile): self.paths = paths self.outfile = outfile self.basename = Path(outfile).stem def execute(self, version="IFC4"): self.create_ifc_file(version) for path in self.paths: self.path = path self._execute() def _execute(self): self.scene = pywavefront.Wavefront(self.path, create_materials=True, collect_faces=True) for mesh in self.scene.mesh_list: faces = mesh.faces ifc_faces = np.zeros([len(faces)], dtype=object) for i, face in enumerate(faces): ifc_faces[i] = self.file.createIfcFace( [ self.file.createIfcFaceOuterBound( self.file.createIfcPolyLoop( [ self.file.createIfcCartesianPoint(self.get_coordinates(self.scene.vertices[index])) for index in face ] ), True, ) ] ) representation = self.file.createIfcProductDefinitionShape( None, None, [ self.file.createIfcShapeRepresentation( self.context, "Body", "Brep", [self.file.createIfcFacetedBrep(self.file.createIfcClosedShell(ifc_faces.tolist()))], ) ], ) product = self.file.create_entity( "IfcBuildingElementProxy", GlobalId=ifcopenshell.guid.new(), Name=mesh.name or self.basename, Representation=representation, ) ifcopenshell.api.spatial.assign_container(self.file, products=[product], relating_structure=self.storey) product.ObjectPlacement = self.placement self.file.write(self.outfile) def get_coordinates(self, coordinates): # OBJ swaps Y and Z axis return [coordinates[0], -coordinates[2], coordinates[1]] def create_ifc_file(self, version): self.file = ifcopenshell.api.project.create_file(version=version) person = ifcopenshell.api.owner.add_person(self.file) person[0] = person.GivenName = None person.FamilyName = "user" org = ifcopenshell.api.owner.add_organisation(self.file) org[0] = None org.Name = "template" user = ifcopenshell.api.owner.add_person_and_organisation(self.file, person=person, organisation=org) application = ifcopenshell.api.owner.add_application(self.file) ifcopenshell.api.owner.settings.get_user = lambda ifc: user ifcopenshell.api.owner.settings.get_application = lambda ifc: application project = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject", name=self.basename) lengthunit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT") ifcopenshell.api.unit.assign_unit(self.file, units=[lengthunit]) model = ifcopenshell.api.context.add_context(self.file, context_type="Model") self.context = ifcopenshell.api.context.add_context( self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model, ) site = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSite", name="My Site") building = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuilding", name="My Building") self.storey = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuildingStorey", name="My Storey") ifcopenshell.api.aggregate.assign_object(self.file, products=[site], relating_object=project) ifcopenshell.api.aggregate.assign_object(self.file, products=[building], relating_object=site) ifcopenshell.api.aggregate.assign_object(self.file, products=[self.storey], relating_object=building) ifcopenshell.api.geometry.edit_object_placement(self.file, product=site) ifcopenshell.api.geometry.edit_object_placement(self.file, product=building) ifcopenshell.api.geometry.edit_object_placement(self.file, product=self.storey) self.origin = self.file.createIfcAxis2Placement3D( self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), self.file.createIfcDirection((0.0, 0.0, 1.0)), self.file.createIfcDirection((1.0, 0.0, 0.0)), ) self.placement = self.file.createIfcLocalPlacement(self.storey.ObjectPlacement, self.origin) self.history = ifcopenshell.api.owner.create_owner_history(self.file) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Converts an OBJ to an IFC") parser.add_argument("obj", type=str, nargs="+", help="One or more OBJ files") parser.add_argument("-o", "--output", type=str, help="The output file to save the patched IFC", default="out.ifc") args = parser.parse_args() obj2ifc = Obj2Ifc(args.obj, args.output) obj2ifc.execute()