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(context
, 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")
69 collection
= context
.collection
72 kw_copy
["source_limit"] = recursion_source_limit
74 from . import fracture_cell_setup
76 # not essential but selection is visual distraction.
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"],
92 # must apply after boolean.
94 bpy
.ops
.object.origin_set({"selected_editable_objects": objects
},
95 type='ORIGIN_GEOMETRY', center
='MEDIAN')
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
)
132 if recursion_clamp
and len(objects
) + len(objects_recursive
) >= recursion_clamp
:
134 objects
.extend(objects_recursive
)
136 if recursion_clamp
and len(objects
) > recursion_clamp
:
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
,
155 if use_layer_index
!= 0:
156 layers_new
= [False] * 20
157 layers_new
[use_layer_index
- 1] = True
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
168 group
= bpy
.data
.collections
.get(group_name
)
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
184 def main(context
, **kw
):
187 objects_context
= context
.selected_editable_objects
192 mass_mode
= kw_copy
.pop("mass_mode")
193 mass
= kw_copy
.pop("mass")
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))
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]
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
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 # -------------------------------------------------------------------------
254 source
: EnumProperty(
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 "
260 ('PARTICLE_CHILD', "Child Particles", ("All particle systems of the "
262 ('PENCIL', "Grease Pencil", "This object's grease pencil"),
264 options
={'ENUM_FLAG'},
265 default
={'PARTICLE_OWN'},
268 source_limit
: IntProperty(
270 description
="Limit the number of input points, 0 for unlimited",
275 source_noise
: FloatProperty(
277 description
="Randomize point distribution",
282 cell_scale
: FloatVectorProperty(
284 description
="Scale Cell Shape",
287 default
=(1.0, 1.0, 1.0),
290 # -------------------------------------------------------------------------
293 recursion
: IntProperty(
295 description
="Break shards recursively",
300 recursion_source_limit
: IntProperty(
302 description
="Limit the number of input points, 0 for unlimited (applies to recursion only)",
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",
314 recursion_chance
: FloatProperty(
315 name
="Random Factor",
316 description
="Likelihood of recursion",
321 recursion_chance_select
: EnumProperty(
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"),
332 # -------------------------------------------------------------------------
335 use_smooth_faces
: BoolProperty(
340 use_sharp_edges
: BoolProperty(
342 description
="Set sharp edges when disabled",
346 use_sharp_edges_apply
: BoolProperty(
347 name
="Apply Split Edge",
348 description
="Split sharp hard edges",
352 use_data_match
: BoolProperty(
354 description
="Match original mesh materials and data layers",
358 use_island_split
: BoolProperty(
359 name
="Split Islands",
360 description
="Split disconnected meshes",
364 margin
: FloatProperty(
366 description
="Gaps for the fracture (gives more stable physics)",
371 material_index
: IntProperty(
373 description
="Material index for interior faces",
377 use_interior_vgroup
: BoolProperty(
378 name
="Interior VGroup",
379 description
="Create a vertex group for interior verts",
383 # -------------------------------------------------------------------------
386 mass_mode
: EnumProperty(
388 items
=(('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
389 ('UNIFORM', "Uniform", "All objects get the specified mass"),
396 description
="Mass to give created objects",
397 min=0.001, max=1000.0,
402 # -------------------------------------------------------------------------
405 use_recenter
: BoolProperty(
407 description
="Recalculate the center points after splitting",
411 use_remove_original
: BoolProperty(
412 name
="Remove Original",
413 description
="Removes the parents used to create the shatter",
417 # -------------------------------------------------------------------------
420 # .. different from object options in that this controls how the objects
421 # are setup in the scene.
423 use_layer_index
: IntProperty(
425 description
="Layer to add the objects into or 0 for existing",
430 use_layer_next
: BoolProperty(
432 description
="At the object into the next layer (layer index overrides)",
436 group_name
: StringProperty(
438 description
="Create objects int a group "
439 "(use existing or create new)",
442 # -------------------------------------------------------------------------
444 use_debug_points
: BoolProperty(
446 description
="Create mesh data showing the points used for fracture",
450 use_debug_redraw
: BoolProperty(
451 name
="Show Progress Realtime",
452 description
="Redraw as fracture is done",
456 use_debug_bool
: BoolProperty(
457 name
="Debug Boolean",
458 description
="Skip applying the boolean modifier",
462 def execute(self
, context
):
463 keywords
= self
.as_keywords() # ignore=("blah",)
465 main(context
, **keywords
)
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
):
479 col
.label(text
="Point Source")
481 rowsub
.prop(self
, "source")
483 rowsub
.prop(self
, "source_limit")
484 rowsub
.prop(self
, "source_noise")
486 rowsub
.prop(self
, "cell_scale")
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")
496 rowsub
.prop(self
, "recursion_chance")
497 rowsub
.prop(self
, "recursion_chance_select", expand
=True)
501 col
.label(text
="Mesh Data")
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")
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")
520 col
.label(text
="Physics")
521 rowsub
= col
.row(align
=True)
522 rowsub
.prop(self
, "mass_mode")
523 rowsub
.prop(self
, "mass")
528 col
.label(text
="Object")
529 rowsub
= col
.row(align
=True)
530 rowsub
.prop(self
, "use_recenter")
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")
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
):
552 layout
.label(text
="Cell Fracture:")
553 layout
.operator("object.add_fracture_cell_objects",
554 text
="Cell Fracture")
558 bpy
.utils
.register_class(FractureCell
)
559 bpy
.types
.VIEW3D_PT_tools_object
.append(menu_func
)
563 bpy
.utils
.unregister_class(FractureCell
)
564 bpy
.types
.VIEW3D_PT_tools_object
.remove(menu_func
)
567 if __name__
== "__main__":