diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index e9925eafca..32d33173be 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -275,7 +275,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator): ifc_representation_class: bpy.props.StringProperty() def _execute(self, context): - if context.active_object and context.active_object.mode != "OBJECT": + if context.view_layer.objects.active and context.view_layer.objects.active.mode != "OBJECT": # Ensure mode is object to prevent invalid mesh data causing CTD bpy.ops.object.mode_set(mode="OBJECT", toggle=False) diff --git a/src/blenderbim/blenderbim/bim/module/patch/operator.py b/src/blenderbim/blenderbim/bim/module/patch/operator.py index 6561bd43b7..c78ea435b8 100644 --- a/src/blenderbim/blenderbim/bim/module/patch/operator.py +++ b/src/blenderbim/blenderbim/bim/module/patch/operator.py @@ -87,6 +87,9 @@ class ExecuteIfcPatch(bpy.types.Operator): input_file = props.ifc_patch_input file = ifcopenshell.open(props.ifc_patch_input) + # Store this in case the patch recipe resets the Blender session, such as by loading a new project. + ifc_patch_output = props.ifc_patch_output + output = ifcpatch.execute( { "input": input_file, @@ -96,7 +99,7 @@ class ExecuteIfcPatch(bpy.types.Operator): "log": os.path.join(context.scene.BIMProperties.data_dir, "process.log"), } ) - ifcpatch.write(output, props.ifc_patch_output) + ifcpatch.write(output, ifc_patch_output) return {"FINISHED"} diff --git a/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py b/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py index b8050dc71d..5f5c031bf6 100644 --- a/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py +++ b/src/ifcpatch/ifcpatch/recipes/FixRevitTINs.py @@ -18,7 +18,7 @@ class Patcher: - def __init__(self, src, file, logger): + def __init__(self, src, file, logger, is_solid=True): """Fix missing or spot-coordinate bugged TINs loading in Revit TINs exported from 12D or Civil 3D may contain dense or highly obtuse @@ -27,11 +27,19 @@ class Patcher: See bug: https://github.com/Autodesk/revit-ifc/issues/511 - The solution will delete any faces with a Z normal less than 0.5. In - case the mesh has any side faces or thickness, this should leave only - the top surface which is relevant for spot coordinates and elevations. - (e.g. I have come across surfaces which are extruded by 1mm from Civil - 3D). Vertices closer than 10mm will also be merged to prevent dense + If `is_solid` is enabled, we assume the surface is represented as a + solid (e.g. I have come across surfaces which are extruded by 1mm from + Civil 3D). The solution will delete any faces with a Z normal less + than 0.5. In case the mesh has any side faces or thickness, this should + leave only the top surface which is relevant for spot coordinates and + elevations. + + If `is_solid` is disabled, then we assume the surface has no thickness. + In this scenario, all faces with any normals pointing in a negative + global Z direction will be flipped. This is because Revit cannot + annotate backfaces. + + Vertices closer than 10mm will also be merged to prevent dense portions of the TIN at a minor sacrifice of surveying accuracy. It will also triangulate all meshes to prevent non-coplanar surfaces, and delete any obtuse triangles where one of their XY angles is less than @@ -52,11 +60,12 @@ class Patcher: .. code:: python - ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "Fix12DToRevitTINs", "arguments": []}) + ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixRevitTINs", "arguments": []}) """ self.src = src self.file = file self.logger = logger + self.is_solid = is_solid def patch(self): import bpy @@ -77,30 +86,46 @@ class Patcher: for obj in bpy.data.objects: if not obj.BIMObjectProperties.ifc_definition_id or not obj.data: continue + if not obj.data.polygons: + continue data = obj.data bm = bmesh.new() bm.from_mesh(data) bm.faces.ensure_lookup_table() faces_to_delete = [] - for face in bm.faces: - if face.normal.z < 0.5: - faces_to_delete.append(face) + + if self.is_solid: + for face in bm.faces: + if face.normal.z < 0.5: + faces_to_delete.append(face) + bmesh.ops.delete(bm, geom=faces_to_delete, context='FACES_ONLY') bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.01) bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method="BEAUTY", ngon_method="BEAUTY") bm.faces.ensure_lookup_table() for polygon in bm.faces: - v1, v2, v3 = [v.co.to_2d() for v in polygon.verts] - d1 = degrees((v2 - v1).angle(v3 - v1)) - d2 = degrees((v3 - v2).angle(v1 - v2)) - d3 = degrees((v1 - v3).angle(v2 - v3)) - if d1 < angle_threshold or d2 < angle_threshold or d3 < angle_threshold: + try: + v1, v2, v3 = [v.co.to_2d() for v in polygon.verts] + d1 = degrees((v2 - v1).angle(v3 - v1)) + d2 = degrees((v3 - v2).angle(v1 - v2)) + d3 = degrees((v1 - v3).angle(v2 - v3)) + if d1 < angle_threshold or d2 < angle_threshold or d3 < angle_threshold: + bm.faces.remove(polygon) + except: bm.faces.remove(polygon) - bm.to_mesh(data) - bm.free() - bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="") + if bm.faces: + if not self.is_solid: + bmesh.ops.recalc_face_normals(bm, faces=bm.faces) + for face in bm.faces: + global_normal = obj.matrix_world.to_3x3() @ face.normal + if global_normal.z < 0: + face.normal_flip() + + bm.to_mesh(data) + bm.free() + bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="") tool.Ifc.get().history_size = old_history_size bpy.context.preferences.edit.undo_steps = old_undo_steps