Cleanup: quiet character escape warnings
[blender-addons.git] / object_fracture_cell / fracture_cell_setup.py
blobf083af2fe7e5e6ac4cedcf4dca17086fae435981
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 compliant>
21 # Script copyright (C) Blender Foundation 2012
23 import bpy
24 import bmesh
27 def _redraw_yasiamevil():
28 _redraw_yasiamevil.opr(**_redraw_yasiamevil.arg)
29 _redraw_yasiamevil.opr = bpy.ops.wm.redraw_timer
30 _redraw_yasiamevil.arg = dict(type='DRAW_WIN_SWAP', iterations=1)
33 def _points_from_object(depsgraph, scene, obj, source):
35 _source_all = {
36 'PARTICLE_OWN', 'PARTICLE_CHILD',
37 'PENCIL',
38 'VERT_OWN', 'VERT_CHILD',
41 # print(source - _source_all)
42 # print(source)
43 assert(len(source | _source_all) == len(_source_all))
44 assert(len(source))
46 points = []
48 def edge_center(mesh, edge):
49 v1, v2 = edge.vertices
50 return (mesh.vertices[v1].co + mesh.vertices[v2].co) / 2.0
52 def poly_center(mesh, poly):
53 from mathutils import Vector
54 co = Vector()
55 tot = 0
56 for i in poly.loop_indices:
57 co += mesh.vertices[mesh.loops[i].vertex_index].co
58 tot += 1
59 return co / tot
61 def points_from_verts(obj):
62 """Takes points from _any_ object with geometry"""
63 if obj.type == 'MESH':
64 mesh = obj.data
65 matrix = obj.matrix_world.copy()
66 points.extend([matrix @ v.co for v in mesh.vertices])
67 else:
68 ob_eval = ob.evaluated_get(depsgraph)
69 try:
70 mesh = ob_eval.to_mesh()
71 except:
72 mesh = None
74 if mesh is not None:
75 matrix = obj.matrix_world.copy()
76 points.extend([matrix @ v.co for v in mesh.vertices])
77 ob_eval.to_mesh_clear()
79 def points_from_particles(obj):
80 obj_eval = obj.evaluated_get(depsgraph)
81 points.extend([p.location.copy()
82 for psys in obj_eval.particle_systems
83 for p in psys.particles])
85 # geom own
86 if 'VERT_OWN' in source:
87 points_from_verts(obj)
89 # geom children
90 if 'VERT_CHILD' in source:
91 for obj_child in obj.children:
92 points_from_verts(obj_child)
94 # geom particles
95 if 'PARTICLE_OWN' in source:
96 points_from_particles(obj)
98 if 'PARTICLE_CHILD' in source:
99 for obj_child in obj.children:
100 points_from_particles(obj_child)
102 # grease pencil
103 def get_points(stroke):
104 return [point.co.copy() for point in stroke.points]
106 def get_splines(gp):
107 if gp.layers.active:
108 frame = gp.layers.active.active_frame
109 return [get_points(stroke) for stroke in frame.strokes]
110 else:
111 return []
113 if 'PENCIL' in source:
114 # Used to be from object in 2.7x, now from scene.
115 gp = scene.grease_pencil
116 if gp:
117 points.extend([p for spline in get_splines(gp)
118 for p in spline])
120 print("Found %d points" % len(points))
122 return points
125 def cell_fracture_objects(context, collection, obj,
126 source={'PARTICLE_OWN'},
127 source_limit=0,
128 source_noise=0.0,
129 clean=True,
130 # operator options
131 use_smooth_faces=False,
132 use_data_match=False,
133 use_debug_points=False,
134 margin=0.0,
135 material_index=0,
136 use_debug_redraw=False,
137 cell_scale=(1.0, 1.0, 1.0),
140 from . import fracture_cell_calc
141 depsgraph = context.evaluated_depsgraph_get()
142 scene = context.scene
143 view_layer = context.view_layer
145 # -------------------------------------------------------------------------
146 # GET POINTS
148 points = _points_from_object(depsgraph, scene, obj, source)
150 if not points:
151 # print using fallback
152 points = _points_from_object(depsgraph, scene, obj, {'VERT_OWN'})
154 if not points:
155 print("no points found")
156 return []
158 # apply optional clamp
159 if source_limit != 0 and source_limit < len(points):
160 import random
161 random.shuffle(points)
162 points[source_limit:] = []
164 # saddly we cant be sure there are no doubles
165 from mathutils import Vector
166 to_tuple = Vector.to_tuple
167 points = list({to_tuple(p, 4): p for p in points}.values())
168 del to_tuple
169 del Vector
171 # end remove doubles
172 # ------------------
174 if source_noise > 0.0:
175 from random import random
176 # boundbox approx of overall scale
177 from mathutils import Vector
178 matrix = obj.matrix_world.copy()
179 bb_world = [matrix @ Vector(v) for v in obj.bound_box]
180 scalar = source_noise * ((bb_world[0] - bb_world[6]).length / 2.0)
182 from mathutils.noise import random_unit_vector
184 points[:] = [p + (random_unit_vector() * (scalar * random())) for p in points]
186 if use_debug_points:
187 bm = bmesh.new()
188 for p in points:
189 bm.verts.new(p)
190 mesh_tmp = bpy.data.meshes.new(name="DebugPoints")
191 bm.to_mesh(mesh_tmp)
192 bm.free()
193 obj_tmp = bpy.data.objects.new(name=mesh_tmp.name, object_data=mesh_tmp)
194 collection.objects.link(obj_tmp)
195 del obj_tmp, mesh_tmp
197 mesh = obj.data
198 matrix = obj.matrix_world.copy()
199 verts = [matrix @ v.co for v in mesh.vertices]
201 cells = fracture_cell_calc.points_as_bmesh_cells(verts,
202 points,
203 cell_scale,
204 margin_cell=margin)
206 # some hacks here :S
207 cell_name = obj.name + "_cell"
209 objects = []
211 for center_point, cell_points in cells:
213 # ---------------------------------------------------------------------
214 # BMESH
216 # create the convex hulls
217 bm = bmesh.new()
219 # WORKAROUND FOR CONVEX HULL BUG/LIMIT
220 # XXX small noise
221 import random
222 def R():
223 return (random.random() - 0.5) * 0.001
224 # XXX small noise
226 for i, co in enumerate(cell_points):
228 # XXX small noise
229 co.x += R()
230 co.y += R()
231 co.z += R()
232 # XXX small noise
234 bm_vert = bm.verts.new(co)
236 import mathutils
237 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
238 try:
239 bmesh.ops.convex_hull(bm, input=bm.verts)
240 except RuntimeError:
241 import traceback
242 traceback.print_exc()
244 if clean:
245 bm.normal_update()
246 try:
247 bmesh.ops.dissolve_limit(bm, verts=bm.verts, angle_limit=0.001)
248 except RuntimeError:
249 import traceback
250 traceback.print_exc()
251 # Smooth faces will remain only inner faces, after applying boolean modifier.
252 if use_smooth_faces:
253 for bm_face in bm.faces:
254 bm_face.smooth = True
256 if material_index != 0:
257 for bm_face in bm.faces:
258 bm_face.material_index = material_index
261 # ---------------------------------------------------------------------
262 # MESH
263 mesh_dst = bpy.data.meshes.new(name=cell_name)
265 bm.to_mesh(mesh_dst)
266 bm.free()
267 del bm
269 if use_data_match:
270 # match materials and data layers so boolean displays them
271 # currently only materials + data layers, could do others...
272 mesh_src = obj.data
273 for mat in mesh_src.materials:
274 mesh_dst.materials.append(mat)
275 for lay_attr in ("vertex_colors", "uv_layers"):
276 lay_src = getattr(mesh_src, lay_attr)
277 lay_dst = getattr(mesh_dst, lay_attr)
278 for key in lay_src.keys():
279 lay_dst.new(name=key)
281 # ---------------------------------------------------------------------
282 # OBJECT
284 obj_cell = bpy.data.objects.new(name=cell_name, object_data=mesh_dst)
285 collection.objects.link(obj_cell)
286 # scene.objects.active = obj_cell
287 obj_cell.location = center_point
289 objects.append(obj_cell)
291 # support for object materials
292 if use_data_match:
293 for i in range(len(mesh_dst.materials)):
294 slot_src = obj.material_slots[i]
295 slot_dst = obj_cell.material_slots[i]
297 slot_dst.link = slot_src.link
298 slot_dst.material = slot_src.material
300 if use_debug_redraw:
301 view_layer.update()
302 _redraw_yasiamevil()
304 view_layer.update()
306 return objects
309 def cell_fracture_boolean(context, collection, obj, objects,
310 use_debug_bool=False,
311 clean=True,
312 use_island_split=False,
313 use_interior_hide=False,
314 use_debug_redraw=False,
315 level=0,
316 remove_doubles=True
319 objects_boolean = []
320 scene = context.scene
321 view_layer = context.view_layer
323 if use_interior_hide and level == 0:
324 # only set for level 0
325 obj.data.polygons.foreach_set("hide", [False] * len(obj.data.polygons))
327 for obj_cell in objects:
328 mod = obj_cell.modifiers.new(name="Boolean", type='BOOLEAN')
329 mod.object = obj
330 mod.operation = 'INTERSECT'
332 if not use_debug_bool:
334 if use_interior_hide:
335 obj_cell.data.polygons.foreach_set("hide", [True] * len(obj_cell.data.polygons))
337 # Calculates all booleans at once (faster).
338 depsgraph = context.evaluated_depsgraph_get()
340 for obj_cell in objects:
342 if not use_debug_bool:
344 obj_cell_eval = obj_cell.evaluated_get(depsgraph)
345 mesh_new = bpy.data.meshes.new_from_object(obj_cell_eval)
346 mesh_old = obj_cell.data
347 obj_cell.data = mesh_new
348 obj_cell.modifiers.remove(obj_cell.modifiers[-1])
350 # remove if not valid
351 if not mesh_old.users:
352 bpy.data.meshes.remove(mesh_old)
353 if not mesh_new.vertices:
354 collection.objects.unlink(obj_cell)
355 if not obj_cell.users:
356 bpy.data.objects.remove(obj_cell)
357 obj_cell = None
358 if not mesh_new.users:
359 bpy.data.meshes.remove(mesh_new)
360 mesh_new = None
362 # avoid unneeded bmesh re-conversion
363 if mesh_new is not None:
364 bm = None
366 if clean:
367 if bm is None: # ok this will always be true for now...
368 bm = bmesh.new()
369 bm.from_mesh(mesh_new)
370 bm.normal_update()
371 try:
372 bmesh.ops.dissolve_limit(bm, verts=bm.verts, edges=bm.edges, angle_limit=0.001)
373 except RuntimeError:
374 import traceback
375 traceback.print_exc()
377 if remove_doubles:
378 if bm is None:
379 bm = bmesh.new()
380 bm.from_mesh(mesh_new)
381 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
383 if bm is not None:
384 bm.to_mesh(mesh_new)
385 bm.free()
387 del mesh_new
388 del mesh_old
390 if obj_cell is not None:
391 objects_boolean.append(obj_cell)
393 if use_debug_redraw:
394 _redraw_yasiamevil()
396 if (not use_debug_bool) and use_island_split:
397 # this is ugly and Im not proud of this - campbell
398 for ob in view_layer.objects:
399 ob.select_set(False)
400 for obj_cell in objects_boolean:
401 obj_cell.select_set(True)
403 bpy.ops.mesh.separate(type='LOOSE')
405 objects_boolean[:] = [obj_cell for obj_cell in view_layer.objects if obj_cell.select_get()]
407 context.view_layer.update()
409 return objects_boolean
412 def cell_fracture_interior_handle(objects,
413 use_interior_vgroup=False,
414 use_sharp_edges=False,
415 use_sharp_edges_apply=False,
417 """Run after doing _all_ booleans"""
419 assert(use_interior_vgroup or use_sharp_edges or use_sharp_edges_apply)
421 for obj_cell in objects:
422 mesh = obj_cell.data
423 bm = bmesh.new()
424 bm.from_mesh(mesh)
426 if use_interior_vgroup:
427 for bm_vert in bm.verts:
428 bm_vert.tag = True
429 for bm_face in bm.faces:
430 if not bm_face.hide:
431 for bm_vert in bm_face.verts:
432 bm_vert.tag = False
434 # now add all vgroups
435 defvert_lay = bm.verts.layers.deform.verify()
436 for bm_vert in bm.verts:
437 if bm_vert.tag:
438 bm_vert[defvert_lay][0] = 1.0
440 # add a vgroup
441 obj_cell.vertex_groups.new(name="Interior")
443 if use_sharp_edges:
444 for bm_edge in bm.edges:
445 if len({bm_face.hide for bm_face in bm_edge.link_faces}) == 2:
446 bm_edge.smooth = False
448 if use_sharp_edges_apply:
449 edges = [edge for edge in bm.edges if edge.smooth is False]
450 if edges:
451 bm.normal_update()
452 bmesh.ops.split_edges(bm, edges=edges)
454 for bm_face in bm.faces:
455 bm_face.hide = False
457 bm.to_mesh(mesh)
458 bm.free()