Blender views layout export (top bottom front back right left perpective views)

BLENDER SCREEN VİEWS TO LAYOUT

  1. Copy the following code and paste it into Notepad, saving it with the .py extension.
    Save the example.py file as a zip file and install it as a plugin in Blender.

  2. Layout Export Preview

Task:

To extract layout output (PNG) from camera views of a model using the Blender 3D application.

Usage:

  • Install the plugin.

  • Open the side panel.

  • First click the camera option button, then the screenshot button on the left. Repeat this for each view.

  • Once all view saves are complete, select the folder where the output will be saved using the “Create Layout” button and save.

Future:

  • Layout Options
  • Fine-Tuning
  • Page Selection
  • Render Settings
  • Simple / Advanced Render Mode Output Option
  • PDF Output Option
  • One-touch automatic output (manual version preferred for now due to faulty camera area selection).

This application was developed with QWEN3-Max AI. I would be very grateful if you could share more advanced interfaces and features here. Thank you.

CODE:

import bpy
from mathutils import Vector
import os
import shutil
from PIL import Image, ImageDraw, ImageFont, ImageEnhance
from datetime import datetime
import platform

bl_info = {
“name”: “Multi-View Layout Renderer”,
“blender”: (4, 5, 0),
“category”: “Render”,
}

-------------------------------------------------------------

FONT YÜKLEYİCİ

-------------------------------------------------------------

def load_font(size):
addon_dir = os.path.dirname(file)
font_path = os.path.join(addon_dir, “fonts”, “arial.ttf”)
try:
return ImageFont.truetype(font_path, size)
except:
return ImageFont.load_default()

-------------------------------------------------------------

GEÇİCİ KLÖSÖR (MASAÜSTÜ / .multiview_cache)

-------------------------------------------------------------

def get_cache_dir():
desktop = os.path.join(os.path.expanduser(“~”), “Desktop”)
cache_dir = os.path.join(desktop, “.multiview_cache”)
os.makedirs(cache_dir, exist_ok=True)
if platform.system() == “Windows”:
import subprocess
subprocess.check_call([“attrib”, “+H”, cache_dir], shell=True)
return cache_dir

-------------------------------------------------------------

PROPERTY GROUP – SADE

-------------------------------------------------------------

class MultiViewProperties(bpy.types.PropertyGroup):
company_name: bpy.props.StringProperty(name=“Company”, default=“AURACAD”)
project_name: bpy.props.StringProperty(name=“Project”, default=“GENERAL MESH TRANSFORM”)
drawing_code: bpy.props.StringProperty(name=“Drawing Code”, default=“DWG-001”)
version: bpy.props.StringProperty(name=“Version”, default=“1.0”)
date: bpy.props.StringProperty(name=“Date”, default=datetime.now().strftime(“%d.%m.%Y”))
notes: bpy.props.StringProperty(name=“Extra Notes”, default=“”)

light_color: bpy.props.FloatVectorProperty(
    name="Light Color",
    subtype='COLOR',
    default=(1.0, 1.0, 1.0),
    min=0.0,
    max=1.0
)
light_energy: bpy.props.FloatProperty(
    name="Light Energy",
    default=2.5,
    min=0.5,
    max=10.0
)

front_img: bpy.props.StringProperty()
back_img: bpy.props.StringProperty()
left_img: bpy.props.StringProperty()
right_img: bpy.props.StringProperty()
top_img: bpy.props.StringProperty()
bottom_img: bpy.props.StringProperty()
perspective_img: bpy.props.StringProperty()

-------------------------------------------------------------

GEOMETRY UTILS

-------------------------------------------------------------

def get_object_bounds(obj):
if obj.type == ‘MESH’:
points = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
x = [p.x for p in points]
y = [p.y for p in points]
z = [p.z for p in points]
min_b = Vector((min(x), min(y), min(z)))
max_b = Vector((max(x), max(y), max(z)))
center = (min_b + max_b) / 2
return center, min_b, max_b
return Vector((0,0,0)), Vector((0,0,0)), Vector((0,0,0))

-------------------------------------------------------------

KAMERA AYARLAMA – BASİT VE SABİT

-------------------------------------------------------------

