[{"data":1,"prerenderedAt":1407},["ShallowReactive",2],{"tutorial-mesh-slicing-and-cross-sections-in-javascript":3},{"id":4,"title":5,"author":6,"body":7,"date":1382,"description":1383,"extension":1384,"keywords":1385,"meta":1396,"navigation":151,"ogImage":1397,"path":1398,"published":151,"seo":1399,"stem":1400,"tags":1401,"__hash__":1406},"tutorials\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-javascript.md","Fast and Robust Mesh Slicing and Cross-Sections in JavaScript","Žiga Sajovic",{"type":8,"value":9,"toc":1371},"minimark",[10,28,40,44,51,56,81,94,98,185,196,233,237,252,366,372,375,390,393,479,483,486,612,618,621,709,712,716,724,993,999,1003,1006,1087,1093,1096,1156,1160,1188,1191,1218,1221,1246,1260,1276,1280,1309,1313,1317,1337,1345,1351,1363,1367],[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-python","Python"," and ",[18,24,26],{"href":25},"\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-cpp","C++",".",[14,29,30,31,35,36,39],{},"A cross-section is the curve where a plane cuts a mesh. Slicing is many cross-sections at once, one per layer. 3D printing, CNC, inspection and medical imaging all need them. In ",[32,33,34],"code",{},"trueform"," a slice is an isocontour of the distance to a plane, so one call gives one section or a whole stack. One ",[32,37,38],{},"npm install",". It runs as WebAssembly in Node.js and the browser, with TypeScript types. This tutorial covers a single plane, many layers, filled sections, and cutting the mesh into slabs.",[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\u002Fts\u002Fmodules\u002Fiso#isocontours\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Flive-examples\u002Fslicing\",\"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","npm install @polydera\u002Ftrueform\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","npm",[66,75,77],{"class":76},"sU2Wk"," install",[66,79,80],{"class":76}," @polydera\u002Ftrueform\n",[14,82,83,84,87,88,27],{},"The WebAssembly binary loads on first use. In the browser, multithreading needs ",[32,85,86],{},"SharedArrayBuffer"," headers. See the ",[18,89,93],{"href":90,"rel":91},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fgetting-started\u002Finstallation",[92],"nofollow","installation guide",[52,95,97],{"id":96},"loading-the-mesh","Loading the mesh",[57,99,103],{"className":100,"code":101,"language":102,"meta":62,"style":62},"language-typescript shiki shiki-themes github-dark","import * as tf from \"@polydera\u002Ftrueform\";\nimport { readFileSync } from \"node:fs\";\n\nconst mesh = tf.readStl(readFileSync(\"part.stl\"));\n","typescript",[32,104,105,131,146,153],{"__ignoreMap":62},[66,106,107,111,115,118,122,125,128],{"class":68,"line":69},[66,108,110],{"class":109},"snl16","import",[66,112,114],{"class":113},"sDLfK"," *",[66,116,117],{"class":109}," as",[66,119,121],{"class":120},"s95oV"," tf ",[66,123,124],{"class":109},"from",[66,126,127],{"class":76}," \"@polydera\u002Ftrueform\"",[66,129,130],{"class":120},";\n",[66,132,134,136,139,141,144],{"class":68,"line":133},2,[66,135,110],{"class":109},[66,137,138],{"class":120}," { readFileSync } ",[66,140,124],{"class":109},[66,142,143],{"class":76}," \"node:fs\"",[66,145,130],{"class":120},[66,147,149],{"class":68,"line":148},3,[66,150,152],{"emptyLinePlaceholder":151},true,"\n",[66,154,156,159,162,165,168,171,174,177,179,182],{"class":68,"line":155},4,[66,157,158],{"class":109},"const",[66,160,161],{"class":113}," mesh",[66,163,164],{"class":109}," =",[66,166,167],{"class":120}," tf.",[66,169,170],{"class":72},"readStl",[66,172,173],{"class":120},"(",[66,175,176],{"class":72},"readFileSync",[66,178,173],{"class":120},[66,180,181],{"class":76},"\"part.stl\"",[66,183,184],{"class":120},"));\n",[14,186,187,188,191,192,195],{},"In the browser, pass the bytes from ",[32,189,190],{},"fetch"," instead. ",[32,193,194],{},"tf.readObj"," works the same way. To follow along without a file, make a unit sphere:",[57,197,199],{"className":100,"code":198,"language":102,"meta":62,"style":62},"const mesh = tf.sphereMesh(1.0, 100, 200);\n",[32,200,201],{"__ignoreMap":62},[66,202,203,205,207,209,211,214,216,219,222,225,227,230],{"class":68,"line":69},[66,204,158],{"class":109},[66,206,161],{"class":113},[66,208,164],{"class":109},[66,210,167],{"class":120},[66,212,213],{"class":72},"sphereMesh",[66,215,173],{"class":120},[66,217,218],{"class":113},"1.0",[66,220,221],{"class":120},", ",[66,223,224],{"class":113},"100",[66,226,221],{"class":120},[66,228,229],{"class":113},"200",[66,231,232],{"class":120},");\n",[52,234,236],{"id":235},"one-cross-section","One cross-section",[14,238,239,240,243,244,247,248,251],{},"A plane is a normal and an offset. Points on it satisfy ",[32,241,242],{},"n · x + offset = 0",", so the plane ",[32,245,246],{},"z = 0.25"," has offset ",[32,249,250],{},"-0.25",". Take the signed distance from every mesh point to the plane, then extract the zero level:",[57,253,255],{"className":100,"code":254,"language":102,"meta":62,"style":62},"const plane = tf.plane(tf.vector(0, 0, 1), -0.25);\nconst d = tf.distance(tf.point(mesh.points), plane);  \u002F\u002F one value per point\n\nconst section = tf.isocontours(mesh, d, 0.0);\n\u002F\u002F section.paths:  one index list per curve\n\u002F\u002F section.points: NDArray [n, 3]\n",[32,256,257,302,328,332,354,360],{"__ignoreMap":62},[66,258,259,261,264,266,268,271,274,277,279,282,284,286,288,291,294,297,300],{"class":68,"line":69},[66,260,158],{"class":109},[66,262,263],{"class":113}," plane",[66,265,164],{"class":109},[66,267,167],{"class":120},[66,269,270],{"class":72},"plane",[66,272,273],{"class":120},"(tf.",[66,275,276],{"class":72},"vector",[66,278,173],{"class":120},[66,280,281],{"class":113},"0",[66,283,221],{"class":120},[66,285,281],{"class":113},[66,287,221],{"class":120},[66,289,290],{"class":113},"1",[66,292,293],{"class":120},"), ",[66,295,296],{"class":109},"-",[66,298,299],{"class":113},"0.25",[66,301,232],{"class":120},[66,303,304,306,309,311,313,316,318,321,324],{"class":68,"line":133},[66,305,158],{"class":109},[66,307,308],{"class":113}," d",[66,310,164],{"class":109},[66,312,167],{"class":120},[66,314,315],{"class":72},"distance",[66,317,273],{"class":120},[66,319,320],{"class":72},"point",[66,322,323],{"class":120},"(mesh.points), plane);  ",[66,325,327],{"class":326},"sAwPA","\u002F\u002F one value per point\n",[66,329,330],{"class":68,"line":148},[66,331,152],{"emptyLinePlaceholder":151},[66,333,334,336,339,341,343,346,349,352],{"class":68,"line":155},[66,335,158],{"class":109},[66,337,338],{"class":113}," section",[66,340,164],{"class":109},[66,342,167],{"class":120},[66,344,345],{"class":72},"isocontours",[66,347,348],{"class":120},"(mesh, d, ",[66,350,351],{"class":113},"0.0",[66,353,232],{"class":120},[66,355,357],{"class":68,"line":356},5,[66,358,359],{"class":326},"\u002F\u002F section.paths:  one index list per curve\n",[66,361,363],{"class":68,"line":362},6,[66,364,365],{"class":326},"\u002F\u002F section.points: NDArray [n, 3]\n",[14,367,368],{},[47,369],{"alt":370,"src":371},"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,373,374],{},"On the sphere there is one closed curve, a circle of radius √(1 − 0.25²). A part with holes or several bodies gives several curves.",[14,376,377,378,381,382,385,386,389],{},"Build the normal with ",[32,379,380],{},"tf.vector",". In 0.10.8, a plain array or ",[32,383,384],{},"Float32Array"," normal passed with a separate offset gives a wrong plane. The packed form ",[32,387,388],{},"tf.plane([0, 0, 1, -0.25])"," works too.",[14,391,392],{},"Any direction works. Normalize the normal so the distance is in model units:",[57,394,396],{"className":100,"code":395,"language":102,"meta":62,"style":62},"const tilted = tf.plane(tf.normalize(tf.vector(1, 0, 1)), 0);\nconst dt = tf.distance(tf.point(mesh.points), tilted);\nconst tiltedSection = tf.isocontours(mesh, dt, 0.0);\n",[32,397,398,439,459],{"__ignoreMap":62},[66,399,400,402,405,407,409,411,413,416,418,420,422,424,426,428,430,432,435,437],{"class":68,"line":69},[66,401,158],{"class":109},[66,403,404],{"class":113}," tilted",[66,406,164],{"class":109},[66,408,167],{"class":120},[66,410,270],{"class":72},[66,412,273],{"class":120},[66,414,415],{"class":72},"normalize",[66,417,273],{"class":120},[66,419,276],{"class":72},[66,421,173],{"class":120},[66,423,290],{"class":113},[66,425,221],{"class":120},[66,427,281],{"class":113},[66,429,221],{"class":120},[66,431,290],{"class":113},[66,433,434],{"class":120},")), ",[66,436,281],{"class":113},[66,438,232],{"class":120},[66,440,441,443,446,448,450,452,454,456],{"class":68,"line":133},[66,442,158],{"class":109},[66,444,445],{"class":113}," dt",[66,447,164],{"class":109},[66,449,167],{"class":120},[66,451,315],{"class":72},[66,453,273],{"class":120},[66,455,320],{"class":72},[66,457,458],{"class":120},"(mesh.points), tilted);\n",[66,460,461,463,466,468,470,472,475,477],{"class":68,"line":148},[66,462,158],{"class":109},[66,464,465],{"class":113}," tiltedSection",[66,467,164],{"class":109},[66,469,167],{"class":120},[66,471,345],{"class":72},[66,473,474],{"class":120},"(mesh, dt, ",[66,476,351],{"class":113},[66,478,232],{"class":120},[52,480,482],{"id":481},"many-layers","Many layers",[14,484,485],{},"For layers along an axis, the field is the coordinate itself. Pass all layer heights in one call:",[57,487,489],{"className":100,"code":488,"language":102,"meta":62,"style":62},"const z = mesh.points.take(null, 2);  \u002F\u002F the z column\nconst layer = 0.1;\nconst levels = Array.from({ length: 19 }, (_, i) => -0.9 + layer * i);\n\nconst slices = tf.isocontours(mesh, z, levels);\n",[32,490,491,522,536,592,596],{"__ignoreMap":62},[66,492,493,495,498,500,503,506,508,511,513,516,519],{"class":68,"line":69},[66,494,158],{"class":109},[66,496,497],{"class":113}," z",[66,499,164],{"class":109},[66,501,502],{"class":120}," mesh.points.",[66,504,505],{"class":72},"take",[66,507,173],{"class":120},[66,509,510],{"class":113},"null",[66,512,221],{"class":120},[66,514,515],{"class":113},"2",[66,517,518],{"class":120},");  ",[66,520,521],{"class":326},"\u002F\u002F the z column\n",[66,523,524,526,529,531,534],{"class":68,"line":133},[66,525,158],{"class":109},[66,527,528],{"class":113}," layer",[66,530,164],{"class":109},[66,532,533],{"class":113}," 0.1",[66,535,130],{"class":120},[66,537,538,540,543,545,548,550,553,556,559,563,565,568,571,574,577,580,583,586,589],{"class":68,"line":148},[66,539,158],{"class":109},[66,541,542],{"class":113}," levels",[66,544,164],{"class":109},[66,546,547],{"class":120}," Array.",[66,549,124],{"class":72},[66,551,552],{"class":120},"({ length: ",[66,554,555],{"class":113},"19",[66,557,558],{"class":120}," }, (",[66,560,562],{"class":561},"s9osk","_",[66,564,221],{"class":120},[66,566,567],{"class":561},"i",[66,569,570],{"class":120},") ",[66,572,573],{"class":109},"=>",[66,575,576],{"class":109}," -",[66,578,579],{"class":113},"0.9",[66,581,582],{"class":109}," +",[66,584,585],{"class":120}," layer ",[66,587,588],{"class":109},"*",[66,590,591],{"class":120}," i);\n",[66,593,594],{"class":68,"line":155},[66,595,152],{"emptyLinePlaceholder":151},[66,597,598,600,603,605,607,609],{"class":68,"line":356},[66,599,158],{"class":109},[66,601,602],{"class":113}," slices",[66,604,164],{"class":109},[66,606,167],{"class":120},[66,608,345],{"class":72},[66,610,611],{"class":120},"(mesh, z, levels);\n",[14,613,614],{},[47,615],{"alt":616,"src":617},"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,619,620],{},"The curves do not come back in level order. For height slices, read the layer from the curve itself: every point on it has the layer height as its z.",[57,622,624],{"className":100,"code":623,"language":102,"meta":62,"style":62},"const p = slices.points.data;\nfor (const pathIds of slices.paths) {\n  const ids = pathIds.data;\n  const height = p[3 * ids[0] + 2];\n}\n",[32,625,626,638,657,670,704],{"__ignoreMap":62},[66,627,628,630,633,635],{"class":68,"line":69},[66,629,158],{"class":109},[66,631,632],{"class":113}," p",[66,634,164],{"class":109},[66,636,637],{"class":120}," slices.points.data;\n",[66,639,640,643,646,648,651,654],{"class":68,"line":133},[66,641,642],{"class":109},"for",[66,644,645],{"class":120}," (",[66,647,158],{"class":109},[66,649,650],{"class":113}," pathIds",[66,652,653],{"class":109}," of",[66,655,656],{"class":120}," slices.paths) {\n",[66,658,659,662,665,667],{"class":68,"line":148},[66,660,661],{"class":109},"  const",[66,663,664],{"class":113}," ids",[66,666,164],{"class":109},[66,668,669],{"class":120}," pathIds.data;\n",[66,671,672,674,677,679,682,685,687,690,692,695,698,701],{"class":68,"line":155},[66,673,661],{"class":109},[66,675,676],{"class":113}," height",[66,678,164],{"class":109},[66,680,681],{"class":120}," p[",[66,683,684],{"class":113},"3",[66,686,114],{"class":109},[66,688,689],{"class":120}," ids[",[66,691,281],{"class":113},[66,693,694],{"class":120},"] ",[66,696,697],{"class":109},"+",[66,699,700],{"class":113}," 2",[66,702,703],{"class":120},"];\n",[66,705,706],{"class":68,"line":356},[66,707,708],{"class":120},"}\n",[14,710,711],{},"For layers along any other direction, use the plane distance from the previous section as the field and pass the offsets as levels.",[52,713,715],{"id":714},"filled-sections","Filled sections",[14,717,718,719,723],{},"A closed section curve repeats its first index at the end. Drop it, gather the points, and ",[18,720,722],{"href":721},"\u002Ftutorials\u002Fconstrained-delaunay-triangulation-in-javascript","triangulate the polygon",":",[57,725,727],{"className":100,"code":726,"language":102,"meta":62,"style":62},"function loopPoints(curves, i) {\n  const ids = curves.paths.get(i).data;\n  const p = curves.points.data;\n  const n = ids[0] === ids[ids.length - 1] ? ids.length - 1 : ids.length;\n  const out = new Float32Array(3 * n);\n  for (let k = 0; k \u003C n; k++) out.set(p.subarray(3 * ids[k], 3 * ids[k] + 3), 3 * k);\n  return out;\n}\n\nconst cap = tf.triangulate(tf.polygon(loopPoints(section, 0)));\ntf.area(cap);  \u002F\u002F section area\n",[32,728,729,749,766,777,829,853,926,935,940,945,978],{"__ignoreMap":62},[66,730,731,734,737,739,742,744,746],{"class":68,"line":69},[66,732,733],{"class":109},"function",[66,735,736],{"class":72}," loopPoints",[66,738,173],{"class":120},[66,740,741],{"class":561},"curves",[66,743,221],{"class":120},[66,745,567],{"class":561},[66,747,748],{"class":120},") {\n",[66,750,751,753,755,757,760,763],{"class":68,"line":133},[66,752,661],{"class":109},[66,754,664],{"class":113},[66,756,164],{"class":109},[66,758,759],{"class":120}," curves.paths.",[66,761,762],{"class":72},"get",[66,764,765],{"class":120},"(i).data;\n",[66,767,768,770,772,774],{"class":68,"line":148},[66,769,661],{"class":109},[66,771,632],{"class":113},[66,773,164],{"class":109},[66,775,776],{"class":120}," curves.points.data;\n",[66,778,779,781,784,786,788,790,792,795,798,801,803,806,808,811,814,816,818,820,823,825,827],{"class":68,"line":155},[66,780,661],{"class":109},[66,782,783],{"class":113}," n",[66,785,164],{"class":109},[66,787,689],{"class":120},[66,789,281],{"class":113},[66,791,694],{"class":120},[66,793,794],{"class":109},"===",[66,796,797],{"class":120}," ids[ids.",[66,799,800],{"class":113},"length",[66,802,576],{"class":109},[66,804,805],{"class":113}," 1",[66,807,694],{"class":120},[66,809,810],{"class":109},"?",[66,812,813],{"class":120}," ids.",[66,815,800],{"class":113},[66,817,576],{"class":109},[66,819,805],{"class":113},[66,821,822],{"class":109}," :",[66,824,813],{"class":120},[66,826,800],{"class":113},[66,828,130],{"class":120},[66,830,831,833,836,838,841,844,846,848,850],{"class":68,"line":356},[66,832,661],{"class":109},[66,834,835],{"class":113}," out",[66,837,164],{"class":109},[66,839,840],{"class":109}," new",[66,842,843],{"class":72}," Float32Array",[66,845,173],{"class":120},[66,847,684],{"class":113},[66,849,114],{"class":109},[66,851,852],{"class":120}," n);\n",[66,854,855,858,860,863,866,869,872,875,878,881,884,887,890,893,896,898,900,902,905,907,909,912,914,917,919,921,923],{"class":68,"line":362},[66,856,857],{"class":109},"  for",[66,859,645],{"class":120},[66,861,862],{"class":109},"let",[66,864,865],{"class":120}," k ",[66,867,868],{"class":109},"=",[66,870,871],{"class":113}," 0",[66,873,874],{"class":120},"; k ",[66,876,877],{"class":109},"\u003C",[66,879,880],{"class":120}," n; k",[66,882,883],{"class":109},"++",[66,885,886],{"class":120},") out.",[66,888,889],{"class":72},"set",[66,891,892],{"class":120},"(p.",[66,894,895],{"class":72},"subarray",[66,897,173],{"class":120},[66,899,684],{"class":113},[66,901,114],{"class":109},[66,903,904],{"class":120}," ids[k], ",[66,906,684],{"class":113},[66,908,114],{"class":109},[66,910,911],{"class":120}," ids[k] ",[66,913,697],{"class":109},[66,915,916],{"class":113}," 3",[66,918,293],{"class":120},[66,920,684],{"class":113},[66,922,114],{"class":109},[66,924,925],{"class":120}," k);\n",[66,927,929,932],{"class":68,"line":928},7,[66,930,931],{"class":109},"  return",[66,933,934],{"class":120}," out;\n",[66,936,938],{"class":68,"line":937},8,[66,939,708],{"class":120},[66,941,943],{"class":68,"line":942},9,[66,944,152],{"emptyLinePlaceholder":151},[66,946,948,950,953,955,957,960,962,965,967,970,973,975],{"class":68,"line":947},10,[66,949,158],{"class":109},[66,951,952],{"class":113}," cap",[66,954,164],{"class":109},[66,956,167],{"class":120},[66,958,959],{"class":72},"triangulate",[66,961,273],{"class":120},[66,963,964],{"class":72},"polygon",[66,966,173],{"class":120},[66,968,969],{"class":72},"loopPoints",[66,971,972],{"class":120},"(section, ",[66,974,281],{"class":113},[66,976,977],{"class":120},")));\n",[66,979,981,984,987,990],{"class":68,"line":980},11,[66,982,983],{"class":120},"tf.",[66,985,986],{"class":72},"area",[66,988,989],{"class":120},"(cap);  ",[66,991,992],{"class":326},"\u002F\u002F section area\n",[14,994,995,996,998],{},"On the sphere at ",[32,997,246],{}," the area comes out at 2.94, against π(1 − 0.25²) ≈ 2.95 for the true circle. This fills one loop. A section with holes needs the loops nested first.",[52,1000,1002],{"id":1001},"slabs","Slabs",[14,1004,1005],{},"To cut the mesh itself, not just get the curves, use isobands. The mesh is split along the section curves, so they become edges:",[57,1007,1009],{"className":100,"code":1008,"language":102,"meta":62,"style":62},"const { mesh: cut, labels } = tf.isobands(mesh, z, [-0.5, 0.0, 0.5]);\nconst { components } = tf.splitIntoComponents(cut, labels);\n\u002F\u002F four slabs, bottom to top\n",[32,1010,1011,1061,1082],{"__ignoreMap":62},[66,1012,1013,1015,1018,1021,1024,1027,1029,1032,1035,1037,1039,1042,1045,1047,1050,1052,1054,1056,1058],{"class":68,"line":69},[66,1014,158],{"class":109},[66,1016,1017],{"class":120}," { ",[66,1019,1020],{"class":561},"mesh",[66,1022,1023],{"class":120},": ",[66,1025,1026],{"class":113},"cut",[66,1028,221],{"class":120},[66,1030,1031],{"class":113},"labels",[66,1033,1034],{"class":120}," } ",[66,1036,868],{"class":109},[66,1038,167],{"class":120},[66,1040,1041],{"class":72},"isobands",[66,1043,1044],{"class":120},"(mesh, z, [",[66,1046,296],{"class":109},[66,1048,1049],{"class":113},"0.5",[66,1051,221],{"class":120},[66,1053,351],{"class":113},[66,1055,221],{"class":120},[66,1057,1049],{"class":113},[66,1059,1060],{"class":120},"]);\n",[66,1062,1063,1065,1067,1070,1072,1074,1076,1079],{"class":68,"line":133},[66,1064,158],{"class":109},[66,1066,1017],{"class":120},[66,1068,1069],{"class":113},"components",[66,1071,1034],{"class":120},[66,1073,868],{"class":109},[66,1075,167],{"class":120},[66,1077,1078],{"class":72},"splitIntoComponents",[66,1080,1081],{"class":120},"(cut, labels);\n",[66,1083,1084],{"class":68,"line":148},[66,1085,1086],{"class":326},"\u002F\u002F four slabs, bottom to top\n",[14,1088,1089],{},[47,1090],{"alt":1091,"src":1092},"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,1094,1095],{},"Keep one slab, with its rims:",[57,1097,1099],{"className":100,"code":1098,"language":102,"meta":62,"style":62},"const { mesh: band, curves } = tf.isobands(\n  mesh, z, [-0.5, 0.5], { selectedBands: [1], returnCurves: true });\n",[32,1100,1101,1129],{"__ignoreMap":62},[66,1102,1103,1105,1107,1109,1111,1114,1116,1118,1120,1122,1124,1126],{"class":68,"line":69},[66,1104,158],{"class":109},[66,1106,1017],{"class":120},[66,1108,1020],{"class":561},[66,1110,1023],{"class":120},[66,1112,1113],{"class":113},"band",[66,1115,221],{"class":120},[66,1117,741],{"class":113},[66,1119,1034],{"class":120},[66,1121,868],{"class":109},[66,1123,167],{"class":120},[66,1125,1041],{"class":72},[66,1127,1128],{"class":120},"(\n",[66,1130,1131,1134,1136,1138,1140,1142,1145,1147,1150,1153],{"class":68,"line":133},[66,1132,1133],{"class":120},"  mesh, z, [",[66,1135,296],{"class":109},[66,1137,1049],{"class":113},[66,1139,221],{"class":120},[66,1141,1049],{"class":113},[66,1143,1144],{"class":120},"], { selectedBands: [",[66,1146,290],{"class":113},[66,1148,1149],{"class":120},"], returnCurves: ",[66,1151,1152],{"class":113},"true",[66,1154,1155],{"class":120}," });\n",[52,1157,1159],{"id":1158},"working-with-results","Working with results",[14,1161,1162,1165,1166,22,1169,1172,1173,1176,1177,22,1180,1183,1184,1187],{},[32,1163,1164],{},"paths"," is an ",[32,1167,1168],{},"OffsetBlockedBuffer",[32,1170,1171],{},"points"," an ",[32,1174,1175],{},"NDArray",". ",[32,1178,1179],{},"curves.paths.offsets.data",[32,1181,1182],{},"curves.paths.data.data"," give the raw ",[32,1185,1186],{},"Int32Array","s, ready for a line renderer or a G-code writer.",[14,1189,1190],{},"To show the sections as solid geometry, sweep them into tubes:",[57,1192,1194],{"className":100,"code":1193,"language":102,"meta":62,"style":62},"const tubes = tf.tubeMesh(slices, 0.005);\n",[32,1195,1196],{"__ignoreMap":62},[66,1197,1198,1200,1203,1205,1207,1210,1213,1216],{"class":68,"line":69},[66,1199,158],{"class":109},[66,1201,1202],{"class":113}," tubes",[66,1204,164],{"class":109},[66,1206,167],{"class":120},[66,1208,1209],{"class":72},"tubeMesh",[66,1211,1212],{"class":120},"(slices, ",[66,1214,1215],{"class":113},"0.005",[66,1217,232],{"class":120},[14,1219,1220],{},"Write the slab to a file:",[57,1222,1224],{"className":100,"code":1223,"language":102,"meta":62,"style":62},"const stlBytes = tf.writeStl(band).data;  \u002F\u002F Int8Array, save or download\n",[32,1225,1226],{"__ignoreMap":62},[66,1227,1228,1230,1233,1235,1237,1240,1243],{"class":68,"line":69},[66,1229,158],{"class":109},[66,1231,1232],{"class":113}," stlBytes",[66,1234,164],{"class":109},[66,1236,167],{"class":120},[66,1238,1239],{"class":72},"writeStl",[66,1241,1242],{"class":120},"(band).data;  ",[66,1244,1245],{"class":326},"\u002F\u002F Int8Array, save or download\n",[14,1247,1248,1249,1252,1253,22,1256,1259],{},"The field is computed from ",[32,1250,1251],{},"mesh.points",", the stored coordinates. If the mesh carries a transformation, express the plane in the mesh's own frame. For long slicing jobs in the browser, ",[32,1254,1255],{},"tf.async.isocontours",[32,1257,1258],{},"tf.async.isobands"," run off the main thread.",[14,1261,1262,1263,1266,1267,1271,1272,27],{},"Slicing is a special case of ",[18,1264,345],{"href":1265},"\u002Ftutorials\u002Fisocontours-on-a-triangle-mesh-in-javascript",". To cut by another mesh instead of a plane, use ",[18,1268,1270],{"href":1269},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript","booleans"," or ",[18,1273,1275],{"href":1274},"\u002Ftutorials\u002Fmesh-intersection-curves-in-javascript","intersection curves",[52,1277,1279],{"id":1278},"related-tutorials","Related tutorials",[1281,1282,1283,1290,1296,1302],"ul",{},[1284,1285,1286,1289],"li",{},[18,1287,1288],{"href":1265},"Fast and Robust Isocontours on a Triangle Mesh in JavaScript"," — curves and bands of equal value on a scalar field.",[1284,1291,1292,1295],{},[18,1293,1294],{"href":721},"Fast and Exact Constrained Delaunay Triangulation in JavaScript"," — triangulate points and outlines, with holes and constraints.",[1284,1297,1298,1301],{},[18,1299,1300],{"href":1274},"Fast and Exact Mesh Intersection Curves in JavaScript"," — exact curves where two or more meshes cross.",[1284,1303,1304,1308],{},[18,1305,1307],{"href":1306},"\u002Ftutorials\u002Ffast-connected-components-in-javascript","Fast Connected Components in JavaScript"," — split a mesh into parts and find its boundary loops.",[52,1310,1312],{"id":1311},"performance","Performance",[1314,1315],"headline-numbers",{":items":1316},"[{\"value\":\"9.4ms\",\"label\":\"Isocontours\",\"detail\":\"1,025,766 polygons, JavaScript\"}]",[14,1318,1319,1324,1325,1324,1329,1324,1333],{},[66,1320,1323],{"className":1321},[1322],"tag-pill","Apple M4 Max"," ",[66,1326,1328],{"className":1327},[1322],"16 threads",[66,1330,1332],{"className":1331},[1322],"Node.js",[66,1334,1336],{"className":1335},[1322],"WebAssembly",[14,1338,1339,1340,22,1342,1344],{},"A slice is an isocontour. Isocontour extraction on a 1,025,766-polygon mesh takes 9.4 ms in Node.js. The JavaScript benchmark has no direct competitor on triangle surfaces, so there is no comparison here. The same core is measured against VTK on the ",[18,1341,21],{"href":20},[18,1343,26],{"href":25}," pages.",[14,1346,1347],{},[47,1348],{"alt":1349,"src":1350},"Benchmark chart: trueform against other TypeScript libraries, isocontours","\u002Fimg\u002Falgorithms\u002Fbenchmark-isocontours-typescript-2026.png",[14,1352,1353,1357,1358],{},[18,1354,1356],{"href":1355},"\u002Ftrueform\u002Fbenchmarks","Full benchmarks"," · ",[18,1359,1362],{"href":1360,"rel":1361},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fiso",[92],"Documentation",[1364,1365],"cite-as",{"author":1366,"title":5},"Sajovic, {\\v{Z}}iga",[1368,1369,1370],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":62,"searchDepth":133,"depth":133,"links":1372},[1373,1374,1375,1376,1377,1378,1379,1380,1381],{"id":54,"depth":133,"text":55},{"id":96,"depth":133,"text":97},{"id":235,"depth":133,"text":236},{"id":481,"depth":133,"text":482},{"id":714,"depth":133,"text":715},{"id":1001,"depth":133,"text":1002},{"id":1158,"depth":133,"text":1159},{"id":1278,"depth":133,"text":1279},{"id":1311,"depth":133,"text":1312},"2026-10-31","Slice a triangle mesh with a plane in JavaScript and TypeScript. One cross-section, many layers at once, filled section polygons, and slabs cut out as meshes. WebAssembly, runs in Node.js and the browser.","md",[1386,1387,1388,1389,1390,1391,1392,1393,1394,1395],"mesh slicing JavaScript","cross section mesh TypeScript","slice STL JavaScript","plane mesh intersection browser","mesh layers 3D printing JavaScript","WebAssembly mesh slicer","three.js cross section","npm mesh slicer","section polygon mesh","VTK cutter alternative",{},"\u002Fimg\u002Fog\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-javascript.png","\u002Ftutorials\u002Fmesh-slicing-and-cross-sections-in-javascript",{"title":5,"description":1383},"tutorials\u002Fmesh-slicing-and-cross-sections-in-javascript",[1402,102,1403,1404,1405],"javascript","slicing","tutorial","webassembly","DXmpSXqxdXkNZ6VZ-apvzgIa9z9zV3ru1za44NyXqeU",1793517812008]