1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Generic helper functions, to be used by any modules.
9 def bmesh_copy_from_object(obj
, transform
=True, triangulate
=True, apply_modifiers
=False):
10 """Returns a transformed, triangulated copy of the mesh"""
12 assert obj
.type == 'MESH'
14 if apply_modifiers
and obj
.modifiers
:
16 depsgraph
= bpy
.context
.evaluated_depsgraph_get()
17 obj_eval
= obj
.evaluated_get(depsgraph
)
18 me
= obj_eval
.to_mesh()
21 obj_eval
.to_mesh_clear()
24 if obj
.mode
== 'EDIT':
25 bm_orig
= bmesh
.from_edit_mesh(me
)
31 # TODO. remove all customdata layers.
35 matrix
= obj
.matrix_world
.copy()
36 if not matrix
.is_identity
:
38 # Update normals if the matrix has no rotation.
39 matrix
.translation
.zero()
40 if not matrix
.is_identity
:
44 bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
49 def bmesh_from_object(obj
):
50 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
53 if obj
.mode
== 'EDIT':
54 bm
= bmesh
.from_edit_mesh(me
)
62 def bmesh_to_object(obj
, bm
):
63 """Object/Edit Mode update the object."""
66 if obj
.mode
== 'EDIT':
67 bmesh
.update_edit_mesh(me
, loop_triangles
=True)
73 def bmesh_calc_area(bm
):
74 """Calculate the surface area."""
75 return sum(f
.calc_area() for f
in bm
.faces
)
78 def bmesh_check_self_intersect_object(obj
):
79 """Check if any faces self intersect returns an array of edge index values."""
83 if not obj
.data
.polygons
:
84 return array
.array('i', ())
86 bm
= bmesh_copy_from_object(obj
, transform
=False, triangulate
=False)
87 tree
= mathutils
.bvhtree
.BVHTree
.FromBMesh(bm
, epsilon
=0.00001)
88 overlap
= tree
.overlap(tree
)
89 faces_error
= {i
for i_pair
in overlap
for i
in i_pair
}
91 return array
.array('i', faces_error
)
94 def bmesh_face_points_random(f
, num_points
=1, margin
=0.05):
96 from random
import uniform
98 # for pradictable results
101 uniform_args
= 0.0 + margin
, 1.0 - margin
102 vecs
= [v
.co
for v
in f
.verts
]
104 for _
in range(num_points
):
105 u1
= uniform(*uniform_args
)
106 u2
= uniform(*uniform_args
)
113 side1
= vecs
[1] - vecs
[0]
114 side2
= vecs
[2] - vecs
[0]
116 yield vecs
[0] + u1
* side1
+ u2
* side2
119 def bmesh_check_thick_object(obj
, thickness
):
124 bm
= bmesh_copy_from_object(obj
, transform
=True, triangulate
=False)
126 # map original faces to their index.
127 face_index_map_org
= {f
: i
for i
, f
in enumerate(bm
.faces
)}
128 ret
= bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
129 face_map
= ret
["face_map"]
131 # old edge -> new mapping
133 # Convert new/old map to index dict.
135 # Create a real mesh (lame!)
136 context
= bpy
.context
137 layer
= context
.view_layer
138 scene_collection
= context
.layer_collection
.collection
140 me_tmp
= bpy
.data
.meshes
.new(name
="~temp~")
142 obj_tmp
= bpy
.data
.objects
.new(name
=me_tmp
.name
, object_data
=me_tmp
)
143 scene_collection
.objects
.link(obj_tmp
)
146 ray_cast
= obj_tmp
.ray_cast
151 bm_faces_new
= bm
.faces
[:]
153 for f
in bm_faces_new
:
155 no_sta
= no
* EPS_BIAS
156 no_end
= no
* thickness
157 for p
in bmesh_face_points_random(f
, num_points
=6):
158 # Cast the ray backwards
163 ok
, _co
, no
, index
= ray_cast(p_a
, p_dir
, distance
=p_dir
.length
)
166 # Add the face we hit
167 for f_iter
in (f
, bm_faces_new
[index
]):
168 # if the face wasn't triangulated, just use existing
169 f_org
= face_map
.get(f_iter
, f_iter
)
170 f_org_index
= face_index_map_org
[f_org
]
171 faces_error
.add(f_org_index
)
175 scene_collection
.objects
.unlink(obj_tmp
)
176 bpy
.data
.objects
.remove(obj_tmp
)
177 bpy
.data
.meshes
.remove(me_tmp
)
181 return array
.array('i', faces_error
)
184 def face_is_distorted(ele
, angle_distort
):
188 for loop
in ele
.loops
:
189 loopno
= loop
.calc_normal()
191 if loopno
.dot(no
) < 0.0:
194 if angle_fn(loopno
, 1000.0) > angle_distort
: