Files
IfcOpenShell/src/bonsai/scripts/obj2ifc.py
T
2025-06-09 18:03:19 +05:00

151 lines
6.4 KiB
Python

# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
# 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()