diff options
Diffstat (limited to 'object_fracture_cell/process/cell_functions.py')
-rw-r--r-- | object_fracture_cell/process/cell_functions.py | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/object_fracture_cell/process/cell_functions.py b/object_fracture_cell/process/cell_functions.py new file mode 100644 index 00000000..80f77870 --- /dev/null +++ b/object_fracture_cell/process/cell_functions.py @@ -0,0 +1,599 @@ +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): + 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.btool.boolean_inters() + bpy.ops.object.modifier_apply(apply_as='DATA', modifier="BTool_" + original.name) + + 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 + bpy.ops.btool.remove(thisObj=original.name, Prop="THIS") + + 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)
\ No newline at end of file |