Revert changes to 'object_fracture_cell'

This reverts commits:
daaff4199b.
64d8956792.
fffaf5d275.
f7c91d3382.

These changes removed/added functionality
beyond the scope of porting 2.7x to 2.8x,
where only minor changes were needed.
This commit is contained in:
Campbell Barton 2019-07-07 11:19:05 +10:00
parent 8f4c893499
commit 3f68f8442a
15 changed files with 1554 additions and 1771 deletions

View File

@ -18,23 +18,20 @@
bl_info = {
"name": "Cell Fracture",
"author": "ideasman42, phymec, Sergey Sharybin, Nobuyuki Hirakata",
"version": (1, 0, 2),
"blender": (2, 80, 0),
"location": "View3D > Sidebar > Transform tab",
"description": "Fractured Object, or Cracked Surface",
"warning": "Work in Progress",
"author": "ideasman42, phymec, Sergey Sharybin",
"version": (0, 1),
"blender": (2, 70, 0),
"location": "Edit panel of Tools tab, in Object mode, 3D View tools",
"description": "Fractured Object, Bomb, Projectile, Recorder",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Object/CellFracture",
"category": "Object"}
if "bpy" in locals():
import importlib
importlib.reload(operator)
else:
from . import operator
#if "bpy" in locals():
# import importlib
# importlib.reload(fracture_cell_setup)
import bpy
from bpy.props import (
@ -44,114 +41,244 @@ from bpy.props import (
FloatProperty,
FloatVectorProperty,
EnumProperty,
BoolVectorProperty,
PointerProperty,
)
from bpy.types import (
Panel,
PropertyGroup,
)
from bpy.types import Operator
def main_object(context, obj, level, **kw):
import random
# pull out some args
kw_copy = kw.copy()
use_recenter = kw_copy.pop("use_recenter")
use_remove_original = kw_copy.pop("use_remove_original")
recursion = kw_copy.pop("recursion")
recursion_source_limit = kw_copy.pop("recursion_source_limit")
recursion_clamp = kw_copy.pop("recursion_clamp")
recursion_chance = kw_copy.pop("recursion_chance")
recursion_chance_select = kw_copy.pop("recursion_chance_select")
use_layer_next = kw_copy.pop("use_layer_next")
use_layer_index = kw_copy.pop("use_layer_index")
group_name = kw_copy.pop("group_name")
use_island_split = kw_copy.pop("use_island_split")
use_debug_bool = kw_copy.pop("use_debug_bool")
use_interior_vgroup = kw_copy.pop("use_interior_vgroup")
use_sharp_edges = kw_copy.pop("use_sharp_edges")
use_sharp_edges_apply = kw_copy.pop("use_sharp_edges_apply")
collection = context.collection
if level != 0:
kw_copy["source_limit"] = recursion_source_limit
from . import fracture_cell_setup
# not essential but selection is visual distraction.
obj.select_set(False)
if kw_copy["use_debug_redraw"]:
obj_display_type_prev = obj.display_type
obj.display_type = 'WIRE'
objects = fracture_cell_setup.cell_fracture_objects(context, obj, **kw_copy)
objects = fracture_cell_setup.cell_fracture_boolean(context, obj, objects,
use_island_split=use_island_split,
use_interior_hide=(use_interior_vgroup or use_sharp_edges),
use_debug_bool=use_debug_bool,
use_debug_redraw=kw_copy["use_debug_redraw"],
level=level,
)
# must apply after boolean.
if use_recenter:
bpy.ops.object.origin_set({"selected_editable_objects": objects},
type='ORIGIN_GEOMETRY', center='MEDIAN')
if level == 0:
for level_sub in range(1, recursion + 1):
objects_recurse_input = [(i, o) for i, o in enumerate(objects)]
if recursion_chance != 1.0:
from mathutils import Vector
if recursion_chance_select == 'RANDOM':
random.shuffle(objects_recurse_input)
elif recursion_chance_select in {'SIZE_MIN', 'SIZE_MAX'}:
objects_recurse_input.sort(key=lambda ob_pair:
(Vector(ob_pair[1].bound_box[0]) -
Vector(ob_pair[1].bound_box[6])).length_squared)
if recursion_chance_select == 'SIZE_MAX':
objects_recurse_input.reverse()
elif recursion_chance_select in {'CURSOR_MIN', 'CURSOR_MAX'}:
c = scene.cursor.location.copy()
objects_recurse_input.sort(key=lambda ob_pair:
(ob_pair[1].location - c).length_squared)
if recursion_chance_select == 'CURSOR_MAX':
objects_recurse_input.reverse()
objects_recurse_input[int(recursion_chance * len(objects_recurse_input)):] = []
objects_recurse_input.sort()
# reverse index values so we can remove from original list.
objects_recurse_input.reverse()
objects_recursive = []
for i, obj_cell in objects_recurse_input:
assert(objects[i] is obj_cell)
objects_recursive += main_object(context, obj_cell, level_sub, **kw)
if use_remove_original:
collection.objects.unlink(obj_cell)
del objects[i]
if recursion_clamp and len(objects) + len(objects_recursive) >= recursion_clamp:
break
objects.extend(objects_recursive)
if recursion_clamp and len(objects) > recursion_clamp:
break
#--------------
# Level Options
if level == 0:
# import pdb; pdb.set_trace()
if use_interior_vgroup or use_sharp_edges:
fracture_cell_setup.cell_fracture_interior_handle(objects,
use_interior_vgroup=use_interior_vgroup,
use_sharp_edges=use_sharp_edges,
use_sharp_edges_apply=use_sharp_edges_apply,
)
#--------------
# Scene Options
# layer
layers_new = None
if use_layer_index != 0:
layers_new = [False] * 20
layers_new[use_layer_index - 1] = True
elif use_layer_next:
layers_new = [False] * 20
layers_new[(obj.layers[:].index(True) + 1) % 20] = True
if layers_new is not None:
for obj_cell in objects:
obj_cell.layers = layers_new
# group
if group_name:
group = bpy.data.collections.get(group_name)
if group is None:
group = bpy.data.collections.new(group_name)
group_objects = group.objects[:]
for obj_cell in objects:
if obj_cell not in group_objects:
group.objects.link(obj_cell)
if kw_copy["use_debug_redraw"]:
obj.display_type = obj_display_type_prev
# testing only!
# obj.hide = True
return objects
class FRACTURE_PT_Menu(Panel):
bl_idname = 'FRACTURE_PT_Menu'
bl_label = "Fracture Cell"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Create"
bl_context = 'objectmode'
bl_options = {"DEFAULT_CLOSED"}
def main(context, **kw):
import time
t = time.time()
objects_context = context.selected_editable_objects
def draw(self, context):
# Show pop-upped menu when the button is hit.
layout = self.layout
#layout.label(text="Cell Fracture:")
layout.operator(operator.FRACTURE_OT_Cell.bl_idname,
text="1. Cell Fracture")
layout.operator(operator.FRACTURE_OT_Crack.bl_idname,
text="2. Cell to Crack")
kw_copy = kw.copy()
material_props = context.window_manager.fracture_material_props
layout.separator()
box = layout.box()
col = box.column()
row = col.row(align=True)
row.label(text="Material Preset:")
'''
row_sub = row.row()
row_sub.prop(material_props, "material_lib_name", text="",
toggle=True, icon="LONGDISPLAY")
'''
row = box.row()
row.prop(material_props, "material_preset", text="")
# mass
mass_mode = kw_copy.pop("mass_mode")
mass = kw_copy.pop("mass")
row = box.row()
row.operator(operator.FRACTURE_OT_Material.bl_idname, icon="MATERIAL_DATA",
text="Append Material")
objects = []
for obj in objects_context:
if obj.type == 'MESH':
objects += main_object(context, obj, 0, **kw_copy)
bpy.ops.object.select_all(action='DESELECT')
for obj_cell in objects:
obj_cell.select_set(True)
if mass_mode == 'UNIFORM':
for obj_cell in objects:
obj_cell.game.mass = mass
elif mass_mode == 'VOLUME':
from mathutils import Vector
def _get_volume(obj_cell):
def _getObjectBBMinMax():
min_co = Vector((1000000.0, 1000000.0, 1000000.0))
max_co = -min_co
matrix = obj_cell.matrix_world
for i in range(0, 8):
bb_vec = obj_cell.matrix_world * Vector(obj_cell.bound_box[i])
min_co[0] = min(bb_vec[0], min_co[0])
min_co[1] = min(bb_vec[1], min_co[1])
min_co[2] = min(bb_vec[2], min_co[2])
max_co[0] = max(bb_vec[0], max_co[0])
max_co[1] = max(bb_vec[1], max_co[1])
max_co[2] = max(bb_vec[2], max_co[2])
return (min_co, max_co)
def _getObjectVolume():
min_co, max_co = _getObjectBBMinMax()
x = max_co[0] - min_co[0]
y = max_co[1] - min_co[1]
z = max_co[2] - min_co[2]
volume = x * y * z
return volume
return _getObjectVolume()
class FractureCellProperties(PropertyGroup):
obj_volume_ls = [_get_volume(obj_cell) for obj_cell in objects]
obj_volume_tot = sum(obj_volume_ls)
if obj_volume_tot > 0.0:
mass_fac = mass / obj_volume_tot
for i, obj_cell in enumerate(objects):
obj_cell.game.mass = obj_volume_ls[i] * mass_fac
else:
assert(0)
print("Done! %d objects in %.4f sec" % (len(objects), time.time() - t))
class FractureCell(Operator):
bl_idname = "object.add_fracture_cell_objects"
bl_label = "Cell fracture selected mesh objects"
bl_options = {'PRESET'}
# -------------------------------------------------------------------------
# Source Options
source_vert_own: IntProperty(
name="Own Verts",
description="Use own vertices",
min=0, max=2000,
default=100,
source: EnumProperty(
name="Source",
items=(('VERT_OWN', "Own Verts", "Use own vertices"),
('VERT_CHILD', "Child Verts", "Use child object vertices"),
('PARTICLE_OWN', "Own Particles", ("All particle systems of the "
"source object")),
('PARTICLE_CHILD', "Child Particles", ("All particle systems of the "
"child objects")),
('PENCIL', "Grease Pencil", "This object's grease pencil"),
),
options={'ENUM_FLAG'},
default={'PARTICLE_OWN'},
)
source_vert_child: IntProperty(
name="Child Verts",
description="Use child object vertices",
min=0, max=2000,
default=0,
)
source_particle_own: IntProperty(
name="Own Particles",
description="All particle systems of the source object",
min=0, max=2000,
default=0,
)
source_particle_child: IntProperty(
name="Child Particles",
description="All particle systems of the child objects",
min=0, max=2000,
default=0,
)
source_pencil: IntProperty(
name="Annotation Pencil",
description="Annotation Grease Pencil",
min=0, max=2000,
default=0,
)
source_random: IntProperty(
name="Random",
description="Random seed position",
min=0, max=2000,
default=0,
)
'''
source_limit: IntProperty(
name="Source Limit",
description="Limit the number of input points, 0 for unlimited",
min=0, max=5000,
default=100,
)
'''
# -------------------------------------------------------------------------
# Transform
source_noise: FloatProperty(
name="Noise",
description="Randomize point distribution",
min=0.0, max=1.0,
default=0.0,
)
margin: FloatProperty(
name="Margin",
description="Gaps for the fracture (gives more stable physics)",
min=0.0, max=1.0,
default=0.001,
)
cell_scale: FloatVectorProperty(
name="Scale",
description="Scale Cell Shape",
@ -159,51 +286,40 @@ class FractureCellProperties(PropertyGroup):
min=0.0, max=1.0,
default=(1.0, 1.0, 1.0),
)
pre_simplify : FloatProperty(
name="Simplify Base Mesh",
description="Simplify base mesh before making cell. Lower face size, faster calculation",
default=0.00,
min=0.00,
max=1.00
)
use_recenter: BoolProperty(
name="Recenter",
description="Recalculate the center points after splitting",
default=True,
)
use_island_split: BoolProperty(
name="Split Islands",
description="Split disconnected meshes",
default=True,
)
# -------------------------------------------------------------------------
# Recursion
recursion: IntProperty(
name="Recursion",
description="Break shards recursively",
min=0, max=2000,
min=0, max=5000,
default=0,
)
recursion_source_limit: IntProperty(
name="Fracture Each",
name="Source Limit",
description="Limit the number of input points, 0 for unlimited (applies to recursion only)",
min=2, max=2000, # Oviously, dividing in more than two objects is needed, to avoid no fracture object.
min=0, max=5000,
default=8,
)
recursion_clamp: IntProperty(
name="Max Fracture",
name="Clamp Recursion",
description="Finish recursion when this number of objects is reached (prevents recursing for extended periods of time), zero disables",
min=0, max=10000,
default=250,
)
recursion_chance: FloatProperty(
name="Rec Chance",
name="Random Factor",
description="Likelihood of recursion",
min=0.0, max=1.0,
default=1.00,
default=0.25,
)
recursion_chance_select: EnumProperty(
name="Target",
name="Recurse Over",
items=(('RANDOM', "Random", ""),
('SIZE_MIN', "Small", "Recursively subdivide smaller objects"),
('SIZE_MAX', "Big", "Recursively subdivide bigger objects"),
@ -212,77 +328,61 @@ class FractureCellProperties(PropertyGroup):
),
default='SIZE_MIN',
)
# -------------------------------------------------------------------------
# Interior Meshes Options
# Mesh Data Options
use_smooth_faces: BoolProperty(
name="Smooth Faces",
description="Smooth Faces of inner side",
default=False,
)
use_sharp_edges: BoolProperty(
name="Mark Sharp Edges",
name="Sharp Edges",
description="Set sharp edges when disabled",
default=False,
default=True,
)
use_sharp_edges_apply: BoolProperty(
name="Edge Split Modifier",
description="Add edge split modofier for sharp edges",
default=False,
name="Apply Split Edge",
description="Split sharp hard edges",
default=True,
)
use_data_match: BoolProperty(
name="Copy Original Data",
name="Match Data",
description="Match original mesh materials and data layers",
default=True,
)
use_island_split: BoolProperty(
name="Split Islands",
description="Split disconnected meshes",
default=True,
)
margin: FloatProperty(
name="Margin",
description="Gaps for the fracture (gives more stable physics)",
min=0.0, max=1.0,
default=0.001,
)
material_index: IntProperty(
name="Interior Material Slot",
name="Material",
description="Material index for interior faces",
default=0,
)
use_interior_vgroup: BoolProperty(
name="Vertex Group",
name="Interior VGroup",
description="Create a vertex group for interior verts",
default=False,
)
# -------------------------------------------------------------------------
# Scene Options
use_collection: BoolProperty(
name="Use Collection",
description="Use collection to organize fracture objects",
default=True,
)
new_collection: BoolProperty(
name="Use New",
description="Make new collection for fracture objects",
default=True,
)
collection_name: StringProperty(
name="Name",
description="Collection name.",
default="Fracture",
)
original_hide: BoolProperty(
name="Hide Original",
description="Hide original object after cell fracture.",
default=False,
)
cell_relocate : BoolProperty(
name="Move Beside Original",
description="Move cells beside the original object.",
default=False,
)
# -------------------------------------------------------------------------
# Custom Property Options
use_mass: BoolProperty(
name="Mass",
description="Append mass data on custom properties of cell objects.",
default=False,
)
mass_name: StringProperty(
name="Property Name",
description="Name for custome properties.",
default="mass",
)
# Physics Options
mass_mode: EnumProperty(
name="Mass Mode",
items=(('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
@ -290,12 +390,55 @@ class FractureCellProperties(PropertyGroup):
),
default='VOLUME',
)
mass: FloatProperty(
name="Mass Factor",
name="Mass",
description="Mass to give created objects",
min=0.001, max=1000.0,
default=1.0,
)
# -------------------------------------------------------------------------
# Object Options
use_recenter: BoolProperty(
name="Recenter",
description="Recalculate the center points after splitting",
default=True,
)
use_remove_original: BoolProperty(
name="Remove Original",
description="Removes the parents used to create the shatter",
default=True,
)
# -------------------------------------------------------------------------
# Scene Options
#
# .. different from object options in that this controls how the objects
# are setup in the scene.
use_layer_index: IntProperty(
name="Layer Index",
description="Layer to add the objects into or 0 for existing",
default=0,
min=0, max=20,
)
use_layer_next: BoolProperty(
name="Next Layer",
description="At the object into the next layer (layer index overrides)",
default=True,
)
group_name: StringProperty(
name="Group",
description="Create objects int a group "
"(use existing or create new)",
)
# -------------------------------------------------------------------------
# Debug
use_debug_points: BoolProperty(
@ -307,7 +450,7 @@ class FractureCellProperties(PropertyGroup):
use_debug_redraw: BoolProperty(
name="Show Progress Realtime",
description="Redraw as fracture is done",
default=False,
default=True,
)
use_debug_bool: BoolProperty(
@ -316,109 +459,110 @@ class FractureCellProperties(PropertyGroup):
default=False,
)
def execute(self, context):
keywords = self.as_keywords() # ignore=("blah",)
class FractureCrackProperties(PropertyGroup):
modifier_decimate : FloatProperty(
name="Reduce Faces",
description="Apply Decimate Modifier to reduce face number",
default=0.40,
min=0.00,
max=1.00
)
modifier_smooth : FloatProperty(
name="Loose | Tight",
description="Smooth Modifier",
default=-0.50,
min=-3.00,
max=3.00
)
extrude_scale : FloatProperty(
name="Extrude Blob",
description="Extrude Scale",
default=0.00,
min=0.00,
max=5.00
)
extrude_var : FloatProperty(
name="Extrude Random ",
description="Extrude Varriant",
default=0.01,
min=-4.00,
max=4.00
)
extrude_num : IntProperty(
name="Extrude Num",
description="Extrude Number",
default=1,
min=0,
max=10
)
modifier_wireframe : BoolProperty(
name="Wireframe Modifier",
description="Wireframe Modifier",
default=False
)
main(context, **keywords)
return {'FINISHED'}
class FractureMaterialProperties(PropertyGroup):
# Note: you can choose the original name in the library blend
# or the prop name
material_preset : EnumProperty(
name="Preset",
description="Material Preset",
items=[
('crackit_organic_mud', "Organic Mud", "Mud material"),
('crackit_mud', "Mud", "Mud material"),
('crackit_tree_moss', "Tree Moss", "Tree Material"),
('crackit_tree_dry', "Tree Dry", "Tree Material"),
('crackit_tree_red', "Tree Red", "Tree Material"),
('crackit_rock', "Rock", "Rock Material"),
('crackit_lava', "Lava", "Lava Material"),
('crackit_wet-paint', "Wet Paint", "Paint Material"),
('crackit_soap', "Soap", "Soap Material"),
]
)
material_lib_name : BoolProperty(
name="Library Name",
description="Use the original Material name from the .blend library\n"
"instead of the one defined in the Preset",
default=True
)
def invoke(self, context, event):
print(self.recursion_chance_select)
wm = context.window_manager
return wm.invoke_props_dialog(self, width=600)
def draw(self, context):
layout = self.layout
box = layout.box()
col = box.column()
col.label(text="Point Source")
rowsub = col.row()
rowsub.prop(self, "source")
rowsub = col.row()
rowsub.prop(self, "source_limit")
rowsub.prop(self, "source_noise")
rowsub = col.row()
rowsub.prop(self, "cell_scale")
box = layout.box()
col = box.column()
col.label(text="Recursive Shatter")
rowsub = col.row(align=True)
rowsub.prop(self, "recursion")
rowsub.prop(self, "recursion_source_limit")
rowsub.prop(self, "recursion_clamp")
rowsub = col.row()
rowsub.prop(self, "recursion_chance")
rowsub.prop(self, "recursion_chance_select", expand=True)
box = layout.box()
col = box.column()
col.label(text="Mesh Data")
rowsub = col.row()
rowsub.prop(self, "use_smooth_faces")
rowsub.prop(self, "use_sharp_edges")
rowsub.prop(self, "use_sharp_edges_apply")
rowsub.prop(self, "use_data_match")
rowsub = col.row()
# on same row for even layout but infact are not all that related
rowsub.prop(self, "material_index")
rowsub.prop(self, "use_interior_vgroup")
# could be own section, control how we subdiv
rowsub.prop(self, "margin")
rowsub.prop(self, "use_island_split")
box = layout.box()
col = box.column()
col.label(text="Physics")
rowsub = col.row(align=True)
rowsub.prop(self, "mass_mode")
rowsub.prop(self, "mass")
box = layout.box()
col = box.column()
col.label(text="Object")
rowsub = col.row(align=True)
rowsub.prop(self, "use_recenter")
box = layout.box()
col = box.column()
col.label(text="Scene")
rowsub = col.row(align=True)
rowsub.prop(self, "use_layer_index")
rowsub.prop(self, "use_layer_next")
rowsub.prop(self, "group_name")
box = layout.box()
col = box.column()
col.label(text="Debug")
rowsub = col.row(align=True)
rowsub.prop(self, "use_debug_redraw")
rowsub.prop(self, "use_debug_points")
rowsub.prop(self, "use_debug_bool")
def menu_func(self, context):
layout = self.layout
layout.label(text="Cell Fracture:")
layout.operator("object.add_fracture_cell_objects",
text="Cell Fracture")
classes = (
FractureCellProperties,
FractureCrackProperties,
FractureMaterialProperties,
operator.FRACTURE_OT_Cell,
operator.FRACTURE_OT_Crack,
operator.FRACTURE_OT_Material,
FRACTURE_PT_Menu,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
bpy.utils.register_class(FractureCell)
bpy.types.VIEW3D_PT_tools_object.append(menu_func)
bpy.types.WindowManager.fracture_cell_props = PointerProperty(
type=FractureCellProperties
)
bpy.types.WindowManager.fracture_crack_props = PointerProperty(
type=FractureCrackProperties
)
bpy.types.WindowManager.fracture_material_props = PointerProperty(
type=FractureMaterialProperties
)
def unregister():
del bpy.types.WindowManager.fracture_material_props
del bpy.types.WindowManager.fracture_crack_props
del bpy.types.WindowManager.fracture_cell_props
bpy.utils.unregister_class(FractureCell)
bpy.types.VIEW3D_PT_tools_object.remove(menu_func)
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
if __name__ == "__main__":
register()
register()

View File

@ -0,0 +1,120 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) Blender Foundation 2012
def points_as_bmesh_cells(verts,
points,
points_scale=None,
margin_bounds=0.05,
margin_cell=0.0):
from math import sqrt
import mathutils
from mathutils import Vector
cells = []
points_sorted_current = [p for p in points]
plane_indices = []
vertices = []
if points_scale is not None:
points_scale = tuple(points_scale)
if points_scale == (1.0, 1.0, 1.0):
points_scale = None
# there are many ways we could get planes - convex hull for eg
# but it ends up fastest if we just use bounding box
if 1:
xa = [v[0] for v in verts]
ya = [v[1] for v in verts]
za = [v[2] for v in verts]
xmin, xmax = min(xa) - margin_bounds, max(xa) + margin_bounds
ymin, ymax = min(ya) - margin_bounds, max(ya) + margin_bounds
zmin, zmax = min(za) - margin_bounds, max(za) + margin_bounds
convexPlanes = [
Vector((+1.0, 0.0, 0.0, -xmax)),
Vector((-1.0, 0.0, 0.0, +xmin)),
Vector((0.0, +1.0, 0.0, -ymax)),
Vector((0.0, -1.0, 0.0, +ymin)),
Vector((0.0, 0.0, +1.0, -zmax)),
Vector((0.0, 0.0, -1.0, +zmin)),
]
for i, point_cell_current in enumerate(points):
planes = [None] * len(convexPlanes)
for j in range(len(convexPlanes)):
planes[j] = convexPlanes[j].copy()
planes[j][3] += planes[j].xyz.dot(point_cell_current)
distance_max = 10000000000.0 # a big value!
points_sorted_current.sort(key=lambda p: (p - point_cell_current).length_squared)
for j in range(1, len(points)):
normal = points_sorted_current[j] - point_cell_current
nlength = normal.length
if points_scale is not None:
normal_alt = normal.copy()
normal_alt.x *= points_scale[0]
normal_alt.y *= points_scale[1]
normal_alt.z *= points_scale[2]
# rotate plane to new distance
# should always be positive!! - but abs incase
scalar = normal_alt.normalized().dot(normal.normalized())
# assert(scalar >= 0.0)
nlength *= scalar
normal = normal_alt
if nlength > distance_max:
break
plane = normal.normalized()
plane.resize_4d()
plane[3] = (-nlength / 2.0) + margin_cell
planes.append(plane)
vertices[:], plane_indices[:] = mathutils.geometry.points_in_planes(planes)
if len(vertices) == 0:
break
if len(plane_indices) != len(planes):
planes[:] = [planes[k] for k in plane_indices]
# for comparisons use length_squared and delay
# converting to a real length until the end.
distance_max = 10000000000.0 # a big value!
for v in vertices:
distance = v.length_squared
if distance_max < distance:
distance_max = distance
distance_max = sqrt(distance_max) # make real length
distance_max *= 2.0
if len(vertices) == 0:
continue
cells.append((point_cell_current, vertices[:]))
del vertices[:]
return cells

View File

@ -0,0 +1,459 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) Blender Foundation 2012
import bpy
import bmesh
def _redraw_yasiamevil():
_redraw_yasiamevil.opr(**_redraw_yasiamevil.arg)
_redraw_yasiamevil.opr = bpy.ops.wm.redraw_timer
_redraw_yasiamevil.arg = dict(type='DRAW_WIN_SWAP', iterations=1)
def _points_from_object(obj, source):
_source_all = {
'PARTICLE_OWN', 'PARTICLE_CHILD',
'PENCIL',
'VERT_OWN', 'VERT_CHILD',
}
print(source - _source_all)
print(source)
assert(len(source | _source_all) == len(_source_all))
assert(len(source))
points = []
def edge_center(mesh, edge):
v1, v2 = edge.vertices
return (mesh.vertices[v1].co + mesh.vertices[v2].co) / 2.0
def poly_center(mesh, poly):
from mathutils import Vector
co = Vector()
tot = 0
for i in poly.loop_indices:
co += mesh.vertices[mesh.loops[i].vertex_index].co
tot += 1
return co / tot
def points_from_verts(obj):
"""Takes points from _any_ object with geometry"""
if obj.type == 'MESH':
mesh = obj.data
matrix = obj.matrix_world.copy()
points.extend([matrix * v.co for v in mesh.vertices])
else:
depsgraph = bpy.context.evaluated_depsgraph_get()
ob_eval = ob.evaluated_get(depsgraph)
try:
mesh = ob_eval.to_mesh()
except:
mesh = None
if mesh is not None:
matrix = obj.matrix_world.copy()
points.extend([matrix * v.co for v in mesh.vertices])
ob_eval.to_mesh_clear()
def points_from_particles(obj):
points.extend([p.location.copy()
for psys in obj.particle_systems
for p in psys.particles])
# geom own
if 'VERT_OWN' in source:
points_from_verts(obj)
# geom children
if 'VERT_CHILD' in source:
for obj_child in obj.children:
points_from_verts(obj_child)
# geom particles
if 'PARTICLE_OWN' in source:
points_from_particles(obj)
if 'PARTICLE_CHILD' in source:
for obj_child in obj.children:
points_from_particles(obj_child)
# grease pencil
def get_points(stroke):
return [point.co.copy() for point in stroke.points]
def get_splines(gp):
if gp.layers.active:
frame = gp.layers.active.active_frame
return [get_points(stroke) for stroke in frame.strokes]
else:
return []
if 'PENCIL' in source:
gp = obj.grease_pencil
if gp:
points.extend([p for spline in get_splines(gp)
for p in spline])
print("Found %d points" % len(points))
return points
def cell_fracture_objects(context, obj,
source={'PARTICLE_OWN'},
source_limit=0,
source_noise=0.0,
clean=True,
# operator options
use_smooth_faces=False,
use_data_match=False,
use_debug_points=False,
margin=0.0,
material_index=0,
use_debug_redraw=False,
cell_scale=(1.0, 1.0, 1.0),
):
from . import fracture_cell_calc
collection = context.collection
view_layer = context.view_layer
# -------------------------------------------------------------------------
# GET POINTS
points = _points_from_object(obj, source)
if not points:
# print using fallback
points = _points_from_object(obj, {'VERT_OWN'})
if not points:
print("no points found")
return []
# apply optional clamp
if source_limit != 0 and source_limit < len(points):
import random
random.shuffle(points)
points[source_limit:] = []
# saddly we cant be sure there are no doubles
from mathutils import Vector
to_tuple = Vector.to_tuple
points = list({to_tuple(p, 4): p for p in points}.values())
del to_tuple
del Vector
# end remove doubles
# ------------------
if source_noise > 0.0:
from random import random
# boundbox approx of overall scale
from mathutils import Vector
matrix = obj.matrix_world.copy()
bb_world = [matrix * Vector(v) for v in obj.bound_box]
scalar = source_noise * ((bb_world[0] - bb_world[6]).length / 2.0)
from mathutils.noise import random_unit_vector
points[:] = [p + (random_unit_vector() * (scalar * random())) for p in points]
if use_debug_points:
bm = bmesh.new()
for p in points:
bm.verts.new(p)
mesh_tmp = bpy.data.meshes.new(name="DebugPoints")
bm.to_mesh(mesh_tmp)
bm.free()
obj_tmp = bpy.data.objects.new(name=mesh_tmp.name, object_data=mesh_tmp)
collection.objects.link(obj_tmp)
del obj_tmp, mesh_tmp
mesh = obj.data
matrix = obj.matrix_world.copy()
verts = [matrix * v.co for v in mesh.vertices]
cells = fracture_cell_calc.points_as_bmesh_cells(verts,
points,
cell_scale,
margin_cell=margin)
# some hacks here :S
cell_name = obj.name + "_cell"
objects = []
for center_point, cell_points in cells:
# ---------------------------------------------------------------------
# BMESH
# create the convex hulls
bm = bmesh.new()
# WORKAROUND FOR CONVEX HULL BUG/LIMIT
# XXX small noise
import random
def R():
return (random.random() - 0.5) * 0.001
# XXX small noise
for i, co in enumerate(cell_points):
# XXX small noise
co.x += R()
co.y += R()
co.z += R()
# XXX small noise
bm_vert = bm.verts.new(co)
import mathutils
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
try:
bmesh.ops.convex_hull(bm, input=bm.verts)
except RuntimeError:
import traceback
traceback.print_exc()
if clean:
bm.normal_update()
try:
bmesh.ops.dissolve_limit(bm, verts=bm.verts, angle_limit=0.001)
except RuntimeError:
import traceback
traceback.print_exc()
if use_smooth_faces:
for bm_face in bm.faces:
bm_face.smooth = True
if material_index != 0:
for bm_face in bm.faces:
bm_face.material_index = material_index
# ---------------------------------------------------------------------
# MESH
mesh_dst = bpy.data.meshes.new(name=cell_name)
bm.to_mesh(mesh_dst)
bm.free()
del bm
if use_data_match:
# match materials and data layers so boolean displays them
# currently only materials + data layers, could do others...
mesh_src = obj.data
for mat in mesh_src.materials:
mesh_dst.materials.append(mat)
for lay_attr in ("vertex_colors", "uv_textures"):
lay_src = getattr(mesh_src, lay_attr)
lay_dst = getattr(mesh_dst, lay_attr)
for key in lay_src.keys():
lay_dst.new(name=key)
# ---------------------------------------------------------------------
# OBJECT
obj_cell = bpy.data.objects.new(name=cell_name, object_data=mesh_dst)
collection.objects.link(obj_cell)
# scene.objects.active = obj_cell
obj_cell.location = center_point
objects.append(obj_cell)
# support for object materials
if use_data_match:
for i in range(len(mesh_dst.materials)):
slot_src = obj.material_slots[i]
slot_dst = obj_cell.material_slots[i]
slot_dst.link = slot_src.link
slot_dst.material = slot_src.material
if use_debug_redraw:
view_layer.update()
_redraw_yasiamevil()
view_layer.update()
# move this elsewhere...
for obj_cell in objects:
game = obj_cell.game
game.physics_type = 'RIGID_BODY'
game.use_collision_bounds = True
game.collision_bounds_type = 'CONVEX_HULL'
return objects
def cell_fracture_boolean(context, obj, objects,
use_debug_bool=False,
clean=True,
use_island_split=False,
use_interior_hide=False,
use_debug_redraw=False,
level=0,
remove_doubles=True
):
objects_boolean = []
collection = context.collection
scene = context.scene
view_layer = context.view_layer
depsgraph = context.evaluated_depsgraph_get()
if use_interior_hide and level == 0:
# only set for level 0
obj.data.polygons.foreach_set("hide", [False] * len(obj.data.polygons))
for obj_cell in objects:
mod = obj_cell.modifiers.new(name="Boolean", type='BOOLEAN')
mod.object = obj
mod.operation = 'INTERSECT'
if not use_debug_bool:
if use_interior_hide:
obj_cell.data.polygons.foreach_set("hide", [True] * len(obj_cell.data.polygons))
obj_cell_eval = obj_cell.evaluated_get(depsgraph)
mesh_new = bpy.data.meshes.new_from_object(obj_cell_eval)
mesh_old = obj_cell.data
obj_cell.data = mesh_new
obj_cell.modifiers.remove(mod)
# remove if not valid
if not mesh_old.users:
bpy.data.meshes.remove(mesh_old)
if not mesh_new.vertices:
collection.objects.unlink(obj_cell)
if not obj_cell.users:
bpy.data.objects.remove(obj_cell)
obj_cell = None
if not mesh_new.users:
bpy.data.meshes.remove(mesh_new)
mesh_new = None
# avoid unneeded bmesh re-conversion
if mesh_new is not None:
bm = None
if clean:
if bm is None: # ok this will always be true for now...
bm = bmesh.new()
bm.from_mesh(mesh_new)
bm.normal_update()
try:
bmesh.ops.dissolve_limit(bm, verts=bm.verts, edges=bm.edges, angle_limit=0.001)
except RuntimeError:
import traceback
traceback.print_exc()
if remove_doubles:
if bm is None:
bm = bmesh.new()
bm.from_mesh(mesh_new)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
if bm is not None:
bm.to_mesh(mesh_new)
bm.free()
del mesh_new
del mesh_old
if obj_cell is not None:
objects_boolean.append(obj_cell)
if use_debug_redraw:
_redraw_yasiamevil()
if (not use_debug_bool) and use_island_split:
# this is ugly and Im not proud of this - campbell
for ob in view_layer.objects:
ob.select_set(True)
for obj_cell in objects_boolean:
obj_cell.select_set(True)
bpy.ops.mesh.separate(type='LOOSE')
objects_boolean[:] = [obj_cell for obj_cell in scene.objects if obj_cell.select]
context.view_layer.update()
return objects_boolean
def cell_fracture_interior_handle(objects,
use_interior_vgroup=False,
use_sharp_edges=False,
use_sharp_edges_apply=False,
):
"""Run after doing _all_ booleans"""
assert(use_interior_vgroup or use_sharp_edges or use_sharp_edges_apply)
for obj_cell in objects:
mesh = obj_cell.data
bm = bmesh.new()
bm.from_mesh(mesh)
if use_interior_vgroup:
for bm_vert in bm.verts:
bm_vert.tag = True
for bm_face in bm.faces:
if not bm_face.hide:
for bm_vert in bm_face.verts:
bm_vert.tag = False
# now add all vgroups
defvert_lay = bm.verts.layers.deform.verify()
for bm_vert in bm.verts:
if bm_vert.tag:
bm_vert[defvert_lay][0] = 1.0
# add a vgroup
obj_cell.vertex_groups.new(name="Interior")
if use_sharp_edges:
mesh.show_edge_sharp = True
for bm_edge in bm.edges:
if len({bm_face.hide for bm_face in bm_edge.link_faces}) == 2:
bm_edge.smooth = False
if use_sharp_edges_apply:
edges = [edge for edge in bm.edges if edge.smooth is False]
if edges:
bm.normal_update()
bmesh.ops.split_edges(bm, edges=edges)
for bm_face in bm.faces:
bm_face.hide = False
bm.to_mesh(mesh)
bm.free()

View File

@ -1,284 +0,0 @@
if "bpy" in locals():
import importlib
importlib.reload(cell_main)
importlib.reload(crack_functions)
importlib.reload(material_functions)
importlib.reload(utilities)
else:
from .process import cell_main
from .process import crack_functions
from .process import material_functions
from . import utilities
import bpy
from bpy.types import (
Operator,
)
class FRACTURE_OT_Cell(Operator):
bl_idname = "object.add_fracture_cell"
bl_label = "Cell fracture"
bl_description = "Make fractured cells from selected object."
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == "MESH"
def execute(self, context):
#keywords = self.as_keywords() # ignore=("blah",)
fracture_cell_props = context.window_manager.fracture_cell_props
cell_keywords = utilities._cell_props_to_dict(fracture_cell_props)
originals = context.selected_editable_objects
for original in originals:
cell_main.main(context, original, **cell_keywords)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=350)
def draw(self, context):
cell_props = context.window_manager.fracture_cell_props
layout = self.layout
box = layout.box()
col = box.column()
col.label(text="Fracture From")
row = col.row()
#row.prop(cell_props, "source")
row.prop(cell_props, "source_vert_own")
row.prop(cell_props, "source_vert_child")
row = col.row()
row.prop(cell_props, "source_particle_own")
row.prop(cell_props, "source_particle_child")
row = col.row()
row.prop(cell_props, "source_random")
row.prop(cell_props, "source_pencil")
box = layout.box()
col = box.column()
col.label(text="Transform")
row = col.row()
row.prop(cell_props, "pre_simplify")
row.prop(cell_props, "source_noise")
row = col.row(align=True)
row.prop(cell_props, "margin")
row.prop(cell_props, "use_recenter")
row = col.row(align=True)
row.prop(cell_props, "cell_scale")
# could be own section, control how we subdiv
#row.prop(cell_props, "use_island_split")
box = layout.box()
col = box.column()
col.label(text="Recursive Shatter")
row = col.row(align=True)
row.prop(cell_props, "recursion")
row.prop(cell_props, "recursion_chance")
row = col.row(align=True)
if cell_props.recursion > 0:
row.enabled = True
else:
row.enabled = False
row.prop(cell_props, "recursion_source_limit")
row.prop(cell_props, "recursion_clamp")
row = col.row()
row.prop(cell_props, "recursion_chance_select")#, expand=True)
box = layout.box()
col = box.column()
col.label(text="Interior Meshes")
row = col.row(align=True)
row.prop(cell_props, "use_data_match")
row.prop(cell_props, "use_interior_vgroup")
row = col.row(align=True)
row.prop(cell_props, "use_smooth_faces")
row.prop(cell_props, "use_sharp_edges")
if cell_props.use_sharp_edges == True:
row.prop(cell_props, "use_sharp_edges_apply")
row = col.row()
if cell_props.use_data_match == True:
row.enabled = True
else:
row.enabled = False
row.alignment = 'LEFT'
# on same row for even layout but infact are not all that related
row.prop(cell_props, "material_index")
box = layout.box()
col = box.column()
col.label(text="Custom Properties")
row = col.row(align=True)
row.prop(cell_props, "use_mass")
if cell_props.use_mass:
row = col.row(align=True)
row.prop(cell_props, "mass_name")
row = col.row(align=True)
row.prop(cell_props, "mass_mode")
row.prop(cell_props, "mass")
box = layout.box()
col = box.column()
col.label(text="Object Management")
row = col.row(align=True)
row.prop(cell_props, "original_hide")
row.prop(cell_props, "cell_relocate")
box = layout.box()
col = box.column()
col.label(text="Collections:")
row = col.row(align=True)
row.prop(cell_props, "use_collection")
if cell_props.use_collection:
row.prop(cell_props, "new_collection")
row.prop(cell_props, "collection_name")
box = layout.box()
col = box.column()
col.label(text="Debug")
row = col.row(align=True)
row.prop(cell_props, "use_debug_points")
row.prop(cell_props, "use_debug_bool")
row = col.row(align=True)
row.prop(cell_props, "use_debug_redraw")
class FRACTURE_OT_Crack(Operator):
bl_idname = "object.add_fracture_crack"
bl_label = "Cell To Crack"
bl_description = "Make a cracked object from cell objects"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == "MESH"
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=350)
def execute(self, context):
crack_props = context.window_manager.fracture_crack_props
cells = context.selected_editable_objects
object = None
if cells:
# clear sharp edges for correct crack surface.
bpy.context.view_layer.objects.active = cells[0]
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.reveal()
bpy.ops.mesh.mark_sharp(clear=True, use_verts=True)
bpy.ops.object.mode_set(mode='OBJECT')
for cell in cells:
bpy.context.view_layer.objects.active = cell
bpy.ops.object.modifier_remove(modifier="EDGE_SPLIT_cell")
bpy.context.object.vertex_groups.clear()
bpy.context.view_layer.objects.active = cells[0]
object = crack_functions.make_join(cells)
if object:
bpy.context.view_layer.objects.active = object
crack_functions.add_modifiers()
bpy.context.object.modifiers['DECIMATE_crackit'].ratio = crack_props.modifier_decimate
bpy.context.object.modifiers['SMOOTH_crackit'].factor = crack_props.modifier_smooth
crack_functions.multiExtrude(
off=0.1,
rotx=0, roty=0, rotz=0,
sca=crack_props.extrude_scale,
var1=crack_props.extrude_var, var2=crack_props.extrude_var, var3=crack_props.extrude_var,
num=crack_props.extrude_num, ran=0
)
bpy.ops.object.modifier_apply(apply_as='DATA', modifier='DECIMATE_crackit')
bpy.ops.object.shade_smooth()
if crack_props.modifier_wireframe == True:
bpy.ops.object.modifier_add(type='WIREFRAME')
wireframe = bpy.context.object.modifiers[-1]
wireframe.name = 'WIREFRAME_crackit'
wireframe.use_even_offset = False
wireframe.thickness = 0.01
else:
assert("Joining into One object had been failed. Mesh object can only be joined.")
return {'FINISHED'}
def draw(self, context):
cell_props = context.window_manager.fracture_cell_props
crack_props = context.window_manager.fracture_crack_props
layout = self.layout
box = layout.box()
col = box.column()
col.label(text='* Execute After "1. Cell Fracture"')
box = layout.box()
col = box.column()
col.label(text="Surface:")
row = col.row(align=True)
row.alignment = 'LEFT'
row.prop(crack_props, "modifier_decimate")
row.prop(crack_props, "modifier_smooth")
col = box.column()
col.label(text="Extrude:")
row = col.row(align=True)
row.prop(crack_props, "extrude_scale")
row.prop(crack_props, "extrude_var")
row.prop(crack_props, "extrude_num")
col = box.column()
col.label(text="Post Processing")
row = col.row(align=True)
row.prop(crack_props, "modifier_wireframe")
class FRACTURE_OT_Material(Operator):
bl_idname = "object.add_fracture_material"
bl_label = "Material Preset"
bl_description = ("Material preset for cracked object")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
# included - type that can have materials
included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META']
return (obj is not None and obj.type in included)
def execute(self, context):
material_props = context.window_manager.fracture_material_props
mat_name = material_props.material_preset
mat_lib_name = material_props.material_lib_name
mat_ui_name = material_props.get_ui_mat_name(mat_name) if not mat_lib_name else mat_name
try:
material_functions.appendMaterial(
mat_lib_name = mat_lib_name,
mat_name = mat_name,
mat_ui_names = mat_ui_name
)
except Exception as e:
material_functions.error_handlers(
self, "mesh.crackit_material", e,
"The active Object could not have the Material {} applied".format(mat_ui_name)
)
return {"CANCELLED"}
return {'FINISHED'}

View File

@ -1,149 +0,0 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) Blender Foundation 2012
def points_to_verts(original_xyz_minmax,
points,
points_scale=None,
margin_bounds=0.05,
margin_cell=0.0):
from math import sqrt
import mathutils
from mathutils import Vector
cells = []
plane_indices = []
vertices = []
if points_scale is not None:
points_scale = tuple(points_scale)
if points_scale == (1.0, 1.0, 1.0):
points_scale = None
# there are many ways we could get planes - convex hull for eg
# but it ends up fastest if we just use bounding box
if 1:
xmin, xmax = original_xyz_minmax["x"]
ymin, ymax = original_xyz_minmax["y"]
zmin, zmax = original_xyz_minmax["z"]
xmin -= margin_bounds
xmax += margin_bounds
ymin -= margin_bounds
ymax += margin_bounds
zmin -= margin_bounds
zmax += margin_bounds
# (x,y,z,scaler) for plane. xyz is normaliized direction. scaler is scale for plane.
# Plane will be made at the perpendicular direction of the normal vector.
convexPlanes = [
Vector((+1.0, 0.0, 0.0, -xmax)),
Vector((-1.0, 0.0, 0.0, +xmin)),
Vector((0.0, +1.0, 0.0, -ymax)),
Vector((0.0, -1.0, 0.0, +ymin)),
Vector((0.0, 0.0, +1.0, -zmax)),
Vector((0.0, 0.0, -1.0, +zmin)),
]
if len(points) > 1:
points_dist_sorted = [(Vector(p[0]), p[1]) for p in points]
for i, point_current in enumerate(points):
planes = [None] * len(convexPlanes)
for j in range(len(convexPlanes)):
planes[j] = convexPlanes[j].copy()
# e.g. Dot product point's (xyz) with convex's (+1.0,0.0,0.0) detects x value of the point.
# e.g. Then, x scaler += point's x value.
planes[j][3] += planes[j].xyz.dot(point_current[0])
distance_max = 10000000000.0 # a big value!
points_dist_sorted_current = points_dist_sorted.copy()
# Closer points to the current point are earlier order. Of course, current point is the first.
points_dist_sorted_current.sort(key=lambda p: (p[0] - point_current[0]).length_squared)
# The point itself is removed.
points_dist_sorted_current.pop(0)
# Compare the current point with other points.
for j in range(len(points_dist_sorted_current)):
point_target = points_dist_sorted_current[j]
normal = 0
normal = point_target[0] - point_current[0]
nlength = normal.length # is sqrt(X^2+y^2+z^2).
if points_scale is not None:
normal_alt = normal.copy()
normal_alt.x *= points_scale[0]
normal_alt.y *= points_scale[1]
normal_alt.z *= points_scale[2]
# -rotate plane to new distance
# -should always be positive!! - but abs incase
# Scale rate (normal_alt/normal). If these are the same, dot product is 1.
scalar = normal_alt.normalized().dot(normal.normalized())
# assert(scalar >= 0.0)
nlength *= scalar
normal = normal_alt
if nlength > distance_max:
break
# 4D vector, the same form as convexPlanes. (x,y,z,scaler).
plane = normal.normalized()
plane.resize_4d()
plane[3] = (-nlength / 2.0) + margin_cell
planes.append(plane)
# Make vertex points of cell, by crossing point of planes.
vertices[:], plane_indices[:] = mathutils.geometry.points_in_planes(planes)
#if len(vertices) == 0:
# break
if len(plane_indices) != len(planes):
planes[:] = [planes[k] for k in plane_indices]
# for comparisons use length_squared and delay
# converting to a real length until the end.
distance_max = 10000000000.0 # a big value!
for v in vertices:
distance = v.length_squared
if distance_max < distance:
distance_max = distance
distance_max = sqrt(distance_max) # make real length ここでルートでマックスを下げているのかでも下で倍にしているが。
distance_max *= 2.0
if len(vertices) == 0:
continue
cells.append((point_current[0], vertices[:]))
del vertices[:]
else:
vertices[:], plane_indices[:] = mathutils.geometry.points_in_planes(convexPlanes)
#convex_center = Vector(((xmin-xmax)/2, (ymin-ymax)/2, (zmin-zmax)/2))
convex_center = Vector((0,0,0))
cells.append((convex_center, vertices[:]))
return cells

