Batch-process Polycam GLB room scans into web-ready 3D models (≤4 MB) with
<model-viewer> embed snippets and JSON quality reports.
# requires uv (https://docs.astral.sh/uv/)
git clone <repo> && cd 3DMesh
uv sync# Inspect a single scan before processing
uv run scan-cleanup inspect samples/5_24_2026/5_24_2026.glb
# Process all scans in a folder (default output: ./cleaned/)
uv run scan-cleanup process samples/
# Test config on a few files before a full batch
uv run scan-cleanup process samples/ --limit 5 --dry-runRead-only stats for one GLB. Exits non-zero if the file is degenerate (0 faces).
| Option | Default | Description |
|---|---|---|
--output-dir |
./cleaned/ |
Where to write cleaned GLBs and reports |
--target-size-mb |
4.0 |
Target output file size |
--min-component-ratio |
0.05 |
Drop components smaller than 5% of largest |
--fill-holes / --no-fill-holes |
on | Fill small holes in mesh |
--align-floor / --no-align-floor |
on | Detect and align floor to Y=0 |
--parallel |
min(cpu_count, 4) |
Worker processes |
--max-input-size-mb |
200 |
Files above this run serially (not in parallel pool) |
--dry-run |
off | Run pipeline, write reports, skip GLB write |
--overwrite |
off | Replace existing outputs |
--limit N |
— | Process only first N files (for config iteration) |
--alt-text-template |
"3D scan of {filename}" |
Alt text for embed HTML |
--verbose / --quiet |
— | Console log level |
Step B — Component filtering (most impactful)
Polycam scans contain dozens of small floating geometry shards from tracking
failures. This step removes all connected components whose face count is below
min-component-ratio × largest-component-face-count. Disable with caution —
on raw scans this removes 90%+ of the mesh artifact count.
Step C — Hole filling
Fills small open holes (measured by bounding-box diagonal of the boundary loop).
Only fills holes smaller than 10% of the mesh's smallest bounding-box dimension —
larger holes are missing walls/ceilings and filling them looks worse than leaving
them. Disable with --no-fill-holes.
Step D — Floor alignment
Detects the floor plane using 500-iteration RANSAC on face centroids in the bottom
third of the bounding box, then rotates the mesh so the floor aligns with Y=0.
Disable with --no-align-floor for scans without a clear floor (e.g., exterior
shots, scans with only ceiling captured).
Step E — Decimation Reduces face count to fit the target file size using pymeshlab's UV-preserving quadric edge-collapse. Iterates up to 3 times to converge. If geometry alone can't reach the target, resizes the embedded texture to 50% dimensions (logged as a warning in the report).
No detectable floor / floor alignment skipped
The scan doesn't have enough horizontal geometry in the bottom third of the
bounding box for RANSAC to find a reliable plane. Use --no-align-floor or
check that the floor was actually captured in the scan.
Texture-dominated size (texture_resized warning)
The mesh geometry is small but the embedded texture is large. The texture was
automatically downsampled. To keep original texture quality, raise
--target-size-mb or accept the larger output.
Degenerate scan (0 faces after load)
Polycam tracking failure — the scan didn't capture enough geometry to produce a
mesh. Re-scan the space. The file will be logged to failures-latest.json.
OOM on very large scans
Reduce --parallel (try --parallel 2) or increase --max-input-size-mb to
route large files through the serial queue where they run alone.
--config FILE: loadProcessConfigfrom a TOML file. Useful for saving config sets (flagship.toml,bulk.toml) for a 250-property workflow.