210 lines
7.2 KiB
Python
210 lines
7.2 KiB
Python
import bpy
|
|
import os
|
|
from . import utils
|
|
|
|
|
|
@bpy.app.handlers.persistent
|
|
def on_render_complete(scene, depsgraph=None):
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
|
|
@bpy.app.handlers.persistent
|
|
def on_render_cancel(scene, depsgraph=None):
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
SCENERENDERER_OT_render_all._cancel_requested = True
|
|
|
|
|
|
class SCENERENDERER_OT_render_all(bpy.types.Operator):
|
|
bl_idname = "scenerenderer.render_all"
|
|
bl_label = "Render All Scenes"
|
|
bl_description = "Render all scenes to //renders/"
|
|
bl_options = {'REGISTER'}
|
|
|
|
_timer = None
|
|
_scenes = []
|
|
_index = 0
|
|
_total = 0
|
|
_orig_lock = None
|
|
|
|
_is_rendering = False
|
|
_cancel_requested = False
|
|
|
|
def modal(self, context, event):
|
|
if event.type == 'ESC' or self._cancel_requested:
|
|
self.report({'WARNING'}, "Render batch cancelled")
|
|
self.finish(context)
|
|
return {'CANCELLED'}
|
|
|
|
if event.type == 'TIMER':
|
|
if self._is_rendering:
|
|
return {'PASS_THROUGH'}
|
|
|
|
if self._index >= self._total:
|
|
self.report({'INFO'}, f"All {self._total} scenes rendered")
|
|
self.finish(context)
|
|
return {'FINISHED'}
|
|
|
|
scene_name = self._scenes[self._index]
|
|
self._index += 1
|
|
|
|
try:
|
|
SCENERENDERER_OT_render_all._is_rendering = True
|
|
self._render_scene(context, scene_name)
|
|
except Exception as e:
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
self.report({'ERROR'}, f"Failed {scene_name}: {e}")
|
|
|
|
context.area.tag_redraw()
|
|
|
|
return {'PASS_THROUGH'}
|
|
|
|
def finish(self, context):
|
|
if self._timer:
|
|
context.window_manager.event_timer_remove(self._timer)
|
|
self._timer = None
|
|
context.window.cursor_set('DEFAULT')
|
|
if self._orig_lock is not None:
|
|
for sc in bpy.data.scenes:
|
|
sc.render.use_lock_interface = self._orig_lock
|
|
|
|
if on_render_complete in bpy.app.handlers.render_complete:
|
|
bpy.app.handlers.render_complete.remove(on_render_complete)
|
|
if on_render_cancel in bpy.app.handlers.render_cancel:
|
|
bpy.app.handlers.render_cancel.remove(on_render_cancel)
|
|
|
|
def _render_scene(self, context, scene_name):
|
|
settings = context.scene.scene_renderer
|
|
scene = bpy.data.scenes.get(scene_name)
|
|
if not scene:
|
|
print(f" [SKIP] Scene not found: {scene_name}")
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
return
|
|
|
|
if scene.name == "_Shots":
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
return
|
|
|
|
bpy.context.window.scene = scene
|
|
|
|
cam = scene.objects.get("Profile")
|
|
if cam:
|
|
scene.camera = cam
|
|
elif not scene.camera:
|
|
print(f" [SKIP] No camera in {scene.name}")
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
return
|
|
|
|
r = scene.render
|
|
r.engine = 'CYCLES'
|
|
r.image_settings.file_format = 'PNG'
|
|
r.image_settings.color_mode = 'RGBA'
|
|
r.film_transparent = True
|
|
|
|
c = scene.cycles
|
|
c.samples = 1 if settings.test_mode else settings.samples
|
|
|
|
if settings.exposure != 0.0:
|
|
scene.view_settings.exposure = settings.exposure
|
|
scene.cycles.film_exposure = 2.0 ** settings.exposure
|
|
if settings.look != 'None':
|
|
scene.view_settings.look = settings.look
|
|
|
|
if settings.test_mode:
|
|
r.resolution_percentage = 10
|
|
|
|
render_dir = bpy.path.abspath("//renders")
|
|
if settings.test_mode:
|
|
render_dir = os.path.join(render_dir, "test")
|
|
os.makedirs(render_dir, exist_ok=True)
|
|
|
|
safe_name = scene.name.replace("/", "_").replace("\\", "_").replace(":", "_").strip()
|
|
output_path = os.path.join(render_dir, safe_name)
|
|
|
|
if settings.skip_existing and os.path.exists(output_path + ".png"):
|
|
print(f" [SKIP] Already exists: {safe_name}.png")
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
return
|
|
|
|
r.filepath = output_path
|
|
|
|
print(f"\n [{self._index}/{self._total}] Rendering: {scene.name}")
|
|
with context.temp_override(window=context.window, area=context.area, region=context.region):
|
|
bpy.ops.render.render('INVOKE_DEFAULT', write_still=True, scene=scene.name)
|
|
print(f" [STARTED] {scene.name}")
|
|
|
|
def invoke(self, context, event):
|
|
scenes = utils.get_scenes()
|
|
return context.window_manager.invoke_props_dialog(self, width=300)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
scenes = utils.get_scenes()
|
|
|
|
layout.label(text=f"Ready to render {len(scenes)} scenes:", icon='RENDER_STILL')
|
|
box = layout.box()
|
|
col = box.column(flow=True)
|
|
for s in scenes[:8]:
|
|
col.label(text=f" * {s}", icon='SCENE_DATA')
|
|
if len(scenes) > 8:
|
|
col.label(text=f" ... and {len(scenes) - 8} more")
|
|
layout.separator()
|
|
layout.label(text="Click OK to start.", icon='INFO')
|
|
|
|
def execute(self, context):
|
|
settings = context.scene.scene_renderer
|
|
|
|
if on_render_complete not in bpy.app.handlers.render_complete:
|
|
bpy.app.handlers.render_complete.append(on_render_complete)
|
|
if on_render_cancel not in bpy.app.handlers.render_cancel:
|
|
bpy.app.handlers.render_cancel.append(on_render_cancel)
|
|
|
|
SCENERENDERER_OT_render_all._is_rendering = False
|
|
SCENERENDERER_OT_render_all._cancel_requested = False
|
|
|
|
silenced = utils.silence_puresky()
|
|
if silenced:
|
|
print(f" [FIX] Silenced {silenced} PureSky handlers.")
|
|
|
|
fxd = utils.fix_lib_overrides()
|
|
print(f"Fixed {fxd} lib-override parameters")
|
|
|
|
img_fxd = utils.fix_missing_images()
|
|
if img_fxd:
|
|
print(f"Fixed {img_fxd} missing images with magenta dummies")
|
|
|
|
if not settings.use_cpu:
|
|
metal_ok = utils.init_metal_gpu()
|
|
if metal_ok:
|
|
print("Initialized Metal GPU")
|
|
else:
|
|
print("Metal GPU init failed, using whatever is available")
|
|
|
|
self._orig_lock = bpy.data.scenes[0].render.use_lock_interface
|
|
for sc in bpy.data.scenes:
|
|
sc.render.use_lock_interface = True
|
|
sc.cycles.device = 'CPU' if settings.use_cpu else 'GPU'
|
|
|
|
bpy.context.view_layer.update()
|
|
depsgraph = bpy.context.evaluated_depsgraph_get()
|
|
depsgraph.update()
|
|
|
|
self._scenes = utils.get_scenes()
|
|
if settings.test_mode:
|
|
self._scenes = self._scenes[:1]
|
|
|
|
self._total = len(self._scenes)
|
|
self._index = 0
|
|
|
|
if self._total == 0:
|
|
self.report({'WARNING'}, "No scenes to render!")
|
|
return {'CANCELLED'}
|
|
|
|
print(f"\n{'='*50}")
|
|
print(f" Scene Renderer -- {self._total} scenes"
|
|
f"{' (TEST MODE)' if settings.test_mode else ''}")
|
|
print(f"{'='*50}")
|
|
|
|
context.window_manager.modal_handler_add(self)
|
|
self._timer = context.window_manager.event_timer_add(0.5, window=context.window)
|
|
context.window.cursor_set('WAIT')
|
|
|
|
return {'RUNNING_MODAL'} |