Files
ShortVideoGenerator/projects/water-hydrogen-bond-surface-tension/blender/verify_production.py
T

114 lines
5.4 KiB
Python

"""정식 물 영상의 실제 9개 장면·기준 재사용·부피·주석 위치를 검사한다."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
import bpy
import bmesh
from bpy_extras.object_utils import world_to_camera_view
sys.path.insert(0,str(Path(__file__).resolve().parent))
from verify_model import verify
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
CUTS=json.loads((PROJECT/'stages/07-cuts/r1/artifact.json').read_text(encoding='utf-8'))['payload']['cuts']
SCENES=json.loads((PROJECT/'stages/08-scenes/r3/artifact.json').read_text(encoding='utf-8'))['payload']['scenes']
EXPECTED={}
for cut,plan in zip(CUTS,SCENES):
name=plan['start_state'].split(';')[0].split('=')[1]
start,end=cut['start_ms']//20+1,cut['end_ms']//20+1
EXPECTED[name]=(min(EXPECTED.get(name,(start,end))[0],start),end)
def volume(obj):
evaluated=obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
mesh=evaluated.to_mesh()
bm=bmesh.new()
bm.from_mesh(mesh)
assert all(e.is_manifold for e in bm.edges), '닫힌 물방울 mesh 아님'
bm.transform(evaluated.matrix_world)
value=abs(bm.calc_volume(signed=True))
bm.free()
evaluated.to_mesh_clear()
return value
def run(source,animated):
result=verify(source,EXPECTED)
assert bpy.context.scene.get('approved_source_sha256') or bpy.data.scenes['BondPair'].get('approved_source_sha256'), '승인 원본 연결 없음'
assert not bpy.data.libraries and not bpy.data.images and not bpy.data.fonts and not bpy.data.sounds
comparison=bpy.data.scenes['EqualVolumeComparison']
bpy.context.window.scene=comparison
comparison.frame_set(comparison.frame_start)
volumes=[volume(comparison.objects[name]) for name in ['ComparisonLong','ComparisonRound']]
assert abs(volumes[0]/volumes[1]-1)<.01, '비교 두 물방울 부피 불일치'
result['comparison_volumes']=volumes
anchors=[]
for cut,plan in zip(CUTS,SCENES):
scene=bpy.data.scenes[plan['start_state'].split(';')[0].split('=')[1]]
bpy.context.window.scene=scene
start,end=cut['start_ms']//20,cut['end_ms']//20
samples=[]
for logical in sorted(set([start,(start+end)//2,end])):
scene.frame_set(logical+1)
labels={}
for obj in scene.objects:
if obj.get('anchor'):
point=world_to_camera_view(scene,scene.camera,obj.matrix_world.translation)
assert 0<point.x<1 and 0<point.y<1 and point.z>0, '주석 위치 화면 밖'
labels[obj['anchor']]=dict(object_name=obj.name,x=round(point.x*1080,3),y=round((1-point.y)*1920,3),depth=round(point.z,3))
assert labels, '컷 주석 앵커 누락'
samples.append(dict(logical_frame=logical,blender_frame=logical+1,labels=labels))
anchors.append(dict(cut_id=cut['id'],scene_name=scene.name,start_frame=start,end_frame=end,samples=samples))
result['cut_anchors']=anchors
if animated:
assert bpy.data.scenes['BondPair'].get('reference_source_sha256'), '실제 10 원본 연결 없음'
scene=bpy.data.scenes['DropletStretch']
bpy.context.window.scene=scene
values=[]
for logical in range(3172,4077):
scene.frame_set(logical+1)
values.append(volume(scene.objects['Droplet']))
relative=max(abs(v/values[0]-1) for v in values)
assert relative<.01, '복귀 포함 물방울 부피 1% 초과'
scene.frame_set(3853)
assert max(abs(s-1) for s in scene.objects['Droplet'].scale)<.001, 'c21 구형 복귀 없음'
result['full_drop_volume']=dict(logical_start=3172,logical_end=4076,frames=905,relative_change=relative)
network=bpy.data.scenes['LiquidNetwork']
bpy.context.window.scene=network
pair_sets=[]
maximum=0
for frame in range(1637,2470):
network.frame_set(frame)
pairs=set()
for obj in network.objects:
if obj.get('role')!='hydrogen_bond' or obj.scale.x<=.0011:
continue
h,o=network.objects[obj['hydrogen']],network.objects[obj['oxygen']]
assert h.parent!=o.parent and h['element']=='H' and o['element']=='O'
a,b=h.matrix_world.translation,o.matrix_world.translation
error=(obj.location-a).cross(b-a).length
assert error<.003, '액체 점선이 실제 H···O 선분에서 벗어남'
maximum=max(maximum,error)
pairs.add((h.name,o.name))
if frame in [1637,2019,2257,2469]:
pair_sets.append(sorted(pairs))
assert pair_sets[0]!=pair_sets[1] and pair_sets[1]!=pair_sets[2], '액체 연결 반복 교체 없음'
result['network_frames']=833
result['network_active_pairs']=pair_sets
result['network_maximum_line_error']=maximum
result['animated']=animated
result['result']='GREEN'
return result
if __name__=='__main__':
parser=argparse.ArgumentParser()
parser.add_argument('--source',required=True)
parser.add_argument('--out',required=True)
parser.add_argument('--animated',action='store_true')
args=parser.parse_args(sys.argv[sys.argv.index('--')+1:])
result=run(Path(args.source),args.animated)
Path(args.out).write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('정식 원본 검사 GREEN',result['source_sha256'])