├── LICENSE └── render_8_direction_sprites /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Joe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /render_8_direction_sprites: -------------------------------------------------------------------------------- 1 | import bpy 2 | import os 3 | import math 4 | 5 | def render8directions_selected_objects(path): 6 | # path fixing 7 | path = os.path.abspath(path) 8 | 9 | # get list of selected objects 10 | selected_list = bpy.context.selected_objects 11 | 12 | # deselect all in scene 13 | bpy.ops.object.select_all(action='TOGGLE') 14 | 15 | s=bpy.context.scene 16 | 17 | s.render.resolution_x = 256 # set to whatever you want! 18 | s.render.resolution_y = 256 19 | 20 | # I left this in as in some of my models, I needed to translate the "root" object but 21 | # the animations were on the armature which I selected. 22 | # 23 | # obRoot = bpy.context.scene.objects["root"] 24 | 25 | # The below is if you also want to render a normal to use in your engine for lighting. 26 | # For this to work, you need to: 27 | # 1. Enable "Normal" under View Layer - > Passes - > Data - > Normal in the properties panel. 28 | # 2. In the compositing node tree, you need to add an: Output - > File Output node. 29 | # 3. Connect the Normal node from the Rendered Layers Node to the File Output node. 30 | # 4. Select the File Output node and change the label in it's properties to 'Normal'. We will use this to dynamically find the node and dynamically change the output path. 31 | 32 | # Access the File Output node in the compositor for the normal map 33 | node_tree = s.node_tree 34 | normal_output = None 35 | for node in node_tree.nodes: 36 | if node.type == 'OUTPUT_FILE' and 'Normal' in node.label: # Ensure it's the right node by label 37 | normal_output = node 38 | node.base_path = "" # Clear the base path to prevent concatenation 39 | break 40 | 41 | if normal_output is None: 42 | print("Normal output node not found.") 43 | return 44 | # Assuming we have the normal_output node from the above, we can then use for each frame to update the output path while rendering lower in the code. 45 | 46 | # loop all initial selected objects (which will likely just be one obect.. I haven't tried setting up multiple yet) 47 | for o in selected_list: 48 | 49 | # select the object 50 | bpy.context.scene.objects[o.name].select_set(True) 51 | 52 | scn = bpy.context.scene 53 | 54 | # loop through the actions 55 | for a in bpy.data.actions: 56 | #assign the action 57 | bpy.context.active_object.animation_data.action = bpy.data.actions.get(a.name) 58 | 59 | #dynamically set the last frame to render based on action 60 | scn.frame_end = int(bpy.context.active_object.animation_data.action.frame_range[1]) 61 | 62 | #set which actions you want to render. Make sure to use the exact name of the action! 63 | if ( 64 | # a.name == "idle" 65 | a.name == "run" 66 | # or a.name == "dying" 67 | ): 68 | #create folder for animation 69 | action_folder = os.path.join(path, a.name) 70 | if not os.path.exists(action_folder): 71 | os.makedirs(action_folder) 72 | 73 | #loop through all 8 directions 74 | for angle in range(0, 360, 45): 75 | if angle == 0: 76 | angleDir = "E" 77 | if angle == 45: 78 | angleDir = "NE" 79 | if angle == 90: 80 | angleDir = "N" 81 | if angle == 135: 82 | angleDir = "NW" 83 | if angle == 180: 84 | angleDir = "W" 85 | if angle == 225: 86 | angleDir = "SW" 87 | if angle == 270: 88 | angleDir = "S" 89 | if angle == 315: 90 | angleDir = "SE" 91 | 92 | #set which angles we want to render. 93 | if ( 94 | angle == 0 95 | or angle == 45 96 | or angle == 90 97 | or angle == 135 98 | or angle == 180 99 | or angle == 225 100 | or angle == 270 101 | or angle == 315 102 | ): 103 | 104 | # Create folder for specific angle renders and normals 105 | animation_folder = os.path.join(action_folder, angleDir) 106 | if not os.path.exists(animation_folder): 107 | os.makedirs(animation_folder) 108 | 109 | # Feel free to disable below if not using normals. 110 | normal_folder = os.path.join(action_folder, f"{angleDir}_Normals") 111 | if not os.path.exists(normal_folder): 112 | os.makedirs(normal_folder) 113 | 114 | #rotate the model for the new angle 115 | bpy.context.active_object.rotation_euler[2] = math.radians(angle) 116 | 117 | # the below is for the use case where the root needed to be translated. 118 | # obRoot.rotation_euler[2] = math.radians(angle) 119 | 120 | 121 | #loop through and render frames. Can set how "often" it renders. 122 | #Every frame is likely not needed. Currently set to 2 (every other). 123 | for i in range(s.frame_start,s.frame_end,1): 124 | s.frame_current = i 125 | 126 | s.render.filepath = ( 127 | animation_folder 128 | + "\\" 129 | + str(a.name) 130 | + "_" 131 | + str(angle) 132 | + "_" 133 | + str(s.frame_current).zfill(3) 134 | ) 135 | 136 | # Set the full path for the normal map in the angle-specific normals folder 137 | normal_output.file_slots[0].path = os.path.join( 138 | normal_folder, 139 | f"{a.name}_{angle}_{str(s.frame_current).zfill(3)}_normal" 140 | ) 141 | 142 | bpy.ops.render.render( #{'dict': "override"}, 143 | #'INVOKE_DEFAULT', 144 | False, # undo support 145 | animation=False, 146 | write_still=True 147 | ) 148 | 149 | 150 | render8directions_selected_objects('C:\\tmp\\Mutant') 151 | --------------------------------------------------------------------------------