# ##### 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, 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=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.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.create_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=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.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) 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: fracture_group(context, self.group) return {'FINISHED'} ##################################################################### # 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'}