[{"data":1,"prerenderedAt":3502},["ShallowReactive",2],{"algo-csg-for-engineering":3},{"id":4,"title":5,"author":6,"body":7,"date":3477,"description":3478,"extension":3479,"hub":306,"keywords":3480,"meta":3491,"navigation":306,"ogImage":3492,"path":3493,"published":306,"seo":3494,"stem":3497,"tags":3498,"updated":3500,"__hash__":3501},"algorithms\u002Falgorithms\u002Fcsg-for-engineering.md","CSG for engineering","Žiga Sajovic",{"type":8,"value":9,"toc":3465},"minimark",[10,14,17,32,36,45,58,63,66,101,104,107,113,117,120,135,145,151,161,170,180,189,193,196,199,203,221,225,228,248,251,254,754,761,764,768,771,777,783,867,873,876,880,883,897,974,981,985,999,1004,2547,2553,2559,2565,2569,2572,2577,2580,2584,2587,3365,3373,3377,3445,3453,3457,3461],[11,12,13],"p",{},"Engineering models are not two clean solids. A ground model is a block, a terrain, faults, horizons that stop at them, ore bodies and a tunnel in 420 slices. A CFD setup is a tunnel box, refinement zones and a vehicle shell that crosses itself. The meshes are open. They overlap. They touch along whole planes.",[11,15,16],{},"trueform's CSG graph takes all of them at once. One exact build cuts every operand against every other. After that, any region is one read: a rock unit, the rock the drive takes, the fluid around a body. Each comes back as a closed mesh, every face traced to its source. The domains are conformal: neighbouring regions share their interface triangle for triangle.",[11,18,19,20,25,26,31],{},"CAD meshers call this a fragment or a general fuse. trueform does it on triangle meshes, as they come from scans, exports and modellers. For two meshes and one answer, see ",[21,22,24],"a",{"href":23},"\u002Falgorithms\u002Fmesh-booleans","mesh booleans",". Dig a tunnel through layered ground in the ",[21,27,30],{"href":28,"external":29},"\u002Ftrueform\u002Flive\u002Fcsg-domains","","live example",".",[33,34],"headline-numbers",{":items":35},"[{\"value\":\"103 ms\",\"label\":\"64 meshes, one build\",\"detail\":\"N-ary benchmark median · 14× Manifold\"},{\"value\":\"445\",\"label\":\"Operands in one graph\",\"detail\":\"the tunnel model below · 19 sheets\"},{\"value\":\"0\",\"label\":\"Tolerance\",\"detail\":\"exact decisions, nothing glued\"}]",[11,37,38],{},[39,40],"img",{"alt":41,"height":42,"src":43,"width":44},"A synthetic ground model cut away along its length: six rock units offset by two faults, four ore bodies and a cavity, with a tunnel bore driven through the rock",800,"\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-tunnel-model.webp",1200,[11,46,47,53,54],{},[48,49,52],"span",{"className":50},[51],"tag-pill","Synthetic data"," ",[48,55,57],{"className":56},[51],"445 operands · 19 sheets · one graph",[59,60,62],"h2",{"id":61},"what-comes-back","What comes back",[11,64,65],{},"An engineer rarely wants one mesh. One graph returns all of these:",[67,68,69,77,83,89,95],"ul",{},[70,71,72,76],"li",{},[73,74,75],"strong",{},"Conformal domains."," Every region the surfaces enclose, each a closed mesh with a stable id, meeting its neighbours on the same triangles. Each can take a material, a boundary condition or a volume.",[70,78,79,82],{},[73,80,81],{},"An inclusion matrix."," One row per domain, one column per operand: inside or outside.",[70,84,85,88],{},[73,86,87],{},"Regions."," Domains picked by a boolean expression, or by a mask over the matrix.",[70,90,91,94],{},[73,92,93],{},"Surfaces."," One operand's walls of a region, or the part of a surface that lies inside one.",[70,96,97,100],{},[73,98,99],{},"Provenance."," For every output face, the operand and the input face it came from.",[11,102,103],{},"A model for simulation needs conformal domains. Two regions that touch must share one interface: the same points, the same triangles, no gap and no overlap between them. Interfaces that almost agree leave slivers and holes that a solver cannot use.",[11,105,106],{},"The graph returns them that way. Below, a block is cut by a wavy horizon sheet into two domains, and the upper one is turned over like the page of a book. The two faces of the interface are mirror images, triangle for triangle, with identical coordinates. Weld the coincident points and the two domains share one interface, not two that almost agree.",[11,108,109],{},[39,110],{"alt":111,"height":42,"src":112,"width":44},"A block cut by a wavy horizon into two domains, opened like a book: the lower domain in orange and the upper domain turned over in teal, both showing the same triangle mesh on the interface","\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-conformal.webp",[59,114,116],{"id":115},"load-the-model-as-it-is","Load the model as it is",[11,118,119],{},"The graph takes inputs as they come. Where only the engineer knows the intent, the engineer declares it.",[11,121,122,125,126,130,131,134],{},[73,123,124],{},"Open surfaces cut."," A horizon, a fault or a terrain has a rim and bounds no volume. List it in ",[127,128,129],"code",{},"sheets"," and it becomes an oriented separator. It cuts and is cut like any other operand, and ",[127,132,133],{},"op(i)"," means the side behind its normal. A volume cut by a sheet comes back closed, capped by the sheet.",[11,136,137,140,141,144],{},[73,138,139],{},"Shells that overlap themselves."," One file can hold several shells that cross each other: instanced parts that touch, a merged export. Pass ",[127,142,143],{},"within=True"," and the graph also cuts each operand against itself.",[11,146,147],{},[39,148],{"alt":149,"height":42,"src":150,"width":44},"One operand holding two overlapping spheres, cut by a slab operand, pulled apart: the lens where the spheres overlap is its own domain in pink, the rest of each sphere above the slab in teal and inside it in orange","\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-within.webp",[11,152,153,154,157,158,31],{},"In the picture, operand 0 is two overlapping spheres stored as one mesh and operand 1 is a slab. ",[127,155,156],{},"domains()"," returns six cells: each sphere's own part and the lens, above the slab and inside it. Expressions still read each operand as one solid; to name a self-overlapping operand in one, repair it first with ",[127,159,160],{},"outer_shell",[11,162,163,166,167,169],{},[73,164,165],{},"Faces that coincide."," Where two layers touch, their faces lie in one plane. The graph classifies coincident faces exactly, as it does for two meshes. The tests on ",[21,168,24],{"href":23}," show what that means.",[11,171,172,175,176,179],{},[73,173,174],{},"Surfaces that fall short."," A horizon that stops before it reaches a fault seals nothing. The graph does not guess a side for it. ",[127,177,178],{},"sheet_rim_volumes"," lists every sheet whose free rim ends inside a volume, with that volume, so an empty region is never silent.",[11,181,182,185,186,31],{},[73,183,184],{},"Faces that disagree on their winding."," Each volume operand needs a clear inside, so neighbouring faces must agree. Orient them before the build with ",[127,187,188],{},"orient_faces_consistently",[59,190,192],{"id":191},"build-once-read-many-times","Build once, read many times",[11,194,195],{},"The build computes one exact arrangement of all operands and classifies every region against every operand. Every read reuses it. A chain of pairwise booleans instead starts over from each new mesh.",[11,197,198],{},"On the public N-ary benchmark, a union of 64 Thingi10K meshes, trueform builds the arrangement in 103 ms median: 14× faster than Solidean and Manifold, 44× Cherchi, 138× MeshLib, 536× CGAL and 648× Geogram. Apple M4 Max, 16 threads, trueform 0.9.8.",[200,201],"hub-benchmark-figures",{"category":202},"variadic-booleans",[11,204,205,206,210,211,215,216,220],{},"Full tables and protocol: ",[21,207,209],{"href":208},"\u002Falgorithms\u002Fbenchmarking-mesh-boolean-libraries-2026","Mesh Boolean Library Comparison and Benchmarks — 2026",". One graph over 201 meshes, many expressions: the ",[21,212,214],{"href":213},"\u002Ftutorials\u002Ffast-variadic-mesh-booleans-in-cpp","variadic tutorial",". Every module: the ",[21,217,219],{"href":218},"\u002Ftrueform\u002Fbenchmarks","trueform benchmarks"," page.",[59,222,224],{"id":223},"a-tunnel-drive-slice-by-slice","A tunnel drive, slice by slice",[11,226,227],{},"The ground model at the top of this page is synthetic, in local metres, with 445 operands in one graph:",[67,229,230,233,236,239,242,245],{},[70,231,232],{},"the ground volume and the terrain;",[70,234,235],{},"two faults, with the rock offset across them;",[70,237,238],{},"15 horizon pieces, five per fault block, bounding six rock units;",[70,240,241],{},"a cavity and four ore bodies;",[70,243,244],{},"420 excavation slices, 0.5 m each, along a curved alignment;",[70,246,247],{},"a curtain sheet that cuts the model away along its length for display.",[11,249,250],{},"Nineteen of the operands are sheets: the terrain, the faults, the horizons and the curtain.",[11,252,253],{},"A rock unit is an expression. In each fault block it lies below its top horizon and above the one beneath. The cavity and every ore are subtracted, so each volume of rock is counted once. The excavation after n slices is the union of the first n slices. The rock a unit loses to the drive is one more intersection.",[255,256,260],"pre",{"className":257,"code":258,"language":259,"meta":29,"style":29},"language-python shiki shiki-themes github-dark","# GROUND, TERRAIN, F0, F1, CAVITY, ORES, FIRST_SLICE and horizon(b, k)\n# are operand indices in `meshes`.\ngraph = tf.CsgGraph(meshes, sheets=sheets)       # 445 operands, built once\n\ndef unit(k):\n    # rock unit k: between its horizons in each fault block,\n    # inside the ground, minus the cavity and every ore\n    blocks = [tf.op(F0), ~tf.op(F0) & tf.op(F1), ~tf.op(F1)]\n    e = None\n    for b in range(3):\n        top = tf.op(horizon(b, k)) if k \u003C 5 else tf.op(TERRAIN)\n        part = blocks[b] & top\n        if k > 0:\n            part = part - tf.op(horizon(b, k - 1))\n        e = part if e is None else e | part\n    e = tf.op(GROUND) & e - tf.op(CAVITY)\n    for ore in ORES:\n        e = e - tf.op(ore)\n    return e\n\ndef dug(n):\n    # the first n excavation slices\n    e = tf.op(FIRST_SLICE)\n    for i in range(FIRST_SLICE + 1, FIRST_SLICE + n):\n        e = e | tf.op(i)\n    return e\n\nfor n in range(1, 421):                          # as the face advances\n    for k in range(6):\n        faces, points = graph.mesh(unit(k) & dug(n))\n        excavated = tf.volume(tf.Mesh(faces, points))\n","python",[127,261,262,270,276,301,308,321,327,333,361,373,397,432,448,465,490,521,549,564,578,587,592,603,609,623,654,668,675,680,709,727,743],{"__ignoreMap":29},[48,263,266],{"class":264,"line":265},"line",1,[48,267,269],{"class":268},"sAwPA","# GROUND, TERRAIN, F0, F1, CAVITY, ORES, FIRST_SLICE and horizon(b, k)\n",[48,271,273],{"class":264,"line":272},2,[48,274,275],{"class":268},"# are operand indices in `meshes`.\n",[48,277,279,283,287,290,293,295,298],{"class":264,"line":278},3,[48,280,282],{"class":281},"s95oV","graph ",[48,284,286],{"class":285},"snl16","=",[48,288,289],{"class":281}," tf.CsgGraph(meshes, ",[48,291,129],{"class":292},"s9osk",[48,294,286],{"class":285},[48,296,297],{"class":281},"sheets)       ",[48,299,300],{"class":268},"# 445 operands, built once\n",[48,302,304],{"class":264,"line":303},4,[48,305,307],{"emptyLinePlaceholder":306},true,"\n",[48,309,311,314,318],{"class":264,"line":310},5,[48,312,313],{"class":285},"def",[48,315,317],{"class":316},"svObZ"," unit",[48,319,320],{"class":281},"(k):\n",[48,322,324],{"class":264,"line":323},6,[48,325,326],{"class":268},"    # rock unit k: between its horizons in each fault block,\n",[48,328,330],{"class":264,"line":329},7,[48,331,332],{"class":268},"    # inside the ground, minus the cavity and every ore\n",[48,334,336,339,341,344,347,350,353,356,358],{"class":264,"line":335},8,[48,337,338],{"class":281},"    blocks ",[48,340,286],{"class":285},[48,342,343],{"class":281}," [tf.op(F0), ",[48,345,346],{"class":285},"~",[48,348,349],{"class":281},"tf.op(F0) ",[48,351,352],{"class":285},"&",[48,354,355],{"class":281}," tf.op(F1), ",[48,357,346],{"class":285},[48,359,360],{"class":281},"tf.op(F1)]\n",[48,362,364,367,369],{"class":264,"line":363},9,[48,365,366],{"class":281},"    e ",[48,368,286],{"class":285},[48,370,372],{"class":371},"sDLfK"," None\n",[48,374,376,379,382,385,388,391,394],{"class":264,"line":375},10,[48,377,378],{"class":285},"    for",[48,380,381],{"class":281}," b ",[48,383,384],{"class":285},"in",[48,386,387],{"class":371}," range",[48,389,390],{"class":281},"(",[48,392,393],{"class":371},"3",[48,395,396],{"class":281},"):\n",[48,398,400,403,405,408,411,414,417,420,423,426,429],{"class":264,"line":399},11,[48,401,402],{"class":281},"        top ",[48,404,286],{"class":285},[48,406,407],{"class":281}," tf.op(horizon(b, k)) ",[48,409,410],{"class":285},"if",[48,412,413],{"class":281}," k ",[48,415,416],{"class":285},"\u003C",[48,418,419],{"class":371}," 5",[48,421,422],{"class":285}," else",[48,424,425],{"class":281}," tf.op(",[48,427,428],{"class":371},"TERRAIN",[48,430,431],{"class":281},")\n",[48,433,435,438,440,443,445],{"class":264,"line":434},12,[48,436,437],{"class":281},"        part ",[48,439,286],{"class":285},[48,441,442],{"class":281}," blocks[b] ",[48,444,352],{"class":285},[48,446,447],{"class":281}," top\n",[48,449,451,454,456,459,462],{"class":264,"line":450},13,[48,452,453],{"class":285},"        if",[48,455,413],{"class":281},[48,457,458],{"class":285},">",[48,460,461],{"class":371}," 0",[48,463,464],{"class":281},":\n",[48,466,468,471,473,476,479,482,484,487],{"class":264,"line":467},14,[48,469,470],{"class":281},"            part ",[48,472,286],{"class":285},[48,474,475],{"class":281}," part ",[48,477,478],{"class":285},"-",[48,480,481],{"class":281}," tf.op(horizon(b, k ",[48,483,478],{"class":285},[48,485,486],{"class":371}," 1",[48,488,489],{"class":281},"))\n",[48,491,493,496,498,500,502,505,508,511,513,515,518],{"class":264,"line":492},15,[48,494,495],{"class":281},"        e ",[48,497,286],{"class":285},[48,499,475],{"class":281},[48,501,410],{"class":285},[48,503,504],{"class":281}," e ",[48,506,507],{"class":285},"is",[48,509,510],{"class":371}," None",[48,512,422],{"class":285},[48,514,504],{"class":281},[48,516,517],{"class":285},"|",[48,519,520],{"class":281}," part\n",[48,522,524,526,528,530,533,536,538,540,542,544,547],{"class":264,"line":523},16,[48,525,366],{"class":281},[48,527,286],{"class":285},[48,529,425],{"class":281},[48,531,532],{"class":371},"GROUND",[48,534,535],{"class":281},") ",[48,537,352],{"class":285},[48,539,504],{"class":281},[48,541,478],{"class":285},[48,543,425],{"class":281},[48,545,546],{"class":371},"CAVITY",[48,548,431],{"class":281},[48,550,552,554,557,559,562],{"class":264,"line":551},17,[48,553,378],{"class":285},[48,555,556],{"class":281}," ore ",[48,558,384],{"class":285},[48,560,561],{"class":371}," ORES",[48,563,464],{"class":281},[48,565,567,569,571,573,575],{"class":264,"line":566},18,[48,568,495],{"class":281},[48,570,286],{"class":285},[48,572,504],{"class":281},[48,574,478],{"class":285},[48,576,577],{"class":281}," tf.op(ore)\n",[48,579,581,584],{"class":264,"line":580},19,[48,582,583],{"class":285},"    return",[48,585,586],{"class":281}," e\n",[48,588,590],{"class":264,"line":589},20,[48,591,307],{"emptyLinePlaceholder":306},[48,593,595,597,600],{"class":264,"line":594},21,[48,596,313],{"class":285},[48,598,599],{"class":316}," dug",[48,601,602],{"class":281},"(n):\n",[48,604,606],{"class":264,"line":605},22,[48,607,608],{"class":268},"    # the first n excavation slices\n",[48,610,612,614,616,618,621],{"class":264,"line":611},23,[48,613,366],{"class":281},[48,615,286],{"class":285},[48,617,425],{"class":281},[48,619,620],{"class":371},"FIRST_SLICE",[48,622,431],{"class":281},[48,624,626,628,631,633,635,637,639,642,644,647,649,651],{"class":264,"line":625},24,[48,627,378],{"class":285},[48,629,630],{"class":281}," i ",[48,632,384],{"class":285},[48,634,387],{"class":371},[48,636,390],{"class":281},[48,638,620],{"class":371},[48,640,641],{"class":285}," +",[48,643,486],{"class":371},[48,645,646],{"class":281},", ",[48,648,620],{"class":371},[48,650,641],{"class":285},[48,652,653],{"class":281}," n):\n",[48,655,657,659,661,663,665],{"class":264,"line":656},25,[48,658,495],{"class":281},[48,660,286],{"class":285},[48,662,504],{"class":281},[48,664,517],{"class":285},[48,666,667],{"class":281}," tf.op(i)\n",[48,669,671,673],{"class":264,"line":670},26,[48,672,583],{"class":285},[48,674,586],{"class":281},[48,676,678],{"class":264,"line":677},27,[48,679,307],{"emptyLinePlaceholder":306},[48,681,683,686,689,691,693,695,698,700,703,706],{"class":264,"line":682},28,[48,684,685],{"class":285},"for",[48,687,688],{"class":281}," n ",[48,690,384],{"class":285},[48,692,387],{"class":371},[48,694,390],{"class":281},[48,696,697],{"class":371},"1",[48,699,646],{"class":281},[48,701,702],{"class":371},"421",[48,704,705],{"class":281},"):                          ",[48,707,708],{"class":268},"# as the face advances\n",[48,710,712,714,716,718,720,722,725],{"class":264,"line":711},29,[48,713,378],{"class":285},[48,715,413],{"class":281},[48,717,384],{"class":285},[48,719,387],{"class":371},[48,721,390],{"class":281},[48,723,724],{"class":371},"6",[48,726,396],{"class":281},[48,728,730,733,735,738,740],{"class":264,"line":729},30,[48,731,732],{"class":281},"        faces, points ",[48,734,286],{"class":285},[48,736,737],{"class":281}," graph.mesh(unit(k) ",[48,739,352],{"class":285},[48,741,742],{"class":281}," dug(n))\n",[48,744,746,749,751],{"class":264,"line":745},31,[48,747,748],{"class":281},"        excavated ",[48,750,286],{"class":285},[48,752,753],{"class":281}," tf.volume(tf.Mesh(faces, points))\n",[11,755,756],{},[39,757],{"alt":758,"height":42,"src":759,"width":44,"loading":760},"The excavated volume after 280 slices, coloured by the rock unit each piece came from, with the ore bodies it crosses in pink inside a faint outline of the ground","\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-tunnel-excavated.webp","lazy",[11,762,763],{},"One build serves all 420 reads, fast enough to follow the drive as it advances. The six units plus the four ores add up to the ground below the terrain minus the cavity: nothing is counted twice and nothing is lost.",[59,765,767],{"id":766},"fluid-domains-and-finite-element-models","Fluid domains and finite element models",[11,769,770],{},"The pattern is not specific to ground. A wind-tunnel box, a refinement zone and a vehicle make a CFD setup. The fluid domain is the box minus the vehicle. The near-field and wake region is the zone minus the vehicle. Both come from one graph as conformal domains: they meet on shared triangles.",[11,772,773,774,776],{},"The car below is a public body shell as it was uploaded: closed, 282,570 triangles, with 246 pairs of faces that cross. ",[127,775,160],{}," repairs it in one call, and it goes into the graph as is.",[11,778,779],{},[39,780],{"alt":781,"height":42,"src":782,"width":44,"loading":760},"A car body inside a wind-tunnel box cut open along its middle: the far field in faint teal, the near-field and wake zone in orange, the car in sand","\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-fluid.webp",[255,784,786],{"className":257,"code":785,"language":259,"meta":29,"style":29},"car = tf.outer_shell(car)                      # drops the self-crossings\ngraph = tf.CsgGraph([tunnel, zone, car])\n\nfar_field = graph.mesh(tf.op(0) - tf.op(1) - tf.op(2))\nnear_field = graph.mesh(tf.op(1) - tf.op(2))\n",[127,787,788,801,810,814,846],{"__ignoreMap":29},[48,789,790,793,795,798],{"class":264,"line":265},[48,791,792],{"class":281},"car ",[48,794,286],{"class":285},[48,796,797],{"class":281}," tf.outer_shell(car)                      ",[48,799,800],{"class":268},"# drops the self-crossings\n",[48,802,803,805,807],{"class":264,"line":272},[48,804,282],{"class":281},[48,806,286],{"class":285},[48,808,809],{"class":281}," tf.CsgGraph([tunnel, zone, car])\n",[48,811,812],{"class":264,"line":278},[48,813,307],{"emptyLinePlaceholder":306},[48,815,816,819,821,824,827,829,831,833,835,837,839,841,844],{"class":264,"line":303},[48,817,818],{"class":281},"far_field ",[48,820,286],{"class":285},[48,822,823],{"class":281}," graph.mesh(tf.op(",[48,825,826],{"class":371},"0",[48,828,535],{"class":281},[48,830,478],{"class":285},[48,832,425],{"class":281},[48,834,697],{"class":371},[48,836,535],{"class":281},[48,838,478],{"class":285},[48,840,425],{"class":281},[48,842,843],{"class":371},"2",[48,845,489],{"class":281},[48,847,848,851,853,855,857,859,861,863,865],{"class":264,"line":310},[48,849,850],{"class":281},"near_field ",[48,852,286],{"class":285},[48,854,823],{"class":281},[48,856,697],{"class":371},[48,858,535],{"class":281},[48,860,478],{"class":285},[48,862,425],{"class":281},[48,864,843],{"class":371},[48,866,489],{"class":281},[11,868,869],{},[48,870,872],{"className":871},[51],"Rally Fighter Body by LocalMotors, Thingiverse 40862, CC BY",[11,874,875],{},"For a finite element model, the conformal domains are the material regions, and each domain's row in the inclusion matrix says which parts contain it.",[59,877,879],{"id":878},"read-surfaces-not-only-volumes","Read surfaces, not only volumes",[11,881,882],{},"Sometimes the answer is an area. The part of a horizon inside an excavation is a contact surface. The walls of one unit carry a load. Two keywords read them off the same build:",[67,884,885,891],{},[70,886,887,890],{},[127,888,889],{},"selection=[i]"," returns the faces of operand i that bound the region, wound outward.",[70,892,893,896],{},[127,894,895],{},"inside=[i]"," returns the surface of operand i that lies inside the region, in its own winding.",[255,898,900],{"className":257,"code":899,"language":259,"meta":29,"style":29},"# operand BOX is a unit box, operand PLANE a sheet through its middle\nfaces, points = graph.mesh(tf.op(BOX), inside=[PLANE])                  # the cap: area 1.0\nfaces, points = graph.mesh(tf.op(BOX) - tf.op(PLANE), selection=[BOX])  # walls above: area 3.0\n",[127,901,902,907,939],{"__ignoreMap":29},[48,903,904],{"class":264,"line":265},[48,905,906],{"class":268},"# operand BOX is a unit box, operand PLANE a sheet through its middle\n",[48,908,909,912,914,916,919,922,925,927,930,933,936],{"class":264,"line":272},[48,910,911],{"class":281},"faces, points ",[48,913,286],{"class":285},[48,915,823],{"class":281},[48,917,918],{"class":371},"BOX",[48,920,921],{"class":281},"), ",[48,923,924],{"class":292},"inside",[48,926,286],{"class":285},[48,928,929],{"class":281},"[",[48,931,932],{"class":371},"PLANE",[48,934,935],{"class":281},"])                  ",[48,937,938],{"class":268},"# the cap: area 1.0\n",[48,940,941,943,945,947,949,951,953,955,957,959,962,964,966,968,971],{"class":264,"line":278},[48,942,911],{"class":281},[48,944,286],{"class":285},[48,946,823],{"class":281},[48,948,918],{"class":371},[48,950,535],{"class":281},[48,952,478],{"class":285},[48,954,425],{"class":281},[48,956,932],{"class":371},[48,958,921],{"class":281},[48,960,961],{"class":292},"selection",[48,963,286],{"class":285},[48,965,929],{"class":281},[48,967,918],{"class":371},[48,969,970],{"class":281},"])  ",[48,972,973],{"class":268},"# walls above: area 3.0\n",[11,975,976,977,980],{},"Pass ",[127,978,979],{},"return_source_ids=True"," to any read and every output face also carries its operand and its input face. Colours, materials and survey attributes then follow the cut.",[59,982,984],{"id":983},"the-graph-in-code","The graph in code",[11,986,987,988,994,995,998],{},"The trueform docs teach the graph with one small scene: ",[21,989,993],{"href":990,"rel":991},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fexamples\u002Farrangements",[992],"nofollow","Booleans and Domains from One Build",". A sphere straddles a flat knife. Two smaller spheres float above and below it and touch nothing. The knife is declared a sheet, so ",[127,996,997],{},"op(3)"," means below it. One build answers three questions.",[1000,1001],"image-grid",{":eager":1002,":images":1003},"false","[{\"src\":\"\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-input.webp\",\"label\":\"Input\",\"detail\":\"three solids, one sheet\",\"alt\":\"Input: a sphere straddling a flat translucent knife sheet, with a small sphere above and a small sphere below\",\"width\":900,\"height\":900},{\"src\":\"\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-domains.webp\",\"label\":\"Domains\",\"detail\":\"four cells, one colour each\",\"alt\":\"Domains: the straddling sphere cut by the sheet into a teal upper half and an orange lower half, both capped, with the two floating spheres as their own cells\",\"width\":900,\"height\":900},{\"src\":\"\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-selected.webp\",\"label\":\"Selected\",\"detail\":\"below the sheet\",\"alt\":\"Selection: the two cells below the sheet in orange, the cells above it faded\",\"width\":900,\"height\":900},{\"src\":\"\u002Fimg\u002Falgorithms\u002Fcsg\u002Fcsg-sides.webp\",\"label\":\"Boolean sides\",\"detail\":\"one closed mesh per side\",\"alt\":\"Boolean sides: everything above the sheet as one teal mesh and everything below as one pink mesh, pulled apart\",\"width\":900,\"height\":900}]",[1005,1006,1007,1395,2057],"code-group",{},[255,1008,1011],{"className":257,"code":1009,"filename":1010,"language":259,"meta":29,"style":29},"import numpy as np\nimport trueform as tf\n\ndef sphere(center, radius):\n    sf, sp = tf.make_sphere_mesh(radius, 32, 32)\n    return tf.Mesh(sf, np.asarray(sp) + np.asarray(center, sp.dtype))\n\nplane_faces, plane_points = tf.make_plane_mesh(4.0, 4.0)\nmeshes = [sphere((0, 0, 0), 1.0),      # op 0: straddles the knife\n          sphere((0, 0, 2), 0.5),      # op 1: floats above\n          sphere((0, 0, -2), 0.5),     # op 2: floats below\n          tf.Mesh(plane_faces, plane_points)]  # op 3: the knife\n\ngraph = tf.CsgGraph(meshes, sheets=[3])\nsolids = tf.op(0) | tf.op(1) | tf.op(2)\n\n# 1. boolean meshes: one closed mesh per side\nabove_mesh = graph.mesh(solids - tf.op(3))\nbelow_mesh = graph.mesh(solids & tf.op(3))\n\n# 2. domains by expression: the same volumes, one mesh each\nabove_cells, above_ids = graph.domains(solids - tf.op(3))\nbelow_cells, below_ids = graph.domains(solids & tf.op(3))\n\n# 3. domains by hand: extract once, select by mask\ncells, ids, imap = graph.domains(return_index_map=True)\nbelow = imap.inclusion[:, 3]\nabove = ~below\n","Python",[127,1012,1013,1027,1039,1043,1053,1072,1085,1089,1108,1139,1164,1190,1198,1202,1221,1250,1254,1259,1277,1294,1298,1303,1321,1338,1342,1347,1367,1382],{"__ignoreMap":29},[48,1014,1015,1018,1021,1024],{"class":264,"line":265},[48,1016,1017],{"class":285},"import",[48,1019,1020],{"class":281}," numpy ",[48,1022,1023],{"class":285},"as",[48,1025,1026],{"class":281}," np\n",[48,1028,1029,1031,1034,1036],{"class":264,"line":272},[48,1030,1017],{"class":285},[48,1032,1033],{"class":281}," trueform ",[48,1035,1023],{"class":285},[48,1037,1038],{"class":281}," tf\n",[48,1040,1041],{"class":264,"line":278},[48,1042,307],{"emptyLinePlaceholder":306},[48,1044,1045,1047,1050],{"class":264,"line":303},[48,1046,313],{"class":285},[48,1048,1049],{"class":316}," sphere",[48,1051,1052],{"class":281},"(center, radius):\n",[48,1054,1055,1058,1060,1063,1066,1068,1070],{"class":264,"line":310},[48,1056,1057],{"class":281},"    sf, sp ",[48,1059,286],{"class":285},[48,1061,1062],{"class":281}," tf.make_sphere_mesh(radius, ",[48,1064,1065],{"class":371},"32",[48,1067,646],{"class":281},[48,1069,1065],{"class":371},[48,1071,431],{"class":281},[48,1073,1074,1076,1079,1082],{"class":264,"line":323},[48,1075,583],{"class":285},[48,1077,1078],{"class":281}," tf.Mesh(sf, np.asarray(sp) ",[48,1080,1081],{"class":285},"+",[48,1083,1084],{"class":281}," np.asarray(center, sp.dtype))\n",[48,1086,1087],{"class":264,"line":329},[48,1088,307],{"emptyLinePlaceholder":306},[48,1090,1091,1094,1096,1099,1102,1104,1106],{"class":264,"line":335},[48,1092,1093],{"class":281},"plane_faces, plane_points ",[48,1095,286],{"class":285},[48,1097,1098],{"class":281}," tf.make_plane_mesh(",[48,1100,1101],{"class":371},"4.0",[48,1103,646],{"class":281},[48,1105,1101],{"class":371},[48,1107,431],{"class":281},[48,1109,1110,1113,1115,1118,1120,1122,1124,1126,1128,1130,1133,1136],{"class":264,"line":363},[48,1111,1112],{"class":281},"meshes ",[48,1114,286],{"class":285},[48,1116,1117],{"class":281}," [sphere((",[48,1119,826],{"class":371},[48,1121,646],{"class":281},[48,1123,826],{"class":371},[48,1125,646],{"class":281},[48,1127,826],{"class":371},[48,1129,921],{"class":281},[48,1131,1132],{"class":371},"1.0",[48,1134,1135],{"class":281},"),      ",[48,1137,1138],{"class":268},"# op 0: straddles the knife\n",[48,1140,1141,1144,1146,1148,1150,1152,1154,1156,1159,1161],{"class":264,"line":375},[48,1142,1143],{"class":281},"          sphere((",[48,1145,826],{"class":371},[48,1147,646],{"class":281},[48,1149,826],{"class":371},[48,1151,646],{"class":281},[48,1153,843],{"class":371},[48,1155,921],{"class":281},[48,1157,1158],{"class":371},"0.5",[48,1160,1135],{"class":281},[48,1162,1163],{"class":268},"# op 1: floats above\n",[48,1165,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1187],{"class":264,"line":399},[48,1167,1143],{"class":281},[48,1169,826],{"class":371},[48,1171,646],{"class":281},[48,1173,826],{"class":371},[48,1175,646],{"class":281},[48,1177,478],{"class":285},[48,1179,843],{"class":371},[48,1181,921],{"class":281},[48,1183,1158],{"class":371},[48,1185,1186],{"class":281},"),     ",[48,1188,1189],{"class":268},"# op 2: floats below\n",[48,1191,1192,1195],{"class":264,"line":434},[48,1193,1194],{"class":281},"          tf.Mesh(plane_faces, plane_points)]  ",[48,1196,1197],{"class":268},"# op 3: the knife\n",[48,1199,1200],{"class":264,"line":450},[48,1201,307],{"emptyLinePlaceholder":306},[48,1203,1204,1206,1208,1210,1212,1214,1216,1218],{"class":264,"line":467},[48,1205,282],{"class":281},[48,1207,286],{"class":285},[48,1209,289],{"class":281},[48,1211,129],{"class":292},[48,1213,286],{"class":285},[48,1215,929],{"class":281},[48,1217,393],{"class":371},[48,1219,1220],{"class":281},"])\n",[48,1222,1223,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1246,1248],{"class":264,"line":492},[48,1224,1225],{"class":281},"solids ",[48,1227,286],{"class":285},[48,1229,425],{"class":281},[48,1231,826],{"class":371},[48,1233,535],{"class":281},[48,1235,517],{"class":285},[48,1237,425],{"class":281},[48,1239,697],{"class":371},[48,1241,535],{"class":281},[48,1243,517],{"class":285},[48,1245,425],{"class":281},[48,1247,843],{"class":371},[48,1249,431],{"class":281},[48,1251,1252],{"class":264,"line":523},[48,1253,307],{"emptyLinePlaceholder":306},[48,1255,1256],{"class":264,"line":551},[48,1257,1258],{"class":268},"# 1. boolean meshes: one closed mesh per side\n",[48,1260,1261,1264,1266,1269,1271,1273,1275],{"class":264,"line":566},[48,1262,1263],{"class":281},"above_mesh ",[48,1265,286],{"class":285},[48,1267,1268],{"class":281}," graph.mesh(solids ",[48,1270,478],{"class":285},[48,1272,425],{"class":281},[48,1274,393],{"class":371},[48,1276,489],{"class":281},[48,1278,1279,1282,1284,1286,1288,1290,1292],{"class":264,"line":580},[48,1280,1281],{"class":281},"below_mesh ",[48,1283,286],{"class":285},[48,1285,1268],{"class":281},[48,1287,352],{"class":285},[48,1289,425],{"class":281},[48,1291,393],{"class":371},[48,1293,489],{"class":281},[48,1295,1296],{"class":264,"line":589},[48,1297,307],{"emptyLinePlaceholder":306},[48,1299,1300],{"class":264,"line":594},[48,1301,1302],{"class":268},"# 2. domains by expression: the same volumes, one mesh each\n",[48,1304,1305,1308,1310,1313,1315,1317,1319],{"class":264,"line":605},[48,1306,1307],{"class":281},"above_cells, above_ids ",[48,1309,286],{"class":285},[48,1311,1312],{"class":281}," graph.domains(solids ",[48,1314,478],{"class":285},[48,1316,425],{"class":281},[48,1318,393],{"class":371},[48,1320,489],{"class":281},[48,1322,1323,1326,1328,1330,1332,1334,1336],{"class":264,"line":611},[48,1324,1325],{"class":281},"below_cells, below_ids ",[48,1327,286],{"class":285},[48,1329,1312],{"class":281},[48,1331,352],{"class":285},[48,1333,425],{"class":281},[48,1335,393],{"class":371},[48,1337,489],{"class":281},[48,1339,1340],{"class":264,"line":625},[48,1341,307],{"emptyLinePlaceholder":306},[48,1343,1344],{"class":264,"line":656},[48,1345,1346],{"class":268},"# 3. domains by hand: extract once, select by mask\n",[48,1348,1349,1352,1354,1357,1360,1362,1365],{"class":264,"line":670},[48,1350,1351],{"class":281},"cells, ids, imap ",[48,1353,286],{"class":285},[48,1355,1356],{"class":281}," graph.domains(",[48,1358,1359],{"class":292},"return_index_map",[48,1361,286],{"class":285},[48,1363,1364],{"class":371},"True",[48,1366,431],{"class":281},[48,1368,1369,1372,1374,1377,1379],{"class":264,"line":677},[48,1370,1371],{"class":281},"below ",[48,1373,286],{"class":285},[48,1375,1376],{"class":281}," imap.inclusion[:, ",[48,1378,393],{"class":371},[48,1380,1381],{"class":281},"]\n",[48,1383,1384,1387,1389,1392],{"class":264,"line":682},[48,1385,1386],{"class":281},"above ",[48,1388,286],{"class":285},[48,1390,1391],{"class":285}," ~",[48,1393,1394],{"class":281},"below\n",[255,1396,1401],{"className":1397,"code":1398,"filename":1399,"language":1400,"meta":29,"style":29},"language-cpp shiki shiki-themes github-dark","#include \u003Ctrueform\u002Ftrueform.hpp>\n\nauto straddle = tf::make_sphere_mesh\u003Cint>(1.f, 32, 32);\nauto above_s  = tf::make_sphere_mesh\u003Cint>(0.5f, 32, 32);\nauto below_s  = tf::make_sphere_mesh\u003Cint>(0.5f, 32, 32);\nauto plane    = tf::make_plane_mesh\u003Cint>(4.f, 4.f);\n\nauto at = [](float z) {\n  return tf::make_frame(tf::make_transformation_from_translation(\n      tf::vector\u003Cfloat, 3>{0.f, 0.f, z}));\n};\nstd::vector forms{straddle.polygons() | tf::tag(at(0)),   \u002F\u002F op 0\n                  above_s.polygons()  | tf::tag(at(2)),   \u002F\u002F op 1\n                  below_s.polygons()  | tf::tag(at(-2)),  \u002F\u002F op 2\n                  plane.polygons()    | tf::tag(at(0))};  \u002F\u002F op 3: the knife\n\nstd::array\u003Cint, 1> sheets{3};\nauto graph = tf::make_csg_graph(tf::make_range(forms), tf::make_range(sheets));\nauto solids = tf::csg::merge(tf::csg::merge(0, 1), 2);\n\n\u002F\u002F 1. boolean meshes\nauto above_mesh = tf::make_csg_mesh(graph, tf::csg::difference(solids, 3));\nauto below_mesh = tf::make_csg_mesh(graph, tf::csg::intersection(solids, 3));\n\n\u002F\u002F 2. domains by expression\nauto [above_cells, above_ids] =\n    tf::make_csg_domains(graph, tf::csg::difference(solids, 3));\n\n\u002F\u002F 3. domains by hand: imap.inclusion[k][3] is true below the knife\nauto [cells, ids, imap] = tf::make_csg_domains(graph, tf::return_index_map);\n","C++","cpp",[127,1402,1403,1412,1416,1460,1495,1530,1565,1569,1590,1615,1649,1654,1692,1723,1755,1787,1791,1814,1851,1898,1902,1907,1945,1979,1983,1988,1998,2026,2030,2035],{"__ignoreMap":29},[48,1404,1405,1408],{"class":264,"line":265},[48,1406,1407],{"class":285},"#include",[48,1409,1411],{"class":1410},"sU2Wk"," \u003Ctrueform\u002Ftrueform.hpp>\n",[48,1413,1414],{"class":264,"line":272},[48,1415,307],{"emptyLinePlaceholder":306},[48,1417,1418,1421,1424,1426,1429,1432,1435,1437,1440,1443,1446,1449,1451,1453,1455,1457],{"class":264,"line":278},[48,1419,1420],{"class":285},"auto",[48,1422,1423],{"class":281}," straddle ",[48,1425,286],{"class":285},[48,1427,1428],{"class":316}," tf",[48,1430,1431],{"class":281},"::",[48,1433,1434],{"class":316},"make_sphere_mesh",[48,1436,416],{"class":281},[48,1438,1439],{"class":285},"int",[48,1441,1442],{"class":281},">(",[48,1444,1445],{"class":371},"1.",[48,1447,1448],{"class":285},"f",[48,1450,646],{"class":281},[48,1452,1065],{"class":371},[48,1454,646],{"class":281},[48,1456,1065],{"class":371},[48,1458,1459],{"class":281},");\n",[48,1461,1462,1464,1467,1469,1471,1473,1475,1477,1479,1481,1483,1485,1487,1489,1491,1493],{"class":264,"line":303},[48,1463,1420],{"class":285},[48,1465,1466],{"class":281}," above_s  ",[48,1468,286],{"class":285},[48,1470,1428],{"class":316},[48,1472,1431],{"class":281},[48,1474,1434],{"class":316},[48,1476,416],{"class":281},[48,1478,1439],{"class":285},[48,1480,1442],{"class":281},[48,1482,1158],{"class":371},[48,1484,1448],{"class":285},[48,1486,646],{"class":281},[48,1488,1065],{"class":371},[48,1490,646],{"class":281},[48,1492,1065],{"class":371},[48,1494,1459],{"class":281},[48,1496,1497,1499,1502,1504,1506,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528],{"class":264,"line":310},[48,1498,1420],{"class":285},[48,1500,1501],{"class":281}," below_s  ",[48,1503,286],{"class":285},[48,1505,1428],{"class":316},[48,1507,1431],{"class":281},[48,1509,1434],{"class":316},[48,1511,416],{"class":281},[48,1513,1439],{"class":285},[48,1515,1442],{"class":281},[48,1517,1158],{"class":371},[48,1519,1448],{"class":285},[48,1521,646],{"class":281},[48,1523,1065],{"class":371},[48,1525,646],{"class":281},[48,1527,1065],{"class":371},[48,1529,1459],{"class":281},[48,1531,1532,1534,1537,1539,1541,1543,1546,1548,1550,1552,1555,1557,1559,1561,1563],{"class":264,"line":323},[48,1533,1420],{"class":285},[48,1535,1536],{"class":281}," plane    ",[48,1538,286],{"class":285},[48,1540,1428],{"class":316},[48,1542,1431],{"class":281},[48,1544,1545],{"class":316},"make_plane_mesh",[48,1547,416],{"class":281},[48,1549,1439],{"class":285},[48,1551,1442],{"class":281},[48,1553,1554],{"class":371},"4.",[48,1556,1448],{"class":285},[48,1558,646],{"class":281},[48,1560,1554],{"class":371},[48,1562,1448],{"class":285},[48,1564,1459],{"class":281},[48,1566,1567],{"class":264,"line":329},[48,1568,307],{"emptyLinePlaceholder":306},[48,1570,1571,1573,1576,1578,1581,1584,1587],{"class":264,"line":335},[48,1572,1420],{"class":285},[48,1574,1575],{"class":281}," at ",[48,1577,286],{"class":285},[48,1579,1580],{"class":281}," [](",[48,1582,1583],{"class":285},"float",[48,1585,1586],{"class":292}," z",[48,1588,1589],{"class":281},") {\n",[48,1591,1592,1595,1597,1599,1602,1604,1607,1609,1612],{"class":264,"line":363},[48,1593,1594],{"class":285},"  return",[48,1596,1428],{"class":316},[48,1598,1431],{"class":281},[48,1600,1601],{"class":316},"make_frame",[48,1603,390],{"class":281},[48,1605,1606],{"class":316},"tf",[48,1608,1431],{"class":281},[48,1610,1611],{"class":316},"make_transformation_from_translation",[48,1613,1614],{"class":281},"(\n",[48,1616,1617,1620,1623,1626,1628,1630,1632,1635,1638,1640,1642,1644,1646],{"class":264,"line":375},[48,1618,1619],{"class":316},"      tf",[48,1621,1622],{"class":281},"::vector",[48,1624,1625],{"class":285},"\u003Cfloat",[48,1627,646],{"class":281},[48,1629,393],{"class":371},[48,1631,458],{"class":285},[48,1633,1634],{"class":281},"{",[48,1636,1637],{"class":371},"0.",[48,1639,1448],{"class":285},[48,1641,646],{"class":281},[48,1643,1637],{"class":371},[48,1645,1448],{"class":285},[48,1647,1648],{"class":281},", z}));\n",[48,1650,1651],{"class":264,"line":399},[48,1652,1653],{"class":281},"};\n",[48,1655,1656,1659,1662,1665,1668,1670,1672,1674,1677,1679,1682,1684,1686,1689],{"class":264,"line":434},[48,1657,1658],{"class":316},"std",[48,1660,1661],{"class":281},"::vector forms{straddle.",[48,1663,1664],{"class":316},"polygons",[48,1666,1667],{"class":281},"() ",[48,1669,517],{"class":285},[48,1671,1428],{"class":316},[48,1673,1431],{"class":281},[48,1675,1676],{"class":316},"tag",[48,1678,390],{"class":281},[48,1680,1681],{"class":316},"at",[48,1683,390],{"class":281},[48,1685,826],{"class":371},[48,1687,1688],{"class":281},")),",[48,1690,1691],{"class":268},"   \u002F\u002F op 0\n",[48,1693,1694,1697,1699,1702,1704,1706,1708,1710,1712,1714,1716,1718,1720],{"class":264,"line":450},[48,1695,1696],{"class":281},"                  above_s.",[48,1698,1664],{"class":316},[48,1700,1701],{"class":281},"()  ",[48,1703,517],{"class":285},[48,1705,1428],{"class":316},[48,1707,1431],{"class":281},[48,1709,1676],{"class":316},[48,1711,390],{"class":281},[48,1713,1681],{"class":316},[48,1715,390],{"class":281},[48,1717,843],{"class":371},[48,1719,1688],{"class":281},[48,1721,1722],{"class":268},"   \u002F\u002F op 1\n",[48,1724,1725,1728,1730,1732,1734,1736,1738,1740,1742,1744,1746,1748,1750,1752],{"class":264,"line":467},[48,1726,1727],{"class":281},"                  below_s.",[48,1729,1664],{"class":316},[48,1731,1701],{"class":281},[48,1733,517],{"class":285},[48,1735,1428],{"class":316},[48,1737,1431],{"class":281},[48,1739,1676],{"class":316},[48,1741,390],{"class":281},[48,1743,1681],{"class":316},[48,1745,390],{"class":281},[48,1747,478],{"class":285},[48,1749,843],{"class":371},[48,1751,1688],{"class":281},[48,1753,1754],{"class":268},"  \u002F\u002F op 2\n",[48,1756,1757,1760,1762,1765,1767,1769,1771,1773,1775,1777,1779,1781,1784],{"class":264,"line":492},[48,1758,1759],{"class":281},"                  plane.",[48,1761,1664],{"class":316},[48,1763,1764],{"class":281},"()    ",[48,1766,517],{"class":285},[48,1768,1428],{"class":316},[48,1770,1431],{"class":281},[48,1772,1676],{"class":316},[48,1774,390],{"class":281},[48,1776,1681],{"class":316},[48,1778,390],{"class":281},[48,1780,826],{"class":371},[48,1782,1783],{"class":281},"))};",[48,1785,1786],{"class":268},"  \u002F\u002F op 3: the knife\n",[48,1788,1789],{"class":264,"line":523},[48,1790,307],{"emptyLinePlaceholder":306},[48,1792,1793,1795,1798,1801,1803,1805,1807,1810,1812],{"class":264,"line":551},[48,1794,1658],{"class":316},[48,1796,1797],{"class":281},"::array",[48,1799,1800],{"class":285},"\u003Cint",[48,1802,646],{"class":281},[48,1804,697],{"class":371},[48,1806,458],{"class":285},[48,1808,1809],{"class":281}," sheets{",[48,1811,393],{"class":371},[48,1813,1653],{"class":281},[48,1815,1816,1818,1821,1823,1825,1827,1830,1832,1834,1836,1839,1842,1844,1846,1848],{"class":264,"line":566},[48,1817,1420],{"class":285},[48,1819,1820],{"class":281}," graph ",[48,1822,286],{"class":285},[48,1824,1428],{"class":316},[48,1826,1431],{"class":281},[48,1828,1829],{"class":316},"make_csg_graph",[48,1831,390],{"class":281},[48,1833,1606],{"class":316},[48,1835,1431],{"class":281},[48,1837,1838],{"class":316},"make_range",[48,1840,1841],{"class":281},"(forms), ",[48,1843,1606],{"class":316},[48,1845,1431],{"class":281},[48,1847,1838],{"class":316},[48,1849,1850],{"class":281},"(sheets));\n",[48,1852,1853,1855,1858,1860,1862,1864,1867,1869,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896],{"class":264,"line":580},[48,1854,1420],{"class":285},[48,1856,1857],{"class":281}," solids ",[48,1859,286],{"class":285},[48,1861,1428],{"class":316},[48,1863,1431],{"class":281},[48,1865,1866],{"class":316},"csg",[48,1868,1431],{"class":281},[48,1870,1871],{"class":316},"merge",[48,1873,390],{"class":281},[48,1875,1606],{"class":316},[48,1877,1431],{"class":281},[48,1879,1866],{"class":316},[48,1881,1431],{"class":281},[48,1883,1871],{"class":316},[48,1885,390],{"class":281},[48,1887,826],{"class":371},[48,1889,646],{"class":281},[48,1891,697],{"class":371},[48,1893,921],{"class":281},[48,1895,843],{"class":371},[48,1897,1459],{"class":281},[48,1899,1900],{"class":264,"line":589},[48,1901,307],{"emptyLinePlaceholder":306},[48,1903,1904],{"class":264,"line":594},[48,1905,1906],{"class":268},"\u002F\u002F 1. boolean meshes\n",[48,1908,1909,1911,1914,1916,1918,1920,1923,1926,1928,1930,1932,1934,1937,1940,1942],{"class":264,"line":605},[48,1910,1420],{"class":285},[48,1912,1913],{"class":281}," above_mesh ",[48,1915,286],{"class":285},[48,1917,1428],{"class":316},[48,1919,1431],{"class":281},[48,1921,1922],{"class":316},"make_csg_mesh",[48,1924,1925],{"class":281},"(graph, ",[48,1927,1606],{"class":316},[48,1929,1431],{"class":281},[48,1931,1866],{"class":316},[48,1933,1431],{"class":281},[48,1935,1936],{"class":316},"difference",[48,1938,1939],{"class":281},"(solids, ",[48,1941,393],{"class":371},[48,1943,1944],{"class":281},"));\n",[48,1946,1947,1949,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1973,1975,1977],{"class":264,"line":611},[48,1948,1420],{"class":285},[48,1950,1951],{"class":281}," below_mesh ",[48,1953,286],{"class":285},[48,1955,1428],{"class":316},[48,1957,1431],{"class":281},[48,1959,1922],{"class":316},[48,1961,1925],{"class":281},[48,1963,1606],{"class":316},[48,1965,1431],{"class":281},[48,1967,1866],{"class":316},[48,1969,1431],{"class":281},[48,1971,1972],{"class":316},"intersection",[48,1974,1939],{"class":281},[48,1976,393],{"class":371},[48,1978,1944],{"class":281},[48,1980,1981],{"class":264,"line":625},[48,1982,307],{"emptyLinePlaceholder":306},[48,1984,1985],{"class":264,"line":656},[48,1986,1987],{"class":268},"\u002F\u002F 2. domains by expression\n",[48,1989,1990,1992,1995],{"class":264,"line":670},[48,1991,1420],{"class":285},[48,1993,1994],{"class":281}," [above_cells, above_ids] ",[48,1996,1997],{"class":285},"=\n",[48,1999,2000,2003,2005,2008,2010,2012,2014,2016,2018,2020,2022,2024],{"class":264,"line":677},[48,2001,2002],{"class":316},"    tf",[48,2004,1431],{"class":281},[48,2006,2007],{"class":316},"make_csg_domains",[48,2009,1925],{"class":281},[48,2011,1606],{"class":316},[48,2013,1431],{"class":281},[48,2015,1866],{"class":316},[48,2017,1431],{"class":281},[48,2019,1936],{"class":316},[48,2021,1939],{"class":281},[48,2023,393],{"class":371},[48,2025,1944],{"class":281},[48,2027,2028],{"class":264,"line":682},[48,2029,307],{"emptyLinePlaceholder":306},[48,2031,2032],{"class":264,"line":711},[48,2033,2034],{"class":268},"\u002F\u002F 3. domains by hand: imap.inclusion[k][3] is true below the knife\n",[48,2036,2037,2039,2042,2044,2046,2048,2050,2052,2054],{"class":264,"line":729},[48,2038,1420],{"class":285},[48,2040,2041],{"class":281}," [cells, ids, imap] ",[48,2043,286],{"class":285},[48,2045,1428],{"class":316},[48,2047,1431],{"class":281},[48,2049,2007],{"class":316},[48,2051,1925],{"class":281},[48,2053,1606],{"class":316},[48,2055,2056],{"class":281},"::return_index_map);\n",[255,2058,2063],{"className":2059,"code":2060,"filename":2061,"language":2062,"meta":29,"style":29},"language-ts shiki shiki-themes github-dark","import * as tf from \"@polydera\u002Ftrueform\";\n\nfunction sphere(z: number, radius: number) {\n  const m = tf.sphereMesh(radius, 32, 32);\n  m.points.add_(tf.ndarray(new Float32Array([0, 0, z]), [3]));\n  return m;\n}\nconst meshes = [sphere(0, 1.0),       \u002F\u002F op 0: straddles the knife\n                sphere(2, 0.5),       \u002F\u002F op 1: floats above\n                sphere(-2, 0.5),      \u002F\u002F op 2: floats below\n                tf.planeMesh(4, 4)];  \u002F\u002F op 3: the knife\n\nconst graph = tf.csgGraph(meshes, { sheets: [3] });\nconst solids = tf.op(0).or(1).or(2);\n\n\u002F\u002F 1. boolean meshes\nconst aboveMesh = graph.mesh(solids.sub(3));\nconst belowMesh = graph.mesh(solids.and(3));\n\n\u002F\u002F 2. domains by expression: { meshes, ids }\nconst above = graph.domains(solids.sub(3));\nconst below = graph.domains(solids.and(3));\n\n\u002F\u002F 3. domains by hand: a mask over the inclusion matrix\nconst all = graph.domains({ returnIndexMap: true });\nconst belowCells = all.meshes.filter((_, k) => all.inclusion.data[k * all.nTags + 3]);\n","JavaScript","ts",[127,2064,2065,2087,2091,2120,2148,2187,2194,2199,2229,2247,2266,2289,2293,2315,2353,2357,2361,2388,2412,2416,2421,2445,2468,2472,2477,2499],{"__ignoreMap":29},[48,2066,2067,2069,2072,2075,2078,2081,2084],{"class":264,"line":265},[48,2068,1017],{"class":285},[48,2070,2071],{"class":371}," *",[48,2073,2074],{"class":285}," as",[48,2076,2077],{"class":281}," tf ",[48,2079,2080],{"class":285},"from",[48,2082,2083],{"class":1410}," \"@polydera\u002Ftrueform\"",[48,2085,2086],{"class":281},";\n",[48,2088,2089],{"class":264,"line":272},[48,2090,307],{"emptyLinePlaceholder":306},[48,2092,2093,2096,2098,2100,2103,2106,2109,2111,2114,2116,2118],{"class":264,"line":278},[48,2094,2095],{"class":285},"function",[48,2097,1049],{"class":316},[48,2099,390],{"class":281},[48,2101,2102],{"class":292},"z",[48,2104,2105],{"class":285},":",[48,2107,2108],{"class":371}," number",[48,2110,646],{"class":281},[48,2112,2113],{"class":292},"radius",[48,2115,2105],{"class":285},[48,2117,2108],{"class":371},[48,2119,1589],{"class":281},[48,2121,2122,2125,2128,2131,2134,2137,2140,2142,2144,2146],{"class":264,"line":303},[48,2123,2124],{"class":285},"  const",[48,2126,2127],{"class":371}," m",[48,2129,2130],{"class":285}," =",[48,2132,2133],{"class":281}," tf.",[48,2135,2136],{"class":316},"sphereMesh",[48,2138,2139],{"class":281},"(radius, ",[48,2141,1065],{"class":371},[48,2143,646],{"class":281},[48,2145,1065],{"class":371},[48,2147,1459],{"class":281},[48,2149,2150,2153,2156,2159,2162,2164,2167,2170,2173,2175,2177,2179,2182,2184],{"class":264,"line":310},[48,2151,2152],{"class":281},"  m.points.",[48,2154,2155],{"class":316},"add_",[48,2157,2158],{"class":281},"(tf.",[48,2160,2161],{"class":316},"ndarray",[48,2163,390],{"class":281},[48,2165,2166],{"class":285},"new",[48,2168,2169],{"class":316}," Float32Array",[48,2171,2172],{"class":281},"([",[48,2174,826],{"class":371},[48,2176,646],{"class":281},[48,2178,826],{"class":371},[48,2180,2181],{"class":281},", z]), [",[48,2183,393],{"class":371},[48,2185,2186],{"class":281},"]));\n",[48,2188,2189,2191],{"class":264,"line":323},[48,2190,1594],{"class":285},[48,2192,2193],{"class":281}," m;\n",[48,2195,2196],{"class":264,"line":329},[48,2197,2198],{"class":281},"}\n",[48,2200,2201,2204,2207,2209,2212,2215,2217,2219,2221,2223,2226],{"class":264,"line":335},[48,2202,2203],{"class":285},"const",[48,2205,2206],{"class":371}," meshes",[48,2208,2130],{"class":285},[48,2210,2211],{"class":281}," [",[48,2213,2214],{"class":316},"sphere",[48,2216,390],{"class":281},[48,2218,826],{"class":371},[48,2220,646],{"class":281},[48,2222,1132],{"class":371},[48,2224,2225],{"class":281},"),       ",[48,2227,2228],{"class":268},"\u002F\u002F op 0: straddles the knife\n",[48,2230,2231,2234,2236,2238,2240,2242,2244],{"class":264,"line":363},[48,2232,2233],{"class":316},"                sphere",[48,2235,390],{"class":281},[48,2237,843],{"class":371},[48,2239,646],{"class":281},[48,2241,1158],{"class":371},[48,2243,2225],{"class":281},[48,2245,2246],{"class":268},"\u002F\u002F op 1: floats above\n",[48,2248,2249,2251,2253,2255,2257,2259,2261,2263],{"class":264,"line":375},[48,2250,2233],{"class":316},[48,2252,390],{"class":281},[48,2254,478],{"class":285},[48,2256,843],{"class":371},[48,2258,646],{"class":281},[48,2260,1158],{"class":371},[48,2262,1135],{"class":281},[48,2264,2265],{"class":268},"\u002F\u002F op 2: floats below\n",[48,2267,2268,2271,2274,2276,2279,2281,2283,2286],{"class":264,"line":399},[48,2269,2270],{"class":281},"                tf.",[48,2272,2273],{"class":316},"planeMesh",[48,2275,390],{"class":281},[48,2277,2278],{"class":371},"4",[48,2280,646],{"class":281},[48,2282,2278],{"class":371},[48,2284,2285],{"class":281},")];  ",[48,2287,2288],{"class":268},"\u002F\u002F op 3: the knife\n",[48,2290,2291],{"class":264,"line":434},[48,2292,307],{"emptyLinePlaceholder":306},[48,2294,2295,2297,2300,2302,2304,2307,2310,2312],{"class":264,"line":450},[48,2296,2203],{"class":285},[48,2298,2299],{"class":371}," graph",[48,2301,2130],{"class":285},[48,2303,2133],{"class":281},[48,2305,2306],{"class":316},"csgGraph",[48,2308,2309],{"class":281},"(meshes, { sheets: [",[48,2311,393],{"class":371},[48,2313,2314],{"class":281},"] });\n",[48,2316,2317,2319,2322,2324,2326,2329,2331,2333,2336,2339,2341,2343,2345,2347,2349,2351],{"class":264,"line":467},[48,2318,2203],{"class":285},[48,2320,2321],{"class":371}," solids",[48,2323,2130],{"class":285},[48,2325,2133],{"class":281},[48,2327,2328],{"class":316},"op",[48,2330,390],{"class":281},[48,2332,826],{"class":371},[48,2334,2335],{"class":281},").",[48,2337,2338],{"class":316},"or",[48,2340,390],{"class":281},[48,2342,697],{"class":371},[48,2344,2335],{"class":281},[48,2346,2338],{"class":316},[48,2348,390],{"class":281},[48,2350,843],{"class":371},[48,2352,1459],{"class":281},[48,2354,2355],{"class":264,"line":492},[48,2356,307],{"emptyLinePlaceholder":306},[48,2358,2359],{"class":264,"line":523},[48,2360,1906],{"class":268},[48,2362,2363,2365,2368,2370,2373,2376,2379,2382,2384,2386],{"class":264,"line":551},[48,2364,2203],{"class":285},[48,2366,2367],{"class":371}," aboveMesh",[48,2369,2130],{"class":285},[48,2371,2372],{"class":281}," graph.",[48,2374,2375],{"class":316},"mesh",[48,2377,2378],{"class":281},"(solids.",[48,2380,2381],{"class":316},"sub",[48,2383,390],{"class":281},[48,2385,393],{"class":371},[48,2387,1944],{"class":281},[48,2389,2390,2392,2395,2397,2399,2401,2403,2406,2408,2410],{"class":264,"line":566},[48,2391,2203],{"class":285},[48,2393,2394],{"class":371}," belowMesh",[48,2396,2130],{"class":285},[48,2398,2372],{"class":281},[48,2400,2375],{"class":316},[48,2402,2378],{"class":281},[48,2404,2405],{"class":316},"and",[48,2407,390],{"class":281},[48,2409,393],{"class":371},[48,2411,1944],{"class":281},[48,2413,2414],{"class":264,"line":580},[48,2415,307],{"emptyLinePlaceholder":306},[48,2417,2418],{"class":264,"line":589},[48,2419,2420],{"class":268},"\u002F\u002F 2. domains by expression: { meshes, ids }\n",[48,2422,2423,2425,2428,2430,2432,2435,2437,2439,2441,2443],{"class":264,"line":594},[48,2424,2203],{"class":285},[48,2426,2427],{"class":371}," above",[48,2429,2130],{"class":285},[48,2431,2372],{"class":281},[48,2433,2434],{"class":316},"domains",[48,2436,2378],{"class":281},[48,2438,2381],{"class":316},[48,2440,390],{"class":281},[48,2442,393],{"class":371},[48,2444,1944],{"class":281},[48,2446,2447,2449,2452,2454,2456,2458,2460,2462,2464,2466],{"class":264,"line":605},[48,2448,2203],{"class":285},[48,2450,2451],{"class":371}," below",[48,2453,2130],{"class":285},[48,2455,2372],{"class":281},[48,2457,2434],{"class":316},[48,2459,2378],{"class":281},[48,2461,2405],{"class":316},[48,2463,390],{"class":281},[48,2465,393],{"class":371},[48,2467,1944],{"class":281},[48,2469,2470],{"class":264,"line":611},[48,2471,307],{"emptyLinePlaceholder":306},[48,2473,2474],{"class":264,"line":625},[48,2475,2476],{"class":268},"\u002F\u002F 3. domains by hand: a mask over the inclusion matrix\n",[48,2478,2479,2481,2484,2486,2488,2490,2493,2496],{"class":264,"line":656},[48,2480,2203],{"class":285},[48,2482,2483],{"class":371}," all",[48,2485,2130],{"class":285},[48,2487,2372],{"class":281},[48,2489,2434],{"class":316},[48,2491,2492],{"class":281},"({ returnIndexMap: ",[48,2494,2495],{"class":371},"true",[48,2497,2498],{"class":281}," });\n",[48,2500,2501,2503,2506,2508,2511,2514,2517,2520,2522,2525,2527,2530,2533,2536,2539,2541,2544],{"class":264,"line":670},[48,2502,2203],{"class":285},[48,2504,2505],{"class":371}," belowCells",[48,2507,2130],{"class":285},[48,2509,2510],{"class":281}," all.meshes.",[48,2512,2513],{"class":316},"filter",[48,2515,2516],{"class":281},"((",[48,2518,2519],{"class":292},"_",[48,2521,646],{"class":281},[48,2523,2524],{"class":292},"k",[48,2526,535],{"class":281},[48,2528,2529],{"class":285},"=>",[48,2531,2532],{"class":281}," all.inclusion.data[k ",[48,2534,2535],{"class":285},"*",[48,2537,2538],{"class":281}," all.nTags ",[48,2540,1081],{"class":285},[48,2542,2543],{"class":371}," 3",[48,2545,2546],{"class":281},"]);\n",[11,2548,2549,2552],{},[73,2550,2551],{},"One mesh per side."," Each side comes back as one closed mesh: the straddler's half, capped by the knife, plus the floater on that side. Both sides have volume 2.5949. Use this when you want one surface, such as rock to export.",[11,2554,2555,2558],{},[73,2556,2557],{},"One mesh per domain."," The same volumes, now as separate cells: two above, two below. Each cell is its own closed mesh. Use this when each region needs its own material, volume or mesh.",[11,2560,2561,2564],{},[73,2562,2563],{},"A mask over the matrix."," Extract every domain once. Then any selection is a mask over the inclusion matrix. The knife's column says which cells lie below it. Ids are stable across reads of one graph, so the mask and the expression pick the same cells. Use this when a program, not a person, decides what to keep.",[59,2566,2568],{"id":2567},"if-you-know-fragments","If you know fragments",[11,2570,2571],{},"Engineering tools have names for these steps. Here is how they map.",[2573,2574],"data-table",{":headers":2575,":rows":2576},"[\"In Gmsh, OpenCASCADE or a geological modeller\",\"In the CSG graph\"]","[[\"Fragment, general fuse: split every operand against every other, each interface once\",\"tf.CsgGraph(meshes), then graph.domains(): conformal domains\"],[\"The parent–child map a fragment returns\",\"return_source_ids=True: the operand and input face of every output face\"],[\"Physical groups, material regions\",\"domain ids, and the rows of the inclusion matrix\"],[\"A contact surface with a younger and an older side\",\"a sheet: op(i) is the side behind its normal\"],[\"The surface chronology, which surface cuts which\",\"a boolean expression, like unit(k) in the tunnel drive above\"],[\"A fuzzy value, a gluing tolerance\",\"tolerance=0.0 by default: exact\"]]",[11,2578,2579],{},"The difference is the input. A CAD kernel splits B-rep solids and surfaces. The graph splits triangle meshes, open or closed, as they come from scans, exports and implicit modellers. CAD solids go in tessellated.",[59,2581,2583],{"id":2582},"fields-buffers-blends-and-slices","Fields: buffers, blends and slices",[11,2585,2586],{},"Some work is easier on a grid. A buffer zone around an ore body is an offset surface. A scan may already be a field. The volume module samples a closed mesh into a signed distance field, combines fields by union, intersection and difference, extracts a level set by flying edges or dual contouring, and draws contour lines on any plane.",[1005,2588,2589,2868],{},[255,2590,2592],{"className":257,"code":2591,"filename":1010,"language":259,"meta":29,"style":29},"# ore_mesh, block_mesh: closed meshes, e.g. domains read off the graph\ndims, spacing, origin = (96, 96, 48), (0.025,) * 3, (-1.2, -1.2, -0.6)\n\nore_field = tf.mesh_sdf(ore_mesh, dims, spacing, origin)\nrock_field = tf.mesh_sdf(block_mesh, dims, spacing, origin)\n\nbuffer = tf.isosurface(ore_field, 0.1)          # 0.1 outside the ore\nrock = tf.volume_boolean(rock_field, ore_field, \"difference\")\nfaces, points = tf.isosurface(rock, method=\"dual_contouring\")  # keeps edges sharp\n\npaths, points = tf.volume_slice_contours(       # a vertical section through the ore\n    rock, plane_origin=(-1.2, 0.1, -0.6), u=(1, 0, 0), v=(0, 0, 1),\n    dims2=(96, 48), spacing2=(0.025, 0.025), isovalues=0.0)\n",[127,2593,2594,2599,2657,2661,2671,2681,2685,2704,2719,2742,2746,2759,2826],{"__ignoreMap":29},[48,2595,2596],{"class":264,"line":265},[48,2597,2598],{"class":268},"# ore_mesh, block_mesh: closed meshes, e.g. domains read off the graph\n",[48,2600,2601,2604,2606,2609,2612,2614,2616,2618,2621,2624,2627,2630,2632,2634,2637,2639,2642,2644,2646,2648,2650,2652,2655],{"class":264,"line":272},[48,2602,2603],{"class":281},"dims, spacing, origin ",[48,2605,286],{"class":285},[48,2607,2608],{"class":281}," (",[48,2610,2611],{"class":371},"96",[48,2613,646],{"class":281},[48,2615,2611],{"class":371},[48,2617,646],{"class":281},[48,2619,2620],{"class":371},"48",[48,2622,2623],{"class":281},"), (",[48,2625,2626],{"class":371},"0.025",[48,2628,2629],{"class":281},",) ",[48,2631,2535],{"class":285},[48,2633,2543],{"class":371},[48,2635,2636],{"class":281},", (",[48,2638,478],{"class":285},[48,2640,2641],{"class":371},"1.2",[48,2643,646],{"class":281},[48,2645,478],{"class":285},[48,2647,2641],{"class":371},[48,2649,646],{"class":281},[48,2651,478],{"class":285},[48,2653,2654],{"class":371},"0.6",[48,2656,431],{"class":281},[48,2658,2659],{"class":264,"line":278},[48,2660,307],{"emptyLinePlaceholder":306},[48,2662,2663,2666,2668],{"class":264,"line":303},[48,2664,2665],{"class":281},"ore_field ",[48,2667,286],{"class":285},[48,2669,2670],{"class":281}," tf.mesh_sdf(ore_mesh, dims, spacing, origin)\n",[48,2672,2673,2676,2678],{"class":264,"line":310},[48,2674,2675],{"class":281},"rock_field ",[48,2677,286],{"class":285},[48,2679,2680],{"class":281}," tf.mesh_sdf(block_mesh, dims, spacing, origin)\n",[48,2682,2683],{"class":264,"line":323},[48,2684,307],{"emptyLinePlaceholder":306},[48,2686,2687,2690,2692,2695,2698,2701],{"class":264,"line":329},[48,2688,2689],{"class":281},"buffer ",[48,2691,286],{"class":285},[48,2693,2694],{"class":281}," tf.isosurface(ore_field, ",[48,2696,2697],{"class":371},"0.1",[48,2699,2700],{"class":281},")          ",[48,2702,2703],{"class":268},"# 0.1 outside the ore\n",[48,2705,2706,2709,2711,2714,2717],{"class":264,"line":335},[48,2707,2708],{"class":281},"rock ",[48,2710,286],{"class":285},[48,2712,2713],{"class":281}," tf.volume_boolean(rock_field, ore_field, ",[48,2715,2716],{"class":1410},"\"difference\"",[48,2718,431],{"class":281},[48,2720,2721,2723,2725,2728,2731,2733,2736,2739],{"class":264,"line":363},[48,2722,911],{"class":281},[48,2724,286],{"class":285},[48,2726,2727],{"class":281}," tf.isosurface(rock, ",[48,2729,2730],{"class":292},"method",[48,2732,286],{"class":285},[48,2734,2735],{"class":1410},"\"dual_contouring\"",[48,2737,2738],{"class":281},")  ",[48,2740,2741],{"class":268},"# keeps edges sharp\n",[48,2743,2744],{"class":264,"line":375},[48,2745,307],{"emptyLinePlaceholder":306},[48,2747,2748,2751,2753,2756],{"class":264,"line":399},[48,2749,2750],{"class":281},"paths, points ",[48,2752,286],{"class":285},[48,2754,2755],{"class":281}," tf.volume_slice_contours(       ",[48,2757,2758],{"class":268},"# a vertical section through the ore\n",[48,2760,2761,2764,2767,2769,2771,2773,2775,2777,2779,2781,2783,2785,2787,2790,2792,2794,2796,2798,2800,2802,2804,2806,2809,2811,2813,2815,2817,2819,2821,2823],{"class":264,"line":434},[48,2762,2763],{"class":281},"    rock, ",[48,2765,2766],{"class":292},"plane_origin",[48,2768,286],{"class":285},[48,2770,390],{"class":281},[48,2772,478],{"class":285},[48,2774,2641],{"class":371},[48,2776,646],{"class":281},[48,2778,2697],{"class":371},[48,2780,646],{"class":281},[48,2782,478],{"class":285},[48,2784,2654],{"class":371},[48,2786,921],{"class":281},[48,2788,2789],{"class":292},"u",[48,2791,286],{"class":285},[48,2793,390],{"class":281},[48,2795,697],{"class":371},[48,2797,646],{"class":281},[48,2799,826],{"class":371},[48,2801,646],{"class":281},[48,2803,826],{"class":371},[48,2805,921],{"class":281},[48,2807,2808],{"class":292},"v",[48,2810,286],{"class":285},[48,2812,390],{"class":281},[48,2814,826],{"class":371},[48,2816,646],{"class":281},[48,2818,826],{"class":371},[48,2820,646],{"class":281},[48,2822,697],{"class":371},[48,2824,2825],{"class":281},"),\n",[48,2827,2828,2831,2833,2835,2837,2839,2841,2843,2846,2848,2850,2852,2854,2856,2858,2861,2863,2866],{"class":264,"line":450},[48,2829,2830],{"class":292},"    dims2",[48,2832,286],{"class":285},[48,2834,390],{"class":281},[48,2836,2611],{"class":371},[48,2838,646],{"class":281},[48,2840,2620],{"class":371},[48,2842,921],{"class":281},[48,2844,2845],{"class":292},"spacing2",[48,2847,286],{"class":285},[48,2849,390],{"class":281},[48,2851,2626],{"class":371},[48,2853,646],{"class":281},[48,2855,2626],{"class":371},[48,2857,921],{"class":281},[48,2859,2860],{"class":292},"isovalues",[48,2862,286],{"class":285},[48,2864,2865],{"class":371},"0.0",[48,2867,431],{"class":281},[255,2869,2871],{"className":1397,"code":2870,"filename":1399,"language":1400,"meta":29,"style":29},"#include \u003Ctrueform\u002Ftrueform.hpp>\n#include \u003Ctrueform\u002Fvolume.hpp>\n\nauto field = [](const auto &polygons) {\n  return tf::make_mesh_sdf(polygons, {96, 96, 48},\n                           tf::point\u003Cfloat, 3>{0.025f, 0.025f, 0.025f},\n                           tf::point\u003Cfloat, 3>{-1.2f, -1.2f, -0.6f});\n};\nauto ore_field = field(ore.polygons());\nauto rock_field = field(block.polygons());\n\nauto buffer = tf::make_isosurface(ore_field.volume(), 0.1f);\nauto rock = tf::make_boolean(rock_field.volume(), ore_field.volume(),\n                             tf::volume_boolean_op::difference);\nauto surface = tf::make_isosurface(rock.volume(), 0.f,\n                                   tf::isosurface_method::dual_contouring);\n\nauto slice = tf::make_volume_slice(rock.volume(),\n                                   tf::point\u003Cfloat, 3>{-1.2f, 0.1f, -0.6f},\n                                   tf::point\u003Cfloat, 3>{1.f, 0.f, 0.f},\n                                   tf::point\u003Cfloat, 3>{0.f, 0.f, 1.f},\n                                   {96, 48}, {0.025f, 0.025f});\nauto section = tf::make_isocontours(slice.volume(), 0.f);\n",[127,2872,2873,2879,2886,2890,2913,2940,2976,3017,3021,3041,3059,3063,3094,3123,3136,3165,3178,3182,3204,3242,3276,3310,3336],{"__ignoreMap":29},[48,2874,2875,2877],{"class":264,"line":265},[48,2876,1407],{"class":285},[48,2878,1411],{"class":1410},[48,2880,2881,2883],{"class":264,"line":272},[48,2882,1407],{"class":285},[48,2884,2885],{"class":1410}," \u003Ctrueform\u002Fvolume.hpp>\n",[48,2887,2888],{"class":264,"line":278},[48,2889,307],{"emptyLinePlaceholder":306},[48,2891,2892,2894,2897,2899,2901,2903,2906,2909,2911],{"class":264,"line":303},[48,2893,1420],{"class":285},[48,2895,2896],{"class":281}," field ",[48,2898,286],{"class":285},[48,2900,1580],{"class":281},[48,2902,2203],{"class":285},[48,2904,2905],{"class":285}," auto",[48,2907,2908],{"class":285}," &",[48,2910,1664],{"class":292},[48,2912,1589],{"class":281},[48,2914,2915,2917,2919,2921,2924,2927,2929,2931,2933,2935,2937],{"class":264,"line":310},[48,2916,1594],{"class":285},[48,2918,1428],{"class":316},[48,2920,1431],{"class":281},[48,2922,2923],{"class":316},"make_mesh_sdf",[48,2925,2926],{"class":281},"(polygons, {",[48,2928,2611],{"class":371},[48,2930,646],{"class":281},[48,2932,2611],{"class":371},[48,2934,646],{"class":281},[48,2936,2620],{"class":371},[48,2938,2939],{"class":281},"},\n",[48,2941,2942,2945,2948,2950,2952,2954,2956,2958,2960,2962,2964,2966,2968,2970,2972,2974],{"class":264,"line":323},[48,2943,2944],{"class":316},"                           tf",[48,2946,2947],{"class":281},"::point",[48,2949,1625],{"class":285},[48,2951,646],{"class":281},[48,2953,393],{"class":371},[48,2955,458],{"class":285},[48,2957,1634],{"class":281},[48,2959,2626],{"class":371},[48,2961,1448],{"class":285},[48,2963,646],{"class":281},[48,2965,2626],{"class":371},[48,2967,1448],{"class":285},[48,2969,646],{"class":281},[48,2971,2626],{"class":371},[48,2973,1448],{"class":285},[48,2975,2939],{"class":281},[48,2977,2978,2980,2982,2984,2986,2988,2990,2992,2994,2996,2998,3000,3002,3004,3006,3008,3010,3012,3014],{"class":264,"line":329},[48,2979,2944],{"class":316},[48,2981,2947],{"class":281},[48,2983,1625],{"class":285},[48,2985,646],{"class":281},[48,2987,393],{"class":371},[48,2989,458],{"class":285},[48,2991,1634],{"class":281},[48,2993,478],{"class":285},[48,2995,2641],{"class":371},[48,2997,1448],{"class":285},[48,2999,646],{"class":281},[48,3001,478],{"class":285},[48,3003,2641],{"class":371},[48,3005,1448],{"class":285},[48,3007,646],{"class":281},[48,3009,478],{"class":285},[48,3011,2654],{"class":371},[48,3013,1448],{"class":285},[48,3015,3016],{"class":281},"});\n",[48,3018,3019],{"class":264,"line":335},[48,3020,1653],{"class":281},[48,3022,3023,3025,3028,3030,3033,3036,3038],{"class":264,"line":363},[48,3024,1420],{"class":285},[48,3026,3027],{"class":281}," ore_field ",[48,3029,286],{"class":285},[48,3031,3032],{"class":316}," field",[48,3034,3035],{"class":281},"(ore.",[48,3037,1664],{"class":316},[48,3039,3040],{"class":281},"());\n",[48,3042,3043,3045,3048,3050,3052,3055,3057],{"class":264,"line":375},[48,3044,1420],{"class":285},[48,3046,3047],{"class":281}," rock_field ",[48,3049,286],{"class":285},[48,3051,3032],{"class":316},[48,3053,3054],{"class":281},"(block.",[48,3056,1664],{"class":316},[48,3058,3040],{"class":281},[48,3060,3061],{"class":264,"line":399},[48,3062,307],{"emptyLinePlaceholder":306},[48,3064,3065,3067,3070,3072,3074,3076,3079,3082,3085,3088,3090,3092],{"class":264,"line":434},[48,3066,1420],{"class":285},[48,3068,3069],{"class":281}," buffer ",[48,3071,286],{"class":285},[48,3073,1428],{"class":316},[48,3075,1431],{"class":281},[48,3077,3078],{"class":316},"make_isosurface",[48,3080,3081],{"class":281},"(ore_field.",[48,3083,3084],{"class":316},"volume",[48,3086,3087],{"class":281},"(), ",[48,3089,2697],{"class":371},[48,3091,1448],{"class":285},[48,3093,1459],{"class":281},[48,3095,3096,3098,3101,3103,3105,3107,3110,3113,3115,3118,3120],{"class":264,"line":450},[48,3097,1420],{"class":285},[48,3099,3100],{"class":281}," rock ",[48,3102,286],{"class":285},[48,3104,1428],{"class":316},[48,3106,1431],{"class":281},[48,3108,3109],{"class":316},"make_boolean",[48,3111,3112],{"class":281},"(rock_field.",[48,3114,3084],{"class":316},[48,3116,3117],{"class":281},"(), ore_field.",[48,3119,3084],{"class":316},[48,3121,3122],{"class":281},"(),\n",[48,3124,3125,3128,3130,3133],{"class":264,"line":467},[48,3126,3127],{"class":316},"                             tf",[48,3129,1431],{"class":281},[48,3131,3132],{"class":316},"volume_boolean_op",[48,3134,3135],{"class":281},"::difference);\n",[48,3137,3138,3140,3143,3145,3147,3149,3151,3154,3156,3158,3160,3162],{"class":264,"line":492},[48,3139,1420],{"class":285},[48,3141,3142],{"class":281}," surface ",[48,3144,286],{"class":285},[48,3146,1428],{"class":316},[48,3148,1431],{"class":281},[48,3150,3078],{"class":316},[48,3152,3153],{"class":281},"(rock.",[48,3155,3084],{"class":316},[48,3157,3087],{"class":281},[48,3159,1637],{"class":371},[48,3161,1448],{"class":285},[48,3163,3164],{"class":281},",\n",[48,3166,3167,3170,3172,3175],{"class":264,"line":523},[48,3168,3169],{"class":316},"                                   tf",[48,3171,1431],{"class":281},[48,3173,3174],{"class":316},"isosurface_method",[48,3176,3177],{"class":281},"::dual_contouring);\n",[48,3179,3180],{"class":264,"line":551},[48,3181,307],{"emptyLinePlaceholder":306},[48,3183,3184,3186,3189,3191,3193,3195,3198,3200,3202],{"class":264,"line":566},[48,3185,1420],{"class":285},[48,3187,3188],{"class":281}," slice ",[48,3190,286],{"class":285},[48,3192,1428],{"class":316},[48,3194,1431],{"class":281},[48,3196,3197],{"class":316},"make_volume_slice",[48,3199,3153],{"class":281},[48,3201,3084],{"class":316},[48,3203,3122],{"class":281},[48,3205,3206,3208,3210,3212,3214,3216,3218,3220,3222,3224,3226,3228,3230,3232,3234,3236,3238,3240],{"class":264,"line":580},[48,3207,3169],{"class":316},[48,3209,2947],{"class":281},[48,3211,1625],{"class":285},[48,3213,646],{"class":281},[48,3215,393],{"class":371},[48,3217,458],{"class":285},[48,3219,1634],{"class":281},[48,3221,478],{"class":285},[48,3223,2641],{"class":371},[48,3225,1448],{"class":285},[48,3227,646],{"class":281},[48,3229,2697],{"class":371},[48,3231,1448],{"class":285},[48,3233,646],{"class":281},[48,3235,478],{"class":285},[48,3237,2654],{"class":371},[48,3239,1448],{"class":285},[48,3241,2939],{"class":281},[48,3243,3244,3246,3248,3250,3252,3254,3256,3258,3260,3262,3264,3266,3268,3270,3272,3274],{"class":264,"line":589},[48,3245,3169],{"class":316},[48,3247,2947],{"class":281},[48,3249,1625],{"class":285},[48,3251,646],{"class":281},[48,3253,393],{"class":371},[48,3255,458],{"class":285},[48,3257,1634],{"class":281},[48,3259,1445],{"class":371},[48,3261,1448],{"class":285},[48,3263,646],{"class":281},[48,3265,1637],{"class":371},[48,3267,1448],{"class":285},[48,3269,646],{"class":281},[48,3271,1637],{"class":371},[48,3273,1448],{"class":285},[48,3275,2939],{"class":281},[48,3277,3278,3280,3282,3284,3286,3288,3290,3292,3294,3296,3298,3300,3302,3304,3306,3308],{"class":264,"line":594},[48,3279,3169],{"class":316},[48,3281,2947],{"class":281},[48,3283,1625],{"class":285},[48,3285,646],{"class":281},[48,3287,393],{"class":371},[48,3289,458],{"class":285},[48,3291,1634],{"class":281},[48,3293,1637],{"class":371},[48,3295,1448],{"class":285},[48,3297,646],{"class":281},[48,3299,1637],{"class":371},[48,3301,1448],{"class":285},[48,3303,646],{"class":281},[48,3305,1445],{"class":371},[48,3307,1448],{"class":285},[48,3309,2939],{"class":281},[48,3311,3312,3315,3317,3319,3321,3324,3326,3328,3330,3332,3334],{"class":264,"line":605},[48,3313,3314],{"class":281},"                                   {",[48,3316,2611],{"class":371},[48,3318,646],{"class":281},[48,3320,2620],{"class":371},[48,3322,3323],{"class":281},"}, {",[48,3325,2626],{"class":371},[48,3327,1448],{"class":285},[48,3329,646],{"class":281},[48,3331,2626],{"class":371},[48,3333,1448],{"class":285},[48,3335,3016],{"class":281},[48,3337,3338,3340,3343,3345,3347,3349,3352,3355,3357,3359,3361,3363],{"class":264,"line":611},[48,3339,1420],{"class":285},[48,3341,3342],{"class":281}," section ",[48,3344,286],{"class":285},[48,3346,1428],{"class":316},[48,3348,1431],{"class":281},[48,3350,3351],{"class":316},"make_isocontours",[48,3353,3354],{"class":281},"(slice.",[48,3356,3084],{"class":316},[48,3358,3087],{"class":281},[48,3360,1637],{"class":371},[48,3362,1448],{"class":285},[48,3364,1459],{"class":281},[11,3366,3367,3368,31],{},"A field boolean is bounded by the grid's spacing. The graph's boolean has no grid. Use fields for offsets, blends and resampling. Use the graph when the meshes must come back as they went in. See the ",[21,3369,3372],{"href":3370,"rel":3371},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fvolume",[992],"volume docs",[59,3374,3376],{"id":3375},"where-to-go-next","Where to go next",[67,3378,3379,3388,3408,3436],{},[70,3380,3381,53,3384,3387],{},[73,3382,3383],{},"Two meshes.",[21,3385,3386],{"href":23},"Mesh booleans",": the hard-input tests, the speed of a single boolean, and code in three languages.",[70,3389,3390,53,3393,3396,3397,646,3400,3403,3404,3407],{},[73,3391,3392],{},"Tutorial.",[21,3394,3395],{"href":213},"Fast Variadic Mesh Booleans in C++",": one graph over 201 meshes, many expressions. The pairwise tutorials in ",[21,3398,1399],{"href":3399},"\u002Ftutorials\u002Ffast-mesh-booleans-in-cpp",[21,3401,1010],{"href":3402},"\u002Ftutorials\u002Ffast-mesh-booleans-in-python"," and ",[21,3405,2061],{"href":3406},"\u002Ftutorials\u002Ffast-mesh-booleans-in-javascript"," use the same module.",[70,3409,3410,3413,3414,646,3418,3403,3422,3427,3428,2608,3431,2335],{},[73,3411,3412],{},"Docs."," The CSG module, with sheets, selections, domains and the inclusion matrix, in ",[21,3415,1399],{"href":3416,"rel":3417},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fmodules\u002Fcsg",[992],[21,3419,1010],{"href":3420,"rel":3421},"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fcsg",[992],[21,3423,3426],{"href":3424,"rel":3425},"https:\u002F\u002Ftrueform.polydera.com\u002Fts\u002Fmodules\u002Fcsg",[992],"TypeScript",". The worked example: ",[21,3429,993],{"href":990,"rel":3430},[992],[21,3432,3435],{"href":3433,"rel":3434},"https:\u002F\u002Ftrueform.polydera.com\u002Fcpp\u002Fexamples\u002Farrangements",[992],"C++ version",[70,3437,3438,53,3441,31],{},[73,3439,3440],{},"Method.",[21,3442,3444],{"href":3443},"\u002Falgorithms\u002Ffast-and-exact-mesh-booleans-and-arrangements","Fast and Exact Mesh Booleans and Arrangements",[11,3446,3447,3448,3452],{},"The CSG graph is one module of ",[21,3449,3451],{"href":3450},"\u002Ftrueform","trueform",". The same engine does spatial queries, topology, remeshing and more, in C++, Python and the browser.",[3454,3455],"article-cta",{":buttons":3456},"[{\"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\":\"CSG docs\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fmodules\u002Fcsg\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Worked example\",\"to\":\"https:\u002F\u002Ftrueform.polydera.com\u002Fpy\u002Fexamples\u002Farrangements\",\"variant\":\"soft\",\"color\":\"neutral\"},{\"label\":\"Try it live\",\"to\":\"\u002Ftrueform\u002Flive\u002Fcsg-domains\",\"icon\":\"i-lucide-play\"}]",[3458,3459],"cite-as",{"author":3460,"title":5},"Sajovic, {\\v{Z}}iga",[3462,3463,3464],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":29,"searchDepth":272,"depth":272,"links":3466},[3467,3468,3469,3470,3471,3472,3473,3474,3475,3476],{"id":61,"depth":272,"text":62},{"id":115,"depth":272,"text":116},{"id":191,"depth":272,"text":192},{"id":223,"depth":272,"text":224},{"id":766,"depth":272,"text":767},{"id":878,"depth":272,"text":879},{"id":983,"depth":272,"text":984},{"id":2567,"depth":272,"text":2568},{"id":2582,"depth":272,"text":2583},{"id":3375,"depth":272,"text":3376},"2026-11-08","Hundreds of large, open, overlapping meshes in one exact build: sheets as knives, every enclosed region as its own conformal domain, regions picked by expression or mask, and the same model asked again as the design moves.","md",[5,3481,3482,3483,3484,3485,3486,3487,3488,3489,3490],"conformal domains","conformal mesh domains","constructive solid geometry","mesh CSG library","domain decomposition mesh","cut mesh with surface","mesh regions","N-ary mesh boolean","geological model CSG","tunnel excavation volume",{},"\u002Fimg\u002Fog\u002Falgorithms\u002Fcsg-for-engineering.png","\u002Falgorithms\u002Fcsg-for-engineering",{"title":3495,"description":3496},"CSG for Engineering: Conformal Domains, Sheets and Regions","Constructive solid geometry for engineering: many dirty meshes in one exact arrangement, cut by sheets into conformal domains, regions picked by expression or mask.","algorithms\u002Fcsg-for-engineering",[1866,2434,3499],"overview","2026-11-12T12:06:28.000Z","NfptNDUXKCgmIgMzFpGoSDCPFMkQ2A7o2bmShJg67CA",1794486339528]