Revert changes to 'object_fracture_cell'
[blender-addons.git] / object_fracture_cell / __init__.py
blobe1fcb41f838a6545407e1c5ce126bd1bf406df04
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(context, 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 collection = context.collection
71 if level != 0:
72 kw_copy["source_limit"] = recursion_source_limit
74 from . import fracture_cell_setup
76 # not essential but selection is visual distraction.
77 obj.select_set(False)
79 if kw_copy["use_debug_redraw"]:
80 obj_display_type_prev = obj.display_type
81 obj.display_type = 'WIRE'
83 objects = fracture_cell_setup.cell_fracture_objects(context, obj, **kw_copy)
84 objects = fracture_cell_setup.cell_fracture_boolean(context, obj, objects,
85 use_island_split=use_island_split,
86 use_interior_hide=(use_interior_vgroup or use_sharp_edges),
87 use_debug_bool=use_debug_bool,
88 use_debug_redraw=kw_copy["use_debug_redraw"],
89 level=level,
92 # must apply after boolean.
93 if use_recenter:
94 bpy.ops.object.origin_set({"selected_editable_objects": objects},
95 type='ORIGIN_GEOMETRY', center='MEDIAN')
97 if level == 0:
98 for level_sub in range(1, recursion + 1):
100 objects_recurse_input = [(i, o) for i, o in enumerate(objects)]
102 if recursion_chance != 1.0:
103 from mathutils import Vector
104 if recursion_chance_select == 'RANDOM':
105 random.shuffle(objects_recurse_input)
106 elif recursion_chance_select in {'SIZE_MIN', 'SIZE_MAX'}:
107 objects_recurse_input.sort(key=lambda ob_pair:
108 (Vector(ob_pair[1].bound_box[0]) -
109 Vector(ob_pair[1].bound_box[6])).length_squared)
110 if recursion_chance_select == 'SIZE_MAX':
111 objects_recurse_input.reverse()
112 elif recursion_chance_select in {'CURSOR_MIN', 'CURSOR_MAX'}:
113 c = scene.cursor.location.copy()
114 objects_recurse_input.sort(key=lambda ob_pair:
115 (ob_pair[1].location - c).length_squared)
116 if recursion_chance_select == 'CURSOR_MAX':
117 objects_recurse_input.reverse()
119 objects_recurse_input[int(recursion_chance * len(objects_recurse_input)):] = []
120 objects_recurse_input.sort()
122 # reverse index values so we can remove from original list.
123 objects_recurse_input.reverse()
125 objects_recursive = []
126 for i, obj_cell in objects_recurse_input:
127 assert(objects[i] is obj_cell)
128 objects_recursive += main_object(context, obj_cell, level_sub, **kw)
129 if use_remove_original:
130 collection.objects.unlink(obj_cell)
131 del objects[i]
132 if recursion_clamp and len(objects) + len(objects_recursive) >= recursion_clamp:
133 break
134 objects.extend(objects_recursive)
136 if recursion_clamp and len(objects) > recursion_clamp:
137 break
139 #--------------
140 # Level Options
141 if level == 0:
142 # import pdb; pdb.set_trace()
143 if use_interior_vgroup or use_sharp_edges:
144 fracture_cell_setup.cell_fracture_interior_handle(objects,
145 use_interior_vgroup=use_interior_vgroup,
146 use_sharp_edges=use_sharp_edges,
147 use_sharp_edges_apply=use_sharp_edges_apply,
150 #--------------
151 # Scene Options
153 # layer
154 layers_new = None
155 if use_layer_index != 0:
156 layers_new = [False] * 20
157 layers_new[use_layer_index - 1] = True
158 elif use_layer_next:
159 layers_new = [False] * 20
160 layers_new[(obj.layers[:].index(True) + 1) % 20] = True
162 if layers_new is not None:
163 for obj_cell in objects:
164 obj_cell.layers = layers_new
166 # group
167 if group_name:
168 group = bpy.data.collections.get(group_name)
169 if group is None:
170 group = bpy.data.collections.new(group_name)
171 group_objects = group.objects[:]
172 for obj_cell in objects:
173 if obj_cell not in group_objects:
174 group.objects.link(obj_cell)
176 if kw_copy["use_debug_redraw"]:
177 obj.display_type = obj_display_type_prev
179 # testing only!
180 # obj.hide = True
181 return objects
184 def main(context, **kw):
185 import time
186 t = time.time()
187 objects_context = context.selected_editable_objects
189 kw_copy = kw.copy()
191 # mass
192 mass_mode = kw_copy.pop("mass_mode")
193 mass = kw_copy.pop("mass")
195 objects = []
196 for obj in objects_context:
197 if obj.type == 'MESH':
198 objects += main_object(context, obj, 0, **kw_copy)
200 bpy.ops.object.select_all(action='DESELECT')
201 for obj_cell in objects:
202 obj_cell.select_set(True)
204 if mass_mode == 'UNIFORM':
205 for obj_cell in objects:
206 obj_cell.game.mass = mass
207 elif mass_mode == 'VOLUME':
208 from mathutils import Vector
209 def _get_volume(obj_cell):
210 def _getObjectBBMinMax():
211 min_co = Vector((1000000.0, 1000000.0, 1000000.0))
212 max_co = -min_co
213 matrix = obj_cell.matrix_world
214 for i in range(0, 8):
215 bb_vec = obj_cell.matrix_world * Vector(obj_cell.bound_box[i])
216 min_co[0] = min(bb_vec[0], min_co[0])
217 min_co[1] = min(bb_vec[1], min_co[1])
218 min_co[2] = min(bb_vec[2], min_co[2])
219 max_co[0] = max(bb_vec[0], max_co[0])
220 max_co[1] = max(bb_vec[1], max_co[1])
221 max_co[2] = max(bb_vec[2], max_co[2])
222 return (min_co, max_co)
224 def _getObjectVolume():
225 min_co, max_co = _getObjectBBMinMax()
226 x = max_co[0] - min_co[0]
227 y = max_co[1] - min_co[1]
228 z = max_co[2] - min_co[2]
229 volume = x * y * z
230 return volume
232 return _getObjectVolume()
235 obj_volume_ls = [_get_volume(obj_cell) for obj_cell in objects]
236 obj_volume_tot = sum(obj_volume_ls)
237 if obj_volume_tot > 0.0:
238 mass_fac = mass / obj_volume_tot
239 for i, obj_cell in enumerate(objects):
240 obj_cell.game.mass = obj_volume_ls[i] * mass_fac
241 else:
242 assert(0)
244 print("Done! %d objects in %.4f sec" % (len(objects), time.time() - t))
247 class FractureCell(Operator):
248 bl_idname = "object.add_fracture_cell_objects"
249 bl_label = "Cell fracture selected mesh objects"
250 bl_options = {'PRESET'}
252 # -------------------------------------------------------------------------
253 # Source Options
254 source: EnumProperty(
255 name="Source",
256 items=(('VERT_OWN', "Own Verts", "Use own vertices"),
257 ('VERT_CHILD', "Child Verts", "Use child object vertices"),
258 ('PARTICLE_OWN', "Own Particles", ("All particle systems of the "
259 "source object")),
260 ('PARTICLE_CHILD', "Child Particles", ("All particle systems of the "
261 "child objects")),
262 ('PENCIL', "Grease Pencil", "This object's grease pencil"),
264 options={'ENUM_FLAG'},
265 default={'PARTICLE_OWN'},
268 source_limit: IntProperty(
269 name="Source Limit",
270 description="Limit the number of input points, 0 for unlimited",
271 min=0, max=5000,
272 default=100,
275 source_noise: FloatProperty(
276 name="Noise",
277 description="Randomize point distribution",
278 min=0.0, max=1.0,
279 default=0.0,
282 cell_scale: FloatVectorProperty(
283 name="Scale",
284 description="Scale Cell Shape",
285 size=3,
286 min=0.0, max=1.0,
287 default=(1.0, 1.0, 1.0),
290 # -------------------------------------------------------------------------
291 # Recursion
293 recursion: IntProperty(
294 name="Recursion",
295 description="Break shards recursively",
296 min=0, max=5000,
297 default=0,
300 recursion_source_limit: IntProperty(
301 name="Source Limit",
302 description="Limit the number of input points, 0 for unlimited (applies to recursion only)",
303 min=0, max=5000,
304 default=8,
307 recursion_clamp: IntProperty(
308 name="Clamp Recursion",
309 description="Finish recursion when this number of objects is reached (prevents recursing for extended periods of time), zero disables",
310 min=0, max=10000,
311 default=250,
314 recursion_chance: FloatProperty(
315 name="Random Factor",
316 description="Likelihood of recursion",
317 min=0.0, max=1.0,
318 default=0.25,
321 recursion_chance_select: EnumProperty(
322 name="Recurse Over",
323 items=(('RANDOM', "Random", ""),
324 ('SIZE_MIN', "Small", "Recursively subdivide smaller objects"),
325 ('SIZE_MAX', "Big", "Recursively subdivide bigger objects"),
326 ('CURSOR_MIN', "Cursor Close", "Recursively subdivide objects closer to the cursor"),
327 ('CURSOR_MAX', "Cursor Far", "Recursively subdivide objects farther from the cursor"),
329 default='SIZE_MIN',
332 # -------------------------------------------------------------------------
333 # Mesh Data Options
335 use_smooth_faces: BoolProperty(
336 name="Smooth Faces",
337 default=False,
340 use_sharp_edges: BoolProperty(
341 name="Sharp Edges",
342 description="Set sharp edges when disabled",
343 default=True,
346 use_sharp_edges_apply: BoolProperty(
347 name="Apply Split Edge",
348 description="Split sharp hard edges",
349 default=True,
352 use_data_match: BoolProperty(
353 name="Match Data",
354 description="Match original mesh materials and data layers",
355 default=True,
358 use_island_split: BoolProperty(
359 name="Split Islands",
360 description="Split disconnected meshes",
361 default=True,
364 margin: FloatProperty(
365 name="Margin",
366 description="Gaps for the fracture (gives more stable physics)",
367 min=0.0, max=1.0,
368 default=0.001,
371 material_index: IntProperty(
372 name="Material",
373 description="Material index for interior faces",
374 default=0,
377 use_interior_vgroup: BoolProperty(
378 name="Interior VGroup",
379 description="Create a vertex group for interior verts",
380 default=False,
383 # -------------------------------------------------------------------------
384 # Physics Options
386 mass_mode: EnumProperty(
387 name="Mass Mode",
388 items=(('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
389 ('UNIFORM', "Uniform", "All objects get the specified mass"),
391 default='VOLUME',
394 mass: FloatProperty(
395 name="Mass",
396 description="Mass to give created objects",
397 min=0.001, max=1000.0,
398 default=1.0,
402 # -------------------------------------------------------------------------
403 # Object Options
405 use_recenter: BoolProperty(
406 name="Recenter",
407 description="Recalculate the center points after splitting",
408 default=True,
411 use_remove_original: BoolProperty(
412 name="Remove Original",
413 description="Removes the parents used to create the shatter",
414 default=True,
417 # -------------------------------------------------------------------------
418 # Scene Options
420 # .. different from object options in that this controls how the objects
421 # are setup in the scene.
423 use_layer_index: IntProperty(
424 name="Layer Index",
425 description="Layer to add the objects into or 0 for existing",
426 default=0,
427 min=0, max=20,
430 use_layer_next: BoolProperty(
431 name="Next Layer",
432 description="At the object into the next layer (layer index overrides)",
433 default=True,
436 group_name: StringProperty(
437 name="Group",
438 description="Create objects int a group "
439 "(use existing or create new)",
442 # -------------------------------------------------------------------------
443 # Debug
444 use_debug_points: BoolProperty(
445 name="Debug Points",
446 description="Create mesh data showing the points used for fracture",
447 default=False,
450 use_debug_redraw: BoolProperty(
451 name="Show Progress Realtime",
452 description="Redraw as fracture is done",
453 default=True,
456 use_debug_bool: BoolProperty(
457 name="Debug Boolean",
458 description="Skip applying the boolean modifier",
459 default=False,
462 def execute(self, context):
463 keywords = self.as_keywords() # ignore=("blah",)
465 main(context, **keywords)
467 return {'FINISHED'}
470 def invoke(self, context, event):
471 print(self.recursion_chance_select)
472 wm = context.window_manager
473 return wm.invoke_props_dialog(self, width=600)
475 def draw(self, context):
476 layout = self.layout
477 box = layout.box()
478 col = box.column()
479 col.label(text="Point Source")
480 rowsub = col.row()
481 rowsub.prop(self, "source")
482 rowsub = col.row()
483 rowsub.prop(self, "source_limit")
484 rowsub.prop(self, "source_noise")
485 rowsub = col.row()
486 rowsub.prop(self, "cell_scale")
488 box = layout.box()
489 col = box.column()
490 col.label(text="Recursive Shatter")
491 rowsub = col.row(align=True)
492 rowsub.prop(self, "recursion")
493 rowsub.prop(self, "recursion_source_limit")
494 rowsub.prop(self, "recursion_clamp")
495 rowsub = col.row()
496 rowsub.prop(self, "recursion_chance")
497 rowsub.prop(self, "recursion_chance_select", expand=True)
499 box = layout.box()
500 col = box.column()
501 col.label(text="Mesh Data")
502 rowsub = col.row()
503 rowsub.prop(self, "use_smooth_faces")
504 rowsub.prop(self, "use_sharp_edges")
505 rowsub.prop(self, "use_sharp_edges_apply")
506 rowsub.prop(self, "use_data_match")
507 rowsub = col.row()
509 # on same row for even layout but infact are not all that related
510 rowsub.prop(self, "material_index")
511 rowsub.prop(self, "use_interior_vgroup")
513 # could be own section, control how we subdiv
514 rowsub.prop(self, "margin")
515 rowsub.prop(self, "use_island_split")
518 box = layout.box()
519 col = box.column()
520 col.label(text="Physics")
521 rowsub = col.row(align=True)
522 rowsub.prop(self, "mass_mode")
523 rowsub.prop(self, "mass")
526 box = layout.box()
527 col = box.column()
528 col.label(text="Object")
529 rowsub = col.row(align=True)
530 rowsub.prop(self, "use_recenter")
533 box = layout.box()
534 col = box.column()
535 col.label(text="Scene")
536 rowsub = col.row(align=True)
537 rowsub.prop(self, "use_layer_index")
538 rowsub.prop(self, "use_layer_next")
539 rowsub.prop(self, "group_name")
541 box = layout.box()
542 col = box.column()
543 col.label(text="Debug")
544 rowsub = col.row(align=True)
545 rowsub.prop(self, "use_debug_redraw")
546 rowsub.prop(self, "use_debug_points")
547 rowsub.prop(self, "use_debug_bool")
550 def menu_func(self, context):
551 layout = self.layout
552 layout.label(text="Cell Fracture:")
553 layout.operator("object.add_fracture_cell_objects",
554 text="Cell Fracture")
557 def register():
558 bpy.utils.register_class(FractureCell)
559 bpy.types.VIEW3D_PT_tools_object.append(menu_func)
562 def unregister():
563 bpy.utils.unregister_class(FractureCell)
564 bpy.types.VIEW3D_PT_tools_object.remove(menu_func)
567 if __name__ == "__main__":
568 register()