Created
April 8, 2026 12:56
-
-
Save tonis2/a9f0a4c254756851b05c193dba032ea1 to your computer and use it in GitHub Desktop.
Addon for blender, to automatically create multi angle screenshot from mesh.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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