adds openpose
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Compass Render", # Main display name
|
"name": "Compass Render", # Main display name
|
||||||
"author": "Bryce", # Your name/credit
|
"author": "Bryce", # Your name/credit
|
||||||
"version": (1, 1, 0), # Version number
|
"version": (1, 2, 0), # Version number (bumped)
|
||||||
"blender": (4, 3, 0), # Minimum Blender version
|
"blender": (4, 3, 0), # Minimum Blender version
|
||||||
"location": "Properties > Render Properties > Multi-Angle Renderer",
|
"location": "Properties > Render Properties > Multi-Angle Renderer",
|
||||||
"description": "Multi-angle animation renderer with Line Art support",
|
"description": "Multi-angle animation renderer with Line Art + optional OpenPose layer output",
|
||||||
"warning": "Beta version - use with caution", # Optional warning
|
"warning": "Beta version - use with caution", # Optional warning
|
||||||
"doc_url": "https://your-documentation.com", # Optional docs link
|
"doc_url": "https://your-documentation.com", # Optional docs link
|
||||||
"category": "Render", # Category in add-ons list
|
"category": "Render", # Category in add-ons list
|
||||||
@@ -18,13 +18,35 @@ from bpy.props import StringProperty, FloatProperty, BoolProperty, EnumProperty
|
|||||||
from bpy.types import Panel, Operator
|
from bpy.types import Panel, Operator
|
||||||
|
|
||||||
|
|
||||||
|
def find_openpose_view_layer(scene: bpy.types.Scene):
|
||||||
|
"""
|
||||||
|
Tries to find an OpenPose View Layer by common names.
|
||||||
|
Customize this list if your pipeline uses a specific layer name.
|
||||||
|
"""
|
||||||
|
candidates = {
|
||||||
|
"OpenPose",
|
||||||
|
"OpenPose_Layer",
|
||||||
|
"OpenPoseLayer",
|
||||||
|
"OPENPOSE",
|
||||||
|
"openpose",
|
||||||
|
"openpose_layer",
|
||||||
|
}
|
||||||
|
for vl in scene.view_layers:
|
||||||
|
if vl.name in candidates:
|
||||||
|
return vl
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class RENDER_OT_setup_compositor(Operator):
|
class RENDER_OT_setup_compositor(Operator):
|
||||||
"""Setup compositor for multi-pass rendering with Line Art"""
|
"""Setup compositor for multi-pass rendering with Line Art (and optional OpenPose)"""
|
||||||
bl_idname = "render.setup_compositor"
|
bl_idname = "render.setup_compositor"
|
||||||
bl_label = "Setup Multi-Pass Compositor"
|
bl_label = "Setup Multi-Pass Compositor"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
lineart_view_layer = None
|
||||||
|
openpose_view_layer = None
|
||||||
|
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
props = scene.multi_angle_props
|
props = scene.multi_angle_props
|
||||||
|
|
||||||
@@ -47,7 +69,6 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
render_layers.location = (0, 0)
|
render_layers.location = (0, 0)
|
||||||
render_layers.layer = "ViewLayer"
|
render_layers.layer = "ViewLayer"
|
||||||
|
|
||||||
# Create file output nodes for each pass
|
|
||||||
# Main image output
|
# Main image output
|
||||||
image_output = nodes.new(type='CompositorNodeOutputFile')
|
image_output = nodes.new(type='CompositorNodeOutputFile')
|
||||||
image_output.location = (600, 300)
|
image_output.location = (600, 300)
|
||||||
@@ -79,7 +100,6 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
lineart_mix_node = None
|
lineart_mix_node = None
|
||||||
|
|
||||||
if props.use_lineart:
|
if props.use_lineart:
|
||||||
# Create Line Art grease pencil output
|
|
||||||
lineart_output = nodes.new(type='CompositorNodeOutputFile')
|
lineart_output = nodes.new(type='CompositorNodeOutputFile')
|
||||||
lineart_output.location = (600, -100)
|
lineart_output.location = (600, -100)
|
||||||
lineart_output.label = "LineArt Output"
|
lineart_output.label = "LineArt Output"
|
||||||
@@ -91,9 +111,7 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
# Find or create Grease Pencil Line Art object
|
# Find or create Grease Pencil Line Art object
|
||||||
gp_obj = self.ensure_lineart_object(context)
|
gp_obj = self.ensure_lineart_object(context)
|
||||||
|
|
||||||
# Create viewer node for Line Art (Grease Pencil objects render through different path)
|
|
||||||
# We'll use a separate view layer for Line Art
|
# We'll use a separate view layer for Line Art
|
||||||
lineart_view_layer = None
|
|
||||||
for vl in scene.view_layers:
|
for vl in scene.view_layers:
|
||||||
if vl.name == "LineArt_Layer":
|
if vl.name == "LineArt_Layer":
|
||||||
lineart_view_layer = vl
|
lineart_view_layer = vl
|
||||||
@@ -130,13 +148,33 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
else:
|
else:
|
||||||
links.new(render_layers.outputs['Image'], image_output.inputs['image'])
|
links.new(render_layers.outputs['Image'], image_output.inputs['image'])
|
||||||
|
|
||||||
# Depth with improved mapping
|
|
||||||
|
# Optional OpenPose layer support
|
||||||
|
openpose_output = None
|
||||||
|
openpose_view_layer = find_openpose_view_layer(scene)
|
||||||
|
if openpose_view_layer is not None:
|
||||||
|
openpose_output = nodes.new(type='CompositorNodeOutputFile')
|
||||||
|
openpose_output.location = (600, -300)
|
||||||
|
openpose_output.label = "OpenPose Output"
|
||||||
|
openpose_output.base_path = ""
|
||||||
|
openpose_output.file_slots.clear()
|
||||||
|
openpose_output.file_slots.new("openpose")
|
||||||
|
openpose_output.format.file_format = 'PNG'
|
||||||
|
|
||||||
|
openpose_render_layers = nodes.new(type='CompositorNodeRLayers')
|
||||||
|
openpose_render_layers.location = (0, -400)
|
||||||
|
openpose_render_layers.layer = openpose_view_layer.name
|
||||||
|
|
||||||
|
# Links
|
||||||
|
|
||||||
links.new(render_layers.outputs['Depth'], depth_map_range.inputs['Value'])
|
links.new(render_layers.outputs['Depth'], depth_map_range.inputs['Value'])
|
||||||
links.new(depth_map_range.outputs['Value'], depth_output.inputs['depth'])
|
links.new(depth_map_range.outputs['Value'], depth_output.inputs['depth'])
|
||||||
|
|
||||||
# Line Art output
|
|
||||||
if lineart_output and 'lineart_render_layers' in locals():
|
if lineart_output and 'lineart_render_layers' in locals():
|
||||||
links.new(lineart_render_layers.outputs['Image'], lineart_output.inputs['lineart'])
|
links.new(lineart_render_layers.outputs['Image'], lineart_output.inputs['lineart'])
|
||||||
|
|
||||||
|
if openpose_output and 'openpose_render_layers' in locals():
|
||||||
|
links.new(openpose_render_layers.outputs['Image'], openpose_output.inputs['openpose'])
|
||||||
|
|
||||||
# Store node references for later use
|
# Store node references for later use
|
||||||
scene['multiangle_image_output'] = image_output.name
|
scene['multiangle_image_output'] = image_output.name
|
||||||
@@ -145,8 +183,18 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
|
|
||||||
if lineart_output:
|
if lineart_output:
|
||||||
scene['multiangle_lineart_output'] = lineart_output.name
|
scene['multiangle_lineart_output'] = lineart_output.name
|
||||||
|
|
||||||
|
if openpose_output:
|
||||||
|
scene['multiangle_openpose_output'] = openpose_output.name
|
||||||
|
|
||||||
self.report({'INFO'}, "Multi-pass compositor setup complete")
|
self.report({'INFO'}, "Multi-pass compositor setup complete")
|
||||||
|
if openpose_view_layer is not None:
|
||||||
|
self.report({'INFO'}, f"OpenPose layer detected: '{openpose_view_layer.name}' (output enabled)")
|
||||||
|
else:
|
||||||
|
# Ensure stale key doesn't linger if user removed the layer
|
||||||
|
if 'multiangle_openpose_output' in scene:
|
||||||
|
del scene['multiangle_openpose_output']
|
||||||
|
self.report({'INFO'}, "OpenPose layer not found (output not enabled)")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def ensure_lineart_object(self, context):
|
def ensure_lineart_object(self, context):
|
||||||
@@ -161,14 +209,11 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not gp_obj:
|
if not gp_obj:
|
||||||
# Create new Grease Pencil object for Line Art
|
|
||||||
# Try multiple methods for different Blender versions
|
|
||||||
gp_data = None
|
gp_data = None
|
||||||
self.report({'INFO'}, 'HERE')
|
|
||||||
# Method 1: Try Grease Pencil v3 (Blender 4.3+)
|
# Method 1: Try Grease Pencil v3 (Blender 4.3+)
|
||||||
if hasattr(bpy.data, 'grease_pencils_v3'):
|
if hasattr(bpy.data, 'grease_pencils_v3'):
|
||||||
try:
|
try:
|
||||||
self.report({'INFO'}, 'grease pencil v3')
|
|
||||||
gp_data = bpy.data.grease_pencils_v3.new("LineArt_GP")
|
gp_data = bpy.data.grease_pencils_v3.new("LineArt_GP")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -176,7 +221,6 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
# Method 2: Try legacy grease pencils collection
|
# Method 2: Try legacy grease pencils collection
|
||||||
if not gp_data and hasattr(bpy.data, 'grease_pencils'):
|
if not gp_data and hasattr(bpy.data, 'grease_pencils'):
|
||||||
try:
|
try:
|
||||||
self.report({'INFO'}, 'grease pencil GP')
|
|
||||||
gp_data = bpy.data.grease_pencils.new("LineArt_GP")
|
gp_data = bpy.data.grease_pencils.new("LineArt_GP")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
@@ -184,18 +228,15 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
# Method 3: Try operator methods
|
# Method 3: Try operator methods
|
||||||
if not gp_data:
|
if not gp_data:
|
||||||
try:
|
try:
|
||||||
# Try modern operator name
|
|
||||||
bpy.ops.object.grease_pencil_add(type='EMPTY')
|
bpy.ops.object.grease_pencil_add(type='EMPTY')
|
||||||
gp_obj = context.active_objecthas
|
gp_obj = context.active_object # FIXED typo
|
||||||
gp_obj.name = "LineArt_Object"
|
gp_obj.name = "LineArt_Object"
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
# Try legacy operator name
|
|
||||||
bpy.ops.object.gpencil_add(type='EMPTY')
|
bpy.ops.object.gpencil_add(type='EMPTY')
|
||||||
gp_obj = context.active_object
|
gp_obj = context.active_object
|
||||||
gp_obj.name = "LineArt_Object"
|
gp_obj.name = "LineArt_Object"
|
||||||
except:
|
except:
|
||||||
# Create empty as fallback and warn user
|
|
||||||
bpy.ops.object.empty_add(type='PLAIN_AXES')
|
bpy.ops.object.empty_add(type='PLAIN_AXES')
|
||||||
gp_obj = context.active_object
|
gp_obj = context.active_object
|
||||||
gp_obj.name = "LineArt_Object_EMPTY"
|
gp_obj.name = "LineArt_Object_EMPTY"
|
||||||
@@ -223,7 +264,6 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
scene.collection.objects.unlink(gp_obj)
|
scene.collection.objects.unlink(gp_obj)
|
||||||
lineart_collection.objects.link(gp_obj)
|
lineart_collection.objects.link(gp_obj)
|
||||||
|
|
||||||
self.report({'INFO'}, f"HERE {gp_obj.type}")
|
|
||||||
# Only proceed with modifier setup if we have a proper Grease Pencil object
|
# Only proceed with modifier setup if we have a proper Grease Pencil object
|
||||||
if not gp_obj or gp_obj.type != 'GREASEPENCIL':
|
if not gp_obj or gp_obj.type != 'GREASEPENCIL':
|
||||||
return gp_obj
|
return gp_obj
|
||||||
@@ -282,6 +322,14 @@ class RENDER_OT_setup_compositor(Operator):
|
|||||||
mat.grease_pencil.show_fill = False
|
mat.grease_pencil.show_fill = False
|
||||||
|
|
||||||
gp_obj.data.materials.append(mat)
|
gp_obj.data.materials.append(mat)
|
||||||
|
|
||||||
|
# Configure the Line Art modifier
|
||||||
|
if lineart_mod:
|
||||||
|
lineart_mod.source_type = 'SCENE'
|
||||||
|
lineart_mod.use_contour = True
|
||||||
|
lineart_mod.use_crease = True
|
||||||
|
lineart_mod.target_layer = gp_layer.name
|
||||||
|
lineart_mod.target_material = gp_obj.data.materials[0]
|
||||||
|
|
||||||
return gp_obj
|
return gp_obj
|
||||||
|
|
||||||
@@ -307,24 +355,23 @@ class RENDER_OT_multi_angle(Operator):
|
|||||||
'multiangle_depth_output' in scene)
|
'multiangle_depth_output' in scene)
|
||||||
|
|
||||||
use_lineart = props.use_lineart and 'multiangle_lineart_output' in scene
|
use_lineart = props.use_lineart and 'multiangle_lineart_output' in scene
|
||||||
|
use_openpose = use_multipass and ('multiangle_openpose_output' in scene) and (find_openpose_view_layer(scene) is not None)
|
||||||
|
|
||||||
# Find camera and its parent
|
# Find camera and its parent
|
||||||
camera = None
|
camera = None
|
||||||
camera_parent = None
|
camera_parent = None
|
||||||
|
|
||||||
# Look for active camera first
|
|
||||||
if scene.camera:
|
if scene.camera:
|
||||||
camera = scene.camera
|
camera = scene.camera
|
||||||
if camera.parent:
|
if camera.parent:
|
||||||
camera_parent = 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:
|
if not camera or not camera_parent:
|
||||||
for obj in scene.objects:
|
for obj in scene.objects:
|
||||||
if obj.type == 'CAMERA' and obj.parent:
|
if obj.type == 'CAMERA' and obj.parent:
|
||||||
camera = obj
|
camera = obj
|
||||||
camera_parent = obj.parent
|
camera_parent = obj.parent
|
||||||
scene.camera = camera # Set as active camera
|
scene.camera = camera
|
||||||
break
|
break
|
||||||
|
|
||||||
if not camera:
|
if not camera:
|
||||||
@@ -335,33 +382,27 @@ class RENDER_OT_multi_angle(Operator):
|
|||||||
self.report({'ERROR'}, "Camera must be parented to an empty object")
|
self.report({'ERROR'}, "Camera must be parented to an empty object")
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# Store original rotation
|
|
||||||
original_rotation = camera_parent.rotation_euler.copy()
|
original_rotation = camera_parent.rotation_euler.copy()
|
||||||
|
|
||||||
# Store original active view layer
|
|
||||||
original_view_layer = context.view_layer.name
|
original_view_layer = context.view_layer.name
|
||||||
|
|
||||||
# Define 8 angles (in degrees) and their directory names
|
|
||||||
angles_and_dirs = [
|
angles_and_dirs = [
|
||||||
(0, "s"), # South
|
(0, "s"),
|
||||||
(45, "sw"), # Southwest
|
(45, "sw"),
|
||||||
(90, "w"), # West
|
(90, "w"),
|
||||||
(135, "nw"), # Northwest
|
(135, "nw"),
|
||||||
(180, "n"), # North
|
(180, "n"),
|
||||||
(225, "ne"), # Northeast
|
(225, "ne"),
|
||||||
(270, "e"), # East
|
(270, "e"),
|
||||||
(315, "se") # Southeast
|
(315, "se")
|
||||||
]
|
]
|
||||||
|
|
||||||
# Store original output path
|
|
||||||
original_filepath = scene.render.filepath
|
original_filepath = scene.render.filepath
|
||||||
base_path = bpy.path.abspath(original_filepath)
|
base_path = bpy.path.abspath(original_filepath)
|
||||||
|
|
||||||
# Create main animation directory if it doesn't exist
|
|
||||||
if base_path:
|
if base_path:
|
||||||
base_dir = os.path.dirname(base_path)
|
base_dir = os.path.dirname(base_path)
|
||||||
else:
|
else:
|
||||||
base_dir = bpy.path.abspath("//") # Use blend file directory
|
base_dir = bpy.path.abspath("//")
|
||||||
|
|
||||||
animation_dir = os.path.join(base_dir, animation_name)
|
animation_dir = os.path.join(base_dir, animation_name)
|
||||||
|
|
||||||
@@ -382,12 +423,16 @@ class RENDER_OT_multi_angle(Operator):
|
|||||||
}
|
}
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
output_nodes['lineart'] = scene.node_tree.nodes[scene['multiangle_lineart_output']]
|
output_nodes['lineart'] = scene.node_tree.nodes[scene['multiangle_lineart_output']]
|
||||||
|
if use_openpose:
|
||||||
|
output_nodes['openpose'] = scene.node_tree.nodes[scene['multiangle_openpose_output']]
|
||||||
|
|
||||||
if 'multiangle_depth_map_range' in scene:
|
if 'multiangle_depth_map_range' in scene:
|
||||||
depth_map_node = scene.node_tree.nodes[scene['multiangle_depth_map_range']]
|
depth_map_node = scene.node_tree.nodes[scene['multiangle_depth_map_range']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.report({'WARNING'}, "Compositor nodes not found. Run 'Setup Multi-Pass Compositor' first.")
|
self.report({'WARNING'}, "Compositor nodes not found. Run 'Setup Multi-Pass Compositor' first.")
|
||||||
use_multipass = False
|
use_multipass = False
|
||||||
|
use_openpose = False
|
||||||
|
use_lineart = False
|
||||||
|
|
||||||
# Update depth mapping range if specified
|
# Update depth mapping range if specified
|
||||||
if depth_map_node and props.depth_range > 0:
|
if depth_map_node and props.depth_range > 0:
|
||||||
@@ -400,34 +445,32 @@ class RENDER_OT_multi_angle(Operator):
|
|||||||
render_type = "multi-pass " if use_multipass else ""
|
render_type = "multi-pass " if use_multipass else ""
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
render_type += "with Line Art "
|
render_type += "with Line Art "
|
||||||
|
if use_openpose:
|
||||||
|
render_type += "with OpenPose "
|
||||||
|
|
||||||
self.report({'INFO'}, f"Starting multi-angle {render_type}render: {len(angles_and_dirs)} angles")
|
self.report({'INFO'}, f"Starting multi-angle {render_type}render: {len(angles_and_dirs)} angles")
|
||||||
|
|
||||||
# Render from each angle
|
|
||||||
for angle_deg, direction in angles_and_dirs:
|
for angle_deg, direction in angles_and_dirs:
|
||||||
# Create subdirectory for this angle
|
|
||||||
angle_dir = os.path.join(animation_dir, direction)
|
angle_dir = os.path.join(animation_dir, direction)
|
||||||
try:
|
try:
|
||||||
os.makedirs(angle_dir, exist_ok=True)
|
os.makedirs(angle_dir, exist_ok=True)
|
||||||
if use_multipass:
|
if use_multipass:
|
||||||
# Create subdirectories for each pass
|
|
||||||
os.makedirs(os.path.join(angle_dir, "image"), exist_ok=True)
|
os.makedirs(os.path.join(angle_dir, "image"), exist_ok=True)
|
||||||
os.makedirs(os.path.join(angle_dir, "depth"), exist_ok=True)
|
os.makedirs(os.path.join(angle_dir, "depth"), exist_ok=True)
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
os.makedirs(os.path.join(angle_dir, "lineart"), exist_ok=True)
|
os.makedirs(os.path.join(angle_dir, "lineart"), exist_ok=True)
|
||||||
|
if use_openpose:
|
||||||
|
os.makedirs(os.path.join(angle_dir, "openpose"), exist_ok=True)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.report({'ERROR'}, f"Could not create directory {angle_dir}: {e}")
|
self.report({'ERROR'}, f"Could not create directory {angle_dir}: {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Set rotation (convert degrees to radians)
|
|
||||||
angle_rad = mathutils.Matrix.Rotation(math.radians(angle_deg), 4, 'Z')
|
angle_rad = mathutils.Matrix.Rotation(math.radians(angle_deg), 4, 'Z')
|
||||||
camera_parent.rotation_euler = angle_rad.to_euler()
|
camera_parent.rotation_euler = angle_rad.to_euler()
|
||||||
|
|
||||||
# Update scene
|
|
||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
|
|
||||||
if use_multipass:
|
if use_multipass:
|
||||||
# Set up compositor output paths for each pass
|
|
||||||
base_filename = f"{animation_name}_{direction}_"
|
base_filename = f"{animation_name}_{direction}_"
|
||||||
|
|
||||||
output_nodes['image'].base_path = os.path.join(angle_dir, "image") + os.sep
|
output_nodes['image'].base_path = os.path.join(angle_dir, "image") + os.sep
|
||||||
@@ -439,16 +482,16 @@ class RENDER_OT_multi_angle(Operator):
|
|||||||
if use_lineart:
|
if use_lineart:
|
||||||
output_nodes['lineart'].base_path = os.path.join(angle_dir, "lineart") + os.sep
|
output_nodes['lineart'].base_path = os.path.join(angle_dir, "lineart") + os.sep
|
||||||
output_nodes['lineart'].file_slots[0].path = base_filename
|
output_nodes['lineart'].file_slots[0].path = base_filename
|
||||||
|
|
||||||
|
if use_openpose:
|
||||||
|
output_nodes['openpose'].base_path = os.path.join(angle_dir, "openpose") + os.sep
|
||||||
|
output_nodes['openpose'].file_slots[0].path = base_filename
|
||||||
|
|
||||||
# Don't set scene.render.filepath as compositor handles output
|
|
||||||
scene.render.filepath = ""
|
scene.render.filepath = ""
|
||||||
else:
|
else:
|
||||||
# Set output path for regular rendering
|
|
||||||
scene.render.filepath = os.path.join(angle_dir, f"{animation_name}_{direction}_")
|
scene.render.filepath = os.path.join(angle_dir, f"{animation_name}_{direction}_")
|
||||||
|
|
||||||
self.report({'INFO'}, f"Rendering {direction} angle ({angle_deg}°)")
|
self.report({'INFO'}, f"Rendering {direction} angle ({angle_deg}°)")
|
||||||
|
|
||||||
# Render animation
|
|
||||||
bpy.ops.render.render(animation=True)
|
bpy.ops.render.render(animation=True)
|
||||||
|
|
||||||
current_render += total_frames
|
current_render += total_frames
|
||||||
@@ -528,7 +571,6 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
scene = context.scene
|
scene = context.scene
|
||||||
props = scene.multi_angle_props
|
props = scene.multi_angle_props
|
||||||
|
|
||||||
# Instructions
|
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="Setup Requirements:", icon='INFO')
|
box.label(text="Setup Requirements:", icon='INFO')
|
||||||
box.label(text="• Camera must be parented to an Empty")
|
box.label(text="• Camera must be parented to an Empty")
|
||||||
@@ -537,27 +579,24 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
# Compositor setup section
|
|
||||||
compositor_box = layout.box()
|
compositor_box = layout.box()
|
||||||
compositor_box.label(text="Multi-Pass Setup:", icon='NODETREE')
|
compositor_box.label(text="Multi-Pass Setup:", icon='NODETREE')
|
||||||
|
|
||||||
# Check if compositor is set up
|
use_multipass = (scene.use_nodes and 'multiangle_image_output' in scene)
|
||||||
use_multipass = (scene.use_nodes and
|
|
||||||
'multiangle_image_output' in scene)
|
|
||||||
|
|
||||||
use_lineart = props.use_lineart and 'multiangle_lineart_output' in scene
|
use_lineart = props.use_lineart and 'multiangle_lineart_output' in scene
|
||||||
|
use_openpose = use_multipass and ('multiangle_openpose_output' in scene) and (find_openpose_view_layer(scene) is not None)
|
||||||
|
|
||||||
if use_multipass:
|
if use_multipass:
|
||||||
compositor_box.label(text="✓ Multi-pass compositor ready", icon='CHECKMARK')
|
compositor_box.label(text="✓ Multi-pass compositor ready", icon='CHECKMARK')
|
||||||
passes = ["Image", "Depth"]
|
passes = ["Image", "Depth"]
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
passes.append("Line Art")
|
passes.append("Line Art")
|
||||||
row = compositor_box.row()
|
if use_openpose:
|
||||||
row.label(text=f"Renders: {', '.join(passes)}")
|
passes.append("OpenPose")
|
||||||
|
compositor_box.label(text=f"Renders: {', '.join(passes)}")
|
||||||
else:
|
else:
|
||||||
compositor_box.label(text="⚠ Multi-pass not configured", icon='ERROR')
|
compositor_box.label(text="⚠ Multi-pass not configured", icon='ERROR')
|
||||||
|
|
||||||
# Line Art settings
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
lineart_box = layout.box()
|
lineart_box = layout.box()
|
||||||
lineart_box.label(text="Line Art Settings:", icon='GREASEPENCIL')
|
lineart_box.label(text="Line Art Settings:", icon='GREASEPENCIL')
|
||||||
@@ -573,17 +612,13 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
|
|
||||||
row = compositor_box.row()
|
row = compositor_box.row()
|
||||||
row.scale_y = 1.5
|
row.scale_y = 1.5
|
||||||
op = row.operator("render.setup_compositor", text="Setup Multi-Pass Compositor", icon='NODETREE')
|
row.operator("render.setup_compositor", text="Setup Multi-Pass Compositor", icon='NODETREE')
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
# Animation name input
|
|
||||||
layout.prop(props, "animation_name")
|
layout.prop(props, "animation_name")
|
||||||
|
|
||||||
# Depth range setting
|
|
||||||
layout.prop(props, "depth_range")
|
layout.prop(props, "depth_range")
|
||||||
|
|
||||||
# Display current settings
|
|
||||||
col = layout.column(align=True)
|
col = layout.column(align=True)
|
||||||
col.label(text="Current Settings:", icon='SETTINGS')
|
col.label(text="Current Settings:", icon='SETTINGS')
|
||||||
|
|
||||||
@@ -602,19 +637,19 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
total_frames = frame_end - frame_start + 1
|
total_frames = frame_end - frame_start + 1
|
||||||
col.label(text=f"Frames: {frame_start}-{frame_end} ({total_frames} total)")
|
col.label(text=f"Frames: {frame_start}-{frame_end} ({total_frames} total)")
|
||||||
|
|
||||||
# Calculate total renders
|
pass_count = 1
|
||||||
pass_count = 1 # Always have image
|
|
||||||
if use_multipass:
|
if use_multipass:
|
||||||
pass_count = 2 # Image + Depth
|
pass_count = 2
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
pass_count = 3 # Image + Depth + Line Art
|
pass_count += 1
|
||||||
|
if use_openpose:
|
||||||
|
pass_count += 1
|
||||||
|
|
||||||
total_renders = total_frames * 8 * pass_count
|
total_renders = total_frames * 8 * pass_count
|
||||||
col.label(text=f"Total renders: {total_frames * 8} × {pass_count} passes = {total_renders}")
|
col.label(text=f"Total renders: {total_frames * 8} × {pass_count} passes = {total_renders}")
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
# Render button
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.scale_y = 2.0
|
row.scale_y = 2.0
|
||||||
button_text = "Render 8 Angles"
|
button_text = "Render 8 Angles"
|
||||||
@@ -622,13 +657,14 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
button_text += " (Multi-Pass"
|
button_text += " (Multi-Pass"
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
button_text += " + Line Art"
|
button_text += " + Line Art"
|
||||||
|
if use_openpose:
|
||||||
|
button_text += " + OpenPose"
|
||||||
button_text += ")"
|
button_text += ")"
|
||||||
else:
|
else:
|
||||||
button_text += " (Image Only)"
|
button_text += " (Image Only)"
|
||||||
|
|
||||||
row.operator("render.multi_angle", text=button_text, icon='RENDER_ANIMATION')
|
row.operator("render.multi_angle", text=button_text, icon='RENDER_ANIMATION')
|
||||||
|
|
||||||
# Output structure info
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="Output Structure:", icon='FILE_FOLDER')
|
box.label(text="Output Structure:", icon='FILE_FOLDER')
|
||||||
@@ -638,6 +674,8 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
passes_text = "image/, depth/"
|
passes_text = "image/, depth/"
|
||||||
if use_lineart:
|
if use_lineart:
|
||||||
passes_text += ", lineart/"
|
passes_text += ", lineart/"
|
||||||
|
if use_openpose:
|
||||||
|
passes_text += ", openpose/"
|
||||||
box.label(text=f" ├── s/{passes_text}")
|
box.label(text=f" ├── s/{passes_text}")
|
||||||
box.label(text=f" ├── sw/{passes_text}")
|
box.label(text=f" ├── sw/{passes_text}")
|
||||||
box.label(text=f" └── ... (8 angles × {pass_count} passes)")
|
box.label(text=f" └── ... (8 angles × {pass_count} passes)")
|
||||||
@@ -647,12 +685,10 @@ class RENDER_PT_multi_angle_panel(Panel):
|
|||||||
box.label(text=" ├── sw/ (image files)")
|
box.label(text=" ├── sw/ (image files)")
|
||||||
box.label(text=" └── ... (8 angles)")
|
box.label(text=" └── ... (8 angles)")
|
||||||
|
|
||||||
# Angles info
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="Render Angles:", icon='CAMERA_DATA')
|
box.label(text="Render Angles:", icon='CAMERA_DATA')
|
||||||
|
|
||||||
# Create two columns for the angles
|
|
||||||
split = box.split(factor=0.5)
|
split = box.split(factor=0.5)
|
||||||
col1 = split.column()
|
col1 = split.column()
|
||||||
col2 = split.column()
|
col2 = split.column()
|
||||||
|
|||||||
Reference in New Issue
Block a user