View File

@ -1,601 +0,0 @@
import bpy
import bmesh
def _redraw_yasiamevil():
_redraw_yasiamevil.opr(**_redraw_yasiamevil.arg)
_redraw_yasiamevil.opr = bpy.ops.wm.redraw_timer
_redraw_yasiamevil.arg = dict(type='DRAW_WIN_SWAP', iterations=1)
def _limit_source(points, source_limit):
if source_limit != 0 and source_limit < len(points):
import random
random.shuffle(points)
points[source_limit:] = []
return points
else:
return points
def simplify_original(original, pre_simplify):
bpy.context.view_layer.objects.active = original
bpy.ops.object.modifier_add(type='DECIMATE')
decimate = bpy.context.object.modifiers[-1]
decimate.name = 'DECIMATE_crackit_original'
decimate.ratio = 1-pre_simplify
def desimplify_original(original):
bpy.context.view_layer.objects.active = original
if 'DECIMATE_crackit_original' in bpy.context.object.modifiers.keys():
bpy.ops.object.modifier_remove(modifier='DECIMATE_crackit_original')
def original_minmax(original_verts):
xa = [v[0] for v in original_verts]
ya = [v[1] for v in original_verts]
za = [v[2] for v in original_verts]
xmin, xmax = min(xa), max(xa)
ymin, ymax = min(ya), max(ya)
zmin, zmax = min(za), max(za)
return {"x":(xmin,xmax), "y":(ymin,ymax), "z":(zmin,zmax)}
def points_from_object(original, original_xyz_minmax,
source_vert_own=100,
source_vert_child=0,
source_particle_own=0,
source_particle_child=0,
source_pencil=0,
source_random=0):
points = []
# This is not used by anywhere
def edge_center(mesh, edge):
v1, v2 = edge.vertices
return (mesh.vertices[v1].co + mesh.vertices[v2].co) / 2.0
# This is not used by anywhere
def poly_center(mesh, poly):
from mathutils import Vector
co = Vector()
tot = 0
for i in poly.loop_indices:
co += mesh.vertices[mesh.loops[i].vertex_index].co
tot += 1
return co / tot
def points_from_verts(original):
"""Takes points from _any_ object with geometry"""
if original.type == 'MESH':
mesh = original.data
matrix = original.matrix_world.copy()
p = [(matrix @ v.co, 'VERTS') for v in mesh.vertices]
return p
else:
depsgraph = bpy.context.evaluated_depsgraph_get()
ob_eval = original.evaluated_get(depsgraph)
try:
mesh = ob_eval.to_mesh()
except:
mesh = None
if mesh is not None:
matrix = original.matrix_world.copy()
p = [(matrix @ v.co, 'VERTS') for v in mesh.vertices]
ob_eval.to_mesh_clear()
return p
def points_from_particles(original):
depsgraph = bpy.context.evaluated_depsgraph_get()
obj_eval = original.evaluated_get(depsgraph)
p = [(particle.location.copy(), 'PARTICLE')
for psys in obj_eval.particle_systems
for particle in psys.particles]
return p
def points_from_random(original, original_xyz_minmax):
xmin, xmax = original_xyz_minmax["x"]
ymin, ymax = original_xyz_minmax["y"]
zmin, zmax = original_xyz_minmax["z"]
from random import uniform
from mathutils import Vector
p = []
for i in range(source_random):
new_pos = Vector( (uniform(xmin, xmax), uniform(ymin, ymax), uniform(zmin, zmax)) )
p.append((new_pos, 'RANDOM'))
return p
# geom own
if source_vert_own > 0:
new_points = points_from_verts(original)
new_points = _limit_source(new_points, source_vert_own)
points.extend(new_points)
# random
if source_random > 0:
new_points = points_from_random(original, original_xyz_minmax)
points.extend(new_points)
# geom children
if source_vert_child > 0:
for original_child in original.children:
new_points = points_from_verts(original_child)
new_points = _limit_source(new_points, source_vert_child)
points.extend(new_points)
# geom particles
if source_particle_own > 0:
new_points = points_from_particles(original)
new_points = _limit_source(new_points, source_particle_own)
points.extend(new_points)
if source_particle_child > 0:
for original_child in original.children:
new_points = points_from_particles(original_child)
new_points = _limit_source(new_points, source_particle_child)
points.extend(new_points)
# grease pencil
def get_points(stroke):
return [point.co.copy() for point in stroke.points]
def get_splines(gp):
gpl = gp.layers.active
if gpl:
fr = gpl.active_frame
if not fr:
current = bpy.context.scene.frame_current
gpl.frames.new(current)
gpl.active_frame = current
fr = gpl.active_frame
return [get_points(stroke) for stroke in fr.strokes]
else:
return []
if source_pencil > 0:
gp = bpy.context.scene.grease_pencil
if gp:
line_points = []
line_points = [(p, 'PENCIL') for spline in get_splines(gp)
for p in spline]
if len(line_points) > 0:
line_points = _limit_source(line_points, source_pencil)
# Make New point between the line point and the closest point.
if not points:
points.extend(line_points)
else:
for lp in line_points:
# Make vector between the line point and its closest point.
points.sort(key=lambda p: (p[0] - lp[0]).length_squared)
closest_point = points[0]
normal = lp[0].xyz - closest_point[0].xyz
new_point = (lp[0], lp[1])
new_point[0].xyz += normal / 2
points.append(new_point)
#print("Found %d points" % len(points))
return points
def points_to_cells(context, original, original_xyz_minmax, points,
source_limit=0,
source_noise=0.0,
use_smooth_faces=False,
use_data_match=False,
use_debug_points=False,
margin=0.0,
material_index=0,
use_debug_redraw=False,
cell_scale=(1.0, 1.0, 1.0),
clean=True):
from . import cell_calc
collection = context.collection
view_layer = context.view_layer
# apply optional clamp
if source_limit != 0 and source_limit < len(points):
points = _limit_source(points, source_limit)
# saddly we cant be sure there are no doubles
from mathutils import Vector
to_tuple = Vector.to_tuple
# To remove doubles, round the values.
points = [(Vector(to_tuple(p[0], 4)),p[1]) for p in points]
del to_tuple
del Vector
if source_noise > 0.0:
from random import random
# boundbox approx of overall scale
from mathutils import Vector
matrix = original.matrix_world.copy()
bb_world = [matrix @ Vector(v) for v in original.bound_box]
scalar = source_noise * ((bb_world[0] - bb_world[6]).length / 2.0)
from mathutils.noise import random_unit_vector
points[:] = [(p[0] + (random_unit_vector() * (scalar * random())), p[1]) for p in points]
if use_debug_points:
bm = bmesh.new()
for p in points:
bm.verts.new(p[0])
mesh_tmp = bpy.data.meshes.new(name="DebugPoints")
bm.to_mesh(mesh_tmp)
bm.free()
obj_tmp = bpy.data.objects.new(name=mesh_tmp.name, object_data=mesh_tmp)
collection.objects.link(obj_tmp)
del obj_tmp, mesh_tmp
cells_verts = cell_calc.points_to_verts(original_xyz_minmax,
points,
cell_scale,
margin_cell=margin)
# some hacks here :S
cell_name = original.name + "_cell"
cells = []
for center_point, cell_verts in cells_verts:
# ---------------------------------------------------------------------
# BMESH
# create the convex hulls
bm = bmesh.new()
# WORKAROUND FOR CONVEX HULL BUG/LIMIT
# XXX small noise
import random
def R():
return (random.random() - 0.5) * 0.001
for i, co in enumerate(cell_verts):
co.x += R()
co.y += R()
co.z += R()
bm_vert = bm.verts.new(co)
import mathutils
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
try:
# Making cell meshes as convex full here!
bmesh.ops.convex_hull(bm, input=bm.verts)
except RuntimeError:
import traceback
traceback.print_exc()
if clean:
bm.normal_update()
try:
bmesh.ops.dissolve_limit(bm, verts=bm.verts, angle_limit=0.001)
except RuntimeError:
import traceback
traceback.print_exc()
# smooth faces will remain only inner faces, after appling boolean modifier.
if use_smooth_faces:
for bm_face in bm.faces:
bm_face.smooth = True
if material_index != 0:
for bm_face in bm.faces:
bm_face.material_index = material_index
# ---------------------------------------------------------------------
# MESH
mesh_dst = bpy.data.meshes.new(name=cell_name)
bm.to_mesh(mesh_dst)
bm.free()
del bm
if use_data_match:
# match materials and data layers so boolean displays them
# currently only materials + data layers, could do others...
mesh_src = original.data
for mat in mesh_src.materials:
mesh_dst.materials.append(mat)
for lay_attr in ("vertex_colors", "uv_layers"):
lay_src = getattr(mesh_src, lay_attr)
lay_dst = getattr(mesh_dst, lay_attr)
for key in lay_src.keys():
lay_dst.new(name=key)
# ---------------------------------------------------------------------
# OBJECT
cell = bpy.data.objects.new(name=cell_name, object_data=mesh_dst)
collection.objects.link(cell)
cell.location = center_point
cells.append(cell)
# support for object materials
if use_data_match:
for i in range(len(mesh_dst.materials)):
slot_src = original.material_slots[i]
slot_dst = cell.material_slots[i]
slot_dst.link = slot_src.link
slot_dst.material = slot_src.material
if use_debug_redraw:
view_layer.update()
_redraw_yasiamevil()
view_layer.update()
# move this elsewhere...
# Blender 2.8: BGE integration was disabled, --
# -- because BGE was deleted in Blender 2.8.
'''
for cell in cells:
game = cell.game
game.physics_type = 'RIGID_BODY'
game.use_collision_bounds = True
game.collision_bounds_type = 'CONVEX_HULL'
'''
return cells
def cell_boolean(context, original, cells,
use_debug_bool=False,
clean=True,
use_island_split=False,
use_interior_hide=False,
use_debug_redraw=False,
level=0,
remove_doubles=True
):
cells_boolean = []
collection = context.collection
scene = context.scene
view_layer = context.view_layer
if use_interior_hide and level == 0:
# only set for level 0
original.data.polygons.foreach_set("hide", [False] * len(original.data.polygons))
# The first object can't be applied by bool, so it is used as a no-effect first straw-man.
bpy.ops.mesh.primitive_cube_add(enter_editmode=False, location=(original.location.x+10000000000.0, 0, 0))
temp_cell = bpy.context.active_object
cells.insert(0, temp_cell)
bpy.ops.object.select_all(action='DESELECT')
for i, cell in enumerate(cells):
mod = cell.modifiers.new(name="Boolean", type='BOOLEAN')
mod.object = original
mod.operation = 'INTERSECT'
if not use_debug_bool:
if use_interior_hide:
cell.data.polygons.foreach_set("hide", [True] * len(cell.data.polygons))
# mesh_old should be made before appling boolean modifier.
mesh_old = cell.data
original.select_set(True)
cell.select_set(True)
bpy.context.view_layer.objects.active = cell
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")
if i == 0:
bpy.data.objects.remove(cell, do_unlink=True)
continue
cell = bpy.context.active_object
cell.select_set(False)
# depsgraph sould be gotten after applied boolean modifier, for new_mesh.
depsgraph = context.evaluated_depsgraph_get()
cell_eval = cell.evaluated_get(depsgraph)
mesh_new = bpy.data.meshes.new_from_object(cell_eval)
cell.data = mesh_new
'''
check_hide = [11] * len(cell.data.polygons)
cell.data.polygons.foreach_get("hide", check_hide)
print(check_hide)
'''
# remove if not valid
if not mesh_old.users:
bpy.data.meshes.remove(mesh_old)
if not mesh_new.vertices:
collection.objects.unlink(cell)
if not cell.users:
bpy.data.objects.remove(cell)
cell = None
if not mesh_new.users:
bpy.data.meshes.remove(mesh_new)
mesh_new = None
# avoid unneeded bmesh re-conversion
if mesh_new is not None:
bm = None
if clean:
if bm is None: # ok this will always be true for now...
bm = bmesh.new()
bm.from_mesh(mesh_new)
bm.normal_update()
try:
bmesh.ops.dissolve_limit(bm, verts=bm.verts, edges=bm.edges, angle_limit=0.001)
except RuntimeError:
import traceback
traceback.print_exc()
if remove_doubles:
if bm is None:
bm = bmesh.new()
bm.from_mesh(mesh_new)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.005)
if bm is not None:
bm.to_mesh(mesh_new)
bm.free()
del mesh_new
del mesh_old
if cell is not None:
cells_boolean.append(cell)
if use_debug_redraw:
_redraw_yasiamevil()
bpy.context.view_layer.objects.active = original
if (not use_debug_bool) and use_island_split:
# this is ugly and Im not proud of this - campbell
for ob in view_layer.objects:
ob.select_set(False)
for cell in cells_boolean:
cell.select_set(True)
# If new separated meshes are made, selected objects is increased.
if cells_boolean:
bpy.ops.mesh.separate(type='LOOSE')
cells_boolean[:] = [cell for cell in scene.objects if cell.select_get()]
context.view_layer.update()
return cells_boolean
def interior_handle(cells,
use_interior_vgroup=False,
use_sharp_edges=False,
use_sharp_edges_apply=False,
):
"""Run after doing _all_ booleans"""
assert(use_interior_vgroup or use_sharp_edges or use_sharp_edges_apply)
for cell in cells:
mesh = cell.data
bm = bmesh.new()
bm.from_mesh(mesh)
if use_interior_vgroup:
for bm_vert in bm.verts:
bm_vert.tag = True
for bm_face in bm.faces:
if not bm_face.hide:
for bm_vert in bm_face.verts:
bm_vert.tag = False
# now add all vgroups
defvert_lay = bm.verts.layers.deform.verify()
for bm_vert in bm.verts:
if bm_vert.tag:
bm_vert[defvert_lay][0] = 1.0
# add a vgroup
cell.vertex_groups.new(name="Interior")
if use_sharp_edges:
bpy.context.space_data.overlay.show_edge_sharp = True
for bm_edge in bm.edges:
if len({bm_face.hide for bm_face in bm_edge.link_faces}) == 2:
bm_edge.smooth = False
if use_sharp_edges_apply:
bpy.context.view_layer.objects.active = cell
bpy.ops.object.modifier_add(type='EDGE_SPLIT')
edge_split = bpy.context.object.modifiers[-1]
edge_split.name = 'EDGE_SPLIT_cell'
edge_split.use_edge_angle = False
'''
edges = [edge for edge in bm.edges if edge.smooth is False]
if edges:
bm.normal_update()
bmesh.ops.split_edges(bm, edges=edges)
'''
for bm_face in bm.faces:
bm_face.hide = False
bm.to_mesh(mesh)
bm.free()
def post_process(cells,
use_collection=False,
new_collection=False,
collection_name="Fracture",
use_mass=False,
mass=1.0,
mass_mode='VOLUME', mass_name='mass',
):
"""Run after Interiro handle"""
#--------------
# Collection Options
if use_collection:
colle = None
if not new_collection:
colle = bpy.data.collections.get(collection_name)
if colle is None:
colle = bpy.data.collections.new(collection_name)
# THe collection should be children of master collection to show in outliner.
child_names = [m.name for m in bpy.context.scene.collection.children]
if colle.name not in child_names:
bpy.context.scene.collection.children.link(colle)
# Cell objects are only link to the collection.
bpy.ops.collection.objects_remove_all() # For all selected object.
for colle_obj in cells:
colle.objects.link(colle_obj)
#--------------
# Mass Options
if use_mass:
# Blender 2.8: Mass for BGE was no more available.--
# -- Instead, Mass values is used for custom properies on cell objects.
if mass_mode == 'UNIFORM':
for cell in cells:
#cell.game.mass = mass
cell[mass_name] = mass
elif mass_mode == 'VOLUME':
from mathutils import Vector
def _get_volume(cell):
def _getObjectBBMinMax():
min_co = Vector((1000000.0, 1000000.0, 1000000.0))
max_co = -min_co
matrix = cell.matrix_world
for i in range(0, 8):
bb_vec = cell.matrix_world @ Vector(cell.bound_box[i])
min_co[0] = min(bb_vec[0], min_co[0])
min_co[1] = min(bb_vec[1], min_co[1])
min_co[2] = min(bb_vec[2], min_co[2])
max_co[0] = max(bb_vec[0], max_co[0])
max_co[1] = max(bb_vec[1], max_co[1])
max_co[2] = max(bb_vec[2], max_co[2])
return (min_co, max_co)
def _getObjectVolume():
min_co, max_co = _getObjectBBMinMax()
x = max_co[0] - min_co[0]
y = max_co[1] - min_co[1]
z = max_co[2] - min_co[2]
volume = x * y * z
return volume
return _getObjectVolume()
cell_volume_ls = [_get_volume(cell) for cell in cells]
cell_volume_tot = sum(cell_volume_ls)
if cell_volume_tot > 0.0:
mass_fac = mass / cell_volume_tot
for i, cell in enumerate(cells):
cell[mass_name] = cell_volume_ls[i] * mass_fac
else:
assert(0)

View File

@ -1,208 +0,0 @@
if "bpy" in locals():
import importlib
importlib.reload(cell_functions)
else:
from . import cell_functions
import bpy
def main_object(context, original, level, **kw):
import random
# pull out some args
kw_copy = kw.copy()
source_vert_own = kw_copy.pop("source_vert_own")
source_vert_child = kw_copy.pop("source_vert_child")
source_particle_own = kw_copy.pop("source_particle_own")
source_particle_child = kw_copy.pop("source_particle_child")
source_pencil = kw_copy.pop("source_pencil")
source_random = kw_copy.pop("source_random")
use_recenter = kw_copy.pop("use_recenter")
recursion = kw_copy.pop("recursion")
recursion_source_limit = kw_copy.pop("recursion_source_limit")
recursion_clamp = kw_copy.pop("recursion_clamp")
recursion_chance = kw_copy.pop("recursion_chance")
recursion_chance_select = kw_copy.pop("recursion_chance_select")
use_island_split = kw_copy.pop("use_island_split")
use_debug_bool = kw_copy.pop("use_debug_bool")
use_interior_vgroup = kw_copy.pop("use_interior_vgroup")
use_sharp_edges = kw_copy.pop("use_sharp_edges")
use_sharp_edges_apply = kw_copy.pop("use_sharp_edges_apply")
cell_relocate = kw_copy.pop("cell_relocate")
collection = context.collection
scene = context.scene
if level != 0:
kw_copy["source_limit"] = recursion_source_limit
from . import cell_functions
# not essential but selection is visual distraction.
original.select_set(False)
if kw_copy["use_debug_redraw"]:
original_display_type_prev = original.display_type
original.display_type = 'WIRE'
original_mesh = original.data
original_matrix = original.matrix_world.copy()
original_verts = [original_matrix @ v.co for v in original_mesh.vertices]
original_xyz_minmax = cell_functions.original_minmax(original_verts)
cells = []
points = cell_functions.points_from_object(original, original_xyz_minmax,
source_vert_own=source_vert_own,
source_vert_child=source_vert_child,
source_particle_own=source_particle_own,
source_particle_child=source_particle_child,
source_pencil=source_pencil,
source_random=source_random)
cells = cell_functions.points_to_cells(context, original, original_xyz_minmax, points, **kw_copy)
cells = cell_functions.cell_boolean(context, original, cells,
use_island_split=use_island_split,
use_interior_hide=(use_interior_vgroup or use_sharp_edges),
use_debug_bool=use_debug_bool,
use_debug_redraw=kw_copy["use_debug_redraw"],
level=level,
)
# must apply after boolean.
if use_recenter:
bpy.ops.object.origin_set({"selected_editable_objects": cells},
type='ORIGIN_GEOMETRY', center='MEDIAN')
#--------------
# Recursion.
if level == 0:
for level_sub in range(1, recursion + 1):
objects_recurse_input = [(i, o) for i, o in enumerate(cells)]
if recursion_chance != 1.0:
from mathutils import Vector
if recursion_chance_select == 'RANDOM':
random.shuffle(objects_recurse_input)
elif recursion_chance_select in {'SIZE_MIN', 'SIZE_MAX'}:
objects_recurse_input.sort(key=lambda ob_pair:
(Vector(ob_pair[1].bound_box[0]) -
Vector(ob_pair[1].bound_box[6])).length_squared)
if recursion_chance_select == 'SIZE_MAX':
objects_recurse_input.reverse()
elif recursion_chance_select in {'CURSOR_MIN', 'CURSOR_MAX'}:
c = scene.cursor.location.copy()
objects_recurse_input.sort(key=lambda ob_pair:
(ob_pair[1].location - c).length_squared)
if recursion_chance_select == 'CURSOR_MAX':
objects_recurse_input.reverse()
objects_recurse_input[int(recursion_chance * len(objects_recurse_input)):] = []
objects_recurse_input.sort()
# reverse index values so we can remove from original list.
objects_recurse_input.reverse()
objects_recursive = []
for i, obj_cell in objects_recurse_input:
assert(cells[i] is obj_cell)
# Repeat main_object() here.
objects_recursive += main_object(context, obj_cell, level_sub, **kw)
#if original_remove:
collection.objects.unlink(obj_cell)
del cells[i]
if recursion_clamp and len(cells) + len(objects_recursive) >= recursion_clamp:
break
cells.extend(objects_recursive)
if recursion_clamp and len(cells) > recursion_clamp:
break
#--------------
# Level Options
if level == 0:
# import pdb; pdb.set_trace()
if use_interior_vgroup or use_sharp_edges:
cell_functions.interior_handle(cells,
use_interior_vgroup=use_interior_vgroup,
use_sharp_edges=use_sharp_edges,
use_sharp_edges_apply=use_sharp_edges_apply,
)
if cell_relocate:
for cell in cells:
cell.location.x += (original_xyz_minmax["x"][1] - original_xyz_minmax["x"][0]) + 1
if kw_copy["use_debug_redraw"]:
original.display_type = original_display_type_prev
return cells
def main(context, original, **kw):
'''
import time
t = time.time()
'''
kw_copy = kw.copy()
# Pre_Simplify
pre_simplify = kw_copy.pop("pre_simplify")
# collection
use_collection = kw_copy.pop("use_collection")
new_collection = kw_copy.pop("new_collection")
collection_name = kw_copy.pop("collection_name")
# object visibility
original_hide = kw_copy.pop("original_hide")
# mass
use_mass = kw_copy.pop("use_mass")
mass_name = kw_copy.pop("mass_name")
mass_mode = kw_copy.pop("mass_mode")
mass = kw_copy.pop("mass")
cells = []
if original.type == 'MESH':
if pre_simplify > 0.0:
cell_functions.simplify_original(original=original, pre_simplify=pre_simplify)
cells += main_object(context, original, 0, **kw_copy)
if pre_simplify > 0.0:
cell_functions.desimplify_original(original=original)
else:
assert obj.type == 'MESH', "No MESH object selected."
bpy.ops.object.select_all(action='DESELECT')
for cell in cells:
cell.select_set(True)
cell_functions.post_process(cells,
use_collection=use_collection,
new_collection=new_collection,
collection_name=collection_name,
use_mass=use_mass,
mass=mass,
mass_mode=mass_mode,
mass_name=mass_name,
)
# To avoid select both original object and cells in EDIT mode.
bpy.context.view_layer.objects.active = cells[0]
# de-hide all objects and meshes.
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.reveal()
bpy.ops.object.mode_set(mode='OBJECT')
if original_hide:
original.hide_set(True)
#print("Done! %d objects in %.4f sec" % (len(cells), time.time() - t))
#print("Done!")
return (original, cells)

