A free, open-source desktop studio for Gaussian Splatting. Take a folder of photos or a video all the way to a trained splat, through three steps: Assets → Pointcloud → Splat.
Splattr is a clean UI layer over two established open-source tools: COLMAP for structure-from-motion / sparse reconstruction, and OpenSplat for training the Gaussian splat. Think of it as a lightweight, open alternative to tools like PostShot.
Early scaffold. The app shell, project format, and 3D viewport are real and working. The Assets step is fully real: drag-and-drop or native-picker import, splitting videos into frames via a bundled ffmpeg binary with live progress, a live thumbnail gallery (grid or list view) in place of the 3D viewport while on that tab, and per-asset delete with an optional per-project "don't ask again" preference. COLMAP and OpenSplat are not yet wired to real subprocesses: the Pointcloud/Splat steps currently simulate a run (progress bar, live viewport population with placeholder data) so the full workflow and UI can be exercised end to end. Wiring real colmap / opensplat binary execution is the next milestone; see Roadmap.
- Electron + electron-vite: desktop shell, fast HMR for main/preload/renderer
- React 18 + TypeScript
- Tailwind CSS v4: theme tokens defined in
src/renderer/src/styles/main.css - Three.js + React Three Fiber + drei: the live 3D viewport
- Zustand: renderer state (
project-store,scene-store) - chokidar: watches a project's
assets/,colmap/, andsplat/folders in the main process and pushes change events to the renderer, so the asset gallery and 3D viewport both update live as files change on disk - ffmpeg-static: bundled
ffmpegbinary used to split imported videos into frames (no system ffmpeg install required) - lucide-react: icon set
- IBM Plex Sans / IBM Plex Mono (self-hosted via
@fontsource): typography
pnpm install
pnpm devThis launches the Electron app with hot-reload on the renderer and auto-restart on main/preload changes.
Gotcha: if your shell has ELECTRON_RUN_AS_NODE=1 set (some Node/Electron tooling sets this globally), Electron launches as plain Node and crashes with Cannot read properties of undefined (reading 'isPackaged'). If you hit that, run:
env -u ELECTRON_RUN_AS_NODE pnpm devOther scripts:
pnpm typecheck # tsc --noEmit for both the node (main/preload) and web (renderer) projects
pnpm build # production build (out/)
pnpm build:mac # build + electron-builder mac target
pnpm build:win # build + electron-builder win target
pnpm build:linux # build + electron-builder linux targetA project is a folder (e.g. My Scan.splattr) containing:
My Scan.splattr/
├── project.json # manifest: name, timestamps, per-step state (see src/shared/types.ts)
├── assets/ # imported source images / video
├── colmap/ # COLMAP working directory (database, sparse/dense reconstruction)
├── splat/ # OpenSplat output (trained .ply and checkpoints)
└── logs/
project.json tracks per-step status (empty | ready | running | complete | error), tool-specific settings (camera model, matcher type, target iterations, etc.), and per-project preferences (e.g. whether to skip the "delete asset" confirmation). See the ProjectManifest type in src/shared/types.ts for the exact shape.
src/
├── main/ # Electron main process
│ ├── index.ts # window creation, IPC handler registration
│ ├── project.ts # .splattr create/open/read/save + recent-projects list
│ ├── assets.ts # file picker, import, list/delete assets, video frame extraction
│ ├── ffmpeg.ts # spawns the bundled ffmpeg-static binary, parses progress
│ └── watcher.ts # chokidar wrapper, pushes fs change events over IPC
├── preload/
│ ├── index.ts # contextBridge: exposes `window.splattr` to the renderer
│ └── index.d.ts # ambient Window typing for the renderer
├── shared/ # imported by main, preload, AND renderer: the single
│ │ # source of truth for the IPC contract
│ ├── types.ts # ProjectManifest, step states, .splattr layout constants
│ ├── ipc-channels.ts # IPC channel name constants
│ └── preload-api.ts # the `SplattrAPI` interface implemented by preload/index.ts
└── renderer/src/
├── App.tsx # ProjectGate vs AppShell, based on whether a project is open
├── components/
│ ├── ui/ # generic, reusable primitives: Button, Modal, Tooltip,
│ │ # IconButton, Select, Checkbox, ProgressBar, StatRow, Logo
│ ├── dialogs/ # Modal-based popups: NewProjectDialog, ImportAssetsDialog,
│ │ # ConfirmDialog
│ ├── gate/ # pre-project screen (ProjectGate)
│ ├── shell/ # app chrome: AppShell, Header, StepNav, StatusBar
│ ├── panels/ # left Scene panel + right per-step Parameters panels
│ │ # (AssetsPanel, PointcloudPanel, SplatPanel)
│ └── viewport/ # center content: the 3D viewport (Viewport + layers) for
│ # Pointcloud/Splat, the asset gallery (AssetGallery, AssetTile)
│ # for Assets — AppShell swaps between them by active step
├── store/ # zustand: project-store (manifest/steps), scene-store (3D data)
├── hooks/ # useProjectWatcher, wires the main-process file watcher in
└── lib/ # cn (tailwind-merge), format, path helpers
components/ui/: dumb, reusable, no app state. If you need a new button/badge/input variant, it goes here.components/dialogs/: anything shown via<Modal>. Build new popups on top ofModalrather than rolling a new overlay.components/panels/andcomponents/shell/: app-specific composition, free to depend on the zustand stores.- Prefer a
Tooltipover an inline caption/help paragraph for anything that's only relevant on hover (disabled-button reasons, icon-only button labels, secondary hints). Reserve visible body text for things the user needs to read without hovering.
- Spawn real
colmap(feature extraction, matching, mapper) as a child process, parse progress and the resulting sparse reconstruction (cameras.bin/images.bin/points3D.bin) into the viewport instead of the current placeholder point cloud - Spawn real
opensplat, parse training progress and load the actual.plyoutput into the splat viewer - Video to frame extraction (bundled
ffmpeg-static, configurable fps, live progress) - Poster-frame thumbnails for video assets that weren't split into frames
- Persist Scene panel layer visibility per project
- Export/share flow for a finished splat
MIT