2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -0,0 +1,525 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# if "bpy" in locals():
# import importlib
# importlib.reload(fracture_cell_setup)
import bpy
from bpy.props import (
StringProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
EnumProperty,
)
from bpy.types import Operator
def main_object(context, collection, 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_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")
scene = context.scene
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, collection, obj, **kw_copy)
objects = fracture_cell_setup.cell_fracture_boolean(
context, collection, 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:
context_override = {"selected_editable_objects": objects}
with bpy.context.temp_override(**context_override):
bpy.ops.object.origin_set(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(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, collection, 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,
)
if kw_copy["use_debug_redraw"]:
obj.display_type = obj_display_type_prev
# testing only!
# obj.hide = True
return objects
def main(context, **kw):
import time
t = time.time()
objects_context = context.selected_editable_objects
kw_copy = kw.copy()
# mass
mass_mode = kw_copy.pop("mass_mode")
mass = kw_copy.pop("mass")
collection_name = kw_copy.pop("collection_name")
collection = context.collection
if collection_name:
collection_current = collection
collection = bpy.data.collections.get(collection_name)
if collection is None:
collection = bpy.data.collections.new(collection_name)
collection_current.children.link(collection)
del collection_current
objects = []
for obj in objects_context:
if obj.type == 'MESH':
objects += main_object(context, collection, obj, 0, **kw_copy)
bpy.ops.object.select_all(action='DESELECT')
for obj_cell in objects:
obj_cell.select_set(True)
# FIXME(campbell): we should be able to initialize rigid-body data.
if mass_mode == 'UNIFORM':
for obj_cell in objects:
rb = obj_cell.rigid_body
if rb is not None:
rb.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.copy()
for i in range(0, 8):
bb_vec = matrix @ 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()
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):
rb = obj_cell.rigid_body
if rb is not None:
rb.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', 'UNDO'}
# -------------------------------------------------------------------------
# Source Options
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', "Annotation Pencil", "Annotation Grease Pencil."),
),
options={'ENUM_FLAG'},
default={'PARTICLE_OWN'},
)
source_limit: IntProperty(
name="Source Limit",
description="Limit the number of input points, 0 for unlimited",
min=0, max=5000,
default=100,
)
source_noise: FloatProperty(
name="Noise",
description="Randomize point distribution",
min=0.0, max=1.0,
default=0.0,
)
cell_scale: FloatVectorProperty(
name="Scale",
description="Scale Cell Shape",
size=3,
min=0.0, max=1.0,
default=(1.0, 1.0, 1.0),
)
# -------------------------------------------------------------------------
# Recursion
recursion: IntProperty(
name="Recursion",
description="Break shards recursively",
min=0, max=5000,
default=0,
)
recursion_source_limit: IntProperty(
name="Source Limit",
description="Limit the number of input points, 0 for unlimited (applies to recursion only)",
min=0, max=5000,
default=8,
)
recursion_clamp: IntProperty(
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="Random Factor",
description="Likelihood of recursion",
min=0.0, max=1.0,
default=0.25,
)
recursion_chance_select: EnumProperty(
name="Recurse Over",
items=(
('RANDOM', "Random", ""),
('SIZE_MIN', "Small", "Recursively subdivide smaller objects"),
('SIZE_MAX', "Big", "Recursively subdivide bigger objects"),
('CURSOR_MIN', "Cursor Close", "Recursively subdivide objects closer to the cursor"),
('CURSOR_MAX', "Cursor Far", "Recursively subdivide objects farther from the cursor"),
),
default='SIZE_MIN',
)
# -------------------------------------------------------------------------
# Mesh Data Options
use_smooth_faces: BoolProperty(
name="Smooth Interior",
description="Smooth Faces of inner side",
default=False,
)
use_sharp_edges: BoolProperty(
name="Sharp Edges",
description="Set sharp edges when disabled",
default=True,
)
use_sharp_edges_apply: BoolProperty(
name="Apply Split Edge",
description="Split sharp hard edges",
default=True,
)
use_data_match: BoolProperty(
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="Material",
description="Material index for interior faces",
default=0,
)
use_interior_vgroup: BoolProperty(
name="Interior VGroup",
description="Create a vertex group for interior verts",
default=False,
)
# -------------------------------------------------------------------------
# Physics Options
mass_mode: EnumProperty(
name="Mass Mode",
items=(
('VOLUME', "Volume", "Objects get part of specified mass based on their volume"),
('UNIFORM', "Uniform", "All objects get the specified mass"),
),
default='VOLUME',
)
mass: FloatProperty(
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.
collection_name: StringProperty(
name="Collection",
description=(
"Create objects in a collection "
"(use existing or create new)"
),
)
# -------------------------------------------------------------------------
# Debug
use_debug_points: BoolProperty(
name="Debug Points",
description="Create mesh data showing the points used for fracture",
default=False,
)
use_debug_redraw: BoolProperty(
name="Show Progress Realtime",
description="Redraw as fracture is done",
default=True,
)
use_debug_bool: BoolProperty(
name="Debug Boolean",
description="Skip applying the boolean modifier",
default=False,
)
def execute(self, context):
keywords = self.as_keywords() # ignore=("blah",)
main(context, **keywords)
return {'FINISHED'}
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 in fact 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, "collection_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.separator()
layout.operator("object.add_fracture_cell_objects", text="Cell Fracture")
def register():
bpy.utils.register_class(FractureCell)
bpy.types.VIEW3D_MT_object_quick_effects.append(menu_func)
def unregister():
bpy.utils.unregister_class(FractureCell)
bpy.types.VIEW3D_MT_object_quick_effects.remove(menu_func)
if __name__ == "__main__":
register()
@@ -0,0 +1,16 @@
schema_version = "1.0.0"
id = "cell_fracture"
name = "Cell Fracture"
version = "0.2.1"
tagline = "Fractured Object Creation"
maintainer = "Community"
type = "add-on"
tags = ["Object"]
blender_version_min = "4.2.0"
license = ["SPDX:GPL-3.0-or-later"]
website = "https://projects.blender.org/extensions/object_fracture_cell"
copyright = [
"2024 ideasman42",
"2024 phymec",
"2024 Sergey Sharybin",
]
@@ -0,0 +1,103 @@
# SPDX-FileCopyrightText: 2012 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
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,448 @@
# SPDX-FileCopyrightText: 2012 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
import bmesh
if bpy.app.background:
def _redraw_yasiamevil():
pass
else:
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(context, depsgraph, 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:
ob_eval = obj.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):
obj_eval = obj.evaluated_get(depsgraph)
points.extend([p.location.copy()
for psys in obj_eval.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_index:
for layer in gp.layers:
if layer.info == gp.layers.active_note:
frame = layer.active_frame
return [get_points(stroke) for stroke in frame.strokes]
else:
return []
if 'PENCIL' in source:
# Used to be from object in 2.7x, now from scene.
gp = context.annotation_data
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, collection, 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
depsgraph = context.evaluated_depsgraph_get()
scene = context.scene
view_layer = context.view_layer
# -------------------------------------------------------------------------
# GET POINTS
points = _points_from_object(context, depsgraph, obj, source)
if not points:
# print using fallback
points = _points_from_object(context, depsgraph, 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:] = []
# sadly we can't 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()
# Smooth faces will remain only inner faces, after applying 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 = obj.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
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()
return objects
def cell_fracture_boolean(
context, collection, 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 = []
scene = context.scene
view_layer = context.view_layer
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))
# Calculates all booleans at once (faster).
depsgraph = context.evaluated_depsgraph_get()
for obj_cell in objects:
if not use_debug_bool:
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(obj_cell.modifiers[-1])
# 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(False)
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 view_layer.objects if obj_cell.select_get()]
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:
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()