Pose library: fix asset creation operator poll when no object active
[blender-addons.git] / object_fracture_cell / __init__.py
blob543f86f132a04d776b703975ae7972e1b1245fab
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Cell Fracture",
5 "author": "ideasman42, phymec, Sergey Sharybin",
6 "version": (0, 2),
7 "blender": (2, 80, 0),
8 "location": "Viewport Object Menu -> Quick Effects",
9 "description": "Fractured Object Creation",
10 "warning": "",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/cell_fracture.html",
12 "category": "Object",
16 # if "bpy" in locals():
17 # import importlib
18 # importlib.reload(fracture_cell_setup)
20 import bpy
21 from bpy.props import (
22 StringProperty,
23 BoolProperty,
24 IntProperty,
25 FloatProperty,
26 FloatVectorProperty,
27 EnumProperty,
30 from bpy.types import Operator
33 def main_object(context, collection, obj, level, **kw):
34 import random
36 # pull out some args
37 kw_copy = kw.copy()
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")
51 scene = context.scene
53 if level != 0:
54 kw_copy["source_limit"] = recursion_source_limit
56 from . import fracture_cell_setup
58 # not essential but selection is visual distraction.
59 obj.select_set(False)
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"],
72 level=level,
75 # must apply after boolean.
76 if use_recenter:
77 bpy.ops.object.origin_set(
78 {"selected_editable_objects": objects},
79 type='ORIGIN_GEOMETRY',
80 center='MEDIAN',
83 # ----------
84 # Recursion
85 if level == 0:
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])
98 ).length_squared)
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)
119 del objects[i]
120 if recursion_clamp and len(objects) + len(objects_recursive) >= recursion_clamp:
121 break
122 objects.extend(objects_recursive)
124 if recursion_clamp and len(objects) > recursion_clamp:
125 break
127 # --------------
128 # Level Options
129 if level == 0:
130 # import pdb; pdb.set_trace()
131 if use_interior_vgroup or use_sharp_edges:
132 fracture_cell_setup.cell_fracture_interior_handle(
133 objects,
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
142 # testing only!
143 # obj.hide = True
144 return objects
147 def main(context, **kw):
148 import time
149 t = time.time()
150 objects_context = context.selected_editable_objects
152 kw_copy = kw.copy()
154 # mass
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
160 if collection_name:
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
168 objects = []
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
181 if rb is not None:
182 rb.mass = mass
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))
189 max_co = -min_co
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]
206 volume = x * y * z
207 return volume
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
217 if rb is not None:
218 rb.mass = obj_volume_ls[i] * mass_fac
219 else:
220 assert(0)
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 # -------------------------------------------------------------------------
231 # Source Options
232 source: EnumProperty(
233 name="Source",
234 items=(
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 "
239 "source object"
241 ('PARTICLE_CHILD', "Child Particles", (
242 "All particle systems of the "
243 "child objects"
245 ('PENCIL', "Annotation Pencil", "Annotation Grease Pencil."),
247 options={'ENUM_FLAG'},
248 default={'PARTICLE_OWN'},
251 source_limit: IntProperty(
252 name="Source Limit",
253 description="Limit the number of input points, 0 for unlimited",
254 min=0, max=5000,
255 default=100,
258 source_noise: FloatProperty(
259 name="Noise",
260 description="Randomize point distribution",
261 min=0.0, max=1.0,
262 default=0.0,
265 cell_scale: FloatVectorProperty(
266 name="Scale",
267 description="Scale Cell Shape",
268 size=3,
269 min=0.0, max=1.0,
270 default=(1.0, 1.0, 1.0),
273 # -------------------------------------------------------------------------
274 # Recursion
276 recursion: IntProperty(
277 name="Recursion",
278 description="Break shards recursively",
279 min=0, max=5000,
280 default=0,
283 recursion_source_limit: IntProperty(
284 name="Source Limit",
285 description="Limit the number of input points, 0 for unlimited (applies to recursion only)",
286 min=0, max=5000,
287 default=8,
290 recursion_clamp: IntProperty(
291 name="Clamp Recursion",
292 description=(
293 "Finish recursion when this number of objects is reached "
294 "(prevents recursing for extended periods of time), zero disables"
296 min=0, max=10000,
297 default=250,
300 recursion_chance: FloatProperty(
301 name="Random Factor",
302 description="Likelihood of recursion",
303 min=0.0, max=1.0,
304 default=0.25,
307 recursion_chance_select: EnumProperty(
308 name="Recurse Over",
309 items=(
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"),
316 default='SIZE_MIN',
319 # -------------------------------------------------------------------------
320 # Mesh Data Options
322 use_smooth_faces: BoolProperty(
323 name="Smooth Interior",
324 description="Smooth Faces of inner side",
325 default=False,
328 use_sharp_edges: BoolProperty(
329 name="Sharp Edges",
330 description="Set sharp edges when disabled",
331 default=True,
334 use_sharp_edges_apply: BoolProperty(
335 name="Apply Split Edge",
336 description="Split sharp hard edges",
337 default=True,
340 use_data_match: BoolProperty(
341 name="Match Data",
342 description="Match original mesh materials and data layers",
343 default=True,
346 use_island_split: BoolProperty(
347 name="Split Islands",
348 description="Split disconnected meshes",
349 default=True,
352 margin: FloatProperty(
353 name="Margin",
354 description="Gaps for the fracture (gives more stable physics)",
355 min=0.0, max=1.0,
356 default=0.001,
359 material_index: IntProperty(
360 name="Material",
361 description="Material index for interior faces",
362 default=0,
365 use_interior_vgroup: BoolProperty(
366 name="Interior VGroup",
367 description="Create a vertex group for interior verts",
368 default=False,
371 # -------------------------------------------------------------------------
372 # Physics Options
374 mass_mode: EnumProperty(
375 name="Mass Mode",
376 items=(
377 ('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
378 ('UNIFORM', "Uniform", "All objects get the specified mass"),
380 default='VOLUME',
383 mass: FloatProperty(
384 name="Mass",
385 description="Mass to give created objects",
386 min=0.001, max=1000.0,
387 default=1.0,
390 # -------------------------------------------------------------------------
391 # Object Options
393 use_recenter: BoolProperty(
394 name="Recenter",
395 description="Recalculate the center points after splitting",
396 default=True,
399 use_remove_original: BoolProperty(
400 name="Remove Original",
401 description="Removes the parents used to create the shatter",
402 default=True,
405 # -------------------------------------------------------------------------
406 # Scene Options
408 # .. different from object options in that this controls how the objects
409 # are setup in the scene.
411 collection_name: StringProperty(
412 name="Collection",
413 description=(
414 "Create objects in a collection "
415 "(use existing or create new)"
419 # -------------------------------------------------------------------------
420 # Debug
421 use_debug_points: BoolProperty(
422 name="Debug Points",
423 description="Create mesh data showing the points used for fracture",
424 default=False,
427 use_debug_redraw: BoolProperty(
428 name="Show Progress Realtime",
429 description="Redraw as fracture is done",
430 default=True,
433 use_debug_bool: BoolProperty(
434 name="Debug Boolean",
435 description="Skip applying the boolean modifier",
436 default=False,
439 def execute(self, context):
440 keywords = self.as_keywords() # ignore=("blah",)
442 main(context, **keywords)
444 return {'FINISHED'}
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):
452 layout = self.layout
453 box = layout.box()
454 col = box.column()
455 col.label(text="Point Source")
456 rowsub = col.row()
457 rowsub.prop(self, "source")
458 rowsub = col.row()
459 rowsub.prop(self, "source_limit")
460 rowsub.prop(self, "source_noise")
461 rowsub = col.row()
462 rowsub.prop(self, "cell_scale")
464 box = layout.box()
465 col = box.column()
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")
471 rowsub = col.row()
472 rowsub.prop(self, "recursion_chance")
473 rowsub.prop(self, "recursion_chance_select", expand=True)
475 box = layout.box()
476 col = box.column()
477 col.label(text="Mesh Data")
478 rowsub = col.row()
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")
483 rowsub = col.row()
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")
493 box = layout.box()
494 col = box.column()
495 col.label(text="Physics")
496 rowsub = col.row(align=True)
497 rowsub.prop(self, "mass_mode")
498 rowsub.prop(self, "mass")
500 box = layout.box()
501 col = box.column()
502 col.label(text="Object")
503 rowsub = col.row(align=True)
504 rowsub.prop(self, "use_recenter")
506 box = layout.box()
507 col = box.column()
508 col.label(text="Scene")
509 rowsub = col.row(align=True)
510 rowsub.prop(self, "collection_name")
512 box = layout.box()
513 col = box.column()
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):
522 layout = self.layout
523 layout.separator()
524 layout.operator("object.add_fracture_cell_objects", text="Cell Fracture")
527 def register():
528 bpy.utils.register_class(FractureCell)
529 bpy.types.VIEW3D_MT_object_quick_effects.append(menu_func)
532 def unregister():
533 bpy.utils.unregister_class(FractureCell)
534 bpy.types.VIEW3D_MT_object_quick_effects.remove(menu_func)
537 if __name__ == "__main__":
538 register()