[{"data":1,"prerenderedAt":1170},["ShallowReactive",2],{"tutorial-mesh-intersection-curves-in-python":3},{"id":4,"title":5,"author":6,"body":7,"date":1147,"description":1148,"extension":1149,"keywords":1150,"meta":1161,"navigation":136,"ogImage":1162,"path":1163,"published":136,"seo":1164,"stem":1165,"tags":1166,"__hash__":1169},"tutorials\u002Ftutorials\u002Fmesh-intersection-curves-in-python.md","Fast and Exact Mesh Intersection Curves in Python","Žiga Sajovic",{"type":8,"value":9,"toc":1137},"minimark",[10,28,40,44,51,56,81,91,95,182,185,258,261,265,279,318,324,338,341,345,354,484,490,499,536,539,543,546,794,800,806,810,822,911,917,995,998,1029,1045,1049,1081,1085,1089,1101,1108,1114,1129,1133],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Fmesh-intersection-curves-in-cpp","C++"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Fmesh-intersection-curves-in-javascript","JavaScript",".",[14,29,30,31,35,36,39],{},"An intersection curve is the line where two surfaces cross. You need it to inspect a fit, to trim a part, to mark a seam, or to check a boolean before you run it. ",[32,33,34],"code",{},"trueform"," computes these curves exactly, between two meshes or between many, and returns them as polylines. One ",[32,37,38],{},"pip install",". NumPy arrays in, NumPy arrays out. This tutorial covers two meshes, moved meshes, N meshes, and how to use the curves.",[41,42],"article-cta",{":buttons":43},"[{\"label\":\"trueform\",\"to\":\"\u002Ftrueform\",\"icon\":\"i-lucide-cpu\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"GitHub\",\"to\":\"https:\u002F\u002Fgithub.com\u002Fpolydera\u002Ftrueform\",\"icon\":\"i-simple-icons-github\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Documentation\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fintersect#intersection-curves\",\"variant\":\"soft\",\"color\":\"neutral\"}]",[14,45,46],{},[47,48],"img",{"alt":49,"src":50},"The Stanford dragon and a moved copy, with the curves where they intersect","\u002Fimg\u002Ftutorials\u002Fintersection-curves.png",[52,53,55],"h2",{"id":54},"install","Install",[57,58,63],"pre",{"className":59,"code":60,"language":61,"meta":62,"style":62},"language-bash shiki shiki-themes github-dark","pip install trueform\n","bash","",[32,64,65],{"__ignoreMap":62},[66,67,70,74,78],"span",{"class":68,"line":69},"line",1,[66,71,73],{"class":72},"svObZ","pip",[66,75,77],{"class":76},"sU2Wk"," install",[66,79,80],{"class":76}," trueform\n",[14,82,83,84,90],{},"See the ",[18,85,89],{"href":86,"rel":87},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fgetting-started\u002Finstallation",[88],"nofollow","installation guide"," for all options.",[52,92,94],{"id":93},"loading-the-meshes","Loading the meshes",[57,96,100],{"className":97,"code":98,"language":99,"meta":62,"style":62},"language-python shiki shiki-themes github-dark","import trueform as tf\nimport numpy as np\n\nmesh0 = tf.Mesh(*tf.read_stl(\"part.stl\"))\nmesh1 = tf.Mesh(*tf.read_obj(\"tool.obj\"))\n","python",[32,101,102,118,131,138,162],{"__ignoreMap":62},[66,103,104,108,112,115],{"class":68,"line":69},[66,105,107],{"class":106},"snl16","import",[66,109,111],{"class":110},"s95oV"," trueform ",[66,113,114],{"class":106},"as",[66,116,117],{"class":110}," tf\n",[66,119,121,123,126,128],{"class":68,"line":120},2,[66,122,107],{"class":106},[66,124,125],{"class":110}," numpy ",[66,127,114],{"class":106},[66,129,130],{"class":110}," np\n",[66,132,134],{"class":68,"line":133},3,[66,135,137],{"emptyLinePlaceholder":136},true,"\n",[66,139,141,144,147,150,153,156,159],{"class":68,"line":140},4,[66,142,143],{"class":110},"mesh0 ",[66,145,146],{"class":106},"=",[66,148,149],{"class":110}," tf.Mesh(",[66,151,152],{"class":106},"*",[66,154,155],{"class":110},"tf.read_stl(",[66,157,158],{"class":76},"\"part.stl\"",[66,160,161],{"class":110},"))\n",[66,163,165,168,170,172,174,177,180],{"class":68,"line":164},5,[66,166,167],{"class":110},"mesh1 ",[66,169,146],{"class":106},[66,171,149],{"class":110},[66,173,152],{"class":106},[66,175,176],{"class":110},"tf.read_obj(",[66,178,179],{"class":76},"\"tool.obj\"",[66,181,161],{"class":110},[14,183,184],{},"To follow along without files, build a sphere and a box:",[57,186,188],{"className":97,"code":187,"language":99,"meta":62,"style":62},"sphere = tf.Mesh(*tf.make_sphere_mesh(1.0, stacks=40, segments=40))\nbox = tf.Mesh(*tf.make_box_mesh(1.5, 1.5, 1.5))\n",[32,189,190,231],{"__ignoreMap":62},[66,191,192,195,197,199,201,204,208,211,215,217,220,222,225,227,229],{"class":68,"line":69},[66,193,194],{"class":110},"sphere ",[66,196,146],{"class":106},[66,198,149],{"class":110},[66,200,152],{"class":106},[66,202,203],{"class":110},"tf.make_sphere_mesh(",[66,205,207],{"class":206},"sDLfK","1.0",[66,209,210],{"class":110},", ",[66,212,214],{"class":213},"s9osk","stacks",[66,216,146],{"class":106},[66,218,219],{"class":206},"40",[66,221,210],{"class":110},[66,223,224],{"class":213},"segments",[66,226,146],{"class":106},[66,228,219],{"class":206},[66,230,161],{"class":110},[66,232,233,236,238,240,242,245,248,250,252,254,256],{"class":68,"line":120},[66,234,235],{"class":110},"box ",[66,237,146],{"class":106},[66,239,149],{"class":110},[66,241,152],{"class":106},[66,243,244],{"class":110},"tf.make_box_mesh(",[66,246,247],{"class":206},"1.5",[66,249,210],{"class":110},[66,251,247],{"class":206},[66,253,210],{"class":110},[66,255,247],{"class":206},[66,257,161],{"class":110},[14,259,260],{},"The box is smaller than the sphere, but its corners poke out. Each of its six faces cuts the sphere in a circle.",[52,262,264],{"id":263},"curves-between-two-meshes","Curves between two meshes",[14,266,267,274,275,278],{},[18,268,271],{"href":269,"rel":270},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fintersect#between-two-meshes",[88],[32,272,273],{},"tf.intersection_curves"," takes two meshes and returns ",[32,276,277],{},"(paths, points)",":",[57,280,282],{"className":97,"code":281,"language":99,"meta":62,"style":62},"paths, curve_points = tf.intersection_curves(sphere, box)\n\nlen(paths)          # 6\ncurve_points.shape  # (562, 3)\n",[32,283,284,294,298,310],{"__ignoreMap":62},[66,285,286,289,291],{"class":68,"line":69},[66,287,288],{"class":110},"paths, curve_points ",[66,290,146],{"class":106},[66,292,293],{"class":110}," tf.intersection_curves(sphere, box)\n",[66,295,296],{"class":68,"line":120},[66,297,137],{"emptyLinePlaceholder":136},[66,299,300,303,306],{"class":68,"line":133},[66,301,302],{"class":206},"len",[66,304,305],{"class":110},"(paths)          ",[66,307,309],{"class":308},"sAwPA","# 6\n",[66,311,312,315],{"class":68,"line":140},[66,313,314],{"class":110},"curve_points.shape  ",[66,316,317],{"class":308},"# (562, 3)\n",[14,319,320],{},[47,321],{"alt":322,"src":323},"Step: curves between two meshes. Two Thingi10K parts from the benchmark corpus, a lattice slab and a scanned shape, with their intersection curves in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-intersection-curves-1-two-meshes.png",[14,325,326,327,330,331,334,335,27],{},"Six closed curves, one per box face. If the meshes do not touch, ",[32,328,329],{},"paths"," is empty and ",[32,332,333],{},"curve_points"," has shape ",[32,336,337],{},"(0, 3)",[14,339,340],{},"The computation is exact. Shared edges, shared vertices and coplanar faces are classified, not guessed. Open meshes, non-manifold edges and self-intersecting inputs are accepted.",[52,342,344],{"id":343},"curves-between-moved-meshes","Curves between moved meshes",[14,346,347,348,353],{},"Most real cases have one part in a different pose. Set a 4×4 ",[18,349,352],{"href":350,"rel":351},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fspatial#transformations-on-forms",[88],"transformation"," on the mesh. The points are not copied or rewritten.",[57,355,357],{"className":97,"code":356,"language":99,"meta":62,"style":62},"moved = tf.Mesh(*tf.make_sphere_mesh(1.0, stacks=40, segments=40))\n\nT = np.eye(4, dtype=np.float32)\nT[:3, 3] = [1.0, 0.0, 0.0]\nmoved.transformation = T\n\npaths, curve_points = tf.intersection_curves(sphere, moved)\n# one closed circle in the plane x = 0.5\n",[32,358,359,392,396,419,453,463,468,478],{"__ignoreMap":62},[66,360,361,364,366,368,370,372,374,376,378,380,382,384,386,388,390],{"class":68,"line":69},[66,362,363],{"class":110},"moved ",[66,365,146],{"class":106},[66,367,149],{"class":110},[66,369,152],{"class":106},[66,371,203],{"class":110},[66,373,207],{"class":206},[66,375,210],{"class":110},[66,377,214],{"class":213},[66,379,146],{"class":106},[66,381,219],{"class":206},[66,383,210],{"class":110},[66,385,224],{"class":213},[66,387,146],{"class":106},[66,389,219],{"class":206},[66,391,161],{"class":110},[66,393,394],{"class":68,"line":120},[66,395,137],{"emptyLinePlaceholder":136},[66,397,398,401,403,406,409,411,414,416],{"class":68,"line":133},[66,399,400],{"class":110},"T ",[66,402,146],{"class":106},[66,404,405],{"class":110}," np.eye(",[66,407,408],{"class":206},"4",[66,410,210],{"class":110},[66,412,413],{"class":213},"dtype",[66,415,146],{"class":106},[66,417,418],{"class":110},"np.float32)\n",[66,420,421,424,427,429,431,434,436,439,441,443,446,448,450],{"class":68,"line":140},[66,422,423],{"class":110},"T[:",[66,425,426],{"class":206},"3",[66,428,210],{"class":110},[66,430,426],{"class":206},[66,432,433],{"class":110},"] ",[66,435,146],{"class":106},[66,437,438],{"class":110}," [",[66,440,207],{"class":206},[66,442,210],{"class":110},[66,444,445],{"class":206},"0.0",[66,447,210],{"class":110},[66,449,445],{"class":206},[66,451,452],{"class":110},"]\n",[66,454,455,458,460],{"class":68,"line":164},[66,456,457],{"class":110},"moved.transformation ",[66,459,146],{"class":106},[66,461,462],{"class":110}," T\n",[66,464,466],{"class":68,"line":465},6,[66,467,137],{"emptyLinePlaceholder":136},[66,469,471,473,475],{"class":68,"line":470},7,[66,472,288],{"class":110},[66,474,146],{"class":106},[66,476,477],{"class":110}," tf.intersection_curves(sphere, moved)\n",[66,479,481],{"class":68,"line":480},8,[66,482,483],{"class":308},"# one closed circle in the plane x = 0.5\n",[14,485,486],{},[47,487],{"alt":488,"src":489},"Step: curves between moved meshes. The Stanford bunny and a moved, turned copy of it (a shared view with a transformation), with the closed intersection curve in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-intersection-curves-2-moved.png",[14,491,492,493,498],{},"To intersect a mesh with a moved copy of itself, use a ",[18,494,497],{"href":495,"rel":496},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fspatial#shared-views",[88],"shared view",". It shares the points, faces and spatial tree. Only the transformation differs:",[57,500,502],{"className":97,"code":501,"language":99,"meta":62,"style":62},"view = sphere.shared_view()\nview.transformation = T\n\npaths, curve_points = tf.intersection_curves(sphere, view)\n",[32,503,504,514,523,527],{"__ignoreMap":62},[66,505,506,509,511],{"class":68,"line":69},[66,507,508],{"class":110},"view ",[66,510,146],{"class":106},[66,512,513],{"class":110}," sphere.shared_view()\n",[66,515,516,519,521],{"class":68,"line":120},[66,517,518],{"class":110},"view.transformation ",[66,520,146],{"class":106},[66,522,462],{"class":110},[66,524,525],{"class":68,"line":133},[66,526,137],{"emptyLinePlaceholder":136},[66,528,529,531,533],{"class":68,"line":140},[66,530,288],{"class":110},[66,532,146],{"class":106},[66,534,535],{"class":110}," tf.intersection_curves(sphere, view)\n",[14,537,538],{},"The tree is built once. Moving the view does not rebuild it.",[52,540,542],{"id":541},"curves-between-n-meshes","Curves between N meshes",[14,544,545],{},"Pass a list to get all pairwise curves in one call:",[57,547,549],{"className":97,"code":548,"language":99,"meta":62,"style":62},"m0 = tf.Mesh(*tf.make_sphere_mesh(1.0, stacks=40, segments=40))\nm1 = tf.Mesh(*tf.make_sphere_mesh(1.0, stacks=40, segments=40))\nm2 = tf.Mesh(*tf.make_sphere_mesh(1.0, stacks=40, segments=40))\n\nT1 = np.eye(4, dtype=np.float32); T1[:3, 3] = [1.0, 0.0, 0.0]\nT2 = np.eye(4, dtype=np.float32); T2[:3, 3] = [0.5, 0.8, 0.0]\nm1.transformation = T1\nm2.transformation = T2\n\npaths, curve_points = tf.intersection_curves([m0, m1, m2])\n\nlen(paths)  # 6\n",[32,550,551,584,617,650,654,698,744,754,764,769,779,784],{"__ignoreMap":62},[66,552,553,556,558,560,562,564,566,568,570,572,574,576,578,580,582],{"class":68,"line":69},[66,554,555],{"class":110},"m0 ",[66,557,146],{"class":106},[66,559,149],{"class":110},[66,561,152],{"class":106},[66,563,203],{"class":110},[66,565,207],{"class":206},[66,567,210],{"class":110},[66,569,214],{"class":213},[66,571,146],{"class":106},[66,573,219],{"class":206},[66,575,210],{"class":110},[66,577,224],{"class":213},[66,579,146],{"class":106},[66,581,219],{"class":206},[66,583,161],{"class":110},[66,585,586,589,591,593,595,597,599,601,603,605,607,609,611,613,615],{"class":68,"line":120},[66,587,588],{"class":110},"m1 ",[66,590,146],{"class":106},[66,592,149],{"class":110},[66,594,152],{"class":106},[66,596,203],{"class":110},[66,598,207],{"class":206},[66,600,210],{"class":110},[66,602,214],{"class":213},[66,604,146],{"class":106},[66,606,219],{"class":206},[66,608,210],{"class":110},[66,610,224],{"class":213},[66,612,146],{"class":106},[66,614,219],{"class":206},[66,616,161],{"class":110},[66,618,619,622,624,626,628,630,632,634,636,638,640,642,644,646,648],{"class":68,"line":133},[66,620,621],{"class":110},"m2 ",[66,623,146],{"class":106},[66,625,149],{"class":110},[66,627,152],{"class":106},[66,629,203],{"class":110},[66,631,207],{"class":206},[66,633,210],{"class":110},[66,635,214],{"class":213},[66,637,146],{"class":106},[66,639,219],{"class":206},[66,641,210],{"class":110},[66,643,224],{"class":213},[66,645,146],{"class":106},[66,647,219],{"class":206},[66,649,161],{"class":110},[66,651,652],{"class":68,"line":140},[66,653,137],{"emptyLinePlaceholder":136},[66,655,656,659,661,663,665,667,669,671,674,676,678,680,682,684,686,688,690,692,694,696],{"class":68,"line":164},[66,657,658],{"class":110},"T1 ",[66,660,146],{"class":106},[66,662,405],{"class":110},[66,664,408],{"class":206},[66,666,210],{"class":110},[66,668,413],{"class":213},[66,670,146],{"class":106},[66,672,673],{"class":110},"np.float32); T1[:",[66,675,426],{"class":206},[66,677,210],{"class":110},[66,679,426],{"class":206},[66,681,433],{"class":110},[66,683,146],{"class":106},[66,685,438],{"class":110},[66,687,207],{"class":206},[66,689,210],{"class":110},[66,691,445],{"class":206},[66,693,210],{"class":110},[66,695,445],{"class":206},[66,697,452],{"class":110},[66,699,700,703,705,707,709,711,713,715,718,720,722,724,726,728,730,733,735,738,740,742],{"class":68,"line":465},[66,701,702],{"class":110},"T2 ",[66,704,146],{"class":106},[66,706,405],{"class":110},[66,708,408],{"class":206},[66,710,210],{"class":110},[66,712,413],{"class":213},[66,714,146],{"class":106},[66,716,717],{"class":110},"np.float32); T2[:",[66,719,426],{"class":206},[66,721,210],{"class":110},[66,723,426],{"class":206},[66,725,433],{"class":110},[66,727,146],{"class":106},[66,729,438],{"class":110},[66,731,732],{"class":206},"0.5",[66,734,210],{"class":110},[66,736,737],{"class":206},"0.8",[66,739,210],{"class":110},[66,741,445],{"class":206},[66,743,452],{"class":110},[66,745,746,749,751],{"class":68,"line":470},[66,747,748],{"class":110},"m1.transformation ",[66,750,146],{"class":106},[66,752,753],{"class":110}," T1\n",[66,755,756,759,761],{"class":68,"line":480},[66,757,758],{"class":110},"m2.transformation ",[66,760,146],{"class":106},[66,762,763],{"class":110}," T2\n",[66,765,767],{"class":68,"line":766},9,[66,768,137],{"emptyLinePlaceholder":136},[66,770,772,774,776],{"class":68,"line":771},10,[66,773,288],{"class":110},[66,775,146],{"class":106},[66,777,778],{"class":110}," tf.intersection_curves([m0, m1, m2])\n",[66,780,782],{"class":68,"line":781},11,[66,783,137],{"emptyLinePlaceholder":136},[66,785,787,789,792],{"class":68,"line":786},12,[66,788,302],{"class":206},[66,790,791],{"class":110},"(paths)  ",[66,793,309],{"class":308},[14,795,796],{},[47,797],{"alt":798,"src":799},"Step: curves between N meshes. Three posed copies of the Stanford bunny in one call; the pink curves are split into arcs where they cross, with the split points marked","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-intersection-curves-3-n-meshes.png",[14,801,802,803,805],{},"Three spheres make three pairwise circles. The circles cross each other on the surfaces. ",[32,804,34],{}," splits the curves at every crossing point, so here you get six open arcs instead of three circles.",[52,807,809],{"id":808},"working-with-results","Working with results",[14,811,812,814,815,818,819,821],{},[32,813,329],{}," is an ",[32,816,817],{},"OffsetBlockedArray",". Each block is one curve, as indices into ",[32,820,333],{},". A closed curve repeats its first index at the end.",[57,823,825],{"className":97,"code":824,"language":99,"meta":62,"style":62},"for path_ids in paths:\n    curve = curve_points[path_ids]          # (k, 3) polyline\n    closed = path_ids[0] == path_ids[-1]\n    length = np.linalg.norm(np.diff(curve, axis=0), axis=1).sum()\n",[32,826,827,841,854,882],{"__ignoreMap":62},[66,828,829,832,835,838],{"class":68,"line":69},[66,830,831],{"class":106},"for",[66,833,834],{"class":110}," path_ids ",[66,836,837],{"class":106},"in",[66,839,840],{"class":110}," paths:\n",[66,842,843,846,848,851],{"class":68,"line":120},[66,844,845],{"class":110},"    curve ",[66,847,146],{"class":106},[66,849,850],{"class":110}," curve_points[path_ids]          ",[66,852,853],{"class":308},"# (k, 3) polyline\n",[66,855,856,859,861,864,867,869,872,874,877,880],{"class":68,"line":133},[66,857,858],{"class":110},"    closed ",[66,860,146],{"class":106},[66,862,863],{"class":110}," path_ids[",[66,865,866],{"class":206},"0",[66,868,433],{"class":110},[66,870,871],{"class":106},"==",[66,873,863],{"class":110},[66,875,876],{"class":106},"-",[66,878,879],{"class":206},"1",[66,881,452],{"class":110},[66,883,884,887,889,892,895,897,899,902,904,906,908],{"class":68,"line":140},[66,885,886],{"class":110},"    length ",[66,888,146],{"class":106},[66,890,891],{"class":110}," np.linalg.norm(np.diff(curve, ",[66,893,894],{"class":213},"axis",[66,896,146],{"class":106},[66,898,866],{"class":206},[66,900,901],{"class":110},"), ",[66,903,894],{"class":213},[66,905,146],{"class":106},[66,907,879],{"class":206},[66,909,910],{"class":110},").sum()\n",[14,912,913,914,278],{},"To query the curves as geometry, turn them into an ",[32,915,916],{},"EdgeMesh",[57,918,920],{"className":97,"code":919,"language":99,"meta":62,"style":62},"edges = np.concatenate(\n    [np.stack([p[:-1], p[1:]], axis=1) for p in paths]\n).astype(np.int32)\n\ncurves = tf.EdgeMesh(edges, curve_points)\ntf.distance(curves, m0)  # 0.0, the curves lie on the surface\n",[32,921,922,932,968,973,977,987],{"__ignoreMap":62},[66,923,924,927,929],{"class":68,"line":69},[66,925,926],{"class":110},"edges ",[66,928,146],{"class":106},[66,930,931],{"class":110}," np.concatenate(\n",[66,933,934,937,939,941,944,946,949,951,953,955,958,960,963,965],{"class":68,"line":120},[66,935,936],{"class":110},"    [np.stack([p[:",[66,938,876],{"class":106},[66,940,879],{"class":206},[66,942,943],{"class":110},"], p[",[66,945,879],{"class":206},[66,947,948],{"class":110},":]], ",[66,950,894],{"class":213},[66,952,146],{"class":106},[66,954,879],{"class":206},[66,956,957],{"class":110},") ",[66,959,831],{"class":106},[66,961,962],{"class":110}," p ",[66,964,837],{"class":106},[66,966,967],{"class":110}," paths]\n",[66,969,970],{"class":68,"line":133},[66,971,972],{"class":110},").astype(np.int32)\n",[66,974,975],{"class":68,"line":140},[66,976,137],{"emptyLinePlaceholder":136},[66,978,979,982,984],{"class":68,"line":164},[66,980,981],{"class":110},"curves ",[66,983,146],{"class":106},[66,985,986],{"class":110}," tf.EdgeMesh(edges, curve_points)\n",[66,988,989,992],{"class":68,"line":465},[66,990,991],{"class":110},"tf.distance(curves, m0)  ",[66,993,994],{"class":308},"# 0.0, the curves lie on the surface\n",[14,996,997],{},"If you also want the boolean, ask for the curves on the same call:",[57,999,1001],{"className":97,"code":1000,"language":99,"meta":62,"style":62},"(faces, points), labels, face_labels, (paths, curve_points) = \\\n    tf.boolean_union(sphere, moved, return_curves=True)\n",[32,1002,1003,1013],{"__ignoreMap":62},[66,1004,1005,1008,1010],{"class":68,"line":69},[66,1006,1007],{"class":110},"(faces, points), labels, face_labels, (paths, curve_points) ",[66,1009,146],{"class":106},[66,1011,1012],{"class":110}," \\\n",[66,1014,1015,1018,1021,1023,1026],{"class":68,"line":120},[66,1016,1017],{"class":110},"    tf.boolean_union(sphere, moved, ",[66,1019,1020],{"class":213},"return_curves",[66,1022,146],{"class":106},[66,1024,1025],{"class":206},"True",[66,1027,1028],{"class":110},")\n",[14,1030,1031,1032,1039,1040,27],{},"To split faces along the curves instead, see ",[18,1033,1036],{"href":1034,"rel":1035},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Farrangement",[88],[32,1037,1038],{},"tf.mesh_arrangements",". For the curves where one mesh meets itself, see ",[18,1041,1044],{"href":1042,"rel":1043},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fintersect",[88],"detecting mesh self-intersections",[52,1046,1048],{"id":1047},"related-tutorials","Related tutorials",[1050,1051,1052,1060,1067,1074],"ul",{},[1053,1054,1055,1059],"li",{},[18,1056,1058],{"href":1057},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python","Fast Mesh Booleans in Python"," — exact union, intersection and difference of two meshes.",[1053,1061,1062,1066],{},[18,1063,1065],{"href":1064},"\u002Ftutorials\u002Ffast-mesh-collision-detection-in-python","Fast and Robust Mesh Collision Detection in Python"," — do two meshes touch, how far apart, and where.",[1053,1068,1069,1073],{},[18,1070,1072],{"href":1071},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-python","Fast and Robust Mesh Slicing and Cross-Sections in Python"," — plane sections, layers and slabs of a mesh.",[1053,1075,1076,1080],{},[18,1077,1079],{"href":1078},"\u002Ftutorials\u002Fisocontours-on-a-triangle-mesh-in-python","Fast and Robust Isocontours on a Triangle Mesh in Python"," — curves and bands of equal value on a scalar field.",[52,1082,1084],{"id":1083},"performance","Performance",[1086,1087],"headline-numbers",{":items":1088},"[{\"value\":\"3.5 ms\",\"label\":\"Intersection curves\",\"detail\":\"2×500K polygons\"},{\"value\":\"14 ms\",\"label\":\"Boolean union\",\"detail\":\"2×500K polygons\"},{\"value\":\"28.5 ms\",\"label\":\"Spatial tree build\",\"detail\":\"1,025,766 polygons, vs 302.7 ms MeshLib\"}]",[14,1090,1091,1096,1097],{},[66,1092,1095],{"className":1093},[1094],"tag-pill","Apple M4 Max"," ",[66,1098,1100],{"className":1099},[1094],"16 threads",[14,1102,1103,1104,1107],{},"The curve and boolean timings are on two 500K-polygon meshes. The tree build is measured from Python against MeshLib on a 1,025,766-polygon mesh: 28.5 ms against 302.7 ms, 10.6× faster. Build it ahead of time with ",[32,1105,1106],{},"mesh.build_tree()",". Changing a transformation does not rebuild it.",[14,1109,1110],{},[47,1111],{"alt":1112,"src":1113},"Benchmark chart: trueform against other Python libraries, polygon arrangements","\u002Fimg\u002Falgorithms\u002Fbenchmark-polygon-arrangements-python-2026.png",[14,1115,1116,1120,1121,1120,1125],{},[18,1117,1119],{"href":1118},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1122,1124],{"href":1042,"rel":1123},[88],"Documentation",[18,1126,1128],{"href":1127},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","How the algorithm works",[1130,1131],"cite-as",{"author":1132,"title":5},"Sajovic, {\\v{Z}}iga",[1134,1135,1136],"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":62,"searchDepth":120,"depth":120,"links":1138},[1139,1140,1141,1142,1143,1144,1145,1146],{"id":54,"depth":120,"text":55},{"id":93,"depth":120,"text":94},{"id":263,"depth":120,"text":264},{"id":343,"depth":120,"text":344},{"id":541,"depth":120,"text":542},{"id":808,"depth":120,"text":809},{"id":1047,"depth":120,"text":1048},{"id":1083,"depth":120,"text":1084},"2026-10-31","Compute the exact curves where two or more triangle meshes intersect in Python. Polylines out, with transformations and N-mesh input. 3.5 ms on 2×500K polygons. One pip install, NumPy in and out.","md",[1151,1152,1153,1154,1155,1156,1157,1158,1159,1160],"mesh intersection curves Python","mesh mesh intersection Python","intersection polyline mesh","surface intersection curve Python","numpy mesh intersection","trimesh intersection alternative","PyVista intersection alternative","VTK intersection polydata alternative","libigl intersect other","CGAL alternative Python",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Fmesh-intersection-curves-in-python.png","\u002Ftutorials\u002Fmesh-intersection-curves-in-python",{"title":5,"description":1148},"tutorials\u002Fmesh-intersection-curves-in-python",[99,1167,1168],"intersections","tutorial","1d-bcqjpx6KBr4Gb5ML4lJVmetHNglzlYwU88UClhVU",1793517812008]