View File

@ -1,140 +0,0 @@
# gpl: author Nobuyuki Hirakata
import bpy
import bmesh
from random import (
gauss,
seed,
)
from math import radians, pi
from mathutils import Euler
# Join fractures into an object
def make_join(cells):
# Execute join
bpy.context.view_layer.objects.active = cells[0]
cells[0].select_set(state=True)
bpy.ops.object.join()
bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
joined = bpy.context.active_object
suffix_index = joined.name.rfind("_cell")
if suffix_index != -1:
joined.name = joined.name[:suffix_index] + "_crack"
return bpy.context.active_object
# Add modifier and setting
def add_modifiers(decimate_val=0.4, smooth_val=0.5):
bpy.ops.object.modifier_add(type='DECIMATE')
decimate = bpy.context.object.modifiers[-1]
decimate.name = 'DECIMATE_crackit'
decimate.ratio = decimate_val
bpy.ops.object.modifier_add(type='SUBSURF')
subsurf = bpy.context.object.modifiers[-1]
subsurf.name = 'SUBSURF_crackit'
bpy.ops.object.modifier_add(type='SMOOTH')
smooth = bpy.context.object.modifiers[-1]
smooth.name = 'SMOOTH_crackit'
smooth.factor = smooth_val
# -------------- multi extrude --------------------
# var1=random offset, var2=random rotation, var3=random scale
def multiExtrude(off=0.1, rotx=0, roty=0, rotz=0, sca=0.0,
var1=0.01, var2=0.01, var3=0.01, num=1, ran=0):
obj = bpy.context.object
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
# bmesh operations
bpy.ops.object.mode_set()
bm = bmesh.new()
bm.from_mesh(obj.data)
sel = [f for f in bm.faces if f.select]
# faces loop
for i, of in enumerate(sel):
rot = _vrot(r=i, ran=ran, rotx=rotx, var2=var2, roty=roty, rotz=rotz)
off = _vloc(r=i, ran=ran, off=off, var1=var1)
of.normal_update()
# extrusion loop
for r in range(num):
nf = of.copy()
nf.normal_update()
no = nf.normal.copy()
ce = nf.calc_center_bounds()
s = _vsca(r=i + r, ran=ran, var3=var3, sca=sca)
for v in nf.verts:
v.co -= ce
v.co.rotate(rot)
v.co += ce + no * off
v.co = v.co.lerp(ce, 1 - s)
# extrude code from TrumanBlending
for a, b in zip(of.loops, nf.loops):
sf = bm.faces.new((a.vert, a.link_loop_next.vert,
b.link_loop_next.vert, b.vert))
sf.normal_update()
bm.faces.remove(of)
of = nf
for v in bm.verts:
v.select = False
for e in bm.edges:
e.select = False
bm.to_mesh(obj.data)
obj.data.update()
def _vloc(r, ran, off, var1):
seed(ran + r)
return off * (1 + gauss(0, var1 / 3))
def _vrot(r, ran, rotx, var2, roty, rotz):
seed(ran + r)
return Euler((radians(rotx) + gauss(0, var2 / 3),
radians(roty) + gauss(0, var2 / 3),
radians(rotz) + gauss(0, var2 / 3)), 'XYZ')
def _vsca(r, ran, sca, var3):
seed(ran + r)
return sca * (1 + gauss(0, var3 / 3))
# Centroid of a selection of vertices
'''
def _centro(ver):
vvv = [v for v in ver if v.select]
if not vvv or len(vvv) == len(ver):
return ('error')
x = sum([round(v.co[0], 4) for v in vvv]) / len(vvv)
y = sum([round(v.co[1], 4) for v in vvv]) / len(vvv)
z = sum([round(v.co[2], 4) for v in vvv]) / len(vvv)
return (x, y, z)
'''
# Retrieve the original state of the object
'''
def _volver(obj, copia, om, msm, msv):
for i in copia:
obj.data.vertices[i].select = True
bpy.context.tool_settings.mesh_select_mode = msm
for i in range(len(msv)):
obj.modifiers[i].show_viewport = msv[i]
'''

