commit 2d192fb1adb89e9fb9a18a99f9cc73bde7282ff3 Author: Bryce Date: Fri Sep 5 16:22:34 2025 -0700 blender compass initial commit diff --git a/blender_compass.py b/blender_compass.py new file mode 100644 index 0000000..7c929a4 --- /dev/null +++ b/blender_compass.py @@ -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()