1 # SPDX-License-Identifier: GPL-2.0-or-later
5 # Generic helper functions, to be used by any modules.
11 def bmesh_copy_from_object(obj
, transform
=True, triangulate
=True, apply_modifiers
=False):
12 """Returns a transformed, triangulated copy of the mesh"""
14 assert obj
.type == 'MESH'
16 if apply_modifiers
and obj
.modifiers
:
18 depsgraph
= bpy
.context
.evaluated_depsgraph_get()
19 obj_eval
= obj
.evaluated_get(depsgraph
)
20 me
= obj_eval
.to_mesh()
23 obj_eval
.to_mesh_clear()
26 if obj
.mode
== 'EDIT':
27 bm_orig
= bmesh
.from_edit_mesh(me
)
33 # TODO. remove all customdata layers.
37 bm
.transform(obj
.matrix_world
)
40 bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
45 def bmesh_from_object(obj
):
46 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
49 if obj
.mode
== 'EDIT':
50 bm
= bmesh
.from_edit_mesh(me
)
58 def bmesh_to_object(obj
, bm
):
59 """Object/Edit Mode update the object."""
62 if obj
.mode
== 'EDIT':
63 bmesh
.update_edit_mesh(me
, loop_triangles
=True)
69 def bmesh_calc_area(bm
):
70 """Calculate the surface area."""
71 return sum(f
.calc_area() for f
in bm
.faces
)
74 def bmesh_check_self_intersect_object(obj
):
75 """Check if any faces self intersect returns an array of edge index values."""
79 if not obj
.data
.polygons
:
80 return array
.array('i', ())
82 bm
= bmesh_copy_from_object(obj
, transform
=False, triangulate
=False)
83 tree
= mathutils
.bvhtree
.BVHTree
.FromBMesh(bm
, epsilon
=0.00001)
84 overlap
= tree
.overlap(tree
)
85 faces_error
= {i
for i_pair
in overlap
for i
in i_pair
}
87 return array
.array('i', faces_error
)
90 def bmesh_face_points_random(f
, num_points
=1, margin
=0.05):
92 from random
import uniform
94 # for pradictable results
97 uniform_args
= 0.0 + margin
, 1.0 - margin
98 vecs
= [v
.co
for v
in f
.verts
]
100 for _
in range(num_points
):
101 u1
= uniform(*uniform_args
)
102 u2
= uniform(*uniform_args
)
109 side1
= vecs
[1] - vecs
[0]
110 side2
= vecs
[2] - vecs
[0]
112 yield vecs
[0] + u1
* side1
+ u2
* side2
115 def bmesh_check_thick_object(obj
, thickness
):
120 bm
= bmesh_copy_from_object(obj
, transform
=True, triangulate
=False)
122 # map original faces to their index.
123 face_index_map_org
= {f
: i
for i
, f
in enumerate(bm
.faces
)}
124 ret
= bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
125 face_map
= ret
["face_map"]
127 # old edge -> new mapping
129 # Convert new/old map to index dict.
131 # Create a real mesh (lame!)
132 context
= bpy
.context
133 layer
= context
.view_layer
134 scene_collection
= context
.layer_collection
.collection
136 me_tmp
= bpy
.data
.meshes
.new(name
="~temp~")
138 obj_tmp
= bpy
.data
.objects
.new(name
=me_tmp
.name
, object_data
=me_tmp
)
139 scene_collection
.objects
.link(obj_tmp
)
142 ray_cast
= obj_tmp
.ray_cast
147 bm_faces_new
= bm
.faces
[:]
149 for f
in bm_faces_new
:
151 no_sta
= no
* EPS_BIAS
152 no_end
= no
* thickness
153 for p
in bmesh_face_points_random(f
, num_points
=6):
154 # Cast the ray backwards
159 ok
, _co
, no
, index
= ray_cast(p_a
, p_dir
, distance
=p_dir
.length
)
162 # Add the face we hit
163 for f_iter
in (f
, bm_faces_new
[index
]):
164 # if the face wasn't triangulated, just use existing
165 f_org
= face_map
.get(f_iter
, f_iter
)
166 f_org_index
= face_index_map_org
[f_org
]
167 faces_error
.add(f_org_index
)
171 scene_collection
.objects
.unlink(obj_tmp
)
172 bpy
.data
.objects
.remove(obj_tmp
)
173 bpy
.data
.meshes
.remove(me_tmp
)
177 return array
.array('i', faces_error
)
180 def face_is_distorted(ele
, angle_distort
):
184 for loop
in ele
.loops
:
185 loopno
= loop
.calc_normal()
187 if loopno
.dot(no
) < 0.0:
190 if angle_fn(loopno
, 1000.0) > angle_distort
: