mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 06:21:40 +00:00
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:
@@ -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"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user