[{"data":1,"prerenderedAt":1065},["ShallowReactive",2],{"tutorial-fast-mesh-booleans-in-python":3},{"id":4,"title":5,"author":6,"body":7,"date":1046,"description":1047,"extension":1048,"keywords":1049,"meta":1057,"navigation":346,"path":1058,"published":346,"seo":1059,"stem":1060,"tags":1061,"__hash__":1064},"tutorials\u002Ftutorials\u002Ffast-mesh-booleans-in-python.md","Fast Mesh Booleans in Python","Žiga Sajovic",{"type":8,"value":9,"toc":1032},"minimark",[10,28,39,43,46,50,55,60,85,95,99,135,138,170,173,198,201,357,368,372,381,459,462,466,474,477,567,572,592,596,614,618,636,640,661,664,694,697,722,726,751,787,790,906,909,913,920,949,958,973,977,980,984,996,1002,1007,1012,1024,1028],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp","C++"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript","JavaScript",".",[14,29,30,34,35,38],{},[31,32,33],"code",{},"trueform"," is the fastest Python mesh boolean library for real-world meshes — performing exact boolean union, intersection, and difference at interactive speed. One ",[31,36,37],{},"pip install",". NumPy arrays in, NumPy arrays out. This tutorial covers the basics: loading meshes, running booleans, and working with results. It then shows how to precompute spatial and topological structures and use shared views to avoid rebuilding them, enabling boolean operations on moving geometry at real-time rates.",[40,41],"article-cta",{":buttons":42},"[{\"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\u002Fgetting-started\u002Finstallation\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fboolean\",\"icon\":\"i-lucide-play\"}]",[14,44,45],{},"We will use the Stanford Dragon to demonstrate boolean union, intersection, and difference between two offset copies of the same mesh.",[47,48],"image-grid",{":images":49},"[{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_original.png\",\"label\":\"Dragon\",\"detail\":\"500K triangles\"},{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_union.png\",\"label\":\"Union\",\"detail\":\"A ∪ B\"},{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_difference.png\",\"label\":\"Difference\",\"detail\":\"A \\\\ B\"},{\"src\":\"\u002Fimg\u002Ftutorials\u002Fdragon_intersection.png\",\"label\":\"Intersection\",\"detail\":\"A ∩ B\"}]",[14,51,52,53,27],{},"Each boolean under 14ms on 2×500K polygons. Let's begin by installing ",[31,54,33],{},[56,57,59],"h2",{"id":58},"install","Install",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-bash shiki shiki-themes github-dark","pip install trueform\n","bash","",[31,68,69],{"__ignoreMap":66},[70,71,74,78,82],"span",{"class":72,"line":73},"line",1,[70,75,77],{"class":76},"svObZ","pip",[70,79,81],{"class":80},"sU2Wk"," install",[70,83,84],{"class":80}," trueform\n",[14,86,87,88,94],{},"See the ",[18,89,93],{"href":90,"rel":91},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fgetting-started\u002Finstallation",[92],"nofollow","installation guide"," for all options.",[56,96,98],{"id":97},"loading-the-mesh","Loading the mesh",[61,100,104],{"className":101,"code":102,"language":103,"meta":66,"style":66},"language-python shiki shiki-themes github-dark","import trueform as tf\nimport numpy as np\n","python",[31,105,106,122],{"__ignoreMap":66},[70,107,108,112,116,119],{"class":72,"line":73},[70,109,111],{"class":110},"snl16","import",[70,113,115],{"class":114},"s95oV"," trueform ",[70,117,118],{"class":110},"as",[70,120,121],{"class":114}," tf\n",[70,123,125,127,130,132],{"class":72,"line":124},2,[70,126,111],{"class":110},[70,128,129],{"class":114}," numpy ",[70,131,118],{"class":110},[70,133,134],{"class":114}," np\n",[14,136,137],{},"Load from a file:",[61,139,141],{"className":101,"code":140,"language":103,"meta":66,"style":66},"faces, points = tf.read_stl(\"stanford_dragon.stl\")\ndragon = tf.Mesh(faces, points)\n",[31,142,143,160],{"__ignoreMap":66},[70,144,145,148,151,154,157],{"class":72,"line":73},[70,146,147],{"class":114},"faces, points ",[70,149,150],{"class":110},"=",[70,152,153],{"class":114}," tf.read_stl(",[70,155,156],{"class":80},"\"stanford_dragon.stl\"",[70,158,159],{"class":114},")\n",[70,161,162,165,167],{"class":72,"line":124},[70,163,164],{"class":114},"dragon ",[70,166,150],{"class":110},[70,168,169],{"class":114}," tf.Mesh(faces, points)\n",[14,171,172],{},"Or in one line:",[61,174,176],{"className":101,"code":175,"language":103,"meta":66,"style":66},"dragon = tf.Mesh(*tf.read_stl(\"stanford_dragon.stl\"))\n",[31,177,178],{"__ignoreMap":66},[70,179,180,182,184,187,190,193,195],{"class":72,"line":73},[70,181,164],{"class":114},[70,183,150],{"class":110},[70,185,186],{"class":114}," tf.Mesh(",[70,188,189],{"class":110},"*",[70,191,192],{"class":114},"tf.read_stl(",[70,194,156],{"class":80},[70,196,197],{"class":114},"))\n",[14,199,200],{},"If you already have vertex and face data as NumPy arrays, pass them directly:",[61,202,204],{"className":101,"code":203,"language":103,"meta":66,"style":66},"faces = np.array([[0, 1, 2], [1, 3, 2]], dtype=np.int32)\npoints = np.array([\n    [0, 0, 0], [1, 0, 0],\n    [0, 1, 0], [1, 1, 0]\n], dtype=np.float32)\n\ndragon = tf.Mesh(faces, points)\n",[31,205,206,257,267,298,328,341,348],{"__ignoreMap":66},[70,207,208,211,213,216,220,223,226,228,231,234,236,238,241,243,245,248,252,254],{"class":72,"line":73},[70,209,210],{"class":114},"faces ",[70,212,150],{"class":110},[70,214,215],{"class":114}," np.array([[",[70,217,219],{"class":218},"sDLfK","0",[70,221,222],{"class":114},", ",[70,224,225],{"class":218},"1",[70,227,222],{"class":114},[70,229,230],{"class":218},"2",[70,232,233],{"class":114},"], [",[70,235,225],{"class":218},[70,237,222],{"class":114},[70,239,240],{"class":218},"3",[70,242,222],{"class":114},[70,244,230],{"class":218},[70,246,247],{"class":114},"]], ",[70,249,251],{"class":250},"s9osk","dtype",[70,253,150],{"class":110},[70,255,256],{"class":114},"np.int32)\n",[70,258,259,262,264],{"class":72,"line":124},[70,260,261],{"class":114},"points ",[70,263,150],{"class":110},[70,265,266],{"class":114}," np.array([\n",[70,268,270,273,275,277,279,281,283,285,287,289,291,293,295],{"class":72,"line":269},3,[70,271,272],{"class":114},"    [",[70,274,219],{"class":218},[70,276,222],{"class":114},[70,278,219],{"class":218},[70,280,222],{"class":114},[70,282,219],{"class":218},[70,284,233],{"class":114},[70,286,225],{"class":218},[70,288,222],{"class":114},[70,290,219],{"class":218},[70,292,222],{"class":114},[70,294,219],{"class":218},[70,296,297],{"class":114},"],\n",[70,299,301,303,305,307,309,311,313,315,317,319,321,323,325],{"class":72,"line":300},4,[70,302,272],{"class":114},[70,304,219],{"class":218},[70,306,222],{"class":114},[70,308,225],{"class":218},[70,310,222],{"class":114},[70,312,219],{"class":218},[70,314,233],{"class":114},[70,316,225],{"class":218},[70,318,222],{"class":114},[70,320,225],{"class":218},[70,322,222],{"class":114},[70,324,219],{"class":218},[70,326,327],{"class":114},"]\n",[70,329,331,334,336,338],{"class":72,"line":330},5,[70,332,333],{"class":114},"], ",[70,335,251],{"class":250},[70,337,150],{"class":110},[70,339,340],{"class":114},"np.float32)\n",[70,342,344],{"class":72,"line":343},6,[70,345,347],{"emptyLinePlaceholder":346},true,"\n",[70,349,351,353,355],{"class":72,"line":350},7,[70,352,164],{"class":114},[70,354,150],{"class":110},[70,356,169],{"class":114},[14,358,359,360,367],{},"The ",[18,361,364],{"href":362,"rel":363},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fspatial#mesh",[92],[31,365,366],{},"Mesh"," form wraps your arrays with geometric semantics — spatial queries, topology, and boolean operations all work directly on it.",[56,369,371],{"id":370},"transformations","Transformations",[14,373,374,375,380],{},"To perform a boolean between two copies of the dragon at different positions, set a ",[18,376,379],{"href":377,"rel":378},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fspatial#transformations-on-forms",[92],"transformation"," on the mesh. The underlying data is shared — no vertices are copied.",[61,382,384],{"className":101,"code":383,"language":103,"meta":66,"style":66},"T = np.eye(4, dtype=np.float32)\nT[:3, 3] = [0.0, 1.0, 0.0]  # translate by [0, 1, 0]\n\ndragon.transformation = T\n",[31,385,386,407,445,449],{"__ignoreMap":66},[70,387,388,391,393,396,399,401,403,405],{"class":72,"line":73},[70,389,390],{"class":114},"T ",[70,392,150],{"class":110},[70,394,395],{"class":114}," np.eye(",[70,397,398],{"class":218},"4",[70,400,222],{"class":114},[70,402,251],{"class":250},[70,404,150],{"class":110},[70,406,340],{"class":114},[70,408,409,412,414,416,418,421,423,426,429,431,434,436,438,441],{"class":72,"line":124},[70,410,411],{"class":114},"T[:",[70,413,240],{"class":218},[70,415,222],{"class":114},[70,417,240],{"class":218},[70,419,420],{"class":114},"] ",[70,422,150],{"class":110},[70,424,425],{"class":114}," [",[70,427,428],{"class":218},"0.0",[70,430,222],{"class":114},[70,432,433],{"class":218},"1.0",[70,435,222],{"class":114},[70,437,428],{"class":218},[70,439,440],{"class":114},"]  ",[70,442,444],{"class":443},"sAwPA","# translate by [0, 1, 0]\n",[70,446,447],{"class":72,"line":269},[70,448,347],{"emptyLinePlaceholder":346},[70,450,451,454,456],{"class":72,"line":300},[70,452,453],{"class":114},"dragon.transformation ",[70,455,150],{"class":110},[70,457,458],{"class":114}," T\n",[14,460,461],{},"The spatial tree and topology structures stay valid across transformations — they are applied on-the-fly during queries.",[56,463,465],{"id":464},"boolean-operations","Boolean operations",[14,467,468,469,27],{},"Three operations. Same two inputs. ",[18,470,473],{"href":471,"rel":472},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fcut#boolean-operations",[92],"Full documentation",[14,475,476],{},"To boolean two separate meshes at different positions, create a shared view. The view shares all underlying data and structures but carries its own transformation:",[61,478,480],{"className":101,"code":479,"language":103,"meta":66,"style":66},"dragon = tf.Mesh(*tf.read_stl(\"stanford_dragon.stl\"))\n\ntranslated = dragon.shared_view()\nT = np.eye(4, dtype=np.float32)\nT[:3, 3] = [0.0, 1.0, 0.0]\ntranslated.transformation = T\n",[31,481,482,498,502,512,530,558],{"__ignoreMap":66},[70,483,484,486,488,490,492,494,496],{"class":72,"line":73},[70,485,164],{"class":114},[70,487,150],{"class":110},[70,489,186],{"class":114},[70,491,189],{"class":110},[70,493,192],{"class":114},[70,495,156],{"class":80},[70,497,197],{"class":114},[70,499,500],{"class":72,"line":124},[70,501,347],{"emptyLinePlaceholder":346},[70,503,504,507,509],{"class":72,"line":269},[70,505,506],{"class":114},"translated ",[70,508,150],{"class":110},[70,510,511],{"class":114}," dragon.shared_view()\n",[70,513,514,516,518,520,522,524,526,528],{"class":72,"line":300},[70,515,390],{"class":114},[70,517,150],{"class":110},[70,519,395],{"class":114},[70,521,398],{"class":218},[70,523,222],{"class":114},[70,525,251],{"class":250},[70,527,150],{"class":110},[70,529,340],{"class":114},[70,531,532,534,536,538,540,542,544,546,548,550,552,554,556],{"class":72,"line":330},[70,533,411],{"class":114},[70,535,240],{"class":218},[70,537,222],{"class":114},[70,539,240],{"class":218},[70,541,420],{"class":114},[70,543,150],{"class":110},[70,545,425],{"class":114},[70,547,428],{"class":218},[70,549,222],{"class":114},[70,551,433],{"class":218},[70,553,222],{"class":114},[70,555,428],{"class":218},[70,557,327],{"class":114},[70,559,560,563,565],{"class":72,"line":343},[70,561,562],{"class":114},"translated.transformation ",[70,564,150],{"class":110},[70,566,458],{"class":114},[568,569,571],"h3",{"id":570},"union","Union",[61,573,575],{"className":101,"code":574,"language":103,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels = \n    tf.boolean_union(dragon, translated)\n",[31,576,577,587],{"__ignoreMap":66},[70,578,579,582,584],{"class":72,"line":73},[70,580,581],{"class":114},"(result_faces, result_points), labels, face_labels ",[70,583,150],{"class":110},[70,585,586],{"class":114}," \n",[70,588,589],{"class":72,"line":124},[70,590,591],{"class":114},"    tf.boolean_union(dragon, translated)\n",[568,593,595],{"id":594},"difference","Difference",[61,597,599],{"className":101,"code":598,"language":103,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels = \n    tf.boolean_difference(dragon, translated)\n",[31,600,601,609],{"__ignoreMap":66},[70,602,603,605,607],{"class":72,"line":73},[70,604,581],{"class":114},[70,606,150],{"class":110},[70,608,586],{"class":114},[70,610,611],{"class":72,"line":124},[70,612,613],{"class":114},"    tf.boolean_difference(dragon, translated)\n",[568,615,617],{"id":616},"intersection","Intersection",[61,619,621],{"className":101,"code":620,"language":103,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels = \n    tf.boolean_intersection(dragon, translated)\n",[31,622,623,631],{"__ignoreMap":66},[70,624,625,627,629],{"class":72,"line":73},[70,626,581],{"class":114},[70,628,150],{"class":110},[70,630,586],{"class":114},[70,632,633],{"class":72,"line":124},[70,634,635],{"class":114},"    tf.boolean_intersection(dragon, translated)\n",[568,637,639],{"id":638},"working-with-results","Working with results",[14,641,642,643,646,647,650,651,653,654,656,657,660],{},"Every boolean returns three values. ",[31,644,645],{},"(result_faces, result_points)"," is the output mesh as NumPy arrays. ",[31,648,649],{},"labels"," marks each output face — ",[31,652,219],{}," for faces originating from the first input, ",[31,655,225],{}," from the second. ",[31,658,659],{},"face_labels"," maps each output face back to the index of its source face in the input, enabling attribute transfer.",[14,662,663],{},"To split the result into separate meshes by label:",[61,665,667],{"className":101,"code":666,"language":103,"meta":66,"style":66},"result_mesh = tf.Mesh(result_faces, result_points)\ncomponents, component_labels = \\\n    tf.split_into_components(result_mesh, labels)\n",[31,668,669,679,689],{"__ignoreMap":66},[70,670,671,674,676],{"class":72,"line":73},[70,672,673],{"class":114},"result_mesh ",[70,675,150],{"class":110},[70,677,678],{"class":114}," tf.Mesh(result_faces, result_points)\n",[70,680,681,684,686],{"class":72,"line":124},[70,682,683],{"class":114},"components, component_labels ",[70,685,150],{"class":110},[70,687,688],{"class":114}," \\\n",[70,690,691],{"class":72,"line":269},[70,692,693],{"class":114},"    tf.split_into_components(result_mesh, labels)\n",[14,695,696],{},"Write to file:",[61,698,700],{"className":101,"code":699,"language":103,"meta":66,"style":66},"tf.write_stl((result_faces, result_points), \"result.stl\")\ntf.write_obj((result_faces, result_points), \"result.obj\")\n",[31,701,702,712],{"__ignoreMap":66},[70,703,704,707,710],{"class":72,"line":73},[70,705,706],{"class":114},"tf.write_stl((result_faces, result_points), ",[70,708,709],{"class":80},"\"result.stl\"",[70,711,159],{"class":114},[70,713,714,717,720],{"class":72,"line":124},[70,715,716],{"class":114},"tf.write_obj((result_faces, result_points), ",[70,718,719],{"class":80},"\"result.obj\"",[70,721,159],{"class":114},[56,723,725],{"id":724},"precomputed-structures","Precomputed structures",[14,727,728,729,222,735,742,743,750],{},"When running multiple booleans on the same geometry, build the spatial and topological structures once. The ",[18,730,732],{"href":362,"rel":731},[92],[31,733,734],{},"build_tree",[18,736,739],{"href":737,"rel":738},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Ftopology#cell-membership",[92],[31,740,741],{},"face_membership",", and ",[18,744,747],{"href":745,"rel":746},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Ftopology#manifold-edge-link",[92],[31,748,749],{},"manifold_edge_link"," are computed lazily on first use, but explicit precomputation avoids paying the cost during the boolean itself:",[61,752,754],{"className":101,"code":753,"language":103,"meta":66,"style":66},"dragon = tf.Mesh(*tf.read_stl(\"stanford_dragon.stl\"))\ndragon.build_tree()\ndragon.build_face_membership()\ndragon.build_manifold_edge_link()\n",[31,755,756,772,777,782],{"__ignoreMap":66},[70,757,758,760,762,764,766,768,770],{"class":72,"line":73},[70,759,164],{"class":114},[70,761,150],{"class":110},[70,763,186],{"class":114},[70,765,189],{"class":110},[70,767,192],{"class":114},[70,769,156],{"class":80},[70,771,197],{"class":114},[70,773,774],{"class":72,"line":124},[70,775,776],{"class":114},"dragon.build_tree()\n",[70,778,779],{"class":72,"line":269},[70,780,781],{"class":114},"dragon.build_face_membership()\n",[70,783,784],{"class":72,"line":300},[70,785,786],{"class":114},"dragon.build_manifold_edge_link()\n",[14,788,789],{},"Now create shared views at different positions. Each view shares the same data and structures — only the transformation differs:",[61,791,793],{"className":101,"code":792,"language":103,"meta":66,"style":66},"for t in np.arange(0.1, 1.1, 0.1):\n    view = dragon.shared_view()\n    T = np.eye(4, dtype=np.float32)\n    T[:3, 3] = [0.0, t, 0.0]\n    view.transformation = T\n\n    (result_faces, result_points), labels, face_labels = \\\n        tf.boolean_difference(dragon, view)\n",[31,794,795,824,833,852,878,887,891,900],{"__ignoreMap":66},[70,796,797,800,803,806,809,812,814,817,819,821],{"class":72,"line":73},[70,798,799],{"class":110},"for",[70,801,802],{"class":114}," t ",[70,804,805],{"class":110},"in",[70,807,808],{"class":114}," np.arange(",[70,810,811],{"class":218},"0.1",[70,813,222],{"class":114},[70,815,816],{"class":218},"1.1",[70,818,222],{"class":114},[70,820,811],{"class":218},[70,822,823],{"class":114},"):\n",[70,825,826,829,831],{"class":72,"line":124},[70,827,828],{"class":114},"    view ",[70,830,150],{"class":110},[70,832,511],{"class":114},[70,834,835,838,840,842,844,846,848,850],{"class":72,"line":269},[70,836,837],{"class":114},"    T ",[70,839,150],{"class":110},[70,841,395],{"class":114},[70,843,398],{"class":218},[70,845,222],{"class":114},[70,847,251],{"class":250},[70,849,150],{"class":110},[70,851,340],{"class":114},[70,853,854,857,859,861,863,865,867,869,871,874,876],{"class":72,"line":300},[70,855,856],{"class":114},"    T[:",[70,858,240],{"class":218},[70,860,222],{"class":114},[70,862,240],{"class":218},[70,864,420],{"class":114},[70,866,150],{"class":110},[70,868,425],{"class":114},[70,870,428],{"class":218},[70,872,873],{"class":114},", t, ",[70,875,428],{"class":218},[70,877,327],{"class":114},[70,879,880,883,885],{"class":72,"line":330},[70,881,882],{"class":114},"    view.transformation ",[70,884,150],{"class":110},[70,886,458],{"class":114},[70,888,889],{"class":72,"line":343},[70,890,347],{"emptyLinePlaceholder":346},[70,892,893,896,898],{"class":72,"line":350},[70,894,895],{"class":114},"    (result_faces, result_points), labels, face_labels ",[70,897,150],{"class":110},[70,899,688],{"class":114},[70,901,903],{"class":72,"line":902},8,[70,904,905],{"class":114},"        tf.boolean_difference(dragon, view)\n",[14,907,908],{},"10 booleans on 2×500K polygon meshes in under 140ms total. Spatial and topological structures computed once.",[56,910,912],{"id":911},"extracting-intersection-curves","Extracting intersection curves",[14,914,915,916,919],{},"Any boolean can also return the intersection boundary as polylines embedded on the surface. Pass ",[31,917,918],{},"return_curves=True",":",[61,921,923],{"className":101,"code":922,"language":103,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels, (paths, curve_points) = \\\n    tf.boolean_difference(dragon, translated, return_curves=True)\n",[31,924,925,934],{"__ignoreMap":66},[70,926,927,930,932],{"class":72,"line":73},[70,928,929],{"class":114},"(result_faces, result_points), labels, face_labels, (paths, curve_points) ",[70,931,150],{"class":110},[70,933,688],{"class":114},[70,935,936,939,942,944,947],{"class":72,"line":124},[70,937,938],{"class":114},"    tf.boolean_difference(dragon, translated, ",[70,940,941],{"class":250},"return_curves",[70,943,150],{"class":110},[70,945,946],{"class":218},"True",[70,948,159],{"class":114},[14,950,951,952,957],{},"If only the ",[18,953,956],{"href":954,"rel":955},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fintersect#intersection-curves",[92],"intersection curves"," are needed without the boolean itself:",[61,959,961],{"className":101,"code":960,"language":103,"meta":66,"style":66},"paths, curve_points = tf.intersection_curves(dragon, translated)\n",[31,962,963],{"__ignoreMap":66},[70,964,965,968,970],{"class":72,"line":73},[70,966,967],{"class":114},"paths, curve_points ",[70,969,150],{"class":110},[70,971,972],{"class":114}," tf.intersection_curves(dragon, translated)\n",[56,974,976],{"id":975},"performance-and-robustness","Performance and robustness",[14,978,979],{},"All operations run at interactive speed on million-polygon meshes.",[981,982],"headline-numbers",{":items":983},"[{\"value\":\"3.5ms\",\"label\":\"Intersection curves\",\"detail\":\"2×500K polygons\"},{\"value\":\"14ms\",\"label\":\"Boolean union\",\"detail\":\"2×500K polygons\"},{\"value\":\"86ms\",\"label\":\"Polygon arrangements\",\"detail\":\"2×500K polygons\"}]",[14,985,986,991,992],{},[70,987,990],{"className":988},[989],"tag-pill","Apple M4 Max"," ",[70,993,995],{"className":994},[989],"16 threads",[14,997,998,999,1001],{},"Real meshes are not ideal manifolds. ",[31,1000,33],{}," handles them directly — no preprocessing, no manifold requirement.",[1003,1004],"data-table",{":headers":1005,":highlight":219,":rows":1006},"[\"Feature\",\"Handling\"]","[[\"Convex polygons\",\"Native — not limited to triangles\"],[\"Non-manifold edges\",\"Handled directly\"],[\"Inconsistent winding\",\"Bayesian classification\"],[\"Self-intersecting input\",\"Resolved via polygon arrangements\"],[\"Coplanar primitives\",\"Exact — aligned\u002Fopposing boundary classification\"],[\"Contour crossings\",\"Resolved via indirect predicates\"]]",[14,1008,1009,1011],{},[31,1010,33],{}," is a robust CGAL alternative — and the fastest mesh boolean library available — for exact mesh boolean operations in C++, Python, and TypeScript.",[14,1013,1014,1019,1020],{},[18,1015,1018],{"href":1016,"rel":1017},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fbenchmarks",[92],"Full benchmarks and methodology"," · ",[18,1021,1023],{"href":1022},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[1025,1026],"cite-as",{"author":1027,"title":5},"Sajovic, {\\v{Z}}iga",[1029,1030,1031],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":66,"searchDepth":124,"depth":124,"links":1033},[1034,1035,1036,1037,1043,1044,1045],{"id":58,"depth":124,"text":59},{"id":97,"depth":124,"text":98},{"id":370,"depth":124,"text":371},{"id":464,"depth":124,"text":465,"children":1038},[1039,1040,1041,1042],{"id":570,"depth":269,"text":571},{"id":594,"depth":269,"text":595},{"id":616,"depth":269,"text":617},{"id":638,"depth":269,"text":639},{"id":724,"depth":124,"text":725},{"id":911,"depth":124,"text":912},{"id":975,"depth":124,"text":976},"2026-04-30","Learn how to perform fast mesh boolean operations in Python. Union, intersection, and difference at interactive speed on million-polygon meshes. One pip install, NumPy arrays in and out.","md",[1050,1051,1052,1053,1054,1055,1056],"Python mesh boolean","mesh boolean Python","pip mesh boolean","numpy mesh boolean","CGAL alternative Python","trimesh boolean alternative","pymesh boolean alternative",{},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python",{"title":5,"description":1047},"tutorials\u002Ffast-mesh-booleans-in-python",[103,1062,1063],"booleans","tutorial","AfqQG6dhcIX1TON4QR1CiZTeffZIOJ0mTzVhcIxVDA4",1779353828805]