230 lines
7.9 KiB
Python
230 lines
7.9 KiB
Python
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()
|