1 # SPDX-License-Identifier: GPL-2.0-or-later
4 "name": "Cell Fracture",
5 "author": "ideasman42, phymec, Sergey Sharybin",
8 "location": "Viewport Object Menu -> Quick Effects",
9 "description": "Fractured Object Creation",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/cell_fracture.html",
16 # if "bpy" in locals():
18 # importlib.reload(fracture_cell_setup)
21 from bpy
.props
import (
30 from bpy
.types
import Operator
33 def main_object(context
, collection
, obj
, level
, **kw
):
38 use_recenter
= kw_copy
.pop("use_recenter")
39 use_remove_original
= kw_copy
.pop("use_remove_original")
40 recursion
= kw_copy
.pop("recursion")
41 recursion_source_limit
= kw_copy
.pop("recursion_source_limit")
42 recursion_clamp
= kw_copy
.pop("recursion_clamp")
43 recursion_chance
= kw_copy
.pop("recursion_chance")
44 recursion_chance_select
= kw_copy
.pop("recursion_chance_select")
45 use_island_split
= kw_copy
.pop("use_island_split")
46 use_debug_bool
= kw_copy
.pop("use_debug_bool")
47 use_interior_vgroup
= kw_copy
.pop("use_interior_vgroup")
48 use_sharp_edges
= kw_copy
.pop("use_sharp_edges")
49 use_sharp_edges_apply
= kw_copy
.pop("use_sharp_edges_apply")
54 kw_copy
["source_limit"] = recursion_source_limit
56 from . import fracture_cell_setup
58 # not essential but selection is visual distraction.
61 if kw_copy
["use_debug_redraw"]:
62 obj_display_type_prev
= obj
.display_type
63 obj
.display_type
= 'WIRE'
65 objects
= fracture_cell_setup
.cell_fracture_objects(context
, collection
, obj
, **kw_copy
)
66 objects
= fracture_cell_setup
.cell_fracture_boolean(
67 context
, collection
, obj
, objects
,
68 use_island_split
=use_island_split
,
69 use_interior_hide
=(use_interior_vgroup
or use_sharp_edges
),
70 use_debug_bool
=use_debug_bool
,
71 use_debug_redraw
=kw_copy
["use_debug_redraw"],
75 # must apply after boolean.
77 bpy
.ops
.object.origin_set(
78 {"selected_editable_objects": objects
},
79 type='ORIGIN_GEOMETRY',
86 for level_sub
in range(1, recursion
+ 1):
88 objects_recurse_input
= [(i
, o
) for i
, o
in enumerate(objects
)]
90 if recursion_chance
!= 1.0:
91 from mathutils
import Vector
92 if recursion_chance_select
== 'RANDOM':
93 random
.shuffle(objects_recurse_input
)
94 elif recursion_chance_select
in {'SIZE_MIN', 'SIZE_MAX'}:
95 objects_recurse_input
.sort(key
=lambda ob_pair
: (
96 Vector(ob_pair
[1].bound_box
[0]) -
97 Vector(ob_pair
[1].bound_box
[6])
99 if recursion_chance_select
== 'SIZE_MAX':
100 objects_recurse_input
.reverse()
101 elif recursion_chance_select
in {'CURSOR_MIN', 'CURSOR_MAX'}:
102 c
= scene
.cursor
.location
.copy()
103 objects_recurse_input
.sort(key
=lambda ob_pair
: (ob_pair
[1].location
- c
).length_squared
)
104 if recursion_chance_select
== 'CURSOR_MAX':
105 objects_recurse_input
.reverse()
107 objects_recurse_input
[int(recursion_chance
* len(objects_recurse_input
)):] = []
108 objects_recurse_input
.sort()
110 # reverse index values so we can remove from original list.
111 objects_recurse_input
.reverse()
113 objects_recursive
= []
114 for i
, obj_cell
in objects_recurse_input
:
115 assert(objects
[i
] is obj_cell
)
116 objects_recursive
+= main_object(context
, collection
, obj_cell
, level_sub
, **kw
)
117 if use_remove_original
:
118 collection
.objects
.unlink(obj_cell
)
120 if recursion_clamp
and len(objects
) + len(objects_recursive
) >= recursion_clamp
:
122 objects
.extend(objects_recursive
)
124 if recursion_clamp
and len(objects
) > recursion_clamp
:
130 # import pdb; pdb.set_trace()
131 if use_interior_vgroup
or use_sharp_edges
:
132 fracture_cell_setup
.cell_fracture_interior_handle(
134 use_interior_vgroup
=use_interior_vgroup
,
135 use_sharp_edges
=use_sharp_edges
,
136 use_sharp_edges_apply
=use_sharp_edges_apply
,
139 if kw_copy
["use_debug_redraw"]:
140 obj
.display_type
= obj_display_type_prev
147 def main(context
, **kw
):
150 objects_context
= context
.selected_editable_objects
155 mass_mode
= kw_copy
.pop("mass_mode")
156 mass
= kw_copy
.pop("mass")
157 collection_name
= kw_copy
.pop("collection_name")
159 collection
= context
.collection
161 collection_current
= collection
162 collection
= bpy
.data
.collections
.get(collection_name
)
163 if collection
is None:
164 collection
= bpy
.data
.collections
.new(collection_name
)
165 collection_current
.children
.link(collection
)
166 del collection_current
169 for obj
in objects_context
:
170 if obj
.type == 'MESH':
171 objects
+= main_object(context
, collection
, obj
, 0, **kw_copy
)
173 bpy
.ops
.object.select_all(action
='DESELECT')
174 for obj_cell
in objects
:
175 obj_cell
.select_set(True)
177 # FIXME(campbell): we should be able to initialize rigid-body data.
178 if mass_mode
== 'UNIFORM':
179 for obj_cell
in objects
:
180 rb
= obj_cell
.rigid_body
183 elif mass_mode
== 'VOLUME':
184 from mathutils
import Vector
186 def _get_volume(obj_cell
):
187 def _getObjectBBMinMax():
188 min_co
= Vector((1000000.0, 1000000.0, 1000000.0))
190 matrix
= obj_cell
.matrix_world
.copy()
191 for i
in range(0, 8):
192 bb_vec
= matrix
@ Vector(obj_cell
.bound_box
[i
])
193 min_co
[0] = min(bb_vec
[0], min_co
[0])
194 min_co
[1] = min(bb_vec
[1], min_co
[1])
195 min_co
[2] = min(bb_vec
[2], min_co
[2])
196 max_co
[0] = max(bb_vec
[0], max_co
[0])
197 max_co
[1] = max(bb_vec
[1], max_co
[1])
198 max_co
[2] = max(bb_vec
[2], max_co
[2])
199 return (min_co
, max_co
)
201 def _getObjectVolume():
202 min_co
, max_co
= _getObjectBBMinMax()
203 x
= max_co
[0] - min_co
[0]
204 y
= max_co
[1] - min_co
[1]
205 z
= max_co
[2] - min_co
[2]
209 return _getObjectVolume()
211 obj_volume_ls
= [_get_volume(obj_cell
) for obj_cell
in objects
]
212 obj_volume_tot
= sum(obj_volume_ls
)
213 if obj_volume_tot
> 0.0:
214 mass_fac
= mass
/ obj_volume_tot
215 for i
, obj_cell
in enumerate(objects
):
216 rb
= obj_cell
.rigid_body
218 rb
.mass
= obj_volume_ls
[i
] * mass_fac
222 print("Done! %d objects in %.4f sec" % (len(objects
), time
.time() - t
))
225 class FractureCell(Operator
):
226 bl_idname
= "object.add_fracture_cell_objects"
227 bl_label
= "Cell fracture selected mesh objects"
228 bl_options
= {'PRESET', 'UNDO'}
230 # -------------------------------------------------------------------------
232 source
: EnumProperty(
235 ('VERT_OWN', "Own Verts", "Use own vertices"),
236 ('VERT_CHILD', "Child Verts", "Use child object vertices"),
237 ('PARTICLE_OWN', "Own Particles", (
238 "All particle systems of the "
241 ('PARTICLE_CHILD', "Child Particles", (
242 "All particle systems of the "
245 ('PENCIL', "Annotation Pencil", "Annotation Grease Pencil."),
247 options
={'ENUM_FLAG'},
248 default
={'PARTICLE_OWN'},
251 source_limit
: IntProperty(
253 description
="Limit the number of input points, 0 for unlimited",
258 source_noise
: FloatProperty(
260 description
="Randomize point distribution",
265 cell_scale
: FloatVectorProperty(
267 description
="Scale Cell Shape",
270 default
=(1.0, 1.0, 1.0),
273 # -------------------------------------------------------------------------
276 recursion
: IntProperty(
278 description
="Break shards recursively",
283 recursion_source_limit
: IntProperty(
285 description
="Limit the number of input points, 0 for unlimited (applies to recursion only)",
290 recursion_clamp
: IntProperty(
291 name
="Clamp Recursion",
293 "Finish recursion when this number of objects is reached "
294 "(prevents recursing for extended periods of time), zero disables"
300 recursion_chance
: FloatProperty(
301 name
="Random Factor",
302 description
="Likelihood of recursion",
307 recursion_chance_select
: EnumProperty(
310 ('RANDOM', "Random", ""),
311 ('SIZE_MIN', "Small", "Recursively subdivide smaller objects"),
312 ('SIZE_MAX', "Big", "Recursively subdivide bigger objects"),
313 ('CURSOR_MIN', "Cursor Close", "Recursively subdivide objects closer to the cursor"),
314 ('CURSOR_MAX', "Cursor Far", "Recursively subdivide objects farther from the cursor"),
319 # -------------------------------------------------------------------------
322 use_smooth_faces
: BoolProperty(
323 name
="Smooth Interior",
324 description
="Smooth Faces of inner side",
328 use_sharp_edges
: BoolProperty(
330 description
="Set sharp edges when disabled",
334 use_sharp_edges_apply
: BoolProperty(
335 name
="Apply Split Edge",
336 description
="Split sharp hard edges",
340 use_data_match
: BoolProperty(
342 description
="Match original mesh materials and data layers",
346 use_island_split
: BoolProperty(
347 name
="Split Islands",
348 description
="Split disconnected meshes",
352 margin
: FloatProperty(
354 description
="Gaps for the fracture (gives more stable physics)",
359 material_index
: IntProperty(
361 description
="Material index for interior faces",
365 use_interior_vgroup
: BoolProperty(
366 name
="Interior VGroup",
367 description
="Create a vertex group for interior verts",
371 # -------------------------------------------------------------------------
374 mass_mode
: EnumProperty(
377 ('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
378 ('UNIFORM', "Uniform", "All objects get the specified mass"),
385 description
="Mass to give created objects",
386 min=0.001, max=1000.0,
390 # -------------------------------------------------------------------------
393 use_recenter
: BoolProperty(
395 description
="Recalculate the center points after splitting",
399 use_remove_original
: BoolProperty(
400 name
="Remove Original",
401 description
="Removes the parents used to create the shatter",
405 # -------------------------------------------------------------------------
408 # .. different from object options in that this controls how the objects
409 # are setup in the scene.
411 collection_name
: StringProperty(
414 "Create objects in a collection "
415 "(use existing or create new)"
419 # -------------------------------------------------------------------------
421 use_debug_points
: BoolProperty(
423 description
="Create mesh data showing the points used for fracture",
427 use_debug_redraw
: BoolProperty(
428 name
="Show Progress Realtime",
429 description
="Redraw as fracture is done",
433 use_debug_bool
: BoolProperty(
434 name
="Debug Boolean",
435 description
="Skip applying the boolean modifier",
439 def execute(self
, context
):
440 keywords
= self
.as_keywords() # ignore=("blah",)
442 main(context
, **keywords
)
446 def invoke(self
, context
, event
):
447 # print(self.recursion_chance_select)
448 wm
= context
.window_manager
449 return wm
.invoke_props_dialog(self
, width
=600)
451 def draw(self
, context
):
455 col
.label(text
="Point Source")
457 rowsub
.prop(self
, "source")
459 rowsub
.prop(self
, "source_limit")
460 rowsub
.prop(self
, "source_noise")
462 rowsub
.prop(self
, "cell_scale")
466 col
.label(text
="Recursive Shatter")
467 rowsub
= col
.row(align
=True)
468 rowsub
.prop(self
, "recursion")
469 rowsub
.prop(self
, "recursion_source_limit")
470 rowsub
.prop(self
, "recursion_clamp")
472 rowsub
.prop(self
, "recursion_chance")
473 rowsub
.prop(self
, "recursion_chance_select", expand
=True)
477 col
.label(text
="Mesh Data")
479 rowsub
.prop(self
, "use_smooth_faces")
480 rowsub
.prop(self
, "use_sharp_edges")
481 rowsub
.prop(self
, "use_sharp_edges_apply")
482 rowsub
.prop(self
, "use_data_match")
485 # on same row for even layout but in fact are not all that related
486 rowsub
.prop(self
, "material_index")
487 rowsub
.prop(self
, "use_interior_vgroup")
489 # could be own section, control how we subdiv
490 rowsub
.prop(self
, "margin")
491 rowsub
.prop(self
, "use_island_split")
495 col
.label(text
="Physics")
496 rowsub
= col
.row(align
=True)
497 rowsub
.prop(self
, "mass_mode")
498 rowsub
.prop(self
, "mass")
502 col
.label(text
="Object")
503 rowsub
= col
.row(align
=True)
504 rowsub
.prop(self
, "use_recenter")
508 col
.label(text
="Scene")
509 rowsub
= col
.row(align
=True)
510 rowsub
.prop(self
, "collection_name")
514 col
.label(text
="Debug")
515 rowsub
= col
.row(align
=True)
516 rowsub
.prop(self
, "use_debug_redraw")
517 rowsub
.prop(self
, "use_debug_points")
518 rowsub
.prop(self
, "use_debug_bool")
521 def menu_func(self
, context
):
524 layout
.operator("object.add_fracture_cell_objects", text
="Cell Fracture")
528 bpy
.utils
.register_class(FractureCell
)
529 bpy
.types
.VIEW3D_MT_object_quick_effects
.append(menu_func
)
533 bpy
.utils
.unregister_class(FractureCell
)
534 bpy
.types
.VIEW3D_MT_object_quick_effects
.remove(menu_func
)
537 if __name__
== "__main__":