"""승인된 31컷에서 정식 요청만 작성한다. 실제 결과는 실행 후 채운다.""" import argparse import json from pathlib import Path ROOT=Path(__file__).resolve().parents[3] PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension' PROFILE=dict(engine='BLENDER_EEVEE',device='GPU',fps=50,width=1080,height=1920,samples=64,seed=0) REFERENCE_PLAN=[ ('style-3d','style',None,'BondPair','StyleAssets',1017), ('background-droplet','background','bg-droplet','OpeningDroplet','DropletStudioAssets',0), ('background-molecules','background','bg-molecules','BondPair','MoleculeStudioAssets',1017), ('background-interface','background','bg-interface','Interface','InterfaceAssets',3050), ('background-comparison','background','bg-comparison','EqualVolumeComparison','ComparisonStudioAssets',4076), ('object-molecule','object',None,'SingleMolecule','MoleculeAssets',396), ('object-droplet','object',None,'OpeningDroplet','DropletAssets',0)] def read(stage,revision): return json.loads((PROJECT/f'stages/{stage}/r{revision}/artifact.json').read_text(encoding='utf-8')) def write(stage,request,inputs): path=PROJECT/f'stages/{stage}/r2/artifact.json' existing=json.loads(path.read_text(encoding='utf-8')) if path.exists() else None assert not existing or not existing['payload']['result'], '완료 또는 부분 실제 결과 덮어쓰기 금지' value=dict(schema_version='1.1',project_id=PROJECT.name,stage_id=stage,revision=2,inputs=[dict(stage_id=s,revision=r) for s,r in inputs],payload=dict(request=request,result=None)) path.write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') if __name__=='__main__': parser=argparse.ArgumentParser() parser.add_argument('--stage',choices=['10-references','11-images','12-motion'],required=True) args=parser.parse_args() plans=read('08-scenes',3)['payload']['scenes'] if args.stage!='12-motion': entities=read('09-entities',3)['payload'] read('02-research',1) if args.stage=='10-references': backgrounds={b['id']:b for b in entities['backgrounds']} items=[] for id,kind,entity,scene,obj,frame in REFERENCE_PLAN: prompt='승인 08 r3의 '+scene+' 상태와 교육용 3D 고정 특징. 실제 '+obj+' 자산. ' if entity: prompt+=' '.join(backgrounds[entity]['fixed_features']) elif kind=='object': prompt+='O1H2·V자·내부 남색 실선과 금색 분자간 H···O의 구별을 유지한다.' if id=='object-molecule' else '매끈한 청색 닫힌 mesh와 같은 물의 양을 유지하며 별도 껍질이 없다.' else: prompt+='references/1.png를 보존하고 단순 형태·정돈된 색·부드러운 조명·크림 배경으로 해석한다. 글자는 합성한다.' items.append(dict(id=id,kind=kind,entity_id=entity,prompt=prompt)) write(args.stage,dict(style_reference='references/1.png',items=items,engine='Blender',render_profile=PROFILE),[('02-research',1),('08-scenes',3),('09-entities',3)]) elif args.stage=='11-images': read('03-script',1) cuts=read('07-cuts',1)['payload']['cuts'] reference=read('10-references',2) assert reference['payload']['result'] and len(reference['payload']['result']['items'])==7 appearances={p['cut_id']:p for p in entities['appearances']} mapping={'bg-droplet':'background-droplet','bg-molecules':'background-molecules','bg-interface':'background-interface','bg-comparison':'background-comparison'} frames=[] for cut,plan in zip(cuts,plans): scene=plan['start_state'].split(';')[0].split('=')[1] refs=['style-3d']+[mapping[b] for b in appearances[cut['id']]['background_ids']] refs+=['object-molecule'] if scene in ['SingleMolecule','BondPair','LiquidNetwork','Interface'] else ['object-droplet'] if scene=='AnswerDroplet': refs.append('object-molecule') for position,key in [('start','start_ms'),('end','end_ms')]: frames.append(dict(id=cut['id']+'-'+position,cut_id=cut['id'],position=position,prompt=plan['description']+' '+plan[position+'_state']+' '+plan['camera'],reference_ids=refs,scene_name=scene,frame_index=cut[key]//20)) write(args.stage,dict(frames=frames,engine='Blender',render_profile=PROFILE,scene_path='projects/'+PROJECT.name+'/stages/11-images/r2/generated-source/animation.blend'),[('02-research',1),('03-script',1),('07-cuts',1),('08-scenes',3),('09-entities',3),('10-references',2)]) else: cuts=read('07-cuts',1)['payload']['cuts'] images=read('11-images',2) assert images['payload']['result'] and len(images['payload']['result']['frames'])==62 frames={f['id']:f for f in images['payload']['request']['frames']} clips=[] for cut,plan in zip(cuts,plans): start,end=frames[cut['id']+'-start'],frames[cut['id']+'-end'] clips.append(dict(cut_id=cut['id'],start_frame_id=start['id'],end_frame_id=end['id'],duration_ms=cut['end_ms']-cut['start_ms'],motion_prompt=plan['description']+' '+plan['start_state']+' → '+plan['end_state']+' '+plan['camera'],scene_name=start['scene_name'],start_frame=start['frame_index'],end_frame=end['frame_index'])) write(args.stage,dict(clips=clips,engine='Blender',render_profile=PROFILE,scene_path=images['payload']['result']['scene_file']['path']),[('07-cuts',1),('08-scenes',3),('11-images',2)])