Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'object_fracture/fracture_ops.py')
-rw-r--r--object_fracture/fracture_ops.py496
1 files changed, 496 insertions, 0 deletions
diff --git a/object_fracture/fracture_ops.py b/object_fracture/fracture_ops.py
new file mode 100644
index 00000000..c7a84279
--- /dev/null
+++ b/object_fracture/fracture_ops.py
@@ -0,0 +1,496 @@
+# ##### 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 #####
+
+import bpy
+from bpy.props import *
+import os
+import random
+import mathutils
+from mathutils import *
+
+
+def create_cutter(context, crack_type, scale, roughness):
+ ncuts = 12
+ if crack_type == 'FLAT' or crack_type == 'FLAT_ROUGH':
+ bpy.ops.mesh.primitive_cube_add(
+ view_align=False,
+ enter_editmode=False,
+ location=(0, 0, 0),
+ rotation=(0, 0, 0),
+ layers=(True, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False))
+
+ for v in context.scene.objects.active.data.vertices:
+ v.co[0] += 1
+ v.co[0] *= scale
+ v.co[1] *= scale
+ v.co[2] *= scale
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.faces_shade_smooth()
+ bpy.ops.uv.reset()
+
+ if crack_type == 'FLAT_ROUGH':
+ bpy.ops.mesh.subdivide(
+ number_cuts=ncuts,
+ fractal=roughness * 7 * scale,
+ smoothness=0)
+
+ bpy.ops.mesh.vertices_smooth(repeat=5)
+
+ bpy.ops.object.editmode_toggle()
+
+ if crack_type == 'SPHERE' or crack_type == 'SPHERE_ROUGH':
+ bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4,
+ size=1,
+ view_align=False,
+ enter_editmode=False,
+ location=(0, 0, 0),
+ rotation=(0, 0, 0),
+ layers=(True, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False))
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.faces_shade_smooth()
+ bpy.ops.uv.smart_project(angle_limit=66, island_margin=0)
+
+ bpy.ops.object.editmode_toggle()
+ for v in context.scene.objects.active.data.vertices:
+ v.co[0] += 1
+ v.co[0] *= scale
+ v.co[1] *= scale
+ v.co[2] *= scale
+
+ if crack_type == 'SPHERE_ROUGH':
+ for v in context.scene.objects.active.data.vertices:
+ v.co[0] += roughness * scale * 0.2 * (random.random() - 0.5)
+ v.co[1] += roughness * scale * 0.1 * (random.random() - 0.5)
+ v.co[2] += roughness * scale * 0.1 * (random.random() - 0.5)
+
+ bpy.context.scene.objects.active.select = True
+
+ '''
+ # Adding fracture material
+ # @todo Doesn't work at all yet.
+ sce = bpy.context.scene
+ if bpy.data.materials.get('fracture') is None:
+ bpy.ops.material.new()
+ bpy.ops.object.material_slot_add()
+ sce.objects.active.material_slots[0].material.name = 'fracture'
+ else:
+ bpy.ops.object.material_slot_add()
+ sce.objects.active.material_slots[0].material
+ = bpy.data.materials['fracture']
+ '''
+
+
+#UNWRAP
+def getsizefrommesh(ob):
+ bb = ob.bound_box
+ return (
+ bb[5][0] - bb[0][0],
+ bb[3][1] - bb[0][1],
+ bb[1][2] - bb[0][2])
+
+
+def getIslands(shard):
+ sm = shard.data
+ islands = []
+ vgroups = []
+ fgroups = []
+
+ vgi = []
+ for v in sm.vertices:
+ vgi.append(-1)
+
+ gindex = 0
+ for i in range(len(vgi)):
+ if vgi[i] == -1:
+ gproc = [i]
+ vgroups.append([i])
+ fgroups.append([])
+
+ while len(gproc) > 0:
+ i = gproc.pop(0)
+ for f in sm.faces:
+ #if i in f.vertices:
+ for v in f.vertices:
+ if v == i:
+ for v1 in f.vertices:
+ if vgi[v1] == -1:
+ vgi[v1] = gindex
+ vgroups[gindex].append(v1)
+ gproc.append(v1)
+
+ fgroups[gindex].append(f.index)
+
+ gindex += 1
+
+ #print( gindex)
+
+ if gindex == 1:
+ shards = [shard]
+
+ else:
+ shards = []
+ for gi in range(0, gindex):
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.context.scene.objects.active = shard
+ shard.select = True
+ bpy.ops.object.duplicate(linked=False, mode='DUMMY')
+ a = bpy.context.scene.objects.active
+ sm = a.data
+ print (a.name)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.editmode_toggle()
+
+ for x in range(len(sm.vertices) - 1, -1, -1):
+ if vgi[x] != gi:
+ #print('getIslands: selecting')
+ #print('getIslands: ' + str(x))
+ a.data.vertices[x].select = True
+
+ print(bpy.context.scene.objects.active.name)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.delete()
+ bpy.ops.object.editmode_toggle()
+
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ shards.append(a)
+
+ bpy.context.scene.objects.unlink(shard)
+
+ return shards
+
+
+def boolop(ob, cutter, op):
+ sce = bpy.context.scene
+
+ fault = 0
+ new_shards = []
+
+ sizex, sizey, sizez = getsizefrommesh(ob)
+ gsize = sizex + sizey + sizez
+
+ bpy.ops.object.select_all()
+ ob.select = True
+ sce.objects.active = ob
+ cutter.select = False
+
+ bpy.ops.object.modifier_add(type='BOOLEAN')
+ a = sce.objects.active
+ a.modifiers['Boolean'].object = cutter
+ a.modifiers['Boolean'].operation = op
+
+ nmesh = a.to_mesh(sce, apply_modifiers=True, settings='PREVIEW')
+
+ if len(nmesh.vertices) > 0:
+ a.modifiers.remove(a.modifiers['Boolean'])
+ bpy.ops.object.duplicate(linked=False, mode='DUMMY')
+
+ new_shard = sce.objects.active
+ new_shard.data = nmesh
+ #scene.objects.link(new_shard)
+
+ new_shard.location = a.location
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ sizex, sizey, sizez = getsizefrommesh(new_shard)
+ gsize2 = sizex + sizey + sizez
+
+ if gsize2 > gsize * 1.01: # Size check
+ print (gsize2, gsize, ob.name, cutter.name)
+ fault = 1
+ #print ('boolop: sizeerror')
+
+ # This checks whether returned shards are non-manifold.
+ # Problem is, if org mesh is non-manifold, it will always fail (e.g. with Suzanne).
+ # And disabling it does not seem to cause any problem…
+# elif min(mesh_utils.edge_face_count(nmesh)) < 2: # Manifold check
+# fault = 1
+
+ if not fault:
+ new_shards = getIslands(new_shard)
+
+ else:
+ sce.objects.unlink(new_shard)
+
+ else:
+ fault = 2
+
+ return fault, new_shards
+
+
+def splitobject(context, ob, crack_type, roughness):
+ scene = context.scene
+
+ size = getsizefrommesh(ob)
+ shards = []
+ scale = max(size) * 1.3
+
+ create_cutter(context, crack_type, scale, roughness)
+ cutter = context.active_object
+ cutter.location = ob.location
+
+ cutter.location[0] += random.random() * size[0] * 0.1
+ cutter.location[1] += random.random() * size[1] * 0.1
+ cutter.location[2] += random.random() * size[2] * 0.1
+ cutter.rotation_euler = [
+ random.random() * 5000.0,
+ random.random() * 5000.0,
+ random.random() * 5000.0]
+
+ scene.objects.active = ob
+ operations = ['INTERSECT', 'DIFFERENCE']
+
+ for op in operations:
+ fault, newshards = boolop(ob, cutter, op)
+
+ shards.extend(newshards)
+ if fault > 0:
+ # Delete all shards in case of fault from previous operation.
+ for s in shards:
+ scene.objects.unlink(s)
+
+ scene.objects.unlink(cutter)
+ #print('splitobject: fault')
+
+ return [ob]
+
+ if shards[0] != ob:
+ bpy.context.scene.objects.unlink(ob)
+
+ bpy.context.scene.objects.unlink(cutter)
+
+ return shards
+
+
+def fracture_basic(context, nshards, crack_type, roughness):
+ tobesplit = []
+ shards = []
+
+ for ob in context.scene.objects:
+ if ob.select:
+ tobesplit.append(ob)
+
+ i = 1 # I counts shards, starts with 1 - the original object
+ iter = 0 # counts iterations, to prevent eternal loops in case
+ # of boolean faults
+
+ maxshards = nshards * len(tobesplit)
+
+ while i < maxshards and len(tobesplit) > 0 and iter < maxshards * 10:
+ ob = tobesplit.pop(0)
+ newshards = splitobject(context, ob, crack_type, roughness)
+
+ tobesplit.extend(newshards)
+
+ if len(newshards) > 1:
+ shards.extend(newshards)
+ #shards.remove(ob)
+
+ i += (len(newshards) - 1)
+
+ #print('fracture_basic: ' + str(i))
+ #print('fracture_basic: lenobs', len(context.scene.objects))
+
+ iter += 1
+
+
+def fracture_group(context, group):
+ tobesplit = []
+ shards = []
+
+ for ob in context.scene.objects:
+ if (ob.select
+ and (len(ob.users_group) == 0 or ob.users_group[0].name != group)):
+ tobesplit.append(ob)
+
+ cutters = bpy.data.groups[group].objects
+
+ # @todo This can be optimized.
+ # Avoid booleans on obs where bbox doesn't intersect.
+ i = 0
+ for ob in tobesplit:
+ for cutter in cutters:
+ fault, newshards = boolop(ob, cutter, 'INTERSECT')
+ shards.extend(newshards)
+
+ if fault == 1:
+ # Delete all shards in case of fault from previous operation.
+ for s in shards:
+ bpy.context.scene.objects.unlink(s)
+
+ #print('fracture_group: fault')
+ #print('fracture_group: ' + str(i))
+
+ return
+
+ i += 1
+
+
+class FractureSimple(bpy.types.Operator):
+ '''Split object with boolean operations for simulation, uses an object.'''
+ bl_idname = "object.fracture_simple"
+ bl_label = "Fracture Object"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ exe = BoolProperty(name="Execute",
+ description="If it shall actually run, for optimal performance...",
+ default=False)
+
+ hierarchy = BoolProperty(name="Generate hierarchy",
+ description="Hierarchy is usefull for simulation of objects" \
+ " breaking in motion.",
+ default=False)
+
+ nshards = IntProperty(name="Number of shards",
+ description="Number of shards the object should be split into.",
+ min=2,
+ default=5)
+
+ crack_type = EnumProperty(name='Crack type',
+ items=(
+ ('FLAT', 'Flat', 'a'),
+ ('FLAT_ROUGH', 'Flat rough', 'a'),
+ ('SPHERE', 'Spherical', 'a'),
+ ('SPHERE_ROUGH', 'Spherical rough', 'a')),
+ description='Look of the fracture surface',
+ default='FLAT')
+
+ roughness = FloatProperty(name="Roughness",
+ description="Roughness of the fracture surface",
+ min=0.0,
+ max=3.0,
+ default=0.5)
+
+ def execute(self, context):
+ #getIslands(context.object)
+ if self.exe:
+ fracture_basic(context,
+ self.nshards,
+ self.crack_type,
+ self.roughness)
+
+ return {'FINISHED'}
+
+
+class FractureGroup(bpy.types.Operator):
+ '''Split object with boolean operations for simulation, uses a group.'''
+ bl_idname = "object.fracture_group"
+ bl_label = "Fracture Object (Group)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ exe = BoolProperty(name="Execute",
+ description="If it shall actually run, for optimal performance...",
+ default=False)
+
+ group = StringProperty(name="Group",
+ description="Specify the group used for fracturing")
+
+# e = []
+# for i, g in enumerate(bpy.data.groups):
+# e.append((g.name, g.name, ''))
+# group = EnumProperty(name='Group (hit F8 to refresh list)',
+# items=e,
+# description='Specify the group used for fracturing')
+
+ def execute(self, context):
+ #getIslands(context.object)
+
+ if self.exe and self.group:
+ fracture_group(context, self.group)
+
+ return {'FINISHED'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "exe")
+ layout.prop_search(self, "group", bpy.data, "groups")
+
+#####################################################################
+# Import Functions
+
+def import_object(obname):
+ opath = "//data.blend\\Object\\" + obname
+ s = os.sep
+ dpath = bpy.utils.script_paths()[0] + \
+ '%saddons%sobject_fracture%sdata.blend\\Object\\' % (s, s, s)
+
+ # DEBUG
+ #print('import_object: ' + opath)
+
+ bpy.ops.wm.link_append(
+ filepath=opath,
+ filename=obname,
+ directory=dpath,
+ filemode=1,
+ link=False,
+ autoselect=True,
+ active_layer=True,
+ instance_groups=True,
+ relative_path=True)
+
+ for ob in bpy.context.selected_objects:
+ ob.location = bpy.context.scene.cursor_location
+
+
+class ImportFractureRecorder(bpy.types.Operator):
+ '''Imports a rigidbody recorder'''
+ bl_idname = "object.import_fracture_recorder"
+ bl_label = "Add Rigidbody Recorder (Fracture)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ import_object("RECORDER")
+
+ return {'FINISHED'}
+
+
+class ImportFractureBomb(bpy.types.Operator):
+ '''Import a bomb'''
+ bl_idname = "object.import_fracture_bomb"
+ bl_label = "Add Bomb (Fracture)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ import_object("BOMB")
+
+ return {'FINISHED'}
+
+
+class ImportFractureProjectile(bpy.types.Operator, ):
+ '''Imports a projectile'''
+ bl_idname = "object.import_fracture_projectile"
+ bl_label = "Add Projectile (Fracture)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ import_object("PROJECTILE")
+
+ return {'FINISHED'}