[{"data":1,"prerenderedAt":3688},["ShallowReactive",2],{"tutorial-list":3},[4,1450,2762],{"id":5,"title":6,"author":7,"body":8,"date":1431,"description":1432,"extension":1433,"keywords":1434,"meta":1441,"navigation":255,"path":1442,"published":255,"seo":1443,"stem":1444,"tags":1445,"__hash__":1449},"tutorials\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp.md","Fast Mesh Booleans in C++","Žiga Sajovic",{"type":9,"value":10,"toc":1416},"minimark",[11,29,36,40,43,47,52,57,60,119,129,133,136,152,159,217,220,311,318,418,426,430,433,518,538,542,550,555,594,598,632,642,646,680,684,707,710,742,745,801,805,831,991,994,1140,1143,1147,1154,1236,1245,1269,1273,1294,1346,1349,1353,1356,1360,1380,1386,1391,1396,1408,1412],[12,13,14],"note",{},[15,16,17,18,23,24,28],"p",{},"Also available in ",[19,20,22],"a",{"href":21},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python","Python"," and ",[19,25,27],{"href":26},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript","JavaScript",".",[15,30,31,35],{},[32,33,34],"code",{},"trueform"," is the fastest C++ mesh boolean library for real-world meshes — performing exact boolean union, intersection, and difference at interactive speed. This tutorial covers the basics: loading meshes, running booleans, and working with results. It then shows how to precompute spatial and topological structures and apply transformations to avoid rebuilding them, enabling boolean operations on moving geometry at real-time rates.",[37,38],"article-cta",{":buttons":39},"[{\"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\u002Fcpp\u002Fgetting-started\u002Finstallation\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fboolean\",\"icon\":\"i-lucide-play\"}]",[15,41,42],{},"We will use the Stanford Dragon to demonstrate boolean union, intersection, and difference between two offset copies of the same mesh.",[44,45],"image-grid",{":images":46},"[{\"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\"}]",[15,48,49,50,28],{},"Each boolean under 14ms on 2×500K polygons. Let's begin by installing ",[32,51,34],{},[53,54,56],"h2",{"id":55},"install","Install",[15,58,59],{},"Header-only. Add it to your project via CMake FetchContent:",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-cmake shiki shiki-themes github-dark","FetchContent_Declare(\n  trueform\n  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n)\nFetchContent_MakeAvailable(trueform)\ntarget_link_libraries(my_app PRIVATE tf::trueform)\n","cmake","",[32,68,69,78,84,90,96,102],{"__ignoreMap":66},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,77],{"class":76},"s95oV","FetchContent_Declare(\n",[70,79,81],{"class":72,"line":80},2,[70,82,83],{"class":76},"  trueform\n",[70,85,87],{"class":72,"line":86},3,[70,88,89],{"class":76},"  GIT_REPOSITORY https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\n",[70,91,93],{"class":72,"line":92},4,[70,94,95],{"class":76},")\n",[70,97,99],{"class":72,"line":98},5,[70,100,101],{"class":76},"FetchContent_MakeAvailable(trueform)\n",[70,103,105,109,112,116],{"class":72,"line":104},6,[70,106,108],{"class":107},"snl16","target_link_libraries",[70,110,111],{"class":76},"(my_app ",[70,113,115],{"class":114},"svObZ","PRIVATE",[70,117,118],{"class":76}," tf::trueform)\n",[15,120,121,122,128],{},"Also available via conan, nuget, and pip. See the ",[19,123,127],{"href":124,"rel":125},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fgetting-started\u002Finstallation",[126],"nofollow","installation guide"," for all options.",[53,130,132],{"id":131},"loading-the-mesh","Loading the mesh",[15,134,135],{},"Include the library:",[61,137,141],{"className":138,"code":139,"language":140,"meta":66,"style":66},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n","cpp",[32,142,143],{"__ignoreMap":66},[70,144,145,148],{"class":72,"line":73},[70,146,147],{"class":107},"#include",[70,149,151],{"class":150},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[15,153,154,155,158],{},"All functions live in the ",[32,156,157],{},"tf::"," namespace. To load a mesh from a file:",[61,160,162],{"className":138,"code":161,"language":140,"meta":66,"style":66},"auto dragon_buffer = tf::read_stl(\"stanford_dragon.stl\");\n\u002F\u002F semantic view into the data\nauto dragon = dragon_buffer.polygons();\n",[32,163,164,193,199],{"__ignoreMap":66},[70,165,166,169,172,175,178,181,184,187,190],{"class":72,"line":73},[70,167,168],{"class":107},"auto",[70,170,171],{"class":76}," dragon_buffer ",[70,173,174],{"class":107},"=",[70,176,177],{"class":114}," tf",[70,179,180],{"class":76},"::",[70,182,183],{"class":114},"read_stl",[70,185,186],{"class":76},"(",[70,188,189],{"class":150},"\"stanford_dragon.stl\"",[70,191,192],{"class":76},");\n",[70,194,195],{"class":72,"line":80},[70,196,198],{"class":197},"sAwPA","\u002F\u002F semantic view into the data\n",[70,200,201,203,206,208,211,214],{"class":72,"line":86},[70,202,168],{"class":107},[70,204,205],{"class":76}," dragon ",[70,207,174],{"class":107},[70,209,210],{"class":76}," dragon_buffer.",[70,212,213],{"class":114},"polygons",[70,215,216],{"class":76},"();\n",[15,218,219],{},"If you already have vertex and face data in memory, construct views directly — no copy required:",[61,221,223],{"className":138,"code":222,"language":140,"meta":66,"style":66},"std::vector\u003Cfloat> flat_points;\nstd::vector\u003Cint> flat_triangles;\n\nauto dragon = tf::make_polygons(\n    tf::make_faces\u003C3>(flat_triangles),\n    tf::make_points\u003C3>(flat_points));\n",[32,224,225,239,251,257,275,295],{"__ignoreMap":66},[70,226,227,230,233,236],{"class":72,"line":73},[70,228,229],{"class":114},"std",[70,231,232],{"class":76},"::vector",[70,234,235],{"class":107},"\u003Cfloat>",[70,237,238],{"class":76}," flat_points;\n",[70,240,241,243,245,248],{"class":72,"line":80},[70,242,229],{"class":114},[70,244,232],{"class":76},[70,246,247],{"class":107},"\u003Cint>",[70,249,250],{"class":76}," flat_triangles;\n",[70,252,253],{"class":72,"line":86},[70,254,256],{"emptyLinePlaceholder":255},true,"\n",[70,258,259,261,263,265,267,269,272],{"class":72,"line":92},[70,260,168],{"class":107},[70,262,205],{"class":76},[70,264,174],{"class":107},[70,266,177],{"class":114},[70,268,180],{"class":76},[70,270,271],{"class":114},"make_polygons",[70,273,274],{"class":76},"(\n",[70,276,277,280,282,285,288,292],{"class":72,"line":98},[70,278,279],{"class":114},"    tf",[70,281,180],{"class":76},[70,283,284],{"class":114},"make_faces",[70,286,287],{"class":76},"\u003C",[70,289,291],{"class":290},"sDLfK","3",[70,293,294],{"class":76},">(flat_triangles),\n",[70,296,297,299,301,304,306,308],{"class":72,"line":104},[70,298,279],{"class":114},[70,300,180],{"class":76},[70,302,303],{"class":114},"make_points",[70,305,287],{"class":76},[70,307,291],{"class":290},[70,309,310],{"class":76},">(flat_points));\n",[15,312,313,314,317],{},"The resulting ",[32,315,316],{},"dragon"," gives you full mesh semantics on the views:",[61,319,321],{"className":138,"code":320,"language":140,"meta":66,"style":66},"auto [id0, id1, id2] = dragon.faces().front();\nauto [pt0, pt1, pt2] = dragon.front();\nauto pt = dragon.points().front();\n\nauto pt3 = pt0 + (pt2 - pt1) \u002F 2;\n",[32,322,323,346,361,381,385],{"__ignoreMap":66},[70,324,325,327,330,332,335,338,341,344],{"class":72,"line":73},[70,326,168],{"class":107},[70,328,329],{"class":76}," [id0, id1, id2] ",[70,331,174],{"class":107},[70,333,334],{"class":76}," dragon.",[70,336,337],{"class":114},"faces",[70,339,340],{"class":76},"().",[70,342,343],{"class":114},"front",[70,345,216],{"class":76},[70,347,348,350,353,355,357,359],{"class":72,"line":80},[70,349,168],{"class":107},[70,351,352],{"class":76}," [pt0, pt1, pt2] ",[70,354,174],{"class":107},[70,356,334],{"class":76},[70,358,343],{"class":114},[70,360,216],{"class":76},[70,362,363,365,368,370,372,375,377,379],{"class":72,"line":86},[70,364,168],{"class":107},[70,366,367],{"class":76}," pt ",[70,369,174],{"class":107},[70,371,334],{"class":76},[70,373,374],{"class":114},"points",[70,376,340],{"class":76},[70,378,343],{"class":114},[70,380,216],{"class":76},[70,382,383],{"class":72,"line":92},[70,384,256],{"emptyLinePlaceholder":255},[70,386,387,389,392,394,397,400,403,406,409,412,415],{"class":72,"line":98},[70,388,168],{"class":107},[70,390,391],{"class":76}," pt3 ",[70,393,174],{"class":107},[70,395,396],{"class":76}," pt0 ",[70,398,399],{"class":107},"+",[70,401,402],{"class":76}," (pt2 ",[70,404,405],{"class":107},"-",[70,407,408],{"class":76}," pt1) ",[70,410,411],{"class":107},"\u002F",[70,413,414],{"class":290}," 2",[70,416,417],{"class":76},";\n",[15,419,420,421,28],{},"For details on working with geometric ranges, see the ",[19,422,425],{"href":423,"rel":424},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fexamples\u002Fmesh-assembly",[126],"Geometry Walkthrough",[53,427,429],{"id":428},"translated-views","Translated views",[15,431,432],{},"To perform a boolean between two copies of the dragon at different positions, we tag a transformation onto the form. The underlying data is shared — no vertices are copied.",[61,434,436],{"className":138,"code":435,"language":140,"meta":66,"style":66},"auto T = tf::make_transformation_from_translation(\n    tf::make_vector(0.f, 1.f, 0.f));\n\nauto translated = dragon | tf::tag(T);\n",[32,437,438,456,490,494],{"__ignoreMap":66},[70,439,440,442,445,447,449,451,454],{"class":72,"line":73},[70,441,168],{"class":107},[70,443,444],{"class":76}," T ",[70,446,174],{"class":107},[70,448,177],{"class":114},[70,450,180],{"class":76},[70,452,453],{"class":114},"make_transformation_from_translation",[70,455,274],{"class":76},[70,457,458,460,462,465,467,470,473,476,479,481,483,485,487],{"class":72,"line":80},[70,459,279],{"class":114},[70,461,180],{"class":76},[70,463,464],{"class":114},"make_vector",[70,466,186],{"class":76},[70,468,469],{"class":290},"0.",[70,471,472],{"class":107},"f",[70,474,475],{"class":76},", ",[70,477,478],{"class":290},"1.",[70,480,472],{"class":107},[70,482,475],{"class":76},[70,484,469],{"class":290},[70,486,472],{"class":107},[70,488,489],{"class":76},"));\n",[70,491,492],{"class":72,"line":86},[70,493,256],{"emptyLinePlaceholder":255},[70,495,496,498,501,503,505,508,510,512,515],{"class":72,"line":92},[70,497,168],{"class":107},[70,499,500],{"class":76}," translated ",[70,502,174],{"class":107},[70,504,205],{"class":76},[70,506,507],{"class":107},"|",[70,509,177],{"class":114},[70,511,180],{"class":76},[70,513,514],{"class":114},"tag",[70,516,517],{"class":76},"(T);\n",[15,519,520,523,524,526,527,532,533,535,536,28],{},[32,521,522],{},"translated"," retains the full API of ",[32,525,316],{}," through the ",[19,528,531],{"href":529,"rel":530},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcore#policies",[126],"policy system",". Any operation that accepts ",[32,534,316],{}," also accepts ",[32,537,522],{},[53,539,541],{"id":540},"boolean-operations","Boolean operations",[15,543,544,545,28],{},"With the two forms ready, a boolean is a single call. ",[19,546,549],{"href":547,"rel":548},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcut#boolean-operations",[126],"Full documentation",[551,552,554],"h3",{"id":553},"union","Union",[61,556,558],{"className":138,"code":557,"language":140,"meta":66,"style":66},"auto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::merge);\n",[32,559,560,578],{"__ignoreMap":66},[70,561,562,564,567,569,571,573,576],{"class":72,"line":73},[70,563,168],{"class":107},[70,565,566],{"class":76}," [result_buffer, labels, face_labels] ",[70,568,174],{"class":107},[70,570,177],{"class":114},[70,572,180],{"class":76},[70,574,575],{"class":114},"make_boolean",[70,577,274],{"class":76},[70,579,580,583,586,588,591],{"class":72,"line":80},[70,581,582],{"class":76},"    dragon, translated, ",[70,584,585],{"class":114},"tf",[70,587,180],{"class":76},[70,589,590],{"class":114},"boolean_op",[70,592,593],{"class":76},"::merge);\n",[551,595,597],{"id":596},"difference","Difference",[61,599,601],{"className":138,"code":600,"language":140,"meta":66,"style":66},"auto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::left_difference);\n",[32,602,603,619],{"__ignoreMap":66},[70,604,605,607,609,611,613,615,617],{"class":72,"line":73},[70,606,168],{"class":107},[70,608,566],{"class":76},[70,610,174],{"class":107},[70,612,177],{"class":114},[70,614,180],{"class":76},[70,616,575],{"class":114},[70,618,274],{"class":76},[70,620,621,623,625,627,629],{"class":72,"line":80},[70,622,582],{"class":76},[70,624,585],{"class":114},[70,626,180],{"class":76},[70,628,590],{"class":114},[70,630,631],{"class":76},"::left_difference);\n",[15,633,634,637,638,641],{},[32,635,636],{},"left_difference"," keeps the left mesh minus the right. ",[32,639,640],{},"right_difference"," does the opposite.",[551,643,645],{"id":644},"intersection","Intersection",[61,647,649],{"className":138,"code":648,"language":140,"meta":66,"style":66},"auto [result_buffer, labels, face_labels] = tf::make_boolean(\n    dragon, translated, tf::boolean_op::intersection);\n",[32,650,651,667],{"__ignoreMap":66},[70,652,653,655,657,659,661,663,665],{"class":72,"line":73},[70,654,168],{"class":107},[70,656,566],{"class":76},[70,658,174],{"class":107},[70,660,177],{"class":114},[70,662,180],{"class":76},[70,664,575],{"class":114},[70,666,274],{"class":76},[70,668,669,671,673,675,677],{"class":72,"line":80},[70,670,582],{"class":76},[70,672,585],{"class":114},[70,674,180],{"class":76},[70,676,590],{"class":114},[70,678,679],{"class":76},"::intersection);\n",[551,681,683],{"id":682},"working-with-results","Working with results",[15,685,686,687,690,691,694,695,698,699,702,703,706],{},"Every boolean returns three values. ",[32,688,689],{},"result_buffer"," holds the output mesh. ",[32,692,693],{},"labels"," marks each output face — ",[32,696,697],{},"0"," for faces originating from the first input, ",[32,700,701],{},"1"," from the second. ",[32,704,705],{},"face_labels"," maps each output face back to the index of its source face in the input, enabling attribute transfer.",[15,708,709],{},"To split the result into separate meshes by label:",[61,711,713],{"className":138,"code":712,"language":140,"meta":66,"style":66},"auto [components, component_labels] =\n    tf::split_into_components(result_buffer.polygons(), labels);\n",[32,714,715,725],{"__ignoreMap":66},[70,716,717,719,722],{"class":72,"line":73},[70,718,168],{"class":107},[70,720,721],{"class":76}," [components, component_labels] ",[70,723,724],{"class":107},"=\n",[70,726,727,729,731,734,737,739],{"class":72,"line":80},[70,728,279],{"class":114},[70,730,180],{"class":76},[70,732,733],{"class":114},"split_into_components",[70,735,736],{"class":76},"(result_buffer.",[70,738,213],{"class":114},[70,740,741],{"class":76},"(), labels);\n",[15,743,744],{},"Write the two halves to file:",[61,746,748],{"className":138,"code":747,"language":140,"meta":66,"style":66},"tf::write_stl(components[0].polygons(), \"first.stl\");\ntf::write_obj(components[1].polygons(), \"second.obj\");\n",[32,749,750,777],{"__ignoreMap":66},[70,751,752,754,756,759,762,764,767,769,772,775],{"class":72,"line":73},[70,753,585],{"class":114},[70,755,180],{"class":76},[70,757,758],{"class":114},"write_stl",[70,760,761],{"class":76},"(components[",[70,763,697],{"class":290},[70,765,766],{"class":76},"].",[70,768,213],{"class":114},[70,770,771],{"class":76},"(), ",[70,773,774],{"class":150},"\"first.stl\"",[70,776,192],{"class":76},[70,778,779,781,783,786,788,790,792,794,796,799],{"class":72,"line":80},[70,780,585],{"class":114},[70,782,180],{"class":76},[70,784,785],{"class":114},"write_obj",[70,787,761],{"class":76},[70,789,701],{"class":290},[70,791,766],{"class":76},[70,793,213],{"class":114},[70,795,771],{"class":76},[70,797,798],{"class":150},"\"second.obj\"",[70,800,192],{"class":76},[53,802,804],{"id":803},"precomputed-structures","Precomputed structures",[15,806,807,808,475,815,822,823,830],{},"When running multiple booleans on the same geometry — for example, subtracting a tool at different positions along a path — rebuilding spatial and topological structures every time is wasteful. Build the ",[19,809,812],{"href":810,"rel":811},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fspatial#tftree",[126],[32,813,814],{},"aabb_tree",[19,816,819],{"href":817,"rel":818},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Ftopology#face-membership",[126],[32,820,821],{},"face_membership",", and ",[19,824,827],{"href":825,"rel":826},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Ftopology#manifold-edge-link",[126],[32,828,829],{},"manifold_edge_link"," once, then tag them onto the form:",[61,832,834],{"className":138,"code":833,"language":140,"meta":66,"style":66},"tf::aabb_tree\u003Cint, float, 3> tree;\ntree.build(dragon, tf::config_tree(4, 4));\n\nauto fm = tf::make_face_membership(dragon);\nauto mel = tf::make_manifold_edge_link(dragon);\n\nauto tagged = dragon\n    | tf::tag(tree)\n    | tf::tag(fm)\n    | tf::tag(mel);\n",[32,835,836,861,890,894,913,931,935,948,963,977],{"__ignoreMap":66},[70,837,838,840,843,846,848,851,853,855,858],{"class":72,"line":73},[70,839,585],{"class":114},[70,841,842],{"class":76},"::aabb_tree",[70,844,845],{"class":107},"\u003Cint",[70,847,475],{"class":76},[70,849,850],{"class":107},"float",[70,852,475],{"class":76},[70,854,291],{"class":290},[70,856,857],{"class":107},">",[70,859,860],{"class":76}," tree;\n",[70,862,863,866,869,872,874,876,879,881,884,886,888],{"class":72,"line":80},[70,864,865],{"class":76},"tree.",[70,867,868],{"class":114},"build",[70,870,871],{"class":76},"(dragon, ",[70,873,585],{"class":114},[70,875,180],{"class":76},[70,877,878],{"class":114},"config_tree",[70,880,186],{"class":76},[70,882,883],{"class":290},"4",[70,885,475],{"class":76},[70,887,883],{"class":290},[70,889,489],{"class":76},[70,891,892],{"class":72,"line":86},[70,893,256],{"emptyLinePlaceholder":255},[70,895,896,898,901,903,905,907,910],{"class":72,"line":92},[70,897,168],{"class":107},[70,899,900],{"class":76}," fm ",[70,902,174],{"class":107},[70,904,177],{"class":114},[70,906,180],{"class":76},[70,908,909],{"class":114},"make_face_membership",[70,911,912],{"class":76},"(dragon);\n",[70,914,915,917,920,922,924,926,929],{"class":72,"line":98},[70,916,168],{"class":107},[70,918,919],{"class":76}," mel ",[70,921,174],{"class":107},[70,923,177],{"class":114},[70,925,180],{"class":76},[70,927,928],{"class":114},"make_manifold_edge_link",[70,930,912],{"class":76},[70,932,933],{"class":72,"line":104},[70,934,256],{"emptyLinePlaceholder":255},[70,936,938,940,943,945],{"class":72,"line":937},7,[70,939,168],{"class":107},[70,941,942],{"class":76}," tagged ",[70,944,174],{"class":107},[70,946,947],{"class":76}," dragon\n",[70,949,951,954,956,958,960],{"class":72,"line":950},8,[70,952,953],{"class":107},"    |",[70,955,177],{"class":114},[70,957,180],{"class":76},[70,959,514],{"class":114},[70,961,962],{"class":76},"(tree)\n",[70,964,966,968,970,972,974],{"class":72,"line":965},9,[70,967,953],{"class":107},[70,969,177],{"class":114},[70,971,180],{"class":76},[70,973,514],{"class":114},[70,975,976],{"class":76},"(fm)\n",[70,978,980,982,984,986,988],{"class":72,"line":979},10,[70,981,953],{"class":107},[70,983,177],{"class":114},[70,985,180],{"class":76},[70,987,514],{"class":114},[70,989,990],{"class":76},"(mel);\n",[15,992,993],{},"Now boolean at different positions. The structures are reused — only the transformation changes:",[61,995,997],{"className":138,"code":996,"language":140,"meta":66,"style":66},"for (float t = 0.1f; t \u003C= 1.0f; t += 0.1f) {\n    auto T = tf::make_transformation_from_translation(\n        tf::make_vector(0.f, t, 0.f));\n\n    auto [result, labels, face_labels] = tf::make_boolean(\n        tagged,\n        tagged | tf::tag(T),\n        tf::boolean_op::left_difference);\n}\n",[32,998,999,1042,1059,1083,1087,1104,1109,1125,1135],{"__ignoreMap":66},[70,1000,1001,1004,1007,1009,1012,1014,1017,1019,1022,1025,1028,1030,1032,1035,1037,1039],{"class":72,"line":73},[70,1002,1003],{"class":107},"for",[70,1005,1006],{"class":76}," (",[70,1008,850],{"class":107},[70,1010,1011],{"class":76}," t ",[70,1013,174],{"class":107},[70,1015,1016],{"class":290}," 0.1",[70,1018,472],{"class":107},[70,1020,1021],{"class":76},"; t ",[70,1023,1024],{"class":107},"\u003C=",[70,1026,1027],{"class":290}," 1.0",[70,1029,472],{"class":107},[70,1031,1021],{"class":76},[70,1033,1034],{"class":107},"+=",[70,1036,1016],{"class":290},[70,1038,472],{"class":107},[70,1040,1041],{"class":76},") {\n",[70,1043,1044,1047,1049,1051,1053,1055,1057],{"class":72,"line":80},[70,1045,1046],{"class":107},"    auto",[70,1048,444],{"class":76},[70,1050,174],{"class":107},[70,1052,177],{"class":114},[70,1054,180],{"class":76},[70,1056,453],{"class":114},[70,1058,274],{"class":76},[70,1060,1061,1064,1066,1068,1070,1072,1074,1077,1079,1081],{"class":72,"line":86},[70,1062,1063],{"class":114},"        tf",[70,1065,180],{"class":76},[70,1067,464],{"class":114},[70,1069,186],{"class":76},[70,1071,469],{"class":290},[70,1073,472],{"class":107},[70,1075,1076],{"class":76},", t, ",[70,1078,469],{"class":290},[70,1080,472],{"class":107},[70,1082,489],{"class":76},[70,1084,1085],{"class":72,"line":92},[70,1086,256],{"emptyLinePlaceholder":255},[70,1088,1089,1091,1094,1096,1098,1100,1102],{"class":72,"line":98},[70,1090,1046],{"class":107},[70,1092,1093],{"class":76}," [result, labels, face_labels] ",[70,1095,174],{"class":107},[70,1097,177],{"class":114},[70,1099,180],{"class":76},[70,1101,575],{"class":114},[70,1103,274],{"class":76},[70,1105,1106],{"class":72,"line":104},[70,1107,1108],{"class":76},"        tagged,\n",[70,1110,1111,1114,1116,1118,1120,1122],{"class":72,"line":937},[70,1112,1113],{"class":76},"        tagged ",[70,1115,507],{"class":107},[70,1117,177],{"class":114},[70,1119,180],{"class":76},[70,1121,514],{"class":114},[70,1123,1124],{"class":76},"(T),\n",[70,1126,1127,1129,1131,1133],{"class":72,"line":950},[70,1128,1063],{"class":114},[70,1130,180],{"class":76},[70,1132,590],{"class":114},[70,1134,631],{"class":76},[70,1136,1137],{"class":72,"line":965},[70,1138,1139],{"class":76},"}\n",[15,1141,1142],{},"10 booleans on 2×500K polygon meshes in under 140ms total. Spatial and topological structures computed once.",[53,1144,1146],{"id":1145},"extracting-intersection-curves","Extracting intersection curves",[15,1148,1149,1150,1153],{},"Any boolean can also return the intersection boundary as polylines embedded on the surface. Pass ",[32,1151,1152],{},"tf::return_curves"," as an additional argument:",[61,1155,1157],{"className":138,"code":1156,"language":140,"meta":66,"style":66},"auto [result, labels, face_labels, curves] = tf::make_boolean(\n    dragon, translated,\n    tf::boolean_op::left_difference,\n    tf::return_curves);\n\nauto paths = curves.paths_buffer();\nauto points = curves.points_buffer();\n",[32,1158,1159,1176,1181,1192,1199,1203,1220],{"__ignoreMap":66},[70,1160,1161,1163,1166,1168,1170,1172,1174],{"class":72,"line":73},[70,1162,168],{"class":107},[70,1164,1165],{"class":76}," [result, labels, face_labels, curves] ",[70,1167,174],{"class":107},[70,1169,177],{"class":114},[70,1171,180],{"class":76},[70,1173,575],{"class":114},[70,1175,274],{"class":76},[70,1177,1178],{"class":72,"line":80},[70,1179,1180],{"class":76},"    dragon, translated,\n",[70,1182,1183,1185,1187,1189],{"class":72,"line":86},[70,1184,279],{"class":114},[70,1186,180],{"class":76},[70,1188,590],{"class":114},[70,1190,1191],{"class":76},"::left_difference,\n",[70,1193,1194,1196],{"class":72,"line":92},[70,1195,279],{"class":114},[70,1197,1198],{"class":76},"::return_curves);\n",[70,1200,1201],{"class":72,"line":98},[70,1202,256],{"emptyLinePlaceholder":255},[70,1204,1205,1207,1210,1212,1215,1218],{"class":72,"line":104},[70,1206,168],{"class":107},[70,1208,1209],{"class":76}," paths ",[70,1211,174],{"class":107},[70,1213,1214],{"class":76}," curves.",[70,1216,1217],{"class":114},"paths_buffer",[70,1219,216],{"class":76},[70,1221,1222,1224,1227,1229,1231,1234],{"class":72,"line":937},[70,1223,168],{"class":107},[70,1225,1226],{"class":76}," points ",[70,1228,174],{"class":107},[70,1230,1214],{"class":76},[70,1232,1233],{"class":114},"points_buffer",[70,1235,216],{"class":76},[15,1237,1238,1239,1244],{},"If only the ",[19,1240,1243],{"href":1241,"rel":1242},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fintersect#intersection-curves",[126],"intersection curves"," are needed without the boolean itself:",[61,1246,1248],{"className":138,"code":1247,"language":140,"meta":66,"style":66},"auto curves_buffer = tf::make_intersection_curves(dragon, translated);\n",[32,1249,1250],{"__ignoreMap":66},[70,1251,1252,1254,1257,1259,1261,1263,1266],{"class":72,"line":73},[70,1253,168],{"class":107},[70,1255,1256],{"class":76}," curves_buffer ",[70,1258,174],{"class":107},[70,1260,177],{"class":114},[70,1262,180],{"class":76},[70,1264,1265],{"class":114},"make_intersection_curves",[70,1267,1268],{"class":76},"(dragon, translated);\n",[53,1270,1272],{"id":1271},"higher-precision","Higher precision",[15,1274,1275,1276,1278,1279,1282,1283,1286,1287,1290,1291,1293],{},"By default, ",[32,1277,34],{}," uses ",[32,1280,1281],{},"int32"," coordinates with ",[32,1284,1285],{},"int64"," intermediates and ",[32,1288,1289],{},"int128"," predicates. For meshes spanning very large coordinate ranges, switch to ",[32,1292,1285],{}," base precision:",[61,1295,1297],{"className":138,"code":1296,"language":140,"meta":66,"style":66},"auto [result, labels, face_labels] =\n    tf::make_boolean\u003Ctf::exact::int64>(\n        dragon, translated,\n        tf::boolean_op::left_difference);\n",[32,1298,1299,1307,1331,1336],{"__ignoreMap":66},[70,1300,1301,1303,1305],{"class":72,"line":73},[70,1302,168],{"class":107},[70,1304,1093],{"class":76},[70,1306,724],{"class":107},[70,1308,1309,1311,1313,1315,1317,1319,1321,1324,1326,1328],{"class":72,"line":80},[70,1310,279],{"class":114},[70,1312,180],{"class":76},[70,1314,575],{"class":114},[70,1316,287],{"class":76},[70,1318,585],{"class":114},[70,1320,180],{"class":76},[70,1322,1323],{"class":114},"exact",[70,1325,180],{"class":76},[70,1327,1285],{"class":114},[70,1329,1330],{"class":76},">(\n",[70,1332,1333],{"class":72,"line":86},[70,1334,1335],{"class":76},"        dragon, translated,\n",[70,1337,1338,1340,1342,1344],{"class":72,"line":92},[70,1339,1063],{"class":114},[70,1341,180],{"class":76},[70,1343,590],{"class":114},[70,1345,631],{"class":76},[15,1347,1348],{},"Same API. Wider coordinate range.",[53,1350,1352],{"id":1351},"performance-and-robustness","Performance and robustness",[15,1354,1355],{},"All operations run at interactive speed on million-polygon meshes.",[1357,1358],"headline-numbers",{":items":1359},"[{\"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\"}]",[15,1361,1362,1367,1368,1367,1372,1367,1376],{},[70,1363,1366],{"className":1364},[1365],"tag-pill","Apple M4 Max"," ",[70,1369,1371],{"className":1370},[1365],"16 threads",[70,1373,1375],{"className":1374},[1365],"Clang -O3",[70,1377,1379],{"className":1378},[1365],"mimalloc",[15,1381,1382,1383,1385],{},"Real meshes are not ideal manifolds. ",[32,1384,34],{}," handles them directly — no preprocessing, no manifold requirement.",[1387,1388],"data-table",{":headers":1389,":highlight":697,":rows":1390},"[\"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\"]]",[15,1392,1393,1395],{},[32,1394,34],{}," provides a robust alternative to CGAL, MeshLib, and libigl for fast and exact mesh boolean operations.",[15,1397,1398,1403,1404],{},[19,1399,1402],{"href":1400,"rel":1401},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fbenchmarks",[126],"Full benchmarks and methodology"," · ",[19,1405,1407],{"href":1406},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[1409,1410],"cite-as",{"author":1411,"title":6},"Sajovic, {\\v{Z}}iga",[1413,1414,1415],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}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 .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":66,"searchDepth":80,"depth":80,"links":1417},[1418,1419,1420,1421,1427,1428,1429,1430],{"id":55,"depth":80,"text":56},{"id":131,"depth":80,"text":132},{"id":428,"depth":80,"text":429},{"id":540,"depth":80,"text":541,"children":1422},[1423,1424,1425,1426],{"id":553,"depth":86,"text":554},{"id":596,"depth":86,"text":597},{"id":644,"depth":86,"text":645},{"id":682,"depth":86,"text":683},{"id":803,"depth":80,"text":804},{"id":1145,"depth":80,"text":1146},{"id":1271,"depth":80,"text":1272},{"id":1351,"depth":80,"text":1352},"2026-04-30","Learn how to perform fast mesh boolean operations in C++. Union, intersection, and difference at interactive speed on million-polygon meshes. Exact arithmetic, with support for non-manifold topology and coplanar primitives.","md",[1435,1436,1437,1438,1439,1440],"C++ mesh boolean","mesh boolean C++","CGAL alternative C++","fast boolean C++","mesh CSG C++","mesh boolean library C++",{},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp",{"title":6,"description":1432},"tutorials\u002Ffast-mesh-booleans-in-cpp",[1446,1447,1448],"c++","booleans","tutorial","F-ONTlzJ1bbmcEGZpFhHqudXiY2Ws7AvAsu8YIlheEs",{"id":1451,"title":1452,"author":7,"body":1453,"date":1431,"description":2745,"extension":1433,"keywords":2746,"meta":2755,"navigation":255,"path":26,"published":255,"seo":2756,"stem":2757,"tags":2758,"__hash__":2761},"tutorials\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript.md","Fast Mesh Booleans in JavaScript",{"type":9,"value":1454,"toc":2730},[1455,1464,1469,1472,1474,1476,1481,1483,1501,1513,1515,1544,1555,1630,1633,1770,1781,1785,1794,1825,1828,1900,1903,1905,1912,1915,1979,1981,2022,2024,2060,2062,2098,2109,2111,2124,2126,2156,2159,2219,2221,2224,2280,2283,2394,2396,2398,2403,2516,2522,2543,2547,2552,2684,2686,2688,2691,2707,2711,2713,2718,2725,2727],[12,1456,1457],{},[15,1458,17,1459,23,1462,28],{},[19,1460,1461],{"href":1442},"C++",[19,1463,22],{"href":21},[15,1465,1466,1468],{},[32,1467,34],{}," is the fastest JavaScript mesh boolean library for real-world meshes — performing exact boolean union, intersection, and difference at interactive speed. WebAssembly-powered, it runs in Node.js and the browser with the same API. This tutorial covers the basics: loading meshes, running booleans, and working with results. It then shows how to precompute structures and use shallow copies to avoid rebuilding them, enabling boolean operations on moving geometry at real-time rates.",[37,1470],{":buttons":1471},"[{\"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\u002Fts\u002Fgetting-started\u002Finstallation\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fboolean\",\"icon\":\"i-lucide-play\"}]",[15,1473,42],{},[44,1475],{":images":46},[15,1477,1478,1479,28],{},"Each boolean under 27ms on 2×500K polygons via WebAssembly. Let's begin by installing ",[32,1480,34],{},[53,1482,56],{"id":55},[61,1484,1488],{"className":1485,"code":1486,"language":1487,"meta":66,"style":66},"language-bash shiki shiki-themes github-dark","npm install @polydera\u002Ftrueform\n","bash",[32,1489,1490],{"__ignoreMap":66},[70,1491,1492,1495,1498],{"class":72,"line":73},[70,1493,1494],{"class":114},"npm",[70,1496,1497],{"class":150}," install",[70,1499,1500],{"class":150}," @polydera\u002Ftrueform\n",[15,1502,1503,1504,1507,1508,1512],{},"The WebAssembly binary loads automatically on first use. In the browser, ",[32,1505,1506],{},"SharedArrayBuffer"," headers are required for multithreading — see the ",[19,1509,127],{"href":1510,"rel":1511},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fgetting-started\u002Finstallation",[126]," for details.",[53,1514,132],{"id":131},[61,1516,1520],{"className":1517,"code":1518,"language":1519,"meta":66,"style":66},"language-typescript shiki shiki-themes github-dark","import * as tf from \"@polydera\u002Ftrueform\";\n","typescript",[32,1521,1522],{"__ignoreMap":66},[70,1523,1524,1527,1530,1533,1536,1539,1542],{"class":72,"line":73},[70,1525,1526],{"class":107},"import",[70,1528,1529],{"class":290}," *",[70,1531,1532],{"class":107}," as",[70,1534,1535],{"class":76}," tf ",[70,1537,1538],{"class":107},"from",[70,1540,1541],{"class":150}," \"@polydera\u002Ftrueform\"",[70,1543,417],{"class":76},[15,1545,1546,1547,1550,1551,1554],{},"Load from a file — in the browser via ",[32,1548,1549],{},"fetch",", in Node.js via ",[32,1552,1553],{},"fs",":",[61,1556,1558],{"className":1517,"code":1557,"language":1519,"meta":66,"style":66},"const response = await fetch(\"stanford_dragon.stl\");\nconst bytes = new Uint8Array(await response.arrayBuffer());\nconst dragon = tf.readStl(bytes);\n",[32,1559,1560,1583,1612],{"__ignoreMap":66},[70,1561,1562,1565,1568,1571,1574,1577,1579,1581],{"class":72,"line":73},[70,1563,1564],{"class":107},"const",[70,1566,1567],{"class":290}," response",[70,1569,1570],{"class":107}," =",[70,1572,1573],{"class":107}," await",[70,1575,1576],{"class":114}," fetch",[70,1578,186],{"class":76},[70,1580,189],{"class":150},[70,1582,192],{"class":76},[70,1584,1585,1587,1590,1592,1595,1598,1600,1603,1606,1609],{"class":72,"line":80},[70,1586,1564],{"class":107},[70,1588,1589],{"class":290}," bytes",[70,1591,1570],{"class":107},[70,1593,1594],{"class":107}," new",[70,1596,1597],{"class":114}," Uint8Array",[70,1599,186],{"class":76},[70,1601,1602],{"class":107},"await",[70,1604,1605],{"class":76}," response.",[70,1607,1608],{"class":114},"arrayBuffer",[70,1610,1611],{"class":76},"());\n",[70,1613,1614,1616,1619,1621,1624,1627],{"class":72,"line":86},[70,1615,1564],{"class":107},[70,1617,1618],{"class":290}," dragon",[70,1620,1570],{"class":107},[70,1622,1623],{"class":76}," tf.",[70,1625,1626],{"class":114},"readStl",[70,1628,1629],{"class":76},"(bytes);\n",[15,1631,1632],{},"Or construct a mesh from flat arrays:",[61,1634,1636],{"className":1517,"code":1635,"language":1519,"meta":66,"style":66},"const faces = new Int32Array([0, 1, 2, 0, 2, 3]);\nconst points = new Float32Array([\n    0,0,0, 1,0,0, 0,1,0, 1,1,0]);\n\nconst dragon = tf.mesh(faces, points);\n",[32,1637,1638,1681,1698,1750,1754],{"__ignoreMap":66},[70,1639,1640,1642,1645,1647,1649,1652,1655,1657,1659,1661,1663,1666,1668,1670,1672,1674,1676,1678],{"class":72,"line":73},[70,1641,1564],{"class":107},[70,1643,1644],{"class":290}," faces",[70,1646,1570],{"class":107},[70,1648,1594],{"class":107},[70,1650,1651],{"class":114}," Int32Array",[70,1653,1654],{"class":76},"([",[70,1656,697],{"class":290},[70,1658,475],{"class":76},[70,1660,701],{"class":290},[70,1662,475],{"class":76},[70,1664,1665],{"class":290},"2",[70,1667,475],{"class":76},[70,1669,697],{"class":290},[70,1671,475],{"class":76},[70,1673,1665],{"class":290},[70,1675,475],{"class":76},[70,1677,291],{"class":290},[70,1679,1680],{"class":76},"]);\n",[70,1682,1683,1685,1688,1690,1692,1695],{"class":72,"line":80},[70,1684,1564],{"class":107},[70,1686,1687],{"class":290}," points",[70,1689,1570],{"class":107},[70,1691,1594],{"class":107},[70,1693,1694],{"class":114}," Float32Array",[70,1696,1697],{"class":76},"([\n",[70,1699,1700,1703,1706,1708,1710,1712,1714,1716,1718,1720,1722,1724,1726,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748],{"class":72,"line":86},[70,1701,1702],{"class":290},"    0",[70,1704,1705],{"class":76},",",[70,1707,697],{"class":290},[70,1709,1705],{"class":76},[70,1711,697],{"class":290},[70,1713,475],{"class":76},[70,1715,701],{"class":290},[70,1717,1705],{"class":76},[70,1719,697],{"class":290},[70,1721,1705],{"class":76},[70,1723,697],{"class":290},[70,1725,475],{"class":76},[70,1727,697],{"class":290},[70,1729,1705],{"class":76},[70,1731,701],{"class":290},[70,1733,1705],{"class":76},[70,1735,697],{"class":290},[70,1737,475],{"class":76},[70,1739,701],{"class":290},[70,1741,1705],{"class":76},[70,1743,701],{"class":290},[70,1745,1705],{"class":76},[70,1747,697],{"class":290},[70,1749,1680],{"class":76},[70,1751,1752],{"class":72,"line":92},[70,1753,256],{"emptyLinePlaceholder":255},[70,1755,1756,1758,1760,1762,1764,1767],{"class":72,"line":98},[70,1757,1564],{"class":107},[70,1759,1618],{"class":290},[70,1761,1570],{"class":107},[70,1763,1623],{"class":76},[70,1765,1766],{"class":114},"mesh",[70,1768,1769],{"class":76},"(faces, points);\n",[15,1771,1772,1773,1780],{},"The ",[19,1774,1777],{"href":1775,"rel":1776},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fcore#mesh",[126],[32,1778,1779],{},"Mesh"," form wraps your data with geometric semantics — spatial queries, topology, and boolean operations all work directly on it.",[53,1782,1784],{"id":1783},"transformations","Transformations",[15,1786,1787,1788,1793],{},"To perform a boolean between two copies of the dragon at different positions, set a ",[19,1789,1792],{"href":1790,"rel":1791},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fspatial#transformations-on-forms",[126],"transformation"," on the mesh. The underlying data is shared — no vertices are copied.",[61,1795,1797],{"className":1517,"code":1796,"language":1519,"meta":66,"style":66},"dragon.transformation = tf.makeTranslation(0, 1, 0);\n",[32,1798,1799],{"__ignoreMap":66},[70,1800,1801,1804,1806,1808,1811,1813,1815,1817,1819,1821,1823],{"class":72,"line":73},[70,1802,1803],{"class":76},"dragon.transformation ",[70,1805,174],{"class":107},[70,1807,1623],{"class":76},[70,1809,1810],{"class":114},"makeTranslation",[70,1812,186],{"class":76},[70,1814,697],{"class":290},[70,1816,475],{"class":76},[70,1818,701],{"class":290},[70,1820,475],{"class":76},[70,1822,697],{"class":290},[70,1824,192],{"class":76},[15,1826,1827],{},"Rotations and combined transforms:",[61,1829,1831],{"className":1517,"code":1830,"language":1519,"meta":66,"style":66},"dragon.transformation = tf.makeRotation(90, \"z\");\ndragon.transformation = tf.makeRotation(45, [1, 0, 0], [0, 0, 5]);\n",[32,1832,1833,1856],{"__ignoreMap":66},[70,1834,1835,1837,1839,1841,1844,1846,1849,1851,1854],{"class":72,"line":73},[70,1836,1803],{"class":76},[70,1838,174],{"class":107},[70,1840,1623],{"class":76},[70,1842,1843],{"class":114},"makeRotation",[70,1845,186],{"class":76},[70,1847,1848],{"class":290},"90",[70,1850,475],{"class":76},[70,1852,1853],{"class":150},"\"z\"",[70,1855,192],{"class":76},[70,1857,1858,1860,1862,1864,1866,1868,1871,1874,1876,1878,1880,1882,1884,1887,1889,1891,1893,1895,1898],{"class":72,"line":80},[70,1859,1803],{"class":76},[70,1861,174],{"class":107},[70,1863,1623],{"class":76},[70,1865,1843],{"class":114},[70,1867,186],{"class":76},[70,1869,1870],{"class":290},"45",[70,1872,1873],{"class":76},", [",[70,1875,701],{"class":290},[70,1877,475],{"class":76},[70,1879,697],{"class":290},[70,1881,475],{"class":76},[70,1883,697],{"class":290},[70,1885,1886],{"class":76},"], [",[70,1888,697],{"class":290},[70,1890,475],{"class":76},[70,1892,697],{"class":290},[70,1894,475],{"class":76},[70,1896,1897],{"class":290},"5",[70,1899,1680],{"class":76},[15,1901,1902],{},"The spatial tree and topology structures stay valid across transformations — they are applied on-the-fly during queries.",[53,1904,541],{"id":540},[15,1906,1907,1908,28],{},"With two forms ready, a boolean is a single call. ",[19,1909,549],{"href":1910,"rel":1911},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fcut#boolean-operations",[126],[15,1913,1914],{},"To boolean two copies at different positions, create a shallow copy. It shares all underlying data and structures but carries its own transformation:",[61,1916,1918],{"className":1517,"code":1917,"language":1519,"meta":66,"style":66},"const dragon = tf.readStl(bytes);\n\nconst translated = dragon.shallowCopy();\ntranslated.transformation = tf.makeTranslation(0, 1, 0);\n",[32,1919,1920,1934,1938,1954],{"__ignoreMap":66},[70,1921,1922,1924,1926,1928,1930,1932],{"class":72,"line":73},[70,1923,1564],{"class":107},[70,1925,1618],{"class":290},[70,1927,1570],{"class":107},[70,1929,1623],{"class":76},[70,1931,1626],{"class":114},[70,1933,1629],{"class":76},[70,1935,1936],{"class":72,"line":80},[70,1937,256],{"emptyLinePlaceholder":255},[70,1939,1940,1942,1945,1947,1949,1952],{"class":72,"line":86},[70,1941,1564],{"class":107},[70,1943,1944],{"class":290}," translated",[70,1946,1570],{"class":107},[70,1948,334],{"class":76},[70,1950,1951],{"class":114},"shallowCopy",[70,1953,216],{"class":76},[70,1955,1956,1959,1961,1963,1965,1967,1969,1971,1973,1975,1977],{"class":72,"line":92},[70,1957,1958],{"class":76},"translated.transformation ",[70,1960,174],{"class":107},[70,1962,1623],{"class":76},[70,1964,1810],{"class":114},[70,1966,186],{"class":76},[70,1968,697],{"class":290},[70,1970,475],{"class":76},[70,1972,701],{"class":290},[70,1974,475],{"class":76},[70,1976,697],{"class":290},[70,1978,192],{"class":76},[551,1980,554],{"id":553},[61,1982,1984],{"className":1517,"code":1983,"language":1519,"meta":66,"style":66},"const { mesh, labels, faceLabels } =\n    await tf.async.booleanUnion(dragon, translated);\n",[32,1985,1986,2009],{"__ignoreMap":66},[70,1987,1988,1990,1993,1995,1997,1999,2001,2004,2007],{"class":72,"line":73},[70,1989,1564],{"class":107},[70,1991,1992],{"class":76}," { ",[70,1994,1766],{"class":290},[70,1996,475],{"class":76},[70,1998,693],{"class":290},[70,2000,475],{"class":76},[70,2002,2003],{"class":290},"faceLabels",[70,2005,2006],{"class":76}," } ",[70,2008,724],{"class":107},[70,2010,2011,2014,2017,2020],{"class":72,"line":80},[70,2012,2013],{"class":107},"    await",[70,2015,2016],{"class":76}," tf.async.",[70,2018,2019],{"class":114},"booleanUnion",[70,2021,1268],{"class":76},[551,2023,597],{"id":596},[61,2025,2027],{"className":1517,"code":2026,"language":1519,"meta":66,"style":66},"const { mesh, labels, faceLabels } =\n    await tf.async.booleanDifference(dragon, translated);\n",[32,2028,2029,2049],{"__ignoreMap":66},[70,2030,2031,2033,2035,2037,2039,2041,2043,2045,2047],{"class":72,"line":73},[70,2032,1564],{"class":107},[70,2034,1992],{"class":76},[70,2036,1766],{"class":290},[70,2038,475],{"class":76},[70,2040,693],{"class":290},[70,2042,475],{"class":76},[70,2044,2003],{"class":290},[70,2046,2006],{"class":76},[70,2048,724],{"class":107},[70,2050,2051,2053,2055,2058],{"class":72,"line":80},[70,2052,2013],{"class":107},[70,2054,2016],{"class":76},[70,2056,2057],{"class":114},"booleanDifference",[70,2059,1268],{"class":76},[551,2061,645],{"id":644},[61,2063,2065],{"className":1517,"code":2064,"language":1519,"meta":66,"style":66},"const { mesh, labels, faceLabels } =\n    await tf.async.booleanIntersection(dragon, translated);\n",[32,2066,2067,2087],{"__ignoreMap":66},[70,2068,2069,2071,2073,2075,2077,2079,2081,2083,2085],{"class":72,"line":73},[70,2070,1564],{"class":107},[70,2072,1992],{"class":76},[70,2074,1766],{"class":290},[70,2076,475],{"class":76},[70,2078,693],{"class":290},[70,2080,475],{"class":76},[70,2082,2003],{"class":290},[70,2084,2006],{"class":76},[70,2086,724],{"class":107},[70,2088,2089,2091,2093,2096],{"class":72,"line":80},[70,2090,2013],{"class":107},[70,2092,2016],{"class":76},[70,2094,2095],{"class":114},"booleanIntersection",[70,2097,1268],{"class":76},[15,2099,2100,2101,2104,2105,2108],{},"All heavy operations have async variants under ",[32,2102,2103],{},"tf.async.*"," — they run off the main thread, keeping the UI responsive. Synchronous versions (",[32,2106,2107],{},"tf.booleanUnion",", etc.) are also available.",[551,2110,683],{"id":682},[15,2112,686,2113,2115,2116,694,2118,698,2120,702,2122,706],{},[32,2114,1766],{}," is the output Mesh. ",[32,2117,693],{},[32,2119,697],{},[32,2121,701],{},[32,2123,2003],{},[15,2125,709],{},[61,2127,2129],{"className":1517,"code":2128,"language":1519,"meta":66,"style":66},"const { components } =\n    await tf.async.splitIntoComponents(mesh, labels);\n",[32,2130,2131,2144],{"__ignoreMap":66},[70,2132,2133,2135,2137,2140,2142],{"class":72,"line":73},[70,2134,1564],{"class":107},[70,2136,1992],{"class":76},[70,2138,2139],{"class":290},"components",[70,2141,2006],{"class":76},[70,2143,724],{"class":107},[70,2145,2146,2148,2150,2153],{"class":72,"line":80},[70,2147,2013],{"class":107},[70,2149,2016],{"class":76},[70,2151,2152],{"class":114},"splitIntoComponents",[70,2154,2155],{"class":76},"(mesh, labels);\n",[15,2157,2158],{},"Write to STL or OBJ — the result is a buffer you can download or save:",[61,2160,2162],{"className":1517,"code":2161,"language":1519,"meta":66,"style":66},"const stlBuffer = tf.writeStl(mesh);\nconst blob = new Blob(\n    [stlBuffer.toTypedArray()],\n    { type: \"model\u002Fstl\" });\n",[32,2163,2164,2181,2197,2208],{"__ignoreMap":66},[70,2165,2166,2168,2171,2173,2175,2178],{"class":72,"line":73},[70,2167,1564],{"class":107},[70,2169,2170],{"class":290}," stlBuffer",[70,2172,1570],{"class":107},[70,2174,1623],{"class":76},[70,2176,2177],{"class":114},"writeStl",[70,2179,2180],{"class":76},"(mesh);\n",[70,2182,2183,2185,2188,2190,2192,2195],{"class":72,"line":80},[70,2184,1564],{"class":107},[70,2186,2187],{"class":290}," blob",[70,2189,1570],{"class":107},[70,2191,1594],{"class":107},[70,2193,2194],{"class":114}," Blob",[70,2196,274],{"class":76},[70,2198,2199,2202,2205],{"class":72,"line":86},[70,2200,2201],{"class":76},"    [stlBuffer.",[70,2203,2204],{"class":114},"toTypedArray",[70,2206,2207],{"class":76},"()],\n",[70,2209,2210,2213,2216],{"class":72,"line":92},[70,2211,2212],{"class":76},"    { type: ",[70,2214,2215],{"class":150},"\"model\u002Fstl\"",[70,2217,2218],{"class":76}," });\n",[53,2220,804],{"id":803},[15,2222,2223],{},"When running multiple booleans on the same geometry, build the spatial and topological structures once. They are computed lazily on first use, but explicit precomputation avoids paying the cost during the boolean itself:",[61,2225,2227],{"className":1517,"code":2226,"language":1519,"meta":66,"style":66},"const dragon = tf.readStl(bytes);\n\nawait tf.async.buildTree(dragon);\nawait tf.async.computeFaceMembership(dragon);\nawait tf.async.computeManifoldEdgeLink(dragon);\n",[32,2228,2229,2243,2247,2258,2269],{"__ignoreMap":66},[70,2230,2231,2233,2235,2237,2239,2241],{"class":72,"line":73},[70,2232,1564],{"class":107},[70,2234,1618],{"class":290},[70,2236,1570],{"class":107},[70,2238,1623],{"class":76},[70,2240,1626],{"class":114},[70,2242,1629],{"class":76},[70,2244,2245],{"class":72,"line":80},[70,2246,256],{"emptyLinePlaceholder":255},[70,2248,2249,2251,2253,2256],{"class":72,"line":86},[70,2250,1602],{"class":107},[70,2252,2016],{"class":76},[70,2254,2255],{"class":114},"buildTree",[70,2257,912],{"class":76},[70,2259,2260,2262,2264,2267],{"class":72,"line":92},[70,2261,1602],{"class":107},[70,2263,2016],{"class":76},[70,2265,2266],{"class":114},"computeFaceMembership",[70,2268,912],{"class":76},[70,2270,2271,2273,2275,2278],{"class":72,"line":98},[70,2272,1602],{"class":107},[70,2274,2016],{"class":76},[70,2276,2277],{"class":114},"computeManifoldEdgeLink",[70,2279,912],{"class":76},[15,2281,2282],{},"Now create shallow copies at different positions. Each copy shares the same data and structures — only the transformation differs:",[61,2284,2286],{"className":1517,"code":2285,"language":1519,"meta":66,"style":66},"for (let t = 0.1; t \u003C= 1.0; t += 0.1) {\n    const view = dragon.shallowCopy();\n    view.transformation = tf.makeTranslation(0, t, 0);\n\n    const { mesh, labels, faceLabels } =\n        await tf.async.booleanDifference(dragon, view);\n}\n",[32,2287,2288,2317,2333,2354,2358,2378,2390],{"__ignoreMap":66},[70,2289,2290,2292,2294,2297,2299,2301,2303,2305,2307,2309,2311,2313,2315],{"class":72,"line":73},[70,2291,1003],{"class":107},[70,2293,1006],{"class":76},[70,2295,2296],{"class":107},"let",[70,2298,1011],{"class":76},[70,2300,174],{"class":107},[70,2302,1016],{"class":290},[70,2304,1021],{"class":76},[70,2306,1024],{"class":107},[70,2308,1027],{"class":290},[70,2310,1021],{"class":76},[70,2312,1034],{"class":107},[70,2314,1016],{"class":290},[70,2316,1041],{"class":76},[70,2318,2319,2322,2325,2327,2329,2331],{"class":72,"line":80},[70,2320,2321],{"class":107},"    const",[70,2323,2324],{"class":290}," view",[70,2326,1570],{"class":107},[70,2328,334],{"class":76},[70,2330,1951],{"class":114},[70,2332,216],{"class":76},[70,2334,2335,2338,2340,2342,2344,2346,2348,2350,2352],{"class":72,"line":86},[70,2336,2337],{"class":76},"    view.transformation ",[70,2339,174],{"class":107},[70,2341,1623],{"class":76},[70,2343,1810],{"class":114},[70,2345,186],{"class":76},[70,2347,697],{"class":290},[70,2349,1076],{"class":76},[70,2351,697],{"class":290},[70,2353,192],{"class":76},[70,2355,2356],{"class":72,"line":92},[70,2357,256],{"emptyLinePlaceholder":255},[70,2359,2360,2362,2364,2366,2368,2370,2372,2374,2376],{"class":72,"line":98},[70,2361,2321],{"class":107},[70,2363,1992],{"class":76},[70,2365,1766],{"class":290},[70,2367,475],{"class":76},[70,2369,693],{"class":290},[70,2371,475],{"class":76},[70,2373,2003],{"class":290},[70,2375,2006],{"class":76},[70,2377,724],{"class":107},[70,2379,2380,2383,2385,2387],{"class":72,"line":104},[70,2381,2382],{"class":107},"        await",[70,2384,2016],{"class":76},[70,2386,2057],{"class":114},[70,2388,2389],{"class":76},"(dragon, view);\n",[70,2391,2392],{"class":72,"line":937},[70,2393,1139],{"class":76},[15,2395,1142],{},[53,2397,1146],{"id":1145},[15,2399,1149,2400,1554],{},[32,2401,2402],{},"returnCurves: true",[61,2404,2406],{"className":1517,"code":2405,"language":1519,"meta":66,"style":66},"const { mesh, labels, faceLabels, curves } =\n    await tf.async.booleanDifference(dragon, translated, {\n        returnCurves: true\n    });\n\n\u002F\u002F Access curve data\nfor (const path of curves.paths) {\n    console.log(path.data); \u002F\u002F vertex indices\n}\nconst curvePoints = curves.points; \u002F\u002F NDArrayFloat32 [V, 3]\n",[32,2407,2408,2433,2444,2452,2457,2461,2466,2483,2497,2501],{"__ignoreMap":66},[70,2409,2410,2412,2414,2416,2418,2420,2422,2424,2426,2429,2431],{"class":72,"line":73},[70,2411,1564],{"class":107},[70,2413,1992],{"class":76},[70,2415,1766],{"class":290},[70,2417,475],{"class":76},[70,2419,693],{"class":290},[70,2421,475],{"class":76},[70,2423,2003],{"class":290},[70,2425,475],{"class":76},[70,2427,2428],{"class":290},"curves",[70,2430,2006],{"class":76},[70,2432,724],{"class":107},[70,2434,2435,2437,2439,2441],{"class":72,"line":80},[70,2436,2013],{"class":107},[70,2438,2016],{"class":76},[70,2440,2057],{"class":114},[70,2442,2443],{"class":76},"(dragon, translated, {\n",[70,2445,2446,2449],{"class":72,"line":86},[70,2447,2448],{"class":76},"        returnCurves: ",[70,2450,2451],{"class":290},"true\n",[70,2453,2454],{"class":72,"line":92},[70,2455,2456],{"class":76},"    });\n",[70,2458,2459],{"class":72,"line":98},[70,2460,256],{"emptyLinePlaceholder":255},[70,2462,2463],{"class":72,"line":104},[70,2464,2465],{"class":197},"\u002F\u002F Access curve data\n",[70,2467,2468,2470,2472,2474,2477,2480],{"class":72,"line":937},[70,2469,1003],{"class":107},[70,2471,1006],{"class":76},[70,2473,1564],{"class":107},[70,2475,2476],{"class":290}," path",[70,2478,2479],{"class":107}," of",[70,2481,2482],{"class":76}," curves.paths) {\n",[70,2484,2485,2488,2491,2494],{"class":72,"line":950},[70,2486,2487],{"class":76},"    console.",[70,2489,2490],{"class":114},"log",[70,2492,2493],{"class":76},"(path.data); ",[70,2495,2496],{"class":197},"\u002F\u002F vertex indices\n",[70,2498,2499],{"class":72,"line":965},[70,2500,1139],{"class":76},[70,2502,2503,2505,2508,2510,2513],{"class":72,"line":979},[70,2504,1564],{"class":107},[70,2506,2507],{"class":290}," curvePoints",[70,2509,1570],{"class":107},[70,2511,2512],{"class":76}," curves.points; ",[70,2514,2515],{"class":197},"\u002F\u002F NDArrayFloat32 [V, 3]\n",[15,2517,1238,2518,1244],{},[19,2519,1243],{"href":2520,"rel":2521},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fintersect#intersection-curves",[126],[61,2523,2525],{"className":1517,"code":2524,"language":1519,"meta":66,"style":66},"const curves = tf.intersectionCurves(dragon, translated);\n",[32,2526,2527],{"__ignoreMap":66},[70,2528,2529,2531,2534,2536,2538,2541],{"class":72,"line":73},[70,2530,1564],{"class":107},[70,2532,2533],{"class":290}," curves",[70,2535,1570],{"class":107},[70,2537,1623],{"class":76},[70,2539,2540],{"class":114},"intersectionCurves",[70,2542,1268],{"class":76},[53,2544,2546],{"id":2545},"threejs-integration","Three.js integration",[15,2548,2549,2551],{},[32,2550,34],{}," meshes can be converted to Three.js geometries and back. Transformations use row-major matrices — Three.js uses column-major:",[61,2553,2555],{"className":1517,"code":2554,"language":1519,"meta":66,"style":66},"\u002F\u002F trueform → Three.js\nconst geo = new THREE.BufferGeometry();\ngeo.setIndex(new THREE.BufferAttribute(mesh.faces.data, 1));\ngeo.setAttribute(\"position\",\n    new THREE.BufferAttribute(mesh.points.data, 3));\n\n\u002F\u002F Three.js matrix → trueform (transpose)\nmesh.transformation =\n    tf.ndarray(threeMatrix4.elements, [4, 4]).T;\n",[32,2556,2557,2562,2583,2610,2625,2643,2647,2652,2659],{"__ignoreMap":66},[70,2558,2559],{"class":72,"line":73},[70,2560,2561],{"class":197},"\u002F\u002F trueform → Three.js\n",[70,2563,2564,2566,2569,2571,2573,2576,2578,2581],{"class":72,"line":80},[70,2565,1564],{"class":107},[70,2567,2568],{"class":290}," geo",[70,2570,1570],{"class":107},[70,2572,1594],{"class":107},[70,2574,2575],{"class":290}," THREE",[70,2577,28],{"class":76},[70,2579,2580],{"class":114},"BufferGeometry",[70,2582,216],{"class":76},[70,2584,2585,2588,2591,2593,2596,2598,2600,2603,2606,2608],{"class":72,"line":86},[70,2586,2587],{"class":76},"geo.",[70,2589,2590],{"class":114},"setIndex",[70,2592,186],{"class":76},[70,2594,2595],{"class":107},"new",[70,2597,2575],{"class":290},[70,2599,28],{"class":76},[70,2601,2602],{"class":114},"BufferAttribute",[70,2604,2605],{"class":76},"(mesh.faces.data, ",[70,2607,701],{"class":290},[70,2609,489],{"class":76},[70,2611,2612,2614,2617,2619,2622],{"class":72,"line":92},[70,2613,2587],{"class":76},[70,2615,2616],{"class":114},"setAttribute",[70,2618,186],{"class":76},[70,2620,2621],{"class":150},"\"position\"",[70,2623,2624],{"class":76},",\n",[70,2626,2627,2630,2632,2634,2636,2639,2641],{"class":72,"line":98},[70,2628,2629],{"class":107},"    new",[70,2631,2575],{"class":290},[70,2633,28],{"class":76},[70,2635,2602],{"class":114},[70,2637,2638],{"class":76},"(mesh.points.data, ",[70,2640,291],{"class":290},[70,2642,489],{"class":76},[70,2644,2645],{"class":72,"line":104},[70,2646,256],{"emptyLinePlaceholder":255},[70,2648,2649],{"class":72,"line":937},[70,2650,2651],{"class":197},"\u002F\u002F Three.js matrix → trueform (transpose)\n",[70,2653,2654,2657],{"class":72,"line":950},[70,2655,2656],{"class":76},"mesh.transformation ",[70,2658,724],{"class":107},[70,2660,2661,2664,2667,2670,2672,2674,2676,2679,2682],{"class":72,"line":965},[70,2662,2663],{"class":76},"    tf.",[70,2665,2666],{"class":114},"ndarray",[70,2668,2669],{"class":76},"(threeMatrix4.elements, [",[70,2671,883],{"class":290},[70,2673,475],{"class":76},[70,2675,883],{"class":290},[70,2677,2678],{"class":76},"]).",[70,2680,2681],{"class":290},"T",[70,2683,417],{"class":76},[53,2685,1352],{"id":1351},[15,2687,1355],{},[1357,2689],{":items":2690},"[{\"value\":\"12ms\",\"label\":\"Intersection curves\",\"detail\":\"2×500K polygons\"},{\"value\":\"27ms\",\"label\":\"Boolean union\",\"detail\":\"2×500K polygons\"},{\"value\":\"250ms\",\"label\":\"Polygon arrangements\",\"detail\":\"2×500K polygons\"}]",[15,2692,2693,1367,2696,1367,2699,1367,2703],{},[70,2694,1366],{"className":2695},[1365],[70,2697,1371],{"className":2698},[1365],[70,2700,2702],{"className":2701},[1365],"Node",[70,2704,2706],{"className":2705},[1365],"WebAssembly",[15,2708,1382,2709,1385],{},[32,2710,34],{},[1387,2712],{":headers":1389,":highlight":697,":rows":1390},[15,2714,2715,2717],{},[32,2716,34],{}," is a robust CGAL alternative — and the fastest mesh boolean library available — for exact mesh boolean operations in C++, Python, and TypeScript.",[15,2719,2720,1403,2723],{},[19,2721,1402],{"href":1400,"rel":2722},[126],[19,2724,1407],{"href":1406},[1409,2726],{"author":1411,"title":1452},[1413,2728,2729],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":66,"searchDepth":80,"depth":80,"links":2731},[2732,2733,2734,2735,2741,2742,2743,2744],{"id":55,"depth":80,"text":56},{"id":131,"depth":80,"text":132},{"id":1783,"depth":80,"text":1784},{"id":540,"depth":80,"text":541,"children":2736},[2737,2738,2739,2740],{"id":553,"depth":86,"text":554},{"id":596,"depth":86,"text":597},{"id":644,"depth":86,"text":645},{"id":682,"depth":86,"text":683},{"id":803,"depth":80,"text":804},{"id":1145,"depth":80,"text":1146},{"id":2545,"depth":80,"text":2546},{"id":1351,"depth":80,"text":1352},"Learn how to perform fast mesh boolean operations in JavaScript and TypeScript. Union, intersection, and difference at interactive speed on million-polygon meshes. WebAssembly-powered, runs in Node.js and the browser.",[2747,2748,2749,2750,2751,2752,2753,2754],"JavaScript mesh boolean","TypeScript mesh boolean","mesh boolean browser","WebAssembly mesh boolean","npm mesh boolean","three.js boolean","JS CSG","browser mesh boolean",{},{"title":1452,"description":2745},"tutorials\u002Ffast-mesh-booleans-in-javascript",[2759,1519,1447,1448,2760],"javascript","webassembly","AQJ3-yiyo3nTO3pjBiJ7_fMPNcdNHN6ggqXV1sT4PbQ",{"id":2763,"title":2764,"author":7,"body":2765,"date":1431,"description":3674,"extension":1433,"keywords":3675,"meta":3683,"navigation":255,"path":21,"published":255,"seo":3684,"stem":3685,"tags":3686,"__hash__":3687},"tutorials\u002Ftutorials\u002Ffast-mesh-booleans-in-python.md","Fast Mesh Booleans in Python",{"type":9,"value":2766,"toc":3660},[2767,2775,2784,2787,2789,2791,2795,2797,2812,2819,2821,2853,2856,2885,2888,2913,2916,3058,3067,3069,3075,3150,3152,3154,3161,3164,3253,3255,3275,3277,3295,3297,3315,3317,3331,3333,3363,3366,3391,3393,3415,3451,3454,3565,3567,3569,3574,3603,3609,3624,3626,3628,3630,3638,3642,3644,3648,3655,3657],[12,2768,2769],{},[15,2770,17,2771,23,2773,28],{},[19,2772,1461],{"href":1442},[19,2774,27],{"href":26},[15,2776,2777,2779,2780,2783],{},[32,2778,34],{}," is the fastest Python mesh boolean library for real-world meshes — performing exact boolean union, intersection, and difference at interactive speed. One ",[32,2781,2782],{},"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.",[37,2785],{":buttons":2786},"[{\"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\"}]",[15,2788,42],{},[44,2790],{":images":46},[15,2792,49,2793,28],{},[32,2794,34],{},[53,2796,56],{"id":55},[61,2798,2800],{"className":1485,"code":2799,"language":1487,"meta":66,"style":66},"pip install trueform\n",[32,2801,2802],{"__ignoreMap":66},[70,2803,2804,2807,2809],{"class":72,"line":73},[70,2805,2806],{"class":114},"pip",[70,2808,1497],{"class":150},[70,2810,2811],{"class":150}," trueform\n",[15,2813,2814,2815,128],{},"See the ",[19,2816,127],{"href":2817,"rel":2818},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fgetting-started\u002Finstallation",[126],[53,2820,132],{"id":131},[61,2822,2826],{"className":2823,"code":2824,"language":2825,"meta":66,"style":66},"language-python shiki shiki-themes github-dark","import trueform as tf\nimport numpy as np\n","python",[32,2827,2828,2841],{"__ignoreMap":66},[70,2829,2830,2832,2835,2838],{"class":72,"line":73},[70,2831,1526],{"class":107},[70,2833,2834],{"class":76}," trueform ",[70,2836,2837],{"class":107},"as",[70,2839,2840],{"class":76}," tf\n",[70,2842,2843,2845,2848,2850],{"class":72,"line":80},[70,2844,1526],{"class":107},[70,2846,2847],{"class":76}," numpy ",[70,2849,2837],{"class":107},[70,2851,2852],{"class":76}," np\n",[15,2854,2855],{},"Load from a file:",[61,2857,2859],{"className":2823,"code":2858,"language":2825,"meta":66,"style":66},"faces, points = tf.read_stl(\"stanford_dragon.stl\")\ndragon = tf.Mesh(faces, points)\n",[32,2860,2861,2875],{"__ignoreMap":66},[70,2862,2863,2866,2868,2871,2873],{"class":72,"line":73},[70,2864,2865],{"class":76},"faces, points ",[70,2867,174],{"class":107},[70,2869,2870],{"class":76}," tf.read_stl(",[70,2872,189],{"class":150},[70,2874,95],{"class":76},[70,2876,2877,2880,2882],{"class":72,"line":80},[70,2878,2879],{"class":76},"dragon ",[70,2881,174],{"class":107},[70,2883,2884],{"class":76}," tf.Mesh(faces, points)\n",[15,2886,2887],{},"Or in one line:",[61,2889,2891],{"className":2823,"code":2890,"language":2825,"meta":66,"style":66},"dragon = tf.Mesh(*tf.read_stl(\"stanford_dragon.stl\"))\n",[32,2892,2893],{"__ignoreMap":66},[70,2894,2895,2897,2899,2902,2905,2908,2910],{"class":72,"line":73},[70,2896,2879],{"class":76},[70,2898,174],{"class":107},[70,2900,2901],{"class":76}," tf.Mesh(",[70,2903,2904],{"class":107},"*",[70,2906,2907],{"class":76},"tf.read_stl(",[70,2909,189],{"class":150},[70,2911,2912],{"class":76},"))\n",[15,2914,2915],{},"If you already have vertex and face data as NumPy arrays, pass them directly:",[61,2917,2919],{"className":2823,"code":2918,"language":2825,"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",[32,2920,2921,2965,2975,3005,3034,3046,3050],{"__ignoreMap":66},[70,2922,2923,2926,2928,2931,2933,2935,2937,2939,2941,2943,2945,2947,2949,2951,2953,2956,2960,2962],{"class":72,"line":73},[70,2924,2925],{"class":76},"faces ",[70,2927,174],{"class":107},[70,2929,2930],{"class":76}," np.array([[",[70,2932,697],{"class":290},[70,2934,475],{"class":76},[70,2936,701],{"class":290},[70,2938,475],{"class":76},[70,2940,1665],{"class":290},[70,2942,1886],{"class":76},[70,2944,701],{"class":290},[70,2946,475],{"class":76},[70,2948,291],{"class":290},[70,2950,475],{"class":76},[70,2952,1665],{"class":290},[70,2954,2955],{"class":76},"]], ",[70,2957,2959],{"class":2958},"s9osk","dtype",[70,2961,174],{"class":107},[70,2963,2964],{"class":76},"np.int32)\n",[70,2966,2967,2970,2972],{"class":72,"line":80},[70,2968,2969],{"class":76},"points ",[70,2971,174],{"class":107},[70,2973,2974],{"class":76}," np.array([\n",[70,2976,2977,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998,3000,3002],{"class":72,"line":86},[70,2978,2979],{"class":76},"    [",[70,2981,697],{"class":290},[70,2983,475],{"class":76},[70,2985,697],{"class":290},[70,2987,475],{"class":76},[70,2989,697],{"class":290},[70,2991,1886],{"class":76},[70,2993,701],{"class":290},[70,2995,475],{"class":76},[70,2997,697],{"class":290},[70,2999,475],{"class":76},[70,3001,697],{"class":290},[70,3003,3004],{"class":76},"],\n",[70,3006,3007,3009,3011,3013,3015,3017,3019,3021,3023,3025,3027,3029,3031],{"class":72,"line":92},[70,3008,2979],{"class":76},[70,3010,697],{"class":290},[70,3012,475],{"class":76},[70,3014,701],{"class":290},[70,3016,475],{"class":76},[70,3018,697],{"class":290},[70,3020,1886],{"class":76},[70,3022,701],{"class":290},[70,3024,475],{"class":76},[70,3026,701],{"class":290},[70,3028,475],{"class":76},[70,3030,697],{"class":290},[70,3032,3033],{"class":76},"]\n",[70,3035,3036,3039,3041,3043],{"class":72,"line":98},[70,3037,3038],{"class":76},"], ",[70,3040,2959],{"class":2958},[70,3042,174],{"class":107},[70,3044,3045],{"class":76},"np.float32)\n",[70,3047,3048],{"class":72,"line":104},[70,3049,256],{"emptyLinePlaceholder":255},[70,3051,3052,3054,3056],{"class":72,"line":937},[70,3053,2879],{"class":76},[70,3055,174],{"class":107},[70,3057,2884],{"class":76},[15,3059,1772,3060,3066],{},[19,3061,3064],{"href":3062,"rel":3063},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fspatial#mesh",[126],[32,3065,1779],{}," form wraps your arrays with geometric semantics — spatial queries, topology, and boolean operations all work directly on it.",[53,3068,1784],{"id":1783},[15,3070,1787,3071,1793],{},[19,3072,1792],{"href":3073,"rel":3074},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fspatial#transformations-on-forms",[126],[61,3076,3078],{"className":2823,"code":3077,"language":2825,"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",[32,3079,3080,3100,3137,3141],{"__ignoreMap":66},[70,3081,3082,3085,3087,3090,3092,3094,3096,3098],{"class":72,"line":73},[70,3083,3084],{"class":76},"T ",[70,3086,174],{"class":107},[70,3088,3089],{"class":76}," np.eye(",[70,3091,883],{"class":290},[70,3093,475],{"class":76},[70,3095,2959],{"class":2958},[70,3097,174],{"class":107},[70,3099,3045],{"class":76},[70,3101,3102,3105,3107,3109,3111,3114,3116,3119,3122,3124,3127,3129,3131,3134],{"class":72,"line":80},[70,3103,3104],{"class":76},"T[:",[70,3106,291],{"class":290},[70,3108,475],{"class":76},[70,3110,291],{"class":290},[70,3112,3113],{"class":76},"] ",[70,3115,174],{"class":107},[70,3117,3118],{"class":76}," [",[70,3120,3121],{"class":290},"0.0",[70,3123,475],{"class":76},[70,3125,3126],{"class":290},"1.0",[70,3128,475],{"class":76},[70,3130,3121],{"class":290},[70,3132,3133],{"class":76},"]  ",[70,3135,3136],{"class":197},"# translate by [0, 1, 0]\n",[70,3138,3139],{"class":72,"line":86},[70,3140,256],{"emptyLinePlaceholder":255},[70,3142,3143,3145,3147],{"class":72,"line":92},[70,3144,1803],{"class":76},[70,3146,174],{"class":107},[70,3148,3149],{"class":76}," T\n",[15,3151,1902],{},[53,3153,541],{"id":540},[15,3155,3156,3157,28],{},"Three operations. Same two inputs. ",[19,3158,549],{"href":3159,"rel":3160},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fcut#boolean-operations",[126],[15,3162,3163],{},"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,3165,3167],{"className":2823,"code":3166,"language":2825,"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",[32,3168,3169,3185,3189,3199,3217,3245],{"__ignoreMap":66},[70,3170,3171,3173,3175,3177,3179,3181,3183],{"class":72,"line":73},[70,3172,2879],{"class":76},[70,3174,174],{"class":107},[70,3176,2901],{"class":76},[70,3178,2904],{"class":107},[70,3180,2907],{"class":76},[70,3182,189],{"class":150},[70,3184,2912],{"class":76},[70,3186,3187],{"class":72,"line":80},[70,3188,256],{"emptyLinePlaceholder":255},[70,3190,3191,3194,3196],{"class":72,"line":86},[70,3192,3193],{"class":76},"translated ",[70,3195,174],{"class":107},[70,3197,3198],{"class":76}," dragon.shared_view()\n",[70,3200,3201,3203,3205,3207,3209,3211,3213,3215],{"class":72,"line":92},[70,3202,3084],{"class":76},[70,3204,174],{"class":107},[70,3206,3089],{"class":76},[70,3208,883],{"class":290},[70,3210,475],{"class":76},[70,3212,2959],{"class":2958},[70,3214,174],{"class":107},[70,3216,3045],{"class":76},[70,3218,3219,3221,3223,3225,3227,3229,3231,3233,3235,3237,3239,3241,3243],{"class":72,"line":98},[70,3220,3104],{"class":76},[70,3222,291],{"class":290},[70,3224,475],{"class":76},[70,3226,291],{"class":290},[70,3228,3113],{"class":76},[70,3230,174],{"class":107},[70,3232,3118],{"class":76},[70,3234,3121],{"class":290},[70,3236,475],{"class":76},[70,3238,3126],{"class":290},[70,3240,475],{"class":76},[70,3242,3121],{"class":290},[70,3244,3033],{"class":76},[70,3246,3247,3249,3251],{"class":72,"line":104},[70,3248,1958],{"class":76},[70,3250,174],{"class":107},[70,3252,3149],{"class":76},[551,3254,554],{"id":553},[61,3256,3258],{"className":2823,"code":3257,"language":2825,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels = \n    tf.boolean_union(dragon, translated)\n",[32,3259,3260,3270],{"__ignoreMap":66},[70,3261,3262,3265,3267],{"class":72,"line":73},[70,3263,3264],{"class":76},"(result_faces, result_points), labels, face_labels ",[70,3266,174],{"class":107},[70,3268,3269],{"class":76}," \n",[70,3271,3272],{"class":72,"line":80},[70,3273,3274],{"class":76},"    tf.boolean_union(dragon, translated)\n",[551,3276,597],{"id":596},[61,3278,3280],{"className":2823,"code":3279,"language":2825,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels = \n    tf.boolean_difference(dragon, translated)\n",[32,3281,3282,3290],{"__ignoreMap":66},[70,3283,3284,3286,3288],{"class":72,"line":73},[70,3285,3264],{"class":76},[70,3287,174],{"class":107},[70,3289,3269],{"class":76},[70,3291,3292],{"class":72,"line":80},[70,3293,3294],{"class":76},"    tf.boolean_difference(dragon, translated)\n",[551,3296,645],{"id":644},[61,3298,3300],{"className":2823,"code":3299,"language":2825,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels = \n    tf.boolean_intersection(dragon, translated)\n",[32,3301,3302,3310],{"__ignoreMap":66},[70,3303,3304,3306,3308],{"class":72,"line":73},[70,3305,3264],{"class":76},[70,3307,174],{"class":107},[70,3309,3269],{"class":76},[70,3311,3312],{"class":72,"line":80},[70,3313,3314],{"class":76},"    tf.boolean_intersection(dragon, translated)\n",[551,3316,683],{"id":682},[15,3318,686,3319,3322,3323,694,3325,698,3327,702,3329,706],{},[32,3320,3321],{},"(result_faces, result_points)"," is the output mesh as NumPy arrays. ",[32,3324,693],{},[32,3326,697],{},[32,3328,701],{},[32,3330,705],{},[15,3332,709],{},[61,3334,3336],{"className":2823,"code":3335,"language":2825,"meta":66,"style":66},"result_mesh = tf.Mesh(result_faces, result_points)\ncomponents, component_labels = \\\n    tf.split_into_components(result_mesh, labels)\n",[32,3337,3338,3348,3358],{"__ignoreMap":66},[70,3339,3340,3343,3345],{"class":72,"line":73},[70,3341,3342],{"class":76},"result_mesh ",[70,3344,174],{"class":107},[70,3346,3347],{"class":76}," tf.Mesh(result_faces, result_points)\n",[70,3349,3350,3353,3355],{"class":72,"line":80},[70,3351,3352],{"class":76},"components, component_labels ",[70,3354,174],{"class":107},[70,3356,3357],{"class":76}," \\\n",[70,3359,3360],{"class":72,"line":86},[70,3361,3362],{"class":76},"    tf.split_into_components(result_mesh, labels)\n",[15,3364,3365],{},"Write to file:",[61,3367,3369],{"className":2823,"code":3368,"language":2825,"meta":66,"style":66},"tf.write_stl((result_faces, result_points), \"result.stl\")\ntf.write_obj((result_faces, result_points), \"result.obj\")\n",[32,3370,3371,3381],{"__ignoreMap":66},[70,3372,3373,3376,3379],{"class":72,"line":73},[70,3374,3375],{"class":76},"tf.write_stl((result_faces, result_points), ",[70,3377,3378],{"class":150},"\"result.stl\"",[70,3380,95],{"class":76},[70,3382,3383,3386,3389],{"class":72,"line":80},[70,3384,3385],{"class":76},"tf.write_obj((result_faces, result_points), ",[70,3387,3388],{"class":150},"\"result.obj\"",[70,3390,95],{"class":76},[53,3392,804],{"id":803},[15,3394,3395,3396,475,3402,822,3408,3414],{},"When running multiple booleans on the same geometry, build the spatial and topological structures once. The ",[19,3397,3399],{"href":3062,"rel":3398},[126],[32,3400,3401],{},"build_tree",[19,3403,3406],{"href":3404,"rel":3405},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Ftopology#cell-membership",[126],[32,3407,821],{},[19,3409,3412],{"href":3410,"rel":3411},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Ftopology#manifold-edge-link",[126],[32,3413,829],{}," are computed lazily on first use, but explicit precomputation avoids paying the cost during the boolean itself:",[61,3416,3418],{"className":2823,"code":3417,"language":2825,"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",[32,3419,3420,3436,3441,3446],{"__ignoreMap":66},[70,3421,3422,3424,3426,3428,3430,3432,3434],{"class":72,"line":73},[70,3423,2879],{"class":76},[70,3425,174],{"class":107},[70,3427,2901],{"class":76},[70,3429,2904],{"class":107},[70,3431,2907],{"class":76},[70,3433,189],{"class":150},[70,3435,2912],{"class":76},[70,3437,3438],{"class":72,"line":80},[70,3439,3440],{"class":76},"dragon.build_tree()\n",[70,3442,3443],{"class":72,"line":86},[70,3444,3445],{"class":76},"dragon.build_face_membership()\n",[70,3447,3448],{"class":72,"line":92},[70,3449,3450],{"class":76},"dragon.build_manifold_edge_link()\n",[15,3452,3453],{},"Now create shared views at different positions. Each view shares the same data and structures — only the transformation differs:",[61,3455,3457],{"className":2823,"code":3456,"language":2825,"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",[32,3458,3459,3486,3495,3514,3539,3547,3551,3560],{"__ignoreMap":66},[70,3460,3461,3463,3465,3468,3471,3474,3476,3479,3481,3483],{"class":72,"line":73},[70,3462,1003],{"class":107},[70,3464,1011],{"class":76},[70,3466,3467],{"class":107},"in",[70,3469,3470],{"class":76}," np.arange(",[70,3472,3473],{"class":290},"0.1",[70,3475,475],{"class":76},[70,3477,3478],{"class":290},"1.1",[70,3480,475],{"class":76},[70,3482,3473],{"class":290},[70,3484,3485],{"class":76},"):\n",[70,3487,3488,3491,3493],{"class":72,"line":80},[70,3489,3490],{"class":76},"    view ",[70,3492,174],{"class":107},[70,3494,3198],{"class":76},[70,3496,3497,3500,3502,3504,3506,3508,3510,3512],{"class":72,"line":86},[70,3498,3499],{"class":76},"    T ",[70,3501,174],{"class":107},[70,3503,3089],{"class":76},[70,3505,883],{"class":290},[70,3507,475],{"class":76},[70,3509,2959],{"class":2958},[70,3511,174],{"class":107},[70,3513,3045],{"class":76},[70,3515,3516,3519,3521,3523,3525,3527,3529,3531,3533,3535,3537],{"class":72,"line":92},[70,3517,3518],{"class":76},"    T[:",[70,3520,291],{"class":290},[70,3522,475],{"class":76},[70,3524,291],{"class":290},[70,3526,3113],{"class":76},[70,3528,174],{"class":107},[70,3530,3118],{"class":76},[70,3532,3121],{"class":290},[70,3534,1076],{"class":76},[70,3536,3121],{"class":290},[70,3538,3033],{"class":76},[70,3540,3541,3543,3545],{"class":72,"line":98},[70,3542,2337],{"class":76},[70,3544,174],{"class":107},[70,3546,3149],{"class":76},[70,3548,3549],{"class":72,"line":104},[70,3550,256],{"emptyLinePlaceholder":255},[70,3552,3553,3556,3558],{"class":72,"line":937},[70,3554,3555],{"class":76},"    (result_faces, result_points), labels, face_labels ",[70,3557,174],{"class":107},[70,3559,3357],{"class":76},[70,3561,3562],{"class":72,"line":950},[70,3563,3564],{"class":76},"        tf.boolean_difference(dragon, view)\n",[15,3566,1142],{},[53,3568,1146],{"id":1145},[15,3570,1149,3571,1554],{},[32,3572,3573],{},"return_curves=True",[61,3575,3577],{"className":2823,"code":3576,"language":2825,"meta":66,"style":66},"(result_faces, result_points), labels, face_labels, (paths, curve_points) = \\\n    tf.boolean_difference(dragon, translated, return_curves=True)\n",[32,3578,3579,3588],{"__ignoreMap":66},[70,3580,3581,3584,3586],{"class":72,"line":73},[70,3582,3583],{"class":76},"(result_faces, result_points), labels, face_labels, (paths, curve_points) ",[70,3585,174],{"class":107},[70,3587,3357],{"class":76},[70,3589,3590,3593,3596,3598,3601],{"class":72,"line":80},[70,3591,3592],{"class":76},"    tf.boolean_difference(dragon, translated, ",[70,3594,3595],{"class":2958},"return_curves",[70,3597,174],{"class":107},[70,3599,3600],{"class":290},"True",[70,3602,95],{"class":76},[15,3604,1238,3605,1244],{},[19,3606,1243],{"href":3607,"rel":3608},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fintersect#intersection-curves",[126],[61,3610,3612],{"className":2823,"code":3611,"language":2825,"meta":66,"style":66},"paths, curve_points = tf.intersection_curves(dragon, translated)\n",[32,3613,3614],{"__ignoreMap":66},[70,3615,3616,3619,3621],{"class":72,"line":73},[70,3617,3618],{"class":76},"paths, curve_points ",[70,3620,174],{"class":107},[70,3622,3623],{"class":76}," tf.intersection_curves(dragon, translated)\n",[53,3625,1352],{"id":1351},[15,3627,1355],{},[1357,3629],{":items":1359},[15,3631,3632,1367,3635],{},[70,3633,1366],{"className":3634},[1365],[70,3636,1371],{"className":3637},[1365],[15,3639,1382,3640,1385],{},[32,3641,34],{},[1387,3643],{":headers":1389,":highlight":697,":rows":1390},[15,3645,3646,2717],{},[32,3647,34],{},[15,3649,3650,1403,3653],{},[19,3651,1402],{"href":1400,"rel":3652},[126],[19,3654,1407],{"href":1406},[1409,3656],{"author":1411,"title":2764},[1413,3658,3659],{},"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":80,"depth":80,"links":3661},[3662,3663,3664,3665,3671,3672,3673],{"id":55,"depth":80,"text":56},{"id":131,"depth":80,"text":132},{"id":1783,"depth":80,"text":1784},{"id":540,"depth":80,"text":541,"children":3666},[3667,3668,3669,3670],{"id":553,"depth":86,"text":554},{"id":596,"depth":86,"text":597},{"id":644,"depth":86,"text":645},{"id":682,"depth":86,"text":683},{"id":803,"depth":80,"text":804},{"id":1145,"depth":80,"text":1146},{"id":1351,"depth":80,"text":1352},"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.",[3676,3677,3678,3679,3680,3681,3682],"Python mesh boolean","mesh boolean Python","pip mesh boolean","numpy mesh boolean","CGAL alternative Python","trimesh boolean alternative","pymesh boolean alternative",{},{"title":2764,"description":3674},"tutorials\u002Ffast-mesh-booleans-in-python",[2825,1447,1448],"AfqQG6dhcIX1TON4QR1CiZTeffZIOJ0mTzVhcIxVDA4",1779353828611]