Last active
March 25, 2018 03:25
-
-
Save SamusAranX/6342684293dd9e5bab08a29b1422f1f1 to your computer and use it in GitHub Desktop.
This script converts .babylonbinarymeshdata files (such as the ones used in Microsoft's Xbox Design Lab) to usable .obj files.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
import os | |
import io | |
import sys | |
import glob | |
import json | |
import struct | |
import argparse | |
### | |
# | |
# USAGE: | |
# | |
# ./unpack_binarymeshdata.py -bb "whatever.binary.babylon" "output folder here" | |
# or on Windows: | |
# py .\unpack_binarymeshdata.py -bb "whatever.binary.babylon" "output folder here" | |
# | |
### | |
from os.path import join, dirname, basename, splitext | |
try: | |
from transforms3d.euler import quat2euler | |
TRANSFORMS3D = True | |
except ImportError: | |
TRANSFORMS3D = False | |
FOOTER_LENGTH = 20 | |
def process_binary_json(f): | |
with open(f, "r") as binary: | |
bin_json = json.load(binary) | |
meshes = bin_json["meshes"] | |
for mesh in meshes: | |
mesh_name = mesh["id"] | |
mesh_parent = mesh["parentId"] if "parentId" in mesh else None | |
mesh_pos = mesh["position"] | |
mesh_pos_clean = [f"{c:.24f}".rstrip('0').rstrip('.') for c in mesh_pos] | |
mesh_rot = mesh["rotationQuaternion"] | |
# shift one to the right | |
# BabylonJS apparently saves rotation as [X, Y, Z, W] | |
# but Blender wants [W, X, Y, Z] | |
mesh_rot.insert(0, mesh_rot.pop()) | |
mesh_scale = mesh["scaling"] | |
# If all scaling factors are the same, why bother having three of them | |
if len(set(mesh_scale)) <= 1: | |
mesh_scale = mesh_scale[0] | |
if "_binaryInfo" in mesh and "delayLoadingFile" in mesh: | |
mesh_file = join(dirname(f), mesh["delayLoadingFile"]) | |
yield mesh_file, mesh["_binaryInfo"] | |
print(f"{mesh_name} {{") | |
print(f"\tParent: {mesh_parent}") | |
print(f"\tPosition (XYZ): " + ", ".join(mesh_pos_clean)) | |
print(f"\tRotation (WXYZ): {mesh_rot}") | |
if TRANSFORMS3D: | |
# Display Euler angles as well if transforms3d is installed | |
print(f"\tRotation (XYZ): {quat2euler(mesh_rot)}") | |
print(f"\tScaling (XYZ): {mesh_scale}") | |
print(f"}}") | |
def unpack_binarymeshdata(f, binaryInfo): | |
with open(f, "rb") as babylon_mesh: | |
print(basename(f)) | |
grouped_info = [] | |
for mesh_info in [ | |
("positionsAttrDesc", "Vertex"), | |
("normalsAttrDesc", "Normal"), | |
("uvsAttrDesc", "UV"), | |
("indicesAttrDesc", "Index")]: | |
m_format = binaryInfo[mesh_info[0]]["dataType"] | |
m_offset = binaryInfo[mesh_info[0]]["offset"] | |
m_stride = binaryInfo[mesh_info[0]]["stride"] | |
m_pcount = binaryInfo[mesh_info[0]]["count"] | |
# floats and ulongs used here are 4 bytes | |
m_size = 4 | |
m_type = "f" if bool(m_format) else "L" | |
struct_fmt = f"<{m_pcount}{m_type}" | |
# go to the offset of current block | |
babylon_mesh.seek(m_offset) | |
mesh_data = babylon_mesh.read(m_pcount * m_size) | |
mesh_data_unpacked = struct.unpack_from(struct_fmt, mesh_data) | |
mesh_data_grouped = list(zip(*[iter(mesh_data_unpacked)] * m_stride)) | |
# special case: indices are one-based and must be returned in groups of 3 | |
if mesh_info[1] == "Index": | |
mesh_data_unpacked = [i+1 for i in mesh_data_unpacked] | |
mesh_data_grouped = list(zip(*[iter(mesh_data_unpacked)] * 3)) | |
grouped_info.append(mesh_data_grouped) | |
print(f"{mesh_info[1]} data unpacked. ({len(mesh_data_unpacked) // m_stride})") | |
return grouped_info | |
def write_obj(vertices, normals, texcoords, indices, dest): | |
fmt_decimals = 50 | |
vertex_fmt = f"v {{0:.{fmt_decimals}f}} {{1:.{fmt_decimals}f}} {{2:.{fmt_decimals}f}}\n" | |
texcrd_fmt = f"vt {{0:.{fmt_decimals}f}} {{1:.{fmt_decimals}f}}\n" | |
normal_fmt = f"vn {{0:.{fmt_decimals}f}} {{1:.{fmt_decimals}f}} {{2:.{fmt_decimals}f}}\n" | |
indics_fmt = "f {0}/{0} {1}/{1} {2}/{2}\n" | |
indics_normal_fmt = "f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n" | |
with open(dest, "w") as obj_file: | |
print(f"Writing to {dest}") | |
# obj_file.write(f"# {basename(dest)} created with unpack_binarymeshdata.py\n") | |
print("Writing vertices…") | |
for v in vertices: | |
obj_file.write(vertex_fmt.format(*v)) | |
# print("Writing normals…") | |
# obj_file.write("\n") | |
# for vn in normals: | |
# obj_file.write(normal_fmt.format(*vn)) | |
print("Writing UVs…") | |
obj_file.write("\n") | |
for vt in texcoords: | |
obj_file.write(texcrd_fmt.format(*vt)) | |
print("Writing indices…") | |
obj_file.write("\n") | |
for i in indices: | |
obj_file.write(indics_fmt.format(*i)) | |
obj_file.write(f"f {i[0]} {i[1]} {i[2]}\n") | |
print("Done.") | |
# sys.exit(0) | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description="Convert BabylonJS files to .obj models") | |
parser.add_argument("-bb", "--binary", type=str, required=True, help=".binary.babylon file") | |
parser.add_argument("out", type=str, help="Output directory") | |
args = parser.parse_args() | |
os.makedirs(args.out, exist_ok=True) | |
for mesh_file, mesh_binary_info in process_binary_json(args.binary): | |
mesh_data = unpack_binarymeshdata(mesh_file, mesh_binary_info) | |
mesh_out_file = join(args.out, splitext(basename(mesh_file))[0] + ".obj") | |
write_obj(*mesh_data, mesh_out_file) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment