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, 80, 0),
24 "location": "Viewport Object Menu -> Quick Effects",
25 "description": "Fractured Object Creation",
27 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/cell_fracture.html",
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
, collection
, 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_island_split
= kw_copy
.pop("use_island_split")
61 use_debug_bool
= kw_copy
.pop("use_debug_bool")
62 use_interior_vgroup
= kw_copy
.pop("use_interior_vgroup")
63 use_sharp_edges
= kw_copy
.pop("use_sharp_edges")
64 use_sharp_edges_apply
= kw_copy
.pop("use_sharp_edges_apply")
69 kw_copy
["source_limit"] = recursion_source_limit
71 from . import fracture_cell_setup
73 # not essential but selection is visual distraction.
76 if kw_copy
["use_debug_redraw"]:
77 obj_display_type_prev
= obj
.display_type
78 obj
.display_type
= 'WIRE'
80 objects
= fracture_cell_setup
.cell_fracture_objects(context
, collection
, obj
, **kw_copy
)
81 objects
= fracture_cell_setup
.cell_fracture_boolean(
82 context
, collection
, 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(
93 {"selected_editable_objects": objects
},
94 type='ORIGIN_GEOMETRY',
101 for level_sub
in range(1, recursion
+ 1):
103 objects_recurse_input
= [(i
, o
) for i
, o
in enumerate(objects
)]
105 if recursion_chance
!= 1.0:
106 from mathutils
import Vector
107 if recursion_chance_select
== 'RANDOM':
108 random
.shuffle(objects_recurse_input
)
109 elif recursion_chance_select
in {'SIZE_MIN', 'SIZE_MAX'}:
110 objects_recurse_input
.sort(key
=lambda ob_pair
: (
111 Vector(ob_pair
[1].bound_box
[0]) -
112 Vector(ob_pair
[1].bound_box
[6])
114 if recursion_chance_select
== 'SIZE_MAX':
115 objects_recurse_input
.reverse()
116 elif recursion_chance_select
in {'CURSOR_MIN', 'CURSOR_MAX'}:
117 c
= scene
.cursor
.location
.copy()
118 objects_recurse_input
.sort(key
=lambda ob_pair
:
119 (ob_pair
[1].location
- c
).length_squared
)
120 if recursion_chance_select
== 'CURSOR_MAX':
121 objects_recurse_input
.reverse()
123 objects_recurse_input
[int(recursion_chance
* len(objects_recurse_input
)):] = []
124 objects_recurse_input
.sort()
126 # reverse index values so we can remove from original list.
127 objects_recurse_input
.reverse()
129 objects_recursive
= []
130 for i
, obj_cell
in objects_recurse_input
:
131 assert(objects
[i
] is obj_cell
)
132 objects_recursive
+= main_object(context
, collection
, obj_cell
, level_sub
, **kw
)
133 if use_remove_original
:
134 collection
.objects
.unlink(obj_cell
)
136 if recursion_clamp
and len(objects
) + len(objects_recursive
) >= recursion_clamp
:
138 objects
.extend(objects_recursive
)
140 if recursion_clamp
and len(objects
) > recursion_clamp
:
146 # import pdb; pdb.set_trace()
147 if use_interior_vgroup
or use_sharp_edges
:
148 fracture_cell_setup
.cell_fracture_interior_handle(
150 use_interior_vgroup
=use_interior_vgroup
,
151 use_sharp_edges
=use_sharp_edges
,
152 use_sharp_edges_apply
=use_sharp_edges_apply
,
155 if kw_copy
["use_debug_redraw"]:
156 obj
.display_type
= obj_display_type_prev
163 def main(context
, **kw
):
166 objects_context
= context
.selected_editable_objects
171 mass_mode
= kw_copy
.pop("mass_mode")
172 mass
= kw_copy
.pop("mass")
173 collection_name
= kw_copy
.pop("collection_name")
175 collection
= context
.collection
177 collection_current
= collection
178 collection
= bpy
.data
.collections
.get(collection_name
)
179 if collection
is None:
180 collection
= bpy
.data
.collections
.new(collection_name
)
181 collection_current
.children
.link(collection
)
182 del collection_current
185 for obj
in objects_context
:
186 if obj
.type == 'MESH':
187 objects
+= main_object(context
, collection
, obj
, 0, **kw_copy
)
189 bpy
.ops
.object.select_all(action
='DESELECT')
190 for obj_cell
in objects
:
191 obj_cell
.select_set(True)
193 # FIXME(campbell): we should be able to initialize rigid-body data.
194 if mass_mode
== 'UNIFORM':
195 for obj_cell
in objects
:
196 rb
= obj_cell
.rigid_body
199 elif mass_mode
== 'VOLUME':
200 from mathutils
import Vector
201 def _get_volume(obj_cell
):
202 def _getObjectBBMinMax():
203 min_co
= Vector((1000000.0, 1000000.0, 1000000.0))
205 matrix
= obj_cell
.matrix_world
.copy()
206 for i
in range(0, 8):
207 bb_vec
= matrix
@ Vector(obj_cell
.bound_box
[i
])
208 min_co
[0] = min(bb_vec
[0], min_co
[0])
209 min_co
[1] = min(bb_vec
[1], min_co
[1])
210 min_co
[2] = min(bb_vec
[2], min_co
[2])
211 max_co
[0] = max(bb_vec
[0], max_co
[0])
212 max_co
[1] = max(bb_vec
[1], max_co
[1])
213 max_co
[2] = max(bb_vec
[2], max_co
[2])
214 return (min_co
, max_co
)
216 def _getObjectVolume():
217 min_co
, max_co
= _getObjectBBMinMax()
218 x
= max_co
[0] - min_co
[0]
219 y
= max_co
[1] - min_co
[1]
220 z
= max_co
[2] - min_co
[2]
224 return _getObjectVolume()
227 obj_volume_ls
= [_get_volume(obj_cell
) for obj_cell
in objects
]
228 obj_volume_tot
= sum(obj_volume_ls
)
229 if obj_volume_tot
> 0.0:
230 mass_fac
= mass
/ obj_volume_tot
231 for i
, obj_cell
in enumerate(objects
):
232 rb
= obj_cell
.rigid_body
234 rb
.mass
= obj_volume_ls
[i
] * mass_fac
238 print("Done! %d objects in %.4f sec" % (len(objects
), time
.time() - t
))
241 class FractureCell(Operator
):
242 bl_idname
= "object.add_fracture_cell_objects"
243 bl_label
= "Cell fracture selected mesh objects"
244 bl_options
= {'PRESET', 'UNDO'}
246 # -------------------------------------------------------------------------
248 source
: EnumProperty(
251 ('VERT_OWN', "Own Verts", "Use own vertices"),
252 ('VERT_CHILD', "Child Verts", "Use child object vertices"),
253 ('PARTICLE_OWN', "Own Particles", (
254 "All particle systems of the "
257 ('PARTICLE_CHILD', "Child Particles", (
258 "All particle systems of the "
261 ('PENCIL', "Annotation Pencil", "Annotation 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",
309 "Finish recursion when this number of objects is reached "
310 "(prevents recursing for extended periods of time), zero disables"
316 recursion_chance
: FloatProperty(
317 name
="Random Factor",
318 description
="Likelihood of recursion",
323 recursion_chance_select
: EnumProperty(
326 ('RANDOM', "Random", ""),
327 ('SIZE_MIN', "Small", "Recursively subdivide smaller objects"),
328 ('SIZE_MAX', "Big", "Recursively subdivide bigger objects"),
329 ('CURSOR_MIN', "Cursor Close", "Recursively subdivide objects closer to the cursor"),
330 ('CURSOR_MAX', "Cursor Far", "Recursively subdivide objects farther from the cursor"),
335 # -------------------------------------------------------------------------
338 use_smooth_faces
: BoolProperty(
339 name
="Smooth Interior",
340 description
="Smooth Faces of inner side",
344 use_sharp_edges
: BoolProperty(
346 description
="Set sharp edges when disabled",
350 use_sharp_edges_apply
: BoolProperty(
351 name
="Apply Split Edge",
352 description
="Split sharp hard edges",
356 use_data_match
: BoolProperty(
358 description
="Match original mesh materials and data layers",
362 use_island_split
: BoolProperty(
363 name
="Split Islands",
364 description
="Split disconnected meshes",
368 margin
: FloatProperty(
370 description
="Gaps for the fracture (gives more stable physics)",
375 material_index
: IntProperty(
377 description
="Material index for interior faces",
381 use_interior_vgroup
: BoolProperty(
382 name
="Interior VGroup",
383 description
="Create a vertex group for interior verts",
387 # -------------------------------------------------------------------------
390 mass_mode
: EnumProperty(
393 ('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
394 ('UNIFORM', "Uniform", "All objects get the specified mass"),
401 description
="Mass to give created objects",
402 min=0.001, max=1000.0,
407 # -------------------------------------------------------------------------
410 use_recenter
: BoolProperty(
412 description
="Recalculate the center points after splitting",
416 use_remove_original
: BoolProperty(
417 name
="Remove Original",
418 description
="Removes the parents used to create the shatter",
422 # -------------------------------------------------------------------------
425 # .. different from object options in that this controls how the objects
426 # are setup in the scene.
428 collection_name
: StringProperty(
431 "Create objects in a collection "
432 "(use existing or create new)"
436 # -------------------------------------------------------------------------
438 use_debug_points
: BoolProperty(
440 description
="Create mesh data showing the points used for fracture",
444 use_debug_redraw
: BoolProperty(
445 name
="Show Progress Realtime",
446 description
="Redraw as fracture is done",
450 use_debug_bool
: BoolProperty(
451 name
="Debug Boolean",
452 description
="Skip applying the boolean modifier",
456 def execute(self
, context
):
457 keywords
= self
.as_keywords() # ignore=("blah",)
459 main(context
, **keywords
)
464 def invoke(self
, context
, event
):
465 # print(self.recursion_chance_select)
466 wm
= context
.window_manager
467 return wm
.invoke_props_dialog(self
, width
=600)
469 def draw(self
, context
):
473 col
.label(text
="Point Source")
475 rowsub
.prop(self
, "source")
477 rowsub
.prop(self
, "source_limit")
478 rowsub
.prop(self
, "source_noise")
480 rowsub
.prop(self
, "cell_scale")
484 col
.label(text
="Recursive Shatter")
485 rowsub
= col
.row(align
=True)
486 rowsub
.prop(self
, "recursion")
487 rowsub
.prop(self
, "recursion_source_limit")
488 rowsub
.prop(self
, "recursion_clamp")
490 rowsub
.prop(self
, "recursion_chance")
491 rowsub
.prop(self
, "recursion_chance_select", expand
=True)
495 col
.label(text
="Mesh Data")
497 rowsub
.prop(self
, "use_smooth_faces")
498 rowsub
.prop(self
, "use_sharp_edges")
499 rowsub
.prop(self
, "use_sharp_edges_apply")
500 rowsub
.prop(self
, "use_data_match")
503 # on same row for even layout but infact are not all that related
504 rowsub
.prop(self
, "material_index")
505 rowsub
.prop(self
, "use_interior_vgroup")
507 # could be own section, control how we subdiv
508 rowsub
.prop(self
, "margin")
509 rowsub
.prop(self
, "use_island_split")
514 col
.label(text
="Physics")
515 rowsub
= col
.row(align
=True)
516 rowsub
.prop(self
, "mass_mode")
517 rowsub
.prop(self
, "mass")
522 col
.label(text
="Object")
523 rowsub
= col
.row(align
=True)
524 rowsub
.prop(self
, "use_recenter")
529 col
.label(text
="Scene")
530 rowsub
= col
.row(align
=True)
531 rowsub
.prop(self
, "collection_name")
535 col
.label(text
="Debug")
536 rowsub
= col
.row(align
=True)
537 rowsub
.prop(self
, "use_debug_redraw")
538 rowsub
.prop(self
, "use_debug_points")
539 rowsub
.prop(self
, "use_debug_bool")
542 def menu_func(self
, context
):
545 layout
.operator("object.add_fracture_cell_objects", text
="Cell Fracture")
549 bpy
.utils
.register_class(FractureCell
)
550 bpy
.types
.VIEW3D_MT_object_quick_effects
.append(menu_func
)
554 bpy
.utils
.unregister_class(FractureCell
)
555 bpy
.types
.VIEW3D_MT_object_quick_effects
.remove(menu_func
)
558 if __name__
== "__main__":