# aduc_framework/engineers/planner_3d.py # # Copyright (C) August 4, 2025 Carlos Rodrigues dos Santos # # Versão 4.1.0 (DNA Seguro por Cena) # # - Remove o acúmulo global de keyframes e a divisão por índices. # - Atualiza cada cena diretamente com os keyframes gerados pelo Painter. # - Garante que o DNA final nunca fique “bagunçado”. # - Foco na simplicidade: Planner orquestra, Painter gera, DNA organiza. import logging import json from typing import List, Dict, Any, Generator from .deformes3D import deformes3d_engine_singleton from ..types import KeyframeGenerationJob logger = logging.getLogger(__name__) class Planner3D: """ Gerente de projeto para a fase de keyframes. Prepara e despacha ordens de serviço para o Painter (Deformes3D), e injeta os keyframes diretamente no DNA cena a cena. """ def generate_keyframes_from_dna( self, generation_state: Dict[str, Any], initial_chat_history: List[Dict[str, Any]] ) -> Generator[Dict[str, Any], None, None]: painter = deformes3d_engine_singleton chat_history = initial_chat_history.copy() chat_history.append({ "role": "Planner3D", "content": "Roteiro recebido. Iniciando criação de keyframes..." }) yield {"chat": chat_history, "gallery": [], "dna": generation_state} # 1. Extrai dados globais scenes_list = generation_state.get("scenes", []) ref_image_paths = [ref['caminho'] for ref in generation_state.get("midias_referencia", [])] global_prompt = generation_state.get("prompt_geral", "") pre_prod_params = generation_state.get("parametros_geracao", {}).get("pre_producao", {}) if not scenes_list or not ref_image_paths: logger.warning("Planner3D: Nenhuma cena ou imagem de referência encontrada no DNA.") yield {"chat": chat_history, "status": "keyframes_complete"} return # Mapeamento fixo de imagens de referência ref_id_to_path_map = {i: path for i, path in enumerate(ref_image_paths)} available_ref_ids = list(ref_id_to_path_map.keys()) # 2. Loop de cenas for scene_idx, scene_data in enumerate(scenes_list): scene_id = scene_data.get("id") chat_history.append({ "role": "Planner3D", "content": f"Desenhando a Cena {scene_id}/{len(scenes_list)}..." }) yield {"chat": chat_history} storyboard_da_cena = [ato.get("resumo_ato", "") for ato in scene_data.get("atos", [])] if not storyboard_da_cena: logger.info(f"Planner3D: Cena {scene_id} sem atos, pulando.") continue # 3. Cria ordem de serviço job = KeyframeGenerationJob( storyboard=storyboard_da_cena, global_prompt=global_prompt, ref_image_paths=ref_image_paths, ref_id_to_path_map=ref_id_to_path_map, available_ref_ids=available_ref_ids, keyframe_prefix=f"scene_{scene_id:02d}", params=pre_prod_params ) # 4. Delega para o Painter scene_keyframes = painter.generate_keyframes_from_job(job=job) # 5. Injeta diretamente no DNA scene_data["keyframes"] = scene_keyframes if scene_keyframes: chat_history.append({ "role": "Planner3D", "content": f"Cena {scene_id} concluída ({len(scene_keyframes)} keyframes)." }) yield {"chat": chat_history} logger.info("Planner3D: Geração de todos os keyframes concluída.") yield { "chat": chat_history, "gallery": [kf["caminho_pixel"] for scene in scenes_list for kf in scene.get("keyframes", [])], "status": "keyframes_complete", "dna": generation_state } planner_3d_singleton = Planner3D()