Cleanup: quiet character escape warnings
[blender-addons.git] / object_fracture_cell / __init__.py
blobea4f0d2b8d0ff3cfcd468950e3171cac5c831f00
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 #####
19 bl_info = {
20 "name": "Cell Fracture",
21 "author": "ideasman42, phymec, Sergey Sharybin",
22 "version": (0, 2),
23 "blender": (2, 80, 0),
24 "location": "Viewport Object Menu -> Quick Effects",
25 "description": "Fractured Object Creation",
26 "warning": "",
27 "doc_url": "{BLENDER_MANUAL_URL}/addons/object/cell_fracture.html",
28 "category": "Object",
32 #if "bpy" in locals():
33 # import importlib
34 # importlib.reload(fracture_cell_setup)
36 import bpy
37 from bpy.props import (
38 StringProperty,
39 BoolProperty,
40 IntProperty,
41 FloatProperty,
42 FloatVectorProperty,
43 EnumProperty,
46 from bpy.types import Operator
48 def main_object(context, collection, obj, level, **kw):
49 import random
51 # pull out some args
52 kw_copy = kw.copy()
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")
66 scene = context.scene
68 if level != 0:
69 kw_copy["source_limit"] = recursion_source_limit
71 from . import fracture_cell_setup
73 # not essential but selection is visual distraction.
74 obj.select_set(False)
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"],
87 level=level,
90 # must apply after boolean.
91 if use_recenter:
92 bpy.ops.object.origin_set(
93 {"selected_editable_objects": objects},
94 type='ORIGIN_GEOMETRY',
95 center='MEDIAN',
98 #----------
99 # Recursion
100 if level == 0:
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])
113 ).length_squared)
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)
135 del objects[i]
136 if recursion_clamp and len(objects) + len(objects_recursive) >= recursion_clamp:
137 break
138 objects.extend(objects_recursive)
140 if recursion_clamp and len(objects) > recursion_clamp:
141 break
143 #--------------
144 # Level Options
145 if level == 0:
146 # import pdb; pdb.set_trace()
147 if use_interior_vgroup or use_sharp_edges:
148 fracture_cell_setup.cell_fracture_interior_handle(
149 objects,
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
158 # testing only!
159 # obj.hide = True
160 return objects
163 def main(context, **kw):
164 import time
165 t = time.time()
166 objects_context = context.selected_editable_objects
168 kw_copy = kw.copy()
170 # mass
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
176 if collection_name:
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
184 objects = []
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
197 if rb is not None:
198 rb.mass = mass
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))
204 max_co = -min_co
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]
221 volume = x * y * z
222 return volume
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
233 if rb is not None:
234 rb.mass = obj_volume_ls[i] * mass_fac
235 else:
236 assert(0)
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 # -------------------------------------------------------------------------
247 # Source Options
248 source: EnumProperty(
249 name="Source",
250 items=(
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 "
255 "source object"
257 ('PARTICLE_CHILD', "Child Particles", (
258 "All particle systems of the "
259 "child objects"
261 ('PENCIL', "Annotation Pencil", "Annotation Grease Pencil."),
263 options={'ENUM_FLAG'},
264 default={'PARTICLE_OWN'},
267 source_limit: IntProperty(
268 name="Source Limit",
269 description="Limit the number of input points, 0 for unlimited",
270 min=0, max=5000,
271 default=100,
274 source_noise: FloatProperty(
275 name="Noise",
276 description="Randomize point distribution",
277 min=0.0, max=1.0,
278 default=0.0,
281 cell_scale: FloatVectorProperty(
282 name="Scale",
283 description="Scale Cell Shape",
284 size=3,
285 min=0.0, max=1.0,
286 default=(1.0, 1.0, 1.0),
289 # -------------------------------------------------------------------------
290 # Recursion
292 recursion: IntProperty(
293 name="Recursion",
294 description="Break shards recursively",
295 min=0, max=5000,
296 default=0,
299 recursion_source_limit: IntProperty(
300 name="Source Limit",
301 description="Limit the number of input points, 0 for unlimited (applies to recursion only)",
302 min=0, max=5000,
303 default=8,
306 recursion_clamp: IntProperty(
307 name="Clamp Recursion",
308 description=(
309 "Finish recursion when this number of objects is reached "
310 "(prevents recursing for extended periods of time), zero disables"
312 min=0, max=10000,
313 default=250,
316 recursion_chance: FloatProperty(
317 name="Random Factor",
318 description="Likelihood of recursion",
319 min=0.0, max=1.0,
320 default=0.25,
323 recursion_chance_select: EnumProperty(
324 name="Recurse Over",
325 items=(
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"),
332 default='SIZE_MIN',
335 # -------------------------------------------------------------------------
336 # Mesh Data Options
338 use_smooth_faces: BoolProperty(
339 name="Smooth Interior",
340 description="Smooth Faces of inner side",
341 default=False,
344 use_sharp_edges: BoolProperty(
345 name="Sharp Edges",
346 description="Set sharp edges when disabled",
347 default=True,
350 use_sharp_edges_apply: BoolProperty(
351 name="Apply Split Edge",
352 description="Split sharp hard edges",
353 default=True,
356 use_data_match: BoolProperty(
357 name="Match Data",
358 description="Match original mesh materials and data layers",
359 default=True,
362 use_island_split: BoolProperty(
363 name="Split Islands",
364 description="Split disconnected meshes",
365 default=True,
368 margin: FloatProperty(
369 name="Margin",
370 description="Gaps for the fracture (gives more stable physics)",
371 min=0.0, max=1.0,
372 default=0.001,
375 material_index: IntProperty(
376 name="Material",
377 description="Material index for interior faces",
378 default=0,
381 use_interior_vgroup: BoolProperty(
382 name="Interior VGroup",
383 description="Create a vertex group for interior verts",
384 default=False,
387 # -------------------------------------------------------------------------
388 # Physics Options
390 mass_mode: EnumProperty(
391 name="Mass Mode",
392 items=(
393 ('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
394 ('UNIFORM', "Uniform", "All objects get the specified mass"),
396 default='VOLUME',
399 mass: FloatProperty(
400 name="Mass",
401 description="Mass to give created objects",
402 min=0.001, max=1000.0,
403 default=1.0,
407 # -------------------------------------------------------------------------
408 # Object Options
410 use_recenter: BoolProperty(
411 name="Recenter",
412 description="Recalculate the center points after splitting",
413 default=True,
416 use_remove_original: BoolProperty(
417 name="Remove Original",
418 description="Removes the parents used to create the shatter",
419 default=True,
422 # -------------------------------------------------------------------------
423 # Scene Options
425 # .. different from object options in that this controls how the objects
426 # are setup in the scene.
428 collection_name: StringProperty(
429 name="Collection",
430 description=(
431 "Create objects in a collection "
432 "(use existing or create new)"
436 # -------------------------------------------------------------------------
437 # Debug
438 use_debug_points: BoolProperty(
439 name="Debug Points",
440 description="Create mesh data showing the points used for fracture",
441 default=False,
444 use_debug_redraw: BoolProperty(
445 name="Show Progress Realtime",
446 description="Redraw as fracture is done",
447 default=True,
450 use_debug_bool: BoolProperty(
451 name="Debug Boolean",
452 description="Skip applying the boolean modifier",
453 default=False,
456 def execute(self, context):
457 keywords = self.as_keywords() # ignore=("blah",)
459 main(context, **keywords)
461 return {'FINISHED'}
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):
470 layout = self.layout
471 box = layout.box()
472 col = box.column()
473 col.label(text="Point Source")
474 rowsub = col.row()
475 rowsub.prop(self, "source")
476 rowsub = col.row()
477 rowsub.prop(self, "source_limit")
478 rowsub.prop(self, "source_noise")
479 rowsub = col.row()
480 rowsub.prop(self, "cell_scale")
482 box = layout.box()
483 col = box.column()
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")
489 rowsub = col.row()
490 rowsub.prop(self, "recursion_chance")
491 rowsub.prop(self, "recursion_chance_select", expand=True)
493 box = layout.box()
494 col = box.column()
495 col.label(text="Mesh Data")
496 rowsub = col.row()
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")
501 rowsub = col.row()
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")
512 box = layout.box()
513 col = box.column()
514 col.label(text="Physics")
515 rowsub = col.row(align=True)
516 rowsub.prop(self, "mass_mode")
517 rowsub.prop(self, "mass")
520 box = layout.box()
521 col = box.column()
522 col.label(text="Object")
523 rowsub = col.row(align=True)
524 rowsub.prop(self, "use_recenter")
527 box = layout.box()
528 col = box.column()
529 col.label(text="Scene")
530 rowsub = col.row(align=True)
531 rowsub.prop(self, "collection_name")
533 box = layout.box()
534 col = box.column()
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):
543 layout = self.layout
544 layout.separator()
545 layout.operator("object.add_fracture_cell_objects", text="Cell Fracture")
548 def register():
549 bpy.utils.register_class(FractureCell)
550 bpy.types.VIEW3D_MT_object_quick_effects.append(menu_func)
553 def unregister():
554 bpy.utils.unregister_class(FractureCell)
555 bpy.types.VIEW3D_MT_object_quick_effects.remove(menu_func)
558 if __name__ == "__main__":
559 register()