import numpy as np import os from pygltflib import ( GLTF2, Scene, Node, Mesh, Primitive, Buffer, BufferView, Accessor, Asset, ARRAY_BUFFER ) def export_pointcloud_glb(points_xyz: np.ndarray, out_path: str): """ points_xyz: (N,3) float32 in mm coordinates (X,Y,Z) Writes a strict GLB that XCS can import as a point cloud. Crucial Fix: Injects COLOR_0 (pure white) so the F2 Ultra UV fires. """ os.makedirs(os.path.dirname(out_path), exist_ok=True) pos_xyz = points_xyz.astype(np.float32) # FORCE WHITE COLOR FOR LASER POWER col = np.ones((pos_xyz.shape[0], 3), dtype=np.float32) pos_bytes = pos_xyz.tobytes() col_bytes = col.tobytes() blob = pos_bytes + col_bytes bv_pos = BufferView(buffer=0, byteOffset=0, byteLength=len(pos_bytes), target=ARRAY_BUFFER) bv_col = BufferView(buffer=0, byteOffset=len(pos_bytes), byteLength=len(col_bytes), target=ARRAY_BUFFER) min_xyz = pos_xyz.min(axis=0).tolist() max_xyz = pos_xyz.max(axis=0).tolist() acc_pos = Accessor( bufferView=0, byteOffset=0, componentType=5126, # FLOAT count=pos_xyz.shape[0], type="VEC3", min=min_xyz, max=max_xyz ) acc_col = Accessor( bufferView=1, byteOffset=0, componentType=5126, # FLOAT count=col.shape[0], type="VEC3" ) prim = Primitive(attributes={"POSITION": 0, "COLOR_0": 1}, mode=0) mesh = Mesh(primitives=[prim]) gltf = GLTF2( asset=Asset(version="2.0"), scene=0, scenes=[Scene(nodes=[0])], nodes=[Node(mesh=0)], meshes=[mesh], buffers=[Buffer(byteLength=len(blob))], bufferViews=[bv_pos, bv_col], accessors=[acc_pos, acc_col], ) gltf.set_binary_blob(blob) gltf.save(out_path)