1 # SPDX-FileCopyrightText: 2013-2022 Blender Foundation
3 # 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 matrix
= obj
.matrix_world
.copy()
38 if not matrix
.is_identity
:
40 # Update normals if the matrix has no rotation.
41 matrix
.translation
.zero()
42 if not matrix
.is_identity
:
46 bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
51 def bmesh_from_object(obj
):
52 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
55 if obj
.mode
== 'EDIT':
56 bm
= bmesh
.from_edit_mesh(me
)
64 def bmesh_to_object(obj
, bm
):
65 """Object/Edit Mode update the object."""
68 if obj
.mode
== 'EDIT':
69 bmesh
.update_edit_mesh(me
, loop_triangles
=True)
75 def bmesh_calc_area(bm
):
76 """Calculate the surface area."""
77 return sum(f
.calc_area() for f
in bm
.faces
)
80 def bmesh_check_self_intersect_object(obj
):
81 """Check if any faces self intersect returns an array of edge index values."""
85 if not obj
.data
.polygons
:
86 return array
.array('i', ())
88 bm
= bmesh_copy_from_object(obj
, transform
=False, triangulate
=False)
89 tree
= mathutils
.bvhtree
.BVHTree
.FromBMesh(bm
, epsilon
=0.00001)
90 overlap
= tree
.overlap(tree
)
91 faces_error
= {i
for i_pair
in overlap
for i
in i_pair
}
93 return array
.array('i', faces_error
)
96 def bmesh_face_points_random(f
, num_points
=1, margin
=0.05):
98 from random
import uniform
100 # for pradictable results
103 uniform_args
= 0.0 + margin
, 1.0 - margin
104 vecs
= [v
.co
for v
in f
.verts
]
106 for _
in range(num_points
):
107 u1
= uniform(*uniform_args
)
108 u2
= uniform(*uniform_args
)
115 side1
= vecs
[1] - vecs
[0]
116 side2
= vecs
[2] - vecs
[0]
118 yield vecs
[0] + u1
* side1
+ u2
* side2
121 def bmesh_check_thick_object(obj
, thickness
):
126 bm
= bmesh_copy_from_object(obj
, transform
=True, triangulate
=False)
128 # map original faces to their index.
129 face_index_map_org
= {f
: i
for i
, f
in enumerate(bm
.faces
)}
130 ret
= bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
131 face_map
= ret
["face_map"]
133 # old edge -> new mapping
135 # Convert new/old map to index dict.
137 # Create a real mesh (lame!)
138 context
= bpy
.context
139 layer
= context
.view_layer
140 scene_collection
= context
.layer_collection
.collection
142 me_tmp
= bpy
.data
.meshes
.new(name
="~temp~")
144 obj_tmp
= bpy
.data
.objects
.new(name
=me_tmp
.name
, object_data
=me_tmp
)
145 scene_collection
.objects
.link(obj_tmp
)
148 ray_cast
= obj_tmp
.ray_cast
153 bm_faces_new
= bm
.faces
[:]
155 for f
in bm_faces_new
:
157 no_sta
= no
* EPS_BIAS
158 no_end
= no
* thickness
159 for p
in bmesh_face_points_random(f
, num_points
=6):
160 # Cast the ray backwards
165 ok
, _co
, no
, index
= ray_cast(p_a
, p_dir
, distance
=p_dir
.length
)
168 # Add the face we hit
169 for f_iter
in (f
, bm_faces_new
[index
]):
170 # if the face wasn't triangulated, just use existing
171 f_org
= face_map
.get(f_iter
, f_iter
)
172 f_org_index
= face_index_map_org
[f_org
]
173 faces_error
.add(f_org_index
)
177 scene_collection
.objects
.unlink(obj_tmp
)
178 bpy
.data
.objects
.remove(obj_tmp
)
179 bpy
.data
.meshes
.remove(me_tmp
)
183 return array
.array('i', faces_error
)
186 def face_is_distorted(ele
, angle_distort
):
190 for loop
in ele
.loops
:
191 loopno
= loop
.calc_normal()
193 if loopno
.dot(no
) < 0.0:
196 if angle_fn(loopno
, 1000.0) > angle_distort
: