Fix T77568: turnaround camera crashes undoing
[blender-addons.git] / object_print3d_utils / mesh_helpers.py
blobcffdf7c58655161057763ff9ebd2fd80f79ceaa1
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 #####
19 # <pep8-80 compliant>
21 # Generic helper functions, to be used by any modules.
24 import bmesh
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:
33 import bpy
34 depsgraph = bpy.context.evaluated_depsgraph_get()
35 obj_eval = obj.evaluated_get(depsgraph)
36 me = obj_eval.to_mesh()
37 bm = bmesh.new()
38 bm.from_mesh(me)
39 obj_eval.to_mesh_clear()
40 else:
41 me = obj.data
42 if obj.mode == 'EDIT':
43 bm_orig = bmesh.from_edit_mesh(me)
44 bm = bm_orig.copy()
45 else:
46 bm = bmesh.new()
47 bm.from_mesh(me)
49 # TODO. remove all customdata layers.
50 # would save ram
52 if transform:
53 bm.transform(obj.matrix_world)
55 if triangulate:
56 bmesh.ops.triangulate(bm, faces=bm.faces)
58 return bm
61 def bmesh_from_object(obj):
62 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
63 me = obj.data
65 if obj.mode == 'EDIT':
66 bm = bmesh.from_edit_mesh(me)
67 else:
68 bm = bmesh.new()
69 bm.from_mesh(me)
71 return bm
74 def bmesh_to_object(obj, bm):
75 """Object/Edit Mode update the object."""
76 me = obj.data
78 if obj.mode == 'EDIT':
79 bmesh.update_edit_mesh(me, True)
80 else:
81 bm.to_mesh(me)
82 me.update()
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."""
92 import array
93 import mathutils
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):
107 import random
108 from random import uniform
110 # for pradictable results
111 random.seed(f.index)
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)
119 u_tot = u1 + u2
121 if u_tot > 1.0:
122 u1 = 1.0 - u1
123 u2 = 1.0 - u2
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):
132 import array
133 import bpy
135 # Triangulate
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"]
142 del ret
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~")
153 bm.to_mesh(me_tmp)
154 obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp)
155 scene_collection.objects.link(obj_tmp)
157 layer.update()
158 ray_cast = obj_tmp.ray_cast
160 EPS_BIAS = 0.0001
162 faces_error = set()
163 bm_faces_new = bm.faces[:]
165 for f in bm_faces_new:
166 no = f.normal
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
171 p_a = p - no_sta
172 p_b = p - no_end
173 p_dir = p_b - p_a
175 ok, _co, no, index = ray_cast(p_a, p_dir, distance=p_dir.length)
177 if ok:
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)
185 bm.free()
187 scene_collection.objects.unlink(obj_tmp)
188 bpy.data.objects.remove(obj_tmp)
189 bpy.data.meshes.remove(me_tmp)
191 layer.update()
193 return array.array('i', faces_error)
196 def face_is_distorted(ele, angle_distort):
197 no = ele.normal
198 angle_fn = no.angle
200 for loop in ele.loops:
201 loopno = loop.calc_normal()
203 if loopno.dot(no) < 0.0:
204 loopno.negate()
206 if angle_fn(loopno, 1000.0) > angle_distort:
207 return True
209 return False