def set_camera_view(name, props):
obj = bpy.context.selected_objects[0] if bpy.context.selected_objects else None
if not obj:
return

center, min_b, max_b = get_object_bounds(obj)
base_size = (max_b - min_b).length

views = {
    "front": Vector((0, 0, 1)),
    "back": Vector((0, 0, -1)),
    "left": Vector((-1, 0, 0)),
    "right": Vector((1, 0, 0)),
    "top": Vector((0, 1, 0)),
    "bottom": Vector((0, -1, 0)),
    "perspective": Vector((1, 1, 1)).normalized(),
}

rot = views.get(name, Vector((0, 0, 1)))

cam_name = "ActiveCamera"
if cam_name not in bpy.data.objects:
    cam_data = bpy.data.cameras.new(cam_name)
    cam_obj = bpy.data.objects.new(cam_name, cam_data)
    bpy.context.collection.objects.link(cam_obj)
else:
    cam_obj = bpy.data.objects[cam_name]

if name == "perspective":
    cam_obj.data.type = 'PERSP'
    cam_obj.data.lens = 50
else:
    cam_obj.data.type = 'ORTHO'
    cam_obj.data.ortho_scale = base_size * 1.2

cam_obj.location = center + rot * (base_size * 1.8)
direction = center - cam_obj.location
rot_q = direction.to_track_quat('-Z', 'Y')
cam_obj.rotation_euler = rot_q.to_euler()
bpy.context.scene.camera = cam_obj

for area in bpy.context.screen.areas:
    if area.type == 'VIEW_3D':
        for space in area.spaces:
            if space.type == 'VIEW_3D':
                space.region_3d.view_rotation = rot_q
                space.region_3d.view_location = center
                space.region_3d.view_distance = base_size * 1.8
                break

-------------------------------------------------------------

GÖRÜNTÜ ÇEKME

-------------------------------------------------------------

def capture_view(name, props):
scene = bpy.context.scene
if not scene.camera:
return None

cache_dir = get_cache_dir()
file = os.path.join(cache_dir, f"{name.lower()}_temp.png")
final_file = os.path.join(cache_dir, f"{name.lower()}.png")

scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_mode = 'RGBA'
scene.render.filepath = file
scene.render.resolution_x = 512
scene.render.resolution_y = 512

bpy.context.view_layer.update()
bpy.ops.render.render(write_still=True)

img = Image.open(file).convert("RGB")
img = ImageEnhance.Contrast(img).enhance(1.2)
img = ImageEnhance.Brightness(img).enhance(0.9)

target_size = (600, 600) if name == "perspective" else (300, 300)
w, h = img.size
ratio = min(target_size[0]/w, target_size[1]/h)
img = img.resize((int(w*ratio), int(h*ratio)), Image.LANCZOS)

canvas = Image.new("RGB", target_size, "gray")
x = (target_size[0] - img.width) // 2
y = (target_size[1] - img.height) // 2
canvas.paste(img, (x, y))
canvas.save(final_file)

return final_file

-------------------------------------------------------------

LAYOUT OLUŞTURMA

-------------------------------------------------------------

def create_professional_layout(images, out_path, obj_name, info):
addon_dir = os.path.dirname(file)
small = 300
large = 600
spacing = 20
padding = 40
right_w = 350
title_h = 90
canvas_w = large + small * 2 + spacing * 4 + padding * 2 + right_w
canvas_h = large + small + spacing * 3 + padding * 2 + title_h
canvas = Image.new(“RGB”, (canvas_w, canvas_h), “white”)
draw = ImageDraw.Draw(canvas)
f_title = load_font(32)
f_normal = load_font(22)
f_small = load_font(18)
right_x = canvas_w - right_w
draw.rectangle([right_x, 0, canvas_w, canvas_h], fill=(240,240,240))

logo_path = os.path.join(addon_dir, "logo.png")
if os.path.exists(logo_path):
    try:
        logo = Image.open(logo_path).convert("RGBA")
        logo.thumbnail((300, 120))
        canvas.paste(logo, (right_x + 25, 20), logo)
    except:
        pass

