diff --git a/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py b/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py new file mode 100644 index 0000000000..0f1386d668 --- /dev/null +++ b/src/ifcpatch/ifcpatch/recipes/FixArchiCADToRevitSpaces.py @@ -0,0 +1,79 @@ +class Patcher: + def __init__(self, src, file, logger, args=None): + self.src = src + self.file = file + self.logger = logger + self.args = args + + def patch(self): + # The underlying problem is that Revit does not bring in IFC spaces as + # spaces / rooms in Revit when you link an IFC in Revit. This has been + # broken for at least 3 years and counting. This is a problem typically + # for ArchiCAD architects who want to send rooms to MEP folks using + # Revit. + # + # See bug: https://github.com/Autodesk/revit-ifc/issues/15 + # + # The solution is to open an IFC in Revit instead of linking it, which + # will convert IFC spaces into Revit rooms. However, there are very + # specific scenarios where Revit will convert these rooms, which have + # been painstakingly reverse engineered through trial and error. + # Firstly, the rooms should have a lower bound with a Z value matching + # the Z value of the storey it is on. Secondly, although faceted breps + # do work in some scenarios (I assume Revit has an internal topological + # analysis tool), conversion to an extruded area solid yield much more + # robust results. Finally, changing the Precision value to an obscene + # number very strangely seems to cause a lot more rooms to be converted + # successfully. + # + # This patch is designed to only work on ArchiCAD IFC exports where the + # only contents of the IFC is IFC space and _nothing else_. It also + # requires you to run it using Blender, as the geometric modification + # uses the Blender geometry engine. + import bpy + from blenderbim.bim.ifc import IfcStore + from mathutils import Vector, Matrix + + if len(bpy.data.objects) > 0: + while bpy.data.objects: + bpy.data.objects.remove(bpy.data.objects[0]) + bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True) + + bpy.ops.bim.load_project(filepath=self.src) + + def recalculate_origin(wall): + new_origin = wall.matrix_world @ Vector(wall.bound_box[0]) + if (wall.matrix_world.translation - new_origin).length < 0.001: + return + wall.data.transform( + Matrix.Translation( + (wall.matrix_world.inverted().to_quaternion() @ (wall.matrix_world.translation - new_origin)) + ) + ) + wall.matrix_world.translation = new_origin + + for obj in bpy.context.visible_objects: + if "IfcBuildingStorey" in obj.name and len(bpy.data.collections.get(obj.name).children) > 0: + target_z = obj.location.z + + for obj in bpy.context.visible_objects: + bpy.context.view_layer.update() + if "IfcSpace" not in obj.name: + continue + local_target_z = (obj.matrix_world.inverted() @ Vector((0, 0, target_z))).z + local_target_zup = (obj.matrix_world.inverted() @ Vector((0, 0, target_z + 3))).z + for v in obj.data.vertices: + if round(v.co.z, 3) == 0: + v.co.z = local_target_z + bpy.context.view_layer.update() + recalculate_origin(obj) + obj.select_set(True) + + bpy.ops.bim.update_representation( + ifc_representation_class="IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" + ) + for context in IfcStore.get_file().by_type("IfcGeometricRepresentationContext"): + if context.Precision: + context.Precision = 10 + + self.file = IfcStore.get_file()