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 #####
20 "name": "Cell Fracture",
21 "author": "ideasman42, phymec, Sergey Sharybin",
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",
27 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
28 "Scripts/Object/CellFracture",
32 #if "bpy" in locals():
34 # importlib.reload(fracture_cell_setup)
37 from bpy
.props
import (
46 from bpy
.types
import Operator
48 def main_object(scene
, obj
, level
, **kw
):
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")
70 kw_copy
["source_limit"] = recursion_source_limit
72 from . import fracture_cell_setup
74 # not essential but selection is visual distraction.
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"],
90 # must apply after boolean.
92 bpy
.ops
.object.origin_set({"selected_editable_objects": objects
},
93 type='ORIGIN_GEOMETRY', center
='MEDIAN')
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
)
130 if recursion_clamp
and len(objects
) + len(objects_recursive
) >= recursion_clamp
:
132 objects
.extend(objects_recursive
)
134 if recursion_clamp
and len(objects
) > recursion_clamp
:
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
,
153 if use_layer_index
!= 0:
154 layers_new
= [False] * 20
155 layers_new
[use_layer_index
- 1] = True
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
166 group
= bpy
.data
.collections
.get(group_name
)
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
182 def main(context
, **kw
):
185 scene
= context
.scene
186 objects_context
= context
.selected_editable_objects
191 mass_mode
= kw_copy
.pop("mass_mode")
192 mass
= kw_copy
.pop("mass")
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))
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]
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
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 # -------------------------------------------------------------------------
253 source
= EnumProperty(
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 "
259 ('PARTICLE_CHILD', "Child Particles", ("All particle systems of the "
261 ('PENCIL', "Grease Pencil", "This object's grease pencil"),
263 options
={'ENUM_FLAG'},
264 default
={'PARTICLE_OWN'},
267 source_limit
= IntProperty(
269 description
="Limit the number of input points, 0 for unlimited",
274 source_noise
= FloatProperty(
276 description
="Randomize point distribution",
281 cell_scale
= FloatVectorProperty(
283 description
="Scale Cell Shape",
286 default
=(1.0, 1.0, 1.0),
289 # -------------------------------------------------------------------------
292 recursion
= IntProperty(
294 description
="Break shards recursively",
299 recursion_source_limit
= IntProperty(
301 description
="Limit the number of input points, 0 for unlimited (applies to recursion only)",
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",
313 recursion_chance
= FloatProperty(
314 name
="Random Factor",
315 description
="Likelihood of recursion",
320 recursion_chance_select
= EnumProperty(
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"),
331 # -------------------------------------------------------------------------
334 use_smooth_faces
= BoolProperty(
339 use_sharp_edges
= BoolProperty(
341 description
="Set sharp edges when disabled",
345 use_sharp_edges_apply
= BoolProperty(
346 name
="Apply Split Edge",
347 description
="Split sharp hard edges",
351 use_data_match
= BoolProperty(
353 description
="Match original mesh materials and data layers",
357 use_island_split
= BoolProperty(
358 name
="Split Islands",
359 description
="Split disconnected meshes",
363 margin
= FloatProperty(
365 description
="Gaps for the fracture (gives more stable physics)",
370 material_index
= IntProperty(
372 description
="Material index for interior faces",
376 use_interior_vgroup
= BoolProperty(
377 name
="Interior VGroup",
378 description
="Create a vertex group for interior verts",
382 # -------------------------------------------------------------------------
385 mass_mode
= EnumProperty(
387 items
=(('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
388 ('UNIFORM', "Uniform", "All objects get the specified mass"),
393 mass
= FloatProperty(
395 description
="Mass to give created objects",
396 min=0.001, max=1000.0,
401 # -------------------------------------------------------------------------
404 use_recenter
= BoolProperty(
406 description
="Recalculate the center points after splitting",
410 use_remove_original
= BoolProperty(
411 name
="Remove Original",
412 description
="Removes the parents used to create the shatter",
416 # -------------------------------------------------------------------------
419 # .. different from object options in that this controls how the objects
420 # are setup in the scene.
422 use_layer_index
= IntProperty(
424 description
="Layer to add the objects into or 0 for existing",
429 use_layer_next
= BoolProperty(
431 description
="At the object into the next layer (layer index overrides)",
435 group_name
= StringProperty(
437 description
="Create objects int a group "
438 "(use existing or create new)",
441 # -------------------------------------------------------------------------
443 use_debug_points
= BoolProperty(
445 description
="Create mesh data showing the points used for fracture",
449 use_debug_redraw
= BoolProperty(
450 name
="Show Progress Realtime",
451 description
="Redraw as fracture is done",
455 use_debug_bool
= BoolProperty(
456 name
="Debug Boolean",
457 description
="Skip applying the boolean modifier",
461 def execute(self
, context
):
462 keywords
= self
.as_keywords() # ignore=("blah",)
464 main(context
, **keywords
)
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
):
478 col
.label("Point Source")
480 rowsub
.prop(self
, "source")
482 rowsub
.prop(self
, "source_limit")
483 rowsub
.prop(self
, "source_noise")
485 rowsub
.prop(self
, "cell_scale")
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")
495 rowsub
.prop(self
, "recursion_chance")
496 rowsub
.prop(self
, "recursion_chance_select", expand
=True)
500 col
.label("Mesh Data")
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")
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")
520 rowsub
= col
.row(align
=True)
521 rowsub
.prop(self
, "mass_mode")
522 rowsub
.prop(self
, "mass")
528 rowsub
= col
.row(align
=True)
529 rowsub
.prop(self
, "use_recenter")
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")
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
):
551 layout
.label("Cell Fracture:")
552 layout
.operator("object.add_fracture_cell_objects",
553 text
="Cell Fracture")
557 bpy
.utils
.register_class(FractureCell
)
558 bpy
.types
.VIEW3D_PT_tools_object
.append(menu_func
)
562 bpy
.utils
.unregister_class(FractureCell
)
563 bpy
.types
.VIEW3D_PT_tools_object
.remove(menu_func
)
566 if __name__
== "__main__":