import trimesh, pyvista as pv, pyacvd, numpy as np, json, hashlib, os, time, pymeshfix
src='cfd_ready/Al_Rihla_2022_CFD_0p220m.stl'
tm=trimesh.load(src,process=True)
# Split only oversized source triangles before clustering so large facets cannot survive unchanged.
v,f=trimesh.remesh.subdivide_to_size(tm.vertices,tm.faces,max_edge=0.0025,max_iter=6,return_index=False)
pre=trimesh.Trimesh(v,f,process=True)
print('precluster',len(pre.vertices),len(pre.faces),'maxedge',pre.edges_unique_length.max())
pvpre=pv.wrap(pre).triangulate().clean(tolerance=1e-10,absolute=True)
clus=pyacvd.Clustering(pvpre);t=time.time();clus.cluster(120000);rem=clus.create_mesh().triangulate().clean(tolerance=1e-10,absolute=True);print('cluster sec',time.time()-t)
out='Al_Rihla_2022_remeshed_uniform.stl';rem.save(out,binary=True)
r=trimesh.load(out,process=True)
if not (r.is_watertight and r.euler_number==2 and r.is_winding_consistent):
 mf=pymeshfix.MeshFix(r.vertices,r.faces);mf.repair(joincomp=True,remove_smallest_components=True);r=trimesh.Trimesh(mf.points,mf.faces,process=True)
if r.volume<0:r.invert()
r.export(out)
origpv=pv.read(src).triangulate(); rpv=pv.wrap(r)
d1=np.abs(np.asarray(rpv.compute_implicit_distance(origpv).point_data['implicit_distance']))
opv=pv.wrap(trimesh.load(src,process=True));d2=np.abs(np.asarray(opv.compute_implicit_distance(rpv).point_data['implicit_distance']))
def qs(x):return {str(q):float(np.quantile(x,q)) for q in [0,.5,.9,.95,.99,.999,1]}
# triangle quality using standard 4sqrt(3)A/sum(edge^2)
tri=r.triangles; lens=np.linalg.norm(tri[:,[1,2,0]]-tri[:,[0,1,2]],axis=2);qual=4*np.sqrt(3)*r.area_faces/np.sum(lens*lens,axis=1)
rep={'vertices':len(r.vertices),'triangles':len(r.faces),'watertight':r.is_watertight,'winding_consistent':r.is_winding_consistent,'euler_number':r.euler_number,'body_count':r.body_count,'volume_m3':float(r.volume),'area_m2':float(r.area),'extents_m':r.extents.tolist(),'edge_length_m_quantiles':qs(r.edges_unique_length),'triangle_quality_quantiles':qs(qual),'remesh_to_source_distance_m':qs(d1),'source_to_remesh_distance_m':qs(d2),'sha256':hashlib.sha256(open(out,'rb').read()).hexdigest(),'bytes':os.path.getsize(out)}
open('remesh_uniform_validation.json','w').write(json.dumps(rep,indent=2)+'\n');print(json.dumps(rep,indent=2))
