[{"data":1,"prerenderedAt":1086},["ShallowReactive",2],{"tutorial-fast-stl-and-obj-reading-in-python":3},{"id":4,"title":5,"author":6,"body":7,"date":1059,"description":1060,"extension":1061,"keywords":1062,"meta":1075,"navigation":137,"ogImage":1076,"path":1077,"published":137,"seo":1078,"stem":1079,"tags":1080,"__hash__":1085},"tutorials\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-python.md","Fast STL and OBJ Reading in Python","Žiga Sajovic",{"type":8,"value":9,"toc":1048},"minimark",[10,28,48,52,57,82,92,96,170,177,192,199,237,240,267,271,282,339,342,374,385,424,428,431,598,605,630,634,648,702,705,709,712,796,802,835,839,864,947,951,988,992,995,999,1011,1016,1022,1028,1040,1044],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-cpp","C++"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-javascript","JavaScript",".",[14,29,30,31,35,36,39,40,43,44,47],{},"Most mesh work in Python starts with reading a file, and on large meshes the reader can take longer than the work. ",[32,33,34],"code",{},"trueform"," reads STL and OBJ straight into NumPy arrays: an ",[32,37,38],{},"(N, 3)"," face array and an ",[32,41,42],{},"(M, 3)"," point array. STL triangles are welded into shared vertices as they load. One ",[32,45,46],{},"pip install",". NumPy arrays in, NumPy arrays out. This tutorial covers reading and writing both formats, triangles, quads and mixed polygons, index and point dtypes, writing with a transformation, and measured read times against PyVista, trimesh and meshio.",[49,50],"article-cta",{":buttons":51},"[{\"label\":\"trueform\",\"to\":\"\u002Ftrueform\",\"icon\":\"i-lucide-cpu\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"GitHub\",\"to\":\"https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\",\"icon\":\"i-simple-icons-github\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Documentation\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fio\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[53,54,56],"h2",{"id":55},"install","Install",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-bash shiki shiki-themes github-dark","pip install trueform\n","bash","",[32,65,66],{"__ignoreMap":63},[67,68,71,75,79],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"svObZ","pip",[67,76,78],{"class":77},"sU2Wk"," install",[67,80,81],{"class":77}," trueform\n",[14,83,84,85,91],{},"See the ",[18,86,90],{"href":87,"rel":88},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fgetting-started\u002Finstallation",[89],"nofollow","installation guide"," for all options.",[53,93,95],{"id":94},"reading-stl","Reading STL",[58,97,101],{"className":98,"code":99,"language":100,"meta":63,"style":63},"language-python shiki shiki-themes github-dark","import numpy as np\nimport trueform as tf\n\nfaces, points = tf.read_stl(\"model.stl\")\n# faces:  (N, 3) int32\n# points: (M, 3) float32\n","python",[32,102,103,119,132,139,157,164],{"__ignoreMap":63},[67,104,105,109,113,116],{"class":69,"line":70},[67,106,108],{"class":107},"snl16","import",[67,110,112],{"class":111},"s95oV"," numpy ",[67,114,115],{"class":107},"as",[67,117,118],{"class":111}," np\n",[67,120,122,124,127,129],{"class":69,"line":121},2,[67,123,108],{"class":107},[67,125,126],{"class":111}," trueform ",[67,128,115],{"class":107},[67,130,131],{"class":111}," tf\n",[67,133,135],{"class":69,"line":134},3,[67,136,138],{"emptyLinePlaceholder":137},true,"\n",[67,140,142,145,148,151,154],{"class":69,"line":141},4,[67,143,144],{"class":111},"faces, points ",[67,146,147],{"class":107},"=",[67,149,150],{"class":111}," tf.read_stl(",[67,152,153],{"class":77},"\"model.stl\"",[67,155,156],{"class":111},")\n",[67,158,160],{"class":69,"line":159},5,[67,161,163],{"class":162},"sAwPA","# faces:  (N, 3) int32\n",[67,165,167],{"class":69,"line":166},6,[67,168,169],{"class":162},"# points: (M, 3) float32\n",[14,171,172],{},[173,174],"img",{"alt":175,"src":176},"Reading STL: the triangles as an STL file stores them, each with its own three corners, next to the welded mesh read_stl returns, one connected smooth surface","\u002Fimg\u002Ftutorials\u002Fsteps\u002Ffast-stl-and-obj-reading-1-weld.png",[14,178,179,180,183,184,187,188,191],{},"An STL file stores three corners per triangle with no sharing. ",[32,181,182],{},"tf.read_stl"," merges corners at the same position, so ",[32,185,186],{},"points"," holds each vertex once and ",[32,189,190],{},"faces"," indexes into it. The result is a connected mesh, ready for topology queries.",[14,193,194,195,198],{},"Wrap the arrays in a ",[32,196,197],{},"tf.Mesh"," to use them with the rest of the library:",[58,200,202],{"className":98,"code":201,"language":100,"meta":63,"style":63},"mesh = tf.Mesh(*tf.read_stl(\"model.stl\"))\nprint(mesh)     # Mesh(... points, ... faces, 3-gon, 3D, dtype=float32)\n",[32,203,204,225],{"__ignoreMap":63},[67,205,206,209,211,214,217,220,222],{"class":69,"line":70},[67,207,208],{"class":111},"mesh ",[67,210,147],{"class":107},[67,212,213],{"class":111}," tf.Mesh(",[67,215,216],{"class":107},"*",[67,218,219],{"class":111},"tf.read_stl(",[67,221,153],{"class":77},[67,223,224],{"class":111},"))\n",[67,226,227,231,234],{"class":69,"line":121},[67,228,230],{"class":229},"sDLfK","print",[67,232,233],{"class":111},"(mesh)     ",[67,235,236],{"class":162},"# Mesh(... points, ... faces, 3-gon, 3D, dtype=float32)\n",[14,238,239],{},"For meshes with more than 2 billion vertices, ask for int64 indices:",[58,241,243],{"className":98,"code":242,"language":100,"meta":63,"style":63},"faces, points = tf.read_stl(\"model.stl\", index_dtype=np.int64)\n",[32,244,245],{"__ignoreMap":63},[67,246,247,249,251,253,255,258,262,264],{"class":69,"line":70},[67,248,144],{"class":111},[67,250,147],{"class":107},[67,252,150],{"class":111},[67,254,153],{"class":77},[67,256,257],{"class":111},", ",[67,259,261],{"class":260},"s9osk","index_dtype",[67,263,147],{"class":107},[67,265,266],{"class":111},"np.int64)\n",[53,268,270],{"id":269},"reading-obj","Reading OBJ",[14,272,273,274,277,278,281],{},"OBJ files may mix triangles, quads and larger polygons. By default ",[32,275,276],{},"tf.read_obj"," returns the faces as an ",[32,279,280],{},"OffsetBlockedArray",", one block per face:",[58,283,285],{"className":98,"code":284,"language":100,"meta":63,"style":63},"faces, points = tf.read_obj(\"model.obj\")\nprint(type(faces).__name__)         # OffsetBlockedArray\nprint(faces[0])                     # vertex ids of face 0\n",[32,286,287,301,323],{"__ignoreMap":63},[67,288,289,291,293,296,299],{"class":69,"line":70},[67,290,144],{"class":111},[67,292,147],{"class":107},[67,294,295],{"class":111}," tf.read_obj(",[67,297,298],{"class":77},"\"model.obj\"",[67,300,156],{"class":111},[67,302,303,305,308,311,314,317,320],{"class":69,"line":121},[67,304,230],{"class":229},[67,306,307],{"class":111},"(",[67,309,310],{"class":229},"type",[67,312,313],{"class":111},"(faces).",[67,315,316],{"class":229},"__name__",[67,318,319],{"class":111},")         ",[67,321,322],{"class":162},"# OffsetBlockedArray\n",[67,324,325,327,330,333,336],{"class":69,"line":134},[67,326,230],{"class":229},[67,328,329],{"class":111},"(faces[",[67,331,332],{"class":229},"0",[67,334,335],{"class":111},"])                     ",[67,337,338],{"class":162},"# vertex ids of face 0\n",[14,340,341],{},"If you know the face size, ask for a fixed-width array:",[58,343,345],{"className":98,"code":344,"language":100,"meta":63,"style":63},"tri_faces, points = tf.read_obj(\"model.obj\", ngon=3)    # (N, 3) int32\n",[32,346,347],{"__ignoreMap":63},[67,348,349,352,354,356,358,360,363,365,368,371],{"class":69,"line":70},[67,350,351],{"class":111},"tri_faces, points ",[67,353,147],{"class":107},[67,355,295],{"class":111},[67,357,298],{"class":77},[67,359,257],{"class":111},[67,361,362],{"class":260},"ngon",[67,364,147],{"class":107},[67,366,367],{"class":229},"3",[67,369,370],{"class":111},")    ",[67,372,373],{"class":162},"# (N, 3) int32\n",[14,375,376,377,380,381,384],{},"The point dtype is float32 by default. Pass ",[32,378,379],{},"dtype=np.float64"," for double precision, and ",[32,382,383],{},"index_dtype=np.int64"," for large meshes:",[58,386,388],{"className":98,"code":387,"language":100,"meta":63,"style":63},"faces, points = tf.read_obj(\"model.obj\", ngon=3, dtype=np.float64, index_dtype=np.int64)\n",[32,389,390],{"__ignoreMap":63},[67,391,392,394,396,398,400,402,404,406,408,410,413,415,418,420,422],{"class":69,"line":70},[67,393,144],{"class":111},[67,395,147],{"class":107},[67,397,295],{"class":111},[67,399,298],{"class":77},[67,401,257],{"class":111},[67,403,362],{"class":260},[67,405,147],{"class":107},[67,407,367],{"class":229},[67,409,257],{"class":111},[67,411,412],{"class":260},"dtype",[67,414,147],{"class":107},[67,416,417],{"class":111},"np.float64, ",[67,419,261],{"class":260},[67,421,147],{"class":107},[67,423,266],{"class":111},[53,425,427],{"id":426},"quads-and-mixed-polygons","Quads and mixed polygons",[14,429,430],{},"A small quad mesh shows the two forms:",[58,432,434],{"className":98,"code":433,"language":100,"meta":63,"style":63},"with open(\"quads.obj\", \"w\") as f:\n    f.write(\"v 0 0 0\\nv 1 0 0\\nv 1 1 0\\nv 0 1 0\\nv 2 0 0\\nv 2 1 0\\n\")\n    f.write(\"f 1 2 3 4\\nf 2 5 6 3\\n\")\n\nquads, points = tf.read_obj(\"quads.obj\", ngon=4)\nprint(quads)                        # [[0 1 2 3] [1 4 5 2]]\n\npoly_faces, points = tf.read_obj(\"quads.obj\")\nquad_mesh = tf.Mesh(poly_faces, points)\nprint(quad_mesh)                    # Mesh(6 points, 2 faces, dynamic, 3D, dtype=float32)\n",[32,435,436,462,503,521,525,547,557,562,576,587],{"__ignoreMap":63},[67,437,438,441,444,446,449,451,454,457,459],{"class":69,"line":70},[67,439,440],{"class":107},"with",[67,442,443],{"class":229}," open",[67,445,307],{"class":111},[67,447,448],{"class":77},"\"quads.obj\"",[67,450,257],{"class":111},[67,452,453],{"class":77},"\"w\"",[67,455,456],{"class":111},") ",[67,458,115],{"class":107},[67,460,461],{"class":111}," f:\n",[67,463,464,467,470,473,476,478,481,483,486,488,491,493,496,498,501],{"class":69,"line":121},[67,465,466],{"class":111},"    f.write(",[67,468,469],{"class":77},"\"v 0 0 0",[67,471,472],{"class":229},"\\n",[67,474,475],{"class":77},"v 1 0 0",[67,477,472],{"class":229},[67,479,480],{"class":77},"v 1 1 0",[67,482,472],{"class":229},[67,484,485],{"class":77},"v 0 1 0",[67,487,472],{"class":229},[67,489,490],{"class":77},"v 2 0 0",[67,492,472],{"class":229},[67,494,495],{"class":77},"v 2 1 0",[67,497,472],{"class":229},[67,499,500],{"class":77},"\"",[67,502,156],{"class":111},[67,504,505,507,510,512,515,517,519],{"class":69,"line":134},[67,506,466],{"class":111},[67,508,509],{"class":77},"\"f 1 2 3 4",[67,511,472],{"class":229},[67,513,514],{"class":77},"f 2 5 6 3",[67,516,472],{"class":229},[67,518,500],{"class":77},[67,520,156],{"class":111},[67,522,523],{"class":69,"line":141},[67,524,138],{"emptyLinePlaceholder":137},[67,526,527,530,532,534,536,538,540,542,545],{"class":69,"line":159},[67,528,529],{"class":111},"quads, points ",[67,531,147],{"class":107},[67,533,295],{"class":111},[67,535,448],{"class":77},[67,537,257],{"class":111},[67,539,362],{"class":260},[67,541,147],{"class":107},[67,543,544],{"class":229},"4",[67,546,156],{"class":111},[67,548,549,551,554],{"class":69,"line":166},[67,550,230],{"class":229},[67,552,553],{"class":111},"(quads)                        ",[67,555,556],{"class":162},"# [[0 1 2 3] [1 4 5 2]]\n",[67,558,560],{"class":69,"line":559},7,[67,561,138],{"emptyLinePlaceholder":137},[67,563,565,568,570,572,574],{"class":69,"line":564},8,[67,566,567],{"class":111},"poly_faces, points ",[67,569,147],{"class":107},[67,571,295],{"class":111},[67,573,448],{"class":77},[67,575,156],{"class":111},[67,577,579,582,584],{"class":69,"line":578},9,[67,580,581],{"class":111},"quad_mesh ",[67,583,147],{"class":107},[67,585,586],{"class":111}," tf.Mesh(poly_faces, points)\n",[67,588,590,592,595],{"class":69,"line":589},10,[67,591,230],{"class":229},[67,593,594],{"class":111},"(quad_mesh)                    ",[67,596,597],{"class":162},"# Mesh(6 points, 2 faces, dynamic, 3D, dtype=float32)\n",[14,599,600,601,604],{},"OBJ indices start at 1; the arrays start at 0. To get triangles from polygons, use ",[32,602,603],{},"tf.triangulated",":",[58,606,608],{"className":98,"code":607,"language":100,"meta":63,"style":63},"tri_faces, tri_points = tf.triangulated((poly_faces, points))\nprint(tri_faces.shape)              # (4, 3)\n",[32,609,610,620],{"__ignoreMap":63},[67,611,612,615,617],{"class":69,"line":70},[67,613,614],{"class":111},"tri_faces, tri_points ",[67,616,147],{"class":107},[67,618,619],{"class":111}," tf.triangulated((poly_faces, points))\n",[67,621,622,624,627],{"class":69,"line":121},[67,623,230],{"class":229},[67,625,626],{"class":111},"(tri_faces.shape)              ",[67,628,629],{"class":162},"# (4, 3)\n",[53,631,633],{"id":632},"writing-stl-and-obj","Writing STL and OBJ",[14,635,636,637,640,641,643,644,647],{},"Both writers take a ",[32,638,639],{},"(faces, points)"," tuple or a ",[32,642,197],{}," and return ",[32,645,646],{},"True"," on success:",[58,649,651],{"className":98,"code":650,"language":100,"meta":63,"style":63},"faces, points = tf.read_stl(\"model.stl\")\n\ntf.write_stl((faces, points), \"out.stl\")\ntf.write_obj((faces, points), \"out.obj\")\ntf.write_obj(quad_mesh, \"quads_out.obj\")    # polygons stay polygons in OBJ\n",[32,652,653,665,669,679,689],{"__ignoreMap":63},[67,654,655,657,659,661,663],{"class":69,"line":70},[67,656,144],{"class":111},[67,658,147],{"class":107},[67,660,150],{"class":111},[67,662,153],{"class":77},[67,664,156],{"class":111},[67,666,667],{"class":69,"line":121},[67,668,138],{"emptyLinePlaceholder":137},[67,670,671,674,677],{"class":69,"line":134},[67,672,673],{"class":111},"tf.write_stl((faces, points), ",[67,675,676],{"class":77},"\"out.stl\"",[67,678,156],{"class":111},[67,680,681,684,687],{"class":69,"line":141},[67,682,683],{"class":111},"tf.write_obj((faces, points), ",[67,685,686],{"class":77},"\"out.obj\"",[67,688,156],{"class":111},[67,690,691,694,697,699],{"class":69,"line":159},[67,692,693],{"class":111},"tf.write_obj(quad_mesh, ",[67,695,696],{"class":77},"\"quads_out.obj\"",[67,698,370],{"class":111},[67,700,701],{"class":162},"# polygons stay polygons in OBJ\n",[14,703,704],{},"STL holds triangles only. OBJ keeps quads and mixed polygons as they are.",[53,706,708],{"id":707},"writing-with-a-transformation","Writing with a transformation",[14,710,711],{},"Pass a 4×4 matrix to place the mesh as it is written. The arrays are not changed.",[58,713,715],{"className":98,"code":714,"language":100,"meta":63,"style":63},"T = np.eye(4, dtype=np.float32)\nT[:3, 3] = [0.0, 0.0, 5.0]          # move up by 5\n\ntf.write_stl((faces, points), \"moved.stl\", transformation=T)\n",[32,716,717,738,775,779],{"__ignoreMap":63},[67,718,719,722,724,727,729,731,733,735],{"class":69,"line":70},[67,720,721],{"class":111},"T ",[67,723,147],{"class":107},[67,725,726],{"class":111}," np.eye(",[67,728,544],{"class":229},[67,730,257],{"class":111},[67,732,412],{"class":260},[67,734,147],{"class":107},[67,736,737],{"class":111},"np.float32)\n",[67,739,740,743,745,747,749,752,754,757,760,762,764,766,769,772],{"class":69,"line":121},[67,741,742],{"class":111},"T[:",[67,744,367],{"class":229},[67,746,257],{"class":111},[67,748,367],{"class":229},[67,750,751],{"class":111},"] ",[67,753,147],{"class":107},[67,755,756],{"class":111}," [",[67,758,759],{"class":229},"0.0",[67,761,257],{"class":111},[67,763,759],{"class":229},[67,765,257],{"class":111},[67,767,768],{"class":229},"5.0",[67,770,771],{"class":111},"]          ",[67,773,774],{"class":162},"# move up by 5\n",[67,776,777],{"class":69,"line":134},[67,778,138],{"emptyLinePlaceholder":137},[67,780,781,783,786,788,791,793],{"class":69,"line":141},[67,782,673],{"class":111},[67,784,785],{"class":77},"\"moved.stl\"",[67,787,257],{"class":111},[67,789,790],{"class":260},"transformation",[67,792,147],{"class":107},[67,794,795],{"class":111},"T)\n",[14,797,798,799,801],{},"A transformation set on a ",[32,800,197],{}," is applied too:",[58,803,805],{"className":98,"code":804,"language":100,"meta":63,"style":63},"mesh = tf.Mesh(faces, points)\nmesh.transformation = T\ntf.write_stl(mesh, \"moved.stl\")\n",[32,806,807,816,826],{"__ignoreMap":63},[67,808,809,811,813],{"class":69,"line":70},[67,810,208],{"class":111},[67,812,147],{"class":107},[67,814,815],{"class":111}," tf.Mesh(faces, points)\n",[67,817,818,821,823],{"class":69,"line":121},[67,819,820],{"class":111},"mesh.transformation ",[67,822,147],{"class":107},[67,824,825],{"class":111}," T\n",[67,827,828,831,833],{"class":69,"line":134},[67,829,830],{"class":111},"tf.write_stl(mesh, ",[67,832,785],{"class":77},[67,834,156],{"class":111},[53,836,838],{"id":837},"working-with-results","Working with results",[14,840,841,842,844,845,257,849,257,854,257,859,863],{},"The arrays are plain NumPy, so they go straight into any NumPy code or into the rest of ",[32,843,34],{},": ",[18,846,848],{"href":847},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python","booleans",[18,850,853],{"href":851,"rel":852},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fremesh",[89],"decimation",[18,855,858],{"href":856,"rel":857},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Ffill",[89],"hole filling",[18,860,862],{"href":861},"\u002Ftutorials\u002Ffast-connected-components-in-python","connected components",". A round trip keeps the data:",[58,865,867],{"className":98,"code":866,"language":100,"meta":63,"style":63},"faces, points = tf.make_sphere_mesh(1.0, stacks=100, segments=200)\ntf.write_obj((faces, points), \"sphere.obj\")\n\nfaces2, points2 = tf.read_obj(\"sphere.obj\", ngon=3)\nprint(np.array_equal(faces, faces2), np.allclose(points, points2))    # True True\n",[32,868,869,903,912,916,937],{"__ignoreMap":63},[67,870,871,873,875,878,881,883,886,888,891,893,896,898,901],{"class":69,"line":70},[67,872,144],{"class":111},[67,874,147],{"class":107},[67,876,877],{"class":111}," tf.make_sphere_mesh(",[67,879,880],{"class":229},"1.0",[67,882,257],{"class":111},[67,884,885],{"class":260},"stacks",[67,887,147],{"class":107},[67,889,890],{"class":229},"100",[67,892,257],{"class":111},[67,894,895],{"class":260},"segments",[67,897,147],{"class":107},[67,899,900],{"class":229},"200",[67,902,156],{"class":111},[67,904,905,907,910],{"class":69,"line":121},[67,906,683],{"class":111},[67,908,909],{"class":77},"\"sphere.obj\"",[67,911,156],{"class":111},[67,913,914],{"class":69,"line":134},[67,915,138],{"emptyLinePlaceholder":137},[67,917,918,921,923,925,927,929,931,933,935],{"class":69,"line":141},[67,919,920],{"class":111},"faces2, points2 ",[67,922,147],{"class":107},[67,924,295],{"class":111},[67,926,909],{"class":77},[67,928,257],{"class":111},[67,930,362],{"class":260},[67,932,147],{"class":107},[67,934,367],{"class":229},[67,936,156],{"class":111},[67,938,939,941,944],{"class":69,"line":159},[67,940,230],{"class":229},[67,942,943],{"class":111},"(np.array_equal(faces, faces2), np.allclose(points, points2))    ",[67,945,946],{"class":162},"# True True\n",[53,948,950],{"id":949},"related-tutorials","Related tutorials",[952,953,954,961,968,974,981],"ul",{},[955,956,957,960],"li",{},[18,958,959],{"href":847},"Fast Mesh Booleans in Python"," — exact union, intersection and difference of two meshes.",[955,962,963,967],{},[18,964,966],{"href":965},"\u002Ftutorials\u002Ffast-closest-point-on-a-mesh-in-python","Fast and Robust Closest Point on a Mesh in Python"," — nearest surface point for a batch of queries.",[955,969,970,973],{},[18,971,972],{"href":861},"Fast Connected Components in Python"," — split a mesh into parts and find its boundary loops.",[955,975,976,980],{},[18,977,979],{"href":978},"\u002Ftutorials\u002Fprincipal-curvatures-of-a-mesh-in-python","Fast and Robust Principal Curvatures of a Mesh in Python"," — curvature values and directions at every vertex.",[955,982,983,987],{},[18,984,986],{"href":985},"\u002Ftutorials\u002Fconstrained-delaunay-triangulation-in-python","Fast and Exact Constrained Delaunay Triangulation in Python"," — triangulate points and outlines, with holes and constraints.",[53,989,991],{"id":990},"performance","Performance",[14,993,994],{},"Read times on a 1,025,766-triangle mesh, in Python. OBJ is read as triangles into float32 points and int32 faces. Binary STL is read and welded exactly into shared vertices. Times include opening the file, parsing, and building the output arrays.",[996,997],"headline-numbers",{":items":998},"[{\"value\":\"6.6ms\",\"label\":\"OBJ read\",\"detail\":\"1,025,766 triangles\"},{\"value\":\"36.4ms\",\"label\":\"STL read and weld\",\"detail\":\"1,025,766 triangles\"},{\"value\":\"22×\",\"label\":\"faster OBJ than PyVista\u002FVTK\",\"detail\":\"144 ms\"}]",[14,1000,1001,1006,1007],{},[67,1002,1005],{"className":1003},[1004],"tag-pill","Apple M4 Max"," ",[67,1008,1010],{"className":1009},[1004],"16 threads",[1012,1013],"data-table",{":headers":1014,":highlight":332,":rows":1015},"[\"Library\",\"OBJ\",\"STL\"]","[[\"trueform\",\"6.6 ms\",\"36.4 ms\"],[\"PyVista\u002FVTK\",\"144 ms\",\"53.3 ms\"],[\"trimesh\",\"1,039 ms\",\"1,387 ms\"],[\"meshio\",\"1,296 ms\",\"1,770 ms\"]]",[14,1017,1018,1019,1021],{},"Of the four readers measured, ",[32,1020,34],{}," is the fastest on both formats: 22× PyVista\u002FVTK, 158× trimesh and 197× meshio on OBJ; 1.5× PyVista\u002FVTK, 38× trimesh and 49× meshio on STL. Median of three fresh-process medians. Benchmark run with trueform 0.10.3, PyVista 0.48.4 \u002F VTK 9.5.2, trimesh 5.1.0 and meshio 5.3.5.",[14,1023,1024],{},[173,1025],{"alt":1026,"src":1027},"Benchmark chart: trueform against other Python libraries, io","\u002Fimg\u002Falgorithms\u002Fbenchmark-io-python-2026.png",[14,1029,1030,1034,1035],{},[18,1031,1033],{"href":1032},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1036,1039],{"href":1037,"rel":1038},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fio",[89],"Documentation",[1041,1042],"cite-as",{"author":1043,"title":5},"Sajovic, {\\v{Z}}iga",[1045,1046,1047],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":63,"searchDepth":121,"depth":121,"links":1049},[1050,1051,1052,1053,1054,1055,1056,1057,1058],{"id":55,"depth":121,"text":56},{"id":94,"depth":121,"text":95},{"id":269,"depth":121,"text":270},{"id":426,"depth":121,"text":427},{"id":632,"depth":121,"text":633},{"id":707,"depth":121,"text":708},{"id":837,"depth":121,"text":838},{"id":949,"depth":121,"text":950},{"id":990,"depth":121,"text":991},"2026-10-31","Read and write STL and OBJ mesh files in Python straight into NumPy arrays. A 1M-triangle OBJ in 6.6 ms and a welded binary STL in 36.4 ms, measured against PyVista, trimesh and meshio. One pip install.","md",[1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074],"read STL Python","read OBJ Python","load mesh Python numpy","fast STL reader Python","fast OBJ loader Python","write STL Python","numpy-stl alternative","trimesh load alternative","meshio alternative","PyVista read alternative","Open3D read_triangle_mesh alternative","VTK STL reader Python",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-python.png","\u002Ftutorials\u002Ffast-stl-and-obj-reading-in-python",{"title":5,"description":1060},"tutorials\u002Ffast-stl-and-obj-reading-in-python",[100,1081,1082,1083,1084],"io","stl","obj","tutorial","Bf-j6TIJceFLBmrnUY2NpS8LLjjur5WmTq9kQQ1Mmv0",1793517812008]