Skip to main content

Automação de render

Disparar renders automaticamente, monitorar progresso, enviar notificação quando termina, limpar a cena do Houdini antes de renderizar — Python conecta tudo isso sem você precisar ficar olhando para a tela.

Renderizando o after via linha de comando

import subprocess
def render_ae(projeto, comp, saida):
aerender = "/Applications/Adobe After Effects 2024/aerender"
cmd = [
aerender,
"-project", projeto,
"-comp", comp,
"-output", saida,
]
print(f"Renderizando: {comp}")
subprocess.run(cmd, check=True)
print(f"Concluido: {saida}")
renders = [
("/projetos/abertura.aep", "Main_1920x1080", "/renders/abertura.####.exr"),
("/projetos/abertura.aep", "Main_1080x1920", "/renders/abertura_v.####.exr"),
]
for projeto, comp, saida in renders:
render_ae(projeto, comp, saida)

Monitorando frames renderizados

import time
import glob
import os
def monitorar_render(pasta, total_frames, intervalo=10):
print(f"Monitorando: {pasta}")
while True:
frames = glob.glob(os.path.join(pasta, "*.exr"))
concluidos = len(frames)
pct = (concluidos / total_frames) * 100
print(f"\r{concluidos}/{total_frames} frames ({pct:.1f}%)", end="")
if concluidos >= total_frames:
print("\nRender concluido!")
break
time.sleep(intervalo)
monitorar_render("/renders/cena01", total_frames=240)

Notificação no Mac quando o render termina

import subprocess
def notificar(titulo, mensagem):
script = f'display notification "{mensagem}" with title "{titulo}"'
subprocess.run(["osascript", "-e", script])
notificar("Render Concluido", "cena01 finalizada, 240 frames")

Pipeline completo: simular, renderizar, converter

import subprocess
pasta = "/projeto/cena01"
print("1. Simulando...")
subprocess.run(["hython", f"{pasta}/scripts/simular.py"], check=True)
print("2. Renderizando...")
subprocess.run(["hython", f"{pasta}/scripts/renderizar.py"], check=True)
print("3. Convertendo...")
subprocess.run([
"ffmpeg",
"-i", f"{pasta}/renders/frame.%04d.exr",
"-c:v", "libx264",
"-crf", "18",
f"{pasta}/entrega/cena01_v01.mp4"
], check=True)
print("Pipeline completo!")

Scripts úteis no Houdini antes do render

Limpar nós não conectados

import hou
def clean_unconnected_nodes(context_path='/obj/geo1'):
"""Remove nós sem conexões no network especificado."""
network = hou.node(context_path)
if not network:
print(f"Network não encontrado: {context_path}")
return
removed = 0
for node in network.children():
has_connections = any([node.inputs(), node.outputs()])
if not has_connections and node.type().name() not in ('output',):
node.destroy()
removed += 1
print(f"{removed} nós removidos.")
clean_unconnected_nodes()

Cache automático de simulações

import hou
import os
def cache_all_sims(output_dir='/tmp/cache'):
os.makedirs(output_dir, exist_ok=True)
for node in hou.node('/obj').recursiveGlob('*'):
if node.type().name() == 'filecache':
current_path = node.parm('file').eval()
node.parm('file').set(
os.path.join(output_dir, os.path.basename(current_path))
)
print(f"Cacheando: {node.path()}")
node.parm('execute').pressButton()
print("Cache completo.")
cache_all_sims('/renders/project_cache')

Exportar câmera para JSON (para importar no after)

import hou, json
def export_camera_to_json(cam_path, output_path, frame_range=(1, 100)):
cam = hou.node(cam_path)
if not cam:
print("Camera não encontrada.")
return
data = {'frames': {}}
for frame in range(frame_range[0], frame_range[1] + 1):
hou.setFrame(frame)
pos = cam.worldTransform().extractTranslates()
rot = cam.worldTransform().extractRotates()
data['frames'][frame] = {
'tx': pos[0], 'ty': pos[1], 'tz': pos[2],
'rx': rot[0], 'ry': rot[1], 'rz': rot[2],
'focal': cam.parm('focal').eval(),
'aperture': cam.parm('aperture').eval(),
}
with open(output_path, 'w') as f:
json.dump(data, f, indent=2)
print(f"Camera exportada: {output_path}")
export_camera_to_json('/obj/cam1', '/tmp/camera_data.json', (1, 120))

Info

O JSON de câmera exportado do Houdini pode ser lido por um ExtendScript no after para recriar o movimento de câmera como nulos animados.