FixRevitTINs patch now also flips normals for surface TINs instead of solid TINs.

Minor fixes to use view_layer.object.active etc instead of context.active_object when running via Blender UI (it seems when you run the patch recipe from the Blender UI at least in later versions, context does not have an "active_object" property.
This commit is contained in:
Dion Moult
2023-11-30 14:58:17 +11:00
parent 0679956add
commit 7aa6be8e8a
3 changed files with 48 additions and 20 deletions
@@ -275,7 +275,7 @@ class UpdateRepresentation(bpy.types.Operator, Operator):
ifc_representation_class: bpy.props.StringProperty() ifc_representation_class: bpy.props.StringProperty()
def _execute(self, context): 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 # Ensure mode is object to prevent invalid mesh data causing CTD
bpy.ops.object.mode_set(mode="OBJECT", toggle=False) bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
@@ -87,6 +87,9 @@ class ExecuteIfcPatch(bpy.types.Operator):
input_file = props.ifc_patch_input input_file = props.ifc_patch_input
file = ifcopenshell.open(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( output = ifcpatch.execute(
{ {
"input": input_file, "input": input_file,
@@ -96,7 +99,7 @@ class ExecuteIfcPatch(bpy.types.Operator):
"log": os.path.join(context.scene.BIMProperties.data_dir, "process.log"), "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"} return {"FINISHED"}
+43 -18
View File
@@ -18,7 +18,7 @@
class Patcher: 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 """Fix missing or spot-coordinate bugged TINs loading in Revit
TINs exported from 12D or Civil 3D may contain dense or highly obtuse 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 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 If `is_solid` is enabled, we assume the surface is represented as a
case the mesh has any side faces or thickness, this should leave only solid (e.g. I have come across surfaces which are extruded by 1mm from
the top surface which is relevant for spot coordinates and elevations. Civil 3D). The solution will delete any faces with a Z normal less
(e.g. I have come across surfaces which are extruded by 1mm from Civil than 0.5. In case the mesh has any side faces or thickness, this should
3D). Vertices closer than 10mm will also be merged to prevent dense 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 portions of the TIN at a minor sacrifice of surveying accuracy. It will
also triangulate all meshes to prevent non-coplanar surfaces, and also triangulate all meshes to prevent non-coplanar surfaces, and
delete any obtuse triangles where one of their XY angles is less than delete any obtuse triangles where one of their XY angles is less than
@@ -52,11 +60,12 @@ class Patcher:
.. code:: python .. 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.src = src
self.file = file self.file = file
self.logger = logger self.logger = logger
self.is_solid = is_solid
def patch(self): def patch(self):
import bpy import bpy
@@ -77,30 +86,46 @@ class Patcher:
for obj in bpy.data.objects: for obj in bpy.data.objects:
if not obj.BIMObjectProperties.ifc_definition_id or not obj.data: if not obj.BIMObjectProperties.ifc_definition_id or not obj.data:
continue continue
if not obj.data.polygons:
continue
data = obj.data data = obj.data
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(data) bm.from_mesh(data)
bm.faces.ensure_lookup_table() bm.faces.ensure_lookup_table()
faces_to_delete = [] faces_to_delete = []
for face in bm.faces:
if face.normal.z < 0.5: if self.is_solid:
faces_to_delete.append(face) 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.delete(bm, geom=faces_to_delete, context='FACES_ONLY')
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.01) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.01)
bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method="BEAUTY", ngon_method="BEAUTY") bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method="BEAUTY", ngon_method="BEAUTY")
bm.faces.ensure_lookup_table() bm.faces.ensure_lookup_table()
for polygon in bm.faces: for polygon in bm.faces:
v1, v2, v3 = [v.co.to_2d() for v in polygon.verts] try:
d1 = degrees((v2 - v1).angle(v3 - v1)) v1, v2, v3 = [v.co.to_2d() for v in polygon.verts]
d2 = degrees((v3 - v2).angle(v1 - v2)) d1 = degrees((v2 - v1).angle(v3 - v1))
d3 = degrees((v1 - v3).angle(v2 - v3)) d2 = degrees((v3 - v2).angle(v1 - v2))
if d1 < angle_threshold or d2 < angle_threshold or d3 < angle_threshold: 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.faces.remove(polygon)
bm.to_mesh(data) if bm.faces:
bm.free() if not self.is_solid:
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="") 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 tool.Ifc.get().history_size = old_history_size
bpy.context.preferences.edit.undo_steps = old_undo_steps bpy.context.preferences.edit.undo_steps = old_undo_steps