Skip to content

Instantly share code, notes, and snippets.

@tonis2
Created April 8, 2026 12:56
Show Gist options
  • Select an option

  • Save tonis2/a9f0a4c254756851b05c193dba032ea1 to your computer and use it in GitHub Desktop.

Select an option

Save tonis2/a9f0a4c254756851b05c193dba032ea1 to your computer and use it in GitHub Desktop.
Addon for blender, to automatically create multi angle screenshot from mesh.
bl_info = {
"name": "Multi Angle Capture",
"author": "BakeTurbo",
"version": (1, 0, 0),
"blender": (4, 0, 0),
"location": "3D Viewport > Sidebar > Capture",
"description": "Render selected mesh from multiple angles",
"category": "Render",
}
import bpy
import math
import os
from mathutils import Vector
from bpy.props import (
StringProperty,
IntProperty,
BoolProperty,
)
class MultiAngleCaptureProperties(bpy.types.PropertyGroup):
output_dir: StringProperty(
name="Output Directory",
description="Directory to save captured images",
default="/tmp/mesh_captures",
subtype='DIR_PATH',
)
angles_horizontal: IntProperty(
name="Horizontal Angles",
description="Number of horizontal rotation steps around the object",
default=8,
min=1,
max=64,
)
angles_vertical: IntProperty(
name="Vertical Angles",
description="Number of vertical elevation steps",
default=3,
min=1,
max=16,
)
resolution: IntProperty(
name="Resolution",
description="Image resolution (square)",
default=1024,
min=64,
max=8192,
)
distance_factor: bpy.props.FloatProperty(
name="Distance",
description="Camera distance multiplier (higher = more zoomed out)",
default=2.5,
min=1.0,
max=20.0,
step=10,
)
use_transparent: BoolProperty(
name="Transparent Background",
description="Render with transparent background",
default=True,
)
class CAPTURE_OT_multi_angle(bpy.types.Operator):
bl_idname = "render.multi_angle_capture"
bl_label = "Capture Multi Angle"
bl_description = "Render the selected mesh from multiple angles"
bl_options = {'REGISTER'}
def execute(self, context):
obj = context.active_object
if obj is None:
self.report({'ERROR'}, "No active object selected")
return {'CANCELLED'}
props = context.scene.multi_angle_capture
output_dir = bpy.path.abspath(props.output_dir)
os.makedirs(output_dir, exist_ok=True)
# Bounding box center and camera distance
bbox_corners = [obj.matrix_world @ Vector(c) for c in obj.bound_box]
center = sum(bbox_corners, Vector()) / 8
radius = max((c - center).length for c in bbox_corners) * props.distance_factor
# Store original settings
scene = context.scene
original_camera = scene.camera
original_film_transparent = scene.render.film_transparent
original_res_x = scene.render.resolution_x
original_res_y = scene.render.resolution_y
original_filepath = scene.render.filepath
original_format = scene.render.image_settings.file_format
original_color_mode = scene.render.image_settings.color_mode
# Temporary camera
cam_data = bpy.data.cameras.new("_capture_cam")
cam_obj = bpy.data.objects.new("_capture_cam", cam_data)
context.collection.objects.link(cam_obj)
scene.camera = cam_obj
# Render settings
scene.render.resolution_x = props.resolution
scene.render.resolution_y = props.resolution
scene.render.image_settings.file_format = "PNG"
scene.render.image_settings.color_mode = "RGBA" if props.use_transparent else "RGB"
scene.render.film_transparent = props.use_transparent
# Elevation angles
elevations = []
if props.angles_vertical == 1:
elevations = [math.radians(30)]
else:
for i in range(props.angles_vertical):
elev = math.radians(15 + (60 * i / (props.angles_vertical - 1)))
elevations.append(elev)
count = 0
for vi, elev in enumerate(elevations):
for hi in range(props.angles_horizontal):
azimuth = (2 * math.pi * hi) / props.angles_horizontal
x = center.x + radius * math.cos(elev) * math.cos(azimuth)
y = center.y + radius * math.cos(elev) * math.sin(azimuth)
z = center.z + radius * math.sin(elev)
cam_obj.location = Vector((x, y, z))
direction = center - cam_obj.location
rot_quat = direction.to_track_quat('-Z', 'Y')
cam_obj.rotation_euler = rot_quat.to_euler()
filename = f"angle_h{hi:02d}_v{vi:02d}.png"
scene.render.filepath = os.path.join(output_dir, filename)
bpy.ops.render.render(write_still=True)
count += 1
# Cleanup
bpy.data.objects.remove(cam_obj)
bpy.data.cameras.remove(cam_data)
scene.camera = original_camera
scene.render.film_transparent = original_film_transparent
scene.render.resolution_x = original_res_x
scene.render.resolution_y = original_res_y
scene.render.filepath = original_filepath
scene.render.image_settings.file_format = original_format
scene.render.image_settings.color_mode = original_color_mode
self.report({'INFO'}, f"{count} images saved to {output_dir}")
return {'FINISHED'}
class CAPTURE_PT_panel(bpy.types.Panel):
bl_label = "Multi Angle Capture"
bl_idname = "CAPTURE_PT_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Capture"
def draw(self, context):
layout = self.layout
props = context.scene.multi_angle_capture
layout.prop(props, "output_dir")
layout.separator()
layout.prop(props, "angles_horizontal")
layout.prop(props, "angles_vertical")
layout.prop(props, "resolution")
layout.prop(props, "distance_factor")
layout.prop(props, "use_transparent")
layout.separator()
layout.operator("render.multi_angle_capture", icon='RENDER_STILL')
classes = (
MultiAngleCaptureProperties,
CAPTURE_OT_multi_angle,
CAPTURE_PT_panel,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.multi_angle_capture = bpy.props.PointerProperty(
type=MultiAngleCaptureProperties
)
def unregister():
del bpy.types.Scene.multi_angle_capture
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment