# Blender and Python Gists # BLENDER PYTHON GISTS # ## functions ## some functions that could be helpful when scripting things in Blender * basic launch command structure: `blender $TEMPLATE --background --python $SCRIPT ` ### clear_scene ### ``` def clear_scene(): bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete(use_global=False) ``` ### set_renderer ### ``` bpy.context.scene.render.engine = 'CYCLES' # optionally set current viewport # bpy.context.window.workspace = bpy.data.workspaces["Layout"] for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for space in area.spaces: if space.type == 'VIEW_3D': space.shading.type = 'RENDERED' ``` ### animation concept ### ``` # X, Y, and Z location to set default_cube.location = 0.0, 0.0, 0.0 # Set the keyframe with that location, and which frame. default_cube.keyframe_insert(data_path="location", frame=1) # do it again! default_cube.location = 3.0, 2.0, 1.0 # setting it for frame 10 default_cube.keyframe_insert(data_path="location", frame=10) ``` ### LAUNCH SCRIPT TEMPLATE ``` #! /usr/bin/env zsh BLENDER_WORK_FOLDER="$HOME/Desktop/project-blender-py/blender-work" RENDER_FOLDER="$HOME/Desktop/project-blender-py/renders" TEMPLATE_FOLDER="$HOME/Desktop/project-blender-py/blender-templates" PYTHON_SCRIPT="$HOME/Desktop/project-blender-py/bpy-scripts/projects/bar-graphs/launch.py" CURRENT_TIME=$(date "+%Y%m%d%H%M%S") OUTPUT_PATH="$BLENDER_WORK_FOLDER/output-$CURRENT_TIME.blend" RENDER_PATH="$RENDER_FOLDER/scripted/test-$CURRENT_TIME.png" TEMPLATE="$TEMPLATE_FOLDER/just-a-cam.blend" DATE_FOR_TEXT=$(date "+%Y%m%d") TIME_FOR_TEXT=$(date "+%H:%M:%S") echo "the date for text is being sent in as $DATE_FOR_TEXT" # blender $TEMPLATE --background --python $PYTHON_SCRIPT --SHOOT_DATE="$DATE_FOR_TEXT" --SHOOT_TIME="$TIME_FOR_TEXT" --OUTPUT_PATH="$OUTPUT_PATH" --RENDER_PATH="$RENDER_PATH" # open "$OUTPUT_PATH" -a blender # open $RENDER_PATH -a Preview /Applications/Blender.app/Contents/MacOS/blender $TEMPLATE --python $PYTHON_SCRIPT -- --SHOOT_DATE="$DATE_FOR_TEXT" --SHOOT_TIME="$TIME_FOR_TEXT" --OUTPUT_PATH="$OUTPUT_PATH" --RENDER_PATH="$RENDER_PATH" ``` ### MORE GISTS ### * round to number ``` def round_to(n, decimals=3): multiplier = 10 ** decimals return round(n*multiplier) / multiplier ``` * get radius ``` def getRadius(pQuant): print(f'getting radius for value {pQuant}') radius = round_half_up((pQuant*3./(4.*3.14159))**(1./3)) return radius ``` * append object ``` bpy.ops.wm.append(directory=f'{homeDirectory}/Desktop/_blender/prefabs/pumpkin-template.blend\\Object\\', filename="Pumpkin") ``` * append structure ``` bpy.ops.wm.append(directory={FILEPATH}, filename={OBJECT_NAME}) ``` * change location and rotation of object with known name ``` bpy.data.objects["Plane"].location[0]=-120 bpy.data.objects["Plane"].rotation_euler[2]=-1.18 ``` * open json file as data ``` with open(f'{homeDirectory}/Desktop/_blender/data/pumpkin-data.json') as data_file: data = json.load(data_file) ``` * grab template object and hide in render and viewport ``` pumpkinTemplate = bpy.data.objects["Pumpkin"] pumpkinTemplate.hide_render = True pumpkinTemplate.hide_viewport = True ``` * example of creating series of text objects with loop ``` for i, country in enumerate(data): xVal+=(country["radius"]) print(f'working on {country["name"]}, which produced {country["pumpkins"]} pumpkins.') bpy.ops.object.text_add(enter_editmode=False, location=(0, 0, 0)) bpy.context.active_object.name=(f'{country["name"]}-text') bpy.context.active_object.data.body=f'{country["name"]}\n{country["pumpkins"]}' bpy.context.active_object.data.size=15 bpy.context.active_object.data.align_x='CENTER' bpy.context.active_object.data.extrude=3 bpy.context.active_object.data.bevel_depth=.15 bpy.context.active_object.location[0] = xVal bpy.context.active_object.location[1] = -34 bpy.context.active_object.rotation_euler[0] = 1.5708 bpy.ops.object.add_named(linked=False, name='Pumpkin') bpy.context.active_object.name = f'{country["name"]}-pumpkin' print(f'when we do the radius, it will be {getRadius(country["pumpkins"])}') bpy.context.active_object.location[0] = xVal bpy.context.active_object.location[1] = 0 bpy.context.active_object.location[2] = country["radius"] bpy.context.active_object.scale = [country["radius"],country["radius"],country["radius"]] bpy.context.object.hide_render = False bpy.context.object.hide_viewport = False xVal+=(country["radius"]+10) ``` * save file as ``` bpy.ops.wm.save_as_mainfile(filepath=f'{homeDirectory}/Desktop/output.blend') ``` * parse command line arguments ``` import argparse def get_arguments(): argv = sys.argv usage_text = ( "Run blender in background mode with this script:" " blender --background --python " + __file__ + " -- [options]" ) if "--" not in argv: argv = [] # as if no args are passed else: argv = argv[argv.index("--") + 1:] # get all args after "--" parser = argparse.ArgumentParser(description=usage_text) parser.add_argument( "-t", "--text", dest="text", type=str, required=False, help="This text will be used to render an image", ) parser.add_argument( "-o", "--OUTPUT_PATH", dest="OUTPUT_PATH", type=str, required=True, help="This text will be used to define the output path", ) parser.add_argument( "--RENDER_PATH", dest="RENDER_PATH", type=str, required=True, help="This text will be used to define the render path", ) parser.add_argument( "--SHOOT_DATE", dest="SHOOT_DATE", type=str, required=True, help="This text will be used to define the shoot date string", ) parser.add_argument( "--SHOOT_TIME", dest="SHOOT_TIME", type=str, required=True, help="This text will be used to define the shoot time string", ) args = parser.parse_args(argv) return args theArgs = get_arguments() print(f'the shoot date is {theArgs.SHOOT_DATE}') ``` * typical text object manipulation ``` bpy.ops.object.text_add(enter_editmode=False, location=(0, 0, 0)) bpy.context.active_object.name=(f'shoot-time') bpy.context.active_object.data.body=f'{the_args.SHOOT_TIME}' bpy.context.active_object.data.size=.2 bpy.context.active_object.data.align_x='CENTER' bpy.context.active_object.data.extrude=.04 bpy.context.active_object.data.bevel_depth=.0007 bpy.context.active_object.location[0] = 0 bpy.context.active_object.location[1] = -1 bpy.context.active_object.location[2] = -.29 bpy.context.active_object.rotation_euler[0] = 1.5708 ``` * add a plane and a light ``` bpy.ops.mesh.primitive_plane_add(size=10, location=(0,0,-.3)) bpy.ops.object.light_add(type='SPOT', radius=1, location=(0, -4, 15)) bpy.context.active_object.data.energy=5000 ``` * render ``` print("rendering") sceneKey = bpy.data.scenes.keys()[0] print(f'sceneKey = {sceneKey}') for obj in bpy.data.objects: if ( obj.type == 'CAMERA' ): print(f'Rendering scene: {sceneKey}. Camera name: {obj.name}') bpy.data.scenes[sceneKey].camera = obj bpy.data.scenes[sceneKey].render.engine = 'BLENDER_EEVEE' bpy.data.scenes[sceneKey].render.resolution_x = 1920 bpy.data.scenes[sceneKey].render.resolution_y = 1080 bpy.data.scenes[sceneKey].render.filepath = the_args.RENDER_PATH bpy.ops.render.render(write_still=True, scene=sceneKey) ``` * render all cams ``` sceneKey = bpy.data.scenes.keys()[0] c=1 for obj in bpy.data.objects: if ( obj.type =='CAMERA'): print("Rendering scene["+sceneKey+"] with Camera["+obj.name+"]") bpy.data.scenes[sceneKey].camera = obj #bpy.data.scenes[sceneKey].render.file_format = 'JPEG' bpy.data.scenes[sceneKey].render.filepath = f'{RENDER_FOLDER}/camera_' + str(c) + '_' # Render Scene and store the scene bpy.ops.render.render( animation=True ) c = c + 1 print('Done!') ``` * create text object with font ``` def createTextObject(textContent, size, rotation, extrusion, alignment): print("creating text with textContent=" + textContent) bpy.ops.object.text_add(enter_editmode=False, align='WORLD', location=(0, 0, 0)) textObject = bpy.context.active_object textObject.data.body = str(textContent) textObject.rotation_euler[0] = radians(rotation) textObject.data.extrude = extrusion textObject.data.size=size textObject.name=textContent textObject.data.align_x = alignment return textObject def quickText(textContent): bpy.ops.object.text_add(enter_editmode=False, align='WORLD', location=(0, 0, 0)) textObject = bpy.context.active_object textObject.data.body = str(textContent) textObject.rotation_euler[0] = radians(90) textObject.data.extrude = 0.1 textObject.data.align_x = 'CENTER' return textObject textObject = createTextObject(str(result), 3, 90, 0.2, 'CENTER') textObject.location[0]=result greeting = quickText("hello Conrad") greeting.location[2]=4 # change the font textObject.data.font=bpy.data.fonts.load("/System/Library/Fonts/Avenir Next.ttc") greeting.data.font=bpy.data.fonts.load("/Users/mk/Library/Fonts/Forum-Regular.ttf") # or SFHeavy = bpy.data.fonts.load("/Library/Fonts/SF-Pro-Display-Heavy.otf") bigLetter = createTextObject("Z", 20, 90, 1.0, 'CENTER') bigLetter.location[1]=10 bigLetter.data.font=SFHeavy ``` * light area ``` def lightTheSpot(spotToLight): bpy.ops.object.light_add(type='AREA', radius=1, align='WORLD', location=(0, 0, 0)) light = bpy.context.active_object light.location[2] = 3 light.location[0] = result light.data.energy = 500 lightTheSpot(result) ``` ### MINECRAFT BLOCK ATTEMPT 4 ### ``` import bpy bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete(use_global=False) grassPathTop="/Users/mk/Desktop/ck-blender-minecraft-project/textures/Minecraft-textures/assets/minecraft/textures/block/grass_path_top.png" grassPathSide="/Users/mk/Desktop/ck-blender-minecraft-project/textures/Minecraft-textures/assets/minecraft/textures/block/grass_block_side.png" class Block: def __init__(self, name, side, top, bottom): bpy.ops.mesh.primitive_cube_add() self.object = bpy.context.active_object self.name = name self.side = side if top: self.top = top else: self.top = self.side if bottom: self.bottom = bottom else: self.bottom = self.side self.sideImage = bpy.data.images.load(self.side) self.sideTex = bpy.data.textures.new(self.side, 'IMAGE') self.sideTex.image = self.sideImage self.sideMaterial = bpy.data.materials.new(name=f"{name}-side-material") self.topMaterial = bpy.data.materials.new(name=f"{name}-top-material") self.bottomMaterial = bpy.data.materials.new(name=f"{name}-bottom-material") self.sideMaterial.use_nodes = True self.topMaterial.use_nodes = True self.bottomMaterial.use_nodes = True bsdf = self.sideMaterial.node_tree.nodes["Principled BSDF"] texImage = self.sideMaterial.node_tree.nodes.new('ShaderNodeTexImage') texImage.image = bpy.data.images.load(self.side) self.sideMaterial.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) self.sideMaterial.node_tree.nodes["Image Texture"].interpolation = 'Closest' if self.object.data.materials: self.object.data.materials[0] = self.sideMaterial else: self.object.data.materials.append(self.sideMaterial) self.object.data.materials.append(self.topMaterial) self.object.data.materials.append(self.bottomMaterial) # # #def set_UV_editor_texture(mesh): # """ set the image for the face.tex layer on all the faces # so we have a rough idea of what the mesh will look like # in the 3D view's Texture render mode""" # # load the mesh data into a bmesh object # bm = bmesh.new() # bm.from_mesh(mesh) # bm.faces.ensure_lookup_table() # # Get the "tex" layer for the first UV map # # If you don't already have a UV map, why are you even calling this function? # tex_layer = bm.faces.layers.tex[mesh.uv_layers[0].name] # for i in range(len(bm.faces)): # # figure out which material this face uses # mi = bm.faces[i].material_index # mat = mesh.materials[mi] # # Assume that we want to use the image from the first texture slot; # # and assume that the material has a texture in that first slot; # # and assume that the texture is an image texture instead of a procedural texture. # # if any of several assumptions are wrong, this will explode # img = mat.texture_slots[0].texture.image # bm.faces[i][tex_layer].image = img # # copy the modified data into the mesh # bm.to_mesh(mesh) #fname = "/var/tmp/blender/mohawk-seal0001.png" #obj = bpy.context.active_object #mat = material_for_texture(fname) #if len(obj.data.materials)<1: # obj.data.materials.append(mat) #else: # obj.data.materials[0] = mat #set_UV_editor_texture(obj.data) blockOne = Block("grass", grassPathSide, grassPathTop, None) #blockTwo = Block("iron", "C:/Users/ll/Desktop/_blender/block/iron_block.png", None, None) blockOne.object.location[2] = 3 ``` ### NEW OBJECT FROM TEMPLATE ### ``` homeDirectory = os.getenv("HOME") blocksFolder = "/Users/mk/Desktop/ck-blender-minecraft-project/blender/blocks" class BlockTemplate(name): def __init__: # get rid of any conflicting objects in scene self.filepath = f"{blocksFolder}/{name}.blend\\Object\\" self.name = name if bpy.data.objects[self.name]: bpy.data.objects[self.name].select_set(True) bpy.ops.object.delete() bpy.ops.wm.append(directory=self.filePath, filename=self.name) #self.object=bpy.context.active_object self.object=bpy.data.objects[self.name] self.object.hide_render = True self.object.hide_viewport = True class Block(template, index): def __init__: bpy.ops.object.add_named(linked=False, name=template) self.object = bpy.context.active_object .name = f'{country["name"]}-pumpkin' print(f'when we do the radius, it will be {getRadius(country["pumpkins"])}') bpy.context.active_object.location[0] = xVal bpy.context.active_object.location[1] = 0 bpy.context.active_object.location[2] = country["radius"] bpy.context.active_object.scale = [country["radius"],country["radius"],country["radius"]] bpy.context.object.hide_render = False bpy.context.object.hide_viewport = False xVal+=(country["radius"]+10) ``` ### try looping through scenes to set viewport to rendered ### not functional right now ``` import bpy for area in bpy.context.screen.areas: if area.type == 'VIEW_3D': for space in area.spaces: if space.type == 'VIEW_3D': space.shading.type = 'MATERIAL' ``` #### add text from arguments #### ``` bpy.ops.object.text_add(enter_editmode=False, location=(0, 0, 0)) if the_args.text: bpy.context.active_object.name=(f'text test') print(the_args.text) bpy.context.active_object.data.body=f'{the_args.text}' elif the_args.SHOOT_DATE: bpy.context.active_object.name=(f'shoot-date') bpy.context.active_object.data.body=f'{the_args.SHOOT_DATE}' else: print("there is no text value to add") ``` renaming selected objects: ``` for obj in bpy.context.selected_objects: obj.name = "newName" for obj in bpy.context.scene.objects: if obj.type == 'MESH' and obj.name.lower().startswith("c") obj.name = "newName" ```