diff options
Diffstat (limited to 'object_fracture/fracture_ops.py')
-rw-r--r-- | object_fracture/fracture_ops.py | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/object_fracture/fracture_ops.py b/object_fracture/fracture_ops.py new file mode 100644 index 00000000..c6043b8e --- /dev/null +++ b/object_fracture/fracture_ops.py @@ -0,0 +1,492 @@ +# ##### 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), + layer=(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.verts: + 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), + layer=(True, False, False, False, + False, False, False, False, + False, False, False, False, + False, 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.verts: + 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.verts: + 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.selected = True + + ''' + # Adding fracture material + # @todo Doesn't work at all yet. + sce = bpy.context.scene + if bpy.data.materials.get('fracture')==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.verts: + 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.verts: + for v in f.verts: + if v == i: + for v1 in f.verts: + 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.selected = True + bpy.ops.object.duplicate(linked=False, mode=1) + 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.verts) - 1, -1, -1): + if vgi[x] != gi: + #print('getIslands: selecting') + #print('getIslands: ' + str(x)) + a.data.verts[x].selected = 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.selected = True + sce.objects.active = ob + cutter.selected = False + + bpy.ops.object.modifier_add(type='BOOLEAN') + a = sce.objects.active + a.modifiers['Boolean'].object = cutter + a.modifiers['Boolean'].operation = op + + nmesh = a.create_mesh(sce, apply_modifiers=True, settings='PREVIEW') + + if len(nmesh.verts) > 0: + a.modifiers.remove(a.modifiers['Boolean']) + bpy.ops.object.duplicate(linked=False, mode=1) + + 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') + + elif min(nmesh.edge_face_count) < 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.selected: + 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.selected + 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) + props = self.properties + + if props.exe: + fracture_basic(context, + props.nshards, + props.crack_type, + props.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) + + 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.properties.exe: + fracture_group(context, self.properties.group) + + return {'FINISHED'} + + +##################################################################### +# Import Functions + +def import_object(obname): + opath = "//data.blend\\Object\\" + obname + s = os.sep + dpath = bpy.utils.script_paths()[0] + \ + '%saddons%sfracture%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'} |