Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@SamusAranX
Last active March 25, 2018 03:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SamusAranX/6342684293dd9e5bab08a29b1422f1f1 to your computer and use it in GitHub Desktop.
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.
#!/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