import ast
import bpy
import collections
import json
import os
from mathutils import Vector
bl_info = {
"name": "Export Mesh to JSON",
"category": "Import-Export",
"blender": (2, 80, 0)
}
class MeshToJSON(bpy.types.Operator):
bl_idname = 'import_export.mesh2json'
bl_label = 'Export Mesh to JSON'
bl_options = {'REGISTER'}
bl_category = 'Import-Export'
def execute(self, context):
bpy.ops.object.mode_set(mode='OBJECT')
mesh = bpy.context.view_layer.objects.active
mesh_json = {
'name': mesh.name,
'armature_name': None,
'bounding_box': {
'min_corner': [], 'max_corner': []
},
'materials': {},
'custom_properties': {},
'attribs': {
'vertices_in_each_face': [],
'positions': {
'indices': [],
'attribute': {
'data': [],
'attribute_size': 3
}
},
'normals': {
'indices': [],
'attribute': {
'data': [],
'attribute_size': 3
}
},
'uvs': {
'indices': [],
'attribute': {
'data': [],
'attribute_size': 2
}
},
'bone_influences': {
'bones_per_vertex': {
'NonUniform': []
},
'bone_indices': [],
'bone_weights': []
}
}
}
allBoneNames = []
if mesh.parent is not None and mesh.parent.type == 'ARMATURE':
parentArmature = mesh.parent
mesh_json['armature_name'] = parentArmature.name
for poseBone in parentArmature.pose.bones:
allBoneNames.append(poseBone.name)
index = 0
for face in mesh.data.polygons:
num_vertices_in_face = len(face.vertices)
mesh_json['attribs']['vertices_in_each_face'].append(num_vertices_in_face)
for i in range(num_vertices_in_face):
mesh_json['attribs']['positions']['indices'].append(face.vertices[i])
mesh_json['attribs']['normals']['indices'].append(index)
if mesh.data.uv_layers:
mesh_json['attribs']['uvs']['indices'].append(face.loop_indices[i])
mesh_json['attribs']['normals']['attribute']['data'].append(face.normal.x)
mesh_json['attribs']['normals']['attribute']['data'].append(face.normal.y)
mesh_json['attribs']['normals']['attribute']['data'].append(face.normal.z)
index += 1
for vert in mesh.data.vertices:
mesh_json['attribs']['positions']['attribute']['data'].append(vert.co.x)
mesh_json['attribs']['positions']['attribute']['data'].append(vert.co.y)
mesh_json['attribs']['positions']['attribute']['data'].append(vert.co.z)
num_groups = len(list(vert.groups))
for group in vert.groups:
groupName = mesh.vertex_groups[group.group].name
if groupName not in allBoneNames:
continue
boneIndex = allBoneNames.index(groupName)
mesh_json['attribs']['bone_influences']['bone_indices'].append(boneIndex)
mesh_json['attribs']['bone_influences']['bone_weights'].append(group.weight)
if mesh_json['armature_name'] is not None:
mesh_json['attribs']['bone_influences']['bones_per_vertex']['NonUniform'].append(num_groups)
if mesh.data.uv_layers:
for loop in mesh.data.uv_layers.active.data:
mesh_json['attribs']['uvs']['attribute']['data'].append(loop.uv.x)
mesh_json['attribs']['uvs']['attribute']['data'].append(loop.uv.y)
if not mesh_json['armature_name']:
mesh_json['attribs']['bone_influences'] = None
if not mesh_json['attribs']['uvs']['indices']:
mesh_json['attribs']['uvs'] = None
index = 0
min_corner = [float('inf'), float('inf'), float('inf')];
max_corner = [-float('inf'), -float('inf'), -float('inf')];
bpy.ops.object.mode_set(mode = 'EDIT')
for corner in mesh.bound_box:
corner = Vector(corner)
corner = mesh.matrix_world @ corner
min_corner[0] = min(min_corner[0], corner.x)
min_corner[1] = min(min_corner[1], corner.y)
min_corner[2] = min(min_corner[2], corner.z)
max_corner[0] = max(max_corner[0], corner.x)
max_corner[1] = max(max_corner[1], corner.y)
max_corner[2] = max(max_corner[2], corner.z)
bpy.ops.object.mode_set(mode = 'OBJECT')
mesh_json['bounding_box']['min_corner'] = min_corner
mesh_json['bounding_box']['max_corner'] = max_corner
for material in mesh.data.materials:
if material.node_tree == None:
continue;
for node in material.node_tree.nodes:
baseColor = {}
roughness = {}
metallic = {}
normalMap = None
if node.type == 'BSDF_PRINCIPLED':
if len(node.inputs['Base Color'].links) > 0:
link = node.inputs['Base Color'].links[0]
if link.from_node.type == 'TEX_IMAGE':
baseColor['ImageTexture'] = link.from_node.image.name
else:
color = link.from_node.outputs['Color'].default_value
baseColor['Uniform'] = [
color[0], color[1], color[2]
]
else:
color = node.inputs['Base Color'].default_value
baseColor['Uniform'] = [
color[0], color[1], color[2]
]
if len(node.inputs['Roughness'].links) > 0:
link = node.inputs['Roughness'].links[0]
if link.from_node.type == 'TEX_IMAGE':
roughness['ImageTexture'] = [
link.from_node.image.name,
"R"
]
elif link.from_node.type == 'SEPRGB':
print(mesh.name)
roughness['ImageTexture'] = [
link.from_node.inputs['Image'].links[0].from_node.image.name,
link.from_socket.name ]
else:
roughness['Uniform'] = link.from_node.outputs['Value'].default_value
else:
roughness['Uniform'] = node.inputs['Roughness'].default_value
if len(node.inputs['Metallic'].links) > 0:
link = node.inputs['Metallic'].links[0]
if link.from_node.type == 'TEX_IMAGE':
metallic['ImageTexture'] = [
link.from_node.image.name,
"G"
]
elif link.from_node.type == 'SEPRGB':
metallic['ImageTexture'] = [
link.from_node.inputs['Image'].links[0].from_node.image.name,
link.from_socket.name ]
else:
metallic['Uniform'] = link.from_node.outputs['Value'].default_value
else:
metallic['Uniform'] = node.inputs['Metallic'].default_value
if len(node.inputs['Normal'].links) > 0:
link = node.inputs['Normal'].links[0]
if link.from_node.type == 'NORMAL_MAP':
normalMapNode = link.from_node
normalMap = normalMapNode.inputs['Color'].links[0].from_node.image.name
mesh_json['materials'][material.name] = {
'base_color': baseColor,
'roughness': roughness,
'metallic': metallic,
'normal_map': normalMap
}
for property in mesh.keys():
if property == '_RNA_UI':
continue
try:
value = mesh.get(property)
json.dumps(value)
typed_value = {}
try:
maybe_list = json.loads(value)
if isinstance(maybe_list, list):
typed_value = {"Vec": []}
for item in maybe_list:
if isinstance(item, float):
typed_value["Vec"].append({"Float": item})
elif isinstance(item, int):
typed_value["Vec"].append({"Int": item})
elif isinstance(item, str):
typed_value["Vec"].append({"String": item})
mesh_json['custom_properties'][property] = typed_value
continue
except:
if isinstance(value, float):
typed_value = {"Float": value}
elif isinstance(value, int):
typed_value = {"Int": value}
elif isinstance(value, str):
typed_value = {"String": value}
mesh_json['custom_properties'][property] = typed_value
except:
pass
output = "START_MESH_JSON " + bpy.data.filepath + " " + mesh.name
output += "\n"
output += json.dumps(mesh_json)
output += "\n"
output += "END_MESH_JSON " + bpy.data.filepath + " " + mesh.name
print(output)
return {'FINISHED'}
def register():
bpy.utils.register_class(MeshToJSON)
def unregister():
bpy.utils.unregister_class(MeshToJSON)
if __name__ == "__main__":
register()