yt = 170
tx = right_x + 25
draw.text((tx, yt), "PROJECT INFO", fill=(0,0,0), font=f_title)
yt += 50
for label, key in [
    ("Company:", "company"),
    ("Project:", "project"),
    ("Code:", "code"),
    ("Version:", "version"),
    ("Date:", "date"),
]:
    draw.text((tx, yt), label, fill=(80,80,80), font=f_normal)
    draw.text((tx, yt + 25), info.get(key, ""), fill=(0,0,0), font=f_small)
    yt += 60
draw.text((tx, yt), "Notes:", fill=(80,80,80), font=f_normal)
draw.text((tx, yt + 25), info.get("notes", ""), fill=(0,0,0), font=f_small)

px = padding
py = padding + title_h

# Perspective
if images.get("perspective") and os.path.exists(images["perspective"]):
    img = Image.open(images["perspective"])
    draw.rectangle([px, py, px + large, py + large], outline=(200,200,200), width=3)
    draw.text((px + 5, py - 20), "Perspective", fill=(0,0,0), font=f_small)
    img.thumbnail((large-15, large-15))
    canvas.paste(img, (px + (large - img.width) // 2, py + (large - img.height) // 2))

# Top & Bottom
tx1 = px + large + spacing
ty1 = py
for label, key, xx in [
    ("Top", "top", tx1),
    ("Bottom", "bottom", tx1 + small + spacing),
]:
    draw.text((xx, ty1 - 30), label, fill=(0,0,0), font=f_small)
    draw.rectangle([xx, ty1, xx + small, ty1 + small], outline=(200,200,200), width=3)
    if images.get(key) and os.path.exists(images[key]):
        img = Image.open(images[key])
        img.thumbnail((small-15, small-15))
        canvas.paste(img, (xx + (small - img.width) // 2, ty1 + (small - img.height) // 2))

# Front, Back, Left, Right
oy = py + large + spacing
ortho_s = 200
os_gap = 15
views = [
    ("front", "Front"),
    ("back", "Back"),
    ("left", "Left"),
    ("right", "Right"),
]
ox = padding
for key, label in views:
    draw.text((ox, oy - 25), label, fill=(0,0,0), font=f_small)
    draw.rectangle([ox, oy, ox + ortho_s, oy + ortho_s], outline=(200,200,200), width=3)
    if images.get(key) and os.path.exists(images[key]):
        img = Image.open(images[key])
        img.thumbnail((ortho_s-15, ortho_s-15))
        canvas.paste(img, (ox + (ortho_s - img.width) // 2, oy + (ortho_s - img.height) // 2))
    ox += ortho_s + os_gap

final_path = os.path.join(out_path, "LAYOUT_FINAL.png")
canvas.save(final_path)

-------------------------------------------------------------

OPERATORS

-------------------------------------------------------------

class RENDER_OT_set_view(bpy.types.Operator):
bl_idname = “render.set_view”
bl_label = “Set View”
view_name: bpy.props.StringProperty()
def execute(self, context):
set_camera_view(self.view_name, context.scene.multiview_props)
return {‘FINISHED’}

class RENDER_OT_capture_view(bpy.types.Operator):
bl_idname = “render.capture_view”
bl_label = “Capture View”
view_name: bpy.props.StringProperty()
def execute(self, context):
props = context.scene.multiview_props
obj = bpy.context.selected_objects[0] if bpy.context.selected_objects else None
if not obj:
self.report({‘ERROR’}, “Please select an object”)
return {‘CANCELLED’}
img_path = capture_view(self.view_name, props)
if img_path:
setattr(props, f"{self.view_name}_img", img_path)
self.report({‘INFO’}, f"{self.view_name} captured!")
else:
self.report({‘ERROR’}, “Failed to capture view”)
return {‘FINISHED’}

class RENDER_OT_create_layout(bpy.types.Operator):
bl_idname = “render.create_layout”
bl_label = “Create Layout”
filepath: bpy.props.StringProperty(subtype=‘DIR_PATH’)
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {‘RUNNING_MODAL’}
def execute(self, context):
if not self.filepath:
self.report({‘ERROR’}, “Please select output directory”)
return {‘CANCELLED’}
props = context.scene.multiview_props
obj = bpy.context.selected_objects[0] if bpy.context.selected_objects else None
if not obj:
self.report({‘ERROR’}, “Please select an object”)
return {‘CANCELLED’}

    images = {
        "front": props.front_img,
        "back": props.back_img,
        "left": props.left_img,
        "right": props.right_img,
        "top": props.top_img,
        "bottom": props.bottom_img,
        "perspective": props.perspective_img,
    }

    create_professional_layout(images, self.filepath, obj.name, {
        'company': props.company_name,
        'project': props.project_name,
        'code': props.drawing_code,
        'version': props.version,
        'date': props.date,
        'notes': props.notes,
    })

    # ✅ GEÇİCİ KLÖSÖRÜ SİL
    cache_dir = get_cache_dir()
    if os.path.exists(cache_dir):
        shutil.rmtree(cache_dir)

    self.report({'INFO'}, f"Layout saved to: {self.filepath}")
    return {'FINISHED'}

-------------------------------------------------------------

PANEL – SON DÜZEN

-------------------------------------------------------------

class RENDER_PT_multiview_panel(bpy.types.Panel):
bl_label = “Multi-View Layout”
bl_category = “Multi-View”
bl_space_type = ‘VIEW_3D’
bl_region_type = ‘UI’

def draw(self, context):
    layout = self.layout
    props = context.scene.multiview_props

    layout.label(text="Project Information", icon='INFO')
    box = layout.box()
    box.prop(props, "company_name")
    box.prop(props, "project_name")
    box.prop(props, "drawing_code")
    box.prop(props, "version")
    box.prop(props, "date")
    layout.separator()
    layout.prop(props, "notes")
    layout.separator()

    layout.label(text="Render Settings", icon='LIGHT')
    box = layout.box()
    box.prop(props, "light_color")
    box.prop(props, "light_energy")
    layout.separator()

    layout.label(text="📌 Kontroller", icon='VIEW3D')
    
    # 2x2 Grid
    row = layout.row()
    col1 = row.column(align=True)
    col2 = row.column(align=True)

    # Front / Back
    r1 = col1.row(align=True)
    r1.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "front"
    r1.operator("render.set_view", text="→ Front", icon='FORWARD').view_name = "front"

    r1b = col2.row(align=True)
    r1b.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "back"
    r1b.operator("render.set_view", text="← Back", icon='BACK').view_name = "back"

    # Left / Right
    r2 = col1.row(align=True)
    r2.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "left"
    r2.operator("render.set_view", text="← Left", icon='BACK').view_name = "left"

    r2b = col2.row(align=True)
    r2b.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "right"
    r2b.operator("render.set_view", text="→ Right", icon='FORWARD').view_name = "right"

    # Top / Bottom
    r3 = col1.row(align=True)
    r3.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "top"
    r3.operator("render.set_view", text="↑ Top", icon='TRIA_UP').view_name = "top"

    r3b = col2.row(align=True)
    r3b.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "bottom"
    r3b.operator("render.set_view", text="↓ Bottom", icon='TRIA_DOWN').view_name = "bottom"

    # Perspective
    r4 = layout.row(align=True)
    r4.operator("render.capture_view", text="", icon='CAMERA_DATA').view_name = "perspective"
    r4.operator("render.set_view", text="👁️ Perspective", icon='VIEW_PERSPECTIVE').view_name = "perspective"

    layout.separator()
    layout.operator("render.create_layout", icon='RENDER_STILL')

-------------------------------------------------------------

REGISTER / UNREGISTER

-------------------------------------------------------------

def register():
bpy.utils.register_class(MultiViewProperties)
bpy.utils.register_class(RENDER_OT_set_view)
bpy.utils.register_class(RENDER_OT_capture_view)
bpy.utils.register_class(RENDER_OT_create_layout)
bpy.utils.register_class(RENDER_PT_multiview_panel)
bpy.types.Scene.multiview_props = bpy.props.PointerProperty(type=MultiViewProperties)

def unregister():
bpy.utils.unregister_class(MultiViewProperties)
bpy.utils.unregister_class(RENDER_OT_set_view)
bpy.utils.unregister_class(RENDER_OT_capture_view)
bpy.utils.unregister_class(RENDER_OT_create_layout)
bpy.utils.unregister_class(RENDER_PT_multiview_panel)
if “multiview_props” in bpy.types.Scene.dict:
del bpy.types.Scene.multiview_props

if name == “main”:
register()