View File

@ -1,83 +0,0 @@
# gpl: author Nobuyuki Hirakata
import bpy
import os
# Allow changing the original material names from the .blend file
# by replacing them with the UI Names from the EnumProperty
def get_ui_mat_name(mat_name):
mat_ui_name = "CrackIt Material"
try:
# access the Scene type directly to get the name from the enum
mat_items = bpy.types.Scene.crackit[1]["type"].bl_rna.material_preset[1]["items"]
for mat_id, mat_list in enumerate(mat_items):
if mat_name in mat_list:
mat_ui_name = mat_items[mat_id][1]
break
del mat_items
except Exception as e:
error_handlers(
False, "get_ui_mat_name", e,
"Retrieving the EnumProperty key UI Name could not be completed", True
)
pass
return mat_ui_name
# error_type='ERROR' for popup massage
def error_handlers(self, op_name, error, reports="ERROR", func=False, error_type='WARNING'):
if self and reports:
self.report({error_type}, reports + " (See Console for more info)")
is_func = "Function" if func else "Operator"
print("\n[Cell Fracture Crack It]\n{}: {}\nError: "
"{}\nReport: {}\n".format(is_func, op_name, error, reports))
def appendMaterial(mat_lib_name, mat_name, mat_ui_names="Nameless Material"):
file_path = _makeFilePath(os.path.dirname(__file__))
bpy.ops.wm.append(filename=mat_name, directory=file_path)
# If material is loaded some times, select the last-loaded material
last_material = _getAppendedMaterial(mat_name)
if last_material:
mat = bpy.data.materials[last_material]
# skip renaming if the prop is True
if not mat_lib_name:
mat.name = mat_ui_names
# Apply Only one material in the material slot
for m in bpy.context.object.data.materials:
bpy.ops.object.material_slot_remove()
bpy.context.object.data.materials.append(mat)
return True
return False
# Make file path of addon
def _makeFilePath(addon_path):
material_folder = "/materials"
blend_file = "/materials1.blend"
category = "\\Material\\"
file_path = addon_path + material_folder + blend_file + category
return file_path
# Get last-loaded material, such as ~.002
def _getAppendedMaterial(material_name):
# Get material name list
material_names = [m.name for m in bpy.data.materials if material_name in m.name]
if material_names:
# Return last material in the sorted order
material_names.sort()
return material_names[-1]
return None

View File

@ -1,42 +0,0 @@
def _cell_props_to_dict(fracture_cell_props):
cell_keywords = {
'source_vert_own': fracture_cell_props.source_vert_own,
'source_vert_child': fracture_cell_props.source_vert_child,
'source_particle_own': fracture_cell_props.source_particle_own,
'source_particle_child': fracture_cell_props.source_particle_child,
'source_pencil': fracture_cell_props.source_pencil,
'source_random': fracture_cell_props.source_random,
'source_noise': fracture_cell_props.source_noise,
'margin': fracture_cell_props.margin,
'cell_scale': fracture_cell_props.cell_scale,
'pre_simplify': fracture_cell_props.pre_simplify,
'use_recenter': fracture_cell_props.use_recenter,
'use_island_split': fracture_cell_props.use_island_split,
'recursion': fracture_cell_props.recursion,
'recursion_source_limit': fracture_cell_props.recursion_source_limit,
'recursion_clamp': fracture_cell_props.recursion_clamp,
'recursion_chance': fracture_cell_props.recursion_chance,
'recursion_chance_select': fracture_cell_props.recursion_chance_select,
'use_smooth_faces': fracture_cell_props.use_smooth_faces,
'use_sharp_edges': fracture_cell_props.use_sharp_edges,
'use_sharp_edges_apply': fracture_cell_props.use_sharp_edges_apply,
'use_data_match': fracture_cell_props.use_data_match,
'material_index': fracture_cell_props.material_index,
'use_interior_vgroup': fracture_cell_props.use_interior_vgroup,
'use_collection': fracture_cell_props.use_collection,
'new_collection': fracture_cell_props.new_collection,
'collection_name': fracture_cell_props.collection_name,
'original_hide': fracture_cell_props.original_hide,
'cell_relocate': fracture_cell_props.cell_relocate,
'use_mass': fracture_cell_props.use_mass,
'mass_name': fracture_cell_props.mass_name,
'mass_mode': fracture_cell_props.mass_mode,
'mass': fracture_cell_props.mass,
'use_debug_points': fracture_cell_props.use_debug_points,
'use_debug_redraw': fracture_cell_props.use_debug_redraw,
'use_debug_bool': fracture_cell_props.use_debug_bool
}
return cell_keywords

View File

@ -0,0 +1,148 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Cell Fracture Crack It",
"author": "Nobuyuki Hirakata",
"version": (0, 1, 2),
"blender": (2, 78, 5),
"location": "View3D > Toolshelf > Create Tab",
"description": "Displaced Cell Fracture Addon",
"warning": "Make sure to enable 'Object: Cell Fracture' Addon",
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
"Py/Scripts/Object/CrackIt",
"category": "Object"
}
if 'bpy' in locals():
import importlib
importlib.reload(operator)
else:
from . import operator
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
IntProperty,
PointerProperty,
)
import os
class CrackItProperties(PropertyGroup):
# Input on toolshelf before execution
# In Panel subclass, In bpy.types.Operator subclass,
# reference them by context.scene.crackit
fracture_childverts: BoolProperty(
name="From Child Verts",
description="Use child object's vertices and position for origin of crack",
default=False
)
fracture_scalex: FloatProperty(
name="Scale X",
description="Scale X",
default=1.00,
min=0.00,
max=1.00
)
fracture_scaley: FloatProperty(
name="Scale Y",
description="Scale Y",
default=1.00,
min=0.00,
max=1.00
)
fracture_scalez: FloatProperty(
name="Scale Z",
description="Scale Z",
default=1.00,
min=0.00,
max=1.00
)
fracture_div: IntProperty(
name="Max Crack",
description="Max Crack",
default=100,
min=0,
max=10000
)
fracture_margin: FloatProperty(
name="Margin Size",
description="Margin Size",
default=0.001,
min=0.000,
max=1.000
)
extrude_offset: FloatProperty(
name="Offset",
description="Extrude Offset",
default=0.10,
min=0.00,
max=2.00
)
extrude_random: FloatProperty(
name="Random",
description="Extrude Random",
default=0.30,
min=-1.00,
max=1.00
)
# Path of the addon
material_addonpath = os.path.dirname(__file__)
# Selection of material preset
# Note: you can choose the original name in the library blend
# or the prop name
material_preset: EnumProperty(
name="Preset",
description="Material Preset",
items=[
('crackit_organic_mud', "Organic Mud", "Mud material"),
('crackit_mud1', "Mud", "Mud material"),
('crackit_tree1_moss1', "Tree Moss", "Tree Material"),
('crackit_tree2_dry1', "Tree Dry", "Tree Material"),
('crackit_tree3_red1', "Tree Red", "Tree Material"),
('crackit_rock1', "Rock", "Rock Material")
]
)
material_lib_name: BoolProperty(
name="Library Name",
description="Use the original Material name from the .blend library\n"
"instead of the one defined in the Preset",
default=True
)
def register():
bpy.utils.register_module(__name__)
bpy.types.Scene.crackit = PointerProperty(
type=CrackItProperties
)
def unregister():
del bpy.types.Scene.crackit
bpy.utils.unregister_module(__name__)
if __name__ == "__main__":
register()

