Files
blender-compass/blender_compass.py
2025-09-05 16:22:34 -07:00

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()