Merge branch 'master' into blender2.8
[blender-addons.git] / object_fracture_cell / __init__.py
blobf845ccf12fd186b32f4cb823109b283e9e3f1116
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 bl_info = {
20 "name": "Cell Fracture",
21 "author": "ideasman42, phymec, Sergey Sharybin",
22 "version": (0, 1),
23 "blender": (2, 70, 0),
24 "location": "Edit panel of Tools tab, in Object mode, 3D View tools",
25 "description": "Fractured Object, Bomb, Projectile, Recorder",
26 "warning": "",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Object/CellFracture",
29 "category": "Object"}
32 #if "bpy" in locals():
33 # import importlib
34 # importlib.reload(fracture_cell_setup)
36 import bpy
37 from bpy.props import (
38 StringProperty,
39 BoolProperty,
40 IntProperty,
41 FloatProperty,
42 FloatVectorProperty,
43 EnumProperty,
46 from bpy.types import Operator
48 def main_object(scene, obj, level, **kw):
49 import random
51 # pull out some args
52 kw_copy = kw.copy()
53 use_recenter = kw_copy.pop("use_recenter")
54 use_remove_original = kw_copy.pop("use_remove_original")
55 recursion = kw_copy.pop("recursion")
56 recursion_source_limit = kw_copy.pop("recursion_source_limit")
57 recursion_clamp = kw_copy.pop("recursion_clamp")
58 recursion_chance = kw_copy.pop("recursion_chance")
59 recursion_chance_select = kw_copy.pop("recursion_chance_select")
60 use_layer_next = kw_copy.pop("use_layer_next")
61 use_layer_index = kw_copy.pop("use_layer_index")
62 group_name = kw_copy.pop("group_name")
63 use_island_split = kw_copy.pop("use_island_split")
64 use_debug_bool = kw_copy.pop("use_debug_bool")
65 use_interior_vgroup = kw_copy.pop("use_interior_vgroup")
66 use_sharp_edges = kw_copy.pop("use_sharp_edges")
67 use_sharp_edges_apply = kw_copy.pop("use_sharp_edges_apply")
69 if level != 0:
70 kw_copy["source_limit"] = recursion_source_limit
72 from . import fracture_cell_setup
74 # not essential but selection is visual distraction.
75 obj.select = False
77 if kw_copy["use_debug_redraw"]:
78 obj_display_type_prev = obj.display_type
79 obj.display_type = 'WIRE'
81 objects = fracture_cell_setup.cell_fracture_objects(scene, obj, **kw_copy)
82 objects = fracture_cell_setup.cell_fracture_boolean(scene, obj, objects,
83 use_island_split=use_island_split,
84 use_interior_hide=(use_interior_vgroup or use_sharp_edges),
85 use_debug_bool=use_debug_bool,
86 use_debug_redraw=kw_copy["use_debug_redraw"],
87 level=level,
90 # must apply after boolean.
91 if use_recenter:
92 bpy.ops.object.origin_set({"selected_editable_objects": objects},
93 type='ORIGIN_GEOMETRY', center='MEDIAN')
95 if level == 0:
96 for level_sub in range(1, recursion + 1):
98 objects_recurse_input = [(i, o) for i, o in enumerate(objects)]
100 if recursion_chance != 1.0:
101 from mathutils import Vector
102 if recursion_chance_select == 'RANDOM':
103 random.shuffle(objects_recurse_input)
104 elif recursion_chance_select in {'SIZE_MIN', 'SIZE_MAX'}:
105 objects_recurse_input.sort(key=lambda ob_pair:
106 (Vector(ob_pair[1].bound_box[0]) -
107 Vector(ob_pair[1].bound_box[6])).length_squared)
108 if recursion_chance_select == 'SIZE_MAX':
109 objects_recurse_input.reverse()
110 elif recursion_chance_select in {'CURSOR_MIN', 'CURSOR_MAX'}:
111 c = scene.cursor_location.copy()
112 objects_recurse_input.sort(key=lambda ob_pair:
113 (ob_pair[1].location - c).length_squared)
114 if recursion_chance_select == 'CURSOR_MAX':
115 objects_recurse_input.reverse()
117 objects_recurse_input[int(recursion_chance * len(objects_recurse_input)):] = []
118 objects_recurse_input.sort()
120 # reverse index values so we can remove from original list.
121 objects_recurse_input.reverse()
123 objects_recursive = []
124 for i, obj_cell in objects_recurse_input:
125 assert(objects[i] is obj_cell)
126 objects_recursive += main_object(scene, obj_cell, level_sub, **kw)
127 if use_remove_original:
128 scene.objects.unlink(obj_cell)
129 del objects[i]
130 if recursion_clamp and len(objects) + len(objects_recursive) >= recursion_clamp:
131 break
132 objects.extend(objects_recursive)
134 if recursion_clamp and len(objects) > recursion_clamp:
135 break
137 #--------------
138 # Level Options
139 if level == 0:
140 # import pdb; pdb.set_trace()
141 if use_interior_vgroup or use_sharp_edges:
142 fracture_cell_setup.cell_fracture_interior_handle(objects,
143 use_interior_vgroup=use_interior_vgroup,
144 use_sharp_edges=use_sharp_edges,
145 use_sharp_edges_apply=use_sharp_edges_apply,
148 #--------------
149 # Scene Options
151 # layer
152 layers_new = None
153 if use_layer_index != 0:
154 layers_new = [False] * 20
155 layers_new[use_layer_index - 1] = True
156 elif use_layer_next:
157 layers_new = [False] * 20
158 layers_new[(obj.layers[:].index(True) + 1) % 20] = True
160 if layers_new is not None:
161 for obj_cell in objects:
162 obj_cell.layers = layers_new
164 # group
165 if group_name:
166 group = bpy.data.collections.get(group_name)
167 if group is None:
168 group = bpy.data.collections.new(group_name)
169 group_objects = group.objects[:]
170 for obj_cell in objects:
171 if obj_cell not in group_objects:
172 group.objects.link(obj_cell)
174 if kw_copy["use_debug_redraw"]:
175 obj.display_type = obj_display_type_prev
177 # testing only!
178 # obj.hide = True
179 return objects
182 def main(context, **kw):
183 import time
184 t = time.time()
185 scene = context.scene
186 objects_context = context.selected_editable_objects
188 kw_copy = kw.copy()
190 # mass
191 mass_mode = kw_copy.pop("mass_mode")
192 mass = kw_copy.pop("mass")
194 objects = []
195 for obj in objects_context:
196 if obj.type == 'MESH':
197 objects += main_object(scene, obj, 0, **kw_copy)
199 bpy.ops.object.select_all(action='DESELECT')
200 for obj_cell in objects:
201 obj_cell.select = True
203 if mass_mode == 'UNIFORM':
204 for obj_cell in objects:
205 obj_cell.game.mass = mass
206 elif mass_mode == 'VOLUME':
207 from mathutils import Vector
208 def _get_volume(obj_cell):
209 def _getObjectBBMinMax():
210 min_co = Vector((1000000.0, 1000000.0, 1000000.0))
211 max_co = -min_co
212 matrix = obj_cell.matrix_world
213 for i in range(0, 8):
214 bb_vec = obj_cell.matrix_world * Vector(obj_cell.bound_box[i])
215 min_co[0] = min(bb_vec[0], min_co[0])
216 min_co[1] = min(bb_vec[1], min_co[1])
217 min_co[2] = min(bb_vec[2], min_co[2])
218 max_co[0] = max(bb_vec[0], max_co[0])
219 max_co[1] = max(bb_vec[1], max_co[1])
220 max_co[2] = max(bb_vec[2], max_co[2])
221 return (min_co, max_co)
223 def _getObjectVolume():
224 min_co, max_co = _getObjectBBMinMax()
225 x = max_co[0] - min_co[0]
226 y = max_co[1] - min_co[1]
227 z = max_co[2] - min_co[2]
228 volume = x * y * z
229 return volume
231 return _getObjectVolume()
234 obj_volume_ls = [_get_volume(obj_cell) for obj_cell in objects]
235 obj_volume_tot = sum(obj_volume_ls)
236 if obj_volume_tot > 0.0:
237 mass_fac = mass / obj_volume_tot
238 for i, obj_cell in enumerate(objects):
239 obj_cell.game.mass = obj_volume_ls[i] * mass_fac
240 else:
241 assert(0)
243 print("Done! %d objects in %.4f sec" % (len(objects), time.time() - t))
246 class FractureCell(Operator):
247 bl_idname = "object.add_fracture_cell_objects"
248 bl_label = "Cell fracture selected mesh objects"
249 bl_options = {'PRESET'}
251 # -------------------------------------------------------------------------
252 # Source Options
253 source = EnumProperty(
254 name="Source",
255 items=(('VERT_OWN', "Own Verts", "Use own vertices"),
256 ('VERT_CHILD', "Child Verts", "Use child object vertices"),
257 ('PARTICLE_OWN', "Own Particles", ("All particle systems of the "
258 "source object")),
259 ('PARTICLE_CHILD', "Child Particles", ("All particle systems of the "
260 "child objects")),
261 ('PENCIL', "Grease Pencil", "This object's grease pencil"),
263 options={'ENUM_FLAG'},
264 default={'PARTICLE_OWN'},
267 source_limit = IntProperty(
268 name="Source Limit",
269 description="Limit the number of input points, 0 for unlimited",
270 min=0, max=5000,
271 default=100,
274 source_noise = FloatProperty(
275 name="Noise",
276 description="Randomize point distribution",
277 min=0.0, max=1.0,
278 default=0.0,
281 cell_scale = FloatVectorProperty(
282 name="Scale",
283 description="Scale Cell Shape",
284 size=3,
285 min=0.0, max=1.0,
286 default=(1.0, 1.0, 1.0),
289 # -------------------------------------------------------------------------
290 # Recursion
292 recursion = IntProperty(
293 name="Recursion",
294 description="Break shards recursively",
295 min=0, max=5000,
296 default=0,
299 recursion_source_limit = IntProperty(
300 name="Source Limit",
301 description="Limit the number of input points, 0 for unlimited (applies to recursion only)",
302 min=0, max=5000,
303 default=8,
306 recursion_clamp = IntProperty(
307 name="Clamp Recursion",
308 description="Finish recursion when this number of objects is reached (prevents recursing for extended periods of time), zero disables",
309 min=0, max=10000,
310 default=250,
313 recursion_chance = FloatProperty(
314 name="Random Factor",
315 description="Likelihood of recursion",
316 min=0.0, max=1.0,
317 default=0.25,
320 recursion_chance_select = EnumProperty(
321 name="Recurse Over",
322 items=(('RANDOM', "Random", ""),
323 ('SIZE_MIN', "Small", "Recursively subdivide smaller objects"),
324 ('SIZE_MAX', "Big", "Recursively subdivide bigger objects"),
325 ('CURSOR_MIN', "Cursor Close", "Recursively subdivide objects closer to the cursor"),
326 ('CURSOR_MAX', "Cursor Far", "Recursively subdivide objects farther from the cursor"),
328 default='SIZE_MIN',
331 # -------------------------------------------------------------------------
332 # Mesh Data Options
334 use_smooth_faces = BoolProperty(
335 name="Smooth Faces",
336 default=False,
339 use_sharp_edges = BoolProperty(
340 name="Sharp Edges",
341 description="Set sharp edges when disabled",
342 default=True,
345 use_sharp_edges_apply = BoolProperty(
346 name="Apply Split Edge",
347 description="Split sharp hard edges",
348 default=True,
351 use_data_match = BoolProperty(
352 name="Match Data",
353 description="Match original mesh materials and data layers",
354 default=True,
357 use_island_split = BoolProperty(
358 name="Split Islands",
359 description="Split disconnected meshes",
360 default=True,
363 margin = FloatProperty(
364 name="Margin",
365 description="Gaps for the fracture (gives more stable physics)",
366 min=0.0, max=1.0,
367 default=0.001,
370 material_index = IntProperty(
371 name="Material",
372 description="Material index for interior faces",
373 default=0,
376 use_interior_vgroup = BoolProperty(
377 name="Interior VGroup",
378 description="Create a vertex group for interior verts",
379 default=False,
382 # -------------------------------------------------------------------------
383 # Physics Options
385 mass_mode = EnumProperty(
386 name="Mass Mode",
387 items=(('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
388 ('UNIFORM', "Uniform", "All objects get the specified mass"),
390 default='VOLUME',
393 mass = FloatProperty(
394 name="Mass",
395 description="Mass to give created objects",
396 min=0.001, max=1000.0,
397 default=1.0,
401 # -------------------------------------------------------------------------
402 # Object Options
404 use_recenter = BoolProperty(
405 name="Recenter",
406 description="Recalculate the center points after splitting",
407 default=True,
410 use_remove_original = BoolProperty(
411 name="Remove Original",
412 description="Removes the parents used to create the shatter",
413 default=True,
416 # -------------------------------------------------------------------------
417 # Scene Options
419 # .. different from object options in that this controls how the objects
420 # are setup in the scene.
422 use_layer_index = IntProperty(
423 name="Layer Index",
424 description="Layer to add the objects into or 0 for existing",
425 default=0,
426 min=0, max=20,
429 use_layer_next = BoolProperty(
430 name="Next Layer",
431 description="At the object into the next layer (layer index overrides)",
432 default=True,
435 group_name = StringProperty(
436 name="Group",
437 description="Create objects int a group "
438 "(use existing or create new)",
441 # -------------------------------------------------------------------------
442 # Debug
443 use_debug_points = BoolProperty(
444 name="Debug Points",
445 description="Create mesh data showing the points used for fracture",
446 default=False,
449 use_debug_redraw = BoolProperty(
450 name="Show Progress Realtime",
451 description="Redraw as fracture is done",
452 default=True,
455 use_debug_bool = BoolProperty(
456 name="Debug Boolean",
457 description="Skip applying the boolean modifier",
458 default=False,
461 def execute(self, context):
462 keywords = self.as_keywords() # ignore=("blah",)
464 main(context, **keywords)
466 return {'FINISHED'}
469 def invoke(self, context, event):
470 print(self.recursion_chance_select)
471 wm = context.window_manager
472 return wm.invoke_props_dialog(self, width=600)
474 def draw(self, context):
475 layout = self.layout
476 box = layout.box()
477 col = box.column()
478 col.label("Point Source")
479 rowsub = col.row()
480 rowsub.prop(self, "source")
481 rowsub = col.row()
482 rowsub.prop(self, "source_limit")
483 rowsub.prop(self, "source_noise")
484 rowsub = col.row()
485 rowsub.prop(self, "cell_scale")
487 box = layout.box()
488 col = box.column()
489 col.label("Recursive Shatter")
490 rowsub = col.row(align=True)
491 rowsub.prop(self, "recursion")
492 rowsub.prop(self, "recursion_source_limit")
493 rowsub.prop(self, "recursion_clamp")
494 rowsub = col.row()
495 rowsub.prop(self, "recursion_chance")
496 rowsub.prop(self, "recursion_chance_select", expand=True)
498 box = layout.box()
499 col = box.column()
500 col.label("Mesh Data")
501 rowsub = col.row()
502 rowsub.prop(self, "use_smooth_faces")
503 rowsub.prop(self, "use_sharp_edges")
504 rowsub.prop(self, "use_sharp_edges_apply")
505 rowsub.prop(self, "use_data_match")
506 rowsub = col.row()
508 # on same row for even layout but infact are not all that related
509 rowsub.prop(self, "material_index")
510 rowsub.prop(self, "use_interior_vgroup")
512 # could be own section, control how we subdiv
513 rowsub.prop(self, "margin")
514 rowsub.prop(self, "use_island_split")
517 box = layout.box()
518 col = box.column()
519 col.label("Physics")
520 rowsub = col.row(align=True)
521 rowsub.prop(self, "mass_mode")
522 rowsub.prop(self, "mass")
525 box = layout.box()
526 col = box.column()
527 col.label("Object")
528 rowsub = col.row(align=True)
529 rowsub.prop(self, "use_recenter")
532 box = layout.box()
533 col = box.column()
534 col.label("Scene")
535 rowsub = col.row(align=True)
536 rowsub.prop(self, "use_layer_index")
537 rowsub.prop(self, "use_layer_next")
538 rowsub.prop(self, "group_name")
540 box = layout.box()
541 col = box.column()
542 col.label("Debug")
543 rowsub = col.row(align=True)
544 rowsub.prop(self, "use_debug_redraw")
545 rowsub.prop(self, "use_debug_points")
546 rowsub.prop(self, "use_debug_bool")
549 def menu_func(self, context):
550 layout = self.layout
551 layout.label("Cell Fracture:")
552 layout.operator("object.add_fracture_cell_objects",
553 text="Cell Fracture")
556 def register():
557 bpy.utils.register_class(FractureCell)
558 bpy.types.VIEW3D_PT_tools_object.append(menu_func)
561 def unregister():
562 bpy.utils.unregister_class(FractureCell)
563 bpy.types.VIEW3D_PT_tools_object.remove(menu_func)
566 if __name__ == "__main__":
567 register()