View File

@ -0,0 +1,255 @@
# gpl: author Nobuyuki Hirakata
import bpy
import bmesh
from random import (
gauss,
seed,
)
from math import radians
from mathutils import Euler
# Allow changing the original material names from the .blend file
# by replacing them with the UI Names from the EnumProperty
def get_ui_mat_name(mat_name):
mat_ui_name = "CrackIt Material"
try:
# access the Scene type directly to get the name from the enum
mat_items = bpy.types.Scene.crackit[1]["type"].bl_rna.material_preset[1]["items"]
for mat_id, mat_list in enumerate(mat_items):
if mat_name in mat_list:
mat_ui_name = mat_items[mat_id][1]
break
del mat_items
except Exception as e:
error_handlers(
False, "get_ui_mat_name", e,
"Retrieving the EnumProperty key UI Name could not be completed", True
)
pass
return mat_ui_name
def error_handlers(self, op_name, error, reports="ERROR", func=False):
if self and reports:
self.report({'WARNING'}, reports + " (See Console for more info)")
is_func = "Function" if func else "Operator"
print("\n[Cell Fracture Crack It]\n{}: {}\nError: "
"{}\nReport: {}\n".format(is_func, op_name, error, reports))
# -------------------- Crack -------------------
# Cell fracture and post-process:
def makeFracture(child_verts=False, division=100, noise=0.00,
scaleX=1.00, scaleY=1.00, scaleZ=1.00, recursion=0, margin=0.001):
# Get active object name and active layer
active_name = bpy.context.view_layer.objects.active.name
active_layer = bpy.context.scene.active_layer
# source method of whether use child verts
if child_verts is True:
crack_source = 'VERT_CHILD'
else:
crack_source = 'PARTICLE_OWN'
bpy.ops.object.add_fracture_cell_objects(
source={crack_source}, source_limit=division, source_noise=noise,
cell_scale=(scaleX, scaleY, scaleZ), recursion=recursion,
recursion_source_limit=8, recursion_clamp=250, recursion_chance=0.25,
recursion_chance_select='SIZE_MIN', use_smooth_faces=False,
use_sharp_edges=False, use_sharp_edges_apply=True, use_data_match=True,
use_island_split=True, margin=margin, material_index=0,
use_interior_vgroup=False, mass_mode='VOLUME', mass=1, use_recenter=True,
use_remove_original=True, use_layer_index=0, use_layer_next=False,
group_name="", use_debug_points=False, use_debug_redraw=True, use_debug_bool=False
)
_makeJoin(active_name, active_layer)
# Join fractures into an object
def _makeJoin(active_name, active_layer):
# Get object by name
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.object.select_pattern(pattern=active_name + '_cell*')
fractures = bpy.context.selected_objects
if fractures:
# Execute join
bpy.context.view_layer.objects.active = fractures[0]
fractures[0].select_set(True)
bpy.ops.object.join()
else:
error_handlers(
False, "_makeJoin", "if fractures condition has not passed",
"Warning: No objects could be joined", True
)
# Change name
bpy.context.view_layer.objects.active.name = active_name + '_crack'
# Change origin
bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN')
# Add modifier and setting
def addModifiers():
bpy.ops.object.modifier_add(type='DECIMATE')
decimate = bpy.context.object.modifiers[-1]
decimate.name = 'DECIMATE_crackit'
decimate.ratio = 0.4
bpy.ops.object.modifier_add(type='SUBSURF')
subsurf = bpy.context.object.modifiers[-1]
subsurf.name = 'SUBSURF_crackit'
bpy.ops.object.modifier_add(type='SMOOTH')
smooth = bpy.context.object.modifiers[-1]
smooth.name = 'SMOOTH_crackit'
# -------------- multi extrude --------------------
# var1=random offset, var2=random rotation, var3=random scale
def multiExtrude(off=0.1, rotx=0, roty=0, rotz=0, sca=1.0,
var1=0.01, var2=0.3, var3=0.3, num=1, ran=0):
obj = bpy.context.object
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
# bmesh operations
bpy.ops.object.mode_set()
bm = bmesh.new()
bm.from_mesh(obj.data)
sel = [f for f in bm.faces if f.select]
# faces loop
for i, of in enumerate(sel):
rot = _vrot(r=i, ran=ran, rotx=rotx, var2=var2, roty=roty, rotz=rotz)
off = _vloc(r=i, ran=ran, off=off, var1=var1)
of.normal_update()
# extrusion loop
for r in range(num):
nf = of.copy()
nf.normal_update()
no = nf.normal.copy()
ce = nf.calc_center_bounds()
s = _vsca(r=i + r, ran=ran, var3=var3, sca=sca)
for v in nf.verts:
v.co -= ce
v.co.rotate(rot)
v.co += ce + no * off
v.co = v.co.lerp(ce, 1 - s)
# extrude code from TrumanBlending
for a, b in zip(of.loops, nf.loops):
sf = bm.faces.new((a.vert, a.link_loop_next.vert,
b.link_loop_next.vert, b.vert))
sf.normal_update()
bm.faces.remove(of)
of = nf
for v in bm.verts:
v.select = False
for e in bm.edges:
e.select = False
bm.to_mesh(obj.data)
obj.data.update()
def _vloc(r, ran, off, var1):
seed(ran + r)
return off * (1 + gauss(0, var1 / 3))
def _vrot(r, ran, rotx, var2, roty, rotz):
seed(ran + r)
return Euler((radians(rotx) + gauss(0, var2 / 3),
radians(roty) + gauss(0, var2 / 3),
radians(rotz) + gauss(0, var2 / 3)), 'XYZ')
def _vsca(r, ran, sca, var3):
seed(ran + r)
return sca * (1 + gauss(0, var3 / 3))
# Centroid of a selection of vertices
def _centro(ver):
vvv = [v for v in ver if v.select]
if not vvv or len(vvv) == len(ver):
return ('error')
x = sum([round(v.co[0], 4) for v in vvv]) / len(vvv)
y = sum([round(v.co[1], 4) for v in vvv]) / len(vvv)
z = sum([round(v.co[2], 4) for v in vvv]) / len(vvv)
return (x, y, z)
# Retrieve the original state of the object
def _volver(obj, copia, om, msm, msv):
for i in copia:
obj.data.vertices[i].select = True
bpy.context.tool_settings.mesh_select_mode = msm
for i in range(len(msv)):
obj.modifiers[i].show_viewport = msv[i]
# -------------- Material preset --------------------------
def appendMaterial(addon_path, material_name, mat_ui_names="Nameless Material"):
# Load material from the addon directory
file_path = _makeFilePath(addon_path=addon_path)
bpy.ops.wm.append(filename=material_name, directory=file_path)
# If material is loaded some times, select the last-loaded material
last_material = _getAppendedMaterial(material_name)
if last_material:
mat = bpy.data.materials[last_material]
# skip renaming if the prop is True
if not bpy.context.scene.crackit.material_lib_name:
mat.name = mat_ui_names
# Apply Only one material in the material slot
for m in bpy.context.object.data.materials:
bpy.ops.object.material_slot_remove()
bpy.context.object.data.materials.append(mat)
return True
return False
# Make file path of addon
def _makeFilePath(addon_path):
material_folder = "/materials"
blend_file = "/materials1.blend"
category = "\\Material\\"
file_path = addon_path + material_folder + blend_file + category
return file_path
# Get last-loaded material, such as ~.002
def _getAppendedMaterial(material_name):
# Get material name list
material_names = [m.name for m in bpy.data.materials if material_name in m.name]
if material_names:
# Return last material in the sorted order
material_names.sort()
return material_names[-1]
return None

Binary file not shown.

View File

@ -0,0 +1,164 @@
# gpl: author Nobuyuki Hirakata
import bpy
from bpy.types import (
Operator,
Panel,
)
from . import crack_it
def check_object_cell_fracture():
if "object_fracture_cell" in bpy.context.preferences.addons.keys():
return True
return False
# Access by bpy.ops.mesh.crackit_fracture
class FractureOperation(Operator):
bl_idname = "mesh.crackit_fracture"
bl_label = "Crack it!"
bl_description = ("Make cracks using the cell fracture add-on\n"
"Needs only one Selected Mesh Object")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
sel_obj = len(context.selected_objects) == 1
return (obj is not None and obj.type == "MESH" and sel_obj)
def execute(self, context):
if check_object_cell_fracture():
crackit = context.scene.crackit
try:
crack_it.makeFracture(
child_verts=crackit.fracture_childverts,
division=crackit.fracture_div, scaleX=crackit.fracture_scalex,
scaleY=crackit.fracture_scaley, scaleZ=crackit.fracture_scalez,
margin=crackit.fracture_margin
)
crack_it.addModifiers()
crack_it.multiExtrude(
off=crackit.extrude_offset,
var2=crackit.extrude_random, var3=crackit.extrude_random
)
bpy.ops.object.shade_smooth()
except Exception as e:
crack_it.error_handlers(
self, "mesh.crackit_fracture", e, "Crack It! could not be completed."
)
return {"CANCELLED"}
else:
self.report({'WARNING'},
"Depends on Object: Cell Fracture addon. Please enable it first. "
"Operation Cancelled"
)
return {"CANCELLED"}
return {'FINISHED'}
# Apply material preset
# Access by bpy.ops.mesh.crackit_material
class MaterialOperation(Operator):
bl_idname = "mesh.crackit_material"
bl_label = "Apply Material"
bl_description = ("Apply a preset material\n"
"The Material will be applied to the Active Object\n"
"from the type of Mesh, Curve, Surface, Font, Meta")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
obj = context.active_object
# included - type that can have materials
included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META']
return (obj is not None and obj.type in included)
def execute(self, context):
crackit = context.scene.crackit
mat_name = crackit.material_preset
mat_lib_name = crackit.material_lib_name
mat_ui_name = crack_it.get_ui_mat_name(mat_name) if not mat_lib_name else mat_name
try:
crack_it.appendMaterial(
addon_path=crackit.material_addonpath,
material_name=mat_name,
mat_ui_names=mat_ui_name
)
except Exception as e:
crack_it.error_handlers(
self, "mesh.crackit_material", e,
"The active Object could not have the Material {} applied".format(mat_ui_name)
)
return {"CANCELLED"}
return {'FINISHED'}
# Menu settings
class crackitPanel(Panel):
bl_label = "Crack it!"
bl_idname = 'crack_it'
bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS"
bl_category = "Create"
bl_context = 'objectmode'
bl_options = {"DEFAULT_CLOSED"}
def draw(self, context):
crackit = context.scene.crackit
layout = self.layout
# Crack input
box = layout.box()
row = box.row()
# Warning if the fracture cell addon is not enabled
if not check_object_cell_fracture():
col = box.column()
col.label(text="Please enable Object: Cell Fracture addon", icon="INFO")
col.separator()
col.operator("preferences.addon_show",
text="Go to Cell Fracture addon",
icon="PREFERENCES").module = "object_fracture_cell"
layout.separator()
return
else:
row.operator(FractureOperation.bl_idname, icon="SPLITSCREEN")
row = box.row()
row.prop(crackit, "fracture_childverts")
col = box.column(align=True)
col.prop(crackit, "fracture_scalex")
col.prop(crackit, "fracture_scaley")
col.prop(crackit, "fracture_scalez")
col = box.column(align=True)
col.label(text="Settings:")
col.prop(crackit, "fracture_div")
col.prop(crackit, "fracture_margin")
col = box.column(align=True)
col.label(text="Extrude:")
col.prop(crackit, "extrude_offset")
col.prop(crackit, "extrude_random")
# material Preset:
box = layout.box()
row = box.row()
row.label(text="Material Preset:")
row_sub = row.row()
row_sub.prop(crackit, "material_lib_name", text="",
toggle=True, icon="LONGDISPLAY")
row = box.row()
row.prop(crackit, "material_preset")
row = box.row()
row.operator(MaterialOperation.bl_idname)