"""정답지(골든 TCP540 밀착) 기하 추출: 실제 플랜지 정점(truthTool-로컬) + 노즐끝 + 마운팅축 역산."""
import asyncio, json
import numpy as np
from playwright.async_api import async_playwright
URL='http://localhost:8090/DOZIKWORKS_OS_v5_locked.dc.html'
async def main():
    async with async_playwright() as p:
        br=await p.chromium.launch(channel='msedge',headless=True)
        pg=await br.new_page()
        await pg.goto(URL); await pg.wait_for_function("window.__dzw && window.__dzw._torchMesh",timeout=15000)
        data=await pg.evaluate("""()=>{ const d=window.__dzw,T=d._T; const me=d._torchMesh;
          me.updateMatrixWorld(true); const tt=d._truthTool; tt.updateMatrixWorld(true);
          const g=me.geometry; const pos=g.attributes.position; const N=pos.count;
          // 로컬 z분포로 mount(min z)·nozzle(max z) 판정 (g.translate(-tip)로 nozzle≈0)
          let minz=1e9,maxz=-1e9; for(let i=0;i<N;i++){ const z=pos.getZ(i); if(z<minz)minz=z; if(z>maxz)maxz=z; }
          const span=maxz-minz;
          // 플랜지 끝면: min z + 3% 이내. truthTool-로컬로 변환, 다운샘플
          const flange=[]; const nozzle=[]; const v=new T.Vector3();
          for(let i=0;i<N;i++){ const z=pos.getZ(i);
            if(z < minz+span*0.03){ v.fromBufferAttribute(pos,i); v.applyMatrix4(me.matrixWorld); tt.worldToLocal(v);
              if(flange.length<1500) flange.push([+v.x.toFixed(2),+v.y.toFixed(2),+v.z.toFixed(2)]); }
            if(z > maxz-span*0.01){ v.fromBufferAttribute(pos,i); v.applyMatrix4(me.matrixWorld); tt.worldToLocal(v);
              nozzle.push([v.x,v.y,v.z]); } }
          const nz=nozzle.reduce((a,b)=>[a[0]+b[0],a[1]+b[1],a[2]+b[2]],[0,0,0]).map(x=>x/nozzle.length);
          return {flange, nozzleTip:nz.map(x=>+x.toFixed(2)), tcp:d.tcpOffset, nFlange:flange.length}; }""")
        await br.close()
    F=np.array(data['flange']); c=F.mean(0); M=F-c
    u,s,vt=np.linalg.svd(M,full_matrices=False); n=vt[2]
    if n[2]<0: n=-n
    zc=np.array([0,0,1.0])
    ang=np.degrees(np.arccos(min(1,abs(n@zc))))
    print("=== 정답지(골든 TCP540 밀착) 기하 [truthTool-로컬 mm] ===")
    print(" 플랜지 정점수:",data['nFlange']," 특이값:",s.round(2))
    print(" 플랜지 중심 C_g:",c.round(2).tolist())
    print(" 플랜지 법선 N_g:",n.round(4).tolist())
    print(" 노즐끝 P_g:",data['nozzleTip']," (tcp:",[round(x,2) for x in data['tcp']],")")
    print(" ★ 마운팅축 역산: 플랜지법선 N_g 와 손목+Z 사이각 = %.2f도"%ang)
    print("   (0°≈법선정렬 / 15°≈배럴-tcpOffset정렬 / 30.24°≈배럴축정렬)")
    import numpy as _np
    print(" 노즐끝↔TCP 거리:", round(float(_np.linalg.norm(_np.array(data['nozzleTip'])-_np.array(data['tcp'][:3]))),2),"mm")
asyncio.run(main())
