A command-line tool for 3D mesh analysis, repair, and visualization. Supports
.obj and .glb file formats.
- Mesh Remeshing: Fix and incrementally remesh your 3D models with multiple algorithms
- 3D Viewer: Interactive viewer for inspecting meshes
- Mesh Analysis: Get detailed statistics about your mesh
- Manifold Checking: Verify if your mesh is watertight
- Automatic Repair: Fix holes and issues in damaged meshes
cargo install mshmashFull features:
cargo install mshmash --features remote,renderdocInteractively view a mesh file in a 3D viewer:
msh view <INPUT>
msh view model.obj
msh view scene.glb --mesh name # For GLB files with multiple meshesOptions:
-m, --mesh <MESH>: Mesh name (required if GLB contains multiple meshes)
Remesh a file with automatic fixing and incremental remeshing:
msh remesh <INPUT> --out <OUTPUT>
msh remesh model.obj --out fixed.obj
msh remesh scene.glb --out output.obj --mesh meshnameOptions:
-o, --out <OUT>: Output mesh file (.obj) - required-m, --mesh <MESH>: Mesh name (required if GLB contains multiple meshes)-i, --iterations <ITERATIONS>: Number of incremental remeshing iterations (default: 10)-t, --target-edge-length <TARGET_EDGE_LENGTH>: Target edge length for incremental remeshing (default: 0.01)-v, --voxel-size <VOXEL_SIZE>: Voxel size for fix step (default: 0.01)--tolerance <TOLERANCE>: Vertex merge tolerance for fix step (default: 0.0001)--no-fix: Skip the fix step (just do incremental remesh)
Remeshing Methods:
-
Incremental (default): Edge-based operations that progressively improve mesh quality
msh remesh model.obj --out output.obj incremental
-
Voxel: Converts mesh to signed distance field (SDF) then remeshes for clean topology
msh remesh model.obj --out output.obj voxel
Display detailed information about a mesh:
msh stats <INPUT>
msh stats model.obj
msh stats scene.glb --mesh meshnameOptions:
-m, --mesh <MESH>: Mesh name (required if GLB contains multiple meshes)
Verify if a mesh is manifold (watertight):
msh check <INPUT>
msh check model.objOptions:
-m, --mesh <MESH>: Mesh name (required if GLB contains multiple meshes)
Automatically fix holes and issues in a damaged mesh:
msh fix <INPUT> --out <OUTPUT>
msh fix damaged.obj --out fixed.objOptions:
-o, --out <OUT>: Output mesh file (.obj) - required-m, --mesh <MESH>: Mesh name (required if GLB contains multiple meshes)-v, --voxel-size <VOXEL_SIZE>: Voxel size for remeshing (default: 0.01)-t, --tolerance <TOLERANCE>: Merge vertices closer than this distance before fixing (default: 0.0001)--no-merge: Skip vertex merging step
Display the structure and contents of a GLB or glTF file:
msh inspect-glb <INPUT>
msh inspect-glb scene.glb
msh inspect-glb model.glb --json # Output as JSONThis command shows the scene hierarchy, including:
- Scene structure and node tree
- Transform data (position, rotation, scale)
- Mesh information (name, primitive count)
- Custom properties (extras)
- Cameras and other components
Options:
--json: Output as JSON instead of tree format
- Input:
.obj,.glb - Output:
.obj
Repair a damaged mesh:
msh fix broken.obj --out repaired.objHigh-quality remesh with custom edge length:
msh remesh model.obj --out output.obj --target-edge-length 0.005 --iterations 20Voxel-based remeshing for topology cleanup:
msh remesh model.obj --out clean.obj voxel --voxel-size 0.02Inspect mesh before and after:
msh view original.obj
msh remesh original.obj --out fixed.obj
msh view fixed.objControl the mesh viewer remotely via JSON-RPC. Perfect for automated workflows, scripting, or external tool integration.
Start the viewer with remote control enabled:
# Build with remote feature
cargo build --features remote
# Start viewer with RPC server
msh view model.obj --remoteThe viewer will start a JSON-RPC server on http://127.0.0.1:9001 and display:
✓ RPC server ready at http://127.0.0.1:9001
Available methods:
- load_model(path, mesh_name?)
- set_rotation(x, y, z)
- rotate_around_axis(axis, angle)
...
Control the running viewer from another terminal:
Load a Model
msh remote load path/to/model.obj
msh remote load scene.glb --mesh "Body"Rotate the Model
# Set absolute rotation (Euler angles in radians)
msh remote rotate 0 1.57 0
# Rotate around axis with angle notation
msh remote rotate-axis 0,1,0 90d # 90 degrees around Y axis
msh remote rotate-axis 1,0,0 1.57r # 1.57 radians around X axisControl Camera
# Set camera position
msh remote camera-pos 5.0 3.0 5.0
# Set camera target (look-at point)
msh remote camera-target 0 0 0Toggle Display Options
# Wireframe
msh remote enable-wireframe
msh remote disable-wireframe
msh remote toggle-wireframe
# Backface visualization (shows red reversed faces)
msh remote enable-backfaces
msh remote disable-backfaces
msh remote toggle-backfaces
# UI overlay
msh remote enable-ui
msh remote disable-ui
msh remote toggle-uiGet Mesh Statistics
msh remote statsOutput:
=== Mesh Statistics ===
Vertices: 1234
Edges: 3702
Faces: 2468
Manifold: Yes
Take Screenshot
msh remote screenshot output.png
msh remote screenshot captures/view.png # Creates 'captures' dir if needed
msh remote screenshot deep/nested/dirs/shot.png # Creates all parent dirsCapture Frame (RenderDoc)
msh remote capture
msh remote capture /path/to/save.rdcQuit the Viewer
msh remote quitCapture GPU frames for debugging and analysis using RenderDoc.
cargo build --features renderdoc
# or with both features
cargo build --features remote,renderdocLaunch with RenderDoc:
# Build with RenderDoc support
cargo build --features renderdoc --release
# Launch through RenderDoc
renderdoccmd ./target/release/msh view model.obj
# or
qrenderdoc # Then use GUI: File -> Launch ApplicationWhen launched through RenderDoc, F12 will capture frames (injected by RenderDoc).
Via Remote Control:
# Terminal 1: Launch with both features
cargo build --features remote,renderdoc --release
renderdoccmd ./target/release/msh view model.obj --remote
# Terminal 2: Trigger capture remotely
msh remote capture # Default RenderDoc location
msh remote capture "captures/my_mesh" # Relative to your current directory
msh remote capture "/tmp/debug/mesh_analysis" # Absolute pathThe path parameter sets RenderDoc's capture file path template:
- Relative paths are resolved from your current working directory (where you run
msh remote) - Absolute paths are used as-is
- RenderDoc appends a timestamp and
.rdcextension to the final file
Note: RenderDoc works by injecting itself into your application process. The app must be launched through RenderDoc (via renderdoccmd or the RenderDoc GUI) for frame capture to work.
The remote control system supports flexible angle notation:
- Degrees:
90d,180d,45d,-90d - Radians:
1.57r,3.14r,0.785r - No unit (interpreted as radians):
1.57,3.14
Examples:
msh remote rotate-axis 0,1,0 90d # Quarter turn
msh remote rotate-axis 1,0,0 180d # Half turn
msh remote rotate-axis 0,0,1 1.57r # ~90° in radians# Terminal 1: Start viewer with remote control
msh view model.obj --remote
# Terminal 2: Automate inspection with screenshots
msh remote rotate 0 0 0 # Reset rotation
msh remote camera-pos 10 5 10 # Position camera
msh remote enable-wireframe # Show wireframe
msh remote rotate-axis 0,1,0 45d # Rotate 45° around Y
msh remote screenshot "shots/front_view.png" # Take screenshot
msh remote rotate-axis 0,1,0 90d # Rotate another 90°
msh remote screenshot "shots/side_view.png" # Another screenshot
msh remote disable-wireframe # Toggle for comparison
msh remote screenshot "shots/side_shaded.png"
msh remote stats # Get mesh infoWith RenderDoc for GPU debugging:
# Terminal 1: Start with RenderDoc
renderdoccmd msh view model.obj --remote
# Terminal 2: Mix screenshots and RenderDoc captures
msh remote screenshot "analysis/mesh.png" # High-level view (PNG)
msh remote capture "analysis/mesh" # GPU-level capture (.rdc)Note:
- Launch with
renderdoccmdto enable frame capture. Without it,msh remote capturewill report that RenderDoc is not available. - Relative paths are resolved from your current directory, so if you're in
/home/user/projects/models/, the path"captures/front_view.png"becomes/home/user/projects/models/captures/front_view.png. - Parent directories are automatically created for screenshots, so
"output/shots/view.png"will create theoutput/shots/directory structure if it doesn't exist.
The RPC server implements JSON-RPC 2.0 over HTTP. You can also call methods directly:
curl -X POST http://127.0.0.1:9001 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"set_rotation","params":[0,1.57,0],"id":1}'Available methods:
load_model(path: String, mesh_name: Option<String>)set_rotation(x: f32, y: f32, z: f32)rotate_around_axis(axis: Vec<f32>, angle: String)set_camera_position(x: f32, y: f32, z: f32)set_camera_target(x: f32, y: f32, z: f32)enable_wireframe()/disable_wireframe()/toggle_wireframe()enable_backfaces()/disable_backfaces()/toggle_backfaces()enable_ui()/disable_ui()/toggle_ui()get_stats()→{vertices, edges, faces, is_manifold, holes}screenshot(path: String)- Save current view as PNGcapture_frame(path: Option<String>)(requiresrenderdocfeature)quit()- Exit the viewer
-
remote: Enables JSON-RPC server and remote control CLI- Dependencies: jsonrpsee, tokio, crossbeam
- Build:
cargo build --features remote
-
renderdoc: Enables RenderDoc frame capture- Dependencies: renderdoc crate
- Build:
cargo build --features renderdoc
-
Both:
cargo build --features remote,renderdoc
