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:
parent
8f4c893499
commit
3f68f8442a
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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'}
|
|
@ -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 ここでルートでマックスを下げているのか?でも下で2倍にしているが。
|
||||
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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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]
|
||||
'''
|
|
@ -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
|
Binary file not shown.
|
@ -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
|
|
@ -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()
|
|
@ -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.
|
@ -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)
|
Loading…
Reference in New Issue