[{"data":1,"prerenderedAt":1263},["ShallowReactive",2],{"tutorial-mesh-slicing-and-cross-sections-in-python":3},{"id":4,"title":5,"author":6,"body":7,"date":1238,"description":1239,"extension":1240,"keywords":1241,"meta":1254,"navigation":136,"ogImage":1255,"path":1256,"published":136,"seo":1257,"stem":1258,"tags":1259,"__hash__":1262},"tutorials\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-python.md","Fast and Robust Mesh Slicing and Cross-Sections in Python","Žiga Sajovic",{"type":8,"value":9,"toc":1227},"minimark",[10,28,40,44,51,56,81,91,95,162,172,214,218,228,297,303,309,313,324,444,450,464,467,498,502,505,577,583,586,651,654,717,720,724,727,818,824,827,916,919,923,926,1040,1043,1114,1120,1127,1144,1148,1178,1182,1185,1189,1201,1207,1219,1223],[11,12,13],"note",{},[14,15,16,17,22,23,27],"p",{},"Also available in ",[18,19,21],"a",{"href":20},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-cpp","C++"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-javascript","JavaScript",".",[14,29,30,31,35,36,39],{},"Slicing a mesh by a plane gives the cross-section: the closed curves where the plane cuts the surface. Stack many parallel slices and you have the layers of a 3D print, or the sections of a survey. In ",[32,33,34],"code",{},"trueform"," a slice is an isocontour. Give each point its signed distance to the plane, and the slice is where that distance is zero. One ",[32,37,38],{},"pip install",". NumPy arrays in, NumPy arrays out. This tutorial covers one slice, a slice by any plane, stacked slices, and sections along an axis.",[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\u002Fiso#isocontours\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fcross-section\",\"icon\":\"i-lucide-play\"}]",[14,45,46],{},[47,48],"img",{"alt":49,"src":50},"The Stanford dragon cut into 24 horizontal cross-sections, shown as stacked curves","\u002Fimg\u002Ftutorials\u002Fmesh-slicing.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-mesh","Loading the mesh",[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\nmesh = tf.Mesh(*tf.read_stl(\"part.stl\"))\n","python",[32,101,102,118,131,138],{"__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},"mesh ",[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",[14,163,164,167,168,171],{},[32,165,166],{},"tf.read_obj"," works the same way. If you already have NumPy arrays, pass them to ",[32,169,170],{},"tf.Mesh(faces, points)",". To follow along without a file, make a unit sphere:",[57,173,175],{"className":97,"code":174,"language":99,"meta":62,"style":62},"faces, points = tf.make_sphere_mesh(1.0, 200, 400)\nmesh = tf.Mesh(faces, points)\n",[32,176,177,205],{"__ignoreMap":62},[66,178,179,182,184,187,191,194,197,199,202],{"class":68,"line":69},[66,180,181],{"class":110},"faces, points ",[66,183,146],{"class":106},[66,185,186],{"class":110}," tf.make_sphere_mesh(",[66,188,190],{"class":189},"sDLfK","1.0",[66,192,193],{"class":110},", ",[66,195,196],{"class":189},"200",[66,198,193],{"class":110},[66,200,201],{"class":189},"400",[66,203,204],{"class":110},")\n",[66,206,207,209,211],{"class":68,"line":120},[66,208,143],{"class":110},[66,210,146],{"class":106},[66,212,213],{"class":110}," tf.Mesh(faces, points)\n",[52,215,217],{"id":216},"one-horizontal-slice","One horizontal slice",[14,219,220,221,224,225,27],{},"For a plane ",[32,222,223],{},"z = h",", the field is the height itself. The slice is its isocontour at ",[32,226,227],{},"h",[57,229,231],{"className":97,"code":230,"language":99,"meta":62,"style":62},"height = mesh.points[:, 2]\npaths, curve_points = tf.isocontours(mesh, height, 0.25)\n\nfor path_ids in paths:\n    loop = curve_points[path_ids]   # (n, 3), every z is 0.25\n",[32,232,233,249,264,268,282],{"__ignoreMap":62},[66,234,235,238,240,243,246],{"class":68,"line":69},[66,236,237],{"class":110},"height ",[66,239,146],{"class":106},[66,241,242],{"class":110}," mesh.points[:, ",[66,244,245],{"class":189},"2",[66,247,248],{"class":110},"]\n",[66,250,251,254,256,259,262],{"class":68,"line":120},[66,252,253],{"class":110},"paths, curve_points ",[66,255,146],{"class":106},[66,257,258],{"class":110}," tf.isocontours(mesh, height, ",[66,260,261],{"class":189},"0.25",[66,263,204],{"class":110},[66,265,266],{"class":68,"line":133},[66,267,137],{"emptyLinePlaceholder":136},[66,269,270,273,276,279],{"class":68,"line":140},[66,271,272],{"class":106},"for",[66,274,275],{"class":110}," path_ids ",[66,277,278],{"class":106},"in",[66,280,281],{"class":110}," paths:\n",[66,283,285,288,290,293],{"class":68,"line":284},5,[66,286,287],{"class":110},"    loop ",[66,289,146],{"class":106},[66,291,292],{"class":110}," curve_points[path_ids]   ",[66,294,296],{"class":295},"sAwPA","# (n, 3), every z is 0.25\n",[14,298,299],{},[47,300],{"alt":301,"src":302},"One horizontal slice through the Stanford dragon at z = 0.25: pink loops where the plane, shown in orange, cuts the surface","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-slicing-and-cross-sections-1-slice.png",[14,304,305,308],{},[32,306,307],{},"paths"," holds one index list per curve. On a closed mesh each curve is a closed loop, and the last id repeats the first. A part with holes or several bodies gives several loops in one slice.",[52,310,312],{"id":311},"slice-by-any-plane","Slice by any plane",[14,314,315,316,319,320,323],{},"For a tilted plane, use the signed distance to the plane as the field. ",[32,317,318],{},"tf.distance"," from points to a ",[32,321,322],{},"tf.Plane"," is signed: negative on one side, positive on the other.",[57,325,327],{"className":97,"code":326,"language":99,"meta":62,"style":62},"normal = np.array([1.0, 1.0, 1.0], dtype=np.float32)\nnormal \u002F= np.linalg.norm(normal)\norigin = np.array([0.0, 0.0, 0.2], dtype=np.float32)\n\nplane = tf.Plane.from_point_normal(origin, normal)\nfield = tf.distance(tf.Point(mesh.points), plane)\n\npaths, curve_points = tf.isocontours(mesh, field, 0.0)\n",[32,328,329,361,371,400,404,414,425,430],{"__ignoreMap":62},[66,330,331,334,336,339,341,343,345,347,349,352,356,358],{"class":68,"line":69},[66,332,333],{"class":110},"normal ",[66,335,146],{"class":106},[66,337,338],{"class":110}," np.array([",[66,340,190],{"class":189},[66,342,193],{"class":110},[66,344,190],{"class":189},[66,346,193],{"class":110},[66,348,190],{"class":189},[66,350,351],{"class":110},"], ",[66,353,355],{"class":354},"s9osk","dtype",[66,357,146],{"class":106},[66,359,360],{"class":110},"np.float32)\n",[66,362,363,365,368],{"class":68,"line":120},[66,364,333],{"class":110},[66,366,367],{"class":106},"\u002F=",[66,369,370],{"class":110}," np.linalg.norm(normal)\n",[66,372,373,376,378,380,383,385,387,389,392,394,396,398],{"class":68,"line":133},[66,374,375],{"class":110},"origin ",[66,377,146],{"class":106},[66,379,338],{"class":110},[66,381,382],{"class":189},"0.0",[66,384,193],{"class":110},[66,386,382],{"class":189},[66,388,193],{"class":110},[66,390,391],{"class":189},"0.2",[66,393,351],{"class":110},[66,395,355],{"class":354},[66,397,146],{"class":106},[66,399,360],{"class":110},[66,401,402],{"class":68,"line":140},[66,403,137],{"emptyLinePlaceholder":136},[66,405,406,409,411],{"class":68,"line":284},[66,407,408],{"class":110},"plane ",[66,410,146],{"class":106},[66,412,413],{"class":110}," tf.Plane.from_point_normal(origin, normal)\n",[66,415,417,420,422],{"class":68,"line":416},6,[66,418,419],{"class":110},"field ",[66,421,146],{"class":106},[66,423,424],{"class":110}," tf.distance(tf.Point(mesh.points), plane)\n",[66,426,428],{"class":68,"line":427},7,[66,429,137],{"emptyLinePlaceholder":136},[66,431,433,435,437,440,442],{"class":68,"line":432},8,[66,434,253],{"class":110},[66,436,146],{"class":106},[66,438,439],{"class":110}," tf.isocontours(mesh, field, ",[66,441,382],{"class":189},[66,443,204],{"class":110},[14,445,446],{},[47,447],{"alt":448,"src":449},"Slice by any plane: the Stanford dragon cut by a tilted plane with normal (1, 1, 1), the section loops in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-slicing-and-cross-sections-2-plane.png",[14,451,452,453,456,457,459,460,463],{},"Every curve point lies on the plane. Build the plane from float32 arrays when the mesh is float32: the field must have the mesh's dtype. A plane built from Python lists, such as ",[32,454,455],{},"tf.Plane(normal=[0, 0, 1], offset=0.0)",", comes out float64 and ",[32,458,318],{}," will raise a ",[32,461,462],{},"TypeError"," against a float32 mesh.",[14,465,466],{},"The same field is a dot product, if you prefer NumPy:",[57,468,470],{"className":97,"code":469,"language":99,"meta":62,"style":62},"field = mesh.points @ normal - np.float32(normal @ origin)\n",[32,471,472],{"__ignoreMap":62},[66,473,474,476,478,481,484,487,490,493,495],{"class":68,"line":69},[66,475,419],{"class":110},[66,477,146],{"class":106},[66,479,480],{"class":110}," mesh.points ",[66,482,483],{"class":106},"@",[66,485,486],{"class":110}," normal ",[66,488,489],{"class":106},"-",[66,491,492],{"class":110}," np.float32(normal ",[66,494,483],{"class":106},[66,496,497],{"class":110}," origin)\n",[52,499,501],{"id":500},"stacked-slices-for-3d-printing","Stacked slices for 3D printing",[14,503,504],{},"Parallel planes are levels of one field. Pass them all in one call:",[57,506,508],{"className":97,"code":507,"language":99,"meta":62,"style":62},"layer_height = 0.1\nz_min, z_max = height.min(), height.max()\nlevels = np.arange(z_min + layer_height \u002F 2, z_max, layer_height,\n                   dtype=np.float32)\n\npaths, curve_points = tf.isocontours(mesh, height, levels)\n",[32,509,510,520,530,555,564,568],{"__ignoreMap":62},[66,511,512,515,517],{"class":68,"line":69},[66,513,514],{"class":110},"layer_height ",[66,516,146],{"class":106},[66,518,519],{"class":189}," 0.1\n",[66,521,522,525,527],{"class":68,"line":120},[66,523,524],{"class":110},"z_min, z_max ",[66,526,146],{"class":106},[66,528,529],{"class":110}," height.min(), height.max()\n",[66,531,532,535,537,540,543,546,549,552],{"class":68,"line":133},[66,533,534],{"class":110},"levels ",[66,536,146],{"class":106},[66,538,539],{"class":110}," np.arange(z_min ",[66,541,542],{"class":106},"+",[66,544,545],{"class":110}," layer_height ",[66,547,548],{"class":106},"\u002F",[66,550,551],{"class":189}," 2",[66,553,554],{"class":110},", z_max, layer_height,\n",[66,556,557,560,562],{"class":68,"line":140},[66,558,559],{"class":354},"                   dtype",[66,561,146],{"class":106},[66,563,360],{"class":110},[66,565,566],{"class":68,"line":284},[66,567,137],{"emptyLinePlaceholder":136},[66,569,570,572,574],{"class":68,"line":416},[66,571,253],{"class":110},[66,573,146],{"class":106},[66,575,576],{"class":110}," tf.isocontours(mesh, height, levels)\n",[14,578,579],{},[47,580],{"alt":581,"src":582},"Stacked slices for 3D printing: every layer loop of the Stanford dragon at 0.1 spacing, drawn without the mesh","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-slicing-and-cross-sections-3-layers.png",[14,584,585],{},"This gives every loop of every layer. The levels must have the mesh's dtype. The loops do not come back in layer order. Each loop lies at one height, so read its layer from its first point:",[57,587,589],{"className":97,"code":588,"language":99,"meta":62,"style":62},"z_of_loop = np.array([curve_points[p[0], 2] for p in paths])\nlayer_of_loop = np.rint((z_of_loop - levels[0]) \u002F layer_height).astype(int)\n",[32,590,591,621],{"__ignoreMap":62},[66,592,593,596,598,601,604,606,608,611,613,616,618],{"class":68,"line":69},[66,594,595],{"class":110},"z_of_loop ",[66,597,146],{"class":106},[66,599,600],{"class":110}," np.array([curve_points[p[",[66,602,603],{"class":189},"0",[66,605,351],{"class":110},[66,607,245],{"class":189},[66,609,610],{"class":110},"] ",[66,612,272],{"class":106},[66,614,615],{"class":110}," p ",[66,617,278],{"class":106},[66,619,620],{"class":110}," paths])\n",[66,622,623,626,628,631,633,636,638,641,643,646,649],{"class":68,"line":120},[66,624,625],{"class":110},"layer_of_loop ",[66,627,146],{"class":106},[66,629,630],{"class":110}," np.rint((z_of_loop ",[66,632,489],{"class":106},[66,634,635],{"class":110}," levels[",[66,637,603],{"class":189},[66,639,640],{"class":110},"]) ",[66,642,548],{"class":106},[66,644,645],{"class":110}," layer_height).astype(",[66,647,648],{"class":189},"int",[66,650,204],{"class":110},[14,652,653],{},"Or slice layer by layer, and keep 2D outlines for a slicer:",[57,655,657],{"className":97,"code":656,"language":99,"meta":62,"style":62},"layers = []\nfor z in levels:\n    lp, lc = tf.isocontours(mesh, height, float(z))\n    layers.append([lc[p][:, :2] for p in lp])   # list of (n, 2) loops\n",[32,658,659,669,681,696],{"__ignoreMap":62},[66,660,661,664,666],{"class":68,"line":69},[66,662,663],{"class":110},"layers ",[66,665,146],{"class":106},[66,667,668],{"class":110}," []\n",[66,670,671,673,676,678],{"class":68,"line":120},[66,672,272],{"class":106},[66,674,675],{"class":110}," z ",[66,677,278],{"class":106},[66,679,680],{"class":110}," levels:\n",[66,682,683,686,688,690,693],{"class":68,"line":133},[66,684,685],{"class":110},"    lp, lc ",[66,687,146],{"class":106},[66,689,258],{"class":110},[66,691,692],{"class":189},"float",[66,694,695],{"class":110},"(z))\n",[66,697,698,701,703,705,707,709,711,714],{"class":68,"line":140},[66,699,700],{"class":110},"    layers.append([lc[p][:, :",[66,702,245],{"class":189},[66,704,610],{"class":110},[66,706,272],{"class":106},[66,708,615],{"class":110},[66,710,278],{"class":106},[66,712,713],{"class":110}," lp])   ",[66,715,716],{"class":295},"# list of (n, 2) loops\n",[14,718,719],{},"Slicing at the middle of each layer, as above, is the usual choice for print slicers.",[52,721,723],{"id":722},"survey-sections-along-an-axis","Survey sections along an axis",[14,725,726],{},"For sections across a road, a riverbed, or a tunnel, slice along the axis. The field is the chainage, the distance along the axis. The stations are its levels.",[57,728,730],{"className":97,"code":729,"language":99,"meta":62,"style":62},"direction = np.array([1.0, 0.0, 0.0], dtype=np.float32)\nchainage = mesh.points @ direction\nstations = np.arange(-0.8, 0.81, 0.2, dtype=np.float32)\n\npaths, curve_points = tf.isocontours(mesh, chainage, stations)\n",[32,731,732,759,773,805,809],{"__ignoreMap":62},[66,733,734,737,739,741,743,745,747,749,751,753,755,757],{"class":68,"line":69},[66,735,736],{"class":110},"direction ",[66,738,146],{"class":106},[66,740,338],{"class":110},[66,742,190],{"class":189},[66,744,193],{"class":110},[66,746,382],{"class":189},[66,748,193],{"class":110},[66,750,382],{"class":189},[66,752,351],{"class":110},[66,754,355],{"class":354},[66,756,146],{"class":106},[66,758,360],{"class":110},[66,760,761,764,766,768,770],{"class":68,"line":120},[66,762,763],{"class":110},"chainage ",[66,765,146],{"class":106},[66,767,480],{"class":110},[66,769,483],{"class":106},[66,771,772],{"class":110}," direction\n",[66,774,775,778,780,783,785,788,790,793,795,797,799,801,803],{"class":68,"line":133},[66,776,777],{"class":110},"stations ",[66,779,146],{"class":106},[66,781,782],{"class":110}," np.arange(",[66,784,489],{"class":106},[66,786,787],{"class":189},"0.8",[66,789,193],{"class":110},[66,791,792],{"class":189},"0.81",[66,794,193],{"class":110},[66,796,391],{"class":189},[66,798,193],{"class":110},[66,800,355],{"class":354},[66,802,146],{"class":106},[66,804,360],{"class":110},[66,806,807],{"class":68,"line":140},[66,808,137],{"emptyLinePlaceholder":136},[66,810,811,813,815],{"class":68,"line":284},[66,812,253],{"class":110},[66,814,146],{"class":106},[66,816,817],{"class":110}," tf.isocontours(mesh, chainage, stations)\n",[14,819,820],{},[47,821],{"alt":822,"src":823},"Survey sections along an axis: sections across the Stanford dragon at stations every 0.2 along x","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-slicing-and-cross-sections-4-sections.png",[14,825,826],{},"To draw a section, map its points to offset and elevation:",[57,828,830],{"className":97,"code":829,"language":99,"meta":62,"style":62},"up = np.array([0.0, 0.0, 1.0], dtype=np.float32)\nside = np.cross(up, direction)\n\nfor path_ids in paths:\n    s = curve_points[path_ids]\n    profile = np.column_stack([s @ side, s @ up])   # (n, 2)\n",[32,831,832,859,869,873,883,893],{"__ignoreMap":62},[66,833,834,837,839,841,843,845,847,849,851,853,855,857],{"class":68,"line":69},[66,835,836],{"class":110},"up ",[66,838,146],{"class":106},[66,840,338],{"class":110},[66,842,382],{"class":189},[66,844,193],{"class":110},[66,846,382],{"class":189},[66,848,193],{"class":110},[66,850,190],{"class":189},[66,852,351],{"class":110},[66,854,355],{"class":354},[66,856,146],{"class":106},[66,858,360],{"class":110},[66,860,861,864,866],{"class":68,"line":120},[66,862,863],{"class":110},"side ",[66,865,146],{"class":106},[66,867,868],{"class":110}," np.cross(up, direction)\n",[66,870,871],{"class":68,"line":133},[66,872,137],{"emptyLinePlaceholder":136},[66,874,875,877,879,881],{"class":68,"line":140},[66,876,272],{"class":106},[66,878,275],{"class":110},[66,880,278],{"class":106},[66,882,281],{"class":110},[66,884,885,888,890],{"class":68,"line":284},[66,886,887],{"class":110},"    s ",[66,889,146],{"class":106},[66,891,892],{"class":110}," curve_points[path_ids]\n",[66,894,895,898,900,903,905,908,910,913],{"class":68,"line":416},[66,896,897],{"class":110},"    profile ",[66,899,146],{"class":106},[66,901,902],{"class":110}," np.column_stack([s ",[66,904,483],{"class":106},[66,906,907],{"class":110}," side, s ",[66,909,483],{"class":106},[66,911,912],{"class":110}," up])   ",[66,914,915],{"class":295},"# (n, 2)\n",[14,917,918],{},"If the sections are not parallel, for example along a curved alignment, slice once per plane with the signed-distance field from the previous section.",[52,920,922],{"id":921},"working-with-results","Working with results",[14,924,925],{},"A closed loop is a polygon. Its area, by the shoelace formula:",[57,927,929],{"className":97,"code":928,"language":99,"meta":62,"style":62},"lp, lc = tf.isocontours(mesh, height, 0.0)\nring = lc[lp[0]][:, :2]\narea = 0.5 * abs(ring[:-1, 0] @ ring[1:, 1] - ring[1:, 0] @ ring[:-1, 1])\n# close to pi for the unit sphere's equator\n",[32,930,931,944,963,1035],{"__ignoreMap":62},[66,932,933,936,938,940,942],{"class":68,"line":69},[66,934,935],{"class":110},"lp, lc ",[66,937,146],{"class":106},[66,939,258],{"class":110},[66,941,382],{"class":189},[66,943,204],{"class":110},[66,945,946,949,951,954,956,959,961],{"class":68,"line":120},[66,947,948],{"class":110},"ring ",[66,950,146],{"class":106},[66,952,953],{"class":110}," lc[lp[",[66,955,603],{"class":189},[66,957,958],{"class":110},"]][:, :",[66,960,245],{"class":189},[66,962,248],{"class":110},[66,964,965,968,970,973,976,979,982,984,987,989,991,993,995,998,1000,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021,1024,1026,1028,1030,1032],{"class":68,"line":133},[66,966,967],{"class":110},"area ",[66,969,146],{"class":106},[66,971,972],{"class":189}," 0.5",[66,974,975],{"class":106}," *",[66,977,978],{"class":189}," abs",[66,980,981],{"class":110},"(ring[:",[66,983,489],{"class":106},[66,985,986],{"class":189},"1",[66,988,193],{"class":110},[66,990,603],{"class":189},[66,992,610],{"class":110},[66,994,483],{"class":106},[66,996,997],{"class":110}," ring[",[66,999,986],{"class":189},[66,1001,1002],{"class":110},":, ",[66,1004,986],{"class":189},[66,1006,610],{"class":110},[66,1008,489],{"class":106},[66,1010,997],{"class":110},[66,1012,986],{"class":189},[66,1014,1002],{"class":110},[66,1016,603],{"class":189},[66,1018,610],{"class":110},[66,1020,483],{"class":106},[66,1022,1023],{"class":110}," ring[:",[66,1025,489],{"class":106},[66,1027,986],{"class":189},[66,1029,193],{"class":110},[66,1031,986],{"class":189},[66,1033,1034],{"class":110},"])\n",[66,1036,1037],{"class":68,"line":140},[66,1038,1039],{"class":295},"# close to pi for the unit sphere's equator\n",[14,1041,1042],{},"To keep the solid part between two planes as a mesh, use isobands. They cut the mesh along the slices:",[57,1044,1046],{"className":97,"code":1045,"language":99,"meta":62,"style":62},"cuts = np.array([-0.2, 0.2], dtype=np.float32)\n(slab_faces, slab_points), labels, face_labels = tf.isobands(\n    mesh, height, cuts, selected_bands=[1])\n\ntf.write_obj((slab_faces, slab_points), \"slab.obj\")\n",[32,1047,1048,1073,1083,1100,1104],{"__ignoreMap":62},[66,1049,1050,1053,1055,1057,1059,1061,1063,1065,1067,1069,1071],{"class":68,"line":69},[66,1051,1052],{"class":110},"cuts ",[66,1054,146],{"class":106},[66,1056,338],{"class":110},[66,1058,489],{"class":106},[66,1060,391],{"class":189},[66,1062,193],{"class":110},[66,1064,391],{"class":189},[66,1066,351],{"class":110},[66,1068,355],{"class":354},[66,1070,146],{"class":106},[66,1072,360],{"class":110},[66,1074,1075,1078,1080],{"class":68,"line":120},[66,1076,1077],{"class":110},"(slab_faces, slab_points), labels, face_labels ",[66,1079,146],{"class":106},[66,1081,1082],{"class":110}," tf.isobands(\n",[66,1084,1085,1088,1091,1093,1096,1098],{"class":68,"line":133},[66,1086,1087],{"class":110},"    mesh, height, cuts, ",[66,1089,1090],{"class":354},"selected_bands",[66,1092,146],{"class":106},[66,1094,1095],{"class":110},"[",[66,1097,986],{"class":189},[66,1099,1034],{"class":110},[66,1101,1102],{"class":68,"line":140},[66,1103,137],{"emptyLinePlaceholder":136},[66,1105,1106,1109,1112],{"class":68,"line":284},[66,1107,1108],{"class":110},"tf.write_obj((slab_faces, slab_points), ",[66,1110,1111],{"class":76},"\"slab.obj\"",[66,1113,204],{"class":110},[14,1115,1116],{},[47,1117],{"alt":1118,"src":1119},"Slab between two planes: the part of the Stanford dragon between z = -0.2 and z = 0.2 kept with isobands, in orange, rims in pink","\u002Fimg\u002Ftutorials\u002Fsteps\u002Fmesh-slicing-and-cross-sections-5-slab.png",[14,1121,1122,1123,1126],{},"The slab is open at the cuts. ",[32,1124,1125],{},"face_labels"," maps each slab face back to its source face.",[14,1128,1129,1130,1134,1135,1139,1140,27],{},"Slicing works for any scalar field, not only planes. See ",[18,1131,1133],{"href":1132},"\u002Ftutorials\u002Fisocontours-on-a-triangle-mesh-in-python","isocontours on a triangle mesh",". To cut a mesh by another mesh, use ",[18,1136,1138],{"href":1137},"\u002Ftutorials\u002Fmesh-intersection-curves-in-python","intersection curves"," or ",[18,1141,1143],{"href":1142},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python","booleans",[52,1145,1147],{"id":1146},"related-tutorials","Related tutorials",[1149,1150,1151,1158,1165,1171],"ul",{},[1152,1153,1154,1157],"li",{},[18,1155,1156],{"href":1132},"Fast and Robust Isocontours on a Triangle Mesh in Python"," — curves and bands of equal value on a scalar field.",[1152,1159,1160,1164],{},[18,1161,1163],{"href":1162},"\u002Ftutorials\u002Fconstrained-delaunay-triangulation-in-python","Fast and Exact Constrained Delaunay Triangulation in Python"," — triangulate points and outlines, with holes and constraints.",[1152,1166,1167,1170],{},[18,1168,1169],{"href":1137},"Fast and Exact Mesh Intersection Curves in Python"," — exact curves where two or more meshes cross.",[1152,1172,1173,1177],{},[18,1174,1176],{"href":1175},"\u002Ftutorials\u002Ffast-connected-components-in-python","Fast Connected Components in Python"," — split a mesh into parts and find its boundary loops.",[52,1179,1181],{"id":1180},"performance","Performance",[14,1183,1184],{},"A stack of slices is one isocontour call with many levels.",[1186,1187],"headline-numbers",{":items":1188},"[{\"value\":\"4.3ms\",\"label\":\"Isocontours\",\"detail\":\"1,025,766 polygons (VTK: 155 ms)\"},{\"value\":\"8.7ms\",\"label\":\"Many levels\",\"detail\":\"480,868 polygons (VTK: 212.5 ms)\"},{\"value\":\"36×\",\"label\":\"Faster than VTK\",\"detail\":\"Isocontours, 1,025,766 polygons\"}]",[14,1190,1191,1196,1197],{},[66,1192,1195],{"className":1193},[1194],"tag-pill","Apple M4 Max"," ",[66,1198,1200],{"className":1199},[1194],"16 threads",[14,1202,1203],{},[47,1204],{"alt":1205,"src":1206},"Benchmark chart: trueform against other Python libraries, isocontours","\u002Fimg\u002Falgorithms\u002Fbenchmark-isocontours-python-2026.png",[14,1208,1209,1213,1214],{},[18,1210,1212],{"href":1211},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1215,1218],{"href":1216,"rel":1217},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fiso#isocontours",[88],"Documentation",[1220,1221],"cite-as",{"author":1222,"title":5},"Sajovic, {\\v{Z}}iga",[1224,1225,1226],"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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":62,"searchDepth":120,"depth":120,"links":1228},[1229,1230,1231,1232,1233,1234,1235,1236,1237],{"id":54,"depth":120,"text":55},{"id":93,"depth":120,"text":94},{"id":216,"depth":120,"text":217},{"id":311,"depth":120,"text":312},{"id":500,"depth":120,"text":501},{"id":722,"depth":120,"text":723},{"id":921,"depth":120,"text":922},{"id":1146,"depth":120,"text":1147},{"id":1180,"depth":120,"text":1181},"2026-10-31","Slice a triangle mesh by planes in Python. One cross-section, any tilted plane, stacked layers for 3D printing, and survey sections along an axis. One pip install, NumPy arrays in and out.","md",[1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253],"mesh slicing Python","slice mesh by plane Python","mesh cross-section Python","plane mesh intersection Python","3D printing slicer Python","STL slicing Python","survey cross-sections mesh","meshcut alternative","trimesh section alternative","trimesh section_multiplane alternative","PyVista slice alternative","VTK cutter alternative",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-python.png","\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-python",{"title":5,"description":1239},"tutorials\u002Fmesh-slicing-and-cross-sections-in-python",[99,1260,1261],"slicing","tutorial","kjSg9DRh_SnF71d7ye91PZ8oVMLZxUfW3C6r6Xx--KQ",1793517812008]