물 분자 전체를 실제 물 영역 안에 포함한 r3 시제품 검증

This commit is contained in:
NINI
2026-10-10 01:30:59 +09:00
parent b1d697e320
commit ee3d18be33
20 changed files with 2165 additions and 43 deletions
@@ -6,9 +6,71 @@ from pathlib import Path
import sys
import bpy
import bmesh
import numpy as np
from mathutils import Vector
from bpy_extras.object_utils import world_to_camera_view
def containment(scene):
"""전체 프레임의 실제 mesh 정점을 물 영역·화면 윤곽과 대조한다."""
bpy.context.window.scene = scene
body = scene.objects['WaterCutawayBody']
deps = bpy.context.evaluated_depsgraph_get()
evaluated = body.evaluated_get(deps)
mesh = evaluated.to_mesh()
water = np.array([tuple(evaluated.matrix_world @ v.co) for v in mesh.vertices])
evaluated.to_mesh_clear()
low, high = water.min(axis=0), water.max(axis=0)
# 실제 물 표시 mesh에서 경계를 얻고 중심·별도 메타데이터만으로 판정하지 않는다.
origin = world_to_camera_view(scene,scene.camera,Vector((0,0,0)))
projection = np.array([tuple(world_to_camera_view(scene,scene.camera,Vector(b))-origin)[:2] for b in ((1,0,0),(0,1,0),(0,0,1))])
offset = np.array(tuple(origin)[:2])
screen = (water @ projection + offset) * (1080,1920)
points = sorted(set(map(tuple,screen)))
def cross(a,b,c):
return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])
lower, upper = [], []
for chain, vertices in ((lower,points),(upper,reversed(points))):
for p in vertices:
while len(chain)>=2 and cross(chain[-2],chain[-1],p)<=0:
chain.pop()
chain.append(p)
hull = np.array(lower[:-1]+upper[:-1])
minimum = np.full(3,np.inf)
pixel_margin = float('inf')
vertex_count = 0
for logical in range(2468,3173):
scene.frame_set(logical+1)
deps = bpy.context.evaluated_depsgraph_get()
for obj in scene.objects:
role = obj.get('role')
if role not in ('atom','covalent','hydrogen_bond') or (role=='hydrogen_bond' and obj.scale.x<=.0011):
continue
evaluated = obj.evaluated_get(deps)
mesh = evaluated.to_mesh()
local = np.array([tuple(v.co) for v in mesh.vertices])
transform = np.array(evaluated.matrix_world)
vertices = local @ transform[:3,:3].T + transform[:3,3]
evaluated.to_mesh_clear()
margin = np.minimum(vertices-low,high-vertices).min(axis=0)
assert margin.min()>=.03, f'물 영역 밖 또는 여백 부족: F{logical} {obj.name}, 축 여백 {margin.tolist()}'
minimum = np.minimum(minimum,margin)
projected = (vertices @ projection + offset) * (1080,1920)
assert (projected.min(axis=0)>0).all() and (projected.max(axis=0)<(1080,1920)).all(), '분자 화면 잘림'
for a,b in zip(hull,np.roll(hull,-1,axis=0)):
edge = b-a
distance = (edge[0]*(projected[:,1]-a[1])-edge[1]*(projected[:,0]-a[0]))/np.linalg.norm(edge)
assert distance.min()>=0, f'파란 물 영역 투영 밖: F{logical} {obj.name}'
pixel_margin = min(pixel_margin,float(distance.min()))
vertex_count += len(vertices)
# 앞면과 윗면은 실제 열린 절개이며 물의 고체 껍질을 만들지 않는다.
assert len(body.data.polygons)==4, '물 절개 면 수 불일치'
for face in body.data.polygons:
points=np.array([tuple(body.matrix_world @ body.data.vertices[i].co) for i in face.vertices])
assert not np.allclose(points[:,1],low[1]) and not np.allclose(points[:,2],high[2]), '앞면·윗면 절개가 닫힘'
return dict(logical_frame_start=2468,logical_frame_end=3172,frames=705,water_bounds=[low.tolist(),high.tolist()],minimum_axis_margin=minimum.tolist(),minimum_projected_margin_px=pixel_margin,evaluated_vertices=vertex_count,mesh_roles=['atom','covalent','active_hydrogen_bond'],open_faces=['front','top'])
def verify(source):
assert source.is_file(), "원본 파일 없음"
bpy.ops.wm.open_mainfile(filepath=str(source))
@@ -52,6 +114,7 @@ def verify(source):
assert (pair.objects["PairB"].matrix_world.translation - before).length > .1, "실제 3D 이동 없음"
interface = bpy.data.scenes['Interface']
bpy.context.window.scene = interface
water_containment = containment(interface)
connection_sets, neighborhoods = [], []
for frame in (2807,2907,3051):
interface.frame_set(frame)
@@ -99,7 +162,7 @@ def verify(source):
if obj.get("label"):
p = world_to_camera_view(scene, scene.camera, obj.matrix_world.translation)
anchors[name]['labels'][obj["label"]] = {"x": round(p.x * 1080, 2), "y": round((1-p.y)*1920, 2), "depth": round(p.z, 2)}
return {"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(), "blender_version": bpy.app.version_string, "scenes": checks, "interface_frames_blender":[2807,2907,3051], "interface_active_pairs":connection_sets,"interface_neighborhoods":neighborhoods,"volume_frames_blender": [3173,3223,3273,3323,3373], "volumes": volumes, "relative_volume_change": relative, "evaluated_scales": scales, "label_anchors": anchors, "text_baked": False, "physical_simulation": False}
return {"water_containment":water_containment,"source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(), "blender_version": bpy.app.version_string, "scenes": checks, "interface_frames_blender":[2807,2907,3051], "interface_active_pairs":connection_sets,"interface_neighborhoods":neighborhoods,"volume_frames_blender": [3173,3223,3273,3323,3373], "volumes": volumes, "relative_volume_change": relative, "evaluated_scales": scales, "label_anchors": anchors, "text_baked": False, "physical_simulation": False}
if __name__ == "__main__":