├── README.md └── io_mesh_json ├── __init__.py ├── __init__.py~ ├── __pycache__ ├── __init__.cpython-33.pyc └── export_json.cpython-33.pyc ├── export_json.py └── import_json.py /README.md: -------------------------------------------------------------------------------- 1 | Blender-JSON-Exporter 2 | ===================== 3 | 4 | Blender JSON Exporter (.json) 5 | 6 | 7 | ### File Format 8 | 9 | ``` 10 | { 11 | "vertices": [ 1,1,1, -1,-1,-1, 1.... ], 12 | 13 | "normals": [ 1,1,1, -1,-1,-1, 1.... ], 14 | 15 | "colors": [ 1,1,1 0,0,0, 0.... ], 16 | 17 | "uvs":[ 1,1, 0,0 0,1, 1.... ], 18 | 19 | "faces": [ 0,1,2,3,4... ], 20 | 21 | "bones": [ 22 | { 23 | // parent -1 no parent, else index of parent in bones array 24 | "parent": -1, 25 | "name": "Bone-1, 26 | "bindPose": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], 27 | 28 | // boolean whether has vertices attached or not 29 | "skinned": %(skinned)s, 30 | 31 | "position": [0,0,0], 32 | "rotation": [0,0,0,1], 33 | "scale": [1,1,1], 34 | 35 | "inheritRotation": %(inheritRotation)s, 36 | "inheritScale": %(inheritScale)s 37 | } 38 | ], 39 | 40 | // bones weights of vertex at that index 41 | "boneWeights": [ 0,0, 1,1, 0,1 ], 42 | 43 | // locations index in bones array 44 | "boneIndices": [ 0,0, 1,2, 3,4 ], 45 | 46 | "animations": { 47 | // action name in blender 48 | "idle": [ 49 | //frame 1, each frame contains each bones position rotation and scale for that frame 50 | [ 51 | // pos, rotation, scale 52 | 53 | /* bone at index 0 */[ 0,0,0, 0,0,0,1, 1,1,1 ], 54 | /* bone at index 1 */[ 0,0,0, 0,0,0,1, 1,1,1 ] 55 | ], 56 | //frame 2 57 | [ 58 | [ 0,0,0, 0,0,0,1, 1,1,1 ], 59 | [ 0,0,0, 0,0,0,1, 1,1,1 ] 60 | ] 61 | ], 62 | "run": [ 63 | [ 64 | [ 0,0,0, 0,0,0,1, 1,1,1 ], 65 | [ 0,0,0, 0,0,0,1, 1,1,1 ] 66 | ], 67 | [ 68 | [ 0,0,0, 0,0,0,1, 1,1,1 ], 69 | [ 0,0,0, 0,0,0,1, 1,1,1 ] 70 | ] 71 | ] 72 | } 73 | } 74 | ``` 75 | 76 | 77 | ###Exporting ( after adding and enabling the addon ) 78 | 79 | select the mesh to export go to File > Export > JSON (.json) 80 | 81 | ###Faces 82 | 83 | exports vertices, normals, uvs and colors based on Object's polygons so faces are 0,1,2,3,4... 84 | 85 | ###Animation & Armature 86 | 87 | only exports first armature in scene, all actions are exported -------------------------------------------------------------------------------- /io_mesh_json/__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "JSON Mesh Export", 3 | "author": "Nathan Faucett", 4 | "blender": (2,6,8), 5 | "version": (0,0,1), 6 | "location": "File > Import-Export", 7 | "description": "Import-Export JSON data format (only export avalable now)", 8 | "category": "Import-Export", 9 | "wiki_url": "https://github.com/lonewolfgames", 10 | "tracker_url": "https://github.com/lonewolfgames", 11 | } 12 | 13 | import bpy 14 | from bpy.props import * 15 | from bpy_extras.io_utils import ExportHelper, ImportHelper 16 | 17 | 18 | # ################################################################ 19 | # Import JSON 20 | # ################################################################ 21 | 22 | class ImportJSON( bpy.types.Operator, ImportHelper ): 23 | bl_idname = "import.json" 24 | bl_label = "Import JSON" 25 | 26 | filename_ext = ".json" 27 | filter_glob = StringProperty( default="*.json", options={"HIDDEN"}) 28 | 29 | def execute( self, context ): 30 | import io_mesh_json.import_json 31 | return io_mesh_json.import_json.load( self, context, **self.properties ) 32 | 33 | 34 | # ################################################################ 35 | # Export JSON 36 | # ################################################################ 37 | 38 | class ExportJSON( bpy.types.Operator, ExportHelper ): 39 | bl_idname = "export.json" 40 | bl_label = "Export JSON" 41 | 42 | filename_ext = ".json" 43 | 44 | def invoke( self, context, event ): 45 | return ExportHelper.invoke( self, context, event ) 46 | 47 | @classmethod 48 | def poll( cls, context ): 49 | return context.active_object != None 50 | 51 | def execute( self, context ): 52 | print("Selected: " + context.active_object.name ) 53 | 54 | if not self.properties.filepath: 55 | raise Exception("filename not set") 56 | 57 | filepath = self.filepath 58 | 59 | import io_mesh_json.export_json 60 | return io_mesh_json.export_json.save( self, context, **self.properties ) 61 | 62 | 63 | # ################################################################ 64 | # Common 65 | # ################################################################ 66 | 67 | def menu_func_export( self, context ): 68 | default_path = bpy.data.filepath.replace(".blend", ".json") 69 | self.layout.operator( ExportJSON.bl_idname, text="JSON (.json)").filepath = default_path 70 | 71 | def menu_func_import( self, context ): 72 | self.layout.operator( ImportJSON.bl_idname, text="JSON (.json)") 73 | 74 | def register(): 75 | bpy.utils.register_module(__name__) 76 | bpy.types.INFO_MT_file_export.append(menu_func_export) 77 | bpy.types.INFO_MT_file_import.append(menu_func_import) 78 | 79 | def unregister(): 80 | bpy.utils.unregister_module(__name__) 81 | bpy.types.INFO_MT_file_export.remove(menu_func_export) 82 | bpy.types.INFO_MT_file_import.remove(menu_func_import) 83 | 84 | if __name__ == "__main__": 85 | register() -------------------------------------------------------------------------------- /io_mesh_json/__init__.py~: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Ares.js", 3 | "author": "Nathan Faucett", 4 | "blender": (2,6,0), 5 | "version": (0,0,1), 6 | "location": "File > Import-Export", 7 | "description": "Export Ares.js data format", 8 | "category": "Import-Export", 9 | "wiki_url": "https://github.com/lonewolfgames", 10 | "tracker_url": "https://github.com/lonewolfgames", 11 | } 12 | 13 | import bpy 14 | import time 15 | from bpy_extras.io_utils import ExportHelper 16 | 17 | 18 | def writeToFile( file, string ): 19 | file.write( bytes( string, "UTF-8" ) ) 20 | 21 | 22 | def export_object( context, filepath ): 23 | 24 | ob = context.active_object 25 | 26 | bpy.ops.object.modifier_add( type='TRIANGULATE' ) 27 | ob.modifiers["Triangulate"].use_beauty = False 28 | bpy.ops.object.modifier_apply( apply_as="DATA", modifier="Triangulate" ) 29 | 30 | bpy.ops.object.mode_set(mode='OBJECT') 31 | 32 | vertices = [] 33 | normals = [] 34 | uvs = [] 35 | indices = [] 36 | 37 | vertex_number = -1 38 | 39 | for face in ob.data.polygons: 40 | vertices_in_face = face.vertices[:] 41 | 42 | for vertex in vertices_in_face: 43 | 44 | vertex_number += 1 45 | 46 | vertices.append( ob.data.vertices[ vertex ].co.x ) 47 | vertices.append( ob.data.vertices[ vertex ].co.y ) 48 | vertices.append( ob.data.vertices[ vertex ].co.z ) 49 | 50 | normals.append( ob.data.vertices[ vertex ].normal.x ) 51 | normals.append( ob.data.vertices[ vertex ].normal.y ) 52 | normals.append( ob.data.vertices[ vertex ].normal.z ) 53 | 54 | indices.append( vertex_number ) 55 | 56 | mesh = ob.to_mesh( bpy.context.scene, True, "PREVIEW" ) 57 | useUvs = len( mesh.tessface_uv_textures ) > 0 58 | 59 | if useUvs: 60 | for data in mesh.tessface_uv_textures.active.data: 61 | uvs.append( data.uv1.x ) 62 | uvs.append( data.uv1.y ) 63 | uvs.append( data.uv2.x ) 64 | uvs.append( data.uv2.y ) 65 | uvs.append( data.uv3.x ) 66 | uvs.append( data.uv3.y ) 67 | 68 | file = open( filepath, "wb" ) 69 | 70 | writeToFile( file, "{\n" ); 71 | 72 | writeToFile( file, ' "vertices": [' ); 73 | writeToFile( file, ", ".join( str(f) for f in vertices ) ); 74 | writeToFile( file, "],\n" ); 75 | 76 | writeToFile( file, ' "normals": [' ); 77 | writeToFile( file, ", ".join( str(f) for f in normals ) ); 78 | writeToFile( file, "],\n" ); 79 | 80 | if useUvs: 81 | writeToFile( file, ' "uv": [' ); 82 | writeToFile( file, ", ".join( str(f) for f in uvs ) ); 83 | writeToFile( file, "],\n" ); 84 | 85 | writeToFile( file, ' "faces": [' ); 86 | writeToFile( file, ", ".join( str(f) for f in indices ) ); 87 | writeToFile( file, "]\n" ); 88 | 89 | writeToFile( file, "}" ); 90 | 91 | file.flush() 92 | file.close() 93 | 94 | return True 95 | 96 | 97 | class ExportAresjs( bpy.types.Operator, ExportHelper ): 98 | bl_idname = "export_ares.js" 99 | bl_label = "Export Ares.js" 100 | bl_options = {"PRESET"} 101 | 102 | filename_ext = ".js" 103 | 104 | def execute( self, context ): 105 | start_time = time.time() 106 | 107 | print('\n_____START_____') 108 | 109 | filepath = self.filepath 110 | filepath = bpy.path.ensure_ext( filepath, self.filename_ext ) 111 | 112 | exported = export_object( context, filepath ) 113 | 114 | if exported: 115 | print("finished export in %s seconds" %( ( time.time() - start_time ) ) ) 116 | print( filepath ) 117 | print('\n_____FINISHED_____') 118 | 119 | return {'FINISHED'} 120 | 121 | 122 | def menu_func( self, context ): 123 | self.layout.operator( ExportAresjs.bl_idname, text="Ares.js(.js)") 124 | 125 | def register(): 126 | bpy.utils.register_module(__name__) 127 | bpy.types.INFO_MT_file_export.append( menu_func ) 128 | 129 | def unregister(): 130 | bpy.utils.unregister_module(__name__) 131 | bpy.types.INFO_MT_file_export.remove( menu_func ) 132 | 133 | if __name__ == "__main__": 134 | register() -------------------------------------------------------------------------------- /io_mesh_json/__pycache__/__init__.cpython-33.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanfaucett/Blender-JSON-Exporter/733df2ff26466d58d782842e0d79efaff3e5fcdc/io_mesh_json/__pycache__/__init__.cpython-33.pyc -------------------------------------------------------------------------------- /io_mesh_json/__pycache__/export_json.cpython-33.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanfaucett/Blender-JSON-Exporter/733df2ff26466d58d782842e0d79efaff3e5fcdc/io_mesh_json/__pycache__/export_json.cpython-33.pyc -------------------------------------------------------------------------------- /io_mesh_json/export_json.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | 4 | import os 5 | import os.path 6 | import math 7 | import operator 8 | 9 | MAX_INFLUENCES = 3 10 | 11 | 12 | # ##################################################### 13 | # Utils 14 | # ##################################################### 15 | 16 | def write_file( fname, content ): 17 | out = open( fname, "w" ) 18 | out.write( content ) 19 | out.close() 20 | 21 | def ensure_folder_exist( foldername ): 22 | if not os.access( foldername, os.R_OK|os.W_OK|os.X_OK ): 23 | os.makedirs( foldername ) 24 | 25 | def ensure_extension( filepath, extension ): 26 | if not filepath.lower().endswith( extension ): 27 | filepath += extension 28 | return filepath 29 | 30 | MAT4X4 = "[%s,%s,%s,%s, %s,%s,%s,%s, %s,%s,%s,%s, %s,%s,%s,%s]" 31 | def mat4_string( m ): 32 | return MAT4X4 % ( m[0][0],m[1][0],m[2][0],m[3][0], m[0][1],m[1][1],m[2][1],m[3][1], m[0][2],m[1][2],m[2][2],m[3][2], m[0][3],m[1][3],m[2][3],m[3][3] ) 33 | 34 | def get_action_state( action, bone, frame ): 35 | ngroups = len( action.groups ) 36 | 37 | pos = mathutils.Vector((0,0,0)) 38 | rot = mathutils.Quaternion((0,0,0,1)) 39 | scl = mathutils.Vector((1,1,1)) 40 | 41 | if ngroups > 0: 42 | index = 0 43 | 44 | for i in range( ngroups ): 45 | if action.groups[i].name == bone.name: 46 | index = i 47 | 48 | for channel in action.groups[index].channels: 49 | value = channel.evaluate( frame ) 50 | 51 | if "location" in channel.data_path: 52 | if channel.array_index == 0: 53 | pos.x = value 54 | elif channel.array_index == 1: 55 | pos.y = value 56 | elif channel.array_index == 2: 57 | pos.z = value 58 | 59 | if "quaternion" in channel.data_path: 60 | if channel.array_index == 0: 61 | rot.w = value 62 | elif channel.array_index == 1: 63 | rot.x = value 64 | elif channel.array_index == 2: 65 | rot.y = value 66 | elif channel.array_index == 3: 67 | rot.z = value 68 | 69 | if "scale" in channel.data_path: 70 | if channel.array_index == 0: 71 | scl.x = value 72 | elif channel.array_index == 1: 73 | scl.y = value 74 | elif channel.array_index == 2: 75 | scl.z = value 76 | 77 | pos = bone.head_local + pos 78 | rot = bone.matrix_local.to_quaternion() * rot 79 | rot.normalize() 80 | 81 | return pos, rot, scl 82 | 83 | 84 | 85 | 86 | # ##################################################### 87 | # Templates - mesh 88 | # ##################################################### 89 | 90 | TEMPLATE_FILE = """\ 91 | { 92 | "vertices": [%(vertices)s], 93 | 94 | "normals": [%(normals)s], 95 | 96 | "colors": [%(colors)s], 97 | 98 | "uvs": [%(uvs)s], 99 | 100 | "faces": [%(faces)s], 101 | 102 | "bones": [%(bones)s], 103 | 104 | "boneWeights": [%(boneWeights)s], 105 | 106 | "boneIndices": [%(boneIndices)s], 107 | 108 | "animations": {%(animations)s} 109 | } 110 | """ 111 | 112 | TEMPLATE_KEYFRAMES = '[ %g,%g,%g, %g,%g,%g,%g, %g,%g,%g ]' 113 | TEMPLATE_BONE = """\ 114 | { 115 | "parent": %(parent)d, 116 | "name": "%(name)s", 117 | "bindPose": %(bindPose)s, 118 | "skinned": %(skinned)s, 119 | 120 | "position": [0,0,0], 121 | "rotation": [0,0,0,1], 122 | "scale": [1,1,1], 123 | 124 | "inheritRotation": %(inheritRotation)s, 125 | "inheritScale": %(inheritScale)s 126 | } 127 | """ 128 | 129 | def flat_array( array ): 130 | 131 | return ", ".join( str( round( x, 6 ) ) for x in array ) 132 | 133 | def get_animation(): 134 | if( len( bpy.data.armatures ) == 0 ): 135 | return "" 136 | 137 | fps = bpy.data.scenes[0].render.fps 138 | armature = bpy.data.armatures[0] 139 | animations_string = "" 140 | 141 | count = -1; 142 | action_count = len( bpy.data.actions ) - 1 143 | 144 | for action in bpy.data.actions: 145 | count += 1 146 | 147 | end_frame = int( action.frame_range[1] ) 148 | start_frame = int( action.frame_range[0] ) 149 | frame_length = int( end_frame - start_frame ) 150 | 151 | frames = [] 152 | 153 | for frame in range( frame_length ): 154 | key_frame = [] 155 | 156 | for hierarchy in armature.bones: 157 | pos, rot, scl = get_action_state( action, hierarchy, frame ) 158 | 159 | px, py, pz = pos.x, pos.y, pos.z 160 | rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w 161 | sx, sy, sz = scl.x, scl.y, scl.z 162 | 163 | bone_frame = TEMPLATE_KEYFRAMES % ( px, py, pz, rx, ry, rz, rw, sx, sy, sz ) 164 | key_frame.append( bone_frame ) 165 | 166 | key_frame_string = "[%s]" % ",".join( key_frame ) 167 | frames.append( key_frame_string ); 168 | 169 | frame_string = ",".join( frames ) 170 | animations_string += '"%s": [%s]' % ( action.name, frame_string ) 171 | 172 | if( count < action_count ): 173 | animations_string += "," 174 | 175 | return animations_string 176 | 177 | 178 | def get_mesh_string( obj ): 179 | mesh = obj.to_mesh( bpy.context.scene, True, "PREVIEW" ) 180 | 181 | vertices = [] 182 | normals = [] 183 | colors = [] 184 | uvs = [] 185 | indices = [] 186 | bones = [] 187 | boneIndices = [] 188 | boneWeights = [] 189 | 190 | vertex_number = -1 191 | for face in obj.data.polygons: 192 | vertices_in_face = face.vertices[:] 193 | 194 | for vertex in vertices_in_face: 195 | 196 | vertex_number += 1 197 | 198 | vertices.append( obj.data.vertices[ vertex ].co.x ) 199 | vertices.append( obj.data.vertices[ vertex ].co.y ) 200 | vertices.append( obj.data.vertices[ vertex ].co.z ) 201 | 202 | normals.append( obj.data.vertices[ vertex ].normal.x ) 203 | normals.append( obj.data.vertices[ vertex ].normal.y ) 204 | normals.append( obj.data.vertices[ vertex ].normal.z ) 205 | 206 | indices.append( vertex_number ) 207 | 208 | if len( mesh.tessface_uv_textures ) > 0: 209 | for data in mesh.tessface_uv_textures.active.data: 210 | uvs.append( data.uv1.x ) 211 | uvs.append( data.uv1.y ) 212 | uvs.append( data.uv2.x ) 213 | uvs.append( data.uv2.y ) 214 | uvs.append( data.uv3.x ) 215 | uvs.append( data.uv3.y ) 216 | 217 | if len( mesh.tessface_vertex_colors ) > 0: 218 | for data in mesh.tessface_vertex_colors.active.data: 219 | colors.append( data.color1.r ) 220 | colors.append( data.color1.g ) 221 | colors.append( data.color1.b ) 222 | colors.append( data.color2.r ) 223 | colors.append( data.color2.g ) 224 | colors.append( data.color2.b ) 225 | colors.append( data.color3.r ) 226 | colors.append( data.color3.g ) 227 | colors.append( data.color3.b ) 228 | 229 | if( len( bpy.data.armatures ) > 0 ): 230 | armature = bpy.data.armatures[0] 231 | 232 | for face in obj.data.polygons: 233 | vertices_in_face = face.vertices[:] 234 | 235 | for vertex_index in vertices_in_face: 236 | vertex = obj.data.vertices[ vertex_index ] 237 | bone_array = [] 238 | 239 | for group in vertex.groups: 240 | index = group.group 241 | weight = group.weight 242 | 243 | bone_array.append( (index, weight) ) 244 | 245 | for i in range( MAX_INFLUENCES ): 246 | 247 | if i < len( bone_array ): 248 | bone_proxy = bone_array[i] 249 | 250 | found = 0 251 | index = bone_proxy[0] 252 | weight = bone_proxy[1] 253 | 254 | for j, bone in enumerate( armature.bones ): 255 | if obj.vertex_groups[ index ].name == bone.name: 256 | boneIndices.append("%d" % j ) 257 | boneWeights.append("%g" % weight ) 258 | found = 1 259 | break 260 | 261 | if found != 1: 262 | boneIndices.append("0") 263 | boneWeights.append("0") 264 | else: 265 | boneIndices.append("0") 266 | boneWeights.append("0") 267 | 268 | bone_id = -1 269 | for bone in armature.bones: 270 | bone_id += 1 271 | 272 | parent_index = -1 273 | weight = 0 274 | skinned = "false" 275 | inheritRotation = "false" 276 | inheritScale = "false" 277 | 278 | pos = bone.head 279 | 280 | if bone.parent != None: 281 | parent_index = i = 0 282 | pos = bone.head_local - bone.parent.head_local 283 | 284 | for parent in armature.bones: 285 | if parent.name == bone.parent.name: 286 | parent_index = i 287 | i += 1 288 | 289 | j = -1 290 | for boneIndex in boneIndices: 291 | j += 1 292 | if int( boneIndex ) == bone_id: 293 | weight += float( boneWeights[j] ) 294 | 295 | if weight > 0: 296 | skinned = "true" 297 | 298 | if bone.use_inherit_rotation: 299 | inheritRotation = "true" 300 | 301 | if bone.use_inherit_scale: 302 | inheritScale = "true" 303 | 304 | name = bone.name 305 | bindPose = bone.matrix_local.inverted() 306 | 307 | bones.append(TEMPLATE_BONE % { 308 | "parent": parent_index, 309 | "name": name, 310 | "bindPose": mat4_string( bindPose ), 311 | "skinned": skinned, 312 | "inheritRotation": inheritRotation, 313 | "inheritScale": inheritScale, 314 | }) 315 | 316 | return TEMPLATE_FILE % { 317 | "vertices": flat_array( vertices ), 318 | "normals": flat_array( normals ), 319 | "colors": flat_array( colors ), 320 | "uvs": flat_array( uvs ), 321 | "faces": flat_array( indices ), 322 | "bones": ",".join( bones ), 323 | "boneIndices": ",".join( boneIndices ), 324 | "boneWeights": ",".join( boneWeights ), 325 | "animations": get_animation() 326 | } 327 | 328 | 329 | 330 | def export_mesh( obj, filepath ): 331 | 332 | text = get_mesh_string( obj ) 333 | write_file( filepath, text ) 334 | 335 | print("writing", filepath, "done") 336 | 337 | 338 | # ##################################################### 339 | # Main 340 | # ##################################################### 341 | 342 | def save( operator, context, filepath = "" ): 343 | filepath = ensure_extension( filepath, ".json") 344 | 345 | bpy.ops.object.duplicate() 346 | bpy.ops.object.mode_set( mode = "OBJECT" ) 347 | bpy.ops.object.modifier_add( type="TRIANGULATE" ) 348 | bpy.ops.object.modifier_apply( apply_as = "DATA", modifier = "Triangulate" ) 349 | 350 | export_mesh( context.active_object, filepath ) 351 | bpy.ops.object.delete() 352 | 353 | return {"FINISHED"} -------------------------------------------------------------------------------- /io_mesh_json/import_json.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathanfaucett/Blender-JSON-Exporter/733df2ff26466d58d782842e0d79efaff3e5fcdc/io_mesh_json/import_json.py --------------------------------------------------------------------------------