import sys, code, random, os, math, bpy, numpy, uuid, mathutils, subprocess def pry(globs=globals(), locs=locals()): code.interact(local=dict(globs, **locs)) sys.exit("Aborting execution") def chunk_it(seq, num): avg = len(seq) / float(num) out = [] last = 0.0 while last < len(seq): out.append(seq[int(last):int(last + avg)]) last += avg return out def add_frame(collection, data_paths): for obj in collection: for path in data_paths: obj.keyframe_insert(data_path=path, index=-1) # Hashmap with proba in values def rand_proba(hashmap): return numpy.random.choice( list(hashmap.keys()), 1, p=list(map(lambda x: x/sum(hashmap.values()), hashmap.values())) )[0] def apply_displacement(obj, height_map_folder, strength = 0.2, subdivisions = 2): subdivide(obj, subdivisions) subsurf = obj.modifiers.new(name='subsurf', type='SUBSURF') subsurf.levels = 2 subsurf.render_levels = 2 displace = obj.modifiers.new(name='displace', type='DISPLACE') new_texture = bpy.data.textures.new(name='texture', type='IMAGE') new_texture.image = random_height_map(height_map_folder, low = True) displace.texture = new_texture displace.strength = strength def decimate(obj): modifier = obj.modifiers.new(name='decimate', type='DECIMATE') modifier.decimate_type = 'DISSOLVE' bpy.context.scene.objects.active = obj bpy.ops.object.modifier_apply(modifier="decimate") def look_at(obj): location_camera = CAMERA.matrix_world.to_translation() location_object = obj.matrix_world.to_translation() direction = location_object - location_camera rot_quat = direction.to_track_quat('-Z', 'Y') CAMERA.rotation_euler = rot_quat.to_euler() def shoot(filepath): print('Camera now at location: ' + str(CAMERA.location) + ' / rotation: ' + str(CAMERA.rotation_euler)) bpy.context.scene.render.filepath = filepath if animate: bpy.ops.render.render(animation=animate, write_still=True) else: RENDER_OUTPUT_PATHS.append(filepath) bpy.ops.render.render(write_still=True) def output_name(model_path, index = 0): return './renders/' + os.path.splitext(model_path)[0].split('/')[-1] + '_' + str(index) + '_' + str(datetime.date.today()) + '_' + str(mode) + ('.avi' if animate else '.png') # RGB 0 -> 1 def rand_color_value(): return random.uniform(0, 255) / 255 def rand_location(boundary, positive = False): if positive: return (random.uniform(0, boundary), random.uniform(0, boundary), random.uniform(0, boundary)) return (random.uniform(-boundary, boundary), random.uniform(-boundary, boundary), random.uniform(-boundary, boundary)) def rand_rotation(): return (math.radians(random.uniform(0, 360)), math.radians(random.uniform(0, 360)), math.radians(random.uniform(0, 360))) def unwrap_model(obj): if obj.name.startswith('Camera') or obj.name.startswith('Text') or obj.name.startswith('Cube'): return False bpy.context.scene.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') bpy.ops.uv.unwrap() bpy.ops.object.mode_set(mode='OBJECT') ############# # <materials> ############# # create material and load .osl file from fixtures def load_osl_materials(osl_path): for f in os.listdir(osl_path): if f.endswith('.osl'): material = create_cycles_material('osl_' + f[0:-4] + '_', True) script_node = material.node_tree.nodes.new('ShaderNodeScript') material.node_tree.nodes.new('ShaderNodeOutputMaterial') script_node.mode = 'EXTERNAL' script_node.filepath = osl_path + f assign_node_to_output(material, script_node) def fetch_material(material_name): new_material = bpy.data.materials[material_name].copy() return new_material def assign_material(obj, material): flush_materials(obj.data.materials) if len(obj.data.materials) == 0: obj.data.materials.append(material) else: obj.data.materials[0] = material return material def random_material(materials_list, blacklist = [ 'Smoke Domain Material' ]): eligible_materials = list(set(materials_list) - set(blacklist)) return fetch_material(random.choice(eligible_materials)) # Returns a new Cycles material with default DiffuseBsdf node linked to output def create_cycles_material(name = 'object_material_', clean=False): material = bpy.data.materials.new(name + str(uuid.uuid1())) material.use_nodes = True if clean: flush_nodes(material) return material def random_texture(texture_folder_path): texture_path = texture_folder_path + random.choice(os.listdir(texture_folder_path)) print("LOADING TEXTURE -> " + texture_path) return bpy.data.images.load(texture_path) def random_height_map(height_map_folder, low = False): if low: path = height_map_folder + 'low.png' else: path = height_map_folder + random.choice(os.listdir(height_map_folder)) print("LOADING HEIGHT MAP -> " + path) return bpy.data.images.load(path) def assign_texture_to_material(material, texture): assert material.use_nodes == True texture_node = material.node_tree.nodes.new('ShaderNodeTexImage') node = material.node_tree.nodes.new('ShaderNodeBsdfGlossy') material.node_tree.links.new(texture_node.outputs['Color'], node.inputs['Color']) texture_node.image = texture assign_node_to_output(material, node) def assign_node_to_output(material, new_node): assert material.use_nodes == True output_node = material.node_tree.nodes['Material Output'] material.node_tree.links.new(new_node.outputs[0], output_node.inputs['Surface']) def make_object_reflector(obj, color, reflector_scale, reflector_strength): obj.scale = (reflector_scale, reflector_scale, reflector_scale) make_object_emitter(obj, color, reflector_strength) def make_object_emitter(obj, color, emission_strength = 1): emissive_material = assign_material(obj, fetch_material('emission')) emission_node = emissive_material.node_tree.nodes['Emission'] emission_node.inputs[0].default_value = color emission_node.inputs[1].default_value = emission_strength return emission_node def make_object_gradient_fabulous(obj, color1, color2): material = assign_material(obj, fetch_material('gradient_fabulous')) mixer_node = material.node_tree.nodes['Mix'] mixer_node.inputs['Color1'].default_value = color1 mixer_node.inputs['Color2'].default_value = color2 def texture_object(obj, texture_folder_path): new_material = create_cycles_material() assign_texture_to_material(new_material, random_texture(texture_folder_path)) assign_material(obj, new_material) ############# # </material> ############# def spawn_text(text_file_path, text = None): identifier = str(uuid.uuid1()) new_curve = bpy.data.curves.new(type="FONT",name="text_curve_" + identifier) new_curve.extrude = 0.11 content = text if text else random_text(text_file_path) new_text = bpy.data.objects.new("text_" + content, new_curve) new_text.data.body = content bpy.context.scene.objects.link(new_text) return new_text def wireframize(obj, color, emission_strength = 1, thickness = random.uniform(0.0004, 0.001)): bpy.context.scene.objects.active = obj assert obj.type == 'MESH' obj.modifiers.new(name = 'wireframe', type='WIREFRAME') obj.modifiers['wireframe'].thickness = thickness make_object_emitter(obj, color, emission_strength) return obj # randomize location and rotation of an object def shuffle(obj, boundary): obj.location = rand_location(boundary) obj.rotation_euler = rand_rotation() def add_object(obj, x, y, z, radius): new_obj = infer_primitive(obj, location=(x, y, z), radius=radius) bpy.data.groups['neons'].objects.link(new_obj) group_add(obj, new_obj) return new_obj def infer_primitive(obj, **kwargs): if obj == 'Cube': bpy.ops.mesh.primitive_cube_add(radius = kwargs['radius'], location = kwargs['location']) elif obj == 'Ico': bpy.ops.mesh.primitive_ico_sphere_add(location = kwargs['location']) elif obj == 'Cone': bpy.ops.mesh.primitive_cone_add(location = kwargs['location'], radius1 = kwargs['radius']) elif obj == 'Pyramid': return build_pyramid(location = kwargs['location']) elif obj == 'Plane': bpy.ops.mesh.primitive_plane_add(location = kwargs['location'], radius = kwargs['radius']) return bpy.context.object def group_add(group_name, obj): bpy.data.groups[group_name.lower().title()].objects.link(obj) def last_object_group(group_name): return bpy.data.groups[group_name.lower().title()].objects[-1] def build_composite_object(obj, size, radius): res = [] res.append(build_grid_object(obj, size, -size, radius)) for z in range(0, size): res.append(build_grid_object(obj, size, last_object_group(obj).location.z + 2 * radius, radius)) return res def build_grid_object(obj, size, z_index, radius): res = [] res.append(build_object_line(obj, size, z_index, -size, radius)) for y in range(0, size): res.append(build_object_line(obj, size, z_index, last_object_group(obj).location.y + 2 * radius, radius)) return res def build_object_line(obj, size, z_index, y_index, radius): res = [] res.append(add_object(obj, -size, y_index, z_index, radius)) for x in range(0, size): new_obj = duplicate_object(last_object_group(obj)) group_add(obj, new_obj) res.append(new_obj) new_obj.location = ((last_object_group(obj).location.x + 2 * radius), y_index, z_index) return res # Replace vertex coordinate everywhere def find_and_replace(vector, target, replacement): return mathutils.Vector((float(str(vector.x).replace(target, replacement)), float(str(vector.y).replace(target, replacement)), float(str(vector.z).replace(target, replacement)))) def glitch(obj): bpy.ops.object.mode_set(mode='OBJECT') if obj.type == 'MESH': ints = list(range(10)) target = str(ints.pop(int(random.uniform(0, len(ints) - 1)))) replacement = str(ints.pop(int(random.uniform(0, len(ints))))) for vertex in obj.data.vertices: vertex.co = find_and_replace(vertex.co, target, replacement) elif obj.type == 'CURVE': for p in obj.data.splines.active.points: max_amplitude = 0.5 p.co.z += random.uniform(-max_amplitude, max_amplitude) else: raise TypeError("object cannot be glitched") def displace(obj, max_amplitude = 0.06): bpy.ops.object.mode_set(mode='OBJECT') assert obj.type == 'MESH' for vertex in obj.data.vertices: vertex.co = mathutils.Vector((vertex.co.x + random.uniform(-max_amplitude, max_amplitude), vertex.co.y + random.uniform(-max_amplitude, max_amplitude), vertex.co.z + random.uniform(-max_amplitude, max_amplitude))) def subdivide(obj, cuts): if bpy.context.scene.objects.active != obj: bpy.context.scene.objects.active = obj assert bpy.context.scene.objects.active == obj bpy.ops.object.mode_set(mode='EDIT') for index in range(0, cuts): bpy.ops.mesh.subdivide(cuts) bpy.ops.object.editmode_toggle() # Delete current objects def flush_objects(objs = bpy.data.objects): for obj in objs: bpy.data.objects.remove(obj, do_unlink=True) # Delete materials def flush_materials(mats = bpy.data.materials): for mat in mats: if mat != None: bpy.data.materials.remove(mat, do_unlink=True) def flush_nodes(material): for node in material.node_tree.nodes: material.node_tree.nodes.remove(node) def delete_useless_materials(): for mat in bpy.data.materials: if mat.name.startswith('Material'): bpy.data.materials.remove(mat, do_unlink=True) # Rotate hue to generate a somewhat harmonious palette def adjacent_colors(r, g, b, number): print("Color scheme: adjacent colors") angle = (360 / number) / 360 # angles are in ? h, l, s = colorsys.rgb_to_hls(r, g, b) hue_positions = [] for i in range(number): hue_positions.append(angle * i) h = [(h + offset) % 1 for offset in hue_positions] return [colorsys.hls_to_rgb(hi, l, s) for hi in h] # Use saturation increments to generate a color ramp palette def color_ramp(r, g, b, number): print("Color scheme: color ramp") h, l, s = colorsys.rgb_to_hls(r, g, b) res = [] for i in range(number): saturation = ( s + i * random.uniform(-0.1, 0.1)) lightness = (l + i * random.uniform(-0.1, 0.1) ) hue = (h + i * random.uniform(-0.1, 0.1)) res.append(colorsys.hls_to_rgb(h, lightness, saturation)) return res def rand_color_palette(number): function = random.choice([color_ramp, adjacent_colors]) res = list(map(lambda x: list(x), function(rand_color_value(), rand_color_value(), rand_color_value(), number))) # add alpha component for i in res: i.append(1) print("palette: " + str(res)) return res def build_pyramid(width=random.uniform(1,3), length=random.uniform(1,3), height=random.uniform(1,3), location=(0,0,0)): verts=[] faces=[] verts.append([-(width/2),(length/2),0.0]) verts.append([-(width/2),-(length/2),0.0]) verts.append([(width/2),-(length/2),0.0]) verts.append([(width/2),(length/2),0.0]) verts.append([0.0,0.0,(height/2)]) faces.append([0,1,2,3]) faces.append([0,1,4]) faces.append([1,2,4]) faces.append([2,3,4]) faces.append([3,0,4]) return create_mesh('pyramid_' + str(uuid.uuid1()), verts, faces, location) # Cuts a model horizontally into sub models like a scanner def cut(obj, slices = 10): thiccness = obj.dimensions.z / slices gap = 0.01 * obj.dimensions.z center(obj) print("Slicing " + obj.name + " in " + str(slices) + " parts " + str(thiccness) + " thicc, gap: " + str(gap)) base = obj.location.z - (obj.dimensions.z / 2) for i in range(0,slices - 1): dup = duplicate_object(obj) dup.name = 'subcut_' + obj.name + '_' + str(i) bpy.ops.object.select_all(action='DESELECT') bpy.context.scene.objects.active = dup bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.bisect(plane_co=(0,0,base),plane_no=(0,0,1),clear_outer=False,clear_inner=True) bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.bisect(plane_co=(0,0,base + thiccness),plane_no=(0,0,1),clear_outer=True,clear_inner=False) bpy.ops.object.mode_set(mode='OBJECT') base += thiccness dup.location.z += i * gap dup.location.x += random.uniform(-0.2,0.2) dup.location.y += random.uniform(-0.2,0.2) obj.cycles_visibility.camera = False def duplicate_object(obj): print("Cloning -> " + obj.name) new_object = obj.copy() new_object.data = obj.data.copy() new_object.animation_data_clear() new_object.cycles_visibility.camera = True bpy.context.scene.objects.link(new_object) return new_object def load_random_obj(path): objs = [f for f in os.listdir(path) if f.endswith('.obj') and not f.endswith('_glitched.obj')] bpy.ops.import_scene.obj(filepath = path + random.choice(objs), use_edges=True) return bpy.context.selected_objects[0] def random_text(file_path): lines = open(file_path).readlines() return lines[random.randrange(len(lines))] def add_faces(obj): vertices = [] for v in obj.data.vertices: vertices.append(v.co) new_obj = create_mesh(obj.name, vertices, random_faces(vertices), obj.location) bpy.data.objects.remove(obj, do_unlink=True) return new_obj def random_faces(vertices): faces = [] for i in range(int(len(vertices)/100)): target = vertices[random.choice((range(len(vertices))))] if (random.randint(0, 1) == 1): faces.append(((target + 2), int(target / 6), int(target - 1), target)) else: faces.append((int(target / 6), int(target - 1), target)) return faces ############ # <geometry> ############ def center(obj): bpy.context.scene.objects.active = obj bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) bpy.ops.object.origin_set(type="ORIGIN_CENTER_OF_MASS") local_bounding_box_center = 0.125 * sum((mathutils.Vector(b) for b in obj.bound_box), mathutils.Vector()) obj.location -= local_bounding_box_center obj.location = (0, 0, 0) return obj def resize(obj, minimum = 4.0, maximum = 8.0): print("Resizing: " + obj.name) init_scale = obj.scale assert minimum < maximum scale_multiplier =init_scale.x / (max(obj.dimensions) / (maximum - minimum)) if max(obj.dimensions) > maximum: print("Downscaling by: " + str(scale_multiplier)) while max(obj.dimensions) > maximum: obj.scale = init_scale + mathutils.Vector((- scale_multiplier, - scale_multiplier, - scale_multiplier)) bpy.ops.wm.redraw_timer(type='DRAW', iterations=1) elif max(obj.dimensions) < minimum: print("Upscaling by: " + str(scale_multiplier)) while max(obj.dimensions) < minimum: obj.scale = obj.scale + mathutils.Vector((scale_multiplier, scale_multiplier, scale_multiplier)) bpy.ops.wm.redraw_timer(type='DRAW', iterations=1) def extrude(obj, thickness=0.05): bpy.context.scene.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={"mirror":False}, TRANSFORM_OT_translate={"value":(thickness, 0, 0), "constraint_orientation":'GLOBAL', "mirror":True, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}) bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') def create_line(name, point_list, color, thickness = 0.002, location = (0,0,0)): line_data = bpy.data.curves.new(name=name,type='CURVE') line_data.dimensions = '3D' line_data.fill_mode = 'FULL' # line_data.resolution_u = 4 line_data.bevel_depth = thickness polyline = line_data.splines.new('POLY') # polyline = line_data.splines.new('BEZIER') polyline.points.add(len(point_list)-1) # splines.new return already 1 point # polyline.bezier_points.add(len(point_list)-1) # splines.new return already 1 point for idx, coord in enumerate(point_list): x,y,z = coord polyline.points[idx].co = (x, y, z, 1) # add weight # polyline.bezier_points[idx].co = (x, y, z) polyline.order_u = len(polyline.points)-1 # polyline.use_endpoint_u = True line = bpy.data.objects.new(name, line_data) bpy.context.scene.objects.link(line) line.location = location make_object_emitter(line, color, 1.1) return line def build_segment(location, function, length = 2, pitch = 0.5, name = None): verts = series(length, function, pitch) edges = [] for v in range(0, (len(verts) - 1)): edges.append([v, v+1]) name = name if name else 'segment_' + str(uuid.uuid1()) return create_mesh(name, verts, [], location, edges) def series(length, function, pitch): return list(map(lambda x: (0, x, function(x)), pitched_array(0.0, length, pitch))) def pitched_array(minimum, maximum, pitch): return list(map(lambda x: (minimum + pitch * x), range(int((maximum - minimum) / pitch)))) def create_mesh(name, verts, faces, location, edges=[]): mesh_data = bpy.data.meshes.new("mesh_data") faces = faces if (len(faces) == 0 or len(faces) > 0) else random_faces(verts) mesh_data.from_pydata(verts, edges, faces) mesh_data.update() obj = bpy.data.objects.new(name, mesh_data) obj.location = location bpy.context.scene.objects.link(obj) bpy.context.scene.objects.active = obj center(obj) return obj def camera_path(frame_number, radius = 5): fx = lambda x: radius * math.cos(x) fy = lambda y: radius * math.sin(y) factor = (2 * math.pi / NUMBER_OF_FRAMES) return list(map( lambda t: (fx(t * factor), fy(t * factor), INITIAL_CAMERA_LOCATION[2]), range(0, NUMBER_OF_FRAMES))) # Rotate vector def rotate_vector(angle, axis, vin): # Assume axis is a unit vector. # Find squares of each axis component. xsq = axis.x * axis.x ysq = axis.y * axis.y zsq = axis.z * axis.z cosa = math.cos(angle) sina = math.sin(angle) complcos = 1.0 - cosa complxy = complcos * axis.x * axis.y complxz = complcos * axis.x * axis.z complyz = complcos * axis.y * axis.z sinx = sina * axis.x siny = sina * axis.y sinz = sina * axis.z # Construct the x-axis (i). ix = complcos * xsq + cosa iy = complxy + sinz iz = complxz - siny # Construct the y-axis (j). jx = complxy - sinz jy = complcos * ysq + cosa jz = complyz + sinx # Construct the z-axis (k). kx = complxz + siny ky = complyz - sinx kz = complcos * zsq + cosa vout = mathutils.Vector((0.0, 0.0, 0.0)) vout.x = ix * vin.x + jx * vin.y + kx * vin.z vout.y = iy * vin.x + jy * vin.y + ky * vin.z vout.z = iz * vin.x + jz * vin.y + kz * vin.z return vout ############# # </geometry> ############# ############# # <physics> ############# def add_rigid_body(objs=[], type = 'ACTIVE', mass=2.0): for obj in objs: obj.select = True bpy.ops.rigidbody.objects_add(type='ACTIVE') for obj in objs: if type == 'ACTIVE': obj.rigid_body.mass = mass obj.rigid_body.collision_shape = 'MESH' ############# # </physics> #############