Update scripts to account for removal of the context override to bpy.ops
[blender-addons.git] / object_print3d_utils / mesh_helpers.py
blob444df1f11069e1dbf93950355d8a0f4cc78fa0f0
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # Generic helper functions, to be used by any modules.
6 import bmesh
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:
15 import bpy
16 depsgraph = bpy.context.evaluated_depsgraph_get()
17 obj_eval = obj.evaluated_get(depsgraph)
18 me = obj_eval.to_mesh()
19 bm = bmesh.new()
20 bm.from_mesh(me)
21 obj_eval.to_mesh_clear()
22 else:
23 me = obj.data
24 if obj.mode == 'EDIT':
25 bm_orig = bmesh.from_edit_mesh(me)
26 bm = bm_orig.copy()
27 else:
28 bm = bmesh.new()
29 bm.from_mesh(me)
31 # TODO. remove all customdata layers.
32 # would save ram
34 if transform:
35 matrix = obj.matrix_world.copy()
36 if not matrix.is_identity:
37 bm.transform(matrix)
38 # Update normals if the matrix has no rotation.
39 matrix.translation.zero()
40 if not matrix.is_identity:
41 bm.normal_update()
43 if triangulate:
44 bmesh.ops.triangulate(bm, faces=bm.faces)
46 return bm
49 def bmesh_from_object(obj):
50 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
51 me = obj.data
53 if obj.mode == 'EDIT':
54 bm = bmesh.from_edit_mesh(me)
55 else:
56 bm = bmesh.new()
57 bm.from_mesh(me)
59 return bm
62 def bmesh_to_object(obj, bm):
63 """Object/Edit Mode update the object."""
64 me = obj.data
66 if obj.mode == 'EDIT':
67 bmesh.update_edit_mesh(me, loop_triangles=True)
68 else:
69 bm.to_mesh(me)
70 me.update()
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."""
80 import array
81 import mathutils
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):
95 import random
96 from random import uniform
98 # for pradictable results
99 random.seed(f.index)
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)
107 u_tot = u1 + u2
109 if u_tot > 1.0:
110 u1 = 1.0 - u1
111 u2 = 1.0 - u2
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):
120 import array
121 import bpy
123 # Triangulate
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"]
130 del ret
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~")
141 bm.to_mesh(me_tmp)
142 obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp)
143 scene_collection.objects.link(obj_tmp)
145 layer.update()
146 ray_cast = obj_tmp.ray_cast
148 EPS_BIAS = 0.0001
150 faces_error = set()
151 bm_faces_new = bm.faces[:]
153 for f in bm_faces_new:
154 no = f.normal
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
159 p_a = p - no_sta
160 p_b = p - no_end
161 p_dir = p_b - p_a
163 ok, _co, no, index = ray_cast(p_a, p_dir, distance=p_dir.length)
165 if ok:
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)
173 bm.free()
175 scene_collection.objects.unlink(obj_tmp)
176 bpy.data.objects.remove(obj_tmp)
177 bpy.data.meshes.remove(me_tmp)
179 layer.update()
181 return array.array('i', faces_error)
184 def face_is_distorted(ele, angle_distort):
185 no = ele.normal
186 angle_fn = no.angle
188 for loop in ele.loops:
189 loopno = loop.calc_normal()
191 if loopno.dot(no) < 0.0:
192 loopno.negate()
194 if angle_fn(loopno, 1000.0) > angle_distort:
195 return True
197 return False