Convert .fbx, .unitypackage, .vrm, .pmx, and .uemodel (UEFormat) into each other, entirely with the Python standard library.
No Blender / Autodesk FBX SDK / Unity 3D required, and no plugins such as mmd_tools / UniVRM.
It reads and writes the binary formats directly — just drag a file into the window to convert.
Download the latest version from releases.
| Direction | Description |
|---|---|
| FBX / unitypackage → PMX | PMX 2.0 for MMD; quads are auto-fan-triangulated |
| VRM (0.x / 1.0) → PMX | Auto-detects humanoid bones; fills missing ones with placeholder bones |
| PMX → VRM (0.x / 1.0) | Auto-writes VRM meta, humanoid bone mapping, and morph targets |
| uemodel (UEFormat) → PMX | Reads the public UEFormat .uemodel (v1–v10); can also write an ASCII FBX in the same run |
| PMX → uemodel (UEFormat) | Writes UEFormat .uemodel (v9 by default, v10 optional) for the UE / FModel toolchain |
| PMX → validation + preview only | Read-only structural validation; no file output |
- Zero external dependencies: Pure Python standard library (tkinter + ctypes).
Pillowis an optional accelerator — its absence only affects texture-decode speed and some previews, not the main pipeline. - Drag & drop, ready to use: Uses the native Windows
WM_DROPFILES; you can drop anywhere in the window, with no third-party library like tkinterdnd2. - Selected-file list: after dropping, the files (name / format / size) and the total size are listed right under the drop area. You can append more, remove single rows, or double-click one row to preview just that file — so you can see at a glance what this batch will convert, and nothing runs until you click "Start conversion".
- Real-time back-view preview: Drag in a model and see the 3D back view immediately; drag to rotate / scroll to zoom, and overlay bone points to check alignment.
- Multilingual UI: Switch between Simplified Chinese / Traditional Chinese / English / Japanese in the top-right; the choice is saved to config.
- HiDPI friendly: Auto-scales to system DPI; the top-right "UI zoom" lets you set a manual factor.
- Tabbed options: The options area is split into five tabs — General / FBX / VRM / UE / Output — for easy extension.
- Automatic winding-order detection: Triangle winding is auto-decided by voting between geometric-face normals and vertex normals, avoiding large holes / outline lines smearing into black blobs.
- Texture handling: PNG/JPEG pass through directly; BMP/TGA are converted to PNG on the fly; embedded in GLB or exported to the PMX directory.
- Windows (drag & drop and the GUI depend on native Windows APIs)
- Python 3.10+ (3.12 / 3.13 / 3.14 recommended; must ship with tkinter)
- Optional:
Pillow(accelerates texture decoding and preview)
Model-to-PMX/
├── main.py # ★ GUI entry point (full implementation)
├── main.spec # ★ PyInstaller build config
├── config.json # UI settings (language / zoom / options, etc.)
│
├── formats/ # Format read/write / unpack
│ ├── fbx_reader.py # Binary FBX 7.x parser library
│ ├── fbx_probe.py # Probe FBX structure (mesh/bones/skinning list)
│ ├── pmxio.py # Full PMX 2.0 read/write (incl. morph/IK/additional/rigidbody/joint)
│ ├── vrmio.py # GLB/glTF container read/write + PNG encode + BMP/TGA decode
│ ├── uemodelio.py # UEFormat .uemodel read/write (v1–v10)
│ ├── fbxout.py # ASCII FBX 7.4 writer (the "also export FBX" option)
│ └── unitypackage_unpack.py # Unpack .unitypackage (gzip tar)
│
├── convert/ # Conversion engine
│ ├── fbx2pmx.py # FBX → PMX
│ ├── vrm2pmx.py # VRM → PMX
│ ├── pmx2vrm.py # PMX → VRM
│ ├── uemodel2pmx.py # uemodel (UEFormat) → PMX (+ optional ASCII FBX)
│ ├── pmx2uemodel.py # PMX → uemodel (UEFormat)
│ └── pmx_check.py # PMX validation + software-rendered preview image
│
├── gfx/
│ └── preview.py # Real-time 3D back-view preview widget
│
Engine modules are grouped by responsibility into the
formats/convert/gfx/sub-directories. At runtimemain.pyinserts these three directories intosys.path, so modules still use bare-nameimports (e.g.import fbx2pmx).
- Run
python main.py - Drag a
.fbx/.unitypackage/.vrm/.pmx/.uemodelfile anywhere into the window, or click the drop area to choose a file. Dropping only loads the file and shows the preview — nothing is converted automatically. Check the task direction and options, then click "Start conversion" to run. - A selected-file list (file name / format / size) appears under the drop area, so you can see at a glance what will be converted:
- dropping again appends to the list; duplicates are skipped by absolute path;
- double-click a row to preview just that file;
- select rows and click "Remove selected" (or press Delete), or "Clear list" to start over;
- while the list has files, the drop area shrinks into a thin strip so the list gets the vertical space.
- The "Task" dropdown can manually specify the conversion direction; by default it is auto-detected from the file extension. Switching the task re-filters the already-loaded files (the list updates too).
- Conversion log sits at the bottom-left; the model preview is the full-height right column.
UI highlights:
- Split view: drag the divider in the middle to resize, just like Blender's areas; the position is saved in
config.json - Full-height preview column: live back view, with Front / Left / Back / Top / Reset / Spin / Bones / Wire in the toolbar below it
- Render backend badge (bottom-right of the preview): shows whether
Pillowor pure Python is in use, plus the last frame time in ms - "Language" (top-right): Simplified Chinese / Traditional Chinese / English / Japanese (the preview toolbar follows too)
- "UI zoom" (top-right): Auto-follows system DPI, or set a manual factor
- Option tabs: General / FBX / VRM / UE / Output, five tabs
- UE tab: when the
.uemodel→ PMX task is selected, tick "also export an FBX file" to get an ASCII FBX next to the PMX; the same tab sets target height (cm) and alpha handling - Textures in the exported FBX: the written FBX flips the UV V axis (PMX/MMD put the UV origin top-left, FBX / Blender / Maya bottom-left) and references textures as a relative path
textures/…. Keep the FBX next to the PMX together with itstexturesfolder — otherwise the shape looks right but the patterns are shifted all over - Enlarged checkboxes: The checkboxes and click areas are easier to hit
The live preview is a pure-Python software rasterizer (no OpenGL), so it works in three layers:
- Decimated mesh while dragging — interactively renders ~12k triangles, so a 90k-triangle model costs about 40 ms per frame;
- Sliced refinement after you stop — the refined frame advances 1500 triangles per slice, capped at 24 ms per time slice, so the picture sharpens piece by piece without freezing the UI;
- Adaptive pixel budget — resolution scales with the measured frame time, and Pillow (when installed) upscales the low-res result to canvas size in C.
Always wrap paths containing spaces or non-ASCII characters in double quotes; engine scripts live in the
convert/formats/sub-directories.
# Open the GUI
python main.py
# FBX → PMX
python convert/fbx2pmx.py "model.fbx" -o "model.pmx"
# Unpack a unitypackage (--list lists contents without unpacking)
python formats/unitypackage_unpack.py "pack.unitypackage" --list
python formats/unitypackage_unpack.py "pack.unitypackage"
# Probe FBX structure
python formats/fbx_probe.py "model.fbx"
# PMX → VRM 1.0 (default) / 0.x
python convert/pmx2vrm.py "model.pmx" -o "model.vrm"
python convert/pmx2vrm.py "model.pmx" --spec 0x --title "Name" --author "Author"
# VRM → PMX (textures auto-exported to the PMX directory)
python convert/vrm2pmx.py "model.vrm" -o "model.pmx"
# uemodel (UEFormat) → PMX; add --fbx to also write an ASCII FBX next to the PMX
python convert/uemodel2pmx.py "model.uemodel" -o "model.pmx"
python convert/uemodel2pmx.py "model.uemodel" -o "model.pmx" --fbx
# PMX → uemodel (UEFormat v9 by default; --version 10 for the newer layout)
python convert/pmx2uemodel.py "model.pmx" -o "model.uemodel"
python convert/pmx2uemodel.py "model.pmx" -o "model.uemodel" --version 10
# PMX validation + generate preview image
python convert/pmx_check.py "model.pmx"
python convert/pmx_check.py "model.pmx" --bones # overlay bone positions
# Change a PMX's text encoding to UTF-16LE (fix for files from older builds)
python formats/pmxio.py "model.pmx" # writes model_utf16.pmx
python formats/pmxio.py "model.pmx" --in-place # overwrite (back up first)fbx2pmx.py:
| Option | Description |
|---|---|
--scale mmd |
Default; auto-scale to MMD standard height (≈ 20 units) |
--scale raw |
Keep FBX's original size (meters) |
--scale 12.5 |
Manually specify a scale factor |
--no-flip-z |
Skip right-handed → left-handed conversion (on by default) |
--info |
Only print structure info, no conversion |
pmx2vrm.py: --spec 1.0|0x, --scale auto|factor, --rotate auto|none|y180,
--flip-winding, --force-double-sided, --max-morphs N, --title / --author.
vrm2pmx.py: --scale, --rotate, --flip-winding, --edge (enable outline),
--force-double-sided, --name.
For more detailed options and format mappings, see FBX转PMX_使用说明.md and VRM互转_使用说明.md.
pip install pyinstaller
pip install pillow # optional; makes the bundled build also get the texture-speedupAlways use the spec; do not run
pyinstaller main.pydirectly.
pyinstaller main.specOutput: dist/ModelConvert.exe/ModelConvert.exe (one-folder mode).
Engine modules are placed in the formats/ convert/ gfx/ sub-directories, and main.py uses bare-name
imports like import fbx2pmx. The sub-directories are only added at runtime via sys.path.insert.
PyInstaller only does static analysis and cannot see the runtime path injection — if those sub-directories
are absent from pathex, the build stage silently drops them as "missing modules". The build succeeds, but at
runtime it throws:
ModuleNotFoundError: No module named 'fbx2pmx'
main.spec already configures this correctly:
Analysis(
['main.py'],
pathex=['.', 'formats', 'convert', 'gfx'], # let static analysis find the sub-dir modules
hiddenimports=['fbx2pmx', 'pmx_check', 'pmx2vrm', 'vrm2pmx', 'preview',
'unitypackage_unpack', 'fbx_reader', 'pmxio', 'vrmio'],
...
)If you don't want to use the spec, the equivalent command is:
pyinstaller --paths formats --paths convert --paths gfx -w main.py| Option | Description |
|---|---|
-F |
Build into a single executable |
-D |
Build into a folder containing multiple files (default) |
-w |
Hide the console window (GUI app) |
-i icon.ico |
Specify the executable icon |
-n name |
Specify the generated executable name |
--add-data "src:dst" |
Add resource files |
--hidden-import module |
Manually add a hidden dependency |
- Paths with spaces / non-ASCII: Always wrap them in double quotes on the command line.
- Settings file
config.json: Stores language, UI zoom, task direction, and each option; it is recreated with defaults if deleted. - Shortcuts & interaction: The preview area supports drag-to-rotate and scroll-to-zoom; clicking the drop area opens a file picker.
- Black lines on model edges after conversion: The converter now disables the MMD outline by default (vertex edge=0, material edge_size=0, edge_color alpha=0). If you still see black edges, open the model in MMD / PMXEditor, make sure the outline is turned off for all materials, and check "Double-sided drawing".
- Still has holes: First look at the
绕序自动判定(auto winding-order detection) line in the log. If it's wrong, add--flip-winding; if thin geometry (hair / skirt) relies on double-sided rendering, add--force-double-sided, or check "Force double-sided materials" in the UI.
- FBX structure: both binary FBX 7.x and ASCII FBX are read (the older "ASCII is not supported" note was wrong).
- Texture formats: DDS / KTX2 / WebP are skipped (material degrades to a solid color).
- Physics: PMX rigidbody/joint ↔ VRM SpringBone are not converted between each other.
- Material effects: Spherical maps (.sph/.spa) and toon maps are not preserved on the VRM side.
- Bone names: FBX conversion keeps English bone names (
Hips,Spine…), so they won't match MMD's ready-made motions (.vmd); you must batch-rename them to the Japanese standard names in PMXEditor. - Morphs: When the source model has no BlendShape / morph, the PMX morphs are also 0 and must be created by hand.
MMD only accepts PMX files whose text encoding is UTF-16LE. The original message is
MMDではエンコード方式がUTF16のPMXファイルしか読み込めません
(English string in the same binary: MMD can't read UTF8 encorded PMX. Please exchange it to UTF16.).
The Chinese localizations render it as "MMD 不能载入编码为 UTF16 的 PMX 文件", which is a
mistranslation that reverses the meaning — seeing it means the file was saved as UTF-8.
This tool always writes UTF-16LE. Files exported by older builds can be fixed with
python formats/pmxio.py "old.pmx"; the validation log also reports
文本编码是 UTF-8,MMD 无法载入(需要 UTF-16LE).
- Drag & drop relies on the native Windows
WM_DROPFILESand is only available on Windows; on other systems use the "Browse" button to pick a file. - If the log says "system drag-drop interface not enabled", the current environment does not support native drag & drop; just use the button instead.
- You must use
pyinstaller main.spec(or the equivalent command with--paths), otherwise the engine modules in the sub-directories are left out and runtime throwsNo module named 'fbx2pmx'. - After editing source you must rebuild; double-clicking the old .exe will not pick up changes.
- The Python used for packaging must include tkinter (the official Windows installer includes it by default).
The VRM meta carries license information. This tool writes the most conservative default: "Attribution required, redistribution prohibited, modification prohibited".
Confirm you have the right to use the model before converting — the tool will not perform a copyright check for you.
