Dimitriuses/Roblox-Mesh-Importer
GitHub: Dimitriuses/Roblox-Mesh-Importer
读取 Roblox 未公开的 .mesh v4 二进制格式中的几何体、骨骼及蒙皮数据,并将其作为已绑定的模型导入 Blender。
Stars: 0 | Forks: 0
# Roblox Mesh Importer
读取 Roblox `.mesh` 版本 4 文件 —— 几何体、LOD、骨骼和蒙皮权重 ——
并将其作为绑定了骨骼的对象导入到 Blender 中。
[](https://github.com/Dimitriuses/Roblox-Mesh-Importer/actions/workflows/ci.yml)
[](https://www.python.org/)
[](https://www.blender.org/)
[](LICENSE)
[](ROADMAP.md)
Roblox 的 `.mesh` 是一种未公开的二进制格式,而版本 4 是其最有趣的部
分:它包含了骨骼层级和逐顶点蒙皮权重,这是任何公开的导出工具都不会
提供给你的。本项目可以读取它,并且它所使用的布局已经[记录在案](docs/MESH_FORMAT.md) —— 这是
基于 799 个真实的 mesh 测量得出的,而不是凭空猜测的。
| | | |
|---|---|---|
|  |  |  |
| 导入后,静息姿态 | 摆出姿态 —— 蒙皮跟随骨骼 | 逐顶点骨骼影响 |
*该样本是程序化生成的肢体([`tools/make_sample_mesh.py`](tools/make_sample_mesh.py));此处不重新分发任何 Roblox 资源。可以使用 `python tools/capture_screenshots.py` 重新生成这些截图。*
## 它能做什么
- 解析 `version 4.00` / `4.01` 的 mesh:顶点、法线、UV、切线、
顶点颜色、面、LOD 级别、骨骼和蒙皮。
- 正确解析蒙皮权重 —— 骨骼槽位是*子集本地*索引,必须
通过每个子集的骨骼表进行映射才能找到真实的骨骼。
- 导出为 JSON,然后将其作为 mesh + 骨骼导入到 Blender 中,每个
骨骼对应一个顶点组。
- 验证文件的内部一致性(`check`),这是你发现二进制读取器错误的
方法:错误的读取器会返回看似合理的数字,而不是报错。
- 没有第三方运行时依赖。转换器是仅使用标准库的 Python,因此
它可以在你拥有的任何解释器中运行,包括 Blender 自带的解释器。
## 快速开始
转换器无需安装:
```
git clone https://github.com/Dimitriuses/Roblox-Mesh-Importer.git
cd Roblox-Mesh-Importer
python -m roblox_mesh info samples/arm.mesh # what's in it
python -m roblox_mesh check samples/arm.mesh # is it internally consistent
python -m roblox_mesh convert samples/arm.mesh -o arm.json
```
```
version 4.01
vertices 544
faces 1024
bones 4
subsets 2
LOD levels 2 offsets=[0, 736, 1024]
weighted 544 / 544 vertices
```
然后导入到 Blender:
```
blender --background --python import_to_blender.py -- arm.json
```
去掉 `--background` 可以观察其执行过程。`python convert.py file.mesh` 仍可
作为 `convert` 子命令的简写形式。
### 获取要转换的 `.mesh`
本仓库附带了一个生成的样本,以便上述所有操作都能立即运行。要使用
你自己的 mesh,请从 Roblox Studio 导出,或者从本地 Roblox 安装
中获取一个 —— 客户端将 mesh 存储在 `content/` 及其资源缓存下。
仅转换你有权使用的 mesh;参见 [`NOTICE.md`](NOTICE.md)。
## 用法
### `convert` —— mesh 转 JSON
```
python -m roblox_mesh convert model.mesh # writes model.json
python -m roblox_mesh convert model.mesh -o out.json --compact
```
### `info` —— 摘要
```
python -m roblox_mesh info model.mesh -v # -v lists the bone hierarchy
```
### `check` —— 验证
```
python -m roblox_mesh check model.mesh
python -m roblox_mesh check model.mesh --remap # nearest-bone suggestions
```
如果文件存在结构性错误,则以非零状态退出。`--remap` 会打印
最近骨骼比较;它仅供诊断使用,绝不进行实际应用 —— 参见
[已知限制](#known-limitations)。
### `import_to_blender.py` —— JSON 转换为绑定了骨骼的 Blender 对象
```
blender --background --python import_to_blender.py -- model.json
blender --background --python import_to_blender.py -- model.json --lod 1
blender --background --python import_to_blender.py -- model.json --keep-axes --scale 10
```
| 选项 | 含义 |
|---|---|
| `--lod N` | 要导入的 LOD 级别;0 为最详细(默认) |
| `--keep-axes` | 保留 Roblox 的 Y 轴向上方向,而不是转换为 Z 轴向上 |
| `--scale S` | 等比缩放 |
| `--name NAME` | 所创建对象的名称 |
### 作为库使用
```
from roblox_mesh import read_mesh_file, check_structure
mesh = read_mesh_file("model.mesh")
print(len(mesh.vertices), len(mesh.bones), mesh.lodOffsets)
for issue in check_structure(mesh):
print(issue)
for vertex in mesh.vertices[:3]:
print(vertex.position, vertex.weights) # {"UpperArm": 0.56, "Hand": 0.44}
```
## 组合方式
```
model.mesh ──> roblox_mesh.reader ──> Mesh ──> roblox_mesh.exporter ──> model.json
│ │
└─> roblox_mesh.diagnostics │
v
import_to_blender.py (Blender)
│
v
mesh + armature + weights
```
这种分离是因为这两个部分无法共享同一个解释器:转换器
应该能在任何地方运行,而导入器只在 Blender 内部运行。JSON
是它们之间的交换格式,由 `schemaVersion` 标记版本,以便导入器
拒绝它无法理解的输入。
| 路径 | 职责 |
|---|---|
| [`roblox_mesh/reader.py`](roblox_mesh/reader.py) | 二进制解析 |
| [`roblox_mesh/writer.py`](roblox_mesh/writer.py) | 序列化器 —— 它的存在是为了能对读取器进行往返测试 |
| [`roblox_mesh/model.py`](roblox_mesh/model.py) | Dataclass 和格式的常量 |
| [`roblox_mesh/exporter.py`](roblox_mesh/exporter.py) | JSON schema |
| [`roblox_mesh/diagnostics.py`](roblox_mesh/diagnostics.py) | 结构验证 |
| [`roblox_mesh/cli.py`](roblox_mesh/cli.py) | `convert` / `info` / `check` |
| [`import_to_blender.py`](import_to_blender.py) | Blender 端 |
| [`docs/MESH_FORMAT.md`](docs/MESH_FORMAT.md) | 格式说明及其相关证据 |
## 扩展
- **另一种输出格式** —— 在 `exporter.py` 旁边编写一个消耗
`Mesh` 的模块。其他任何内容都不需要更改。
- **另一个 mesh 版本** —— 将布局添加到 `reader.py` 并扩充
`SUPPORTED_VERSIONS`。首先向 `tools/make_sample_mesh.py` 添加一个 fixture:
往返测试将告诉你布局是否正确。
- **新的格式知识** —— 在 `diagnostics.check_structure` 中为你发现的
任何不变量添加检查。该函数是项目针对错误读取布局的预警
系统。
## 测试
```
pip install pytest
pytest # 50 tests
```
最有趣的测试是往返测试:解析一个文件,将其写回,然后比较
字节。一个存在细微错误的二进制读取器不会抛出异常 —— 它会返回看起来
没问题的数字 —— 而字节相等性检查是捕获字段读取时偏移量或
宽度错误的最廉价的方法。
在默认测试套件之外还有两个层会运行:
```
# 针对本地 Roblox 安装中的真实 meshes(未进行任何分发)
ROBLOX_MESH_CORPUS="$LOCALAPPDATA/Roblox" pytest tests/test_real_corpus.py -v -s
# 通过 Blender 端到端:导入 sample 并断言 rig 完好无损
python tools/blender_smoke.py # or: blender --background --python tools/blender_smoke.py
```
CI 会在 Linux 和 Windows 上跨 Python 3.10 和 3.13 运行单元测试套件,以及
Blender 导入检查。
## 已知限制
- **仅限版本 4。** 版本 1–3 和 5+ 具有不同的布局,并且会
被以明确的提示信息拒绝,而不是被错误地解析。版本 5(包含
面部动画数据)是显而易见的下一个目标 —— 参见 [`ROADMAP.md`](ROADMAP.md)。
- **无法写回 Roblox。** `roblox_mesh.writer` 的存在是为了测试读取器。
它能生成有效的文件,但将 mesh 往返转换*回* Roblox 是未经测试的。
- **骨骼朝向被忽略。** 每个骨骼的 3×3 旋转会被解析并导出,但
Blender 导入器仅使用位置,通过其子级来确定每个骨骼的方向。
旋转对蒙皮来说只是表面属性;只有在计划重定向动画时,
它们才重要。
- **`check --remap` 是一种启发式方法,并且被故意不予应用。** 它
将每个骨骼受影响的顶点质心与骨骼原点进行比较,而骨骼的顶点自然位于其自身的原点和子骨骼之间 —— 因此在
正确解析的 R15 手臂上,它仍然会建议 `RightUpperArm → RightLowerArm`。
它能可靠地检测*严重的*破坏,而不是细微的错误。请将其输出视为
一种迹象,而不是修复方案。
- **语料库范围较窄。** 所测量的每个蒙皮文件都有 6–10 个骨骼和 3 个
子集。接近 26 个骨骼子集限制的宽骨骼绑定未经测试。
- **没有材质或纹理。** Mesh 包含 UV 和顶点颜色,它们会被导入;
纹理位于此工具不会触及的独立 Roblox 资源中。
更多细节及重现方法,请参见 [`KNOWNISSUES.md`](KNOWNISSUES.md)。
## 贡献
欢迎提交 issue 和 pull request。如果你有一个导致此工具失败的 `.mesh`,
那就是你能报告的最有用的东西 —— 请附上
`python -m roblox_mesh check yourfile.mesh` 的输出。请不要附带你不拥有
的 Roblox 资源。
开发说明和不变量在 [`CLAUDE.md`](CLAUDE.md) 中。
## 许可证
MIT —— 参见 [`LICENSE`](LICENSE)。署名、商标和资产声明在
[`NOTICE.md`](NOTICE.md) 中。“Roblox” 是 Roblox Corporation 的商标;这是一个
非官方、无附属关系的工具。
标签:3D模型导入, Blender插件, Homebrew安装, Python, Roblox, 无后门, 网格解析, 逆向工具, 骨骼绑定