{"id":169,"date":"2011-06-26T09:45:31","date_gmt":"2011-06-26T10:45:31","guid":{"rendered":"http:\/\/www.tamats.com\/blog\/?p=169"},"modified":"2012-04-16T16:31:35","modified_gmt":"2012-04-16T17:31:35","slug":"obj-to-json-python-script","status":"publish","type":"post","link":"https:\/\/www.tamats.com\/blog\/?p=169","title":{"rendered":".OBJ to JSON python script"},"content":{"rendered":"<p>I have developed a small script in python to convert OBJs to JSON, well suited for WebGL rendering.<br \/>\nIt is not very optimized, and it only support OBJs with one mesh with UVs and Normals.<br \/>\nIt computes the bounding box and clamp to 3 decimals all the values to avoid some ugly values.<br \/>\nIt support indexed meshes and coordinates swap for 3ds MAX exported meshes.<\/p>\n<p>The code after the jump.<br \/>\n<!--more--><\/p>\n<p>Usage: call the script passing the obj filename as a parameter<br \/>\nYou can add a second parameter specifying a name for the object, this is useful when using the output file as a script include in your code instead of a AJAX call. Then the output will be: myname = {&#8230;}<\/p>\n<p>Enjoy it!<\/p>\n<pre lang=\"python\">\r\n#!\/usr\/bin\/python\r\n# .OBJ to JSON\r\n# by Javi Agenjo (javi.agenjo@gmail.com) 26\/6\/11\r\n# only works with OBJs with normals and uvs\r\nimport os,sys\r\nimport json,math\r\n\r\nversion = \"0.4\"\r\n\r\nif len(sys.argv) < 2:\r\n    print \"Error: Filename parameter missing\"\r\n    exit(1)\r\n\r\nfilename = sys.argv[1]\r\nmeshname = \"\"\r\nobjectname = ''\r\n\r\nif len(sys.argv) > 2:\r\n    objectname = sys.argv[2]\r\n\r\nuse_3dsmax = False\r\nif \"-max\" in sys.argv: use_3dsmax = True\r\n\r\nuse_indexed = True\r\nif \"-indexed\" in sys.argv: use_indexed = True\r\n\r\nindexed_vertices = []\r\nindexed_normals = []\r\nindexed_uvs = []\r\n\r\npositions = []\r\nnormals = []\r\nuvs = []\r\nindices = []\r\n\r\nunique_indexed = 0\r\nindexed_triplets = {}\r\n\r\ndef add(v):\r\n    global positions,normals,uvs,indexed_vertices,indexed_normals,indexed_uvs\r\n    t = v.split(\"\/\")\r\n    #print \"*\"+v+\"*\"\r\n    positions.append( indexed_vertices[ int(t[0])-1 ] )    \r\n    uvs.append( indexed_uvs[ int(t[1])-1 ] )\r\n    normals.append( indexed_normals[ int(t[2])-1 ] )\r\n    return len(positions) - 1\r\n\r\ndef addIndexed(v):\r\n    global indexed_triplets, indices\r\n    i = indexed_triplets.get(v)\r\n    if i == None:\r\n        i = add(v)\r\n\tindexed_triplets[v] = i\r\n    indices.append(i)\r\n\r\ndef array2string(a):\r\n    r = \"[\"\r\n    for v in a:\r\n        for n in v:\r\n            r += str(n) + \",\"\r\n    return r[:-1] + \"]\"\r\n\r\ndef linealize(a):\r\n    r = []\r\n    for v in a:\r\n        for n in v:\r\n            r.append(n)\r\n    return r\r\n\r\ndef computeMinMax(a):\r\n    min_v = [1000000,1000000,1000000]\r\n    max_v = [-1000000,-1000000,-1000000]\r\n    for v in a:\r\n        min_v[0] = min( min_v[0], v[0] )\r\n        min_v[1] = min( min_v[1], v[1] )\r\n        min_v[2] = min( min_v[2], v[2] )\r\n        max_v[0] = max( max_v[0], v[0] )\r\n        max_v[1] = max( max_v[1], v[1] )\r\n        max_v[2] = max( max_v[2], v[2] )\r\n    return (min_v,max_v)\r\n\r\ntry:\r\n    fin = open(filename)\r\nexcept IOError:\r\n    print \"Error: file cannot be opened\"\r\n    exit(1)\r\n\r\ntry:\r\n    #if True:\r\n    for line in fin.readlines():\r\n\t#print line\r\n        line = line.replace(\"\\r\\n\",\"\")\r\n        line = line.replace(\"  \",\" \")\r\n\tline = line.strip()\r\n        words = line.split(' ')\r\n        #print \":::\" + words[0]\r\n        if words[0] == '#':\r\n            continue\r\n        \r\n        if words[0] == 'v':\r\n            if use_3dsmax:\r\n                indexed_vertices.append( [ -1 * float(words[1]), float(words[3]), float(words[2]) ])\r\n            else:\r\n                indexed_vertices.append( [ float(words[1]), float(words[2]) ,float(words[3]) ])    \r\n        elif words[0] == 'vn':\r\n            if use_3dsmax:\r\n\t        indexed_normals.append( [ -1 * float(words[1]), float(words[3]) , float(words[2]) ])\r\n\t    else:\r\n\t        indexed_normals.append( [ float(words[1]), float(words[2]) , float(words[3]) ])\r\n        elif words[0] == 'vt':\r\n            indexed_uvs.append( [ float(words[1]), float(words[2]) ])\r\n        elif words[0] == 'f':\r\n            size = len(words)\r\n            #0 is f, #1 first vertex, start in 2, ends when there is one remaining\r\n            for i in range(2,size-1):\r\n                #print \"T:\"+line+\"=>[\"+str(i)+\"]\"+words[i]+\" len: \"+str(len(words))\r\n\t\tif use_indexed:\r\n                    addIndexed(words[1])\r\n                    addIndexed(words[i])\r\n                    addIndexed(words[i+1])\r\n\t\telse:\r\n                    add(words[1])\r\n                    add(words[i])\r\n                    add(words[i+1])\r\n        elif words[0] == 'g' and len(words) > 1:\r\n\t    #print words\r\n            meshname = words[1]\r\nexcept:\r\n    print \"Error, problem parsing OBJ\"\r\n    exit(1)\r\n\r\nfin.close()\r\n\r\nif len(positions) == 0:\r\n    print \"Error, no OBJ info found\"\r\n    exit(1)\r\n\r\nminmax = computeMinMax(indexed_vertices)\r\n\r\nobj = {}\r\nobj[\"version\"] = version\r\nobj[\"filename\"] = filename\r\nobj[\"meshname\"] = meshname\r\nobj[\"positions\"] = linealize(positions)\r\nobj[\"normals\"] = linealize(normals)\r\nif len(indices):\r\n    obj[\"indices\"] = indices\r\nobj[\"uvs\"] = linealize(uvs)\r\nobj[\"aabb_min\"] = minmax[0]\r\nobj[\"aabb_max\"] = minmax[1]\r\nobj[\"numvertices\"] = len(positions)\r\n\r\nfrom json import encoder\r\nencoder.FLOAT_REPR = lambda o: format(o, '.3f')\r\n\r\nif objectname != '':\r\n    print objectname + \" = \",\r\nprint json.dumps(obj)\r\nexit(0)\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>I have developed a small script in python to convert OBJs to JSON, well suited for WebGL rendering. It is not very optimized, and it only support OBJs with one mesh with UVs and Normals. It computes the bounding box and clamp to 3 decimals all the values to avoid some ugly values. It support [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20,22,4,21],"tags":[],"class_list":["post-169","post","type-post","status-publish","format-standard","hentry","category-coding","category-graphics","category-python","category-webdevelopment"],"_links":{"self":[{"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/169","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=169"}],"version-history":[{"count":7,"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/169\/revisions"}],"predecessor-version":[{"id":199,"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/169\/revisions\/199"}],"wp:attachment":[{"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=169"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=169"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tamats.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=169"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}