"""param_v1(골든 알고리즘) 검증 — 실제 정점 측정(자기충족 금지) + 캡처."""
import asyncio, json
import numpy as np
from playwright.async_api import async_playwright
URL='http://localhost:8090/DOZIKWORKS_OS_v5_locked.dc.html'
OUT=r'E:\도진팩토리\3D스캔및티칭시스템\shots\ceo_feedback'
CEO_HOME={"S":-0.7,"L":-59.4,"U":-44.4,"R":-1.0,"B":-92.9,"T":-1.1}
async def main():
    async with async_playwright() as p:
        br=await p.chromium.launch(channel='msedge',headless=True)
        pg=await br.new_page(viewport={'width':1400,'height':950})
        errs=[]; pg.on('pageerror',lambda e: errs.append(str(e)))
        await pg.goto(URL); await pg.wait_for_function("window.__dzw && window.__dzw._torchMesh",timeout=15000)
        data=await pg.evaluate("""(HOME)=>{ const d=window.__dzw,T=d._T;
          if(d.setJoints) d.setJoints(HOME);
          const me=d._torchMesh; me.updateMatrixWorld(true); const tt=d._truthTool; tt.updateMatrixWorld(true);
          const g=me.geometry; const pos=g.attributes.position; const Nn=pos.count;
          let minz=1e9,maxz=-1e9; for(let i=0;i<Nn;i++){const z=pos.getZ(i); if(z<minz)minz=z; if(z>maxz)maxz=z;}
          const span=maxz-minz; const fl=[]; const v=new T.Vector3(); let wt=null;
          for(let i=0;i<Nn;i++){ const z=pos.getZ(i);
            if(z<minz+span*0.02){ v.fromBufferAttribute(pos,i); v.applyMatrix4(me.matrixWorld); tt.worldToLocal(v);
              if(fl.length<2000) fl.push([+v.x.toFixed(2),+v.y.toFixed(2),+v.z.toFixed(2)]); }
          }
          // 와이어끝 = max z 정점 (truthTool-로컬)
          let bi=0,bz=-1e9; for(let i=0;i<Nn;i++){ if(pos.getZ(i)>bz){bz=pos.getZ(i);bi=i;} }
          v.fromBufferAttribute(pos,bi); v.applyMatrix4(me.matrixWorld); tt.worldToLocal(v);
          const wire=[+v.x.toFixed(2),+v.y.toFixed(2),+v.z.toFixed(2)];
          const cam=new T.Vector3(0,0,0).applyMatrix4(me.matrixWorld);
          if(d.orbit){ d.orbit.tgt.copy(cam); d.orbit.r=0.5; d.orbit.theta=-0.6; d.orbit.phi=1.45; d.updateCam&&d.updateCam(); }
          // 토치끝 가시 구
          const tcp=new T.Vector3(d.tcpOffset[0],d.tcpOffset[1],d.tcpOffset[2]); let sph=[];
          tt.traverse(o=>{if(o.isMesh&&o.geometry&&o.geometry.type==='SphereGeometry'&&o.visible){if(o.position.distanceTo(tcp)<40)sph.push(o.name||'s');}});
          return {flange:fl, wireTip:wire, tcp:d.tcpOffset, stick:d.WIRE_STICKOUT_MM||15, spheres:sph, name:me.name}; }""", CEO_HOME)
        await pg.wait_for_timeout(500)
        await pg.screenshot(path=OUT+r'\fix_attach_from_golden_1.png')
        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
    tilt=np.degrees(np.arccos(min(1,abs(n@np.array([0,0,1.0])))))
    # 플랜지 간극 = 손목면(z=0)까지 정점 z의 |평균|과 스프레드
    zmean=float(F[:,2].mean()); zspread=float(F[:,2].max()-F[:,2].min())
    tcp=np.array(data['tcp'][:3]); wire=np.array(data['wireTip'])
    print("=== param_v1 (골든 알고리즘) 실제정점 검증 [truthTool-로컬 mm] ===")
    print(" 메시:",data['name'],"| 토치끝 가시구:",data['spheres'])
    print(" 플랜지면 중심:",c.round(2).tolist()," 법선:",n.round(4).tolist())
    print(" 플랜지 간극(정점 z 평균|손목면0까지|):", round(abs(zmean),2),"mm  / z스프레드:",round(zspread,2),"mm")
    print(" 플랜지 면 기울기(vs 손목+Z):", round(float(tilt),2),"도")
    print(" 와이어끝:",wire.round(2).tolist())
    print(" 와이어끝↔TCP:", round(float(np.linalg.norm(wire-tcp)),2),"mm  | 와이어끝↔용접점(TCP+15Z):", round(float(np.linalg.norm(wire-(tcp+np.array([0,0,data['stick']])))),2),"mm")
    print(" 캡처: fix_attach_from_golden_1.png")
asyncio.run(main())
