Import_3ds: Improved distance cue node setup
[blender-addons.git] / object_fracture_cell / fracture_cell_setup.py
blob55733f90c8ece5bf4872ef2c1fcc2b6d07eea495
1 # SPDX-FileCopyrightText: 2012 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 import bpy
6 import bmesh
8 if bpy.app.background:
9 def _redraw_yasiamevil():
10 pass
11 else:
12 def _redraw_yasiamevil():
13 _redraw_yasiamevil.opr(**_redraw_yasiamevil.arg)
14 _redraw_yasiamevil.opr = bpy.ops.wm.redraw_timer
15 _redraw_yasiamevil.arg = dict(type='DRAW_WIN_SWAP', iterations=1)
18 def _points_from_object(depsgraph, scene, obj, source):
20 _source_all = {
21 'PARTICLE_OWN', 'PARTICLE_CHILD',
22 'PENCIL',
23 'VERT_OWN', 'VERT_CHILD',
26 # print(source - _source_all)
27 # print(source)
28 assert(len(source | _source_all) == len(_source_all))
29 assert(len(source))
31 points = []
33 def edge_center(mesh, edge):
34 v1, v2 = edge.vertices
35 return (mesh.vertices[v1].co + mesh.vertices[v2].co) / 2.0
37 def poly_center(mesh, poly):
38 from mathutils import Vector
39 co = Vector()
40 tot = 0
41 for i in poly.loop_indices:
42 co += mesh.vertices[mesh.loops[i].vertex_index].co
43 tot += 1
44 return co / tot
46 def points_from_verts(obj):
47 """Takes points from _any_ object with geometry"""
48 if obj.type == 'MESH':
49 mesh = obj.data
50 matrix = obj.matrix_world.copy()
51 points.extend([matrix @ v.co for v in mesh.vertices])
52 else:
53 ob_eval = ob.evaluated_get(depsgraph)
54 try:
55 mesh = ob_eval.to_mesh()
56 except:
57 mesh = None
59 if mesh is not None:
60 matrix = obj.matrix_world.copy()
61 points.extend([matrix @ v.co for v in mesh.vertices])
62 ob_eval.to_mesh_clear()
64 def points_from_particles(obj):
65 obj_eval = obj.evaluated_get(depsgraph)
66 points.extend([p.location.copy()
67 for psys in obj_eval.particle_systems
68 for p in psys.particles])
70 # geom own
71 if 'VERT_OWN' in source:
72 points_from_verts(obj)
74 # geom children
75 if 'VERT_CHILD' in source:
76 for obj_child in obj.children:
77 points_from_verts(obj_child)
79 # geom particles
80 if 'PARTICLE_OWN' in source:
81 points_from_particles(obj)
83 if 'PARTICLE_CHILD' in source:
84 for obj_child in obj.children:
85 points_from_particles(obj_child)
87 # grease pencil
88 def get_points(stroke):
89 return [point.co.copy() for point in stroke.points]
91 def get_splines(gp):
92 if gp.layers.active:
93 frame = gp.layers.active.active_frame
94 return [get_points(stroke) for stroke in frame.strokes]
95 else:
96 return []
98 if 'PENCIL' in source:
99 # Used to be from object in 2.7x, now from scene.
100 gp = scene.grease_pencil
101 if gp:
102 points.extend([p for spline in get_splines(gp) for p in spline])
104 print("Found %d points" % len(points))
106 return points
109 def cell_fracture_objects(
110 context, collection, obj,
111 source={'PARTICLE_OWN'},
112 source_limit=0,
113 source_noise=0.0,
114 clean=True,
115 # operator options
116 use_smooth_faces=False,
117 use_data_match=False,
118 use_debug_points=False,
119 margin=0.0,
120 material_index=0,
121 use_debug_redraw=False,
122 cell_scale=(1.0, 1.0, 1.0),
124 from . import fracture_cell_calc
125 depsgraph = context.evaluated_depsgraph_get()
126 scene = context.scene
127 view_layer = context.view_layer
129 # -------------------------------------------------------------------------
130 # GET POINTS
132 points = _points_from_object(depsgraph, scene, obj, source)
134 if not points:
135 # print using fallback
136 points = _points_from_object(depsgraph, scene, obj, {'VERT_OWN'})
138 if not points:
139 print("no points found")
140 return []
142 # apply optional clamp
143 if source_limit != 0 and source_limit < len(points):
144 import random
145 random.shuffle(points)
146 points[source_limit:] = []
148 # sadly we can't be sure there are no doubles
149 from mathutils import Vector
150 to_tuple = Vector.to_tuple
151 points = list({to_tuple(p, 4): p for p in points}.values())
152 del to_tuple
153 del Vector
155 # end remove doubles
156 # ------------------
158 if source_noise > 0.0:
159 from random import random
160 # boundbox approx of overall scale
161 from mathutils import Vector
162 matrix = obj.matrix_world.copy()
163 bb_world = [matrix @ Vector(v) for v in obj.bound_box]
164 scalar = source_noise * ((bb_world[0] - bb_world[6]).length / 2.0)
166 from mathutils.noise import random_unit_vector
168 points[:] = [p + (random_unit_vector() * (scalar * random())) for p in points]
170 if use_debug_points:
171 bm = bmesh.new()
172 for p in points:
173 bm.verts.new(p)
174 mesh_tmp = bpy.data.meshes.new(name="DebugPoints")
175 bm.to_mesh(mesh_tmp)
176 bm.free()
177 obj_tmp = bpy.data.objects.new(name=mesh_tmp.name, object_data=mesh_tmp)
178 collection.objects.link(obj_tmp)
179 del obj_tmp, mesh_tmp
181 mesh = obj.data
182 matrix = obj.matrix_world.copy()
183 verts = [matrix @ v.co for v in mesh.vertices]
185 cells = fracture_cell_calc.points_as_bmesh_cells(
186 verts,
187 points,
188 cell_scale,
189 margin_cell=margin,
192 # some hacks here :S
193 cell_name = obj.name + "_cell"
195 objects = []
197 for center_point, cell_points in cells:
199 # ---------------------------------------------------------------------
200 # BMESH
202 # create the convex hulls
203 bm = bmesh.new()
205 # WORKAROUND FOR CONVEX HULL BUG/LIMIT
206 # XXX small noise
207 import random
209 def R():
210 return (random.random() - 0.5) * 0.001
211 # XXX small noise
213 for i, co in enumerate(cell_points):
215 # XXX small noise
216 co.x += R()
217 co.y += R()
218 co.z += R()
219 # XXX small noise
221 bm_vert = bm.verts.new(co)
223 import mathutils
224 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
225 try:
226 bmesh.ops.convex_hull(bm, input=bm.verts)
227 except RuntimeError:
228 import traceback
229 traceback.print_exc()
231 if clean:
232 bm.normal_update()
233 try:
234 bmesh.ops.dissolve_limit(bm, verts=bm.verts, angle_limit=0.001)
235 except RuntimeError:
236 import traceback
237 traceback.print_exc()
238 # Smooth faces will remain only inner faces, after applying boolean modifier.
239 if use_smooth_faces:
240 for bm_face in bm.faces:
241 bm_face.smooth = True
243 if material_index != 0:
244 for bm_face in bm.faces:
245 bm_face.material_index = material_index
247 # ---------------------------------------------------------------------
248 # MESH
249 mesh_dst = bpy.data.meshes.new(name=cell_name)
251 bm.to_mesh(mesh_dst)
252 bm.free()
253 del bm
255 if use_data_match:
256 # match materials and data layers so boolean displays them
257 # currently only materials + data layers, could do others...
258 mesh_src = obj.data
259 for mat in mesh_src.materials:
260 mesh_dst.materials.append(mat)
261 for lay_attr in ("vertex_colors", "uv_layers"):
262 lay_src = getattr(mesh_src, lay_attr)
263 lay_dst = getattr(mesh_dst, lay_attr)
264 for key in lay_src.keys():
265 lay_dst.new(name=key)
267 # ---------------------------------------------------------------------
268 # OBJECT
270 obj_cell = bpy.data.objects.new(name=cell_name, object_data=mesh_dst)
271 collection.objects.link(obj_cell)
272 # scene.objects.active = obj_cell
273 obj_cell.location = center_point
275 objects.append(obj_cell)
277 # support for object materials
278 if use_data_match:
279 for i in range(len(mesh_dst.materials)):
280 slot_src = obj.material_slots[i]
281 slot_dst = obj_cell.material_slots[i]
283 slot_dst.link = slot_src.link
284 slot_dst.material = slot_src.material
286 if use_debug_redraw:
287 view_layer.update()
288 _redraw_yasiamevil()
290 view_layer.update()
292 return objects
295 def cell_fracture_boolean(
296 context, collection, obj, objects,
297 use_debug_bool=False,
298 clean=True,
299 use_island_split=False,
300 use_interior_hide=False,
301 use_debug_redraw=False,
302 level=0,
303 remove_doubles=True
306 objects_boolean = []
307 scene = context.scene
308 view_layer = context.view_layer
310 if use_interior_hide and level == 0:
311 # only set for level 0
312 obj.data.polygons.foreach_set("hide", [False] * len(obj.data.polygons))
314 for obj_cell in objects:
315 mod = obj_cell.modifiers.new(name="Boolean", type='BOOLEAN')
316 mod.object = obj
317 mod.operation = 'INTERSECT'
319 if not use_debug_bool:
321 if use_interior_hide:
322 obj_cell.data.polygons.foreach_set("hide", [True] * len(obj_cell.data.polygons))
324 # Calculates all booleans at once (faster).
325 depsgraph = context.evaluated_depsgraph_get()
327 for obj_cell in objects:
329 if not use_debug_bool:
331 obj_cell_eval = obj_cell.evaluated_get(depsgraph)
332 mesh_new = bpy.data.meshes.new_from_object(obj_cell_eval)
333 mesh_old = obj_cell.data
334 obj_cell.data = mesh_new
335 obj_cell.modifiers.remove(obj_cell.modifiers[-1])
337 # remove if not valid
338 if not mesh_old.users:
339 bpy.data.meshes.remove(mesh_old)
340 if not mesh_new.vertices:
341 collection.objects.unlink(obj_cell)
342 if not obj_cell.users:
343 bpy.data.objects.remove(obj_cell)
344 obj_cell = None
345 if not mesh_new.users:
346 bpy.data.meshes.remove(mesh_new)
347 mesh_new = None
349 # avoid unneeded bmesh re-conversion
350 if mesh_new is not None:
351 bm = None
353 if clean:
354 if bm is None: # ok this will always be true for now...
355 bm = bmesh.new()
356 bm.from_mesh(mesh_new)
357 bm.normal_update()
358 try:
359 bmesh.ops.dissolve_limit(bm, verts=bm.verts, edges=bm.edges, angle_limit=0.001)
360 except RuntimeError:
361 import traceback
362 traceback.print_exc()
364 if remove_doubles:
365 if bm is None:
366 bm = bmesh.new()
367 bm.from_mesh(mesh_new)
368 bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
370 if bm is not None:
371 bm.to_mesh(mesh_new)
372 bm.free()
374 del mesh_new
375 del mesh_old
377 if obj_cell is not None:
378 objects_boolean.append(obj_cell)
380 if use_debug_redraw:
381 _redraw_yasiamevil()
383 if (not use_debug_bool) and use_island_split:
384 # this is ugly and Im not proud of this - campbell
385 for ob in view_layer.objects:
386 ob.select_set(False)
387 for obj_cell in objects_boolean:
388 obj_cell.select_set(True)
390 bpy.ops.mesh.separate(type='LOOSE')
392 objects_boolean[:] = [obj_cell for obj_cell in view_layer.objects if obj_cell.select_get()]
394 context.view_layer.update()
396 return objects_boolean
399 def cell_fracture_interior_handle(
400 objects,
401 use_interior_vgroup=False,
402 use_sharp_edges=False,
403 use_sharp_edges_apply=False,
405 """Run after doing _all_ booleans"""
407 assert(use_interior_vgroup or use_sharp_edges or use_sharp_edges_apply)
409 for obj_cell in objects:
410 mesh = obj_cell.data
411 bm = bmesh.new()
412 bm.from_mesh(mesh)
414 if use_interior_vgroup:
415 for bm_vert in bm.verts:
416 bm_vert.tag = True
417 for bm_face in bm.faces:
418 if not bm_face.hide:
419 for bm_vert in bm_face.verts:
420 bm_vert.tag = False
422 # now add all vgroups
423 defvert_lay = bm.verts.layers.deform.verify()
424 for bm_vert in bm.verts:
425 if bm_vert.tag:
426 bm_vert[defvert_lay][0] = 1.0
428 # add a vgroup
429 obj_cell.vertex_groups.new(name="Interior")
431 if use_sharp_edges:
432 for bm_edge in bm.edges:
433 if len({bm_face.hide for bm_face in bm_edge.link_faces}) == 2:
434 bm_edge.smooth = False
436 if use_sharp_edges_apply:
437 edges = [edge for edge in bm.edges if edge.smooth is False]
438 if edges:
439 bm.normal_update()
440 bmesh.ops.split_edges(bm, edges=edges)
442 for bm_face in bm.faces:
443 bm_face.hide = False
445 bm.to_mesh(mesh)
446 bm.free()