Cleanup: io_import_BrushSet, autopep8, formatting
[blender-addons.git] / object_print3d_utils / mesh_helpers.py
blobfdbe776b7ae1893ba77aebafaa86aac570a0fd41
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.
8 import bmesh
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:
17 import bpy
18 depsgraph = bpy.context.evaluated_depsgraph_get()
19 obj_eval = obj.evaluated_get(depsgraph)
20 me = obj_eval.to_mesh()
21 bm = bmesh.new()
22 bm.from_mesh(me)
23 obj_eval.to_mesh_clear()
24 else:
25 me = obj.data
26 if obj.mode == 'EDIT':
27 bm_orig = bmesh.from_edit_mesh(me)
28 bm = bm_orig.copy()
29 else:
30 bm = bmesh.new()
31 bm.from_mesh(me)
33 # TODO. remove all customdata layers.
34 # would save ram
36 if transform:
37 matrix = obj.matrix_world.copy()
38 if not matrix.is_identity:
39 bm.transform(matrix)
40 # Update normals if the matrix has no rotation.
41 matrix.translation.zero()
42 if not matrix.is_identity:
43 bm.normal_update()
45 if triangulate:
46 bmesh.ops.triangulate(bm, faces=bm.faces)
48 return bm
51 def bmesh_from_object(obj):
52 """Object/Edit Mode get mesh, use bmesh_to_object() to write back."""
53 me = obj.data
55 if obj.mode == 'EDIT':
56 bm = bmesh.from_edit_mesh(me)
57 else:
58 bm = bmesh.new()
59 bm.from_mesh(me)
61 return bm
64 def bmesh_to_object(obj, bm):
65 """Object/Edit Mode update the object."""
66 me = obj.data
68 if obj.mode == 'EDIT':
69 bmesh.update_edit_mesh(me, loop_triangles=True)
70 else:
71 bm.to_mesh(me)
72 me.update()
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."""
82 import array
83 import mathutils
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):
97 import random
98 from random import uniform
100 # for pradictable results
101 random.seed(f.index)
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)
109 u_tot = u1 + u2
111 if u_tot > 1.0:
112 u1 = 1.0 - u1
113 u2 = 1.0 - u2
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):
122 import array
123 import bpy
125 # Triangulate
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"]
132 del ret
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~")
143 bm.to_mesh(me_tmp)
144 obj_tmp = bpy.data.objects.new(name=me_tmp.name, object_data=me_tmp)
145 scene_collection.objects.link(obj_tmp)
147 layer.update()
148 ray_cast = obj_tmp.ray_cast
150 EPS_BIAS = 0.0001
152 faces_error = set()
153 bm_faces_new = bm.faces[:]
155 for f in bm_faces_new:
156 no = f.normal
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
161 p_a = p - no_sta
162 p_b = p - no_end
163 p_dir = p_b - p_a
165 ok, _co, no, index = ray_cast(p_a, p_dir, distance=p_dir.length)
167 if ok:
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)
175 bm.free()
177 scene_collection.objects.unlink(obj_tmp)
178 bpy.data.objects.remove(obj_tmp)
179 bpy.data.meshes.remove(me_tmp)
181 layer.update()
183 return array.array('i', faces_error)
186 def face_is_distorted(ele, angle_distort):
187 no = ele.normal
188 angle_fn = no.angle
190 for loop in ele.loops:
191 loopno = loop.calc_normal()
193 if loopno.dot(no) < 0.0:
194 loopno.negate()
196 if angle_fn(loopno, 1000.0) > angle_distort:
197 return True
199 return False