← SplineCONTENT HISTORY

Update to Spline

Snapshot Oct 24, 2026 · 13:29 UTC · version 1.2.0

Collection source: downloaded plugin package.

WHAT CHANGED

First saved snapshot

No earlier snapshot is available to establish a change.

Save Spline and check future changes in Following.

Up to 10 plugins free · no card required.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Boolean (CSG) operations: carving holes, windows and tunnels with subtract(), merging shapes with union(), keeping overlaps with intersect() — operand ordering (which object survives a subtract), live editing of operands, adding cutters later, materials on the result, closed-geometry rules, and performance. Load for prompts like \"make a hole\", \"cut out\", \"carve\", \"boolean\", \"subtract\", \"hollow out\", \"window in a wall\", \"tunnel\", \"merge these shapes\".",
  "included_files": [],
  "name": "spline-3d-boolean-operations",
  "skill_md_contents": "---\nname: spline-3d-boolean-operations\ndescription: \"Boolean (CSG) operations: carving holes, windows and tunnels with subtract(), merging shapes with union(), keeping overlaps with intersect() — operand ordering (which object survives a subtract), live editing of operands, adding cutters later, materials on the result, closed-geometry rules, and performance. Load for prompts like \\\"make a hole\\\", \\\"cut out\\\", \\\"carve\\\", \\\"boolean\\\", \\\"subtract\\\", \\\"hollow out\\\", \\\"window in a wall\\\", \\\"tunnel\\\", \\\"merge these shapes\\\".\"\n---\n\n# Boolean operations (CSG)\n\nA Boolean combines 2+ meshes into ONE object by union, subtraction, or\nintersection — THE tool for holes, windows, tunnels, mugs, arches, and any\nshape the primitives can't express by stacking alone. It is LIVE: the operand\nmeshes become CHILDREN of the Boolean object and keep their own geometry and\ntransforms — move/scale/animate a child and the result recomputes. Only the\ncombined result renders; the children stop rendering individually.\n\nBooleans are HARD-EDGED and exact. If the merge should look SOFT and organic\n— shapes melting into each other like goo, liquid, or clay — that is a Shape\nBlend, not a Boolean: use `shapeBlend(...)` and load the `shape-blends`\nskill instead.\n\n## Create — ALWAYS two run_code calls\n\nBuilding a Boolean needs the operands' runtime bounding boxes, which only\nexist for objects created BEFORE the current run_code call. So the flow is\nalways: create (and name) the operand meshes in one call, combine them in the\nNEXT call. First call:\n```\nmesh('Cube', { width: 300, height: 300, depth: 300 });\nrename('Block');\nmesh('Cylinder', { radius: 60, height: 400 });\nrotation({ x: 90 });                 // aim the cutter along Z, through the cube\nrename('Drill');\n```\nSecond call:\n```\nsubtract(['Block', 'Drill']);\nrename('Block with hole');\n```\n- `boolean(operation, target?)` — wrap the targeted meshes (or the current\n  selection) into a new Boolean. `operation`: `'union'` | `'subtract'` |\n  `'intersect'`. `subtract(target?)` / `union(target?)` /\n  `intersect(target?)` are one-word shorthands for the same thing.\n- Reference operands by NAME or id in the combining call (`rename` them at\n  creation so the names are unambiguous). Objects from a `select(...)` in\n  the combining call also work — they existed before it. Passing a `mesh()`\n  return (or relying on its auto-selection) from the SAME call throws\n  \"cannot build a boolean with …\" — that error always means: split the\n  creation and the boolean into separate calls.\n- Omitting the target uses the current selection (pre-existing objects only,\n  same rule).\n- Non-mesh targets (lights, cameras, Empties) are ignored; a Boolean operates\n  on meshes only. Do NOT try `mesh('Boolean')` — it creates an empty Boolean\n  with no operands, which renders nothing.\n- Like `group`, the call RETURNS the new Boolean object, so `rename` right\n  after applies to it. A freshly created Boolean is itself subject to the\n  two-call rule: to use it as an operand of ANOTHER boolean (nesting), do\n  that in yet another call.\n\n## Subtract order — which object survives\n\nFor `subtract`, the BASE (the object that survives, with the others carved\nout of it) is the BOTTOM child in the scene tree. New objects are inserted at\nthe TOP of the tree, so among freshly created meshes the base is the one you\ncreated FIRST: create the solid first, then the cutters. Selection order does\nNOT matter — only tree order does.\n\nIf the order is wrong (you subtracted the block from the drill), fix it by\nreordering the children: `moveInto(theBoolean, 'Cutter', { before: 'Base' })`\n— the last child is the base. `union`/`intersect` are symmetric; order never\nmatters for them.\n\nCutters should PROTRUDE clean through the base — make them longer/deeper than\nthe wall they pierce (the 400-tall drill vs the 300 cube above). Never leave a\ncutter face exactly flush with a base face: coplanar faces make the CSG\nambiguous and the surface flickery. Overshoot by a few units.\n\n## Edit after creation\n\n- Operation: with ONE existing Boolean selected/targeted, `boolean(op)`\n  changes its operation in place — `boolean('intersect', 'Block with hole')`\n  retypes it, no re-wrapping.\n- Operands: they're normal children — `select(o => o.id === '<childId>')`\n  (ids from `get_scene`, where they're listed with the Boolean as parent),\n  then transform as usual. Moving the drill moves the hole. `animate` on a\n  child transform = an animated hole.\n- Add a cutter later: create the mesh in one call, then reparent it in the\n  NEXT call — for a subtract, keep the base LAST:\n  `moveInto('Block with hole', 'New cutter', { before: 'Drill' })`. Plain\n  `moveInto` appends last, which would silently make the new mesh the base.\n- Remove a cutter: `moveInto('root', cutter)` pulls it back out (world\n  transform preserved), or `remove(cutter)` deletes it.\n- Dissolve: `ungroup(theBoolean)` frees the operands back into plain meshes\n  and deletes the Boolean wrapper.\n- Nesting works: a Boolean can be an operand of another Boolean (the mug\n  recipe below). Booleans transform like any mesh — `position`/`rotation`/\n  `scale`/`group`/`animate` on the Boolean move the whole result.\n\n## Materials\n\nThe result renders with the Boolean object's OWN single material (seeded from\nthe base operand's material at creation). Recoloring a child changes nothing\nvisible — select the BOOLEAN and use `color`/`layer`/`shading` as usual.\n\nShading of cut edges: `geometry({ phongAngle: 60 })` on the selected Boolean\nraises the smooth-shading angle (default 35) — raise it if a curved cut looks\nfaceted, lower it toward 0 for deliberately crisp facets.\n\n## Closed geometry — the rule that breaks booleans\n\nOperands must be CLOSED solids (watertight). An operand with open edges is\nsilently DROPPED from the result (only a subtract's base may be open):\n- Flat shapes (`Plane`, `Rectangle`, `Ellipse`, `Polygon`, `Star`,\n  `Triangle`) have no volume — give them a `depth` (extrude) before using\n  them as operands. A cutter that \"does nothing\" is almost always this.\n- Avoid `openEnded: true` cylinders/cones as operands.\n- Primitives, extruded shapes, extruded `Text`, and Toruses are fine.\nIf the result looks like plain union-less overlap or a cutter has no effect,\ncheck these before anything else; also confirm the operands actually overlap\n(`get_scene` positions + `size`).\n\n## Recipes\n\nWindow in a wall (multiple cutters, one subtract). Call 1 — create + name:\n```\nmesh('Cube', { width: 800, height: 400, depth: 24 });\nrename('Wall base');\nmesh('Cube', { width: 160, height: 200, depth: 60 });\ntranslate({ x: -180, y: 30 });\nrename('Window L');\nmesh('Cube', { width: 160, height: 200, depth: 60 });\ntranslate({ x: 180, y: 30 });\nrename('Window R');\n```\nCall 2 — combine:\n```\nsubtract(['Wall base', 'Window L', 'Window R']);\nrename('Wall');\n```\nAll children above the base are carved out in one operation — one Boolean,\nnot one per cutter. (Cutter depth 60 > wall 24: clean through-cuts.)\n\nMug (hollow with subtract, then union the handle onto the Boolean — nesting\nneeds its own extra call). Call 1 — the parts:\n```\nmesh('Cylinder', { radius: 90, height: 220 });\nrename('Body');\nmesh('Cylinder', { radius: 78, height: 220 });\ntranslate({ y: 24 });                    // leaves a floor; rim overshoots\nrename('Bore');\nmesh('Torus', { width: 130, height: 130 });\ntranslate({ x: 100 });                   // sink it into the cup wall\nrename('Handle');\n```\nCall 2 — hollow: `subtract(['Body', 'Bore']); rename('Cup');`\nCall 3 — attach: `union(['Cup', 'Handle']); rename('Mug');`\n\nLens / rounded tile: `intersect` two overlapping spheres (or a sphere and a\ncube) — the shared volume is the result.\n\n## Performance\n\nThe result recomputes live whenever an operand changes — cost scales with\ntriangle counts. Keep operand segment counts modest (a 24-segment sphere cuts\nlike a 64-segment one, far cheaper); prefer animating child `scale`/position\nover geometry `width`/`height`/`depth`; don't stack dozens of live Booleans\nwhen one subtract with several cutters does the job.\n\n## Verifying\n\n`run_code` reports the Boolean's id; `get_scene` shows it as a Mesh with\ngeometryType `BooleanGeometry`, its `size` being the RESULT's real bounds —\nan intersect that missed (no overlap) reads as size ~0 there. A hole doesn't\nchange the bounds, so judge cuts visually: `set_view` + `take_screenshot`\nfrom an angle that FACES the cut — a hole reads as nothing from the side.\n"
}

SHA-256 of public snapshot: 23f47cfc2107398a562cf725803c1c86830c5dff5b09b787c6f77ddb3fc4160b