blender compass initial commit
This commit is contained in:
229
blender_compass.py
Normal file
229
blender_compass.py
Normal file
@@ -0,0 +1,229 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user