"""Re-tessellate original STEP; retain mesh topology for inspection and GNN data."""
from ac28_audit import *
from OCP.STEPControl import STEPControl_Reader
from OCP.IFSelect import IFSelect_RetDone
from OCP.BRepMesh import BRepMesh_IncrementalMesh
from OCP.StlAPI import StlAPI_Writer
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_SOLID,TopAbs_SHELL,TopAbs_FACE
import xlrd

def main():
    reader=STEPControl_Reader();assert reader.ReadFile(str(next(DATA.glob('*.stp'))))==IFSelect_RetDone
    reader.TransferRoots();shape=reader.OneShape();counts={}
    for name,kind in [('solids',TopAbs_SOLID),('shells',TopAbs_SHELL),('faces',TopAbs_FACE)]:
        ex=TopExp_Explorer(shape,kind);n=0
        while ex.More():n+=1;ex.Next()
        counts[name]=n
    BRepMesh_IncrementalMesh(shape,.15,False,.15,True)
    w=StlAPI_Writer();w.ASCIIMode=False;assert w.Write(shape,str(OUT/'design_fine.stl'))
    mesh=o3d.io.read_triangle_mesh(str(OUT/'design_fine.stl'))
    mesh.remove_duplicated_vertices();mesh.remove_degenerate_triangles();mesh.remove_duplicated_triangles();mesh.compute_vertex_normals()
    # Triangle adjacency, not Euclidean kNN: avoids edges across holes or thin walls.
    small=mesh.simplify_quadric_decimation(24000);small.remove_unreferenced_vertices();small.compute_vertex_normals();small.compute_triangle_normals()
    p=np.asarray(small.vertices);faces=np.asarray(small.triangles);norm=np.asarray(small.vertex_normals)
    edges=np.unique(np.sort(np.concatenate([faces[:,[0,1]],faces[:,[1,2]],faces[:,[2,0]]]),axis=1),axis=0)
    edge_length=np.linalg.norm(p[edges[:,0]]-p[edges[:,1]],axis=1)
    angle=np.arccos(np.clip(np.sum(norm[edges[:,0]]*norm[edges[:,1]],axis=1),-1,1))
    turn=np.zeros(len(p));count=np.zeros(len(p))
    for i in (0,1):np.add.at(turn,edges[:,i],angle);np.add.at(count,edges[:,i],1)
    turn=np.rad2deg(turn/np.maximum(count,1))
    directed=np.concatenate([edges,edges[:,::-1]],axis=0)
    np.savez_compressed(OUT/'cad_graph.npz',pos_mm=p.astype('float32'),normal=norm.astype('float32'),face=faces.T.astype('int64'),edge_index=directed.T.astype('int64'),edge_length_mm=np.tile(edge_length,2).astype('float32'),normal_turn_deg=turn.astype('float32'))
    (OUT/'design_graph.json').write_text(json.dumps({'positions':p.flatten().round(4).tolist(),'normals':norm.flatten().round(5).tolist(),'triangles':faces.flatten().tolist(),'normal_turn_deg':turn.round(2).tolist()},separators=(',',':')))
    counts.update({'mesh_vertices':len(mesh.vertices),'mesh_triangles':len(mesh.triangles),'graph_nodes':len(p),'graph_directed_edges':len(directed),'linear_deflection_mm':.15,'angular_deflection_rad':.15,'watertight':mesh.is_watertight(),'bbox_mm':[p.min(0).tolist(),p.max(0).tolist()]})
    (OUT/'design_metadata.json').write_text(json.dumps(counts,indent=2));print(counts,flush=True)
    process={}
    for path in DATA.glob('2*/*.Xls'):
        name='18h01' if '18' in path.name else '16h';sheet=xlrd.open_workbook(path).sheet_by_index(0)
        rows=[{'row':r+1,'machine':sheet.cell_value(r,0),'channel':sheet.cell_value(r,1),'unit':sheet.cell_value(r,2),'type':sheet.cell_value(r,3),'value':None if sheet.cell_value(r,4)=='' else sheet.cell_value(r,4),'timestamp':sheet.cell_value(r,5).strip()} for r in range(1,sheet.nrows)]
        process[name]={'source':str(path.relative_to(ROOT)),'sheet':sheet.name,'rows':rows}
    (OUT/'process.json').write_text(json.dumps(process,indent=2,ensure_ascii=False))

if __name__=='__main__':main()
