import bpy import os import mathutils from bpy.props import StringProperty from bpy.types import Panel, Operator class RENDER_OT_multi_angle(Operator): """Render animation from multiple angles""" bl_idname = "render.multi_angle" bl_label = "Render Multi-Angle Animation" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): scene = context.scene animation_name = scene.multi_angle_props.animation_name if not animation_name: self.report({'ERROR'}, "Please enter an animation name") return {'CANCELLED'} # Find camera and its parent camera = None camera_parent = None # Look for active camera first if scene.camera: camera = scene.camera if camera.parent: camera_parent = camera.parent # If no active camera or no parent, try to find a parented camera if not camera or not camera_parent: for obj in scene.objects: if obj.type == 'CAMERA' and obj.parent: camera = obj camera_parent = obj.parent scene.camera = camera # Set as active camera break if not camera: self.report({'ERROR'}, "No camera found in the scene") return {'CANCELLED'} if not camera_parent: self.report({'ERROR'}, "Camera must be parented to an empty object") return {'CANCELLED'} # Store original rotation original_rotation = camera_parent.rotation_euler.copy() # Define 8 angles (in degrees) and their directory names angles_and_dirs = [ (0, "s"), # South (45, "sw"), # Southwest (90, "w"), # West (135, "nw"), # Northwest (180, "n"), # North (225, "ne"), # Northeast (270, "e"), # East (315, "se") # Southeast ] # Store original output path original_filepath = scene.render.filepath base_path = bpy.path.abspath(original_filepath) # Create main animation directory if it doesn't exist if base_path: base_dir = os.path.dirname(base_path) else: base_dir = bpy.path.abspath("//") # Use blend file directory animation_dir = os.path.join(base_dir, animation_name) try: os.makedirs(animation_dir, exist_ok=True) except OSError as e: self.report({'ERROR'}, f"Could not create directory: {e}") return {'CANCELLED'} total_frames = scene.frame_end - scene.frame_start + 1 total_renders = len(angles_and_dirs) * total_frames current_render = 0 self.report({'INFO'}, f"Starting multi-angle render: {len(angles_and_dirs)} angles") # Render from each angle for angle_deg, direction in angles_and_dirs: # Create subdirectory for this angle angle_dir = os.path.join(animation_dir, direction) try: os.makedirs(angle_dir, exist_ok=True) except OSError as e: self.report({'ERROR'}, f"Could not create directory {angle_dir}: {e}") continue # Set rotation (convert degrees to radians) angle_rad = mathutils.Matrix.Rotation(mathutils.radians(angle_deg), 4, 'Z') camera_parent.rotation_euler = angle_rad.to_euler() # Update scene bpy.context.view_layer.update() # Set output path for this angle scene.render.filepath = os.path.join(angle_dir, f"{animation_name}_{direction}_") self.report({'INFO'}, f"Rendering {direction} angle ({angle_deg}°)") # Render animation bpy.ops.render.render(animation=True) current_render += total_frames progress = (current_render / total_renders) * 100 self.report({'INFO'}, f"Progress: {progress:.1f}% - Completed {direction}") # Restore original settings camera_parent.rotation_euler = original_rotation scene.render.filepath = original_filepath bpy.context.view_layer.update() self.report({'INFO'}, f"Multi-angle render complete! Files saved in: {animation_dir}") return {'FINISHED'} class MultiAngleProperties(bpy.types.PropertyGroup): animation_name: StringProperty( name="Animation Name", description="Name for the animation (will create folder with this name)", default="my_animation" ) class RENDER_PT_multi_angle_panel(Panel): """Multi-Angle Renderer Panel""" bl_label = "Multi-Angle Renderer" bl_idname = "RENDER_PT_multi_angle" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "render" def draw(self, context): layout = self.layout scene = context.scene props = scene.multi_angle_props # Instructions box = layout.box() box.label(text="Setup Requirements:", icon='INFO') box.label(text="• Camera must be parented to an Empty") box.label(text="• Empty should be at scene center") box.label(text="• Set your frame range before rendering") layout.separator() # Animation name input layout.prop(props, "animation_name") # Display current settings col = layout.column(align=True) col.label(text="Current Settings:", icon='SETTINGS') if context.scene.camera: camera = context.scene.camera if camera.parent: col.label(text=f"Camera: {camera.name}") col.label(text=f"Parent: {camera.parent.name}") else: col.label(text="⚠ Camera has no parent!", icon='ERROR') else: col.label(text="⚠ No active camera!", icon='ERROR') frame_start = context.scene.frame_start frame_end = context.scene.frame_end total_frames = frame_end - frame_start + 1 col.label(text=f"Frames: {frame_start}-{frame_end} ({total_frames} total)") col.label(text=f"Total renders: {total_frames * 8}") layout.separator() # Render button row = layout.row() row.scale_y = 2.0 row.operator("render.multi_angle", text="Render 8 Angles", icon='RENDER_ANIMATION') # Angles info layout.separator() box = layout.box() box.label(text="Render Angles:", icon='CAMERA_DATA') # Create two columns for the angles split = box.split(factor=0.5) col1 = split.column() col2 = split.column() angles_info = [ ("S (0°)", "South"), ("SW (45°)", "Southwest"), ("W (90°)", "West"), ("NW (135°)", "Northwest"), ("N (180°)", "North"), ("NE (225°)", "Northeast"), ("E (270°)", "East"), ("SE (315°)", "Southeast") ] for i, (angle, desc) in enumerate(angles_info): if i < 4: col1.label(text=f"{angle}") else: col2.label(text=f"{angle}") def register(): bpy.utils.register_class(MultiAngleProperties) bpy.utils.register_class(RENDER_OT_multi_angle) bpy.utils.register_class(RENDER_PT_multi_angle_panel) bpy.types.Scene.multi_angle_props = bpy.props.PointerProperty(type=MultiAngleProperties) def unregister(): bpy.utils.unregister_class(RENDER_PT_multi_angle_panel) bpy.utils.unregister_class(RENDER_OT_multi_angle) bpy.utils.unregister_class(MultiAngleProperties) del bpy.types.Scene.multi_angle_props if __name__ == "__main__": register()