1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 # Generic helper functions, to be used by any modules.
27 def bmesh_copy_from_object(obj
, transform
=True, triangulate
=True, apply_modifiers
=False):
28 """Returns a transformed, triangulated copy of the mesh"""
30 assert obj
.type == 'MESH'
32 if apply_modifiers
and obj
.modifiers
:
34 depsgraph
= bpy
.context
.evaluated_depsgraph_get()
35 obj_eval
= obj
.evaluated_get(depsgraph
)
36 me
= obj_eval
.to_mesh()
39 obj_eval
.to_mesh_clear()
42 if obj
.mode
== 'EDIT':
43 bm_orig
= bmesh
.from_edit_mesh(me
)
49 # TODO. remove all customdata layers.
53 bm
.transform(obj
.matrix_world
)
56 bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
61 def bmesh_from_object(obj
):
62 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
65 if obj
.mode
== 'EDIT':
66 bm
= bmesh
.from_edit_mesh(me
)
74 def bmesh_to_object(obj
, bm
):
75 """Object/Edit Mode update the object."""
78 if obj
.mode
== 'EDIT':
79 bmesh
.update_edit_mesh(me
, True)
85 def bmesh_calc_area(bm
):
86 """Calculate the surface area."""
87 return sum(f
.calc_area() for f
in bm
.faces
)
90 def bmesh_check_self_intersect_object(obj
):
91 """Check if any faces self intersect returns an array of edge index values."""
95 if not obj
.data
.polygons
:
96 return array
.array('i', ())
98 bm
= bmesh_copy_from_object(obj
, transform
=False, triangulate
=False)
99 tree
= mathutils
.bvhtree
.BVHTree
.FromBMesh(bm
, epsilon
=0.00001)
100 overlap
= tree
.overlap(tree
)
101 faces_error
= {i
for i_pair
in overlap
for i
in i_pair
}
103 return array
.array('i', faces_error
)
106 def bmesh_face_points_random(f
, num_points
=1, margin
=0.05):
108 from random
import uniform
110 # for pradictable results
113 uniform_args
= 0.0 + margin
, 1.0 - margin
114 vecs
= [v
.co
for v
in f
.verts
]
116 for _
in range(num_points
):
117 u1
= uniform(*uniform_args
)
118 u2
= uniform(*uniform_args
)
125 side1
= vecs
[1] - vecs
[0]
126 side2
= vecs
[2] - vecs
[0]
128 yield vecs
[0] + u1
* side1
+ u2
* side2
131 def bmesh_check_thick_object(obj
, thickness
):
136 bm
= bmesh_copy_from_object(obj
, transform
=True, triangulate
=False)
138 # map original faces to their index.
139 face_index_map_org
= {f
: i
for i
, f
in enumerate(bm
.faces
)}
140 ret
= bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
)
141 face_map
= ret
["face_map"]
143 # old edge -> new mapping
145 # Convert new/old map to index dict.
147 # Create a real mesh (lame!)
148 context
= bpy
.context
149 layer
= context
.view_layer
150 scene_collection
= context
.layer_collection
.collection
152 me_tmp
= bpy
.data
.meshes
.new(name
="~temp~")
154 obj_tmp
= bpy
.data
.objects
.new(name
=me_tmp
.name
, object_data
=me_tmp
)
155 scene_collection
.objects
.link(obj_tmp
)
158 ray_cast
= obj_tmp
.ray_cast
163 bm_faces_new
= bm
.faces
[:]
165 for f
in bm_faces_new
:
167 no_sta
= no
* EPS_BIAS
168 no_end
= no
* thickness
169 for p
in bmesh_face_points_random(f
, num_points
=6):
170 # Cast the ray backwards
175 ok
, _co
, no
, index
= ray_cast(p_a
, p_dir
, distance
=p_dir
.length
)
178 # Add the face we hit
179 for f_iter
in (f
, bm_faces_new
[index
]):
180 # if the face wasn't triangulated, just use existing
181 f_org
= face_map
.get(f_iter
, f_iter
)
182 f_org_index
= face_index_map_org
[f_org
]
183 faces_error
.add(f_org_index
)
187 scene_collection
.objects
.unlink(obj_tmp
)
188 bpy
.data
.objects
.remove(obj_tmp
)
189 bpy
.data
.meshes
.remove(me_tmp
)
193 return array
.array('i', faces_error
)
196 def face_is_distorted(ele
, angle_distort
):
200 for loop
in ele
.loops
:
201 loopno
= loop
.calc_normal()
203 if loopno
.dot(no
) < 0.0:
206 if angle_fn(loopno
, 1000.0) > angle_distort
: