#!/usr/bin/env python3 """Minimal standalone viser viewer for a single PLY point cloud. Usage: python3 viser_ply.py path/to/cloud.ply --port 8200 """ from __future__ import annotations import argparse import sys import time import numpy as np def main(): ap = argparse.ArgumentParser() ap.add_argument("ply", help="PLY file to visualize") ap.add_argument("--port", type=int, default=8200) ap.add_argument("--point-size", type=float, default=0.01) ap.add_argument("--downsample", type=float, default=0.0, help="Voxel downsample size (0 = no downsample)") ap.add_argument("--max-points", type=int, default=2_000_000, help="Random subsample to this many points if cloud is larger") args = ap.parse_args() try: import open3d as o3d import viser except ImportError as e: sys.exit(f"missing dep: {e}") pcd = o3d.io.read_point_cloud(args.ply) if args.downsample > 0: pcd = pcd.voxel_down_sample(args.downsample) pts = np.asarray(pcd.points, dtype=np.float32) cols = np.asarray(pcd.colors, dtype=np.float32) if pcd.has_colors() else np.ones_like(pts) * 0.7 if len(pts) > args.max_points: idx = np.random.choice(len(pts), args.max_points, replace=False) pts, cols = pts[idx], cols[idx] print(f"loaded {len(pts):,} pts from {args.ply}", flush=True) server = viser.ViserServer(host="0.0.0.0", port=args.port) server.scene.add_point_cloud( "/cloud", points=pts, colors=(cols * 255).astype(np.uint8), point_size=args.point_size ) print(f"viser listening on *:{args.port}") while True: time.sleep(60) if __name__ == "__main__": main()