Free 3D asset tool for indie game developers — merge animations, convert between 40+ 3D formats, edit materials with AI
51
stars
2,260
commits
C++
primary language
Sep 10, 2026
updated
Automate your 3D asset pipeline — scan, validate, convert, fix, and merge 3D assets with GUI + CLI + CI/CD support.
Open 3D files from your OS — after install, double-click .fbx, .glb, .gltf, .obj, .dae, .stl, .ply, .mesh, and PS1 .rsd/.tmd in Finder, Explorer, or your Linux file manager (or use Open With). QtMeshEditor loads the model in the running window; a second launch focuses the existing instance instead of spawning another process. Portable Windows ZIP users can run bin/scripts/register-windows-file-associations.ps1 to register handlers without admin rights.
Available on the GitHub Actions Marketplace.
Versioning
fernandotonon/QtMeshEditor@v1 (same pattern as the Marketplace example). The composite action defaults to image-tag: latest, so the Docker CLI tracks the newest published ghcr.io/fernandotonon/qtmesh image.project(QtMeshEditor VERSION …) in CMakeLists.txt (currently 3.38.0). After bumping the version in CMake, run ./scripts/sync-doc-versions-from-cmake.sh to refresh the pinned refs in README.md and the docs site fallback; CI enforces the match with ./scripts/sync-doc-versions-from-cmake.sh --check.Pinned workflow template (action + ghcr.io image aligned):
name: QtMesh Scan
on:
push:
branches: [ "master" ]
jobs:
scan-assets-qtmesh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run QtMesh scan
uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: scan
image-tag: "3.38.0"
env:
QTMESH_CLOUD_TOKEN: ${{ secrets.QTMESH_CLOUD_TOKEN }}
Floating ref (auto-updates when the v1 tag moves on release):
- name: Run QtMesh scan
uses: fernandotonon/QtMeshEditor@v1
with:
command: scan
QTMESH_CLOUD_TOKEN is forwarded into the container and used by scan upload. GitHub Actions metadata (GITHUB_*) is also forwarded so uploads include meta fields (branch/commit/run and context used by QtMesh Cloud).
To fail CI when upload fails, add qtmesh-strict-upload: true (or pass --strict-upload in options).
Release tags are listed on the releases page (also referenced in QtMesh Cloud onboarding).
# Validate a specific mesh
- uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: validate
input-file: ./models/character.fbx
image-tag: "3.38.0"
# Convert FBX → glTF
- uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: convert
input-file: ./models/character.fbx
output-file: ./output/character.gltf2
image-tag: "3.38.0"
# Resample Mixamo animations (200+ keyframes → 30)
- uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: anim
input-file: ./animations/dance.fbx
output-file: ./output/dance_optimized.fbx
options: --resample 30
image-tag: "3.38.0"
# Get mesh info as JSON
- uses: fernandotonon/QtMeshEditor@3.38.0
id: info
with:
command: info
input-file: ./models/character.fbx
options: --json
image-tag: "3.38.0"
# Docker (alternative — :latest tracks newest image; pin :3.4.0 to match semver action ref)
# The image is multi-arch (linux/amd64 + linux/arm64), so it runs natively on
# both Intel and Apple Silicon Macs — no --platform flag or QEMU needed.
docker run --rm -v $(pwd):/workspace ghcr.io/fernandotonon/qtmesh:latest scan ./assets --fail-on error
QTMESH_CLOUD_TOKEN.Badge markdown (replace <owner-slug> and <project-slug>):
[](https://qtmesh.dev)
[](https://qtmesh.dev)
[](https://qtmesh.dev)
qtmesh)Same commands locally, in Docker, or in CI:
# Scan a directory for asset issues
qtmesh scan ./assets --fail-on warning
qtmesh scan ./assets --json --report report.json
# Inspect a mesh
qtmesh info model.fbx --json
# Validate geometry
qtmesh validate model.fbx
# Convert between formats
qtmesh convert model.fbx -o model.gltf2
# Convert + Draco-compress the glTF (smaller .glb; needs a Draco-enabled build)
qtmesh convert model.fbx -o model.glb --compress draco
# Fix / optimize
qtmesh fix model.fbx -o fixed.fbx --all
# Animation tools
qtmesh anim model.fbx --list
qtmesh anim model.fbx --rename "Take 001" "Idle" -o renamed.fbx
qtmesh anim model.fbx --resample 30 -o optimized.fbx
qtmesh anim base.fbx --merge walk.fbx run.fbx -o merged.fbx
# Export animation pose as static mesh (3D printing)
qtmesh pose model.fbx --animation "Dance" --count 4 -o pose_%02d.stl
# LOD generation
qtmesh lod model.fbx --auto
# Bake vertex colors → texture (with UV-seam dilation)
qtmesh bake-vertex-colors model.fbx -o color_map.png --resolution 1024 --dilation 4
qtmesh paint-bake model.fbx --target unreal -o out/ # repack the mesh's PBR textures into an engine layout + sidecar JSON
qtmesh paint-bake model.fbx --target unity -o out/ --resolution 2048 --prefix hero # Unity: metallic+smoothness RGBA, DirectX (+Y down) normal
qtmesh paint-bake --list-targets # generic | unity | unreal | godot | gltf
qtmesh paint --list-stamps # brush stamps (also --list-presets, --list-palettes; all take --json)
qtmesh paint model.fbx --layer list --json # paint layers (layers are a live-session concept; bake to write pixels)
qtmesh paint model.fbx --bake --engine unity -o out/ # alias of paint-bake --target
qtmesh paint model.fbx --apply-stencil decal.png --camera "0,1,4,0,1,0" -o out.mesh # project an image through an explicit camera
# Auto UV unwrap (xatlas — same library Blender/Godot use)
qtmesh uv model.fbx --unwrap -o unwrapped.glb # overwrite UV0
qtmesh uv model.fbx --unwrap --channel 1 -o lightmap.glb # keep UV0, write UV1 (lightmap workflow)
qtmesh uv model.fbx --info --json # report UV channels + coverage
# Quad retopology (triangle-pairing — no new deps)
qtmesh retopo model.fbx -o quads.glb # pair every viable triangle into quads
qtmesh retopo model.fbx --target-faces 5000 -o lo.glb # stop early once near target face count
qtmesh retopo model.fbx --max-angle 15 -o conservative.glb # tighter coplanarity gate
# Compute skin weights (inverse-distance heuristic; mesh must have a skeleton)
qtmesh skin model.fbx -o skinned.glb # default 4 influences, falloff 4
qtmesh skin model.fbx --max-influences 8 --falloff 6 -o skinned.glb
qtmesh skin model.fbx --skip-unweighted --merge -o filled.glb # fill missing weights only
# Auto-generate the 52 ARKit blendshapes on a humanoid FACE mesh (for face capture)
qtmesh facerig head.glb -o rigged.glb # fit ARKit template + transfer 52 shapes
qtmesh facerig head.fbx -o rigged.glb --max-shapes 20 --json # cap shapes / machine-readable report
ZBrush-style polypaint and full PBR channel texture painting — Base Color, Normal, Roughness, Metallic, AO and Emissive — with a 2D preview panel, multiple brush tools (paint, erase, fill, color picker, smudge), layers, a channel picker, channel-aware presets (scratches into roughness, emissive sparks, edge wear, dirt build-up, sticker), and a one-click bake that turns vertex colors into a UV-space texture. Every bake merges with the slot's existing texture rather than overwriting it — scalar channels collapse into their lane of the packed ORM map, and Normal-channel sculpts Sobel-convert to a tangent-space normal and blend onto the existing normal map, all rendering live under IBL. Symmetry mirrors every stroke in real time across the local or world X/Y/Z planes (combine axes for 4- or 8-way mirroring; a topology-aware mode keeps mirrors correct on meshes with an asymmetric UV unwrap), and a line stabilizer (moving-average or trailing) smooths mouse jitter for clean strokes. Projection painting projects an image onto the mesh through the camera — brush through a stencil, lock the projection to a camera pose, or project a photo straight onto the visible surface (with backface culling, depth-limit and per-texel occlusion so it only lands where the camera can see); and the decal tool drops a placeable image rectangle (drag to move / rotate / scale, Enter to commit) onto the surface. Each projection lands in its own layer.
Quick start (texture paint, GUI):
Vertex paint (GUI):
Same flow, but vertex paint operates in Edit Mode instead. Press Tab on a selected mesh to enter Edit Mode, then tick Vertex Color Preview in the Edit Mode Tools section to see vertex colors live.
Bake Vertex Colors → Texture rasterizes the active mesh's vertex colors into a UV-space PNG via barycentric interpolation, then dilates the result outward by N pixels to mask UV-seam bleed at MIP-map time.
Headless (CLI):
qtmesh bake-vertex-colors model.fbx -o color_map.png --resolution 1024 --dilation 4
qtmesh paint-bake model.fbx --target unreal -o out/ # repack the mesh's PBR textures into an engine layout + sidecar JSON
qtmesh paint-bake model.fbx --target unity -o out/ --resolution 2048 --prefix hero # Unity: metallic+smoothness RGBA, DirectX (+Y down) normal
qtmesh paint-bake --list-targets # generic | unity | unreal | godot | gltf
qtmesh paint --list-stamps # brush stamps (also --list-presets, --list-palettes; all take --json)
qtmesh paint model.fbx --layer list --json # paint layers (layers are a live-session concept; bake to write pixels)
qtmesh paint model.fbx --bake --engine unity -o out/ # alias of paint-bake --target
qtmesh paint model.fbx --apply-stencil decal.png --camera "0,1,4,0,1,0" -o out.mesh # project an image through an explicit camera
Export. Vertex colors are preserved on export to formats that support them (glTF preferred — verified round-trip).
Notes / limitations.
albedo or diffuse_map on the
active material — imported PBR materials alias the diffuse texture under
both, and rebinding only one would leave the other pointing at the
original.Download animations from Mixamo, drop them into QtMeshEditor, and merge into a single file — export as glTF, FBX, Collada, OBJ, or Ogre Mesh.
| Split View | Skeleton Animation Controls |
|---|---|
![]() | ![]() |
| MCP tools (AI Agent Control) | Bone Weight Visualization |
qtmesh cloud …) and MCP (cloud_* tools) parityqtmesh segment --split-parts / --explode-parts + MCP (split_mesh_by_segments, explode_mesh_parts, join_mesh_parts) — see docs/PART_OPS.mdqtmesh mocap on the CLI (-DENABLE_MOCAP builds)set_hdr_environment, set_tonemap, …) and QtMesh Cloud (cloud_*)| Platform | Command |
|---|---|
| Windows | winget install FernandoTonon.QtMeshEditor |
| macOS | brew tap fernandotonon/qtmesheditor && brew trust fernandotonon/qtmesheditor && brew install qtmesheditor |
| Linux | sudo snap install qtmesheditor |
| Docker | docker run --rm ghcr.io/fernandotonon/qtmesh --help |
📥 Download latest release · 📖 Website & docs
| Format | Extension | Import | Export | Skeleton/Animation |
|---|---|---|---|---|
| FBX Binary | .fbx | ✅ | ✅ | ✅ |
| glTF 2.0 | .gltf2 / .glb2 | ✅ | ✅ | ✅ |
| Collada | .dae | ✅ | ✅ | ✅ |
| OBJ | .obj | ✅ | ✅ | — |
| STL | .stl | ✅ | ✅ | — |
| Ogre Mesh | .mesh / .mesh.xml | ✅ | ✅ | ✅ |
| PlayStation TMD | .tmd | ✅ | ✅ | — |
| PlayStation RSD | .rsd | ✅ | ✅ | — |
| 3DS | .3ds | ✅ | ✅ | — |
| Stanford PLY | .ply | ✅ | ✅ | — |
| Psy-Q PLY (PlayStation) | .ply | ✅ | ✅ | — |
.ply is dispatched by content: Stanford (ply header) vs Psy-Q (@PLY… header). See documentation/playstation-rsd-ply.md.
Import supports all formats provided by Assimp (40+).
📖 Documentation · 🛠 Build from source · 🐛 Report issues · 💬 Contribute
2,254 commits
6 commits
C++
82.9%
QML
8.0%
Python
3.4%
GLSL
1.2%
CMake
1.0%
Free 3D asset tool for indie game developers — merge animations, convert between 40+ 3D formats, edit materials with AI
51
stars
2,260
commits
C++
primary language
Sep 10, 2026
updated
Automate your 3D asset pipeline — scan, validate, convert, fix, and merge 3D assets with GUI + CLI + CI/CD support.
Open 3D files from your OS — after install, double-click .fbx, .glb, .gltf, .obj, .dae, .stl, .ply, .mesh, and PS1 .rsd/.tmd in Finder, Explorer, or your Linux file manager (or use Open With). QtMeshEditor loads the model in the running window; a second launch focuses the existing instance instead of spawning another process. Portable Windows ZIP users can run bin/scripts/register-windows-file-associations.ps1 to register handlers without admin rights.
Available on the GitHub Actions Marketplace.
Versioning
fernandotonon/QtMeshEditor@v1 (same pattern as the Marketplace example). The composite action defaults to image-tag: latest, so the Docker CLI tracks the newest published ghcr.io/fernandotonon/qtmesh image.project(QtMeshEditor VERSION …) in CMakeLists.txt (currently 3.38.0). After bumping the version in CMake, run ./scripts/sync-doc-versions-from-cmake.sh to refresh the pinned refs in README.md and the docs site fallback; CI enforces the match with ./scripts/sync-doc-versions-from-cmake.sh --check.Pinned workflow template (action + ghcr.io image aligned):
name: QtMesh Scan
on:
push:
branches: [ "master" ]
jobs:
scan-assets-qtmesh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run QtMesh scan
uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: scan
image-tag: "3.38.0"
env:
QTMESH_CLOUD_TOKEN: ${{ secrets.QTMESH_CLOUD_TOKEN }}
Floating ref (auto-updates when the v1 tag moves on release):
- name: Run QtMesh scan
uses: fernandotonon/QtMeshEditor@v1
with:
command: scan
QTMESH_CLOUD_TOKEN is forwarded into the container and used by scan upload. GitHub Actions metadata (GITHUB_*) is also forwarded so uploads include meta fields (branch/commit/run and context used by QtMesh Cloud).
To fail CI when upload fails, add qtmesh-strict-upload: true (or pass --strict-upload in options).
Release tags are listed on the releases page (also referenced in QtMesh Cloud onboarding).
# Validate a specific mesh
- uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: validate
input-file: ./models/character.fbx
image-tag: "3.38.0"
# Convert FBX → glTF
- uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: convert
input-file: ./models/character.fbx
output-file: ./output/character.gltf2
image-tag: "3.38.0"
# Resample Mixamo animations (200+ keyframes → 30)
- uses: fernandotonon/QtMeshEditor@3.38.0
with:
command: anim
input-file: ./animations/dance.fbx
output-file: ./output/dance_optimized.fbx
options: --resample 30
image-tag: "3.38.0"
# Get mesh info as JSON
- uses: fernandotonon/QtMeshEditor@3.38.0
id: info
with:
command: info
input-file: ./models/character.fbx
options: --json
image-tag: "3.38.0"
# Docker (alternative — :latest tracks newest image; pin :3.4.0 to match semver action ref)
# The image is multi-arch (linux/amd64 + linux/arm64), so it runs natively on
# both Intel and Apple Silicon Macs — no --platform flag or QEMU needed.
docker run --rm -v $(pwd):/workspace ghcr.io/fernandotonon/qtmesh:latest scan ./assets --fail-on error
QTMESH_CLOUD_TOKEN.Badge markdown (replace <owner-slug> and <project-slug>):
[](https://qtmesh.dev)
[](https://qtmesh.dev)
[](https://qtmesh.dev)
qtmesh)Same commands locally, in Docker, or in CI:
# Scan a directory for asset issues
qtmesh scan ./assets --fail-on warning
qtmesh scan ./assets --json --report report.json
# Inspect a mesh
qtmesh info model.fbx --json
# Validate geometry
qtmesh validate model.fbx
# Convert between formats
qtmesh convert model.fbx -o model.gltf2
# Convert + Draco-compress the glTF (smaller .glb; needs a Draco-enabled build)
qtmesh convert model.fbx -o model.glb --compress draco
# Fix / optimize
qtmesh fix model.fbx -o fixed.fbx --all
# Animation tools
qtmesh anim model.fbx --list
qtmesh anim model.fbx --rename "Take 001" "Idle" -o renamed.fbx
qtmesh anim model.fbx --resample 30 -o optimized.fbx
qtmesh anim base.fbx --merge walk.fbx run.fbx -o merged.fbx
# Export animation pose as static mesh (3D printing)
qtmesh pose model.fbx --animation "Dance" --count 4 -o pose_%02d.stl
# LOD generation
qtmesh lod model.fbx --auto
# Bake vertex colors → texture (with UV-seam dilation)
qtmesh bake-vertex-colors model.fbx -o color_map.png --resolution 1024 --dilation 4
qtmesh paint-bake model.fbx --target unreal -o out/ # repack the mesh's PBR textures into an engine layout + sidecar JSON
qtmesh paint-bake model.fbx --target unity -o out/ --resolution 2048 --prefix hero # Unity: metallic+smoothness RGBA, DirectX (+Y down) normal
qtmesh paint-bake --list-targets # generic | unity | unreal | godot | gltf
qtmesh paint --list-stamps # brush stamps (also --list-presets, --list-palettes; all take --json)
qtmesh paint model.fbx --layer list --json # paint layers (layers are a live-session concept; bake to write pixels)
qtmesh paint model.fbx --bake --engine unity -o out/ # alias of paint-bake --target
qtmesh paint model.fbx --apply-stencil decal.png --camera "0,1,4,0,1,0" -o out.mesh # project an image through an explicit camera
# Auto UV unwrap (xatlas — same library Blender/Godot use)
qtmesh uv model.fbx --unwrap -o unwrapped.glb # overwrite UV0
qtmesh uv model.fbx --unwrap --channel 1 -o lightmap.glb # keep UV0, write UV1 (lightmap workflow)
qtmesh uv model.fbx --info --json # report UV channels + coverage
# Quad retopology (triangle-pairing — no new deps)
qtmesh retopo model.fbx -o quads.glb # pair every viable triangle into quads
qtmesh retopo model.fbx --target-faces 5000 -o lo.glb # stop early once near target face count
qtmesh retopo model.fbx --max-angle 15 -o conservative.glb # tighter coplanarity gate
# Compute skin weights (inverse-distance heuristic; mesh must have a skeleton)
qtmesh skin model.fbx -o skinned.glb # default 4 influences, falloff 4
qtmesh skin model.fbx --max-influences 8 --falloff 6 -o skinned.glb
qtmesh skin model.fbx --skip-unweighted --merge -o filled.glb # fill missing weights only
# Auto-generate the 52 ARKit blendshapes on a humanoid FACE mesh (for face capture)
qtmesh facerig head.glb -o rigged.glb # fit ARKit template + transfer 52 shapes
qtmesh facerig head.fbx -o rigged.glb --max-shapes 20 --json # cap shapes / machine-readable report
ZBrush-style polypaint and full PBR channel texture painting — Base Color, Normal, Roughness, Metallic, AO and Emissive — with a 2D preview panel, multiple brush tools (paint, erase, fill, color picker, smudge), layers, a channel picker, channel-aware presets (scratches into roughness, emissive sparks, edge wear, dirt build-up, sticker), and a one-click bake that turns vertex colors into a UV-space texture. Every bake merges with the slot's existing texture rather than overwriting it — scalar channels collapse into their lane of the packed ORM map, and Normal-channel sculpts Sobel-convert to a tangent-space normal and blend onto the existing normal map, all rendering live under IBL. Symmetry mirrors every stroke in real time across the local or world X/Y/Z planes (combine axes for 4- or 8-way mirroring; a topology-aware mode keeps mirrors correct on meshes with an asymmetric UV unwrap), and a line stabilizer (moving-average or trailing) smooths mouse jitter for clean strokes. Projection painting projects an image onto the mesh through the camera — brush through a stencil, lock the projection to a camera pose, or project a photo straight onto the visible surface (with backface culling, depth-limit and per-texel occlusion so it only lands where the camera can see); and the decal tool drops a placeable image rectangle (drag to move / rotate / scale, Enter to commit) onto the surface. Each projection lands in its own layer.
Quick start (texture paint, GUI):
Vertex paint (GUI):
Same flow, but vertex paint operates in Edit Mode instead. Press Tab on a selected mesh to enter Edit Mode, then tick Vertex Color Preview in the Edit Mode Tools section to see vertex colors live.
Bake Vertex Colors → Texture rasterizes the active mesh's vertex colors into a UV-space PNG via barycentric interpolation, then dilates the result outward by N pixels to mask UV-seam bleed at MIP-map time.
Headless (CLI):
qtmesh bake-vertex-colors model.fbx -o color_map.png --resolution 1024 --dilation 4
qtmesh paint-bake model.fbx --target unreal -o out/ # repack the mesh's PBR textures into an engine layout + sidecar JSON
qtmesh paint-bake model.fbx --target unity -o out/ --resolution 2048 --prefix hero # Unity: metallic+smoothness RGBA, DirectX (+Y down) normal
qtmesh paint-bake --list-targets # generic | unity | unreal | godot | gltf
qtmesh paint --list-stamps # brush stamps (also --list-presets, --list-palettes; all take --json)
qtmesh paint model.fbx --layer list --json # paint layers (layers are a live-session concept; bake to write pixels)
qtmesh paint model.fbx --bake --engine unity -o out/ # alias of paint-bake --target
qtmesh paint model.fbx --apply-stencil decal.png --camera "0,1,4,0,1,0" -o out.mesh # project an image through an explicit camera
Export. Vertex colors are preserved on export to formats that support them (glTF preferred — verified round-trip).
Notes / limitations.
albedo or diffuse_map on the
active material — imported PBR materials alias the diffuse texture under
both, and rebinding only one would leave the other pointing at the
original.Download animations from Mixamo, drop them into QtMeshEditor, and merge into a single file — export as glTF, FBX, Collada, OBJ, or Ogre Mesh.
| Split View | Skeleton Animation Controls |
|---|---|
![]() | ![]() |
| MCP tools (AI Agent Control) | Bone Weight Visualization |
qtmesh cloud …) and MCP (cloud_* tools) parityqtmesh segment --split-parts / --explode-parts + MCP (split_mesh_by_segments, explode_mesh_parts, join_mesh_parts) — see docs/PART_OPS.mdqtmesh mocap on the CLI (-DENABLE_MOCAP builds)set_hdr_environment, set_tonemap, …) and QtMesh Cloud (cloud_*)| Platform | Command |
|---|---|
| Windows | winget install FernandoTonon.QtMeshEditor |
| macOS | brew tap fernandotonon/qtmesheditor && brew trust fernandotonon/qtmesheditor && brew install qtmesheditor |
| Linux | sudo snap install qtmesheditor |
| Docker | docker run --rm ghcr.io/fernandotonon/qtmesh --help |
📥 Download latest release · 📖 Website & docs
| Format | Extension | Import | Export | Skeleton/Animation |
|---|---|---|---|---|
| FBX Binary | .fbx | ✅ | ✅ | ✅ |
| glTF 2.0 | .gltf2 / .glb2 | ✅ | ✅ | ✅ |
| Collada | .dae | ✅ | ✅ | ✅ |
| OBJ | .obj | ✅ | ✅ | — |
| STL | .stl | ✅ | ✅ | — |
| Ogre Mesh | .mesh / .mesh.xml | ✅ | ✅ | ✅ |
| PlayStation TMD | .tmd | ✅ | ✅ | — |
| PlayStation RSD | .rsd | ✅ | ✅ | — |
| 3DS | .3ds | ✅ | ✅ | — |
| Stanford PLY | .ply | ✅ | ✅ | — |
| Psy-Q PLY (PlayStation) | .ply | ✅ | ✅ | — |
.ply is dispatched by content: Stanford (ply header) vs Psy-Q (@PLY… header). See documentation/playstation-rsd-ply.md.
Import supports all formats provided by Assimp (40+).
📖 Documentation · 🛠 Build from source · 🐛 Report issues · 💬 Contribute
2,254 commits
6 commits
C++
82.9%
QML
8.0%
Python
3.4%
GLSL
1.2%
CMake
1.0%