docs / Design guide

Design guide

Find native cookbook recipes, recorded shop checks, and material catalog snapshots. Read the source, adapt the recipe, and verify your model before manufacture.

Agents can use the existing lookup_cookbook tool to retrieve recipes. Shop rows are dated snapshots of our local rule/catalog data, not current vendor acceptance or a guarantee of manufacturing suitability.

MCP tool reference and setup · Short workflow demos

All entries are listed below. Browser Find also works without JavaScript.

4-DOF open-chain arm release pack — materials, reach animation, static hold

recipe · Prompt asks to turn a production 4-DOF open-chain arm (base yaw, shoulder, elbow, wrist, bridged yokes, named materials) into a release/evidence package with an animationView reach cycle and declared actuator capacities. This script evaluates standalone: param() sits beside animationView, and kinematic.checkStaticHold runs against body-tree revolutes (mates are not in that joint list). Prefer design_loop until evaluate_script reports mechanism=real. Follow-up MCP, not more geometry: inspect/export BOM, exploded PDF/SVG drawing, and usd-isaac with explicit drives on fastened/revolute mates only. run_fea only when CalculiX and gmsh are present — otherwise report FEA UNVERIFIED. No joint.scissorLift and no closed-loop FK.

assembly · connector · mate · revolute · joint · kinematic · parameter · actuator · material · static-hold · torque · bom · export · fea · four DOF release evidence pack · 4 DOF arm release package · open-chain reach cycle animationView · param baseYawDeg shoulderDeg elbowDeg wristDeg · kinematic checkStaticHold actuator torque · BOM drawing USD evidence checklist · FEA unverified CalculiX stop · usd-isaac explicit drives only · no joint.scissorLift closed-loop · bridged yoke production arm materials

Recipe source

Recipe code
// 4-DOF open-chain release pack. KEEP param() beside animationView — tracks
// must name those live params (animation.param.unknown if you drop them).
// No joint.scissorLift, no closed-loop FK, no Ferrari styling.
// checkStaticHold reads arm.revolute actuators, NOT mates. solvedModel must
// receive those joint poses; mates still drive the mechanism geometry.
//
// Joint hardware: pitch joints are joint.clevis (fork sandwiches a round
// tongue, capped pin through both ears). Each link tube starts inside its
// tongue so the spar is seated in the hub. Parent yokes (hub + side spines
// + cheek disks) bridge the fork back into the tube — sub-millimetre
// running clearance, not a multi-mm air gap. Yaw is a spigot inside a bored
// collar (0.28 mm radial, ~0.4 mm axial) so the turret sits on the pedestal
// without a shared volume. Servo bottoms are 0.2 mm above their shelves.
//
// Follow-up AFTER evaluate_script ok + mechanism=real (do not fake results):
// 1. inspect({ of: 'bom' }) or export bom-csv|bom-json. Materials are set;
//    mass is null only if a row is still bom.material.unassigned.
// 2. Export pdf-drawing / svg-drawing with exploded isometric, balloons,
//    parts list, auto dimensions, datums/GD&T, title block. Auto-GD&T is
//    generated annotation, not a certification.
// 3. export({ target: 'model', format: 'usd-isaac', output_path,
//    options: { drives: { 'base-yaw': { stiffness, damping }, ... } } }).
//    Drives are only the gains you declare. Positive mass required.
//    Stop if a mate is cylindrical / pin_slot / ball / planar.
// 4. FEA: fea_summary({}). If CalculiX+gmsh are absent, report UNVERIFIED.
//    If present, feaStudy on a critical base/bracket solid then
//    run_fea({ file, output_dir }) and trust summary.trust before citing
//    a safety factor. Do not treat a missing solver as a pass.
// 5. open_in_studio and capture_animation({ file }) with pose verification.

const baseYawDeg = param('baseYawDeg', 20, { min: -90, max: 90 });
const shoulderDeg = param('shoulderDeg', 25, { min: -15, max: 55 });
const elbowDeg = param('elbowDeg', -40, { min: -85, max: 70 });
const wristDeg = param('wristDeg', 15, { min: -90, max: 90 });
animationView({
  name: 'reach cycle',
  tracks: [
    { param: 'baseYawDeg', keys: [{ atMs: 0, value: -30 }, { atMs: 1200, value: 40, ease: 'easeInOut' }, { atMs: 2400, value: -30, ease: 'easeInOut' }] },
    { param: 'shoulderDeg', keys: [{ atMs: 0, value: 15 }, { atMs: 1200, value: 45, ease: 'easeInOut' }, { atMs: 2400, value: 15, ease: 'easeInOut' }] },
    { param: 'elbowDeg', keys: [{ atMs: 0, value: -30 }, { atMs: 1200, value: -60, ease: 'easeInOut' }, { atMs: 2400, value: -30, ease: 'easeInOut' }] },
    { param: 'wristDeg', keys: [{ atMs: 0, value: 0 }, { atMs: 1200, value: 35, ease: 'easeInOut' }, { atMs: 2400, value: 0, ease: 'easeInOut' }] },
  ],
  fps: 24,
});

const YAW_Z = 54;
const SHOULDER_Z = 36;
const upperLen = 112;
const foreLen = 90;
const wristLen = 44;
const yawBoreR = 16.28;

const pinMat = { baseColor: '#1c2228', metalness: 0.94, roughness: 0.22 };

function clevisStyle(knuckleR, tongueY, plateT, pinR) {
  return {
    knuckleR,
    tongueY,
    forkGapY: tongueY + 0.8,
    plateT,
    pinR,
    pinCapR: pinR + 2.4,
    pinCapThickness: pinR + 2.2,
    holeClearance: 0.15,
    pinMaterial: pinMat,
  };
}

const shoulderStyle = clevisStyle(14, 16, 5.4, 4.2);
const elbowStyle = clevisStyle(12, 13, 4.8, 3.6);
const wristStyle = clevisStyle(10, 11, 4.2, 3.1);
const shoulderLimits = [-15, 55];
const elbowLimits = [-85, 70];
const wristLimits = [-90, 90];

function xTube(x0, x1, r) {
  return cylinder(x1 - x0, r).rotate([0, 1, 0], 90).translate(x0, 0, 0);
}

function yCyl(len, r, cx, cz) {
  return cylinder(len, r).rotate([1, 0, 0], 90).translate(cx, len / 2, cz);
}

function capFaceY(style) {
  // Outer axial face of the clevis cap (pin axis is Y). pinCapR is radial.
  const shaftLen = style.forkGapY + 2 * style.plateT;
  return shaftLen / 2 + style.pinCapThickness - 0.5;
}

function parentYoke(pivotX, style, tubeR) {
  const plateOffset = style.forkGapY / 2 + style.plateT / 2;
  // Stop 0.35 mm before the tongue disk so the parent never enters it.
  const stop = pivotX - style.knuckleR - 0.35;
  const collar = xTube(stop - 18, stop, tubeR + 1.8);
  const cheek = (ySign) => yCyl(style.plateT, style.knuckleR, pivotX, 0)
    .translate(0, ySign * plateOffset, 0);
  const spineZ = Math.max(tubeR * 1.2, style.knuckleR * 0.72);
  const spine = (ySign) => box(style.knuckleR + 12, style.plateT, spineZ, false).translate(
    stop - 10,
    ySign * plateOffset - style.plateT / 2,
    -spineZ / 2,
  );
  return collar.union(cheek(1)).union(cheek(-1)).union(spine(1)).union(spine(-1));
}

const HORN_Z = 8;

function servoPad(mount, bodyW, bodyD, yInner) {
  const zTop = mount[2] - 0.2;
  const yEnd = mount[1] + bodyD / 2 + 4;
  const xHalf = bodyW / 2 + 8;
  return box(xHalf * 2, yEnd - yInner, 5, false).translate(mount[0] - xHalf, yInner, zTop - 5);
}

function dropGusset(pivotX, knuckleR, tubeR, shelfTop) {
  const x1 = pivotX - knuckleR - 1.2;
  const x0 = x1 - 18;
  const z1 = -tubeR + 2.2;
  const z0 = shelfTop - 4;
  return box(x1 - x0, tubeR + 6, z1 - z0, false).translate(x0, -2, z0);
}

function servoSolid(w, d, h) {
  const body = box(w, d, h, true).translate(0, 0, h / 2);
  const ear = box(w + 14, 3.6, 2.8, true).translate(0, 0, h - 0.4);
  const screw = (x) => cylinder(h + 1.7, 1.45).translate(x, 0, -0.12).finish('steel');
  // Side output at pin height. The horn stops 0.6 mm short of the cap face
  // and stays above the mounting pad (pad top is 0.2 mm below the body).
  const stub = cylinder(4.2, 2.3).rotate([1, 0, 0], 90).translate(0, -d / 2 + 0.5, HORN_Z).finish('steel');
  const horn = cylinder(1.6, 5.4).rotate([1, 0, 0], 90).translate(0, -d / 2 - 2.6, HORN_Z).finish('abs', { color: '#c5ced6' });
  return body.union(ear).union(screw((w + 9) / 2)).union(screw(-(w + 9) / 2)).union(stub).union(horn);
}

const upperTubeR = shoulderStyle.tongueY / 2 - 0.45;
const foreTubeR = elbowStyle.tongueY / 2 - 0.45;
const wristTubeR = wristStyle.tongueY / 2 - 0.4;

const foot = (x, y) => cylinder(3.2, 5.2).translate(x, y, 6.2);
const baseSolid = box(152, 116, 8, true).translate(0, 0, 4)
  .union(cylinder(34, 20).translate(0, 0, 8))
  .union(cylinder(4, 32).translate(0, 0, 42))
  .union(cylinder(16, 16).translate(0, 0, 46))
  .union(foot(58, 42)).union(foot(58, -42)).union(foot(-58, 42)).union(foot(-58, -42))
  .union(box(42, 34, 30, true).translate(-54, 0, 20).finish('abs', { color: '#243140' }))
  .union(cylinder(18, 4.2).rotate([0, 1, 0], 90).translate(-36, 0, 30).finish('steel'));

const shoulderPlateY = shoulderStyle.forkGapY / 2 + shoulderStyle.plateT / 2;
const mastTop = SHOULDER_Z - shoulderStyle.knuckleR - 0.4;
const shoulderWeb = (ySign) => box(16, shoulderStyle.plateT, 22, true).translate(
  0,
  ySign * shoulderPlateY,
  SHOULDER_Z - 6,
);
const shoulderServoD = 18;
const shoulderMount = [0, capFaceY(shoulderStyle) + shoulderServoD / 2 + 4.8, SHOULDER_Z - HORN_Z];
const turretSolid = cylinder(17.75, 23).translate(0, 0, -7.55)
  .subtract(cylinder(16.7, yawBoreR).translate(0, 0, -8.4))
  .union(cylinder(mastTop - 8.5, 17).translate(0, 0, 8.5))
  .union(shoulderWeb(1))
  .union(shoulderWeb(-1))
  .union(servoPad(shoulderMount, 32, shoulderServoD, shoulderStyle.tongueY / 2 + 0.5));

const elbowServoD = 16;
const wristServoD = 14;
const elbowMount = [upperLen, capFaceY(elbowStyle) + elbowServoD / 2 + 4.8, -HORN_Z];
const wristMount = [foreLen, capFaceY(wristStyle) + wristServoD / 2 + 4.8, -HORN_Z];
const upperRaw = xTube(shoulderStyle.pinR + 0.55, upperLen - elbowStyle.knuckleR - 0.15, upperTubeR)
  .union(parentYoke(upperLen, elbowStyle, upperTubeR))
  .union(servoPad(elbowMount, 28, elbowServoD, elbowStyle.tongueY / 2 + 0.5))
  .union(dropGusset(upperLen, elbowStyle.knuckleR, upperTubeR, elbowMount[2] - 0.2));
const foreRaw = xTube(elbowStyle.pinR + 0.55, foreLen - wristStyle.knuckleR - 0.15, foreTubeR)
  .union(parentYoke(foreLen, wristStyle, foreTubeR))
  .union(servoPad(wristMount, 24, wristServoD, wristStyle.tongueY / 2 + 0.5))
  .union(dropGusset(foreLen, wristStyle.knuckleR, foreTubeR, wristMount[2] - 0.2));
const wristRaw = xTube(wristStyle.pinR + 0.55, wristLen, wristTubeR)
  .union(xTube(wristLen - 2, wristLen + 9, 11.5));

// Pin axis is -Y so a positive pitch angle lifts the link. The reach-cycle
// keys are positive; +Y would drive the arm down through the pedestal.
const wristJoint = joint.clevis({
  parentBody: foreRaw,
  childBody: wristRaw,
  axis: [0, -1, 0],
  pivotParent: [foreLen, 0, 0],
  pivotChild: [0, 0, 0],
  limitsDeg: wristLimits,
  liftDir: [1, 0, 0],
  liftPivot: false,
  style: wristStyle,
});
const elbowJoint = joint.clevis({
  parentBody: upperRaw,
  childBody: wristJoint.parentGeometry,
  axis: [0, -1, 0],
  pivotParent: [upperLen, 0, 0],
  pivotChild: [0, 0, 0],
  limitsDeg: elbowLimits,
  liftDir: [1, 0, 0],
  liftPivot: false,
  style: elbowStyle,
});
const shoulderJoint = joint.clevis({
  parentBody: turretSolid,
  childBody: elbowJoint.parentGeometry,
  axis: [0, -1, 0],
  pivotParent: [0, 0, SHOULDER_Z],
  pivotChild: [0, 0, 0],
  limitsDeg: shoulderLimits,
  liftDir: [0, 0, 1],
  liftPivot: false,
  style: shoulderStyle,
});

const arm = assembly('multi-dof-4axis-release');

const base = arm.part('base-frame', baseSolid, { material: 'mild-steel' });
base.connector('yaw', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, YAW_Z] },
  axis: [0, 0, 1],
  jointClearanceRadius: yawBoreR,
});

const turret = arm.part('yaw-turret', shoulderJoint.parentGeometry, { material: 'aluminum-6061' });
turret.connector('yaw', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 0] },
  axis: [0, 0, 1],
  jointClearanceRadius: yawBoreR,
});
turret.connector('shoulder', {
  type: 'axis',
  origin: { kind: 'vec3', value: shoulderJoint.parentConnector.origin },
  axis: shoulderJoint.parentConnector.axis,
  jointClearanceRadius: shoulderJoint.parentConnector.clearanceRadius,
});
turret.connector('shoulder-servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: shoulderMount },
});

const shoulderServo = arm.part(
  'shoulder-servo',
  servoSolid(32, shoulderServoD, 30).translate(shoulderMount[0], shoulderMount[1], shoulderMount[2]),
  { material: 'abs' },
);
shoulderServo.connector('mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: shoulderMount },
});

const upper = arm.part('upper-link', shoulderJoint.childGeometry, { material: 'aluminum-6061' });
upper.connector('shoulder', {
  type: 'axis',
  origin: { kind: 'vec3', value: shoulderJoint.childConnector.origin },
  axis: shoulderJoint.childConnector.axis,
  jointClearanceRadius: shoulderJoint.childConnector.clearanceRadius,
});
upper.connector('elbow', {
  type: 'axis',
  origin: { kind: 'vec3', value: elbowJoint.parentConnector.origin },
  axis: elbowJoint.parentConnector.axis,
  jointClearanceRadius: elbowJoint.parentConnector.clearanceRadius,
});
upper.connector('elbow-servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: elbowMount },
});

const elbowServo = arm.part('elbow-servo', servoSolid(28, elbowServoD, 26).translate(elbowMount[0], elbowMount[1], elbowMount[2]), { material: 'abs' });
elbowServo.connector('mount', { type: 'frame', origin: { kind: 'vec3', value: elbowMount } });

const forearm = arm.part('forearm-link', elbowJoint.childGeometry, { material: 'aluminum-6061' });
forearm.connector('elbow', {
  type: 'axis',
  origin: { kind: 'vec3', value: elbowJoint.childConnector.origin },
  axis: elbowJoint.childConnector.axis,
  jointClearanceRadius: elbowJoint.childConnector.clearanceRadius,
});
forearm.connector('wrist', {
  type: 'axis',
  origin: { kind: 'vec3', value: wristJoint.parentConnector.origin },
  axis: wristJoint.parentConnector.axis,
  jointClearanceRadius: wristJoint.parentConnector.clearanceRadius,
});
forearm.connector('wrist-servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: wristMount },
});

const wristServo = arm.part('wrist-servo', servoSolid(24, wristServoD, 22).translate(wristMount[0], wristMount[1], wristMount[2]), { material: 'abs' });
wristServo.connector('mount', { type: 'frame', origin: { kind: 'vec3', value: wristMount } });

const wrist = arm.part('wrist-link', wristJoint.childGeometry, { material: 'aluminum-6061' });
wrist.connector('proximal', {
  type: 'axis',
  origin: { kind: 'vec3', value: wristJoint.childConnector.origin },
  axis: wristJoint.childConnector.axis,
  jointClearanceRadius: wristJoint.childConnector.clearanceRadius,
});

arm.mate('base-yaw', 'base-frame.yaw', 'yaw-turret.yaw', 'revolute', { pose: baseYawDeg, limitsDeg: [-90, 90] });
arm.mate('shoulder-pitch', 'yaw-turret.shoulder', 'upper-link.shoulder', 'revolute', { pose: shoulderDeg, limitsDeg: shoulderLimits });
arm.mate('elbow-pitch', 'upper-link.elbow', 'forearm-link.elbow', 'revolute', { pose: elbowDeg, limitsDeg: elbowLimits });
arm.mate('wrist-pitch', 'forearm-link.wrist', 'wrist-link.proximal', 'revolute', { pose: wristDeg, limitsDeg: wristLimits });
arm.mate('shoulder-servo-fix', 'yaw-turret.shoulder-servo-mount', 'shoulder-servo.mount', 'fastened');
arm.mate('elbow-servo-fix', 'upper-link.elbow-servo-mount', 'elbow-servo.mount', 'fastened');
arm.mate('wrist-servo-fix', 'forearm-link.wrist-servo-mount', 'wrist-servo.mount', 'fastened');

arm.mechanicalJoint('yaw-drive', {
  mate: 'base-yaw', actuator: 'base-frame', shaft: 'base-frame', supports: ['base-frame'], output: 'yaw-turret',
  requiredSupport: { kind: 'bearing', around: 'base-frame.yaw', supports: ['base-frame'], minBearingLengthMm: 20 },
});
arm.mechanicalJoint('shoulder-drive', {
  mate: 'shoulder-pitch', actuator: 'shoulder-servo', shaft: 'yaw-turret', supports: ['yaw-turret'], output: 'upper-link',
  requiredSupport: { kind: 'hinge-bracket', around: 'yaw-turret.shoulder', supports: ['yaw-turret'], minBearingLengthMm: 28 },
});
arm.mechanicalJoint('elbow-drive', {
  mate: 'elbow-pitch', actuator: 'elbow-servo', shaft: 'upper-link', supports: ['upper-link'], output: 'forearm-link',
  requiredSupport: { kind: 'hinge-bracket', around: 'upper-link.elbow', supports: ['upper-link'], minBearingLengthMm: 24 },
});
arm.mechanicalJoint('wrist-drive', {
  mate: 'wrist-pitch', actuator: 'wrist-servo', shaft: 'forearm-link', supports: ['forearm-link'], output: 'wrist-link',
  requiredSupport: { kind: 'hinge-bracket', around: 'forearm-link.wrist', supports: ['forearm-link'], minBearingLengthMm: 20 },
});

arm.revolute('yaw-hold', base, turret, { axis: [0, 0, 1], origin: [0, 0, YAW_Z], limitsDeg: [-90, 90], actuator: { torqueNm: 25 } });
arm.revolute('shoulder-hold', turret, upper, {
  axis: shoulderJoint.parentConnector.axis,
  origin: shoulderJoint.parentConnector.origin,
  limitsDeg: shoulderLimits,
  actuator: { torqueNm: 45 },
});
arm.revolute('elbow-hold', upper, forearm, {
  axis: elbowJoint.parentConnector.axis,
  origin: elbowJoint.parentConnector.origin,
  limitsDeg: elbowLimits,
  actuator: { torqueNm: 22 },
});
arm.revolute('wrist-hold', forearm, wrist, {
  axis: wristJoint.parentConnector.axis,
  origin: wristJoint.parentConnector.origin,
  limitsDeg: wristLimits,
  actuator: { torqueNm: 8 },
});

const hold = await kinematic.checkStaticHold(arm, { minTorqueMarginPct: 15 });
if (!hold.ok) {
  throw new Error('static-hold exceeded: ' + hold.diagnostics.map((d) => d.code + ': ' + d.message).join(' | '));
}

return arm.solvedModel(
  { 'yaw-hold': 0, 'shoulder-hold': 0, 'elbow-hold': 0, 'wrist-hold': 0 },
);

Declare a connector and join parts with a revolute mate

recipe · The model has multiple mechanical parts (not a single fused body) that need a named pivot between them. Declare an axis connector on each part with partRef.connector(name, { type: 'axis', origin: { kind: 'vec3', value: [...] }, axis: [...] }), then join the connectors with arm.mate(name, 'partA.conn', 'partB.conn', 'revolute', { limitsDeg: [min, max] }). This is the canonical assembly-topology vocabulary for hinges, elbows, and any revolute joint — connector origins must use the tagged { kind: 'vec3', value: [...] } form, not a bare [x, y, z] array.

assembly · connector · mate · revolute · hinge · joint · assembly with parts connectors and mates · how do I declare a connector · revolute joint · hinge · axis connector origin · pivot joint between parts · declare connector and mate

Recipe source

Recipe code
const arm = assembly('two-link-arm');

const base = arm.part('base', box(40, 40, 16));
const link = arm.part('arm', box(10, 40, 6).translate(0, 20, 0));

base.connector('pivot', { type: 'axis', origin: { kind: 'vec3', value: [5, 20, 16] }, axis: [0, 1, 0] });
link.connector('pivot', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 1, 0] });

arm.mate('elbow', 'base.pivot', 'arm.pivot', 'revolute', { limitsDeg: [0, 90] });

return arm.solvedModel({ elbow: 45 });

Await sectionSketch/faceSketch/silhouette before chaining extrude

recipe · You called shape.sectionSketch(...), .faceSketch(...), or .silhouette(...) and chained a Sketch method (.extrude, .revolve, .sweep, .loft, .reflect) directly on the result. All three producers are async and return Promise<Sketch> — chaining without awaiting throws feature.async-result.missing-await instead of silently doing the wrong thing.

sketch · extrude · sectionSketch extrude is not a function · faceSketch is not a function · silhouette is not a function · await sectionSketch · promise sketch extrude · missing await

Recipe source

Recipe code
// WRONG — sectionSketch() returns Promise<Sketch>; .extrude() does not exist
// on a Promise. This throws feature.async-result.missing-await, naming the
// exact fix, rather than a cryptic "extrude is not a function" TypeError:
//
//   const plate = box(60, 40, 10)
//     .hole('top', { u: -15, v: 0, diameter: 10, depth: 'through' })
//     .hole('top', { u: 15, v: 0, diameter: 8, depth: 'through' });
//   return plate.sectionSketch({ plane: 'xy', offset: 5 }).extrude(3);

// RIGHT — await the Promise<Sketch>, then chain.
const plate = box(60, 40, 10)
  .hole('top', { u: -15, v: 0, diameter: 10, depth: 'through' })
  .hole('top', { u: 15, v: 0, diameter: 8, depth: 'through' });

const section = await plate.sectionSketch({ plane: 'xy', offset: 5 });
return section.extrude(3);

Real car body from measured station sections (ported open hatchback)

recipe · You need a car / van / berlinetta / fairing body that reads as a REAL vehicle, not a child-like extruded side outline with a box roof. Copy this ported hatchback (station tables measured from an open CC-BY model) and re-param it; for a different car, measure its stations from an open reference instead of free-handing. Degree-3 nurbsSegment sections lofted in short runs, greenhouse as a second loft, glass/lamps as thin separate skins, wheels in arches.

shell · sketch · continuity · sweep · assembly · reference-image · automotive body envelope · organic car body · realistic car body · sedan hatchback body · car from open source model · station sections from reference mesh · berlinetta body · sports car body shell · rail loft body · spline loft sections · nurbsSegment loft sections · surfaceFromCurves car body · fairing body envelope · car body with glass separate · wheel arches wheels in arches · body likeness gate · network body panels via sew

Recipe source

Recipe code
// Compact hatchback ported from OSRF gazebo_models `hatchback` (CC-BY 3.0).
// Full provenance + extraction script: examples/from-reference/hatchback/.
// Check: render side --pose 180,0, front --pose -90,0, 3/4 --pose -130,22
// (nose is at x = 0, the car runs along +x), then verify body-likeness.

// --- Editable parameters (defaults = source model) ---------------------------
const length = param('length', 4001, { min: 3600, max: 4600, description: 'overall length, bumper to bumper (mm)' });
const width = param('width', 1880, { min: 1650, max: 2000, description: 'body width without mirrors (mm)' });
const height = param('height', 1568, { min: 1350, max: 1750, description: 'overall height, ground to roof (mm)' });
const wheelbase = param('wheelbase', 2516, { min: 2300, max: 2900, description: 'front to rear axle (mm)' });
const rideHeight = param('rideHeight', 196, { min: 120, max: 260, description: 'sill height above ground (mm)' });

// Source dimensions the station tables were measured in.
const SRC_L = 4001;
const SRC_W = 1880;
const SRC_H = 1568;
const SRC_WB = 2516;
const SRC_RIDE = 196;
const SRC_FRONT_AXLE = 804;
const SRC_OVERHANGS = SRC_L - SRC_WB;
const WHEEL_R = 320;
const HALF_TRACK = 787;

type Num = Editable<number>;

// Source s (mm from the front bumper) -> model x. Overhangs follow
// (length - wheelbase), the span between the axles follows wheelbase.
function X(s: number): ParamRef<number> {
  const overhang = length.subtract(wheelbase);
  if (s <= SRC_FRONT_AXLE) return overhang.multiply(s / SRC_OVERHANGS);
  const frontAxle = overhang.multiply(SRC_FRONT_AXLE / SRC_OVERHANGS);
  if (s <= SRC_FRONT_AXLE + SRC_WB) return frontAxle.add(wheelbase.multiply((s - SRC_FRONT_AXLE) / SRC_WB));
  return frontAxle.add(wheelbase).add(overhang.multiply((s - SRC_FRONT_AXLE - SRC_WB) / SRC_OVERHANGS));
}
const Y = (w: number): ParamRef<number> => width.multiply(w / SRC_W);
const Z = (z: number): ParamRef<number> => rideHeight.add(height.subtract(rideHeight).multiply((z - SRC_RIDE) / (SRC_H - SRC_RIDE)));

// --- Station tables measured from the source mesh (mm) -----------------------
// Lower body: [s, zSill, zMaxWidth, wMax, zShoulder, wShoulder, zTop]
const LOWER: number[][] = [
  [0, 248, 500, 396, 554, 344, 615], // measured at s = 40; cap placed on the bumper face
  [150, 209, 440, 763, 739, 436, 809],
  [350, 200, 450, 904, 846, 724, 916],
  [600, 196, 540, 940, 942, 766, 1012],
  [800, 197, 560, 940, 1013, 770, 1083],
  [1000, 198, 560, 940, 1063, 785, 1088],
  [1300, 201, 560, 940, 1067, 796, 1092],
  [1900, 217, 550, 940, 1076, 807, 1101],
  [2600, 235, 550, 940, 1085, 813, 1110],
  [3150, 256, 560, 940, 1093, 810, 1118],
  [3450, 274, 560, 940, 1097, 799, 1122],
  [3650, 287, 570, 921, 1099, 784, 1124],
  [3780, 311, 600, 861, 1088, 640, 1113],
  [3880, 338, 616, 773, 812, 549, 864],
  [4001, 422, 578, 503, 598, 293, 655], // measured at s = 3960; cap on the bumper face
];
// Greenhouse: [s, zBelt, wBelt, wMidGlass, wRoofRail, zRoof]
const GREENHOUSE: number[][] = [
  [860, 1050, 600, 300, 295, 1109],
  [960, 1063, 730, 448, 307, 1152],
  [1150, 1065, 783, 697, 373, 1235],
  [1450, 1069, 800, 700, 425, 1366],
  [1900, 1076, 805, 675, 454, 1513],
  [2500, 1084, 812, 678, 454, 1566],
  [3050, 1091, 809, 684, 453, 1554],
  [3350, 1095, 805, 681, 408, 1535],
  [3550, 1098, 784, 684, 282, 1415],
  [3700, 1100, 766, 669, 304, 1283],
  [3790, 1078, 595, 381, 246, 1166],
];

// --- Sections and loft -------------------------------------------------------
// Closed, left/right-symmetric degree-3 NURBS section from right-half control
// points listed bottom -> roof centre. A flat floor line closes it.
function section(half: [number, number][]): Sketch {
  const pts: [Num, Num][] = half.map(([w, z]) => [Y(w), Z(z)]);
  for (let i = half.length - 2; i >= 0; i--) pts.push([Y(-half[i][0]), Z(half[i][1])]);
  const [w0, z0] = half[0];
  return path().moveTo(Y(-w0), Z(z0)).lineTo(Y(w0), Z(z0)).nurbsSegment(pts, { degree: 3 }).close();
}
function lowerSection(r: number[], grow = 0): Sketch {
  const [, zSill, zMax, wMax, zShoulder, wShoulder, zTop] = r;
  return section([
    [wMax - 70 + grow, zSill - grow],
    [wMax + grow, zSill + 25],
    [wMax + grow, zMax],
    [wMax - 12 + grow, (zMax + zShoulder) / 2],
    [wShoulder + grow, zShoulder],
    [wShoulder * 0.72 + grow, zTop + grow],
    [0, zTop + grow],
  ]);
}
function greenhouseSection(r: number[], grow = 0): Sketch {
  const [, zBelt, wBelt, wMid, wRail, zRoof] = r;
  return section([
    [wBelt + grow, zBelt - 60],
    [wBelt + grow, zBelt],
    [wMid + grow, zBelt + 0.55 * (zRoof - zBelt)],
    [wRail + 60 + grow, zRoof + grow],
    [0, zRoof + grow],
  ]);
}
// Loft through the rows in short runs that share their end sections, then
// fuse. Same surface as one long loft, but it meshes in seconds.
function loftStations(rows: number[][], make: (r: number[]) => Sketch, spans = 3): Shape {
  const pieces: Shape[] = [];
  for (let i = 0; i < rows.length - 1; i += spans) {
    const run = rows.slice(i, Math.min(i + spans + 1, rows.length));
    const sketches = run.map(make);
    pieces.push(sketches[0].loft(sketches.slice(1), {
      planes: run.map((r) => ({ plane: 'YZ' as const, origin: [X(r[0]), 0, 0] as [Num, Num, Num] })),
    }));
  }
  return pieces.length === 1 ? pieces[0] : pieces[0].union(...pieces.slice(1));
}
// Axis-aligned mask box in source coordinates (s along x, |w| across, z up).
function mask(s0: number, s1: number, halfW: number, z0: number, z1: number): Shape {
  const footprint: [Num, Num][] = [[X(s0), Y(-halfW)], [X(s1), Y(-halfW)], [X(s1), Y(halfW)], [X(s0), Y(halfW)]];
  return extrudePolygon(footprint, height.multiply((z1 - z0) / (SRC_H - SRC_RIDE))).translate(0, 0, Z(z0));
}

// --- Body --------------------------------------------------------------------
const PAINT = '#b3202a';
const AXLES = [SRC_FRONT_AXLE, SRC_FRONT_AXLE + SRC_WB];

// Wheel wells: a revolved tub (rounded inner lip) per corner, cut only
// through the outer flank, never a through-Y cut across the whole loft.
const ARCH_R = WHEEL_R + 45;
const archCut = (s: number, side: 1 | -1): Shape =>
  path().moveTo(0, 0).lineTo(ARCH_R, 0).lineTo(ARCH_R, 360).threePointsArc(ARCH_R - 60, 420, ARCH_R - 18, 402)
    .lineTo(0, 420).close()
    .revolve()
    .alongAxis([0, side, 0])
    .translate(X(s), Y(side * (HALF_TRACK - 200)), WHEEL_R);

// Lower intake: a dark rounded plate set 8 mm into the flat front cap.
const intakePlate = (depth: number): Shape =>
  extrudeRoundedRect(Z(480).subtract(Z(330)), Y(620), 60, depth).rotateY(90).translate(X(0), 0, Z(405));
const cabin = loftStations(GREENHOUSE, (r) => greenhouseSection(r));
const body = loftStations(LOWER, (r) => lowerSection(r))
  .subtract(...AXLES.flatMap((s) => [archCut(s, 1), archCut(s, -1)]))
  .union(cabin)
  .subtract(intakePlate(8))
  .color(PAINT);

// --- Glass: 4 mm skin (grown loft minus the cabin loft), masked to the windows -
const windowMask = mask(870, 3770, 1200, 1105, 1700).subtract(
  mask(1470, 3400, 520, 1105, 1700), // roof panel
  mask(2030, 2130, 1200, 1105, 1700), // B-pillar
  mask(3170, 3330, 1200, 1105, 1700), // C-pillar
  mask(870, 1130, 1200, 1105, 1700).subtract(mask(870, 1130, 640, 1105, 1700)), // A-pillar feet
);
const glass = loftStations(GREENHOUSE, (r) => greenhouseSection(r, 4))
  .subtract(cabin)
  .intersect(windowMask)
  .color('#16202a');

// --- Lamps and intake: 6 mm skins on the nose / tail, masked to each lamp ----
const skin = (rows: number[][]): Shape =>
  loftStations(rows, (r) => lowerSection(r, 6)).subtract(loftStations(rows, (r) => lowerSection(r)));
const noseSkin = skin(LOWER.slice(0, 4));
// Slices start on a run boundary of the body loft (rows 0 and 9), so the skin
// follows exactly the same surface as the body underneath it.
const tailSkin = skin(LOWER.slice(9)).subtract(cabin);
const outboard = (s0: number, s1: number, inner: number, z0: number, z1: number): Shape =>
  mask(s0, s1, 1100, z0, z1).subtract(mask(s0 - 10, s1 + 10, inner, z0 - 10, z1 + 10));
const headlamps = noseSkin.intersect(outboard(60, 430, 420, 640, 820)).color('#e8eef4');
const intake = intakePlate(8).color('#202226');
const taillamps = tailSkin.intersect(outboard(3700, 3970, 560, 760, 1110)).color('#c4141c');

// --- Mirrors and wheels (separate parts) -------------------------------------
const mirror = (side: 1 | -1): Shape =>
  sphere(1).scale([130, 100, 70]).translate(X(1520), Y(side * 960), Z(1030))
    .union(box(90, 120, 40, true).translate(X(1540), Y(side * 880), Z(1030)))
    .color(PAINT);
// Tyre Ø640 x 200 wide: revolved section with a rounded tread.
const tyre = (s: number, side: 1 | -1): Shape =>
  path().moveTo(205, -100).lineTo(285, -100).threePointsArc(285, 100, WHEEL_R, 0).lineTo(205, 100).close()
    .revolve()
    .rotateX(90)
    .translate(X(s), Y(side * HALF_TRACK), WHEEL_R)
    .color('#1b1b1d');
const rim = (s: number, side: 1 | -1): Shape =>
  cylinder(170, 200).translate(0, 0, -85)
    .union(cylinder(184, 60).translate(0, 0, -92))
    .rotateX(90)
    .translate(X(s), Y(side * HALF_TRACK), WHEEL_R)
    .color('#c9ccd1');

const car = assembly('compact hatchback (port of OSRF gazebo_models hatchback, CC-BY 3.0)');
car.part('body', body);
car.part('glass', glass);
car.part('headlamps', headlamps);
car.part('intake', intake);
car.part('taillamps', taillamps);
car.part('mirror-left', mirror(1));
car.part('mirror-right', mirror(-1));
const corners: [string, number, 1 | -1][] = [
  ['front-left', AXLES[0], 1], ['front-right', AXLES[0], -1],
  ['rear-left', AXLES[1], 1], ['rear-right', AXLES[1], -1],
];
for (const [name, s, side] of corners) {
  car.part(`tyre-${name}`, tyre(s, side));
  car.part(`rim-${name}`, rim(s, side));
}
return car.model();

Blind pocket cut into one face only

recipe · You want a pocket cut into the top face only — the cylinder is shorter than the plate so it does not reach the bottom face.

subtract · pocket · plate · primitive · pocket that does not go through · blind hole from the top · partial-depth cut

Recipe source

Recipe code
const t = 12;
const pocketDepth = 6;
const plate = box(40, 40, t);
const pocket = cylinder(pocketDepth + 1, 6).translate(20, 20, t - pocketDepth);
return plate.subtract(pocket);

Assign material and quantity so a bill of materials can be extracted

recipe · Before calling inspect({ of 'bom' }) or export({ format 'bom-csv' | 'bom-json' }), declare material/density on assembly.part(...) so mass rows are real numbers instead of bom.material.unassigned gaps; instances placed in a loop with distinct names group into one BOM row by real quantity.

bom · material · density · fasteners · catalog · bill of materials · bom extraction · part quantity from pattern · purchased vs fabricated parts · material density mass

Recipe source

Recipe code
const arm = assembly('bracket-set');

// A named engineering material seeds BOTH density (for BOM mass) and a
// default finish. Purchased parts (lib.fetchPart) carry their own catalog
// identity automatically — no material needed there.
for (let i = 0; i < 3; i++) {
  arm.part(`bracket_${i}`, box(20, 10, 4), {
    at: [i * 25, 0, 0],
    material: 'aluminum',
  });
}

return arm.model();

Chained 3-joint revolute arm with collision-free knuckles

recipe · You are building a serial robot arm (shoulder, elbow, wrist) as an assembly. Each link carries a tongue + pin at its proximal pivot and a clevis at its distal pivot (disjoint axial ranges, see revolute-joint-knuckle), so every joint is collision-free at any angle; the joint limits and link lengths keep non-adjacent links clear of each other and of the base at every combination of limits.

assembly · connector · mate · revolute · joint · robot arm with three revolute joints · chained arm links shoulder elbow wrist · serial arm passes mechanism gates at every limit · clevis tongue pin knuckle per link

Recipe source

Recipe code
// Every pivot axis is Y. Axial ranges along Y at each joint:
//   child tongue y in [-4.5, 4.5]; parent cheeks y in [±5, ±9].
const R = 6, pinR = 2, boreR = 2.2;
const yCyl = (r, y0, y1, x = 0, z = 0) =>
  cylinder(y1 - y0, r).rotateX(-90).translate(x, y0, z);

// Clevis whose pivot sits at the origin, body behind it along -X.
const clevis = () => union(
  union(
    yCyl(R, 5, 9), box(14, 4, 2 * R).translate(-14, 5, -R),
    yCyl(R, -9, -5), box(14, 4, 2 * R).translate(-14, -9, -R),
  ).subtract(yCyl(boreR, -10, 10)),
  box(6, 18, 2 * R).translate(-14, -9, -R),       // bridge, x in [-14, -8]
);

// Link of length L: tongue + pin at the origin, clevis at (L, 0, 0).
const link = (L) => union(
  yCyl(R, -4.5, 4.5),
  yCyl(pinR, -9.5, 9.5),
  box(L - 14, 9, 8).translate(0, -4.5, -4),
  clevis().translate(L, 0, 0),
);

const H = 80;                        // shoulder pivot height
const L1 = 70, L2 = 50, L3 = 40;
const base = union(
  box(80, 60, 10).translate(-40, -30, 0),                  // plate
  box(12, 18, H - 24).translate(-6, -9, 10),               // column
  clevis().rotateY(-90).translate(0, 0, H),                // cheeks point up
);

const arm = assembly('chained-revolute-arm');
const parts = {
  base: arm.part('base', base),
  upper: arm.part('upper', link(L1)),
  fore: arm.part('fore', link(L2)),
  hand: arm.part('hand', link(L3)),
};
const axis = (part, name, x, z) =>
  part.connector(name, { type: 'axis', origin: { kind: 'vec3', value: [x, 0, z] }, axis: [0, 1, 0] });
axis(parts.base, 'shoulder', 0, H);
axis(parts.upper, 'proximal', 0, 0);
axis(parts.upper, 'distal', L1, 0);
axis(parts.fore, 'proximal', 0, 0);
axis(parts.fore, 'distal', L2, 0);
axis(parts.hand, 'proximal', 0, 0);

// Limits keep links out of the bridges behind each pivot (|angle| <= 100)
// and keep the folded hand above the base plate and clear of the column.
arm.mate('shoulder', 'base.shoulder', 'upper.proximal', 'revolute', { limitsDeg: [-100, 0] });
arm.mate('elbow', 'upper.distal', 'fore.proximal', 'revolute', { limitsDeg: [-90, 90] });
arm.mate('wrist', 'fore.distal', 'hand.proximal', 'revolute', { limitsDeg: [-90, 90] });

return arm.solvedModel({ shoulder: -45, elbow: 45, wrist: 30 });

Chamfer a canonical face after the part is rotated

recipe · You rotated a primitive and now want to chamfer one of its canonical faces by name (face-name semantics survive transforms).

chamfer · rotate · face-ref · edge-features · bevel the top edge after rotate · chamfer after rotation · face name survives rotate

Recipe source

Recipe code
return box(40, 30, 20).rotate([1, 0, 0], 30).chamfer(1.5, { face: 'top' });

Clamshell enclosure release pack — clevis spine, lid swing, materials

recipe · Prompt asks for a clamshell enclosure release pack: two walled shells on a clevis spine, a lidDeg swing with animationView, named materials, and a drawing/BOM/USD checklist. Declare lidDeg with param() in the same block as animationView — tracks must name that live param. The lid is modeled open, side by side with the base; limits stop it before the shells fold onto each other. A gasket-sealed close is not modeled. No latch mate, no joint.scissorLift, no closed-loop FK. After evaluate_script reports mechanism=real, export BOM and a PDF or SVG drawing, and usd-isaac with an explicit drive on the hinge only. Report FEA UNVERIFIED unless CalculiX and gmsh are present. Prefer design_loop until green.

assembly · connector · mate · revolute · hinge · joint · parameter · material · bom · manufacturing · datum · clamshell enclosure release pack · instrument case clevis spine shells · lidDeg animationView beside param · gasket seal not modeled · BOM drawing USD explicit hinge drive · mechanism real open shells · no latch mate no closed-loop FK

Recipe source

Recipe code
// Clamshell enclosure release pack. KEEP param() beside animationView —
// tracks must name lidDeg (animation.param.unknown if you drop it).
// Clevis cheeks and the lid tongue occupy disjoint ranges along Y, with a
// clearance pin, so the revolute does not collide inside limitsDeg.
// The shells are modeled side by side (lidDeg 0 = open flat). Limits stop
// the lid BEFORE it folds onto the base. A gasket-sealed close is NOT
// modeled — those shells would interpenetrate. No latch mate, no
// joint.scissorLift, no closed-loop FK.
//
// Follow-up AFTER evaluate_script ok + mechanism=real (do not fake results):
// 1. inspect({ of: 'bom' }) or export bom-csv|bom-json. Materials are set.
// 2. pdf-drawing / svg-drawing (exploded isometric, balloons, parts list,
//    title block). Datums below are annotation intent, not a certification.
// 3. usd-isaac with an explicit drive on 'hinge' only. This graph is
//    revolute-only — do not add cylindrical / pin_slot / ball.
// 4. FEA: fea_summary({}). If CalculiX+gmsh are absent, report UNVERIFIED.
// 5. open_in_studio and capture_animation({ file }) with pose verification.

const lidDeg = param('lidDeg', 35, { min: -65, max: 65 });
animationView({
  name: 'lid swing',
  tracks: [
    { param: 'lidDeg', keys: [{ atMs: 0, value: -40 }, { atMs: 1200, value: 50, ease: 'easeInOut' }, { atMs: 2400, value: -40, ease: 'easeInOut' }] },
  ],
  fps: 24,
});

const R = 9;
const pinR = 2.4;
const boreR = 2.75;
const yCyl = (r, y0, y1, x = 0, z = 0) =>
  cylinder(y1 - y0, r).rotateX(-90).translate(x, y0, z);

// Fork knuckles. The tongue sits in the open center so the pin stays a
// clearance fit instead of a volume overlap.
const cheeks = union(
  yCyl(R, 6.4, 13.2),
  yCyl(R, -13.2, -6.4),
).subtract(yCyl(boreR, -14, 14));
const web = (y: number) => cylinder(15.2, 3.4).alongAxis([1, 0, 0]).translate(-22, y, 0);
const bridge = web(9.6).union(web(-9.6));

let baseShell = extrudeRoundedRect(78, 72, 12, 28).translate(-59, 0, -18);
baseShell = baseShell.subtract(extrudeRoundedRect(62, 56, 8, 24).translate(-57, 0, -14));
baseShell = baseShell.subtract(extrudeRoundedRect(40, 32, 5, 1.3).translate(-59, 0, -18.1));
baseShell = baseShell.subtract(cylinder(12, 2.1).translate(-60, 0, -20));
// Outer lip plus a lower land: mating face and wall thickness both read from above.
const baseLip = extrudeRoundedRect(78, 72, 12, 3.4).translate(-59, 0, 9.7)
  .subtract(extrudeRoundedRect(68, 62, 9, 4.2).translate(-59, 0, 9.3));
const baseLand = extrudeRoundedRect(68, 62, 9, 1.8).translate(-59, 0, 8.15)
  .subtract(extrudeRoundedRect(58, 52, 6, 2.6).translate(-59, 0, 7.8));
// Floor rib so the cavity is not an empty tub. Metal hardware is
// separate parts below — a finish on this union would paint screws the
// same ABS as the shell.
const ribs = extrudeRoundedRect(32, 2.6, 1, 5).translate(-62, 8, -14)
  .union(extrudeRoundedRect(2.6, 20, 1, 5).translate(-70, 0, -14));
function screwBoss(x: number, y: number) {
  return cylinder(9, 4).translate(x, y, -14)
    .subtract(cylinder(6, 1.45).translate(x, y, -8));
}
let bosses = screwBoss(-78, -18);
for (const [x, y] of [[-42, -18], [-78, 18], [-42, 18]] as const) {
  bosses = bosses.union(screwBoss(x, y));
}

const baseBody = union(cheeks, bridge, baseShell, baseLip, baseLand, ribs, bosses)
  .datum('A', { atZ: -18 })
  .datum('B', { atX: -98 })
  .datum('C', { atY: -36 })
  .tolerance({
    type: 'position', value: 0.3, modifier: '⌀', datums: ['A', 'B', 'C'],
    edge: { ofCurveType: 'CIRCLE', near: [-60, 0, -18] },
  })
  .finish('abs', { color: '#5a6e7c' });

// Tongue is bored. The steel pin is its own part and must not share volume.
const tongue = yCyl(R - 0.4, -4.8, 4.8).subtract(yCyl(2.95, -6.2, 6.2));
const neck = cylinder(18, 4.6).alongAxis([1, 0, 0]).translate(3.2, 0, 0);
let lidShell = extrudeRoundedRect(64, 68, 11, 20).translate(50, 0, -8);
lidShell = lidShell.subtract(extrudeRoundedRect(50, 54, 7, 16).translate(52, 0, -6));
lidShell = lidShell.subtract(extrudeRoundedRect(36, 28, 4, 2.2).translate(50, 0, 9.0));
const screwXY = [[26, 24], [74, 24], [26, -24], [74, -24]] as const;
// Lid screw clearances as real hole features, counterbored from the top face.
// The lid is centred on (50, 0), so u = x - 50 and v = y.
lidShell = lidShell.holes({ byNormal: 'Z', atX: 50, atY: 0, atZ: 12 }, {
  positions: screwXY.map(([x, y]) => ({ u: x - 50, v: y })),
  diameter: 3.9,
  depth: 8,
  counterbore: { diameter: 7.8, depth: 1.8 },
});
const lidLip = extrudeRoundedRect(64, 68, 11, 2.8).translate(50, 0, 11.5)
  .subtract(extrudeRoundedRect(54, 58, 8, 3.6).translate(50, 0, 11.1));
const lidBody = union(tongue, neck, lidShell, lidLip).finish('abs', { color: '#f3f6f8' });

// Steel pin with socket heads past the fork. Washers are separate rings.
let pinShape = yCyl(2.3, -18.4, 18.4)
  .union(yCyl(4.5, 14.55, 18.4))
  .union(yCyl(4.5, -18.4, -14.55))
  .subtract(yCyl(1.65, 16.3, 18.8))
  .subtract(yCyl(1.65, -18.8, -16.3))
  .finish('stainless');
function washerRing(y0: number) {
  return yCyl(5.8, y0, y0 + 0.7).subtract(yCyl(2.55, y0 - 0.3, y0 + 1.0)).finish('steel');
}
function lidScrew(x: number, y: number) {
  return cylinder(2.7, 3.45).translate(x, y, 10.55)
    .union(cylinder(5.2, 1.5).translate(x, y, 6.2))
    .subtract(cylinder(1.6, 1.4).translate(x, y, 12.2))
    .finish('steel');
}
// Pads sit under the floor and break the front edge so the hero elevation
// still sees the standoffs.
function footPad(x: number, y: number) {
  return cylinder(7.6, 6.2).translate(x, y, -25.7);
}
// Four separate pads, one part each (like the lid screws): fused into one
// "feet" body they were four solids 45-74 mm apart (union.disconnected).
const FOOT_XY = [[-90, -32], [-32, -32], [-90, 32], [-32, 32]] as const;
// Seal land on the mating rim. Not a compressed close — the shells still
// stop before they meet.
const gasketShape = extrudeRoundedRect(64, 58, 8, 1.45).translate(-59, 0, 10.1)
  .subtract(extrudeRoundedRect(54, 48, 5, 2.2).translate(-59, 0, 9.7))
  .finish('abs', { color: '#c6a15a' });
// Static latch hardware. Not a latch mate and not animated: the hook rides
// the lid, the strike stays on the base, and the open swing never catches.
const latchShape = cylinder(11, 4.2).alongAxis([1, 0, 0]).translate(82.2, 0, 1)
  .union(cylinder(7, 2.6).translate(91.5, 0, -6))
  .union(cylinder(5, 2.6).alongAxis([1, 0, 0]).translate(88.5, 0, -6))
  .finish('steel');
const strikeShape = cylinder(20, 3.4).translate(-101.8, -26, -4)
  .union(cylinder(6, 2.2).alongAxis([1, 0, 0]).translate(-107.2, -26, 12))
  .finish('steel');

const arm = assembly('clamshell-enclosure-release');
const base = arm.part('base-shell', baseBody, { material: 'abs' });
const lid = arm.part('lid-shell', lidBody, { material: 'abs' });
const pin = arm.part('hinge-pin', pinShape, { material: 'mild-steel' });
const washerA = arm.part('washer-1', washerRing(13.45), { material: 'mild-steel' });
const washerB = arm.part('washer-2', washerRing(-14.15), { material: 'mild-steel' });
const screws = screwXY.map(([x, y], i) => arm.part(`lid-screw-${i + 1}`, lidScrew(x, y), { material: 'mild-steel' }));
const feet = FOOT_XY.map(([x, y], i) =>
  arm.part(`foot-${i + 1}`, footPad(x, y).finish('rubber'), { material: 'abs' }));
const gasket = arm.part('gasket', gasketShape, { material: 'abs' });
const latch = arm.part('latch-hook', latchShape, { material: 'mild-steel' });
const strike = arm.part('strike', strikeShape, { material: 'mild-steel' });

// The revolute child is the steel pin, not the plastic lid. The exposure
// gate measures stick-out on the two revolute bodies; a pin fused into the
// lid was the only way the old script passed, and that painted the pin white.
base.connector('hinge', {
  type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 1, 0],
});
pin.connector('hinge', {
  type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 1, 0],
});
arm.mate('hinge', 'base-shell.hinge', 'hinge-pin.hinge', 'revolute', {
  pose: lidDeg,
  limitsDeg: [-65, 65],
});

function frameAt(part, name, at, clearance) {
  part.connector(name, {
    type: 'frame',
    origin: { kind: 'vec3', value: at },
    ...(clearance ? { jointClearanceRadius: clearance } : {}),
  });
}
frameAt(pin, 'lid', [0, 0, 0], 0);
frameAt(lid, 'pin', [0, 0, 0], 3.1);
arm.mate('lid-spine', 'hinge-pin.lid', 'lid-shell.pin', 'fastened');
frameAt(pin, 'washer-a', [0, 13.8, 0], 2.6);
frameAt(washerA, 'bore', [0, 13.8, 0], 2.6);
arm.mate('washer-1', 'hinge-pin.washer-a', 'washer-1.bore', 'fastened');
frameAt(pin, 'washer-b', [0, -13.8, 0], 2.6);
frameAt(washerB, 'bore', [0, -13.8, 0], 2.6);
arm.mate('washer-2', 'hinge-pin.washer-b', 'washer-2.bore', 'fastened');
screwXY.forEach(([x, y], i) => {
  const at = [x, y, 11.2] as [number, number, number];
  frameAt(lid, `screw-${i + 1}`, at, 2.1);
  frameAt(screws[i], 'head', at, 0);
  arm.mate(`lid-screw-${i + 1}`, `lid-shell.screw-${i + 1}`, `lid-screw-${i + 1}.head`, 'fastened');
});
FOOT_XY.forEach(([x, y], i) => {
  frameAt(base, `foot-${i + 1}`, [x, y, -19], 2);
  frameAt(feet[i], 'pad', [x, y, -19], 2);
  arm.mate(`foot-${i + 1}`, `base-shell.foot-${i + 1}`, `foot-${i + 1}.pad`, 'fastened');
});
frameAt(base, 'gasket', [-59, -26.5, 10.2], 1.2);
frameAt(gasket, 'land', [-59, -26.5, 10.2], 1.2);
arm.mate('gasket', 'base-shell.gasket', 'gasket.land', 'fastened');
frameAt(lid, 'latch', [86, 0, 1], 2);
frameAt(latch, 'root', [86, 0, 1], 2);
arm.mate('latch', 'lid-shell.latch', 'latch-hook.root', 'fastened');
frameAt(base, 'strike', [-101.8, -26, 6], 2);
frameAt(strike, 'root', [-101.8, -26, 6], 2);
arm.mate('strike', 'base-shell.strike', 'strike.root', 'fastened');

return arm.solvedModel({});

Clamshell hinge between a base and a lid, joined by a revolute mate

recipe · You are modeling a laptop-lid-style clamshell hinge, or any assembly where one rigid body swings about a fixed pivot line on another rigid body. Declare a matching axis connector on both bodies along the physical hinge line, then join them with a revolute mate and limitsDeg to bound the swing angle. Relevant for hinge, revolute joint, or assembly with connectors and mates.

assembly · connector · mate · revolute · hinge · joint · hinge · revolute joint · clamshell hinge · lid pivots on base · assembly with connectors and mates · declare a connector · hingeAxis connector

Recipe source

Recipe code
const arm = assembly('clamshell-hinge');

const base = arm.part('base', box(300, 200, 12).translate(0, 0, 6));
const lid = arm.part('lid', box(300, 200, 6).translate(0, 100, 3));

base.connector('hingeAxis', { type: 'axis', origin: { kind: 'vec3', value: [0, -100, 12] }, axis: [1, 0, 0] });
lid.connector('hingeAxis', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [1, 0, 0] });

arm.mate('hinge', 'base.hingeAxis', 'lid.hingeAxis', 'revolute', { limitsDeg: [-15, 135] });

return arm.solvedModel({ hinge: 90 });

Through-hole sized for a bolt with clearance

recipe · You need a through-hole sized for a bolt with a small clearance margin. Use `hole(face, { diameter, depth: 'through' })` (or `holes` for a pattern), not a subtracted cylinder; for a tapped hole see `threaded-hole-tap-drill`.

subtract · hole · bolt · plate · parameter · clearance fit hole for a bolt · through-hole for M5 bolt · bolt diameter plus 0.5mm clearance · ISO 273 close normal clearance hole M3 M4 M5 M6

Recipe source

Recipe code
const t = 8;
const boltDiam = 5;
// ISO 273 clearance for M5: close 5.3, normal 5.5, coarse 5.8. Bolt + 0.5 = normal.
// hole u/v are offsets from the centre of the top face; 'through' cuts the full plate.
const plate = box(40, 40, t);
return plate.hole('top', { u: 0, v: 0, diameter: boltDiam + 0.5, depth: 'through', name: 'boltClear' });

Countersunk hole seating a flat-head screw flush

recipe · You need countersunk holes whose cone matches a flat-head screw so the head sits flush: hole or holes with countersink { diameter, angleDeg } cut a real cone widest at the entry face, and a revolved 90° head of the same diameter fills it with no gap on the cone and no overlap (M3–M6 ISO 10642 table).

fastener · revolve · assembly · countersink cone 90 degrees for ISO 10642 flat head · flat head screw seated flush with the face · countersink diameter equals head diameter · holes with countersink option

Recipe source

Recipe code
// ISO 10642 countersunk head, 90°: nominal d, head diameter dk; ISO 273 medium
// clearance bore dh.
const ISO10642 = {
  M3: { d: 3, dk: 6.72, dh: 3.4 },
  M4: { d: 4, dk: 8.96, dh: 4.5 },
  M5: { d: 5, dk: 11.2, dh: 5.5 },
  M6: { d: 6, dk: 13.44, dh: 6.6 },
};
const spec = ISO10642.M4;
const thickness = 6;
const length = 12;
const spacing = 30;

// Two countersunk holes in a 50 × 20 bar. The countersink diameter is the head
// diameter, so the cone rim matches the head rim at the face.
const bar = box(50, 20, thickness).translate(-25, -10, 0).holes({ atZ: thickness, byNormal: 'Z' }, {
  positions: [{ u: -spacing / 2, v: 0 }, { u: spacing / 2, v: 0 }],
  diameter: spec.dh,
  depth: 'through',
  countersink: { diameter: spec.dk, angleDeg: 90 },
});

// Flat-head screw as a revolve profile (radius, height): shank of Ø d, then
// the 90° head cone from Ø d up to Ø dk at the top face (z = 0 of the screw).
const headHeight = (spec.dk - spec.d) / 2; // 90° included angle → rise = radial step
function flatHeadScrew() {
  return path()
    .moveTo(0, -length)
    .lineTo(spec.d / 2, -length)
    .lineTo(spec.d / 2, -headHeight)
    .lineTo(spec.dk / 2, 0)
    .lineTo(0, 0)
    .close()
    .revolve();
}

const arm = assembly('countersunk-flat-head-screw');
arm.part('bar', bar, { material: 'aluminum-6061' });
arm.part('screw-a', flatHeadScrew(), { material: 'mild-steel', at: [-spacing / 2, 0, thickness] });
arm.part('screw-b', flatHeadScrew(), { material: 'mild-steel', at: [spacing / 2, 0, thickness] });
return arm.model();

Find out what material changed between revisions of a model

recipe · You edited a script and need to know what physically changed, not just that something did. A signed volume delta is ambiguous — a boss that grew and a pocket that deepened report the same magnitude, and a body that only moved reports zero. Call diff_geometry({ baseFile, params }) to re-lower the SAME script with different param() values, or diff_geometry({ baseFile, file }) to compare separate scripts. Per matched body you get addedMm3 = volume(revised-base), removedMm3 = volume(base-revised), commonMm3, exact bbox/face/edge/hole-count deltas, a surface deviation, and a verdict — identical | moved | resized | topology-changed. Branch on the verdict; quote the numbers. Author the model with named params and named features (as below) so both the override form and the report read cleanly. Reach for this when diff_scripts sets deeperDiffAvailable, instead of re-rendering and eyeballing.

diff · compare · revision · what changed between versions · added removed volume · diff_geometry · material level diff · did the edit add or remove material · topology changed verdict · resized verdict · parameter sweep diff · compare scripts geometrically · surface deviation between revisions

Recipe source

Recipe code
const plateT = param('plateT', 6, { min: 3, max: 20 });
const boltDia = param('boltDia', 5, { min: 3, max: 10 });
const addCablePort = param('addCablePort', true);

// Face-relative features resolve against the face as it exists at that
// moment, so cut the port BEFORE the holes: otherwise changing boltDia
// nudges the port and the diff reports motion that is not a design change.
return box(80, 50, plateT)
  .cutout(
    path().moveTo(-9, -6).lineTo(9, -6).lineTo(9, 6).lineTo(-9, 6).close(),
    { face: 'top', depth: 'through', name: 'cablePort', enabled: addCablePort },
  )
  .holes('top', {
    positions: [{ u: -30, v: -18 }, { u: 30, v: -18 }],
    diameter: boltDia,
    depth: 'through',
    name: 'mountBolts',
  });

Declare dimension callouts on the 3D viewer

recipe · You want the viewer to show a named measurement on the model, for example the spacing between two holes or a hole diameter, by declaring it with shape.dimension() in the script. Labels are names, the viewer adds the value; open the shared link with ?dims=1 to force the callouts on.

dimension · callout · hole-spacing · annotation · show hole spacing in the viewer · label a distance on the model · declared dimension callout · measurement overlay

Recipe source

Recipe code
// A mounting plate with two holes; the viewer shows "hole spacing 30".
// A circular edge anchor measures to the hole centre, and the label is a
// name: the viewer appends the measured value itself.
const holeAt = (x: number, y: number) => ({
  edge: { ofCurveType: 'CIRCLE' as const, near: [x, y, 6] as [number, number, number] },
});
const plate = box(60, 40, 6)
  .holes('top', {
    positions: [{ u: -15, v: 0 }, { u: 15, v: 0 }],
    diameter: 5,
    depth: 'through',
  })
  .dimension({ kind: 'linear', from: holeAt(15, 20), to: holeAt(45, 20), label: 'hole spacing' })
  .dimension({ kind: 'diameter', edge: holeAt(15, 20).edge });
return plate;

Automatic dimensions, datums and position tolerances on an svg-drawing sheet

recipe · You need a quotable engineering drawing without writing every callout. Export svg-drawing with autoAnnotate to derive datums, grouped bore callouts with position frames, positions from the datums, overall size, radii, chamfers, flatness and an ISO 2768 note from the geometry, and declare the datums or tolerances that are design intent with shape.datum and shape.tolerance.

drawing · dimension · gdt · tolerance · datum · auto dimension a drawing with GD&T · autoAnnotate automatic dimensioning · datum reference frame A B C · ISO 2768 general tolerance note · declare a datum and a position tolerance in the script · oblique section view

Recipe source

Recipe code
// Export:
//   kernelcad export svg-drawing mount.kcad.ts -o mount.svg --options '{"autoAnnotate":true}'
//   MCP: export({ target: 'model', file, format: 'svg-drawing', output_path,
//                 options: { format: 'svg-drawing', autoAnnotate: true } })
// Sheet: datums A (bottom, declared) / B / C, "4× ⌀6.5 THRU" over "⌖ ⌀0.05 A B C"
// (the declared frame replaces the ISO 2768-m default ⌀0.1), hole positions
// 10 / 70 and 10 / 40 from the datums, overall 80 / 50 / 10, "4× R5",
// flatness "⏥ 0.2" on A, and "ISO 2768-mK" beside the title block.
// drawing_report (MCP) / drawingReport (CLI --json) gives placed / overlapped counts.
// Bolt holes as real hole features: u/v are mm from the top face centre (40, 25).
const mount = box(80, 50, 10)
  .fillet(5, { parallel: [0, 0, 1] })
  .holes('top', {
    positions: [{ u: -30, v: -15 }, { u: 30, v: -15 }, { u: -30, v: 15 }, { u: 30, v: 15 }],
    diameter: 6.5,
    depth: 'through',
  })
  .datum('A', { atZ: 0 })
  .tolerance({
    type: 'position', value: 0.05, modifier: '⌀', datums: ['A', 'B', 'C'],
    edge: { ofCurveType: 'CIRCLE', near: [10, 10, 10] },
  });
return mount;

Standard hole/fillet/chamfer callouts and a position tolerance on a svg-drawing sheet

recipe · You exported a svg-drawing sheet and need standard fabrication callouts — ⌀ hole diameters with THRU/blind/counterbore/countersink, a pattern count, a fillet radius, a chamfer size, a datum letter, and a feature-control-frame tolerance — instead of only the automatic bounding-box dimensions.

hole · plate · bolt · drawing · dimension · gdt · tolerance · hole callout THRU blind counterbore countersink · feature control frame position tolerance datum · dimension a hole on an engineering drawing · GD&T annotation svg-drawing

Recipe source

Recipe code
// Model: a plate with a counterbored hole and three plain through-holes.
// hole()/holes() rather than subtracted cylinders: only a real hole feature
// carries the diameter, depth and counterbore that the drawing callouts,
// `inspect` and the DFM gates read back.
const w = 80, h = 50, t = 10;
// u/v are measured from the CENTRE of the face, so world (10, 10) is
// (10 - w/2, 10 - h/2) = (-30, -15) on the top face.
const plate = box(w, h, t)
  .holes('top', {
    positions: [{ u: 30, v: -15 }, { u: -30, v: 15 }, { u: 30, v: 15 }],
    diameter: 6.5,
    depth: 'through',
  })
  .hole('top', {
    u: -30,
    v: -15,
    diameter: 6.5,
    depth: 'through',
    counterbore: { diameter: 12, depth: 4 },
  });

// export({ target: 'model', format: 'svg-drawing', options: { annotations } })
// annotations:
// [
//   { kind: 'hole', view: 'top', edge: { ofCurveType: 'CIRCLE', near: [10, 10, -2] },
//     through: true, counterbore: { diameter: 12, depth: 4 } },
//     // -> "⌀6.5 THRU ⌴⌀12 ▾ 4"
//   { kind: 'hole', view: 'top', edge: { ofCurveType: 'CIRCLE', near: [70, 10, -2] },
//     through: true, count: 4 },
//     // -> "4× ⌀6.5 THRU"
//   { kind: 'fillet', view: 'top', edge: { ofCurveType: 'CIRCLE', near: [65, 25, 18] } },
//     // -> "R<radius>"
//   { kind: 'chamfer', view: 'top', edge: { ofCurveType: 'LINE', near: [0, 0, 10] }, size: 2 },
//     // -> "2 × 45°" (leg size is author-supplied; not recoverable from a bare edge)
//   { kind: 'datum', view: 'top', face: { atZ: 0 }, label: 'A' },
//     // -> square datum-feature symbol + leader
//   { kind: 'fcf', view: 'top', edge: { ofCurveType: 'CIRCLE', near: [70, 40, -2] },
//     type: 'position', value: 0.1, datums: ['A'], modifier: '⌀' },
//     // -> feature control frame: [⌖][⌀0.1][A]
// ]
return plate;

Printable engineering drawing as a PDF on a standard sheet, in one export call

recipe · You need a drawing to send to a shop or publish — a vector PDF on an ISO A0–A4 or ANSI A–E sheet with orthographic views, hidden lines, the automatic dimensions, hole callouts and GD&T, and a title block carrying title, part name, material, scale, units, projection, sheet size, date and revision. Export format pdf-drawing; the sheet content is the svg-drawing sheet.

drawing · dimension · gdt · tolerance · datum · export a drawing as PDF · printable engineering drawing for a machine shop · A3 A4 ANSI sheet title block · first angle third angle projection · title block material revision part number · pdf-drawing

Recipe source

Recipe code
// Export:
//   kernelcad export pdf-drawing bearing-plate.kcad.ts -o bearing-plate.pdf \
//     --title "Bearing plate" --material "S235JR" --revision A --sheet a3
//   MCP: export({ target: 'model', file, format: 'pdf-drawing', output_path: 'bearing-plate.pdf',
//                 options: { format: 'pdf-drawing', title: 'Bearing plate', partName: 'BP-100',
//                            material: 'S235JR', revision: 'A', projection: 'first' } })
// Sheet: A3 landscape (default; sheet: 'auto' picks the smallest ISO sheet that holds the
// views at 1:1), scale snapped to the standard series and printed in the title block.
// Views front / top / left + isometric; the bore and bolt holes show as dashed hidden lines.
// autoAnnotate is on by default: datums A/B/C, "4× Ø9 THRU" and "Ø30 THRU" each with a
// "⌖ Ø0.1 A B C" frame, hole positions, overall 100 / 70 / 12, "4× R6", ISO 2768-mK.
// Unset title-block fields print "—". drawing_report (MCP) / drawingReport (CLI --json)
// gives placed / overlapped counts.
const w = 100, h = 70, t = 12;
// Bore and bolt holes as real hole features: u/v are mm from the top face centre.
const plate = box(w, h, t)
  .fillet(6, { parallel: [0, 0, 1] })
  .hole('top', { u: 0, v: 0, diameter: 30, depth: 'through', name: 'bore' })
  .holes('top', {
    positions: [{ u: -38, v: -23 }, { u: 38, v: -23 }, { u: -38, v: 23 }, { u: 38, v: 23 }],
    diameter: 9,
    depth: 'through',
  });
return plate.datum('A', { atZ: 0 });

Enclosure base and screw-on lid with corner bosses and clearance holes

recipe · Prompt asks for an electronics enclosure with a removable screw-on lid/cover (not a hinged clamshell). Build a walled base with corner bosses + lid with matching clearance holes. Prefer this over `clamshell-hinge-two-part-assembly` when the user wants fasteners, not a revolute hinge.

shell · hole · pocket · fillet · plate · parameter · manufacturing · assembly · subtract · enclosure lid · screw-on cover · electronics box lid · corner bosses with taps · clamshell without hinge fasteners · production enclosure cover plate · M3 tapped screw bosses and lid clearance holes with holes() · Arduino Raspberry Pi ESP32 project box case with screws

Recipe source

Recipe code
const L = param('length', 100, { min: 50, max: 200 });
const W = param('width', 70, { min: 40, max: 140 });
const H = param('height', 35, { min: 20, max: 80 });
const wall = param('wall', 2.5, { min: 1.5, max: 5 });
const lidT = param('lidThickness', 2.5, { min: 1.5, max: 5 });

const arm = assembly('enclosure');

let base = box(L, W, H);
base = base.subtract(
  box(L.subtract(wall.multiply(2)), W.subtract(wall.multiply(2)), H.subtract(wall))
    .translate(wall, wall, wall),
);

// Corner bosses inside the cavity, tapped M3 through the floor; matching M3
// clearance holes (ISO 273 normal, 3.4 mm) in the lid.
const bossR = 5;
const inset = wall.add(8);
const corners = [
  [inset, inset],
  [L.subtract(inset), inset],
  [inset, W.subtract(inset)],
  [L.subtract(inset), W.subtract(inset)],
] as const;
for (const [x, y] of corners) {
  base = base.union(cylinder(H.subtract(wall), bossR).translate(x, y, wall));
}
// Entry face: the outside of the floor. After the cavity cut 'bottom' is
// ambiguous (the inner floor also came from it), so pick it by normal + z.
// hole u/v are offsets from the centre of that face (u = +X, v = +Y).
// thread: diameter is the nominal M3; the kernel drills the ISO minor diameter
// (2.46 mm tap drill) up through floor and boss. Not a subtracted cylinder.
const fromCentre = corners.map(([x, y]) => ({ u: x.subtract(L.divide(2)), v: y.subtract(W.divide(2)) }));
base = base.holes({ byNormal: '-Z', atZ: 0 }, {
  positions: fromCentre,
  diameter: 3,
  depth: H, // full height: 'through' would stop where the floor opens into the cavity
  thread: { pitch: 0.5 },
  name: 'bossTap',
});
base = base.fillet(1, { parallel: [0, 0, 1] });
arm.part('base', base, { material: 'abs' });

const lid = box(L, W, lidT).holes('top', {
  positions: fromCentre,
  diameter: 3.4,
  depth: 'through',
  name: 'lidClear',
});
arm.part('lid', lid.translate(0, 0, H), { material: 'abs' });

return arm.model();

Named engineering material presets driving mass

recipe · You want engineering material presets by grade name (mild-steel, aluminum-6061, pla, petg, abs, nylon) to drive the mass, default finish and recorded material of identical geometry. One registry serves arm.part({ material }), inspect({ of: 'mass', material }), feaStudy({ material }) and .finish(), so the grade name works everywhere; steel / aluminum / pet are aliases for the same grades.

material · mass · density · assembly · mild-steel density driving mass · aluminum-6061 mass properties and finish · nylon pla petg abs material grades · same material name for mass FEA and finish · named engineering material presets

Recipe source

Recipe code
// One vocabulary: each grade seeds the part's density (mass / inertia), its
// default finish, and the material name the part records; feaStudy({ material })
// takes the same spelling. Bulk aliases (steel, aluminum, pet) resolve to these
// grades, and an unknown name fails listing the accepted ones.
const arm = assembly('engineering-material-presets-mass');
arm.part('mild-steel-cube', box(20, 20, 20), { material: 'mild-steel', at: [0, 0, 0] });
arm.part('aluminum-6061-cube', box(20, 20, 20), { material: 'aluminum-6061', at: [40, 0, 0] });
arm.part('nylon-cube', box(20, 20, 20), { material: 'nylon', at: [80, 0, 0] });
return arm.model();

Exploded isometric with BOM balloons and a bill table

recipe · You need an exploded isometric of a multi-body assembly — bodies pulled apart along mate axes or radially from the centroid — with BOM-numbered balloons and a bill table on a svg-drawing sheet, or the same explode on render_preview.

exploded-view · balloons · drawing · bom · exploded view · mate-axis explode · radial explode · item balloons · bill table · svg-drawing exploded

Recipe source

Recipe code
const arm = assembly('enclosure');
arm.part('plate', box(80, 60, 3), { material: 'aluminum' });
for (let i = 0; i < 4; i++) {
  const x = i % 2 === 0 ? 8 : 72;
  const y = i < 2 ? 8 : 52;
  arm.part('standoff_' + i, cylinder(3, 12), { at: [x, y, 3], material: 'pla' });
}
arm.part('board', box(68, 48, 1.6), { at: [6, 6, 15], material: 'pla' });
arm.part('lid', box(80, 60, 2), { at: [0, 0, 16.6], material: 'abs' });
return arm.model();

DXF for laser, waterjet and CNC — flat blanks, panels, cut lists and sections

recipe · You need a 2D cut file — a flat blank with holes and slots, a panel, an extruded profile, or a whole cut list of flat panels — or a section of any 3D body at one height (plans, floor plans, profiles). Export format dxf finds the blank's own plane in any orientation and writes the outline and holes with exact arcs and circles.

export · manufacturing · export a flat blank to DXF for laser or waterjet cutting · CNC cut list of plywood panels with one DXF file per panel · true arcs and circles in the DXF, not faceted polylines · cross-section DXF of a 3D body at a height for a plan or floor plan · export.dxf.non-planar refusal for a body that is not flat

Recipe source

Recipe code
// A 3 mm blank: two holes and a slot. Any orientation works — the DXF is
// drawn in the blank's own plane with its true sizes.
const t = param('thickness', 3, { min: 1, max: 20 });

// Holes as real hole features: u/v are mm from the top face centre (50, 30).
const blank = box(100, 60, t).holes('top', {
  positions: [{ u: -30, v: 0 }, { u: 30, v: 0 }],
  diameter: 8,
  depth: 'through',
});
const slot = box(20, 6, t).translate(40, 12, 0)
  .union(cylinder(t, 3).translate(40, 15, 0))
  .union(cylinder(t, 3).translate(60, 15, 0));
return blank.subtract(slot);

// Then, outside the script:
//   export target:'model' format:'dxf' output_path:'blank.dxf'
//   -> one closed LWPOLYLINE per loop on layer 'cut': the 100 x 60
//      outline from (0, 0), each hole as an exact circle, the slot with
//      exact half-circle ends ($INSUNITS 4 = mm; options.unit 'cm' | 'in').
//
// A cut list: return an assembly of flat panels.
//   -> output_path gets them side by side, one layer each, and
//      parts/<name>.dxf holds one file each (reported in part_files).
//      options: { format: 'dxf', layout: 'sheet' } writes the sheet only.
//
// A 3D body (pocketed block, housing, building): export a section.
//   options: { format: 'dxf', section: { axis: 'z', at: 25 } }
//   -> the cross-section at z = 25 in world X/Y.
//
// A body that is neither flat nor sectioned fails with
// export.dxf.non-planar; its hint names section, flatten_pattern (sheet
// metal) and STEP/STL.

recipe · The consumer is a GPU physics / robot-learning stack that imports a UsdPhysics stage directly instead of parsing URDF or SDF. Author parts with a named material (it seeds both mass and appearance), axis connectors, and fastened / revolute / prismatic mates exactly as for urdf, then call export({ target: 'model', format: 'usd-isaac', output_path: 'robot.usda' }). You get an articulation root, one rigid body per link at its solved pose with mass and principal inertia, one PhysicsRevoluteJoint / PhysicsPrismaticJoint / PhysicsFixedJoint per mate with limits, UsdPreviewSurface materials, and a meshes/<link>.usda layer per link. Declare actuator gains with options.drives { <mate>: { stiffness, damping } } — none are invented. Planar, cylindrical, pin_slot and ball mates fail closed with export.usd.joint-unsupported.

assembly · mate · revolute · export · export to usd · usd-isaac format · isaac sim · isaac lab · articulation root · PhysicsRevoluteJoint · physics simulation stage · robot description for isaac

Recipe source

Recipe code
const arm = assembly('two-link-usd-arm');

const base = arm.part('base', box(40, 40, 20, true).translate(0, 0, 10), { material: 'steel' });
const link1 = arm.part('link1', box(20, 20, 100, true).translate(0, 0, 50), { material: 'aluminum' });

base.connector('shoulder', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 20] }, axis: [0, 0, 1] });
link1.connector('shoulder', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 0, 1] });

arm.mate('shoulder', 'base.shoulder', 'link1.shoulder', 'revolute', { limitsDeg: [-90, 90] });

return arm.model();

Plate with rounded corners via extrudeRoundedRect

recipe · You want a flat plate with rounded corners; use the dedicated rounded-rect extrude rather than building corners by hand.

extrude · plate · primitive · rounded-corner plate · rectangular plate with corner radius · rounded rectangle extruded

Recipe source

Recipe code
return extrudeRoundedRect(60, 40, 5, 8);

FDM print fit clearance by fit type (press/snap/slip/running) checked by dfmSpec

recipe · Two printed parts must fit together (lid lip in a box, pin in a hole, slider in a slot, snap-fit catch). Pick the per-side clearance from the fit type as a param, size the female feature with it (hole diameter + 2c, lip width - 2c), and declare `dfmSpec({ process: 'fdm', printer, minWall, minClearance })` so every evaluate measures the real gap and the bed fit. Do not hard-code an unexplained 0.2.

tolerance · printer · manufacturing · choice-param · hole · assembly · print-orientation · print clearance for a snap fit lid press fit slip fit running fit · FDM tolerance per side 0.1 0.2 0.3 mm for 3D printed parts · Bambu A1 P1S Prusa printer clearance gap between printed parts · pin in a bore, lid lip in an opening, sliding fit gap · dfmSpec process fdm printer minWall minClearance check

Recipe source

Recipe code
// Per-side clearance for a 0.4 mm nozzle, well-tuned FDM printer (PLA/PETG).
// Print a test coupon and tune; coarse printers want +0.05-0.1.
//   fit       per side   use
//   press     0.05       bearing/magnet/pin pressed in, no movement
//   snap      0.15       snap-fit lid lip, clip catch
//   slip      0.20       removable lid, slide-on cover
//   running   0.30       rotating pin, hinge axle, slider
const FIT: Record<string, number> = { press: 0.05, snap: 0.15, slip: 0.2, running: 0.3 };
const fit = param('Fit', 'running', { choices: ['press', 'snap', 'slip', 'running'] });
const c = FIT[fit.value];
const pinD = 8;

// Female part: the bore is the pin diameter + 2 x clearance (a lid lip would be
// the opening - 2 x clearance instead).
const bracket = box(30, 20, 12).translate(-15, -10, 0)
  .hole('top', { u: 0, v: 0, diameter: pinD + 2 * c, depth: 'through', name: 'bore' });
// Male part: nominal size, longer than the bore so it only meets the bore wall.
const pin = cylinder(20, pinD / 2).translate(0, 0, -4);

// The gate measures the real gap between the parts (exact BREP distance) and
// runs the FDM printability check against the named printer's bed.
dfmSpec({
  process: 'fdm',
  printer: 'bambu-a1',
  minWall: 1.2,
  minClearance: c * 0.9,
});

const asm = assembly('fdm-fit');
asm.part('bracket', bracket, { material: 'pla' });
asm.part('pin', pin, { material: 'pla' });
return asm.model();

Check FDM printability and choose a support-free print orientation

recipe · You are about to slice or send a model to an FDM printer and want to know first whether it prints cleanly — no unsupported overhangs, no over-long bridges, walls at least 2 x the nozzle, no pins or holes too small for the nozzle, enough bed contact, fits the printer bed. Declare dfmSpec({ process: 'fdm' }) and evaluate; the dfm.fdm.* diagnostics name the overhang area and locus, and the hint gives the axis-aligned rotation that minimises unsupported area ("rotate -90° about Y ... drops from 938.2 to 0.0 mm²"). Apply it by declaring that buildDirection, which the gcode export also slices in. Full report per orientation via verify({ check: 'dfm' }) or kernelcad dfm.

manufacturing · overhang · print-orientation · overhang steeper than 45 degrees needs support material · bridge span longer than maxBridgeMm sags · wall thinner than 2 x nozzle diameter · which way up should this print · rotate so the print needs no supports · dfmSpec process fdm buildDirection nozzleMm maxOverhangDeg · bed adhesion contact area and tipping risk · design for additive manufacturing before slicing

Recipe source

Recipe code
// Wall hook, modeled the way it hangs. As modeled (+Z up) the arm underside
// is a 40 mm cantilever and dfmSpec({ process: 'fdm' }) fails with
// dfm.fdm.overhang-unsupported. The hint recommended buildDirection '+x'
// (lie it on its side), declared below, so evaluate passes and
// export format:'gcode' slices it in that orientation.
const width = param('width', 24, { min: 10, max: 60 });
const reach = param('reach', 40, { min: 20, max: 80 });

const screwHole = (z: number) => cylinder(5, 2.25).rotateX(-90).translate(width.divide(2), 0, z);
const backPlate = box(width, 5, 70).subtract(screwHole(12)).subtract(screwHole(58));
const arm = box(width, reach, 6).translate(0, 5, 35);
const lip = box(width, 4, 12).translate(0, reach.add(1), 41);

dfmSpec({
  process: 'fdm',
  buildDirection: '+x',  // part-local axis pointing away from the bed
  nozzleMm: 0.4,         // walls < 0.8 mm, holes < 2 mm, pins < 3 mm are flagged
  maxOverhangDeg: 45,    // degrees from vertical; 90 = printing with supports
  maxBridgeMm: 10,
  printer: 'generic-fdm',
});

return backPlate.union(arm).union(lip).fillet(4, { parallel: [1, 0, 0], concave: true });

Check a design against a load with FEA and gate on the safety factor

recipe · The design has to carry a real load and you need evidence, not a guess: peak von Mises stress, peak displacement, and a safety factor against the material's yield. Declare the study on the shape with shape.feaStudy({ material, fixed, loads, meshSize?, minSafetyFactor? }) — `fixed` and `loads[].faces` take the same FaceQuery / @kc[...] selectors as the rest of the API, and `force` is the TOTAL newtons on those faces. Declaring minSafetyFactor makes it an enforcement gate: evaluate_script fails with fea.safety-factor.below-min and names the governing region. Then run run_fea for the full summary, per-region hot spots and heatmap PNGs. Needs the external CalculiX + gmsh toolchain; check with fea_summary({}). Use this instead of verify({ check: 'load-capacity' }) when the geometry is not a plain cantilever beam or when you need to know WHERE it is overloaded.

fea · stress · load · will this hold the load · stress analysis · FEA · finite element · von Mises · safety factor · yield strength · deflection · how strong is this design · is this wall thickness enough · structural check

Recipe source

Recipe code
const wall = param('wall', 8, { min: 3, max: 20 });

// L-bracket: wall leg bolts to the wall, shelf leg carries the load.
const bracket = box(wall, 40, 90)
  .union(box(90, 40, wall))
  .fillet(4, { parallel: [0, 1, 0], concave: true });

bracket.feaStudy({
  name: 'shelf-load',
  material: 'aluminum-6061',              // or mild-steel / pla / petg / abs / nylon
  fixed: { atX: 0 },                      // held rigid: the wall face
  loads: [{ faces: { atZ: wall }, force: [0, 0, -400] }],  // 400 N TOTAL, downward
  meshSize: 4,
  // minSafetyFactor: 2,  // uncomment to make evaluate SOLVE and FAIL below SF 2
});

// Report-only as written: evaluate stays fast and needs no solver. Get the
// evidence with the run_fea MCP tool:
//   run_fea({ file: 'bracket.kcad.ts', output_dir: '/tmp/bracket-fea' })
//   -> run_fea refines the mesh itself: summary.refinement.passes
//      4 mm 42.5 MPa, 3 mm 44.3 MPa (stoppedBy 'still-untrusted'),
//      so summary.minSafetyFactor ~6.1, maxVonMisesMPa ~44.3 (the finer pass),
//      hotSpots[0].region = the root fillet face, plus heatmap PNGs.
// Read summary.trust first: meshTrusted false means the STRESS number is
// still mesh-limited (here it still reads ~31 %); keep a margin or continue
// with run_fea({ mesh_size: 2 }).
return bracket;

Fillet only the top face after subtract

recipe · After subtracting a hole or pocket, you want to round only the rim of the resulting opening — not every edge in the part.

fillet · subtract · face-ref · edge-features · round the rim of a hole · fillet the top edge after cutting · chamfer the lip of a pocket

Recipe source

Recipe code
const plate = box(50, 50, 8);
const hole = cylinder(10, 6).translate(25, 25, -1);
return plate.subtract(hole).fillet(1.5, { face: 'top' });

Fillet a face by name on a translated primitive

recipe · You translated a primitive and now want to fillet one of its canonical faces by name (canonical face refs survive translate).

fillet · translate · face-ref · edge-features · round the top after moving the part · fillet the top face after translate · face name survives translate

Recipe source

Recipe code
return box(40, 30, 10).translate(5, 7, 0).fillet(2, { face: 'top' });

Box semi-trailer with roof solar-battery packs placed on a keep-out and axle-balance rule

recipe · Prompt asks for a freight box / dry-van semi-trailer with solar-battery modules, or solar-panel-plus-battery cabinets, mounted on the roof and an optimal or balanced location for them. Pack the maximum grid that fits inside the front-fairing, rear-door, and side-walkway keep-outs, then center that grid on the kingpin/bogie static-reaction station. The rule is closed-form packing plus a reaction split — not an FEA solve and not a mass-properties query. Doors are modeled closed and fastened; they are not a swinging mechanism.

assembly · connector · mate · parameter · material · extrude · sketch · fillet · plate · manufacturing · choice-param · typed-params · density · freight trailer roof solar batteries · box semi-trailer solar battery placement · optimal roof module layout keep-out · kingpin bogie load share centroid · corrugated dry van landing gear bogie · solar panel battery cabinet roof mount · trailer roof walkway fairing margin

Recipe source

Recipe code
// Freight box semi-trailer + roof solar-battery cabinets.
//
// Optimal placement (closed form, re-run with the choice params):
// 1. Keep-outs: front fairing, rear door-header strip, side walkways.
// 2. rowCount/columnCount default to the maximum grid that fits those
//    keep-outs at the default gaps. Fewer rows or columns are still centered.
// 3. Every cabinet is the same solid, so the grid centroid is the mass centroid.
//    targetX = share * kingpinX + (1 - share) * bogieCenterX
//    is the station that puts `share` of the pack weight on the kingpin.
// 4. Center the grid on targetX and clamp it inside the keep-out window.
// FEA is not solved. Mass properties are not queried. Doors are closed
// and fastened — no revolute, so this is not a swinging-door mechanism.
// animationView scrubs gapAlong / gapAcross; it does not add or remove cabinets.

const LENGTH = 13600;
const WIDTH = 2550;
const HALF_W = 1275;
const KINGPIN_X = 1200;
const AXLE_SPACE = 1320;
const REAR_AXLE_X = 10900;
const FRONT_AXLE_X = REAR_AXLE_X - AXLE_SPACE;
const BOGIE_CENTER_X = (FRONT_AXLE_X + REAR_AXLE_X) / 2;
const FRONT_KEEPOUT = 800;
const REAR_KEEPOUT = 450;
const MODULE_L = 1500;
const MODULE_W = 700;
const GAP_ALONG_0 = 100;
const GAP_ACROSS_0 = 60;
const SIDE_MARGIN_0 = 120;
const SKIN = 14;
const RIB_DEPTH = 32;
const INNER_Y = HALF_W - SKIN - RIB_DEPTH;

const FLOOR_Z = 1180;
const FLOOR_T = 36;
const FLOOR_TOP = FLOOR_Z + FLOOR_T;
const WALL_H = 2460;
const ROOF_Z = FLOOR_TOP + WALL_H;
const ROOF_T = 28;
const ROOF_TOP = ROOF_Z + ROOF_T;
const AXLE_Z = 550;
const RAIL_Z = 820;
const RAIL_H = 300;

function maxFit(usable, size, gap) {
  if (usable < size) return 1;
  return Math.floor((usable + gap) / (size + gap));
}

const optRows = maxFit(LENGTH - FRONT_KEEPOUT - REAR_KEEPOUT, MODULE_L, GAP_ALONG_0);
const optCols = maxFit(WIDTH - 2 * SIDE_MARGIN_0, MODULE_W, GAP_ACROSS_0);

const rowCount = param('rowCount', String(optRows), {
  choices: ['4', '5', '6', '7'],
  description: 'Cabinets along the trailer. Default is the maximum that fits the keep-outs at the default gap.',
});
const columnCount = param('columnCount', String(optCols), {
  choices: ['1', '2', '3'],
  description: 'Lateral columns. Default is the maximum that fits between the walkway margins.',
});
const kingpinLoadShare = param('kingpinLoadShare', '0.35', {
  choices: ['0.30', '0.35', '0.40'],
  description: 'Fraction of pack weight assigned to the kingpin. The rest goes to the bogie center.',
});
const gapAlong = param('gapAlong', GAP_ALONG_0, {
  min: 60, max: 240, unit: 'mm',
  description: 'Gap between cabinets along the trailer. The grid stays centered on the balance station.',
});
const gapAcross = param('gapAcross', GAP_ACROSS_0, {
  min: 40, max: 90, unit: 'mm',
  description: 'Gap between lateral columns.',
});
const sideMargin = param('sideMargin', SIDE_MARGIN_0, {
  min: 80, max: 140, unit: 'mm',
  description: 'Side walkway width. Column count uses the default; the strip itself follows this slider.',
});

const rowN = Number(rowCount.value);
const colN = Number(columnCount.value);
const share = Number(kingpinLoadShare.value);
const targetX = Math.round(share * KINGPIN_X + (1 - share) * BOGIE_CENTER_X);
const packLen0 = MODULE_L * rowN + GAP_ALONG_0 * Math.max(rowN - 1, 0);
let startX0 = targetX - packLen0 / 2;
let clampNote = 'none';
const minStart = FRONT_KEEPOUT;
const maxStart = LENGTH - REAR_KEEPOUT - packLen0;
if (startX0 < minStart) { startX0 = minStart; clampNote = 'front-keepout'; }
if (startX0 > maxStart) { startX0 = maxStart; clampNote = 'rear-keepout'; }
const centroidX = Math.round(startX0 + packLen0 / 2);

const layoutNote = param('layoutNote',
  'rows=' + rowN + ' cols=' + colN + ' targetX=' + targetX + ' startX=' + Math.round(startX0) + ' centroidX=' + centroidX + ' clamp=' + clampNote + ' share=' + share,
  {
    maxLength: 140,
    description: 'Diagnostic of this run. Not a geometry driver. FEA and mass-property queries are not used.',
  },
);

animationView({
  name: 'roof pack spacing',
  tracks: [
    { param: 'gapAlong', keys: [
      { atMs: 0, value: 80 },
      { atMs: 1600, value: 220, ease: 'easeInOut' },
      { atMs: 2800, value: 80, ease: 'easeInOut' },
    ] },
    { param: 'gapAcross', keys: [
      { atMs: 0, value: 40 },
      { atMs: 1600, value: 90, ease: 'easeInOut' },
      { atMs: 2800, value: 40, ease: 'easeInOut' },
    ] },
  ],
  fps: 12,
});

const packLen = gapAlong.multiply(Math.max(rowN - 1, 0)).add(MODULE_L * rowN);
const packWid = gapAcross.multiply(Math.max(colN - 1, 0)).add(MODULE_W * colN);
const startX = gapAlong.multiply(0).add(centroidX).subtract(packLen.divide(2));
const startY = gapAcross.multiply(0).subtract(packWid.divide(2));

function fuse(parts) {
  let acc = parts[0];
  for (let i = 1; i < parts.length; i++) acc = acc.union(parts[i]);
  return acc;
}

function frameConnector(part, name) {
  part.connector(name, {
    type: 'frame',
    origin: { kind: 'vec3', value: [0, 0, 0] },
  });
}

function alongY(length, radius) {
  return cylinder(length, radius).rotateX(90);
}

function iBeam(length, x0, yCenter) {
  const flangeW = 130;
  const flangeT = 18;
  const webT = 16;
  const y0 = yCenter - flangeW / 2;
  const bottom = box(length, flangeW, flangeT).translate(x0, y0, RAIL_Z);
  const top = box(length, flangeW, flangeT).translate(x0, y0, RAIL_Z + RAIL_H - flangeT);
  const web = box(length, webT, RAIL_H - flangeT).translate(x0, yCenter - webT / 2, RAIL_Z + flangeT / 2);
  return fuse([bottom, top, web]);
}

function corrugatedWall() {
  const valley = 150;
  const ribW = 70;
  const pitch = valley + ribW;
  const nRibs = Math.floor((LENGTH - 30) / pitch);
  let profile = path().moveTo(0, 0).lineTo(0, SKIN);
  let x = 0;
  for (let i = 0; i < nRibs; i++) {
    profile = profile
      .lineTo(x + valley, SKIN)
      .lineTo(x + valley, SKIN + RIB_DEPTH)
      .lineTo(x + pitch, SKIN + RIB_DEPTH)
      .lineTo(x + pitch, SKIN);
    x += pitch;
  }
  profile = profile.lineTo(LENGTH, SKIN).lineTo(LENGTH, 0).lineTo(0, 0).close();
  return profile.extrude(WALL_H);
}

const white = (shape) => shape.finish('paint-gloss', { color: '#f3f5f7' });

const chassisParts = [
  iBeam(LENGTH - 200, 100, -500),
  iBeam(LENGTH - 200, 100, 500),
];
for (let x = 500; x <= LENGTH - 700; x += 1450) {
  chassisParts.push(box(70, 1160, 95.5).translate(x, -580, RAIL_Z + RAIL_H - 36));
}
chassisParts.push(box(700, 1400, 28).translate(850, -700, RAIL_Z - 8));
chassisParts.push(cylinder(90, 28).translate(KINGPIN_X, 0, RAIL_Z - 80));
chassisParts.push(box(160, 2300, 80).translate(LENGTH - 280, -1150, 400));
chassisParts.push(box(70, 140, 460).translate(LENGTH - 240, -570, 400));
chassisParts.push(box(70, 140, 460).translate(LENGTH - 240, 430, 400));
chassisParts.push(box(80, 180, 740).translate(3200, 500, 400));
chassisParts.push(box(6400, 70, 70).translate(2800, 620, 440));
chassisParts.push(box(80, 180, 740).translate(3200, -680, 400));
chassisParts.push(box(6400, 70, 70).translate(2800, -690, 440));

const landingParts = [];
function legPost(y) {
  const outer = box(110, 110, 791.5).translate(2460, y, 28);
  const inner = box(86, 86, 800).translate(2472, y + 12, 20);
  const foot = box(240, 200, 36).translate(2395, y - 45, 0);
  return fuse([outer.subtract(inner), foot]);
}
landingParts.push(legPost(-470));
landingParts.push(legPost(360));
landingParts.push(alongY(900, 22).translate(2515, 450, 420));
landingParts.push(box(160, 140, 180).translate(2440, 400, 520));
landingParts.push(cylinder(180, 14).rotateY(90).translate(2440, 470, 600));

const bogieParts = [
  box(2800, 140, 120).translate(8800, -570, 699.5),
  box(2800, 140, 120).translate(8800, 430, 699.5),
];
for (const axleX of [FRONT_AXLE_X, REAR_AXLE_X]) {
  bogieParts.push(alongY(1533, 42).translate(axleX, 766.5, AXLE_Z));
  bogieParts.push(box(90, 150, 280).translate(axleX - 45, -560, 500));
  bogieParts.push(box(90, 150, 280).translate(axleX - 45, 410, 500));
}

function dualWheels(axleX) {
  const major = 450;
  const minor = 100;
  const innerY = 802;
  const outerY = 1030;
  const pieces = [];
  for (const y of [innerY, outerY]) {
    pieces.push(torus(major, minor).rotateX(90).translate(axleX, y, AXLE_Z).finish('rubber'));
    pieces.push(alongY(70, 370).translate(axleX, y + 35, AXLE_Z).finish('anodized'));
  }
  pieces.push(alongY(200, 80).translate(axleX, 1010, AXLE_Z).finish('steel'));
  return fuse(pieces).finish('rubber');
}

function batteryModule() {
  const foot = (x, y) => box(72, 72, 34).translate(x, y, 0).finish('aluminium');
  const bolt = (x, y) => cylinder(10, 8).translate(x, y, 26).finish('steel');
  const pack = box(MODULE_L - 96, MODULE_W - 96, 128)
    .fillet(8)
    .finish('anodized-black')
    .translate(48, 48, 24);
  const pvFrame = box(MODULE_L, MODULE_W, 30).translate(0, 0, 146).finish('aluminium');
  // Photovoltaic laminate. Leaf finishes name the feet, pack, frame, and
  // bolts; the fused cabinet then takes one matte navy appearance. An
  // assembly part renders as a single material, and without this root
  // finish the cabinet inherits the aluminium feet and disappears into
  // the white roof in a studio shot.
  const pv = box(MODULE_L - 36, MODULE_W - 36, 12)
    .translate(18, 18, 166)
    .finish('paint-matte', { color: '#0c2340' });
  return fuse([
    foot(8, 8),
    foot(MODULE_L - 80, 8),
    foot(8, MODULE_W - 80),
    foot(MODULE_L - 80, MODULE_W - 80),
    bolt(44, 44),
    bolt(MODULE_L - 44, 44),
    bolt(44, MODULE_W - 44),
    bolt(MODULE_L - 44, MODULE_W - 44),
    pack,
    pvFrame,
    pv,
  ]).finish('paint-matte', { color: '#0c2340' });
}

const fairingW = WIDTH - 160;
const fairing = path()
  .moveTo(0, 220)
  .lineTo(0, 0)
  .lineTo(760, 220)
  .close()
  .extrude(fairingW)
  .rotateX(-90)
  .translate(0, -fairingW / 2, ROOF_TOP + 218)
  .finish('paint-matte', { color: '#1c2430' });

const bodyPieces = [
  white(box(LENGTH, WIDTH, FLOOR_T).translate(0, -HALF_W, FLOOR_Z)),
  white(corrugatedWall().translate(0, INNER_Y, FLOOR_TOP)),
  white(corrugatedWall().reflect('xz').translate(0, -INNER_Y, FLOOR_TOP)),
  white(box(LENGTH, WIDTH, ROOF_T).translate(0, -HALF_W, ROOF_Z)),
  white(box(42, INNER_Y * 2, WALL_H).translate(0, -INNER_Y, FLOOR_TOP)),
  white(box(50, 140, 2304).translate(LENGTH - 50, -INNER_Y, FLOOR_TOP)),
  white(box(50, 140, 2304).translate(LENGTH - 50, INNER_Y - 140, FLOOR_TOP)),
  white(box(82, INNER_Y * 2, 156).translate(LENGTH - 82, -INNER_Y, ROOF_Z - 156)),
  white(box(82, 2178, 88).translate(LENGTH - 82, -1089, FLOOR_TOP)),
  white(box(36, 1040, 2216).translate(LENGTH - 86, -1080, FLOOR_TOP + 88)),
  white(box(36, 1040, 2216).translate(LENGTH - 86, 40, FLOOR_TOP + 88)),
  fairing,
  box(LENGTH - 1700, sideMargin, 4).translate(860, -HALF_W, ROOF_TOP - 1).finish('paint-matte', { color: '#2c3328' }),
  box(LENGTH - 1700, sideMargin, 4).translate(860, sideMargin.negate().add(HALF_W), ROOF_TOP - 1).finish('paint-matte', { color: '#2c3328' }),
  box(REAR_KEEPOUT, WIDTH - 80, 4).translate(LENGTH - REAR_KEEPOUT, -HALF_W + 40, ROOF_TOP - 1).finish('paint-matte', { color: '#3a3428' }),
];

for (const y of [-900, -450, 0, 450, 900]) {
  bodyPieces.push(box(18, 40, 26).translate(-6, y - 20, ROOF_Z - 70).finish('plastic-glossy', { color: '#e0a322' }));
  bodyPieces.push(box(18, 40, 26).translate(LENGTH - 12, y - 20, ROOF_Z - 70).finish('plastic-glossy', { color: '#b42318' }));
}
bodyPieces.push(box(LENGTH - 120, 10, 64).translate(60, -HALF_W - 4, FLOOR_TOP + 280).finish('plastic-glossy', { color: '#c0392b' }));
bodyPieces.push(box(LENGTH - 120, 10, 64).translate(60, HALF_W - 6, FLOOR_TOP + 280).finish('plastic-glossy', { color: '#c0392b' }));

for (const side of [-1, 1]) {
  const y = side * 916;
  bodyPieces.push(box(22, 460, 520).translate(11500, y - 230, 260).finish('rubber'));
  bodyPieces.push(box(40, 80, 460).translate(11490, y - 40, 740).finish('paint-matte', { color: '#1a1a1a' }));
}
for (const spec of [
  [LENGTH - 68, -1084, 1600],
  [LENGTH - 68, -1084, 2300],
  [LENGTH - 68, -1084, 3000],
  [LENGTH - 68, 1084, 1600],
  [LENGTH - 68, 1084, 2300],
  [LENGTH - 68, 1084, 3000],
]) {
  bodyPieces.push(cylinder(150, 16).translate(spec[0], spec[1], spec[2]).finish('steel'));
}
bodyPieces.push(cylinder(1900, 11).translate(LENGTH - 70, -70, 1450).finish('steel'));
bodyPieces.push(cylinder(1900, 11).translate(LENGTH - 70, 70, 1450).finish('steel'));

const trailer = assembly('freight-trailer');
const chassis = trailer.part('chassis', fuse(chassisParts), { material: 'mild-steel' });
const landing = trailer.part('landing-legs', fuse(landingParts), { material: 'mild-steel' });
const bogie = trailer.part('bogie', fuse(bogieParts), { material: 'mild-steel' });
const body = trailer.part(
  'box-body',
  fuse(bodyPieces).finish('paint-gloss', { color: '#f3f5f7' }),
  { material: 'aluminum-6061' },
);

frameConnector(chassis, 'origin');
frameConnector(chassis, 'to-landing');
frameConnector(chassis, 'to-bogie');
frameConnector(chassis, 'to-body');
frameConnector(landing, 'mount');
frameConnector(bogie, 'mount');
frameConnector(body, 'mount');
trailer.mate('landing-mount', 'chassis.to-landing', 'landing-legs.mount', 'fastened');
trailer.mate('bogie-mount', 'chassis.to-bogie', 'bogie.mount', 'fastened');
trailer.mate('body-mount', 'chassis.to-body', 'box-body.mount', 'fastened');

for (let a = 0; a < 2; a++) {
  const axleX = a === 0 ? FRONT_AXLE_X : REAR_AXLE_X;
  const right = trailer.part('wheel-axle' + a + '-right', dualWheels(axleX), { density: 1200 });
  const left = trailer.part('wheel-axle' + a + '-left', dualWheels(axleX).reflect('xz'), { density: 1200 });
  frameConnector(bogie, 'to-wheel-' + a + '-right');
  frameConnector(bogie, 'to-wheel-' + a + '-left');
  frameConnector(right, 'mount');
  frameConnector(left, 'mount');
  trailer.mate('wheel-' + a + '-right', 'bogie.to-wheel-' + a + '-right', 'wheel-axle' + a + '-right.mount', 'fastened');
  trailer.mate('wheel-' + a + '-left', 'bogie.to-wheel-' + a + '-left', 'wheel-axle' + a + '-left.mount', 'fastened');
}

const pitchX = gapAlong.add(MODULE_L);
const pitchY = gapAcross.add(MODULE_W);
for (let r = 0; r < rowN; r++) {
  for (let c = 0; c < colN; c++) {
    const name = 'pack-r' + r + '-c' + c;
    const mod = trailer.part(
      name,
      batteryModule().translate(startX.add(pitchX.multiply(r)), startY.add(pitchY.multiply(c)), ROOF_TOP + 0.5),
      { material: 'aluminum-6061' },
    );
    frameConnector(body, 'to-' + name);
    frameConnector(mod, 'mount');
    trailer.mate(name + '-mount', 'box-body.to-' + name, name + '.mount', 'fastened');
  }
}

void layoutNote;
return trailer.solvedModel({});

G2-blend adjacent spines then sweep a weld along a surface intersection

recipe · You need a curvature-continuous blend between adjacent 3D curves, or the exact intersection curve of crossing faces (a weld seam, a trim spine) as a Curve3D you can sweep.

sketch · primitive · intersect · G2 blend between existing curves · curveBridge hermite join · surface intersection weld seam · section curve of crossing cylinders

Recipe source

Recipe code
const left = spline3d([
  [-24, 0, 0],
  [-12, 5, 0],
  [0, 0, 0],
]);
const right = spline3d([
  [14, 0, 0],
  [26, -5, 0],
  [38, 0, 0],
]);
const blend = curveBridge(left, right, { continuity: 'G2' });
const ribProfile = path().moveTo(-1, -1).lineTo(1, -1).lineTo(1, 1).lineTo(-1, 1).close();
const rib = variableSweep(blend, [
  { t: 0, profile: ribProfile },
  { t: 1, profile: path().moveTo(-1, -1).lineTo(1, -1).lineTo(1, 1).lineTo(-1, 1).close() },
]);

const run = cylinder(30, 8);
const branch = cylinder(20, 6).rotateY(90).translate(-4, 0, 15);
const seams = await surfaceIntersection(run, branch);
const hit = seams[0].pointAt(0.5);
const marker = box(3, 3, 3, true).translate(hit[0], hit[1], hit[2]);

return rib.union(run.union(branch)).union(marker);

Close the print loop — export gcode, then send_to_printer

recipe · You have a finished, watertight kernelCAD part and want to go straight to a physical print — slice it to G-code with a real slicer (no manual GUI step), then upload it to a network-connected printer and start the print, without a human touching a slicer or a printer's touchscreen.

manufacturing · gcode · printer · parameter · slice geometry to gcode with a real slicer CLI binary · export format gcode layerHeight infill supports material · upload gcode to OctoPrint Moonraker or Bambu Lab over the network · physical print loop from script to a networked FDM printer · bed size check before invoking the slicer export.gcode.exceeds-bed

Recipe source

Recipe code
// Print-ready part sized to fit the bundled 'generic-fdm' bed profile
// (220x220x250mm) — see export.gcode.exceeds-bed if it doesn't.
const width = param('width', 30, { min: 5, max: 200 });
const height = param('height', 20, { min: 5, max: 200 });

return box(width, width, height)
  .fillet(2, { parallel: [0, 0, 1] });

// Then, outside the script:
//   export target:'model' format:'gcode' output_path:'part.gcode'
//     options:{ format:'gcode', printer:'generic-fdm', material:'pla',
//               layerHeight:0.2, infill:15, supports:false }
//   -> ok:true, byte_count, and slicer predictions in
//      the result's optional gcode_stats (estimatedPrintTimeSeconds,
//      filamentUsedGrams, layerCount, maxZMm).
//   No slicer installed? You get ok:false with
//   export.gcode.slicer-unavailable and an install hint — never a
//   placeholder gcode file.
//
//   send_to_printer gcode_path:'part.gcode' protocol:'octoprint'
//     host:'192.168.1.50' api_key:'...'
//   -> uploads and starts the print. Pass dry_run:true first to
//      confirm connectivity/auth before ever touching hardware.

Gridfinity bin with base profile, magnets, dividers and stacking lip

recipe · You need a Gridfinity-compatible storage bin: X × Y grid units on the 42 mm pitch (0.5 mm clearance), height in 7 mm units, one profiled foot per cell (0.8 / 1.8 / 2.15 mm = 4.75 mm), optional Ø6 × 2 mm magnet holes at ±13 mm, optional dividers, and a stacking lip (0.7 / 1.8 / 1.9 mm). The script throws a clear error when magnets do not fit the foot or the floor, or when the bin is too shallow for a cavity under the lip.

parameter · stacking · hole · manufacturing · extrude · chamfer · gridfinity bin storage box organizer 42 mm grid · parametric grid units X by Y and 7 mm height units · base profile 0.8 chamfer 1.8 straight 2.15 chamfer 4.75 mm · magnet holes 6 x 2 mm under each foot · dividers compartments and stacking lip

Recipe source

Recipe code
// Gridfinity bin — parametric X × Y grid units × height units.
//
// Dimensions from the Gridfinity specification (https://gridfinity.xyz/specification/,
// magnets 6 × 2 mm) and its reference constants
// (https://github.com/kennetek/gridfinity-rebuilt-openscad/blob/main/src/core/standard.scad):
//   42 mm grid pitch, bin = 42·n − 0.5 mm (0.5 mm clearance), top corner radius 3.75 mm,
//   base profile from the bottom: 0.8 chamfer / 1.8 straight / 2.15 chamfer = 4.75 mm,
//   7 mm height units, magnet holes Ø6.5 × 2.4 mm at ±13 mm from the cell centre,
//   stacking lip 0.7 chamfer / 1.8 straight / 1.9 chamfer = 4.4 mm, wall 0.95 mm.
const unitsX = 2;          // grid units along X
const unitsY = 1;          // grid units along Y
const heightUnits = 3;     // 7 mm each; bin top (below the lip) at z = 7 · heightUnits
const magnets = true;      // magnet holes under every foot corner
const magnetD = 6;         // magnet Ø (mm); hole = Ø + 0.5
const magnetT = 2;         // magnet thickness (mm); hole = t + 0.4
const dividersX = 1;       // internal walls across X (0 = none)
const dividersY = 0;       // internal walls across Y
const stackingLip = true;

const PITCH = 42, CLEARANCE = 0.5, R_TOP = 3.75;
const C1 = 0.8, S1 = 1.8, C2 = 2.15, BASE_H = C1 + S1 + C2; // 4.75
const LIP_C1 = 0.7, LIP_S = 1.8, LIP_C2 = 1.9, LIP_H = LIP_C1 + LIP_S + LIP_C2; // 4.4
const WALL = 0.95, FLOOR = 1.2, DIVIDER = 1.2, SKIN = 0.6; // SKIN: min plastic over a magnet
const W = PITCH * unitsX - CLEARANCE;
const D = PITCH * unitsY - CLEARANCE;
const TOP = 7 * heightUnits;
const holeD = magnetD + 0.5, holeH = magnetT + 0.4;

// Rounded-rect prism centred in X/Y, spanning z = 0..h.
const slab = (w, d, r, h) => extrudeRoundedRect(w, d, r, h);
// Two-step 45° profile: bottom chamfer c1, straight s, outward chamfer c2 up to
// the full (w × d, radius r) outline, which then runs on for `ext` mm. Used for
// the feet (ext overlaps the bin body) and the lip cutter (ext clears the top).
// Faces are picked by height ({ atZ }) — extruded profiles have no 'top'/'bottom'.
function profiled(w, d, r, c1, s, c2, ext) {
  const lower = slab(w - 2 * c2, d - 2 * c2, r - c2, c1 + s).chamfer(c1, { face: { atZ: 0 } });
  const upper = slab(w, d, r, c2 + ext).chamfer(c2, { face: { atZ: 0 } }).translate(0, 0, c1 + s);
  return lower.union(upper);
}

// Checks: fail loudly instead of printing a magnet that pokes through.
const footHalf = (PITCH - CLEARANCE) / 2 - C2 - C1;   // half-width of a foot's bottom face
if (magnets && 13 + holeD / 2 > footHalf - 0.4) {
  throw new Error(`gridfinity: a Ø${magnetD} magnet does not fit the foot (hole reaches ${13 + holeD / 2} mm, foot edge at ${footHalf} mm). Use Ø6 × 2 magnets.`);
}
const floorZ = BASE_H + FLOOR;                         // top of the bin floor
if (magnets && holeH + SKIN > floorZ) {
  throw new Error(`gridfinity: ${magnetT} mm magnets leave < ${SKIN} mm under the floor. Use thinner magnets or magnets = false.`);
}
const ledge = LIP_C1 + LIP_C2 - WALL;                 // lip overhang, chamfered 45° below
if (TOP - floorZ < (stackingLip ? ledge + 0.2 : 0.2)) {
  throw new Error(`gridfinity: heightUnits = ${heightUnits} is too shallow for a cavity${stackingLip ? ' under the stacking lip' : ''} (floor at ${floorZ} mm, top at ${TOP} mm). Use heightUnits >= 2${stackingLip ? ' or stackingLip = false' : ''}.`);
}

// Feet: one profiled foot per grid cell.
let bin = slab(W, D, R_TOP, TOP - BASE_H).translate(0, 0, BASE_H);
for (let i = 0; i < unitsX; i++) {
  for (let j = 0; j < unitsY; j++) {
    const cx = (i - (unitsX - 1) / 2) * PITCH;
    const cy = (j - (unitsY - 1) / 2) * PITCH;
    bin = bin.union(profiled(PITCH - CLEARANCE, PITCH - CLEARANCE, R_TOP, C1, S1, C2, 1).translate(cx, cy, 0));
  }
}

// Cavity from the floor to the top; with a lip, its top edge is chamfered 45°
// so the lip ledge prints without support.
let cavity = slab(W - 2 * WALL, D - 2 * WALL, R_TOP - WALL, TOP - floorZ).translate(0, 0, floorZ);
if (stackingLip) {
  cavity = slab(W - 2 * WALL, D - 2 * WALL, R_TOP - WALL, TOP - floorZ)
    .chamfer(ledge, { face: { atZ: TOP - floorZ } })
    .translate(0, 0, floorZ);
}
bin = bin.subtract(cavity);

// Dividers: full-height walls from the floor to the top of the bin.
for (let k = 1; k <= dividersX; k++) {
  const x = -W / 2 + (k * W) / (dividersX + 1);
  bin = bin.union(box(DIVIDER, D - 2 * WALL, TOP - floorZ, true).translate(x, 0, floorZ + (TOP - floorZ) / 2));
}
for (let k = 1; k <= dividersY; k++) {
  const y = -D / 2 + (k * D) / (dividersY + 1);
  bin = bin.union(box(W - 2 * WALL, DIVIDER, TOP - floorZ, true).translate(0, y, floorZ + (TOP - floorZ) / 2));
}

// Stacking lip: a W × D ring on top, cut by the inverse of the base profile.
if (stackingLip) {
  const lipCutter = profiled(W, D, R_TOP, LIP_C1, LIP_S, LIP_C2, 1).translate(0, 0, TOP);
  const lip = slab(W, D, R_TOP, LIP_H).translate(0, 0, TOP).subtract(lipCutter);
  bin = bin.union(lip);
}

// Magnet holes: 4 per foot at ±13 mm, from the bottom face up. One holes()
// call per foot — each foot has its own bottom face at z = 0, and
// `containsPoint` picks that foot's face. holes() rather than subtracted
// cylinders so the bores carry diameter/depth for `inspect`, drawings and the
// DFM gates; u/v are measured from the centre of the selected face, which is
// the foot centre, so the four corners are just ±13.
if (magnets) {
  for (let i = 0; i < unitsX; i++) {
    for (let j = 0; j < unitsY; j++) {
      const cx = (i - (unitsX - 1) / 2) * PITCH;
      const cy = (j - (unitsY - 1) / 2) * PITCH;
      bin = bin.holes({ byNormal: '-Z', atZ: 0, near: [cx, cy, 0], containsPoint: [cx, cy, 0] }, {
        positions: [
          { u: 13, v: 13 }, { u: 13, v: -13 }, { u: -13, v: 13 }, { u: -13, v: -13 },
        ],
        diameter: holeD,
        depth: holeH,
      });
    }
  }
}

return bin;

Compact GT2 belt-driven rotary actuator — housing, shaft, 608 bearing, cover

recipe · Prompt asks for a compact manufacturable GT2 belt-driven rotary actuator, not a stack of boxes: a machined housing with cavity walls, a bearing-seat bore, floor bosses, filleted edges, and a four-hole mounting pattern; two separate 20-tooth 2 mm-pitch pulleys; a closed belt; a shaft and 608-style bearing as separate bodies; a removable cover; named materials. The belt is a geometric cord and the pulley teeth are rounded nubs — not tooth meshing, pretension, or power transmission. Do not export USD Isaac: the cylindrical shaft/bearing mate fails with export.usd.joint-unsupported. After a full evaluate_script (not dry-run), inspect BOM and export a PDF or SVG drawing. Auto-GD&T in the script is generated annotation, not a manufacturing certification. Prefer design_loop until green.

assembly · connector · mate · belt · pulley · drive · bearing · shaft · material · manufacturing · fillet · hole · plate · shop-release GT2 rotary actuator · compact manufacturable belt-driven actuator · machined housing cavity walls bearing seat · four-hole mounting pattern removable cover · 608-style bearing separate shaft · two 20-tooth GT2 pulleys · rounded nubs clearance cord not tooth meshing · BOM-ready aluminum nylon abs materials · no USD cylindrical mate

Recipe source

Recipe code
// Compact GT2 belt-driven rotary actuator. Cord + rounded nubs are a
// clearance loop, NOT tooth mesh, belt tension, or power transmission.
// No animationView. Do NOT export usd-isaac: the cylindrical shaft/bearing
// mate is export.usd.joint-unsupported.
// After full evaluate_script: inspect({ of: 'bom' }) or export bom-csv /
// bom-json, then pdf-drawing / svg-drawing (balloons, parts list, title
// block). Datums below are annotation intent, not a GD&T certification.
// Catalog/vendor metadata may be missing — these are fabricated stand-ins.
//
// The cover is a bolted frame with a viewing aperture (and the housing has
// side windows) so the flanged pulleys, belt, and 608 stay visible. A solid
// lid would hide the drive and read as an empty box.

const pitch = 2, teeth = 20, C = 60, face = 7, cordR = 0.9, nubR = 0.82, gap = 0.5;
const pd = (teeth * pitch) / Math.PI;
const pitchR = pd / 2;
const beltR = pitchR + nubR + cordR + gap;
const beltTeeth = Math.round((2 * C + Math.PI * pd) / pitch);
const beltLen = beltTeeth * pitch;

const W = 112, D = 52, wall = 4.5, baseT = 5.5, wallH = 24, flange = 14;
const cx = (W + 2 * flange) / 2;
const cy = (D + 2 * flange) / 2;
const ax = 38, ay = cy, bx = ax + C;
const zPul = 15.2;
const zBelt = zPul + face / 2;
const boreR = 4.25;
const topZ = baseT + wallH;
const flangeR = beltR + cordR + 0.5;

function rounded(w: number, d: number, r: number, t: number, x: number, y: number, z: number) {
  return extrudeRoundedRect(w, d, r, t).translate(x, y, z);
}

function pulley() {
  const flangeT = 1.15;
  const hubR = pitchR - 1.55;
  const nubH = face - 2 * flangeT;
  let body = cylinder(face, hubR)
    .union(cylinder(flangeT, flangeR))
    .union(cylinder(flangeT, flangeR).translate(0, 0, face - flangeT));
  for (let i = 0; i < teeth; i += 1) {
    const a = (2 * Math.PI * i) / teeth;
    body = body.union(
      cylinder(nubH, nubR).translate(pitchR * Math.cos(a), pitchR * Math.sin(a), flangeT),
    );
  }
  // Collar above the top flange so the bore reads as a turned hub.
  body = body.union(cylinder(1.5, 5.05).translate(0, 0, face));
  return body.subtract(cylinder(face + 4, boreR).translate(0, 0, -1));
}

function sideWindow(y0: number) {
  const rad = 5.2;
  const zc = 20.2;
  const x0 = 40;
  const x1 = 100;
  const depth = 18;
  const bar = box(x1 - x0, depth, rad * 2).translate(x0, y0, zc - rad);
  const cap0 = cylinder(depth, rad).alongAxis([0, 1, 0]).translate(x0, y0, zc);
  const cap1 = cylinder(depth, rad).alongAxis([0, 1, 0]).translate(x1, y0, zc);
  return bar.union(cap0, cap1);
}

function outerWrap(ox: number, keepLeft: boolean) {
  const span = (beltR + cordR) * 2 + 6;
  const cutX = keepLeft ? ox + 0.25 : ox - span - 0.25;
  return torus(beltR, cordR).translate(ox, ay, zBelt)
    .subtract(box(span, span, 8).translate(cutX, ay - span / 2, zBelt - 4));
}

// 608-style open bearing: outer race, inner race, seven balls in the groove.
// One solid. Not a plain bushing washer.
function bearing608() {
  const outer = path()
    .moveTo(8.85, 0).lineTo(11, 0).lineTo(11, 7).lineTo(8.85, 7)
    .lineTo(8.85, 5.05).lineTo(9.65, 3.5).lineTo(8.85, 1.95).lineTo(8.85, 0)
    .close().revolve();
  const inner = path()
    .moveTo(4.05, 0).lineTo(6.35, 0).lineTo(6.35, 1.95).lineTo(5.55, 3.5)
    .lineTo(6.35, 5.05).lineTo(6.35, 7).lineTo(4.05, 7).lineTo(4.05, 0)
    .close().revolve();
  // Balls (r 2.1) seat into both grooves (apexes at r 5.55 and 9.65), so the
  // races and balls fuse into one solid. At r 1.35 they touched neither race
  // and the "one solid" was nine loose pieces (union.disconnected).
  let balls = sphere(2.1).translate(7.6, 0, 3.5);
  for (let i = 1; i < 7; i += 1) {
    const a = (2 * Math.PI * i) / 7;
    balls = balls.union(sphere(2.1).translate(7.6 * Math.cos(a), 7.6 * Math.sin(a), 3.5));
  }
  return outer.union(inner, balls);
}

const belt = outerWrap(ax, true).union(outerWrap(bx, false))
  .union(cylinder(C, cordR).alongAxis([1, 0, 0]).translate(ax, ay + beltR, zBelt))
  .union(cylinder(C, cordR).alongAxis([1, 0, 0]).translate(ax, ay - beltR, zBelt))
  .finish('rubber');

// Mounting flange + walled tub. Side windows and an open top keep the drive visible.
let housing = rounded(W + 2 * flange, D + 2 * flange, 6, baseT, cx, cy, 0)
  .union(rounded(W, D, 6, wallH + 0.4, cx, cy, baseT - 0.4));
housing = housing.subtract(rounded(W - 2 * wall, D - 2 * wall, 3, wallH + 1, cx, cy, baseT));
// Bearing boss and idler shoulder fuse into the floor after the cavity is cut.
housing = housing
  .union(cylinder(3.6, 13.4).translate(ax, ay, baseT - 0.3))
  .union(cylinder(4.2, 8.2).translate(bx, ay, baseT - 0.3))
  .union(cylinder(6.0, 5.4).translate(bx, ay, baseT + 3.5))
  .union(cylinder(face + 0.6, 3.7).translate(bx, ay, zPul - 0.2));
housing = housing
  .subtract(cylinder(4.2, 11.35).translate(ax, ay, baseT))
  .subtract(cylinder(baseT + 2, 5.2).translate(ax, ay, -1))
  .subtract(sideWindow(8))
  .subtract(sideWindow(54));
// Mounting holes through the flange as real hole features. The flange ring's
// top face is centred on (cx, cy), so u = x - cx and v = y - cy.
housing = housing.holes({ byNormal: 'Z', atX: cx, atY: cy, atZ: baseT }, {
  positions: [[10, 10], [130, 10], [10, 70], [130, 70]].map(([x, y]) => ({ u: x - cx, v: y - cy })),
  diameter: 3.1,
  depth: 'through',
  counterbore: { diameter: 5.4, depth: 2.2 },
});
housing = housing
  .fillet(0.6, { parallel: [0, 0, 1] })
  .datum('A', { atZ: 0 })
  .datum('B', { atX: 0 })
  .datum('C', { atY: 0 })
  .tolerance({
    type: 'position', value: 0.2, modifier: '⌀', datums: ['A', 'B', 'C'],
    edge: { ofCurveType: 'CIRCLE', near: [10, 10, 0] },
  })
  .finish('aluminium-brushed');

let cover = rounded(W, D, 6, 3.2, cx, cy, topZ + 0.08)
  .subtract(rounded(W - 18, D - 16, 4, 6, cx, cy, topZ - 1));
for (const [x, y] of [[40, 18], [100, 18], [40, 62], [100, 62]] as const) {
  cover = cover.union(cylinder(2.2, 2.35).translate(x, y, topZ + 2.2));
}
cover = cover.finish('anodized-black');

const bearing = bearing608().translate(ax, ay, 5.75).finish('steel');
// Stepped output: nose below the flange, journal through the 608, shoulder, pulley nose.
const shaft = path()
  .moveTo(0, -8).lineTo(4.15, -8).lineTo(4.15, 4.7)
  .lineTo(3.8, 4.7).lineTo(3.8, 12.9)
  .lineTo(5.3, 12.9).lineTo(5.3, 15.0)
  .lineTo(3.8, 15.05).lineTo(3.8, 24.2)
  .lineTo(0, 24.2).close()
  .revolve()
  .translate(ax, ay, 0)
  .finish('stainless');
const drive = pulley().translate(ax, ay, zPul).finish('abs', { color: '#f3efe4' });
const idler = pulley().translate(bx, ay, zPul).finish('abs', { color: '#f3efe4' });

const arm = assembly(`gt2-actuator-${beltTeeth}t-${beltLen}mm`);
const h = arm.part('housing', housing, { material: 'aluminum-6061' });
const c = arm.part('cover', cover, { material: 'aluminum-6061' });
const br = arm.part('bearing-608', bearing, { material: 'mild-steel' });
const sh = arm.part('shaft', shaft, { material: 'mild-steel' });
const pa = arm.part('pulley-drive', drive, { material: 'nylon' });
const pb = arm.part('pulley-idler', idler, { material: 'nylon' });
const bl = arm.part('gt2-belt', belt, { material: 'abs' });

const seat = [ax + 11, ay, 9.1] as [number, number, number];
const spin = [ax, ay, 9.1] as [number, number, number];
const hubA = [ax, ay, zBelt] as [number, number, number];
const hubB = [bx, ay, zBelt] as [number, number, number];
const cord = [ax - beltR, ay, zBelt] as [number, number, number];
const lid = [cx, cy, topZ + 0.08] as [number, number, number];

h.connector('lid', { type: 'frame', origin: { kind: 'vec3', value: lid } });
c.connector('seat', { type: 'frame', origin: { kind: 'vec3', value: lid } });
h.connector('bearing-seat', { type: 'frame', origin: { kind: 'vec3', value: seat }, jointClearanceRadius: 0.4 });
br.connector('outer', { type: 'frame', origin: { kind: 'vec3', value: seat } });
br.connector('bore', { type: 'axis', origin: { kind: 'vec3', value: spin }, axis: [0, 0, 1], jointClearanceRadius: 4.3 });
sh.connector('axis', { type: 'axis', origin: { kind: 'vec3', value: spin }, axis: [0, 0, 1] });
sh.connector('hub', { type: 'frame', origin: { kind: 'vec3', value: hubA } });
pa.connector('bore', { type: 'frame', origin: { kind: 'vec3', value: hubA }, jointClearanceRadius: boreR });
h.connector('stud', { type: 'frame', origin: { kind: 'vec3', value: hubB } });
pb.connector('bore', { type: 'frame', origin: { kind: 'vec3', value: hubB }, jointClearanceRadius: boreR });
bl.connector('cord', { type: 'frame', origin: { kind: 'vec3', value: cord } });
pa.connector('belt-near', { type: 'frame', origin: { kind: 'vec3', value: cord }, jointClearanceRadius: cordR + gap + 0.3 });

arm.mate('cover', 'cover.seat', 'housing.lid', 'fastened');
arm.mate('bearing-seat', 'bearing-608.outer', 'housing.bearing-seat', 'fastened');
arm.mate('shaft-spin', 'shaft.axis', 'bearing-608.bore', 'cylindrical');
arm.mate('drive-hub', 'pulley-drive.bore', 'shaft.hub', 'fastened');
arm.mate('idler-stud', 'pulley-idler.bore', 'housing.stud', 'fastened');
arm.mate('belt-loop', 'gt2-belt.cord', 'pulley-drive.belt-near', 'fastened');

return arm.solvedModel({});

GT2 timing-belt drive with pitch-diameter pulleys

recipe · You need a GT2 timing-belt drive: two toothed pulleys at a center distance, with belt length computed from pitch diameters and center distance then rounded to a real GT2 tooth count (2 mm pitch).

belt · pulley · drive · extrude · sketch · assembly · parameter · GT2 timing belt 2 mm pitch · toothed pulleys at a center distance · belt length from pitch diameters and center distance · rounded to a real GT2 tooth count

Recipe source

Recipe code
const pitch = 2; // GT2
const z1 = 20;
const z2 = 20;
const C = 60; // center distance
const faceWidth = 8;
const boreR = 2.5;
const pd1 = (z1 * pitch) / Math.PI;
const pd2 = (z2 * pitch) / Math.PI;
const rawLength = 2 * C + (Math.PI * (pd1 + pd2)) / 2 + ((pd2 - pd1) ** 2) / (4 * C);
const beltTeeth = Math.round(rawLength / pitch);
const beltLength = beltTeeth * pitch;

function gt2Pulley(teeth, pd) {
  const pitchR = pd / 2;
  const rootR = pitchR - 0.6;
  const nubR = 0.85; // rounded tooth; 2 mm pitch keeps nubs from overlapping
  let body = cylinder(faceWidth, rootR);
  for (let i = 0; i < teeth; i += 1) {
    const a = (2 * Math.PI * i) / teeth;
    body = body.union(
      cylinder(faceWidth, nubR).translate(pitchR * Math.cos(a), pitchR * Math.sin(a), 0),
    );
  }
  return body.subtract(cylinder(faceWidth + 2, boreR).translate(0, 0, -1));
}

const pulleyA = gt2Pulley(z1, pd1);
const pulleyB = gt2Pulley(z2, pd2).translate(C, 0, 0);

// Closed pitch-line loop: two 180° wraps + two tangents. Cord r=0.7 so
// inspect volume / (π r²) recovers beltLength. The wraps are cut exactly at
// the pulley centres (x = 0 and x = C) so their end faces meet the tangent
// cord ends; a gap there leaves the belt as four loose pieces
// (union.disconnected).
const cordR = 0.7;
const wrapA = torus(pd1 / 2, cordR)
  .subtract(box(pd1 * 2, pd1 * 2, 4).translate(0, -pd1, -2));
const wrapB = torus(pd2 / 2, cordR)
  .translate(C, 0, 0)
  .subtract(box(pd2 * 2, pd2 * 2, 4).translate(C - pd2 * 2, -pd2, -2));
const tanTop = cylinder(C, cordR).alongAxis([1, 0, 0]).translate(0, pd1 / 2, 0);
const tanBot = cylinder(C, cordR).alongAxis([1, 0, 0]).translate(0, -pd1 / 2, 0);
const belt = wrapA.union(wrapB, tanTop, tanBot);

const arm = assembly(`gt2-${beltTeeth}t-${beltLength}mm`);
arm.part('pulley-a', pulleyA);
arm.part('pulley-b', pulleyB);
arm.part('gt2-belt', belt);
return arm.model();

Heat-set threaded insert pilot holes for FDM prints (M2.5/M3/M4)

recipe · A printed part takes brass heat-set (melt-in) threaded inserts. Cut the pilot with `hole()` / `holes()` (blind, insert length + 1 mm deep, with a small countersink lead-in), sized from the insert maker's table — not the screw's tap drill and not a subtracted cylinder. Keep >= 1.6 mm of wall around the pilot.

hole · fastener · thread · manufacturing · printer · choice-param · plate · heat set insert hole for M3 M2.5 M4 brass threaded insert · heat-set insert pilot diameter and depth for 3D printing · melt-in insert boss in PLA PETG enclosure · insert pilot 4.0 to 4.2 mm for M3, print a test coupon first · lead-in chamfer countersink on the insert hole

Recipe source

Recipe code
// Typical short brass heat-set inserts (CNC-Kitchen / Ruthex style). The pilot
// is the insert's recommended hole; check your insert's datasheet and print a
// coupon: PLA often wants +0.1-0.2 mm over the table.
//   size   pilot Ø   insert length   min wall around the pilot
//   M2.5   3.6       4.0             1.6
//   M3     4.0       5.7             1.6   (users with 4.2 mm pilots: shorter, knurled inserts)
//   M4     5.6       8.1             2.0
const INSERT: Record<string, { pilot: number; length: number; wall: number }> = {
  'M2.5': { pilot: 3.6, length: 4.0, wall: 1.6 },
  M3: { pilot: 4.0, length: 5.7, wall: 1.6 },
  M4: { pilot: 5.6, length: 8.1, wall: 2.0 },
};
const size = param('Insert', 'M3', { choices: ['M2.5', 'M3', 'M4'] });
const ins = INSERT[size.value];
const spacing = 40;

// Mounting bar: tall enough for the insert plus 1 mm of melt room under it,
// wide enough for the minimum wall on each side of the pilot.
const depth = ins.length + 1;
const barH = depth + 2;
const barW = ins.pilot + 2 * ins.wall + 4;
const bar = box(spacing + barW, barW, barH).translate(-(spacing + barW) / 2, -barW / 2, 0);

const withInserts = bar.holes('top', {
  positions: [{ u: -spacing / 2, v: 0 }, { u: spacing / 2, v: 0 }],
  diameter: ins.pilot,
  depth,                                                  // blind
  countersink: { diameter: ins.pilot + 0.8, angleDeg: 90 }, // lead-in for the hot insert
  name: 'insertPilot',
});

return withInserts;

Quintic Hermite G2 bridge between NURBS curve flanks

recipe · You have a pair of existing NURBS curves whose tangents and curvatures match at the join point and you want a G2-continuous compound spine (so a downstream variableSweep does not kink at the join). Author the flanks via nurbsCurve, then drop a hermiteG2 between them with matching endpoint tangents and curvatures.

primitive · hermiteG2 · quintic hermite transition · g2-continuous curve bridge

Recipe source

Recipe code
const flankL = nurbsCurve([[-30, 0, 0], [-20, 4, 0], [-10, 4, 0]]);
const flankR = nurbsCurve([[ 10, 4, 0], [ 20, 4, 0], [ 30, 0, 0]]);
const bridge = hermiteG2(
  { point: [-10, 4, 0], tangent: [10, 0, 0], curvature: [0, -0.05, 0] },
  { point: [ 10, 4, 0], tangent: [10, 0, 0], curvature: [0, -0.05, 0] },
);
const profile = path().moveTo(-1, -1).lineTo(1, -1).lineTo(1, 1).lineTo(-1, 1).close();
const spineHalf = variableSweep(bridge, [
  { t: 0, profile },
  { t: 1, profile: path().moveTo(-1, -1).lineTo(1, -1).lineTo(1, 1).lineTo(-1, 1).close() },
]);
return spineHalf;

Hex nut trap pocket with bolt clearance hole (M3-M6, FDM clearance)

recipe · A printed or machined part needs a captive hex nut under a bolt. Cut the through clearance hole with `hole()` and the hexagonal pocket with `cutout(hexPath, { face: 'bottom', depth })`, sized from the nut's across-flats (AF) + 2 x clearance and nut height + clearance. Do not subtract a cylinder or a hand-placed prism.

hole · cutout · hex · fastener · bolt · pocket · manufacturing · choice-param · nut trap for M3 M4 M5 M6 bolt captive hex nut pocket · hex pocket across flats plus FDM clearance · captured nut recess under a bolt clearance hole · ISO 4032 nut across-flats and height table · nut pocket in a 3D printed part, printer tolerance

Recipe source

Recipe code
// ISO 4032 hex nut (AF across flats, m height) and ISO 273 normal clearance hole.
//   size  AF    m     clearance hole
//   M3    5.5   2.4   3.4
//   M4    7.0   3.2   4.5
//   M5    8.0   4.7   5.5
//   M6    10.0  5.2   6.6
const NUT: Record<string, { af: number; m: number; clear: number }> = {
  M3: { af: 5.5, m: 2.4, clear: 3.4 },
  M4: { af: 7.0, m: 3.2, clear: 4.5 },
  M5: { af: 8.0, m: 4.7, clear: 5.5 },
  M6: { af: 10.0, m: 5.2, clear: 6.6 },
};
const size = param('Bolt', 'M3', { choices: ['M3', 'M4', 'M5', 'M6'] });
const nut = NUT[size.value];
// Per-side FDM clearance on the hex flats (0.1-0.2 mm for a press-in nut on a
// tuned printer; 0 for a machined pocket).
const fdmClearance = 0.15;
const t = 10;

// Hex with flats = AF + 2*clearance; vertex radius R = AF / sqrt(3).
const af = nut.af + 2 * fdmClearance;
const r = af / Math.sqrt(3);
let hex = path();
for (let i = 0; i < 6; i += 1) {
  const a = (Math.PI / 180) * (30 + 60 * i);
  hex = i === 0 ? hex.moveTo(r * Math.cos(a), r * Math.sin(a)) : hex.lineTo(r * Math.cos(a), r * Math.sin(a));
}

// Block centred in X/Y. Bolt enters from the top, nut is trapped from the bottom.
const block = box(24, 24, t).translate(-12, -12, 0)
  .hole('top', { u: 0, v: 0, diameter: nut.clear, depth: 'through', name: 'boltClear' })
  .cutout(hex.close(), { face: 'bottom', depth: nut.m + 0.3 });

return block;

Involute spur gear mesh at module pitch

recipe · You need a meshing involute spur gear pair (module, tooth counts, 20° pressure angle, face width, bore, backlash). Use the `spurGear(...)` primitive: it generates true involute flanks, a trochoid root fillet and correct undercut as ONE manifold solid. Center distance is m(z1+z2)/2.

gear · involute · extrude · parameter · assembly · involute spur gear primitive spurGear module teeth pressure angle · spur gear pair at 20 degree pressure angle · module and tooth counts set the pitch-circle center distance · meshing pinion and gear with backlash, not trapezoid teeth · two meshing spur gears, gear pair, gear train, reduction ratio z2/z1 · do not hand-build involute tooth points; spurGear and ringGear generate them · 3D printed gears module 1 1.5 2 with FDM backlash

Recipe source

Recipe code
const moduleMm = 1;
const z1 = 20;
const z2 = 40;
const faceWidth = 6;
// Circular play at the pitch circle. Raise to 0.1-0.2 mm for FDM prints.
const backlash = 0.05;
const centerDistance = moduleMm * (z1 + z2) / 2; // m(z1+z2)/2

// spurGear builds one closed involute profile (tooth 0 on +X), extruded
// from z = 0 to z = faceWidth, with a through-bore.
const pinion = spurGear({ module: moduleMm, teeth: z1, faceWidth, bore: 5, backlash });
// Turn the second gear so a tooth SPACE faces the pinion's tooth 0:
// half a pitch when z2 is even, none when it is odd.
const gear = spurGear({ module: moduleMm, teeth: z2, faceWidth, bore: 8, backlash })
  .rotateZ(z2 % 2 === 0 ? 180 / z2 : 0)
  .translate(centerDistance, 0, 0);

const pair = assembly('involute-spur-gear-pair');
pair.part('pinion', pinion);
pair.part('gear', gear);
return pair.model();

ISO metric hex fastener with helical thread

recipe · You need an ISO metric hex bolt threaded into its hex nut (M3–M12) whose pitch, hex across-flats, and head height come from a params table. The bolt thread is a real 60° V profile swept with sweep(helix(...), { spine: 'helix' }); the nut's internal thread is the thread option of its bore (pitch, modeled, play), seated in phase so the two parts never interfere. A cosmetic thread (modeled: false) keeps only the minor-diameter bore for speed.

fastener · helix · sweep · hex · thread · parameter · assembly · ISO metric hex bolt M3 M4 M5 M6 M8 M10 M12 · coarse pitch from the ISO 262 table · hex across-flats and head height · 60-degree ISO V-thread swept along an exact helix · hex nut with a modeled internal thread · thread clearance cap pitch/8 and FDM print tolerance

Recipe source

Recipe code
// ISO 262 coarse / ISO 4017 hex head / ISO 4032 hex nut (mm).
const ISO = {
  M3:  { d: 3,  pitch: 0.5,  af: 5.5,  head: 2.0,  nut: 2.4 },
  M4:  { d: 4,  pitch: 0.7,  af: 7.0,  head: 2.8,  nut: 3.2 },
  M5:  { d: 5,  pitch: 0.8,  af: 8.0,  head: 3.5,  nut: 4.7 },
  M6:  { d: 6,  pitch: 1.0,  af: 10.0, head: 4.0,  nut: 5.2 },
  M8:  { d: 8,  pitch: 1.25, af: 13.0, head: 5.3,  nut: 6.8 },
  M10: { d: 10, pitch: 1.5,  af: 16.0, head: 6.4,  nut: 8.4 },
  M12: { d: 12, pitch: 1.75, af: 18.0, head: 7.5,  nut: 10.8 },
};
const spec = ISO.M6;
const P = spec.pitch;
const turns = 12; // threaded length = turns * pitch
// Nut thread play, mm. Capped at pitch/8 (M6 × 1: 0.125, M8 × 1.25: 0.156) so
// the groove turns never merge; a larger value fails with the numbers to use.
const threadClearance = param('threadClearance', 0.05);
// Extra flank play for a printed (FDM) nut, mm — typically 0.1–0.3 on top of the
// cap. It grows the nut's whole internal thread: a nominal diameter 4 × larger
// shifts bore, crest and flanks outward, and a radial shift δ opens each 60°
// flank by δ/2. 0 keeps the ISO nut.
const printPlay = param('printPlay', 0);

function hexPrism(af, height) {
  // Flat-to-flat = af. Vertex radius R = af / √3 for a flat-top hex.
  const r = af / Math.sqrt(3);
  let sk = path();
  for (let i = 0; i < 6; i += 1) {
    const a = (Math.PI / 180) * (30 + 60 * i);
    const x = r * Math.cos(a);
    const y = r * Math.sin(a);
    sk = i === 0 ? sk.moveTo(x, y) : sk.lineTo(x, y);
  }
  return sk.close().extrude(height);
}

// ISO 68-1 basic profile: H = √3/2·P, minor radius d/2 − 5H/8, ridge 3P/4 wide
// at the minor radius and P/8 wide at the crest, flanks at 30°.
const H = (Math.sqrt(3) / 2) * P;
const rMinor = spec.d / 2 - (5 / 8) * H;
const halfWidth = (r) => (3 / 8) * P - (r - rMinor) * Math.tan(Math.PI / 6);

// Profile in the helix's axial plane: x = radial offset from the helix point,
// y = axial offset. The root runs 0.15·P into the core with straight sides so
// the core cylinder crosses the flanks cleanly.
const kink = -0.05 * P;
const crest = spec.d / 2 - rMinor;
const vProfile = path()
  .moveTo(-0.15 * P, -halfWidth(rMinor + kink))
  .lineTo(kink, -halfWidth(rMinor + kink))
  .lineTo(crest, -halfWidth(rMinor + crest))
  .lineTo(crest, halfWidth(rMinor + crest))
  .lineTo(kink, halfWidth(rMinor + kink))
  .lineTo(-0.15 * P, halfWidth(rMinor + kink))
  .close();

// spine: 'helix' sweeps along the exact helix by screw motion, so the 60° V
// stays a V and the solid is valid.
const thread = vProfile.sweep(helix({ radius: rMinor, pitch: P, turns }), { spine: 'helix' });
const shank = cylinder((turns + 1) * P, rMinor).union(thread);
const bolt = hexPrism(spec.af, spec.head).union(shank.translate(0, 0, spec.head));

// Internal thread: diameter is the nominal size; the hole drills the ISO minor
// diameter and cuts the matching groove. modeled: false would keep only the
// bore (a cosmetic thread) for speed.
const nut = hexPrism(spec.af, spec.nut).hole({ atZ: spec.nut, byNormal: 'Z' }, {
  u: 0,
  v: 0,
  diameter: printPlay.multiply(4).add(spec.d),
  depth: 'through',
  thread: { pitch: P, modeled: true, clearance: threadClearance },
});

// The bolt's thread crosses +X at z = head + k·P; the nut's groove crosses its
// u direction (+X) at its top face. Seat the top face on a whole pitch so the
// two threads are in phase.
const nutTop = spec.head + 10 * P;
const arm = assembly('iso-metric-bolt-and-nut');
arm.part('hex-bolt', bolt, { material: 'mild-steel' });
arm.part('hex-nut', nut, { material: 'mild-steel', at: [0, 0, nutTop - spec.nut] });
return arm.model();

Loft a stylized body shell from polyline cross-section profiles

recipe · Stylized / mechanism-demo shell ONLY — recognizable printable boxy loft from polyline stations (toy car, boat hull demo, fuselage blockout). NOT for berlinetta / sports-car / organic automotive likeness. For organic car bodies use automotive-body-envelope instead (lookup_cookbook("automotive body envelope")).

shell · sketch · symmetry · mirror · loft a stylized body shell · mechanism demo shell · polyline loft shell · hull from cross sections · fuselage loft · cross-section stations · stylized printable shell

Recipe source

Recipe code
// 1. Cross-section profiles at stations along the +Z axis (nose -> mid -> tail).
//    Same point order + winding on every section so the loft stays manifold.
const section = (w: number, h: number) =>
  path()
    .moveTo(-w / 2, -h / 2)
    .lineTo(w / 2, -h / 2)
    .lineTo(w / 2, h / 2)
    .lineTo(-w / 2, h / 2)
    .close();

const nose = section(20, 14);   // station at z = 0
const mid = section(46, 30);    // station at z = 55
const tail = section(30, 20);   // station at z = 110

// 2. Loft a solid through the stations; spacing z-stacks them axially.
//    spacing takes a plain number (mm) — not a param() ref.
const body = nose.loft([mid, tail], { spacing: 55 });

// 3. Soften the four long edges that run along the body axis, then hollow the
//    body into a shell by opening the tail end-cap (the +Z-facing face).
return body
  .fillet(4, { parallel: [0, 0, 1] })
  .shell(2.5, { face: { byNormal: 'Z' } });

2-DOF pan/tilt inspection head with a rigid routed service tube and animationView

recipe · Prompt asks for a production 2-DOF pan/tilt inspection head: grounded base, yaw stage, tilt yoke with cheeks and a servo shelf fused into the parent solid, two revolute mates with limits, mechanicalJoint intent, and a rigid swept service tube fastened to the moving head (NOT a flexible hose). Declare yawDeg and tiltDeg with param() in the same block as animationView — tracks must name those live params; dropping param() while keeping tracks throws animation.param.unknown. Prefer design_loop until evaluate_script reports mechanism=real, then open_in_studio and capture_animation from a file with pose-interference verification. Closed-loop FK and joint.scissorLift are unsupported — stop and say so.

assembly · connector · mate · revolute · joint · kinematic · parameter · actuator · tube · route · sweep · material · pan tilt inspection head · two DOF inspection head · yaw stage tilt yoke · mated revolute inspection head · routed service tube · rigid swept tube fastened to moving head · not a flexible hose · animationView inspection cycle · param yawDeg tiltDeg co-located · mechanicalJoint mechanism real · bend radius circular sweep

Recipe source

Recipe code
// 2-DOF pan/tilt inspection head + rigid routed service tube.
// Tube is a swept solid fastened to the moving head — NOT a flexible hose.
// No joint.scissorLift and no closed-loop FK (unsupported).
// KEEP param() immediately beside animationView. Tracks must name those
// live numeric params; do not replace them with literals when rewriting.

const yawDeg = param('yawDeg', 18, { min: -70, max: 70 });
const tiltDeg = param('tiltDeg', 18, { min: -10, max: 35 });
animationView({
  name: 'inspection cycle',
  tracks: [
    {
      param: 'yawDeg',
      keys: [
        { atMs: 0, value: -25 },
        { atMs: 1400, value: 35, ease: 'easeInOut' },
        { atMs: 2800, value: -25, ease: 'easeInOut' },
      ],
    },
    {
      param: 'tiltDeg',
      keys: [
        { atMs: 0, value: 8 },
        { atMs: 1400, value: 30, ease: 'easeInOut' },
        { atMs: 2800, value: 8, ease: 'easeInOut' },
      ],
    },
  ],
  fps: 24,
});

const tiltZ = 78;
const headLen = 78;
const bendR = 10;
const tubeR = 3.1;
const arm = assembly('inspection-head-2dof');

const sub = (a: number[], b: number[]) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
const add = (a: number[], b: number[]) => [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
const scale = (a: number[], s: number) => [a[0] * s, a[1] * s, a[2] * s];
const dot = (a: number[], b: number[]) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
const cross = (a: number[], b: number[]) => [
  a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0],
];
const len = (a: number[]) => Math.hypot(a[0], a[1], a[2]);
const norm = (a: number[]) => { const n = len(a); return n < 1e-9 ? [0, 0, 0] : scale(a, 1 / n); };

function filletedRail(pts: number[][], R: number, arcSamples: number) {
  const rail: number[][] = [pts[0]];
  for (let i = 1; i < pts.length - 1; i += 1) {
    const A = pts[i - 1], P = pts[i], B = pts[i + 1];
    const uIn = norm(sub(P, A)), uOut = norm(sub(B, P));
    const alpha = Math.acos(Math.min(1, Math.max(-1, dot(uIn, uOut))));
    if (alpha < 1e-6) continue;
    const d = R / Math.tan(alpha / 2);
    const T1 = sub(P, scale(uIn, d));
    const cr = cross(uIn, uOut);
    const center = add(T1, scale(norm(cross(cr, uIn)), R));
    const v0 = sub(T1, center), axis = norm(cr);
    for (let s = 0; s <= arcSamples; s += 1) {
      const ang = alpha * (s / arcSamples), c = Math.cos(ang), si = Math.sin(ang), k = dot(axis, v0);
      rail.push(add(center, add(add(scale(v0, c), scale(cross(axis, v0), si)), scale(axis, k * (1 - c)))));
    }
  }
  rail.push(pts[pts.length - 1]);
  return rail;
}

// Local waypoints: first segment +Z (profile in XY); rotateY(-90) maps onto tilt-head +X.
const tubeWaypoints = [[0, 0, 0], [0, 0, 28.8], [0, -14, 28.8], [12, -14, 28.8]];
const sampled = filletedRail(tubeWaypoints, bendR, 10);
const rail: number[][] = [sampled[0]];
for (let i = 1; i < sampled.length; i += 1) {
  const p = sampled[i], q = rail[rail.length - 1];
  if (Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]) > 1e-4) rail.push(p);
}
const tubeSolid = path()
  .circle(0, 0, tubeR, 24)
  .sweep(rail, { spine: 'polyline', transitionMode: 'round' })
  .rotateY(-90);

function roundPlate(w: number, d: number, r: number, t: number, x: number, y: number, z: number) {
  return extrudeRoundedRect(w, d, r, t).translate(x, y, z);
}

// Hobby-servo stand-in: rounded body, mounting ears, short output boss.
// Built with its origin at the body center.
function servoBody(w: number, d: number, h: number) {
  const earT = Math.min(5, d * 0.4);
  return extrudeRoundedRect(w, d, Math.min(3, d * 0.2), h).translate(0, 0, -h / 2)
    .union(extrudeRoundedRect(w + 10, earT, 1.4, 2.6).translate(0, 0, h / 2 - 2.6))
    .union(cylinder(3.4, Math.min(5.5, d * 0.32)).translate(0, -d / 2 + 1, 0));
}

const base = arm.part(
  'base-frame',
  roundPlate(124, 104, 14, 8, 0, 0, 0)
    .union(cylinder(34, 17).translate(0, 0, 6.5))
    .union(cylinder(3.4, 24).translate(0, 0, 39.5))
    .union(cylinder(5.2, 15).translate(0, 0, 42.2))
    .union(cylinder(1.4, 8).translate(0, 0, 47))
    .union(servoBody(34, 26, 22).translate(-48, 0, 18))
    .finish('aluminium-brushed'),
  { material: 'aluminum-6061' },
);
base.connector('yaw', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 48] }, axis: [0, 0, 1] });

// Yaw stage: a hub that bears on the top face of the base's r8 pivot boss
// (z 48.4), a deck, and a clevis of two bored cheeks for the tilt
// axle. The tilt servo stands on its own plate outboard of the +Y cheek.
const cheekY = 21;
const cheekT = 7;
const cheekOuterY = cheekY + cheekT / 2; // 24.5
const axleR = 4.2;
const axleBoreR = axleR + 0.15; // running clearance
const servoY = cheekOuterY + 0.5 + 9; // 18 mm deep body, 0.5 mm off the cheek
const servoZ = 20 + 14; // 28 mm tall body standing on the deck-level plate
const yawStage = arm.part(
  'yaw-stage',
  cylinder(11.6, 14).translate(0, 0, 0.4)
    .union(roundPlate(18, 54, 3, 8, 0, 0, 12))
    .union(roundPlate(20, cheekT, 2, 26, 0, cheekY, 20))
    .union(roundPlate(20, cheekT, 2, 26, 0, -cheekY, 20))
    .subtract(cylinder(60, axleBoreR, 32).rotate([1, 0, 0], 90).translate(0, 30, tiltZ - 48))
    .union(roundPlate(32, 22, 3, 8, -16, cheekOuterY + 0.5 + 11, 12))
    .finish('anodized', { color: '#8aa0ad' }),
  { material: 'aluminum-6061' },
);
yawStage.connector('yaw', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 0, 1] });
// The tilt pivot is declared on the +Y cheek, where the axle runs in its bore.
yawStage.connector('tilt', {
  type: 'axis', origin: { kind: 'vec3', value: [0, cheekY, tiltZ - 48] }, axis: [0, 1, 0],
  jointClearanceRadius: axleBoreR,
});
yawStage.connector('tilt-servo-mount', { type: 'frame', origin: { kind: 'vec3', value: [-14, servoY, servoZ] } });

const tiltServo = arm.part(
  'tilt-servo',
  servoBody(26, 18, 28).translate(-14, servoY, servoZ).finish('abs', { color: '#243140' }),
  { material: 'nylon' },
);
tiltServo.connector('mount', { type: 'frame', origin: { kind: 'vec3', value: [-14, servoY, servoZ] } });

// Tilt head: the axle spans the clevis cheek to cheek, two hubs sit inside
// the cheeks, and the service tube is butted onto the post's -X face
// (post r5.2 at x 46: face at x 40.8).
const tubeAt: [number, number, number] = [40.8, 0, 10];
const tubePlaced = tubeSolid.translate(tubeAt[0], tubeAt[1], tubeAt[2]);
const tiltHead = arm.part(
  'tilt-head',
  // Tube starts at x = 3, inside the r 4.2 tilt axle (from x = 10 the axle
  // and hubs floated off it: union.disconnected); its far end is unchanged.
  cylinder(headLen - 11, 6.2).rotate([0, 1, 0], 90).translate(3, 0, 0)
    .union(cylinder(2 * cheekOuterY, axleR).rotate([1, 0, 0], 90).translate(0, cheekOuterY, 0))
    .union(cylinder(8, 8).rotate([1, 0, 0], 90).translate(0, 17, 0))
    .union(cylinder(8, 8).rotate([1, 0, 0], 90).translate(0, -9, 0))
    .union(cylinder(20, 11).rotate([0, 1, 0], 90).translate(headLen - 8, 0, -5))
    .union(cylinder(3, 13).rotate([0, 1, 0], 90).translate(headLen + 6, 0, -5))
    .subtract(cylinder(6, 6).rotate([0, 1, 0], 90).translate(headLen + 8, 0, -5))
    .union(cylinder(14, 5.2).translate(46, 0, 0))
    .finish('anodized', { color: '#d5dde3' }),
  { material: 'aluminum-6061' },
);
tiltHead.connector('tilt', { type: 'axis', origin: { kind: 'vec3', value: [0, cheekY, 0] }, axis: [0, 1, 0] });
tiltHead.connector('tube-mount', { type: 'frame', origin: { kind: 'vec3', value: tubeAt } });

const serviceTube = arm.part(
  'service-tube',
  tubePlaced.finish('abs', { color: '#e07a3d' }),
  { material: 'nylon' },
);
serviceTube.connector('mount', { type: 'frame', origin: { kind: 'vec3', value: tubeAt } });

arm.mate('base-yaw', 'base-frame.yaw', 'yaw-stage.yaw', 'revolute', { pose: yawDeg, limitsDeg: [-70, 70] });
arm.mate('tilt-pitch', 'yaw-stage.tilt', 'tilt-head.tilt', 'revolute', { pose: tiltDeg, limitsDeg: [-10, 35] });
arm.mate('tilt-servo-fix', 'yaw-stage.tilt-servo-mount', 'tilt-servo.mount', 'fastened');
arm.mate('tube-fix', 'tilt-head.tube-mount', 'service-tube.mount', 'fastened');

arm.mechanicalJoint('yaw-drive', {
  mate: 'base-yaw', actuator: 'base-frame', shaft: 'base-frame', supports: ['base-frame'], output: 'yaw-stage',
  requiredSupport: { kind: 'bearing', around: 'base-frame.yaw', supports: ['base-frame'], minBearingLengthMm: 18 },
});
arm.mechanicalJoint('tilt-drive', {
  mate: 'tilt-pitch', actuator: 'tilt-servo', shaft: 'yaw-stage', supports: ['yaw-stage'], output: 'tilt-head',
  requiredSupport: { kind: 'hinge-bracket', around: 'yaw-stage.tilt', supports: ['yaw-stage'], minBearingLengthMm: 28 },
});

// After evaluate_script ok + mechanism=real: open_in_studio, then
// capture_animation({ file }) with pose-interference verification on.
// Report collisions even if a video is written. Do not claim a flexible hose.

return arm.solvedModel({}, {
  ignore: [
    ['base-frame', 'yaw-stage'], ['yaw-stage', 'tilt-head'], ['yaw-stage', 'tilt-servo'],
    ['tilt-servo', 'tilt-head'], ['tilt-head', 'service-tube'], ['yaw-stage', 'service-tube'],
    ['base-frame', 'service-tube'], ['tilt-servo', 'service-tube'],
  ],
});

Build half a symmetric part then mirror to complete it

recipe · The part is symmetric across a cardinal plane; build only one half and call mirror to produce the complete symmetric part.

mirror · symmetry · boolean · symmetric part · build half then mirror · reflect across a plane

Recipe source

Recipe code
const half = box(20, 30, 10);
return half.mirror('yz');

Open-chain multi-body mechanism with real machine-element proportions

recipe · Prompt asks for a real / complex robot arm, cobot, open-chain mechanism, or multi-body machine with joints. Bodies must read as plates, towers, yokes — not stick figures. Reuse supported-arm lessons: connectors + revolute mates with limits, jointSupport / mechanicalJoint intent, grounded base-frame, and a tendon or mechanicalJoint so gravity-hold passes. If you add animationView, its tracks must name live param() declarations in the same script — do not drop those param() calls when rewriting (animation.param.unknown). Prefer design_loop until review is green.

assembly · connector · mate · revolute · joint · kinematic · plate · parameter · robot arm · cobot arm · open-chain mechanism · multi-body mechanism · real proportions · machine elements not sticks · bearing towers · yaw turret · supported joints · production robot arm · complex assembly ChatGPT

Recipe source

Recipe code
const shoulderDeg = param('shoulderDeg', 25, { min: -20, max: 70 });

const arm = assembly('supported-shoulder');

// Base: thick desk plate + two cylindrical bearing towers (machine element).
const base = arm.part(
  'base-frame',
  box(120, 100, 8, true)
    .translate(0, 0, 4)
    // BREP cylinder(h,r) sits on z=0..h — start at z=0 so towers fuse into the plate.
    .union(cylinder(56, 10).translate(0, 40, 0))
    .union(cylinder(56, 10).translate(0, -40, 0)),
);
base.connector('shoulder', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 56] },
  axis: [0, 1, 0],
});
base.connector('servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: [-40, 0, 8] },
});

// Seated shoulder servo — fastened to the base so the joint is actively driven.
// Park the servo on the rear of the desk plate — clear of the hinge pin/cheeks.
const servo = arm.part(
  'shoulder-servo',
  box(34, 28, 22, true).translate(-40, 0, 19).color('actuator'),
);
servo.connector('mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: [-40, 0, 8] },
});

// Upper link: beam + yoke cheeks + hinge pin that stick out past the towers.
const upperLen = 110;
const pin = cylinder(100, 4).rotate([1, 0, 0], 90).translate(0, 50, 0);
const upper = arm.part(
  'upper-link',
  // Beam spans X; cheeks overlap beam in X and Y so the part is one solid.
  box(upperLen - 24, 14, 12, true)
    .translate(upperLen / 2, 0, 0)
    .union(box(36, 8, 28, true).translate(8, 10, 0))
    .union(box(36, 8, 28, true).translate(8, -10, 0))
    .union(pin)
    .union(box(28, 8, 22, true).translate(upperLen - 8, 10, 0))
    .union(box(28, 8, 22, true).translate(upperLen - 8, -10, 0)),
);
upper.connector('shoulder', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 0] },
  axis: [0, 1, 0],
});

arm.mate('servo-mount', 'base-frame.servo-mount', 'shoulder-servo.mount', 'fastened');
arm.mate('shoulder-pitch', 'base-frame.shoulder', 'upper-link.shoulder', 'revolute', {
  pose: shoulderDeg,
  limitsDeg: [-20, 70],
});

// Driven hinge contract: towers + pin are the support; servo is the actuator.
arm.mechanicalJoint('shoulder-drive', {
  mate: 'shoulder-pitch',
  actuator: 'shoulder-servo',
  shaft: 'base-frame',
  supports: ['base-frame'],
  output: 'upper-link',
  requiredSupport: {
    kind: 'hinge-bracket',
    around: 'base-frame.shoulder',
    supports: ['base-frame'],
    minBearingLengthMm: 28,
  },
});

// Pin/tower contact is intentional hinge bearing — declare it.
return arm.solvedModel({}, { ignore: [['base-frame', 'upper-link']] });

Production 4-DOF robot arm — bridged yokes, clearances, mechanicalJoint chain, reach animation

recipe · Prompt asks for a production multi-body robot arm with at least 4 revolute DOF (base yaw, shoulder, elbow, wrist), seated servos, mechanicalJoint chain, mechanism=real, optional gravity/actuator check, and animationView reach cycle. CRITICAL: fuse every yoke / servo-shelf / cheek into its parent link with .union() so each part is ONE solid load path — floating yoke fragments fail design_loop / review_cad (floating-body geometry gate + attachment-plausibility). Pitch joints are joint.clevis with the link tube seated in the tongue and the parent collar stopped just short of that disk. Yaw is a spigot in a bored collar (sub-millimetre running fit, no shared volume). Prefer design_loop until green. See also multi-body-mechanism-real-proportions and examples/robot-arm/compact- supported-arm.kcad.ts.

assembly · connector · mate · revolute · joint · kinematic · plate · parameter · actuator · 4-DOF robot arm · four axis industrial arm · base yaw shoulder elbow wrist · multi-dof robot arm · bridged yoke load path · seated servo mechanicalJoint · yaw pedestal clearance · reach cycle animationView · production cobot arm mechanism=real · bridge yoke into parent link

Recipe source

Recipe code
// Production 4-DOF open-chain arm. Every yoke, cheek, and servo shelf is
// .union()'d into its parent link so each part is one solid load path.
// KEEP param() beside animationView — tracks must name those live params.
//
// Joint hardware: pitch joints are joint.clevis (fork sandwiches a round
// tongue, capped pin through both ears). Each link tube starts inside its
// tongue so the spar is seated in the hub. Parent yokes bridge the fork
// back into the tube — sub-millimetre running clearance, not a multi-mm air
// gap. Yaw is a spigot inside a bored collar (0.28 mm radial, ~0.4 mm axial)
// so the turret sits on the pedestal without a shared volume. Servo bottoms
// are 0.2 mm above their shelves. No joint.scissorLift, no closed-loop FK.

const baseYawDeg = param('baseYawDeg', 20, { min: -90, max: 90 });
const shoulderDeg = param('shoulderDeg', 25, { min: -15, max: 55 });
const elbowDeg = param('elbowDeg', -40, { min: -85, max: 70 });
const wristDeg = param('wristDeg', 15, { min: -90, max: 90 });
animationView({
  name: 'reach cycle',
  tracks: [
    { param: 'baseYawDeg', keys: [{ atMs: 0, value: -30 }, { atMs: 1200, value: 40, ease: 'easeInOut' }, { atMs: 2400, value: -30, ease: 'easeInOut' }] },
    { param: 'shoulderDeg', keys: [{ atMs: 0, value: 15 }, { atMs: 1200, value: 45, ease: 'easeInOut' }, { atMs: 2400, value: 15, ease: 'easeInOut' }] },
    { param: 'elbowDeg', keys: [{ atMs: 0, value: -30 }, { atMs: 1200, value: -60, ease: 'easeInOut' }, { atMs: 2400, value: -30, ease: 'easeInOut' }] },
    { param: 'wristDeg', keys: [{ atMs: 0, value: 0 }, { atMs: 1200, value: 35, ease: 'easeInOut' }, { atMs: 2400, value: 0, ease: 'easeInOut' }] },
  ],
  fps: 24,
});

const YAW_Z = 54;
const SHOULDER_Z = 36;
const upperLen = 112;
const foreLen = 90;
const wristLen = 44;
const yawBoreR = 16.28;

const pinMat = { baseColor: '#1c2228', metalness: 0.94, roughness: 0.22 };

function clevisStyle(knuckleR, tongueY, plateT, pinR) {
  return {
    knuckleR,
    tongueY,
    forkGapY: tongueY + 0.8,
    plateT,
    pinR,
    pinCapR: pinR + 2.4,
    pinCapThickness: pinR + 2.2,
    holeClearance: 0.15,
    pinMaterial: pinMat,
  };
}

const shoulderStyle = clevisStyle(14, 16, 5.4, 4.2);
const elbowStyle = clevisStyle(12, 13, 4.8, 3.6);
const wristStyle = clevisStyle(10, 11, 4.2, 3.1);
const shoulderLimits = [-15, 55];
const elbowLimits = [-85, 70];
const wristLimits = [-90, 90];

function xTube(x0, x1, r) {
  return cylinder(x1 - x0, r).rotate([0, 1, 0], 90).translate(x0, 0, 0);
}

function yCyl(len, r, cx, cz) {
  return cylinder(len, r).rotate([1, 0, 0], 90).translate(cx, len / 2, cz);
}

function capFaceY(style) {
  // Outer axial face of the clevis cap (pin axis is Y). pinCapR is radial.
  const shaftLen = style.forkGapY + 2 * style.plateT;
  return shaftLen / 2 + style.pinCapThickness - 0.5;
}

function parentYoke(pivotX, style, tubeR) {
  const plateOffset = style.forkGapY / 2 + style.plateT / 2;
  // Stop 0.35 mm before the tongue disk so the parent never enters it.
  const stop = pivotX - style.knuckleR - 0.35;
  const collar = xTube(stop - 18, stop, tubeR + 1.8);
  const cheek = (ySign) => yCyl(style.plateT, style.knuckleR, pivotX, 0)
    .translate(0, ySign * plateOffset, 0);
  const spineZ = Math.max(tubeR * 1.2, style.knuckleR * 0.72);
  const spine = (ySign) => box(style.knuckleR + 12, style.plateT, spineZ, false).translate(
    stop - 10,
    ySign * plateOffset - style.plateT / 2,
    -spineZ / 2,
  );
  return collar.union(cheek(1)).union(cheek(-1)).union(spine(1)).union(spine(-1));
}

const HORN_Z = 8;

function servoPad(mount, bodyW, bodyD, yInner) {
  const zTop = mount[2] - 0.2;
  const yEnd = mount[1] + bodyD / 2 + 4;
  const xHalf = bodyW / 2 + 8;
  return box(xHalf * 2, yEnd - yInner, 5, false).translate(mount[0] - xHalf, yInner, zTop - 5);
}

function dropGusset(pivotX, knuckleR, tubeR, shelfTop) {
  const x1 = pivotX - knuckleR - 1.2;
  const x0 = x1 - 18;
  const z1 = -tubeR + 2.2;
  const z0 = shelfTop - 4;
  return box(x1 - x0, tubeR + 6, z1 - z0, false).translate(x0, -2, z0);
}

function servoSolid(w, d, h) {
  const body = box(w, d, h, true).translate(0, 0, h / 2);
  const ear = box(w + 14, 3.6, 2.8, true).translate(0, 0, h - 0.4);
  const screw = (x) => cylinder(h + 1.7, 1.45).translate(x, 0, -0.12).finish('steel');
  // Side output at pin height. The horn stops 0.6 mm short of the cap face
  // and stays above the mounting pad (pad top is 0.2 mm below the body).
  const stub = cylinder(4.2, 2.3).rotate([1, 0, 0], 90).translate(0, -d / 2 + 0.5, HORN_Z).finish('steel');
  const horn = cylinder(1.6, 5.4).rotate([1, 0, 0], 90).translate(0, -d / 2 - 2.6, HORN_Z).finish('abs', { color: '#c5ced6' });
  return body.union(ear).union(screw((w + 9) / 2)).union(screw(-(w + 9) / 2)).union(stub).union(horn);
}

const upperTubeR = shoulderStyle.tongueY / 2 - 0.45;
const foreTubeR = elbowStyle.tongueY / 2 - 0.45;
const wristTubeR = wristStyle.tongueY / 2 - 0.4;

const foot = (x, y) => cylinder(3.2, 5.2).translate(x, y, 6.2);
const baseSolid = box(152, 116, 8, true).translate(0, 0, 4)
  .union(cylinder(34, 20).translate(0, 0, 8))
  .union(cylinder(4, 32).translate(0, 0, 42))
  .union(cylinder(16, 16).translate(0, 0, 46))
  .union(foot(58, 42)).union(foot(58, -42)).union(foot(-58, 42)).union(foot(-58, -42))
  .union(box(42, 34, 30, true).translate(-54, 0, 20).finish('abs', { color: '#243140' }))
  .union(cylinder(18, 4.2).rotate([0, 1, 0], 90).translate(-36, 0, 30).finish('steel'));

const shoulderPlateY = shoulderStyle.forkGapY / 2 + shoulderStyle.plateT / 2;
const mastTop = SHOULDER_Z - shoulderStyle.knuckleR - 0.4;
const shoulderWeb = (ySign) => box(16, shoulderStyle.plateT, 22, true).translate(
  0,
  ySign * shoulderPlateY,
  SHOULDER_Z - 6,
);
const shoulderServoD = 18;
const shoulderMount = [0, capFaceY(shoulderStyle) + shoulderServoD / 2 + 4.8, SHOULDER_Z - HORN_Z];
const turretSolid = cylinder(17.75, 23).translate(0, 0, -7.55)
  .subtract(cylinder(16.7, yawBoreR).translate(0, 0, -8.4))
  .union(cylinder(mastTop - 8.5, 17).translate(0, 0, 8.5))
  .union(shoulderWeb(1))
  .union(shoulderWeb(-1))
  .union(servoPad(shoulderMount, 32, shoulderServoD, shoulderStyle.tongueY / 2 + 0.5));

const elbowServoD = 16;
const wristServoD = 14;
const elbowMount = [upperLen, capFaceY(elbowStyle) + elbowServoD / 2 + 4.8, -HORN_Z];
const wristMount = [foreLen, capFaceY(wristStyle) + wristServoD / 2 + 4.8, -HORN_Z];
const upperRaw = xTube(shoulderStyle.pinR + 0.55, upperLen - elbowStyle.knuckleR - 0.15, upperTubeR)
  .union(parentYoke(upperLen, elbowStyle, upperTubeR))
  .union(servoPad(elbowMount, 28, elbowServoD, elbowStyle.tongueY / 2 + 0.5))
  .union(dropGusset(upperLen, elbowStyle.knuckleR, upperTubeR, elbowMount[2] - 0.2));
const foreRaw = xTube(elbowStyle.pinR + 0.55, foreLen - wristStyle.knuckleR - 0.15, foreTubeR)
  .union(parentYoke(foreLen, wristStyle, foreTubeR))
  .union(servoPad(wristMount, 24, wristServoD, wristStyle.tongueY / 2 + 0.5))
  .union(dropGusset(foreLen, wristStyle.knuckleR, foreTubeR, wristMount[2] - 0.2));
const wristRaw = xTube(wristStyle.pinR + 0.55, wristLen, wristTubeR)
  .union(xTube(wristLen - 2, wristLen + 9, 11.5));

// Pin axis is -Y so a positive pitch angle lifts the link. The reach-cycle
// keys are positive; +Y would drive the arm down through the pedestal.
const wristJoint = joint.clevis({
  parentBody: foreRaw,
  childBody: wristRaw,
  axis: [0, -1, 0],
  pivotParent: [foreLen, 0, 0],
  pivotChild: [0, 0, 0],
  limitsDeg: wristLimits,
  liftDir: [1, 0, 0],
  liftPivot: false,
  style: wristStyle,
});
const elbowJoint = joint.clevis({
  parentBody: upperRaw,
  childBody: wristJoint.parentGeometry,
  axis: [0, -1, 0],
  pivotParent: [upperLen, 0, 0],
  pivotChild: [0, 0, 0],
  limitsDeg: elbowLimits,
  liftDir: [1, 0, 0],
  liftPivot: false,
  style: elbowStyle,
});
const shoulderJoint = joint.clevis({
  parentBody: turretSolid,
  childBody: elbowJoint.parentGeometry,
  axis: [0, -1, 0],
  pivotParent: [0, 0, SHOULDER_Z],
  pivotChild: [0, 0, 0],
  limitsDeg: shoulderLimits,
  liftDir: [0, 0, 1],
  liftPivot: false,
  style: shoulderStyle,
});

const arm = assembly('multi-dof-4axis');

const base = arm.part('base-frame', baseSolid, { material: 'mild-steel' });
base.connector('yaw', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, YAW_Z] },
  axis: [0, 0, 1],
  jointClearanceRadius: yawBoreR,
});

const turret = arm.part('yaw-turret', shoulderJoint.parentGeometry, { material: 'aluminum-6061' });
turret.connector('yaw', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 0] },
  axis: [0, 0, 1],
  jointClearanceRadius: yawBoreR,
});
turret.connector('shoulder', {
  type: 'axis',
  origin: { kind: 'vec3', value: shoulderJoint.parentConnector.origin },
  axis: shoulderJoint.parentConnector.axis,
  jointClearanceRadius: shoulderJoint.parentConnector.clearanceRadius,
});
turret.connector('shoulder-servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: shoulderMount },
});

const shoulderServo = arm.part(
  'shoulder-servo',
  servoSolid(32, shoulderServoD, 30).translate(shoulderMount[0], shoulderMount[1], shoulderMount[2]),
  { material: 'abs' },
);
shoulderServo.connector('mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: shoulderMount },
});

const upper = arm.part('upper-link', shoulderJoint.childGeometry, { material: 'aluminum-6061' });
upper.connector('shoulder', {
  type: 'axis',
  origin: { kind: 'vec3', value: shoulderJoint.childConnector.origin },
  axis: shoulderJoint.childConnector.axis,
  jointClearanceRadius: shoulderJoint.childConnector.clearanceRadius,
});
upper.connector('elbow', {
  type: 'axis',
  origin: { kind: 'vec3', value: elbowJoint.parentConnector.origin },
  axis: elbowJoint.parentConnector.axis,
  jointClearanceRadius: elbowJoint.parentConnector.clearanceRadius,
});
upper.connector('elbow-servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: elbowMount },
});

const elbowServo = arm.part('elbow-servo', servoSolid(28, elbowServoD, 26).translate(elbowMount[0], elbowMount[1], elbowMount[2]), { material: 'abs' });
elbowServo.connector('mount', { type: 'frame', origin: { kind: 'vec3', value: elbowMount } });

const forearm = arm.part('forearm-link', elbowJoint.childGeometry, { material: 'aluminum-6061' });
forearm.connector('elbow', {
  type: 'axis',
  origin: { kind: 'vec3', value: elbowJoint.childConnector.origin },
  axis: elbowJoint.childConnector.axis,
  jointClearanceRadius: elbowJoint.childConnector.clearanceRadius,
});
forearm.connector('wrist', {
  type: 'axis',
  origin: { kind: 'vec3', value: wristJoint.parentConnector.origin },
  axis: wristJoint.parentConnector.axis,
  jointClearanceRadius: wristJoint.parentConnector.clearanceRadius,
});
forearm.connector('wrist-servo-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: wristMount },
});

const wristServo = arm.part('wrist-servo', servoSolid(24, wristServoD, 22).translate(wristMount[0], wristMount[1], wristMount[2]), { material: 'abs' });
wristServo.connector('mount', { type: 'frame', origin: { kind: 'vec3', value: wristMount } });

const wrist = arm.part('wrist-link', wristJoint.childGeometry, { material: 'aluminum-6061' });
wrist.connector('proximal', {
  type: 'axis',
  origin: { kind: 'vec3', value: wristJoint.childConnector.origin },
  axis: wristJoint.childConnector.axis,
  jointClearanceRadius: wristJoint.childConnector.clearanceRadius,
});

arm.mate('base-yaw', 'base-frame.yaw', 'yaw-turret.yaw', 'revolute', { pose: baseYawDeg, limitsDeg: [-90, 90] });
arm.mate('shoulder-pitch', 'yaw-turret.shoulder', 'upper-link.shoulder', 'revolute', { pose: shoulderDeg, limitsDeg: shoulderLimits });
arm.mate('elbow-pitch', 'upper-link.elbow', 'forearm-link.elbow', 'revolute', { pose: elbowDeg, limitsDeg: elbowLimits });
arm.mate('wrist-pitch', 'forearm-link.wrist', 'wrist-link.proximal', 'revolute', { pose: wristDeg, limitsDeg: wristLimits });
arm.mate('shoulder-servo-fix', 'yaw-turret.shoulder-servo-mount', 'shoulder-servo.mount', 'fastened');
arm.mate('elbow-servo-fix', 'upper-link.elbow-servo-mount', 'elbow-servo.mount', 'fastened');
arm.mate('wrist-servo-fix', 'forearm-link.wrist-servo-mount', 'wrist-servo.mount', 'fastened');

arm.mechanicalJoint('yaw-drive', {
  mate: 'base-yaw', actuator: 'base-frame', shaft: 'base-frame', supports: ['base-frame'], output: 'yaw-turret',
  requiredSupport: { kind: 'bearing', around: 'base-frame.yaw', supports: ['base-frame'], minBearingLengthMm: 20 },
});
arm.mechanicalJoint('shoulder-drive', {
  mate: 'shoulder-pitch', actuator: 'shoulder-servo', shaft: 'yaw-turret', supports: ['yaw-turret'], output: 'upper-link',
  requiredSupport: { kind: 'hinge-bracket', around: 'yaw-turret.shoulder', supports: ['yaw-turret'], minBearingLengthMm: 28 },
});
arm.mechanicalJoint('elbow-drive', {
  mate: 'elbow-pitch', actuator: 'elbow-servo', shaft: 'upper-link', supports: ['upper-link'], output: 'forearm-link',
  requiredSupport: { kind: 'hinge-bracket', around: 'upper-link.elbow', supports: ['upper-link'], minBearingLengthMm: 24 },
});
arm.mechanicalJoint('wrist-drive', {
  mate: 'wrist-pitch', actuator: 'wrist-servo', shaft: 'forearm-link', supports: ['forearm-link'], output: 'wrist-link',
  requiredSupport: { kind: 'hinge-bracket', around: 'forearm-link.wrist', supports: ['forearm-link'], minBearingLengthMm: 20 },
});

// Optional gravity/actuator check (uncomment when validating torque margins).
// checkStaticHold reads arm.revolute actuators, not mates — add those
// revolutes before enabling it. This snippet stays mate-driven.
// const hold = await kinematic.checkStaticHold(arm, { minTorqueMarginPct: 15 });

return arm.solvedModel({});

Multi-feature machined housing with bosses, pockets, bearing seats, fillets

recipe · Prompt asks for a real / production / complex mechanical housing, bearing housing, gearbox-ish enclosure, or multi-feature machined part with walls, fillets, bosses, pockets, and hole patterns. Prefer this over union-of-stacked-primitives — Adam-level bar requires manufacturing-intent BREP, not a toy stack of boxes.

shell · hole · pocket · fillet · plate · parameter · manufacturing · subtract · primitive · machined housing · bearing housing · gearbox housing · multi-feature enclosure · bearing seat bore · mounting boss · blind pocket · hole pattern · production enclosure · real mechanical part · manufacturing intent walls fillets · complex housing not stacked boxes

Recipe source

Recipe code
const L = param('length', 120, { min: 60, max: 240 });
const W = param('width', 80, { min: 40, max: 160 });
const H = param('height', 50, { min: 25, max: 100 });
const wall = param('wall', 4, { min: 2, max: 10 });
const seatR = param('bearingSeatR', 12, { min: 5, max: 30 });

// Solid blank, then cavity so walls remain (manufacturing intent).
let body = box(L, W, H);
body = body.subtract(
  box(L.subtract(wall.multiply(2)), W.subtract(wall.multiply(2)), H.subtract(wall))
    .translate(wall, wall, wall),
);

// Floor bosses inside the cavity, then through-floor taps.
for (const [x, y] of [[16, 16], [104, 16], [16, 64], [104, 64]] as const) {
  body = body.union(cylinder(8, 8).translate(x, y, wall));
}
body = body.subtract(
  cylinder(H.add(2), 2.6)
    .translate(16, 16, -1)
    .patternGrid({
      x: { count: 2, direction: [1, 0, 0], spacing: 88 },
      y: { count: 2, direction: [0, 1, 0], spacing: 48 },
    }),
);

// Bearing seat: through-Y bore. rotate(axis, degrees) — after +90° about X,
// cylinder height runs −Y, so anchor at +Y past the far wall.
body = body.subtract(
  cylinder(W.add(2), seatR)
    .rotate([1, 0, 0], 90)
    .translate(L.divide(2), W.add(1), H.divide(2)),
);

return body.fillet(1.5, { parallel: [0, 0, 1] });

NEMA-17 style mounting plate with four-bolt pattern and fastened motor

recipe · Prompt asks for a NEMA mounting plate, stepper motor mount, or motor flange plate with the standard four-bolt pattern (±15.5 mm, Ø3.2 clearance). Prefer catalog `lib.standard.nema17()` when the parts catalog is available; this snippet uses a BREP stand-in motor so evaluate stays offline/CI-green. Declare matching `bolt-holes-N` frames on the plate (`.holes()` auto-connectors stay on the holes feature id) and mate with the 4-arg form.

plate · hole · assembly · mate · connector · catalog · fasteners · motor · nema · parameter · NEMA 17 mounting plate · NEMA stepper motor mount · four-bolt motor face pattern · catalog nema17 fastened to plate · motor flange mounting holes 31 mm · production motor plate not hand-drawn bolt circle

Recipe source

Recipe code
const arm = assembly('nema-mount');
const plateShape = box(80, 80, 5, true).holes('top', {
  positions: [
    { u: -15.5, v: -15.5 }, { u: 15.5, v: -15.5 },
    { u: -15.5, v: 15.5 },  { u: 15.5, v: 15.5 },
  ],
  diameter: 3.2,
  depth: 'through',
});
const plate = arm.part('plate', plateShape, { material: 'aluminum' });
const offs = [[-15.5, -15.5], [15.5, -15.5], [-15.5, 15.5], [15.5, 15.5]] as const;
offs.forEach(([u, v], i) => {
  plate.connector(`bolt-holes-${i + 1}`, {
    type: 'frame',
    origin: { kind: 'vec3', value: [u, v, 2.5] },
  });
});

// Offline stand-in for lib.standard.nema17() — swap to catalog motor when
// assets/parts (or remote catalog) is available in the agent runtime.
const motorBody = box(42, 42, 40, true)
  .union(cylinder(22, 2.5).translate(0, 0, 20));
const motor = arm.part('motor', motorBody.translate(0, 0, 25).color('actuator'));
offs.forEach(([u, v], i) => {
  motor.connector(`bolt-holes-${i + 1}`, {
    type: 'frame',
    origin: { kind: 'vec3', value: [u, v, 5] },
  });
});
arm.mate('b1', 'motor.bolt-holes-1', 'plate.bolt-holes-1', 'fastened');
arm.mate('b2', 'motor.bolt-holes-2', 'plate.bolt-holes-2', 'fastened');
arm.mate('b3', 'motor.bolt-holes-3', 'plate.bolt-holes-3', 'fastened');
arm.mate('b4', 'motor.bolt-holes-4', 'plate.bolt-holes-4', 'fastened');
return arm.model();

NEMA plate, motor, shaft, bearing, and spur pair as one drive stack

recipe · Prompt asks for one manufacturable drive stack: a NEMA-style mounting plate, a motor stand-in, two shafts in bearing seats, and an involute spur pair under the plate. Prefer catalog nema17 and bearing608 when those parts are available; this script uses BREP stand-ins so evaluate stays offline. Spur flanks are a static geometric mesh at m(z1+z2)/2 — not tooth-contact dynamics, not coupleMates, not a gear-pair transmission. Do not animate the pair. Do not export USD Isaac: the cylindrical drive-shaft mate fails with export.usd.joint-unsupported. The idler shaft is fastened. Only one of the four bolt-hole frames is mated — four fastened mates on that pair plus the cylindrical mate are a loop the solver refuses. After a full evaluate_script, inspect BOM and export a PDF or SVG drawing. Datums are annotation, not a certification. Report FEA UNVERIFIED unless CalculiX and gmsh are present. Prefer design_loop until green.

plate · hole · assembly · mate · connector · motor · nema · shaft · bearing · gear · involute · material · manufacturing · datum · NEMA shaft spur drive stack · motor plate bearing pinion manufacturable · stepper stand-in involute pair under plate · cylindrical shaft bearing no USD Isaac · geometric mesh not tooth contact dynamics · BOM aluminum steel nylon drive stack · do not animate spur pair

Recipe source

Recipe code
// NEMA plate + motor stand-in + shaft/bearing + involute spur pair.
// Geometric mesh at center distance m(z1+z2)/2 with a half-pitch phase
// on the even gear. NOT tooth-contact dynamics, NOT a coupled-mate ratio,
// NOT a gear-pair transmission. Do NOT animate — rotation will not keep mesh.
// Motor is a BREP stand-in, not lib.standard.nema17(). Bearings are BREP
// stand-ins, not lib.standard.bearing608().
// The drive shaft uses one cylindrical mate (USD Isaac refuses it:
// export.usd.joint-unsupported). The idler shaft is fastened. A second
// cylindrical mate is still a tree, but four bolt mates plus any
// cylindrical mate is a loop the solver refuses at 0 iterations.
// Bearing OD is 14 mm so the races clear a 16 mm gear center distance.
// After full evaluate_script: inspect({ of: 'bom' }) or export bom-csv /
// bom-json, then pdf-drawing / svg-drawing. Datums are annotation intent,
// not a GD&T certification. FEA only when CalculiX + gmsh are present.

const moduleMm = 1;
const z1 = 16;
const z2 = 16;
const face = 5;
const backlash = 0.12;
const cd = (moduleMm * (z1 + z2)) / 2;
const boreD = 8.2;
// Hero camera is on -Y, 26° above the plate. A pair under the deck is
// invisible in that shot, so the mesh, races, and shafts stand on the
// plate in front of the motor.
const gearY = -36;
const gearZ = 12.8;
const bearingZ = 6.2;

let plate = extrudeRoundedRect(108, 96, 8, 7).translate(10, -14, 0);
plate = plate.subtract(extrudeRoundedRect(44, 44, 3, 0.7).translate(0, 0, 6.35));
for (const x of [0, cd]) {
  // Seat opens upward. Race OD is 14 mm; 0.25 mm radial air, no volume overlap.
  plate = plate.subtract(cylinder(2.6, 7.25).translate(x, gearY, 5.0));
  plate = plate.subtract(cylinder(12, 4.7).translate(x, gearY, -1));
}
const offs = [[-15.5, -15.5], [15.5, -15.5], [-15.5, 15.5], [15.5, 15.5]] as const;
// NEMA 17 bolt holes as real hole features with a counterbore, drilled from
// the recess floor (z = 6.35), whose centre is the origin: u = x, v = y.
plate = plate.holes({ byNormal: 'Z', atX: 0, atY: 0, atZ: 6.35 }, {
  positions: offs.map(([x, y]) => ({ u: x, v: y })),
  diameter: 3.6,
  depth: 7,
  counterbore: { diameter: 6.4, depth: 1.95 },
});
plate = plate
  .datum('A', { atZ: 0 })
  .datum('B', { atX: -44 })
  .datum('C', { atY: -62 })
  .finish('anodized', { color: '#c5d0d8' });

function bearingAt(x: number) {
  // OD 14 mm so the two races clear a 16 mm center distance (r=11 would intersect).
  const outer = path()
    .moveTo(5.55, 0).lineTo(7, 0).lineTo(7, 5.5).lineTo(5.55, 5.5)
    .lineTo(5.55, 3.7).lineTo(6.2, 2.75).lineTo(5.55, 1.8).lineTo(5.55, 0)
    .close().revolve();
  const inner = path()
    .moveTo(4.2, 0).lineTo(5.15, 0).lineTo(5.15, 1.8).lineTo(4.55, 2.75)
    .lineTo(5.15, 3.7).lineTo(5.15, 5.5).lineTo(4.2, 5.5).lineTo(4.2, 0)
    .close().revolve();
  let balls = sphere(0.75).translate(5.35, 0, 2.75);
  for (let i = 1; i < 6; i += 1) {
    const a = (2 * Math.PI * i) / 6;
    balls = balls.union(sphere(0.75).translate(5.35 * Math.cos(a), 5.35 * Math.sin(a), 2.75));
  }
  return outer.union(inner, balls).translate(x, gearY, bearingZ).finish('steel');
}
function shaftAt(x: number) {
  // Journal through the plate and the race, shoulder in the air gap,
  // nose through the gear, collar above the mesh.
  return path()
    .moveTo(0, 1.5).lineTo(3.35, 1.5).lineTo(3.35, 11.85)
    .lineTo(4.5, 11.85).lineTo(4.5, 12.65)
    .lineTo(3.45, 12.65).lineTo(3.45, 18.1)
    .lineTo(5.15, 18.1).lineTo(5.15, 20)
    .lineTo(3.05, 20).lineTo(3.05, 23.5)
    .lineTo(0, 23.5)
    .close().revolve().translate(x, gearY, 0).finish('stainless');
}
function gearAt(x: number, phase: number, color: string) {
  return spurGear({ module: moduleMm, teeth: z1, faceWidth: face, bore: boreD, backlash })
    .rotateZ(phase)
    .translate(x, gearY, gearZ)
    .finish('abs', { color });
}

const flangeZ = 7.3;
let motorBody = extrudeRoundedRect(42.3, 42.3, 2, 5).translate(0, 0, flangeZ);
motorBody = motorBody
  .union(extrudeRoundedRect(36, 36, 2.4, 28).translate(0, 0, flangeZ + 4.6))
  // Side ribs bite 0.1 mm into the 36 mm core (at 19.4 they stood 0.3 mm
  // off it: union.disconnected).
  .union(extrudeRoundedRect(22, 2.2, 0.6, 24).translate(0, 19.0, flangeZ + 6))
  .union(extrudeRoundedRect(22, 2.2, 0.6, 24).translate(0, -19.0, flangeZ + 6))
  .union(extrudeRoundedRect(2.2, 22, 0.6, 24).translate(19.0, 0, flangeZ + 6))
  .union(extrudeRoundedRect(2.2, 22, 0.6, 24).translate(-19.0, 0, flangeZ + 6))
  .union(cylinder(2.6, 15).translate(0, 0, flangeZ + 32.4))
  .union(cylinder(2.2, 8).translate(0, 0, flangeZ + 34.8))
  .finish('anodized-black');

const arm = assembly('nema-spur-stack');
const pl = arm.part('plate', plate, { material: 'aluminum-6061' });
const mo = arm.part('motor', motorBody, { material: 'aluminum' });
const db = arm.part('drive-bearing', bearingAt(0), { material: 'mild-steel' });
const ib = arm.part('idler-bearing', bearingAt(cd), { material: 'mild-steel' });
const ds = arm.part('drive-shaft', shaftAt(0), { material: 'mild-steel' });
const idler = arm.part('idler-shaft', shaftAt(cd), { material: 'mild-steel' });
const pinion = arm.part('pinion', gearAt(0, 0, '#f3efe4'), { material: 'nylon' });
const gear = arm.part('gear', gearAt(cd, z2 % 2 === 0 ? 180 / z2 : 0, '#d5e2ea'), { material: 'nylon' });

function axisConn(part, name, x, z, clearance) {
  part.connector(name, {
    type: 'axis',
    origin: { kind: 'vec3', value: [x, gearY, z] },
    axis: [0, 0, 1],
    ...(clearance ? { jointClearanceRadius: clearance } : {}),
  });
}
axisConn(db, 'bore', 0, 9, 4.3);
axisConn(ds, 'axis', 0, 9);

const gearMidZ = gearZ + face / 2;
pinion.connector('bore', {
  type: 'frame', origin: { kind: 'vec3', value: [0, gearY, gearMidZ] }, jointClearanceRadius: 4.3,
});
ds.connector('pinion', { type: 'frame', origin: { kind: 'vec3', value: [0, gearY, gearMidZ] } });
gear.connector('bore', {
  type: 'frame', origin: { kind: 'vec3', value: [cd, gearY, gearMidZ] }, jointClearanceRadius: 4.3,
});
idler.connector('gear', { type: 'frame', origin: { kind: 'vec3', value: [cd, gearY, gearMidZ] } });
db.connector('seat', {
  type: 'frame', origin: { kind: 'vec3', value: [0, gearY, 9] }, jointClearanceRadius: 4.3,
});
pl.connector('drive-seat', {
  type: 'frame', origin: { kind: 'vec3', value: [0, gearY, 9] }, jointClearanceRadius: 7.3,
});
ib.connector('seat', {
  type: 'frame', origin: { kind: 'vec3', value: [cd, gearY, 9] }, jointClearanceRadius: 4.3,
});
pl.connector('idler-seat', {
  type: 'frame', origin: { kind: 'vec3', value: [cd, gearY, 9] }, jointClearanceRadius: 7.3,
});

offs.forEach(([u, v], i) => {
  const at = [u, v, 8] as [number, number, number];
  pl.connector(`bolt-holes-${i + 1}`, { type: 'frame', origin: { kind: 'vec3', value: at } });
  mo.connector(`bolt-holes-${i + 1}`, { type: 'frame', origin: { kind: 'vec3', value: at } });
});
// One fastened mate, not four. Four mates between the same pair are a
// loop; a loop that also contains the cylindrical shaft mate is refused
// up front (assembly.solver.did-not-converge, 0 iterations).
arm.mate('motor-face', 'motor.bolt-holes-1', 'plate.bolt-holes-1', 'fastened');

arm.mate('drive-seat', 'drive-bearing.seat', 'plate.drive-seat', 'fastened');
arm.mate('idler-seat', 'idler-bearing.seat', 'plate.idler-seat', 'fastened');
arm.mate('drive-spin', 'drive-shaft.axis', 'drive-bearing.bore', 'cylindrical');
idler.connector('seat', { type: 'frame', origin: { kind: 'vec3', value: [cd, gearY, 9] } });
ib.connector('shaft-seat', {
  type: 'frame', origin: { kind: 'vec3', value: [cd, gearY, 9] }, jointClearanceRadius: 4.3,
});
arm.mate('idler-seat-shaft', 'idler-shaft.seat', 'idler-bearing.shaft-seat', 'fastened');
arm.mate('pinion-hub', 'pinion.bore', 'drive-shaft.pinion', 'fastened');
arm.mate('gear-hub', 'gear.bore', 'idler-shaft.gear', 'fastened');

return arm.solvedModel({});

Network-style body via multi-patch sew (not >2 loft rails)

recipe · You need a network-style organic body / multi-guide fairing and loft accepts at most 2 rails. Do NOT pass >2 rails to Sketch.loft — panel with surfaceFromCurves / surfaceFromBoundary, sew closed, thicken, then G2 fillet. Pair with automotive-body-envelope for car proportions and verify body-likeness before publish.

shell · continuity · sweep · fillet · network surface body · more than 2 loft rails · multi patch sew body · surfaceFromBoundary panels · surfaceFromCurves sew thicken · MakePipeShell rail limit · G2 fillet body panels

Recipe source

Recipe code
const a = nurbsSurface({
  controls: [
    [[0, 0, 0], [40, 0, 4], [80, 0, 0]],
    [[0, 30, 4], [40, 30, 12], [80, 30, 4]],
    [[0, 60, 0], [40, 60, 4], [80, 60, 0]],
  ],
  degree: { u: 2, v: 2 },
});
const b = nurbsSurface({
  controls: [
    [[80, 0, 0], [120, 0, 2], [160, 0, 0]],
    [[80, 30, 4], [120, 30, 10], [160, 30, 2]],
    [[80, 60, 0], [120, 60, 2], [160, 60, 0]],
  ],
  degree: { u: 2, v: 2 },
});
// sew() returns a Shape. For solids, thicken each Surface first, then boolean/union,
// or sew a closed shell of Surfaces with requireClosed: true.
return sew([a, b]);

Build an L-bracket as two non-overlapping plates

recipe · You're building two perpendicular plates joined at a right angle; both plates have the same thickness; volumes must not overlap at the joint.

boolean · union · plate · bracket · stacking · L-shape from two plates · perpendicular plates joined at a right angle · L bracket without volume overlap

Recipe source

Recipe code
const t = 8;
const horiz = box(40, 30, t);
const vert = box(t, 30, 40).translate(0, 0, t);
return horiz.union(vert);

Parametric bolt-hole skeleton

recipe · You want a compact bolt-hole part with an editable bolt-diameter parameter that can be changed later.

parameter · bolt · hole · subtract · parametric bolt hole with editable diameter · symbolic bolt diameter parameter · edit hole clearance after build

Recipe source

Recipe code
const boltDia = param('boltDia', 5, { min: 3, max: 10 });

return box(24, 24, 10)
  .hole('top', { u: 0, v: 0, diameter: boltDia, depth: 'through', name: 'centerBolt' });

2D quintic Hermite G2 transition between two path runs

recipe · You're authoring a freeform 2D outline that should transition from one prescribed point + tangent (+ curvature) to another with G2 continuity (no visible curvature crease where adjacent neighbours meet). Drop a single .hermiteG2(a, b) call into the chain; a.point must match the current pen position. Tangent magnitude is the first derivative (typical ~ chord length, NOT unit length).

primitive · sketch · extrude · path hermiteG2 · quintic hermite 2D · g2-continuous path transition · eyewear bridge to brow blend

Recipe source

Recipe code
const profile = path()
  .moveTo(-20, 0)
  .lineTo(-10, 0)
  .hermiteG2(
    { point: [-10, 0], tangent: [0,  8], curvature: [0, 0] },
    { point: [ 10, 0], tangent: [0, -8], curvature: [0, 0] },
  )
  .lineTo(20, 0)
  .lineTo(20, -6)
  .lineTo(-20, -6)
  .close();

return profile.extrude(2.5);

Explicit 2D B-spline segment via path().nurbsSegment() control net

recipe · You have an explicit B-spline control polygon (programmatic generation, round-tripping from external CAD, when precise shape control beats waypoint convenience) and want a 2D path segment authored from the control net directly. The first control point must match the current pen position within 1e-6 mm; the pen ends at the last control point.

primitive · sketch · extrude · path nurbsSegment · explicit b-spline control net · 2D nurbs sketch segment · programmatic profile generation

Recipe source

Recipe code
const profile = path()
  .moveTo(0, 0)
  .nurbsSegment(
    [[0, 0], [5, 10], [15, 10], [20, 0]],
    { degree: 3 },
  )
  .lineTo(20, -5)
  .lineTo(0, -5)
  .close();

return profile.extrude(3);

Organic 2D outline via path().spline() N-waypoint B-spline

recipe · You need a freeform 2D outline (eyewear brow, ergonomic grip silhouette, sneaker midsole) authored as a sequence of measured waypoints, and arc primitives + smoothSpline are too rigid. Drop a single .spline([...]) call into the path() chain after moveTo; the path interpolates through every waypoint at degree 3.

primitive · sketch · extrude · path spline · organic 2D outline · freeform sketch profile · eyewear brow profile · b-spline waypoint interpolation

Recipe source

Recipe code
const brow = path()
  .moveTo(-60, 0)
  .spline([
    [-60, 0],
    [-30, 6],
    [  0, 9],
    [ 30, 6],
    [ 60, 0],
  ])
  .lineTo(60, -8)
  .lineTo(-60, -8)
  .close();

return brow.extrude(4);

Swept tube along 3D waypoints with corner bend radius

recipe · You need a pipe or tube that follows 3D waypoints, with a specified bend radius at each corner, by sweeping a circular profile along a rail whose sharp corners are replaced by sampled arcs of that radius.

sweep · tube · route · sketch · pipe route through 3D waypoints · bend radius at each corner · swept circular tube profile · filleted polyline rail then Sketch.sweep

Recipe source

Recipe code
const bendR = 8;
const tubeR = 3;
// Sweep profiles live in XY, so the first rail segment must run along +Z.
// Local waypoints below map to world (0,0,0)→(50,0,0)→(50,0,40)→(50,30,40)
// after a -90° rotation about Y.
const waypoints = [
  [0, 0, 0],
  [0, 0, 50],
  [-40, 0, 50],
  [-40, 30, 50],
];

function sub(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }
function add(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }
function scale(a, s) { return [a[0] * s, a[1] * s, a[2] * s]; }
function dot(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
function cross(a, b) {
  return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
}
function len(a) { return Math.hypot(a[0], a[1], a[2]); }
function norm(a) {
  const n = len(a);
  return n < 1e-9 ? [0, 0, 0] : scale(a, 1 / n);
}

function filletedRail(pts, R, arcSamples) {
  const rail = [];
  rail.push(pts[0]);
  for (let i = 1; i < pts.length - 1; i += 1) {
    const A = pts[i - 1];
    const P = pts[i];
    const B = pts[i + 1];
    const uIn = norm(sub(P, A));
    const uOut = norm(sub(B, P));
    const alpha = Math.acos(Math.min(1, Math.max(-1, dot(uIn, uOut))));
    if (alpha < 1e-6) continue;
    const d = R / Math.tan(alpha / 2);
    const T1 = sub(P, scale(uIn, d));
    const cr = cross(uIn, uOut);
    const perp = norm(cross(cr, uIn));
    const center = add(T1, scale(perp, R));
    // Arc from T1 to T2 about `center` in the (uIn, uOut) plane.
    const v0 = sub(T1, center);
    const axis = norm(cr);
    for (let s = 0; s <= arcSamples; s += 1) {
      const t = s / arcSamples;
      const ang = alpha * t;
      const c = Math.cos(ang);
      const si = Math.sin(ang);
      const k = dot(axis, v0);
      const term = add(
        add(scale(v0, c), scale(cross(axis, v0), si)),
        scale(axis, k * (1 - c)),
      );
      rail.push(add(center, term));
    }
  }
  rail.push(pts[pts.length - 1]);
  return rail;
}

const sampled = filletedRail(waypoints, bendR, 10);
const rail = [sampled[0]];
for (let i = 1; i < sampled.length; i += 1) {
  const p = sampled[i];
  const q = rail[rail.length - 1];
  if (Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]) > 1e-4) rail.push(p);
}
const profile = path().circle(0, 0, tubeR, 24);
return profile.sweep(rail, { spine: 'polyline', transitionMode: 'round' }).rotateY(-90);

Shop-release planetary gearbox — ring, carrier, machined housing, cover

recipe · Prompt asks for a shop-release planetary gearbox, not a bare gear set: a machined housing with a ring pocket, floor, mounting holes and a fillet; a carrier with planet pins; a sun shaft; a cover; named materials. Teeth stay true involutes placed so the pitch circles meet. That is a static assembled pose — not tooth-contact dynamics, backlash simulation, or torque through the flanks. Do not coupleMates, do not declare a gear-pair transmission, and do not animate rotation; the kernel will not keep teeth in mesh. Every mate in this script is fastened so usd-isaac is not blocked by a cylindrical joint. After a full evaluate_script, inspect BOM and export a PDF or SVG drawing. Datums are annotation, not a certification. Report FEA UNVERIFIED unless CalculiX and gmsh are present. Prefer design_loop until green.

gear · involute · planetary · assembly · connector · mate · material · manufacturing · fillet · hole · bom · datum · shop-release planetary gearbox stage · machined gearbox case carrier cover · sun planets ring fastened assembled pose · BOM aluminum nylon steel planetary · involute flanks not tooth contact dynamics · no coupleMates no gear-pair transmission · do not animate planetary rotation · usd-isaac stays fastened only

Recipe source

Recipe code
// Shop-release planetary gearbox. Involute flanks meet at the pitch
// circles (Zring = Zsun + 2·Zplanet). Static assembled pose ONLY — not
// tooth-contact dynamics, not a torque path, not a coupled-mate ratio,
// not a gear-pair transmission. Do NOT animate rotation; teeth will not stay
// in mesh. All mates are fastened (a cylindrical sun shaft would fail
// usd-isaac with export.usd.joint-unsupported).
// After full evaluate_script: inspect({ of: 'bom' }) or export bom-csv /
// bom-json, then pdf-drawing / svg-drawing. Datums are annotation intent,
// not a GD&T certification. FEA only when CalculiX + gmsh are present —
// otherwise report UNVERIFIED. No run_fea in this script.
// Catalog metadata is absent — these are fabricated stand-ins.

const moduleMm = 1;
const zSun = 12;
const zPlanet = 12;
const zRing = zSun + 2 * zPlanet; // 36
const face = 5;
const backlash = 0.08;
const planetCount = 3;
const rim = 3;

planetaryToothCompatibility({ sunTeeth: zSun, planetTeeth: zPlanet, ringTeeth: zRing });

const carrierR = (moduleMm * (zSun + zPlanet)) / 2; // m(Zsun+Zplanet)/2
// internalSpurGear outer radius = pitch + 1.25·module + rimThickness.
const ringOuterR = (moduleMm * zRing) / 2 + 1.25 * moduleMm + rim;
const seatR = ringOuterR + 0.4;

const wallR = seatR + 4.4;
const flangeR = wallR + 8;
// Turned cup: mounting flange, bore for the ring, shaft clearance through the floor.
let housing = path()
  .moveTo(3.4, -12)
  .lineTo(flangeR, -12)
  .lineTo(flangeR, -8)
  .lineTo(wallR, -8)
  .lineTo(wallR, 8)
  .lineTo(seatR, 8)
  .lineTo(seatR, -7.5)
  .lineTo(3.4, -7.5)
  .close()
  .revolve();
const boltR = flangeR - 4;
// Six flange bolt holes as real hole features, entered from the bottom face
// (centre at the origin): u = x, v = y.
housing = housing.holes({ byNormal: '-Z', atX: 0, atY: 0, atZ: -12 }, {
  positions: Array.from({ length: 6 }, (_, i) => {
    const a = Math.PI / 6 + (2 * Math.PI * i) / 6;
    return { u: boltR * Math.cos(a), v: boltR * Math.sin(a) };
  }),
  diameter: 3.1,
  depth: 'through',
});
housing = housing
  .datum('A', { atZ: -12 })
  .datum('B', { atX: -flangeR })
  .datum('C', { atY: 0 })
  .tolerance({
    type: 'position', value: 0.2, modifier: '⌀', datums: ['A', 'B', 'C'],
    edge: { ofCurveType: 'CIRCLE', near: [boltR * Math.cos(Math.PI / 6), boltR * Math.sin(Math.PI / 6), -12] },
  })
  .finish('anodized', { color: '#3e6f86' });

let cover = path()
  .moveTo(17.5, 8.15)
  .lineTo(flangeR - 0.4, 8.15)
  .lineTo(flangeR - 0.4, 11.2)
  .lineTo(17.5, 11.2)
  .close()
  .revolve();
for (let i = 0; i < 6; i += 1) {
  const a = (Math.PI / 6) + (2 * Math.PI * i) / 6;
  cover = cover.union(
    cylinder(1.8, 2.5).translate(boltR * Math.cos(a), boltR * Math.sin(a), 11.2),
  );
}
cover = cover.finish('anodized-black');
const ring = internalSpurGear({
  module: moduleMm, teeth: zRing, faceWidth: face, backlash, rimThickness: rim,
}).finish('steel');
const sun = spurGear({ module: moduleMm, teeth: zSun, faceWidth: face, bore: 5, backlash })
  .finish('abs', { color: '#f3efe4' });
const sunShaft = path()
  .moveTo(0, -8).lineTo(2.15, -8).lineTo(2.15, -1.5)
  .lineTo(2.65, -1.5).lineTo(2.65, -0.25).lineTo(2.05, -0.25).lineTo(2.05, 3.1).lineTo(0, 3.1)
  .close().revolve().finish('stainless');

let carrier = cylinder(4.2, carrierR - 0.4).translate(0, 0, -7.1);
carrier = carrier.union(cylinder(3.2, 5.2).translate(0, 0, -7.4));
for (let i = 0; i < planetCount; i += 1) {
  const a = (2 * Math.PI * i) / planetCount;
  const px = carrierR * Math.cos(a);
  const py = carrierR * Math.sin(a);
  carrier = carrier
    .union(cylinder(3.4, 3.3).translate(px, py, -7.1))
    .union(cylinder(11, 1.55).translate(px, py, -7));
}
carrier = carrier.subtract(cylinder(8, 2.65).translate(0, 0, -9)).finish('aluminium-brushed');

const arm = assembly('planetary-gearbox-shop');
const h = arm.part('housing', housing, { material: 'aluminum-6061' });
const c = arm.part('cover', cover, { material: 'aluminum-6061' });
const rg = arm.part('ring', ring, { material: 'mild-steel' });
const su = arm.part('sun', sun, { material: 'nylon' });
const sh = arm.part('sun-shaft', sunShaft, { material: 'mild-steel' });
const ca = arm.part('carrier', carrier, { material: 'aluminum-6061' });

const planets = [];
for (let i = 0; i < planetCount; i += 1) {
  const ang = (360 / planetCount) * i;
  const rad = (ang * Math.PI) / 180;
  const planet = spurGear({ module: moduleMm, teeth: zPlanet, faceWidth: face, bore: 4, backlash })
    .rotateZ((zPlanet % 2 === 0 ? 180 / zPlanet : 0) + ang)
    .translate(carrierR * Math.cos(rad), carrierR * Math.sin(rad), 0)
    .finish('abs', { color: '#f6f1e4' });
  planets.push(arm.part(`planet-${i + 1}`, planet, { material: 'nylon' }));
}

h.connector('lid', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, 8] } });
c.connector('seat', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, 8] } });
h.connector('ring-seat', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, 2] } });
rg.connector('rim', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, 2] } });
h.connector('carrier-seat', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, -5] } });
ca.connector('hub', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, -5] } });
// Frames, not axes: a fastened mate rejects an axis/axis pair.
sh.connector('seat', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, -2] } });
ca.connector('shaft-bore', {
  type: 'frame', origin: { kind: 'vec3', value: [0, 0, -2] }, jointClearanceRadius: 2.7,
});
su.connector('bore', {
  type: 'frame', origin: { kind: 'vec3', value: [0, 0, 2] }, jointClearanceRadius: 2.6,
});
sh.connector('sun-hub', { type: 'frame', origin: { kind: 'vec3', value: [0, 0, 2] } });

arm.mate('cover', 'cover.seat', 'housing.lid', 'fastened');
arm.mate('ring-seat', 'ring.rim', 'housing.ring-seat', 'fastened');
arm.mate('carrier-seat', 'carrier.hub', 'housing.carrier-seat', 'fastened');
arm.mate('shaft-seat', 'sun-shaft.seat', 'carrier.shaft-bore', 'fastened');
arm.mate('sun-hub', 'sun.bore', 'sun-shaft.sun-hub', 'fastened');

for (let i = 0; i < planetCount; i += 1) {
  const a = (2 * Math.PI * i) / planetCount;
  const at = [carrierR * Math.cos(a), carrierR * Math.sin(a), 2] as [number, number, number];
  ca.connector(`pin-${i + 1}`, { type: 'frame', origin: { kind: 'vec3', value: at } });
  planets[i].connector('bore', {
    type: 'frame', origin: { kind: 'vec3', value: at }, jointClearanceRadius: 2.15,
  });
  arm.mate(`planet-${i + 1}`, `planet-${i + 1}.bore`, `carrier.pin-${i + 1}`, 'fastened');
}

return arm.solvedModel({});

Planetary stage with sun, planets, and internalSpurGear ring

recipe · You need a coaxial planetary gear stage (sun + planet(s) + internal ring) with true involute teeth, not cylinders. Use `spurGear` for the sun and planets, `internalSpurGear` (aliases `ringGear` / `internalGear`) for the annulus, and `planetaryToothCompatibility({ sunTeeth, planetTeeth, ringTeeth })` so Zring = Zsun + 2·Zplanet. Carrier pins sit at centre distance m(Zsun+Zplanet)/2.

gear · involute · planetary · assembly · parameter · planetary gear stage sun planets ring carrier · internalSpurGear ringGear internalGear annulus · Zring equals Zsun plus 2 Zplanet pitch compatibility · planetaryToothCompatibility coaxial reduction gearbox · involute internal spur ring meshing planets

Recipe source

Recipe code
const moduleMm = 1;
const zSun = 18;
const zPlanet = 18;
const zRing = zSun + 2 * zPlanet; // 54 — pitch circles meet
const faceWidth = 8;
const backlash = 0.05;
const planetCount = 3;

// Throws feature.invalid-args unless Zring === Zsun + 2·Zplanet.
planetaryToothCompatibility({ sunTeeth: zSun, planetTeeth: zPlanet, ringTeeth: zRing });

const sunPitchR = (moduleMm * zSun) / 2;
const planetPitchR = (moduleMm * zPlanet) / 2;
const carrierR = sunPitchR + planetPitchR; // m(Zsun+Zplanet)/2

const sun = spurGear({ module: moduleMm, teeth: zSun, faceWidth, bore: 6, backlash });
// Prefer internalSpurGear; ringGear / internalGear are aliases (same solid).
const ring = internalSpurGear({
  module: moduleMm,
  teeth: zRing,
  faceWidth,
  backlash,
  rimThickness: 4,
});

const stage = assembly('planetary-stage');
stage.part('ring', ring);
stage.part('sun', sun);

for (let i = 0; i < planetCount; i += 1) {
  const ang = (360 / planetCount) * i;
  const rad = (ang * Math.PI) / 180;
  // Half-pitch when even, then spin about own centre, then place on carrier circle.
  const planet = spurGear({ module: moduleMm, teeth: zPlanet, faceWidth, bore: 4, backlash })
    .rotateZ((zPlanet % 2 === 0 ? 180 / zPlanet : 0) + ang)
    .translate(carrierR * Math.cos(rad), carrierR * Math.sin(rad), 0);
  stage.part(`planet-${i + 1}`, planet);
}

return stage.model();

Share-ready hero image and 360° turntable of a finished design

recipe · The design is done and you need pictures to share it — a gallery tile, a README image, a social post or a product page — not an engineering review. Colour every part (or give it a material) in the script, then call render_preview({ file, preset: 'publish' }) for ONE studio 'hero' PNG (3/4 front-right, soft contact shadow, white backdrop, 1600×1200; width and height up to 2048; background: 'transparent' for a PNG with alpha, or 'light' / 'dark' / '#rrggbb'). For a seamless 360° loop call capture_animation({ file, turntable: true, output_path: 'x.gif' }) (or .mp4; frames_dir keeps alpha). The camera centres and frames the silhouette itself, so do not add setCameraTarget for these shots. The output is deterministic: re-rendering the same file gives the same pixels. Use the default preset (no preset) while you still iterate on geometry.

render · turntable · assembly · hero image · product shot · studio render · publish preset · transparent background png · gallery tile · readme image · social post · turntable gif · 360 orbit video · render_preview preset publish · capture_animation turntable

Recipe source

Recipe code
// Colour / material per part — the publish preset renders them as authored.
const arm = assembly('desk-lamp');
arm.part('base', cylinder(8, 40).color('#1f2937'));
arm.part('post', cylinder(120, 5).color('#d1d5db'), { at: [0, 0, 8] });
arm.part('shade', cylinder(30, 22).color('#f59e0b'), { at: [0, 0, 128] });
return arm.model();

// Then, from the MCP client:
//   render_preview({ file: 'lamp.kcad.ts', preset: 'publish' })
//     → images: [{ name: 'hero', path: '.../hero.png' }]
//   render_preview({ file: 'lamp.kcad.ts', preset: 'publish', background: 'transparent', width: 2048, height: 2048 })
//   capture_animation({ file: 'lamp.kcad.ts', turntable: true, output_path: 'lamp.gif', width: 640, height: 640, fps: 20 })

// Or from the CLI (same preset, same renderer):
//   kernelcad render lamp.kcad.ts --preset publish -o lamp.png
//   kernelcad render lamp.kcad.ts --preset publish --background transparent -o lamp-alpha.png
//   kernelcad animate lamp.kcad.ts lamp.gif --turntable --width 640 --height 640 --fps 20

Rebuild an editable model from an STL, OBJ or 3MF mesh

recipe · You were handed an STL, OBJ or 3MF of a mostly prismatic mechanical component and need named params and real features, not the faceted solid lib.fromSTL gives you. Call mesh_to_features({ file, out }) (CLI: kernelcad reconstruct scan.stl -o rebuilt.kcad.ts). It measures the mesh, emits a script like the one below — a revolve or extruded profile, drilled and counterbored bores placed on face queries relative to the entry face centroid — then evaluates that script and compares it with the mesh. Rely on the dimensions only when fidelity.verdict is faithful; otherwise read unmatchedRegions and the ledger. Snapped values are ledger facts whose ids are the param names, so resolve_assumptions turns a correction straight into set_param overrides.

mesh · reverse-engineering · parameter · stl to editable model · scan to parametric model · mesh to feature tree · mesh_to_features · kernelcad reconstruct · reverse engineer a mesh · volume iou fidelity verdict · turned flange with bolt circle

Recipe source

Recipe code
// What mesh_to_features emits for a turned flange: params measured from the
// mesh, the revolved (radius, height) profile, then the bores. A hole's u/v
// are measured from the centroid of the face it enters, as that face exists
// at that point in the chain.
const step1Radius = param('step1Radius', 30, { min: 7.5, max: 120 });
const step1Height = param('step1Height', 8, { min: 2, max: 32 });
const step2Radius = param('step2Radius', 15, { min: 3.75, max: 60 });
const step2Height = param('step2Height', 20, { min: 5, max: 80 });
const hole1Diameter = param('hole1Diameter', 12, { min: 3, max: 48 });
const holes2Diameter = param('holes2Diameter', 6, { min: 1.5, max: 24 });

const z2 = step1Height.add(step2Height);
const body = path()
  .moveTo(0, 0)
  .lineTo(step1Radius, 0)
  .lineTo(step1Radius, step1Height)
  .lineTo(step2Radius, step1Height)
  .lineTo(step2Radius, z2)
  .lineTo(0, z2)
  .close()
  .revolve();

return body
  .hole({ byNormal: 'Z', atZ: 28 }, { u: 0, v: 0, diameter: hole1Diameter, depth: 'through', name: 'hole1' })
  .holes({ byNormal: 'Z', atZ: 8 }, {
    positions: [
      { u: 22, v: 0 },
      { u: 0, v: 22 },
      { u: -22, v: 0 },
      { u: 0, v: -22 },
    ],
    diameter: holes2Diameter,
    depth: 'through',
    name: 'holes2',
  });

Rebuild an editable model from an engineering drawing PDF

recipe · You were handed a vector PDF of an engineering drawing (orthographic views, lettered dimensions, a title block) and need an editable model, not a trace of a picture. Call drawing_to_cad({ path, out }): it reads the linework and dimension text, identifies the views by projection alignment, lets stated dimensions win over drawn lengths, extrudes the silhouette of the one non-rectangular view (or revolves a turned shaft), cuts ⌀ callouts along the axis of the view they are drawn in, and writes a script like the one below plus a ledger. Branch on fidelity.verdict, then settle the open ledger facts (mirrored positions, measured values, disagreements) with resolve_assumptions before relying on the model. A scanned page fails with reference.drawing.raster-only; use trace_from_image for that.

drawing · dimension · assumption-ledger · drawing_to_cad · engineering drawing pdf to cad · vector pdf print to parametric script · orthographic views to model · title block scale and projection angle · dimension disagrees with linework · rebuild from a 2d print

Recipe source

Recipe code
// Emitted by drawing_to_cad for examples/drawing-to-cad/motor-mount-bracket.pdf.
// Every param is a stated dimension; the second base hole and the wall hole's
// Y are expressions, because the drawing places them by symmetry (open
// `symmetry:` facts in the ledger).
const width = param('width', 70, { description: 'X span — dimension 70 (front view)' });
const leftThickness = param('leftThickness', 5, { description: 'X span — dimension 5 (front view)' });
const hole1X = param('hole1X', 45, { description: 'X span — dimension 45 (top view)' });
const depth = param('depth', 50, { description: 'Y span — dimension 50 (top view)' });
const hole1Y = param('hole1Y', 12, { description: 'Y span — dimension 12 (top view)' });
const height = param('height', 45, { description: 'Z span — dimension 45 (front view)' });
const bottomThickness = param('bottomThickness', 5, { description: 'Z span — dimension 5 (front view)' });
const hole3Z = param('hole3Z', 27, { description: 'Z span — dimension 27 (left view)' });
const holeDia1 = param('holeDia1', 5.5, { description: 'hole diameter — callout 2× ⌀5.5 THRU' });
const holeDia2 = param('holeDia2', 12, { description: 'hole diameter — callout ⌀12 THRU' });

let part = path()
  .moveTo(0, 0)
  .lineTo(width, 0)
  .lineTo(width, bottomThickness)
  .lineTo(leftThickness, bottomThickness)
  .lineTo(leftThickness, height)
  .lineTo(0, height)
  .lineTo(0, 0)
  .close()
  .extrude(depth)
  .rotateX(90)
  .translate(0, depth, 0);

part = part.subtract(cylinder(bottomThickness.add(2), holeDia1.divide(2)).translate(hole1X, hole1Y, -1));
part = part.subtract(cylinder(bottomThickness.add(2), holeDia1.divide(2)).translate(hole1X, depth.subtract(hole1Y), -1));
part = part.subtract(cylinder(leftThickness.add(2), holeDia2.divide(2)).rotateY(90).translate(-1, depth.divide(2), hole3Z));

return part;

Repair a degenerate 90-degree draft angle

recipe · evaluate_script reported feature.draft.failed because the draft angle was geometrically degenerate (typically 90 degrees) and you want a mould-release angle derived from the lowerer, not a guess.

draft · feature.draft.failed · draft angle 90 · mould release taper · repair_script draft

Recipe source

Recipe code
return box(10, 10, 10).draft(8, { face: 'front' });

Repair an emboss whose glyphs sit over a void

recipe · evaluate_script reported feature.emboss-text.boolean-noop because the glyph prism was parked over a void and you want the UV anchor moved onto remaining solid.

emboss · feature.emboss-text.boolean-noop · emboss over a void · engrave missed the body · repair_script emboss anchor

Recipe source

Recipe code
return box(40, 40, 6).hole('top', { u: 0, v: 0, diameter: 20, depth: 'through' })
  .embossText({ textContent: 'HI', face: 'top', size: 4, depth: -0.5, anchorU: 0.2, anchorV: 0.2 });

Repair a failing fillet whose radius is larger than the edges

recipe · evaluate_script reported feature.edge-feature.short-edges-skipped (every target edge is shorter than twice the radius) and you want a bounded fix instead of guessing a new radius.

fillet · chamfer · edge-features · repair failing fillet · fix a fillet that failed · fillet radius too large · fillet skipped all edges · chamfer distance too large · short-edges-skipped · repair_script · why_did_this_fail

Recipe source

Recipe code
const blockLength = 30;
const blockWidth = 20;
const blockThickness = 8;

// Fillet ceiling = shortest adjacent edge / 2 = blockThickness / 2 = 4 mm.
const edgeRound = 3.2;

const block = box(blockLength, blockWidth, blockThickness);

return block.fillet(edgeRound);

Repair a revolve profile that crossed the rotation axis

recipe · evaluate_script reported feature.revolve.crosses-axis (a path x-coordinate was negative) and you want the profile clamped onto x >= 0 instead of rewriting the sketch by hand.

revolve · sketch · revolve profile crosses axis · feature.revolve.crosses-axis · clamp path x to zero · repair_script revolve

Recipe source

Recipe code
return path()
  .moveTo(0, 0)
  .lineTo(10, 0)
  .lineTo(10, 5)
  .lineTo(0, 5)
  .close()
  .revolve();

Repair a shell thicker than half the solid

recipe · evaluate_script reported feature.kernel-failed on a .shell() whose wall is thicker than half the thinnest bounding-box dimension, and you want a feasible thickness derived from that bbox.

shell · feature.kernel-failed shell · shell thickness too large · hollow wall exceeds half bbox · repair_script shell

Recipe source

Recipe code
return box(10, 10, 10).shell(4, { face: 'top' });

Repair a tangentCircle whose radius cannot sit outside existing circles

recipe · evaluate_script reported sketch.tangency.no-solution because a tangentCircle radius was smaller than (centre-distance - r1 - r2) / 2 between existing circles, and you want the radius derived from that gap.

tangency · sketch · sketch.tangency.no-solution · tangentCircle radius too small · external tangent radius floor · repair_script tangency

Recipe source

Recipe code
return path().tangentCircle(
  [{ kind: 'circle', center: [0, 0], radius: 10 },
   { kind: 'circle', center: [30, 0], radius: 4 }],
  { radius: 8 },
).extrude(10);

Ground a photo trace before building geometry from it

recipe · You want geometry from a photo or picture. Call MCP `trace_from_image` (do not type polygon points by eye), then use this when you want to know which returned waypoints are directly measured vs guessed before you feed them into path().spline() — and how to lock down a real-world scale before committing geometry. `referenceImage(path, { plane, scale })` only overlays the photo in Studio for visual checks; it does not trace.

reference-image · scale-anchor · assumption-ledger · trace_from_image assumption ledger · scale anchor missing after tracing a photo · resolve_assumptions before building from a reference photo · which waypoints are measured vs guessed · trace a shape from a photo or reference image instead of typing polygon points · outline from a picture, logo or sketch image to a profile

Recipe source

Recipe code
// Before this file existed, `trace_from_image` returned a `ledger` alongside
// features/diagnostics. Every fact is classified visible|inferred|assumed|
// missing — never a fabricated confidence number, always echoing the
// backend's own signal. Its `scale` fact came back `missing` (no ruler in
// the photo), so `resolve_assumptions` was called to override it with a
// measurement taken from a known hinge-to-hinge width:
//   resolve_assumptions({
//     ledgerPath: '/abs/path/brow.ledger.json',
//     resolutions: [{ id: 'brow_top', confirm: true }, { id: 'scale', value: 0.185 }],
//   })
// -> paramOverrides = { brow_top: [...waypoints], scale: 0.185 }
// `scale` (mm/px) below is that resolved value — never build geometry from a
// trace while `ledger.unresolvedCount > 0` on a `missing` fact.
const scale = 0.185; // mm per pixel, from resolve_assumptions' paramOverrides.scale
const browPointsPx: [number, number][] = [[10, 0], [40, 8], [70, 4]]; // pixel-space waypoints, resolved
const browPointsMm = browPointsPx.map(([x, y]): [number, number] => [x * scale, y * scale]);

const brow = path()
  .moveTo(browPointsMm[0][0], browPointsMm[0][1])
  .spline(browPointsMm)
  .lineTo(browPointsMm[browPointsMm.length - 1][0], -2)
  .lineTo(browPointsMm[0][0], -2)
  .close();

return brow.extrude(2);

Revolute joint knuckle (clevis + tongue + pin) that never collides

recipe · You are building the physical hinge of a revolute joint (robot-arm elbow, lid, gripper finger). Knuckles built as boxes that straddle the pivot collide at every angle. Split the pivot along the ROTATION AXIS instead: the parent's clevis cheeks and the child's tongue occupy disjoint axial ranges (a gap between them), both are round about the pivot, and a pin on the child runs through clearance bores in the cheeks. Then no angle can make them overlap, and the mechanism gates pass at every limit.

assembly · connector · mate · revolute · hinge · joint · hinge knuckle collides at every angle · clevis and tongue with disjoint axial ranges · pin through clevis bores with clearance · revolute joint limits · robot arm joint knuckle

Recipe source

Recipe code
// Pivot axis = Y. Axial ranges along Y (disjoint = collision-free at any angle):
//   child tongue  y in [-4.5, 4.5]
//   parent cheeks y in [5, 9] and [-9, -5]   (0.5 mm gap each side)
const R = 6;          // knuckle radius around the pivot
const pinR = 2;       // pin radius (part of the child)
const boreR = 2.2;    // cheek bore radius (pin + 0.2 mm clearance)

// Cylinder along Y from y0 to y1, centred on the X/Z point (x, z).
const yCyl = (r, y0, y1, x = 0, z = 0) =>
  cylinder(y1 - y0, r).rotateX(-90).translate(x, y0, z);

// Parent: a bar along -X ending in a clevis. The bridge that joins the two
// cheeks stays outside the tongue's swept circle (x <= -R - 2).
const cheeks = union(
  yCyl(R, 5, 9), box(14, 4, 2 * R).translate(-14, 5, -R),
  yCyl(R, -9, -5), box(14, 4, 2 * R).translate(-14, -9, -R),
).subtract(yCyl(boreR, -10, 10));
const parentBody = union(
  cheeks,
  box(6, 18, 2 * R).translate(-14, -9, -R),     // bridge, x in [-14, -8]
  box(40, 9, 8).translate(-54, -4.5, -4),       // link bar
);

// Child: tongue round about the pivot, bar along +X, pin through the cheeks.
const childBody = union(
  yCyl(R, -4.5, 4.5),
  box(40, 9, 8).translate(0, -4.5, -4),
  yCyl(pinR, -9.5, 9.5),
);

const arm = assembly('revolute-joint-knuckle');
const parent = arm.part('parent', parentBody);
const child = arm.part('child', childBody);
parent.connector('pivot', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 1, 0] });
child.connector('pivot', { type: 'axis', origin: { kind: 'vec3', value: [0, 0, 0] }, axis: [0, 1, 0] });
// Limits stop the child bar before it swings into the bridge behind the pivot.
arm.mate('hinge', 'parent.pivot', 'child.pivot', 'revolute', { limitsDeg: [-100, 100] });

return arm.solvedModel({ hinge: 60 });

Cylindrical wall or ring via path + revolve

recipe · You want a thin cylindrical wall, ring, or tube — author the rectangular profile via path() with the inner radius as the x offset, then call .revolve() to sweep it around Z.

revolve · sketch · primitive · thin cylindrical wall · ring or tube · revolve a rectangle offset from the axis

Recipe source

Recipe code
return path()
  .moveTo(15, 0)
  .lineTo(17, 0)
  .lineTo(17, 20)
  .lineTo(15, 20)
  .close()
  .revolve();

Scissor-lift table — open-chain lift DOF + mid-pose X-links (closed-loop FK unsupported)

recipe · Prompt asks for a scissor-lift / scissor-jack table with base, platform, X-pattern links, mechanism=real, and animationView raising the platform. IMPORTANT: articulated closed-loop FK (true revolute + pin_slot scissor that solves both slider ends from one lift-height DOF) is UNSUPPORTED — it emits assembly.solver.did-not-converge / kinematic.solver.unsupported-config. Do NOT invent joint.scissorLift(...) or close the loop with pin_slot. Use this open-chain production pattern: one prismatic platform DOF (+ mechanicalJoint) driven by platformZ, industrial X-links fused into base-frame at a representative mid pose, animationView raising the platform. Keep the actuator-mount fastener on solid (center pad under the actuator) so evaluate_script mechanism=real — dry-run / cookbook:evaluate alone miss joint-mesh-gap. Prefer design_loop until green. Gallery examples/gallery/scissor-lift.kcad.ts is fastened pose-only (not mechanism-driven).

assembly · connector · mate · prismatic · joint · kinematic · plate · parameter · scissor · lift · scissor lift · scissor-lift table · industrial scissor jack · closed-loop scissor · pin_slot scissor · joint.scissorLift · raising platform animationView · X-pattern lift table · production scissor lift mechanism=real

Recipe source

Recipe code
// Open-chain industrial scissor table. Closed-loop revolute+pin_slot FK is
// unsupported (assembly.solver.did-not-converge). One prismatic DOF raises the
// platform; X-links are fused into base-frame at a mid working pose (ParamRef
// cannot drive Math.sqrt link geometry). Do not invent joint.scissorLift.

const platformZ = param('platformZ', 110, { min: 80, max: 150 });

const linkLen = 160;
const linkW = 10;
const linkT = 8;
const sideY = 28;
const railY = 38;
const baseZ = 10;
// The fused X-pattern is drawn at the rest pose (platformZ = 110): the
// upper bar ends bear on the deck underside (z = 110), they do not pierce
// it. A bar end's top edge sits linkT/2·cos(angle) above its centreline,
// so solve the centreline height for that contact (a few iterations).
const restDeckZ = 110;
let stageH = 100;
for (let i = 0; i < 8; i++) {
  const angle = Math.asin(Math.min(1, stageH / linkLen));
  stageH = restDeckZ - baseZ - (linkT / 2) * Math.cos(angle);
}
const halfSpan = Math.sqrt(Math.max(400, linkLen * linkLen - stageH * stageH)) / 2;
const deckZ = baseZ + stageH;

const arm = assembly('scissor-lift-open-chain');

function linkBar(x1: number, z1: number, x2: number, z2: number, y: number, color: string) {
  const dx = x2 - x1;
  const dz = z2 - z1;
  const len = Math.sqrt(dx * dx + dz * dz);
  const angle = (Math.atan2(dz, dx) * 180) / Math.PI;
  return box(len, linkW, linkT, true)
    .fillet(1)
    .rotate([0, 1, 0], -angle)
    .translate((x1 + x2) / 2, y, (z1 + z2) / 2)
    .color(color);
}

const scissorX = linkBar(-halfSpan, baseZ, halfSpan, deckZ, -sideY, '#f6b23b')
  .union(linkBar(-halfSpan, baseZ, halfSpan, deckZ, sideY, '#f6b23b'))
  .union(linkBar(halfSpan, baseZ, -halfSpan, deckZ, -sideY, '#f0782f'))
  .union(linkBar(halfSpan, baseZ, -halfSpan, deckZ, sideY, '#f0782f'));

// Center actuator pad tops at z=12 so actuator-fix sits on solid (not mid-air
// between the side rails). Pad spans rail-to-rail so it fuses into the frame.
const actuatorPad = box(40, railY * 2, 12, true).translate(0, 0, 6).color('#3b434c');

const base = arm.part(
  'base-frame',
  box(halfSpan * 2 + 50, 10, 10, true)
    .translate(0, -railY, 5)
    .color('#2b3036')
    .union(box(halfSpan * 2 + 50, 10, 10, true).translate(0, railY, 5).color('#2b3036'))
    .union(box(20, railY * 2 + 20, 8, true).translate(-halfSpan - 16, 0, 4).color('#3b434c'))
    .union(box(20, railY * 2 + 20, 8, true).translate(halfSpan + 16, 0, 4).color('#3b434c'))
    .union(actuatorPad)
    .union(scissorX),
);
base.connector('lift-axis', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 0] },
  axis: [0, 0, 1],
});
base.connector('actuator-mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: [0, 0, 12] },
});

const actuator = arm.part(
  'lift-actuator',
  box(28, 22, 18, true).translate(0, 0, 21).color('actuator'),
);
actuator.connector('mount', {
  type: 'frame',
  origin: { kind: 'vec3', value: [0, 0, 12] },
});

const platform = arm.part(
  'platform',
  box(halfSpan * 2 + 56, railY * 2 + 24, 8, true)
    .fillet(2)
    .translate(0, 0, 4)
    .color('#dfe5ea')
    .union(box(halfSpan * 2 + 36, 6, 8, true).translate(0, -railY, -4).color('#aeb7bf'))
    .union(box(halfSpan * 2 + 36, 6, 8, true).translate(0, railY, -4).color('#aeb7bf')),
);
platform.connector('lift-slide', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 0] },
  axis: [0, 0, 1],
});

arm.mate('actuator-fix', 'base-frame.actuator-mount', 'lift-actuator.mount', 'fastened');
arm.mate('platform-lift', 'base-frame.lift-axis', 'platform.lift-slide', 'prismatic', {
  pose: platformZ,
  limitsMm: [80, 150],
});

arm.mechanicalJoint('platform-drive', {
  mate: 'platform-lift',
  actuator: 'lift-actuator',
  shaft: 'base-frame',
  supports: ['base-frame'],
  output: 'platform',
  requiredSupport: {
    kind: 'bracket',
    around: 'base-frame.lift-axis',
    supports: ['base-frame'],
    minBearingLengthMm: 20,
  },
});

animationView({
  name: 'raise platform',
  tracks: [
    {
      param: 'platformZ',
      keys: [
        { atMs: 0, value: 90 },
        { atMs: 1600, value: 140, ease: 'easeInOut' },
        { atMs: 2800, value: 90, ease: 'easeInOut' },
      ],
    },
  ],
  fps: 24,
});

return arm.solvedModel({}, {
  ignore: [['base-frame', 'platform']],
});

Extrude a gasket from a solid cross-section

recipe · You have a 3D part (an imported STEP or a boolean result) and need a 2D profile from its cross-section to extrude a gasket, spacer, or shim — or to re-dimension the section. sectionSketch returns a real Sketch whose arcs stay exact, with holes as inner loops, so one extrude reproduces the cross-section.

sketch · extrude · hole · boolean · plate · subtract · gasket from cross section · section a solid back to a sketch · slice a boolean result into 2D · sectionSketch

Recipe source

Recipe code
// A 60x40x12 plate with two Ø10 through-holes drilled from the top.
const plate = box(60, 40, 12)
  .hole('top', { u: -20, v: 0, diameter: 10, depth: 'through' })
  .hole('top', { u: 20, v: 0, diameter: 10, depth: 'through' });

// Exact planar cross-section at z = 6, expressed in the XY plane's 2D frame.
// The result is a single Sketch: outer boundary first, then the two holes.
const section = await plate.sectionSketch({ plane: 'xy', offset: 6 });

// Extrude the section into a 3 mm gasket that matches the part's outline.
return section.extrude(3);

Linear shaft seated in a deep-groove ball bearing via cylindrical mate

recipe · Prompt asks for a shaft in a bearing, bearing seat with shaft, or rotating shaft on a deep-groove ball bearing. Prefer catalog `lib.fetchPart('shaft-d8-l50')` + `lib.standard.bearing608()` when the parts catalog is available; this snippet uses BREP stand-ins so evaluate stays offline/CI-green. Mate with the 4-arg form `arm.mate(name, 'shaft.axis', 'bearing.inner-bore', 'cylindrical')` — do not Boolean-union them into one solid.

assembly · mate · connector · catalog · shaft · bearing · parameter · shaft and bearing seat · 608 bearing on 8 mm shaft · cylindrical mate shaft axis into bearing bore · linear shaft catalog part · deep-groove ball bearing assembly · production shaft bearing not Boolean fuse

Recipe source

Recipe code
const arm = assembly('shaft-bearing');
// Offline stand-ins for shaft-d8-l50 + bearing-608 (Ø8 bore, ~Ø22 OD × 7 mm).
const shaftShape = cylinder(50, 4);
const bearingShape = cylinder(7, 11).subtract(cylinder(9, 4).translate(0, 0, -1));
const shaft = arm.part('shaft', shaftShape);
const bearing = arm.part('bearing', bearingShape);
shaft.connector('axis', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 25] },
  axis: [0, 0, 1],
});
bearing.connector('inner-bore', {
  type: 'axis',
  origin: { kind: 'vec3', value: [0, 0, 3.5] },
  axis: [0, 0, 1],
});
arm.mate('seat', 'shaft.axis', 'bearing.inner-bore', 'cylindrical');
return arm.model();

Sheet-metal L-bracket from flat blank with one 90-degree bend

recipe · Prompt asks for a folded sheet-metal L-bracket, U-channel, flange, or service panel from a flat blank. Use `sheetMetal(profile, { thickness, kFactor })` then `.bend(...)` — not a solid union of two plates — when fabrication intent is sheet + folds. After evaluate/lower, recover the blank with `flattenPattern()` (MCP `flatten_pattern` / inspect bend-table) for laser/CNC.

sheet-metal · bend · bracket · plate · parameter · manufacturing · sheet metal L-bracket · fold along x bend radius K-factor · flattenPattern flat blank laser · 100x60x2 mm 90 degree fold · sheetMetal bend not solid union plates · service panel flange bend approx · bent sheet metal bracket with flat pattern DXF for laser cutting · folded 5052 aluminium or steel sheet, inner bend radius, bend allowance · do not fake bends by unioning plates

Recipe source

Recipe code
// L-bracket: 100 x 60 x 2 mm, one 90° fold along x=50, inner radius 3 mm.
// flattenPattern() needs a lowered Shape — call it after evaluate via MCP
// flatten_pattern / inspect({ of: 'bend-table' }), not inline in the script body.
const s = path().moveTo(0, 0).lineTo(100, 0).lineTo(100, 60).lineTo(0, 60).close();
const blank = sheetMetal(s, { thickness: 2, kFactor: 0.38 });
return blank.bend({ atX: 50 }, 90, 3);

Export a slicer-ready multi-colour 3MF (parts on the plate, one filament per colour)

recipe · You want a model to open in a desktop slicer ready to print — every assembly member a named object with its colour and material, either packed on the bed at Z=0 without overlap or kept together as one multi-colour object (an inlay), each colour on its own filament slot — instead of a bare STL or already-sliced G-code.

manufacturing · export · printer · assembly · multi colour multi material 3mf for a slicer · open in slicer with objects laid out on the plate · colour and material kept per object in 3mf · two colour keycap inlay print · one filament slot per colour · auto orient largest flat face down · send 3mf project to a bambu lab printer · printer profile bed size for my printer

Recipe source

Recipe code
// Two-colour keycap: the legend is a separate part sitting in a pocket of
// the shell, so the slicer prints it in a second filament. Colours come
// from .color(); the material name ('pla') names the 3MF base material.
const size = param('size', 18, { min: 12, max: 30 });
const height = param('height', 8, { min: 4, max: 15 });
const depth = 1.2;

const legend = box(6, 6, depth).translate(size.divide(2).subtract(3), size.divide(2).subtract(3), height.subtract(depth));
const shell = box(size, size, height).subtract(legend);

const cap = assembly('keycap');
cap.part('shell', shell.color('#202020'), { material: 'pla' });
cap.part('legend', legend.color('#ffffff'), { material: 'pla' });
return cap.model();

// Then, outside the script:
//   export target:'model' format:'3mf' output_path:'keycap.3mf'
//     options:{ format:'3mf', arrange:'assembled', printer:'bambu-a1' }
//   printer picks the bed (A1: 256x256x256 mm) and the slicer family
//   (bambu here; pass slicer to override). List the ids and volumes with
//   `kernelcad print printers` (default 'generic-fdm', 220x220x250 mm).
//   -> one 'keycap' object with parts 'shell' (filament 1, #202020) and
//      'legend' (filament 2, #FFFFFF), dropped to Z=0, centred on the bed.
//   Separate parts instead? arrange:'plate' packs each part on the bed
//   at Z=0 with 5 mm gaps; add orient:true to lay each on its largest
//   flat face. slicer:'prusa' writes that slicer's own volume config;
//   slicer:'generic' (default) writes plain core 3MF only.
//
//   Too much for one bed? The file is still written, and the result
//   carries warn diagnostics: export.3mf.plate-overflow names the parts
//   packed past the bed edge and the footprint the layout needs;
//   export.3mf.exceeds-bed names a part larger than the bed or taller
//   than its build height. Both hints end with the smallest bundled
//   profiles the same layout fits on, e.g.
//   "Fits on: Bambu Lab H2D ('bambu-h2d'), ..." — pass that printer, or
//   export fewer parts per plate.
//
//   Bambu Lab printer over LAN: slice to G-code (format:'gcode'), then
//   send_to_printer gcode_path:'keycap.gcode' model_3mf_path:'keycap.3mf'
//     protocol:'bambu-lan' printer:'bambu-a1' host:'...' access_code:'...' serial:'...'
//   -> uploads keycap.gcode.3mf (this 3MF + Metadata/plate_1.gcode).

SO-ARM100 follower arm — vendor link meshes, STEP jaw, passive horn

recipe · The prompt asks for the real SO-ARM100 / SO-100 follower arm, the kernelcad.com hero arm, or a gripper with the vendor moving jaw — not a tube-and-clevis sketch. execute_cookbook({ id: 'so-arm100' }) evaluates examples/robot-arm/so100/so100-arm.kcad.ts (Apache-2.0 link STLs, the Moving_Jaw STEP solid, and the passive horn on the wrist). One-liner: kernelcad evaluate examples/robot-arm/so100/so100-arm.kcad.ts. Do not redraw it as floating cylinders or the primitive 4-DOF cookbook arm.

assembly · connector · mate · revolute · joint · kinematic · parameter · actuator · SO-ARM100 · SO-100 robot arm · LeRobot follower arm · so100 gripper jaw · real robot arm yellow links black servos · Moving_Jaw passive horn pincer · six axis pan lift elbow wrist roll jaw · execute_cookbook so-arm100

Recipe source

Recipe code
// Not a self-contained snippet. Vendor meshes live next to the example.
// execute_cookbook({ id: 'so-arm100' }) evaluates this file:
//   examples/robot-arm/so100/so100-arm.kcad.ts
// CLI: kernelcad evaluate examples/robot-arm/so100/so100-arm.kcad.ts
//
// Jaw: parts/Moving_Jaw.step (solid, millimetres, same frame as the sim
// STL) baked by the gripper rpy and then the wrist-roll rpy, so the finger
// closes on the fixed jaw. parts/Passive_Horn.step is fastened to the jaw
// mount face (bolt circle at local z = -24). jawDeg -11 is the closed stop.

Check whether an actuator can hold a joint against gravity

recipe · A revolute or prismatic joint drives a downstream mass against gravity (a robot arm shoulder, a lift stage) and you need to know whether the declared actuator torque/force is sufficient — not just whether the mechanism is collision-free. Declare actuator: { torqueNm } (revolute) or actuator: { forceN } (prismatic) on the joint, then call kinematic.checkStaticHold(arm, opts). Real mass properties come from the part's geometry + declared density/material; the worst pose across the joint's declared range is reported alongside the margin. Fires assembly.joint.static-hold.exceeded when the actuator is undersized, assembly.joint.static-hold.margin-low when it clears but under the requested safety margin.

assembly · connector · mate · revolute · hinge · joint · static-hold · actuator · torque · kinematic · will this servo hold the arm up · actuator torque check · static hold · gravitational holding torque · safety margin on a joint · does the motor stall under gravity · checkStaticHold

Recipe source

Recipe code
const arm = assembly('shoulder-hold-check');

const base = arm.part('base', box(40, 40, 40, true));
const link = arm.part(
  'link',
  box(200, 20, 10, true).translate(100, 0, 0),
  { material: 'steel' },
);

arm.revolute('shoulder', base, link, {
  axis: [0, 1, 0],
  origin: [0, 0, 0],
  limitsDeg: [-90, 90],
  actuator: { torqueNm: 1.5 },
});

const hold = await kinematic.checkStaticHold(arm, { minTorqueMarginPct: 20 });
// hold.joints[0] => { jointName: 'shoulder', kind: 'revolute',
//   actuatorCapacity: 1.5, worstRequired: <N*m>, marginPct, worstPose }

return arm.solvedModel({ shoulder: 0 }, { ignore: [['base', 'link']] });

Dense infill only where an FDM print is stressed (FEA-graded infill 3MF)

recipe · A 3D print carries a load and the user wants it strong where it matters and light everywhere else. Declare the load case with shape.feaStudy({ material, fixed, loads }) — fix the mounting holes, load the face the force acts on, force in TOTAL newtons — then export a 3MF with options.infill { fromFea: '<study name>' | true }. The export solves the study, splits the volume into stress bands relative to yield (default < 15 % -> 10 %, 15-40 % -> 25 %, > 40 % -> 60 % gyroid; override with infill.bands [{ name, fromYield, densityPercent }]) and writes one modifier volume per dense band with its own infill density (Orca/Bambu format by default; options.slicer 'prusa' writes PrusaSlicer's format). The result carries infill { bands (volume % per band), saving (filament + time vs uniform infill at the high density), fea (peak stress, safety factor), images { heatmap, bands, cutaway } }. Requires the LOCAL CalculiX + gmsh toolchain (check with fea_summary({})); without it the export fails with fea.solver.unavailable and writes nothing. The two slicer formats are not interchangeable: pick the slicer the file opens in. The FEA treats the print as solid, so the safety factor is an upper bound.

fea · stress · load · printer · export · bracket · infill · weak spots · make it stronger · will it hold · where will it break · stress · lightweight print · save filament · variable infill · infill modifier · Bambu Studio · OrcaSlicer · A1 · P1S · PETG

Recipe source

Recipe code
const wall = param('wall', 6, { min: 4, max: 12 });
const width = 40;
const leg = 70;

// PETG shelf bracket: the wall leg bolts on with two M5 screws, the shelf
// leg carries the load.
// Wall leg with two M5 clearance bores (5.5 mm, through) at 25 and 55 mm
// height (u/v are offsets from the face centre); shelf leg; root fillet.
const wallLeg = box(wall, width, leg)
  .hole('left', { u: 0, v: -10, diameter: 5.5, depth: 'through', name: 'screwLow' })
  .hole('left', { u: 0, v: 20, diameter: 5.5, depth: 'through', name: 'screwHigh' });

const bracket = wallLeg
  .union(box(leg, width, wall))
  .fillet(5, { parallel: [0, 1, 0], concave: true });

bracket.feaStudy({
  name: 'shelf-load',
  material: 'petg',
  // Held by the two screw bores: the only faces fully inside this box.
  fixed: { boundingBoxIn: { xMin: -1, xMax: wall.add(1), yMin: 15, yMax: 25, zMin: 20, zMax: 60 } },
  loads: [{ name: 'shelf', faces: { atZ: wall }, force: [0, 0, -120] }],  // 120 N TOTAL
  meshSize: 2.5,
});

// Then, through MCP:
//   export({ target: 'model', file: 'bracket.kcad.ts', output_path: 'bracket.3mf', format: '3mf',
//            options: { format: '3mf', printer: 'bambu-a1', arrange: 'assembled',
//                       infill: { fromFea: 'shelf-load' } } })
//   -> bands: low 10 % ~65 % of volume, mid 25 % ~27 %, high 60 % ~8 % (both screw
//      bosses, which always print dense, and the wall below the lower bore)
//   -> saving: ~36 % less filament than uniform 60 % (estimate: 21.1 g vs 33.0 g)
//   -> fea: peak ~31 MPa vs 55 MPa yield, safety factor ~1.8, ~4.9 mm deflection;
//      the ~50 MPa at the edge of the lower bore is the clamp singularity, reported
//      as peakAtSupportMPa with a fea.stress.support-singularity warning
// Open bracket.3mf in Bambu Studio / OrcaSlicer: the modifiers show under the
// object with their own infill density. For PrusaSlicer add slicer: 'prusa'
// (modifiers load the same way, as ParameterModifier volumes).
// Sliced for real, 0.2 mm layers, gyroid, same bracket (graded vs uniform 60 %):
//   OrcaSlicer 2.4.2    17.3 g / 1h35m vs 28.2 g / 2h37m  (-38 % filament, -39 % time)
//   PrusaSlicer 2.8.1   23.0 g / 2h17m vs 29.9 g / 2h59m  (-23 % filament, -23 % time)
return bracket;

Plate with a through-hole and a filleted top rim around the hole

recipe · You want a parametric plate, drill a through-hole, and round the rim where the hole meets the top face.

fillet · subtract · hole · plate · parameter · fillet the rim of a through-hole · rounded edge around a circular hole · parametric plate with hole and rim fillet

Recipe source

Recipe code
const plateSize = param('plateSize', 50, { min: 20, max: 200 });
const plateThickness = param('plateThickness', 8, { min: 2, max: 30 });
const holeDia = param('holeDia', 12, { min: 3, max: 30 });
const filletRadius = param('filletRadius', 1.5, { min: 0.2, max: 5 });

return box(plateSize, plateSize, plateThickness)
  .hole('top', { u: 0, v: 0, diameter: holeDia, depth: 'through', name: 'centerHole' })
  .fillet(filletRadius, { face: 'centerHole.wall' });

Compare a plain fillet vs a G2 blend with continuity inspect and zebra

recipe · You filleted or blended a freeform panel and need numbers, not a guess: is the join G0, G1, or G2? inspect({ of: 'continuity' }) samples each shared edge for position gap, normal jump and curvature difference. inspect({ of: 'curvature' }) reports per-face Gaussian and mean curvature (sphere 1/r², cylinder Gaussian 0 and |H|=1/(2r)). render_preview overlay zebra / curvature / continuity is the picture of those numbers.

continuity · G2 blend · G1 fillet · continuity inspect · zebra stripes · curvature inspect · surface quality

Recipe source

Recipe code
const bump = nurbsSurface({
  controls: [
    [[0, 0, 0], [20, 0, 4], [40, 0, 0]],
    [[0, 20, 4], [20, 20, 10], [40, 20, 4]],
    [[0, 40, 0], [20, 40, 4], [40, 40, 0]],
  ],
  degree: { u: 2, v: 2 },
});
const cutter = nurbsSurface({
  controls: [[[20, -5, -10], [20, 45, -10]], [[20, -5, 20], [20, 45, 20]]],
  degree: { u: 1, v: 1 },
});
return sew(bump.split(cutter));

Sweep a param across a tolerance range and re-check the standard gates

recipe · A design has a param() whose real-world value varies (a printed hole that comes out oversized, a clearance gap that shrinks under tolerance) and you need to know whether the mechanism stays buildable across that range, not just at the nominal value. Call kinematic.sweepTolerance({ code|file, params, gates }) with one or more param names as { values: [...] } or { min, max, steps }; it re-evaluates the script per cartesian-product combo (capped at 64) and runs the interference / mounting-hole / joint-axis gates (default on) plus reachability when declared, returning a pass/fail table and the first failing combo per gate.

assembly · connector · mate · revolute · hinge · joint · sweep-tolerance · tolerance · parameter · kinematic · does this design still clear at the worst-case tolerance · sweep a dimension and re-validate · tolerance stackup check · parameter sweep gates · sweepTolerance · find the param value where the design breaks · cartesian product of param values

Recipe source

Recipe code
const result = await kinematic.sweepTolerance({
  code: `
    const arm = assembly('clearance-check');
    const gap = param('GapMm', 5);
    const a = box(20, 20, 20, true);
    const b = box(20, 20, 20, true).translate(gap.add(20), 0, 0);
    arm.part('a', a);
    arm.part('b', b);
    return arm.solvedModel({});
  `,
  params: { GapMm: { values: [5, 2, 0, -2] } },
  gates: { interference: true, mountingHoles: false, jointAxis: false },
});

// result.results[i] => { combo: { GapMm }, gates: { interference: 'pass'|'fail' }, diagnostics }
// result.firstFailure.interference => the first combo (in declaration order) that overlaps

Sweep a param-driven bore diameter through the mounting-hole gate

recipe · A fastened pair of parts has a param()-driven bore diameter and you need to know at which value the mounting-hole gate flips from pass to fail, rather than only checking the nominal.

sweep-tolerance · kinematic · tolerance · sweep bore diameter param · mounting-hole consistency across a tolerance range · ParamRef diameter sweepTolerance · fastener bore mismatch envelope

Recipe source

Recipe code
const code = `
  const arm = assembly('sweep-hole-dia');
  const d = param('HoleDia', 5);
  const a = box(20, 20, 5).hole('top', { u: 0, v: 0, diameter: d, depth: 'through' });
  const b = box(20, 20, 5).hole('bottom', { u: 0, v: 0, diameter: 5, depth: 'through' });
  arm.part('a', a).connector('h', {
    type: 'frame',
    origin: { kind: 'topology', query: { kind: 'face-center', name: 'top' } },
  });
  arm.part('b', b).connector('h', {
    type: 'frame',
    origin: { kind: 'topology', query: { kind: 'face-center', name: 'bottom' } },
  });
  arm.mate('screw', 'a.h', 'b.h', 'fastened');
  return arm.solvedModel({});
`;

const sweep = await kinematic.sweepTolerance({
  code,
  params: { HoleDia: { values: [5, 6] } },
  gates: { interference: false, mountingHoles: true, jointAxis: false },
});
if (sweep.source !== 'local') throw new Error('sweep: source !== local');
if (sweep.ok) throw new Error('sweep: expected ok=false (HoleDia=6 mismatches)');
const firstFail = sweep.firstFailure['mounting-holes'];
if (!firstFail || firstFail.combo['HoleDia'] !== 6)
  throw new Error('sweep: expected HoleDia=6 as the first mounting-holes failure');

return box(10, 10, 10);

Flat-pack tab-and-slot joint — flush tab, clearance on the slot

recipe · You are joining flat stock (laser/CNC plywood, acrylic, sheet) with an interlocking tab-and-slot. The through-tab must span the full mating wall thickness — flush or slightly proud, never recessed — and the fit clearance belongs on the slot, not on the tab.

joinery · subtract · union · plate · pocket · parameter · tab and slot joint for laser-cut or CNC flat stock · through-tab flush or slightly proud of the mating face · slot widened by fit clearance, tab stays nominal · finger joint box joint plywood acrylic · kerf and endmill clearance for interlocking parts

Recipe source

Recipe code
// Rule 1: the through-tab spans the FULL wall thickness and stands slightly
// proud of the outer face (flush-trim after assembly) — never recessed.
// Rule 2: the fit clearance widens the SLOT; the tab stays at nominal size.
const t = 6;      // stock thickness
const tabW = 18;  // nominal tab width
// Per-side slot clearance — process-dependent: laser 0–0.1 (kerf supplies the
// rest), CNC router 0.1–0.25, mating 3D-printed parts 0.15–0.3.
const tabFit = param('tabFit', 0.15, { min: 0, max: 0.5 });
const tabProud = param('tabProud', 0.3, { min: 0, max: 1 });

// Vertical wall: outer face at x = 0, inner face at x = t.
const wall = box(t, 60, 50);

// Horizontal shelf butting the inner wall face at mid-height (z = 22).
const shelf = box(40, 60, t).translate(t, 0, 22);

// Tab at nominal cross-section (tabW × t): through the wall, proud of the
// outer face, with 1 mm overlap into the shelf so the union is unambiguous.
const tab = box(tabProud.add(t + 1), tabW, t).translate(tabProud.negate(), 21, 22);

// Slot: tab cross-section + tabFit per side, cut clean through the wall.
const slot = box(t + 2, tabFit.multiply(2).add(tabW), tabFit.multiply(2).add(t))
  .translate(-1, tabFit.negate().add(21), tabFit.negate().add(22));

return wall.subtract(slot).union(shelf.union(tab));

Threaded (tapped) M3/M4/M5/M6 hole with the ISO tap drill via hole({ thread })

recipe · The request names a metric screw size (M3, M4, M5, M6...) or says threaded, tapped, tap drill or internal thread. Use `shape.hole(face, { diameter: <nominal>, thread: { pitch } })` / `holes(...)`: the kernel drills the ISO 68-1 minor diameter (d - 1.0825*P, the tap drill) and records the thread for drawings and BOM. Do NOT subtract a plain cylinder of a guessed tap size. `modeled: true` cuts the real helical groove (slow; for printed threads).

hole · thread · fastener · bolt · bracket · plate · choice-param · manufacturing · M3 M4 M5 M6 threaded hole tapped hole internal thread · tap drill size from the ISO metric coarse pitch table · hole thread pitch modeled clearance instead of subtracting a cylinder · threaded mounting holes in a 3D printed bracket or machined plate · clearance hole table close normal fit ISO 273 for the mating screw · self-tapping screw pilot into printed plastic

Recipe source

Recipe code
// ISO 261/262 coarse pitch, ISO tap drill (= d - P), ISO 273 clearance holes.
//   size  pitch  tap drill  clearance close (fine)  normal (medium)
//   M3    0.5    2.5        3.2                     3.4
//   M4    0.7    3.3        4.3                     4.5
//   M5    0.8    4.2        5.3                     5.5
//   M6    1.0    5.0        6.4                     6.6
// hole({ thread }) takes the NOMINAL diameter and drills the minor diameter
// d - 1.0825*P itself (M4: 3.24 mm). For a clearance hole in the mating part,
// use hole({ diameter: CLEAR[size].normal }) with no thread.
// FDM: printed holes shrink 0.1-0.2 mm; for a screw tapped straight into
// plastic keep modeled: false (the bore is the pilot) and tap it after printing.
const ISO: Record<string, { d: number; pitch: number; close: number; normal: number }> = {
  M3: { d: 3, pitch: 0.5, close: 3.2, normal: 3.4 },
  M4: { d: 4, pitch: 0.7, close: 4.3, normal: 4.5 },
  M5: { d: 5, pitch: 0.8, close: 5.3, normal: 5.5 },
  M6: { d: 6, pitch: 1.0, close: 6.4, normal: 6.6 },
};
const size = param('Size', 'M4', { choices: ['M3', 'M4', 'M5', 'M6'] });
const spec = ISO[size.value];
const t = param('thickness', 8, { min: 4, max: 20 });
const spacing = 30;

// Bracket plate, centred on the origin. hole u/v are offsets from the centre of
// the entry face (top: u = +X, v = +Y).
const plate = box(50, 20, t).translate(-25, -10, 0);
const tapped = plate.holes('top', {
  positions: [{ u: -spacing / 2, v: 0 }, { u: spacing / 2, v: 0 }],
  diameter: spec.d,             // nominal size, not the tap drill
  depth: 'through',
  thread: { pitch: spec.pitch, modeled: false },
  name: 'tapped',
});

return tapped;

20x20 B-type T-slot extrusion with slot-6 opening and corner connector

recipe · You need a 20×20 B-type T-slot extrusion (slot 6, 6 mm slot opening) trimmed to a length param, plus a matching 90-degree corner connector that fastens on the T-slots.

extrusion · tslot · extrude · subtract · sketch · assembly · parameter · 20x20 B-type T-slot extrusion slot 6 · six millimetre slot opening for a T-nut · aluminum profile trimmed to a length param · 90-degree corner connector for the extrusion

Recipe source

Recipe code
const length = param('length', 120, { min: 20, max: 800 });
const profileSize = 20;
const slotOpening = 6;
const slotInner = 11;
const lip = 1.6;
const slotDepth = 6.2;
const coreR = 2.1; // Ø4.2 core

function slotCutter() {
  const halfOpen = slotOpening / 2;
  const halfWide = slotInner / 2;
  // Overshoot the length param range so the channel is always through.
  const h = 802;
  const stem = box(slotOpening, lip + 2, h)
    .translate(-halfOpen, profileSize / 2 - lip, -1);
  const pocket = box(slotInner, slotDepth - lip + 0.2, h)
    .translate(-halfWide, profileSize / 2 - slotDepth, -1);
  return stem.union(pocket);
}

let bar = box(profileSize, profileSize, length)
  .translate(-profileSize / 2, -profileSize / 2, 0);
for (const deg of [0, 90, 180, 270]) {
  bar = bar.subtract(slotCutter().rotateZ(deg));
}
// Core bore as a real hole feature (not a subtracted cylinder) so export,
// drawings and DFM read its diameter back. The four slot cuts split the top
// face, so name the central island by its normal and centre — `length` is a
// ParamRef, so atZ cannot be used here. u/v are mm from that face's centre
// and the bore is on the rail axis, so both are 0.
const extrusion = bar.hole({ byNormal: 'Z', atX: 0, atY: 0 }, {
  u: 0,
  v: 0,
  diameter: 2 * coreR,
  depth: 'through',
});

// Gauge sitting in the +Y face opening so inspect can read the 6 mm slot.
const gauge = box(slotOpening, 0.2, 20).translate(-slotOpening / 2, profileSize / 2 - 0.1, 50);

const plateT = 3;
const leg = 28;
// Base-plate clearance as a real hole: cut it on the bare plate, whose top
// face spans the full leg × profileSize, so u/v are mm from (leg/2,
// profileSize/2) — world (leg - 10, profileSize / 2) becomes (leg / 2 - 10, 0).
// The second bore has to stay a subtracted cylinder: it runs UP the 3 mm
// upright at x = plateT / 2 with a Ø5.5 tool, so it breaks out of both side
// walls. That is not a bore in a face and hole() cannot express it.
const connector = box(leg, profileSize, plateT)
  .hole('top', { u: leg / 2 - 10, v: 0, diameter: 5.5, depth: 'through' })
  .union(box(plateT, profileSize, leg).translate(0, 0, plateT))
  .subtract(cylinder(leg + 2, 2.75).translate(plateT / 2, profileSize / 2, -1));

const arm = assembly('tslot-extrusion-and-connector');
arm.part('extrusion', extrusion);
arm.part('slot-opening-gauge', gauge);
arm.part('corner-connector', connector, { at: [profileSize + 6, -profileSize / 2, 0] });
return arm.model();

T-slot frame corner — two rails, gusset, cap screws, named materials

recipe · Prompt asks for a T-slot frame corner as a manufacturable stack: two rails, a gusset on the slot faces, and cap screws with named materials so a BOM has mass. Screws are cylinder-head stand-ins with clearance shanks, not a swept ISO V-thread — use the metric bolt snippet when the prompt needs that thread. T-nuts are not modeled; each shank stops in the slot void. After a full evaluate_script, inspect BOM and export a PDF or SVG drawing. Report FEA UNVERIFIED unless CalculiX and gmsh are present.

extrusion · tslot · assembly · connector · mate · material · bom · fastener · manufacturing · hole · T-slot frame corner fastener BOM · two 2020 rails gusset plate · cap screws clearance shank not V-thread · aluminum rails steel screws materials · frame joint shop stack · T-nuts not modeled

Recipe source

Recipe code
// T-slot frame corner. Two rails, a gusset, two cap screws, named
// materials. Screws are cylinder heads + clearance shanks — NOT a swept
// 60° V-thread (lookup the ISO metric bolt snippet for that). T-nuts are
// NOT modeled; the shank ends in the slot void.
// After full evaluate_script: inspect({ of: 'bom' }) or export bom-csv /
// bom-json, then pdf-drawing / svg-drawing. FEA only when CalculiX + gmsh
// are present — otherwise report UNVERIFIED. No run_fea in this script.

// Authored at 80 mm. A length slider is omitted: the gusset and screw
// stations are placed on that default, and a lone param() would move the
// rails without the joint.
const length = 80;
const profileSize = 20;
const slotOpening = 6;
const slotInner = 11;
const lip = 1.6;
const slotDepth = 6.2;
const coreR = 2.1;

function slotCutter() {
  const halfOpen = slotOpening / 2;
  const halfWide = slotInner / 2;
  const h = 802;
  const stem = box(slotOpening, lip + 2, h).translate(-halfOpen, profileSize / 2 - lip, -1);
  const pocket = box(slotInner, slotDepth - lip + 0.2, h)
    .translate(-halfWide, profileSize / 2 - slotDepth, -1);
  return stem.union(pocket);
}

function extrusion() {
  let bar = extrudeRoundedRect(profileSize, profileSize, 1.15, length);
  for (const deg of [0, 90, 180, 270]) {
    bar = bar.subtract(slotCutter().rotateZ(deg));
  }
  // Core bore as a real hole feature (not a subtracted cylinder) so BOM,
  // drawings and DFM read its diameter back. The four slot cuts split the
  // top face, so name the central island by its normal and centre rather
  // than the bare canonical 'top'. u/v are mm from that face's centre, and
  // the bore is on the rail axis, so both are 0.
  return bar.hole({ byNormal: 'Z', atX: 0, atY: 0, atZ: length }, {
    u: 0,
    v: 0,
    diameter: 2 * coreR,
    depth: 'through',
  });
}

const upright = extrusion();
const rail = extrusion().rotateY(90).translate(10, 0, 70);

function yCyl(r, y0, len, x, z) {
  return cylinder(len, r).rotateX(-90).translate(x, y0, z);
}

let gusset = box(42, 3.2, 20).translate(-8, 10, 60);
// Stiffening boss between the two screw heads (heads span x ±4.8 about
// x = 0 and x = 22), so neither head sits inside it.
gusset = gusset.union(box(10, 8, 10).translate(6, 13.2, 66));
gusset = gusset.subtract(yCyl(2.8, 8, 10, 0, 72));
gusset = gusset.subtract(yCyl(2.8, 8, 10, 22, 72));
gusset = gusset.finish('anodized', { color: '#5d6d78' });

function capScrew(x: number, z: number) {
  // Head seats on the gusset face (y = 13.2), not 0.1 mm into it.
  const head = yCyl(4.8, 13.2, 3.1, x, z);
  const socket = yCyl(2.1, 14.5, 1.8, x, z);
  const shank = yCyl(2.25, 6, 7.2, x, z);
  return head.union(shank).subtract(socket).finish('steel');
}

const arm = assembly('tslot-frame-corner');
const up = arm.part('upright', upright.finish('aluminium-brushed'), { material: 'aluminum-6061' });
const rl = arm.part('rail', rail.finish('aluminium-brushed'), { material: 'aluminum-6061' });
const gu = arm.part('gusset', gusset, { material: 'aluminum-6061' });
const s1 = arm.part('screw-1', capScrew(0, 72), { material: 'mild-steel' });
const s2 = arm.part('screw-2', capScrew(22, 72), { material: 'mild-steel' });

const corner = [10, 0, 70] as [number, number, number];
up.connector('end', { type: 'frame', origin: { kind: 'vec3', value: corner } });
rl.connector('end', { type: 'frame', origin: { kind: 'vec3', value: corner } });
gu.connector('face', { type: 'frame', origin: { kind: 'vec3', value: [0, 10, 72] } });
up.connector('gusset', { type: 'frame', origin: { kind: 'vec3', value: [0, 10, 72] } });
s1.connector('head', { type: 'frame', origin: { kind: 'vec3', value: [0, 13, 72] } });
s2.connector('head', { type: 'frame', origin: { kind: 'vec3', value: [22, 13, 72] } });
gu.connector('screw-1', { type: 'frame', origin: { kind: 'vec3', value: [0, 13, 72] } });
gu.connector('screw-2', { type: 'frame', origin: { kind: 'vec3', value: [22, 13, 72] } });

arm.mate('corner', 'rail.end', 'upright.end', 'fastened');
arm.mate('gusset', 'gusset.face', 'upright.gusset', 'fastened');
arm.mate('screw-1', 'screw-1.head', 'gusset.screw-1', 'fastened');
arm.mate('screw-2', 'screw-2.head', 'gusset.screw-2', 'fastened');

return arm.solvedModel({});

Tube bend table, cut length, mass and pressure drop

recipe · You need the data a tube bender or a weight and flow estimate works from: corner points, length / rotation / angle rows, cut length, mass and a pressure-drop estimate. Give the tube a density (or a known material) and a flow, evaluate, and read tubes.lines in the result; export bend-table writes one CSV per tube. The ends here are plain points in free space; an end that sits on an assembly part also names it, { point, axis, part }.

tube-routing · tube bend table · tube cut length · tube LRA rows · tube XYZ points · tube mass · tube pressure drop · bend-table export

Recipe source

Recipe code
// A 10 mm coolant tube with three bends, routed between two plain points.
// Plain point ends carry no size, so `od` is given. `axis` points from the
// end into the tube, at both ends; with it the gate checks the end leg.
tubeSpec({});

const line = tube('coolant-1', {
  from: { point: [0, 0, 0], axis: [1, 0, 0] },
  to: { point: [250, 180, 120], axis: [0, 0, -1] },   // the tube arrives from below
  via: [
    [120, 0, 0],       // on the `from` axis
    [120, 180, 0],
    [250, 180, 0],     // on the `to` axis, under the end point
  ],
  od: 10,
  wall: 1,
  bendRadius: 30,
  system: 'coolant',
  density: 2700,                      // kg/m^3, gives massKg
  flow: { lpm: 6, fluid: 'water' },   // gives pressureDropKPa
});

// evaluate --json then reports, under tubes.lines[0]:
//   cutLength   centreline length in mm
//   xyz         corner points in the tube's own frame (first leg along +X)
//   lra         one row per bend: straight before it, plane rotation, angle
//   massKg, volumeInternalMl, pressureDropKPa
// `kernelcad export bend-table <file> -o <dir>` writes the same table to
// <dir>/coolant-1.csv.
return line;

Bent tube between two ports

recipe · You need one rigid tube from a fitting on one part to a fitting on another (a hydraulic, fuel, oil or coolant line). Declare each fitting as an axis connector with port data, pass part.port(name) as the tube ends, and give the corner points in between. The first corner goes on the first port's axis and the last corner on the second port's axis.

tube-routing · bent tube between ports · tube port connector · tube via corner points · tube bend radius · hydraulic tube line · tubeSpec rules · tube.port.misaligned

Recipe source

Recipe code
// A 9.525 mm hydraulic tube from a pump outlet (top face, +Z) to a valve
// inlet (-X face), two 90 degree bends.
const asm = assembly('line');

// A port is an axis connector with `port: { system, od }`. The origin is in
// the part's own frame; the port position is `at` plus the origin.
const pump = asm.part('pump', box(60, 60, 60), { at: [0, 0, 0] })
  .connector('out', { type: 'axis', origin: { kind: 'vec3', value: [30, 30, 60] }, axis: [0, 0, 1], port: { system: 'hydraulic', od: 9.525 } });
const valve = asm.part('valve', box(40, 40, 40), { at: [200, 150, 100] })
  .connector('in', { type: 'axis', origin: { kind: 'vec3', value: [0, 20, 20] }, axis: [-1, 0, 0], port: { system: 'hydraulic', od: 9.525 } });

// The rules every tube must meet. {} takes the defaults: bend radius 3 x OD,
// 2 x OD straight between bends, 2.5 x OD straight at each end, 6 mm clearance.
tubeSpec({});

// Leg lengths: an end leg needs bendRadius + 2.5 x OD = 52.4 mm, a leg
// between two 90 degree bends needs 2 x bendRadius + 2 x OD = 76.3 mm.
const line = tube('hyd-1', {
  from: pump.port('out'),   // [30, 30, 60], leaves along +Z
  to: valve.port('in'),     // [200, 170, 120], leaves along -X
  via: [
    [30, 30, 120],          // on the pump port axis, 60 mm above the outlet
    [30, 170, 120],         // on the valve port axis, 170 mm from the inlet
  ],
  wall: 0.89,
  bendRadius: 28.6,
});

// No `at`: the tube is already in assembly space.
asm.part('hyd-1', line);
return asm.model();

Tube that branches off another tube (90 degree tee)

recipe · One line has to feed two places: two tubes from the same port overlap and fail tube.clearance.overlap. Route one tube from the port (the trunk) and start the second on it with from: { tube, at, axis }. at is the distance along the trunk centreline from its from end, through straights and bends; axis is square to the trunk leg. The branch is cut to the trunk wall and the trunk is not drilled.

tube-routing · tube branch · tube tee · two tubes from one port · one line feeds two actuators · branch off a trunk tube · tube.clearance.overlap two tubes one port · tube.branch.too-close

Recipe source

Recipe code
// A 12.7 mm supply line from a valve block to actuator 1, with a branch to
// actuator 2 that tees off the trunk.
const asm = assembly('supply');

const block = asm.part('block', box(100, 100, 100), { at: [0, 0, 0] })
  .connector('out', { type: 'axis', origin: { kind: 'vec3', value: [50, 50, 100] }, axis: [0, 0, 1], port: { system: 'act', od: 12.7 } });
const act1 = asm.part('act-1', box(60, 60, 60), { at: [300, 20, 220] })
  .connector('in', { type: 'axis', origin: { kind: 'vec3', value: [0, 30, 30] }, axis: [-1, 0, 0], port: { system: 'act', od: 12.7 } });
const act2 = asm.part('act-2', box(60, 60, 60), { at: [-260, 20, 370] })
  .connector('in', { type: 'axis', origin: { kind: 'vec3', value: [60, 30, 30] }, axis: [1, 0, 0], port: { system: 'act', od: 12.7 } });

tubeSpec({});

// Trunk: up from the block outlet at [50, 50, 100] to z = 250, one 90 degree
// bend of radius 40, then along +X to actuator 1 at [300, 50, 250].
const trunk = tube('act-supply', {
  from: block.port('out'), to: act1.port('in'),
  via: [[50, 50, 250]],
  wall: 0.9, bendRadius: 40,
});

// The tee is at x = 190 on the trunk's second leg. As a station on the trunk:
//   first straight   150 - 40           = 110
//   90 degree bend   40 * pi / 2        = 62.83
//   along the leg    190 - (50 + 40)    = 100
const teeAt = 110 + (40 * Math.PI) / 2 + 100;

// Branch: straight up from the tee (square to the trunk leg, which runs along
// X), then along -X to actuator 2. Its first corner is above the tee, on the
// branch axis. The od comes from the actuator port.
const branch = tube('act-supply-2', {
  from: { tube: 'act-supply', at: teeAt, axis: [0, 0, 1] },
  to: act2.port('in'),
  via: [[190, 50, 400]],
  wall: 0.9, bendRadius: 40,
});

// No `at`: both tubes are already in assembly space.
asm.part('act-supply', trunk);
asm.part('act-supply-2', branch);
return asm.model();

Two fluid lines with a clearance rule between their systems

recipe · Two or more tubes of different fluid systems run close together and the systems must keep a set distance apart (fuel from hydraulic, fuel from bleed air). Name the system on each port, declare the distance in tubeSpec({ clearance: { pairs } }), and run the lines side by side at a pitch that keeps the gap. The gate measures surface to surface and names both lines when the gap is too small.

tube-routing · tube clearance between lines · fuel line hydraulic line clearance · tubeSpec clearance pairs · parallel tube runs · fluid system clearance · tube.clearance.violated

Recipe source

Recipe code
// A fuel line and a hydraulic line leave one block, run side by side and
// end on a second block. The two systems must stay 12 mm apart.
const asm = assembly('two-lines');

// The ports sit on the block faces. The block owns its ports, so each tube
// may touch it along the end straight.
const supply = asm.part('supply', box(120, 60, 60), { at: [0, 0, 0] })
  .connector('fuel-out', { type: 'axis', origin: { kind: 'vec3', value: [30, 30, 60] }, axis: [0, 0, 1], port: { system: 'fuel', od: 12.7 } })
  .connector('hyd-out', { type: 'axis', origin: { kind: 'vec3', value: [90, 30, 60] }, axis: [0, 0, 1], port: { system: 'hydraulic', od: 9.525 } });
const user = asm.part('user', box(60, 120, 60), { at: [300, 200, 120] })
  .connector('fuel-in', { type: 'axis', origin: { kind: 'vec3', value: [0, 90, 30] }, axis: [-1, 0, 0], port: { system: 'fuel', od: 12.7 } })
  .connector('hyd-in', { type: 'axis', origin: { kind: 'vec3', value: [0, 30, 30] }, axis: [-1, 0, 0], port: { system: 'hydraulic', od: 9.525 } });

// 6 mm from a tube to anything; 12 mm between a fuel tube and a hydraulic tube.
tubeSpec({ clearance: { default: 6, pairs: [{ a: 'fuel', b: 'hydraulic', min: 12 }] } });

// Both lines rise to z = 150, run along +Y, then turn into the user block.
// The Y runs are 60 mm apart in X; with ODs of 12.7 and 9.525 the gap between
// the tube surfaces is 60 - 6.35 - 4.76 = 48.9 mm.
const fuel = tube('fuel-1', {
  from: supply.port('fuel-out'),   // [30, 30, 60], +Z
  to: user.port('fuel-in'),        // [300, 290, 150], -X
  via: [[30, 30, 150], [30, 290, 150]],
  wall: 0.89,
  bendRadius: 38.1,
});
const hyd = tube('hyd-1', {
  from: supply.port('hyd-out'),    // [90, 30, 60], +Z
  to: user.port('hyd-in'),         // [300, 230, 150], -X
  via: [[90, 30, 150], [90, 230, 150]],
  wall: 0.89,
  bendRadius: 28.6,
});

asm.part('fuel-1', fuel.color('#d9822b'));
asm.part('hyd-1', hyd.color('#2f6fb5'));
return asm.model();

Twisted, tapered thin-wall vase (hollow by boolean, not shell)

recipe · You need a hollow, open-top vessel whose cross-section twists and tapers with height (a twisted hex vase, a faceted planter, a lamp shade) with an even wall. Build the outer body with variableSweep through per-station profiles, then hollow it by subtracting the same sweep built from profiles inset by the wall thickness — a boolean hollow that works where shell() cannot close the offset (curved faces that meet at sharp edges: multi-station sweeps, ruled lofts, unions of stacked lofts).

sweep · shell · subtract · twisted vase · tapered hexagonal vase · thin wall hollow · variableSweep twist per station · shell fails on stacked lofts · shellWithHistory BRepOffsetAPI_MakeThickSolid failed · hollow with inner offset body

Recipe source

Recipe code
// Twisted, tapered hexagonal vase: 180 mm tall, 90 -> 110 mm across flats,
// 2 mm wall and floor, open top.
//
// TWIST: a regular hexagon looks the same every 60°, so a 60° (or 120°, 180°)
// total twist is INVISIBLE — the top hexagon lands exactly on the bottom one.
// Pick a total that is not a multiple of 60°, or twist per station (below:
// an easing twist that ends at 40°).
const H = 180;
const wall = 2;
const bottomAF = 90;
const topAF = 110;
const twistDeg = [0, 8, 18, 28, 36, 40]; // one entry per station, bottom to top
const n = twistDeg.length;

// Flat-sided hexagon, `af` across flats, rotated `rotDeg` about Z.
function hex(af, rotDeg) {
  const R = af / Math.sqrt(3);
  let p = path();
  for (let i = 0; i < 6; i++) {
    const a = ((rotDeg + 30 + 60 * i) * Math.PI) / 180;
    const x = R * Math.cos(a);
    const y = R * Math.sin(a);
    p = i === 0 ? p.moveTo(x, y) : p.lineTo(x, y);
  }
  return p.close();
}

// Taper and twist at height fraction u in [0, 1] (linear between stations).
const acrossFlats = (u) => bottomAF + (topAF - bottomAF) * u;
function twistAt(u) {
  const x = Math.min(Math.max(u, 0), 1) * (n - 1);
  const k = Math.min(n - 2, Math.floor(x));
  return twistDeg[k] + (twistDeg[k + 1] - twistDeg[k]) * (x - k);
}

// Outer body: one station per twist entry, profiles centred on the spine.
const outer = variableSweep([[0, 0, 0], [0, 0, H]],
  twistDeg.map((tw, i) => ({ t: i / (n - 1), profile: hex(acrossFlats(i / (n - 1)), tw) })));

// Inner body: the same twist and taper at the same heights, every flat moved
// in by the wall thickness (across flats - 2·wall). It starts one wall above
// the base (the floor) and runs 1 mm past the top so the top is open.
const z0 = wall;
const z1 = H + 1;
const inner = variableSweep([[0, 0, z0], [0, 0, z1]],
  twistDeg.map((_, i) => {
    const u = (z0 + ((z1 - z0) * i) / (n - 1)) / H;
    return { t: i / (n - 1), profile: hex(acrossFlats(Math.min(u, 1)) - 2 * wall, twistAt(u)) };
  }));

// Boolean hollow. outer.shell(wall, { face: 'top' }) also works on this body
// (shell retries other join strategies when the first cannot close the
// offset between the station faces), but on a union of stacked lofts or a
// ruled loft it can still fail; the subtract does not depend on offsets.
return outer.subtract(inner);

Boolean, choice, and string script params (beyond numeric param())

recipe · A `.kcad.ts` script needs an editable value that isn't a plain number: a feature on/off switch (`param('HasLid', true)`), a closed set of named options like a fastener size (`param('Screw', 'M4', { choices: [...] })`), or free text such as a nameplate/label (`param('Label', 'KCAD', { maxLength })`). All three resolve eagerly — read `.value` in script logic (`if`, object-key lookup, `sketch.text`) instead of the numeric ParamRef's symbolic `.add()`/`.multiply()` chain.

parameter · typed-params · choice-param · boolean-feature-toggle · boolean param toggles a feature on and off · choice param with a closed set of named options · string param drives sketch text content · param() with choices meta · editable text label param · HasLid toggle · typed script parameters beyond numbers

Recipe source

Recipe code
const hasFoot = param('HasFoot', true, { description: 'add a mounting foot' });

const screw = param('Screw', 'M4', { choices: ['M3', 'M4', 'M5'] });
const screwHoleDiameterMm: Record<string, number> = { M3: 3.4, M4: 4.5, M5: 5.5 };

const label = param('Label', 'KCAD', { maxLength: 24 });

let body = box(50, 30, 10).hole('top', {
  u: -15,
  v: 0,
  diameter: screwHoleDiameterMm[screw.value],
  depth: 'through',
});

if (hasFoot.value) {
  // The foot sits flush under the body (z -4..0): a union of parts that
  // never touch is flagged union.disconnected.
  body = body.union(box(10, 10, 4).translate(0, 0, -4));
}

const text = sketch
  .text(label.value, { size: 4, align: 'center', position: [0, 0] })
  .extrude(0.5)
  .rotate([1, 0, 0], 90)
  .translate(25, 0, 5); // raised label on the front face (y = 0), centred in X

return body.union(text);

Compose multiple primitives by translate then union

recipe · Simple blockouts only — compose primitives by translate+union without overlap. NOT for real / production / complex / enclosure / gearbox / bearing housing / robot-arm prompts (use multi-feature-machined-housing or multi-body-mechanism-real-proportions instead).

boolean · union · translate · stacking · primitive · stack two boxes · join multiple parts with union · compose primitives without overlap

Recipe source

Recipe code
const lower = box(30, 30, 10);
const upper = box(20, 20, 10).translate(5, 5, 10);
return lower.union(upper);

Welded tube frame with a sheet on top — butt-jointed members in one union

recipe · A frame, rack, tray, table base or weldment built from tube / bar members, optionally with a sheet or deck on top. Members are cut to fit BETWEEN each other (butt joints) so they touch but never overlap, and the sheet sits on the tubes. Use one union() when the frame is one welded body; use assembly().part() per member when members are separate BOM lines.

union · tube · plate · parameter · material · manufacturing · welded square tube frame · aluminium tray with cross supports · weldment rack frame cross members between rails · sheet on top of tubes · butt joint member cut to length · frame members must not overlap · union.member-overlap · union.disconnected

Recipe source

Recipe code
const width = param('width', 1080, {min:900,max:1300,description:'Overall X size, mm'});
const depth = param('depth', 1100, {min:900,max:1300,description:'Overall Y size, mm'});
const sheetT = param('sheetThickness', 3, {min:2,max:5,description:'Aluminium sheet thickness, mm'});
const tube = param('tubeOuter', 30, {min:25,max:40,description:'Square tube outside size, mm'});
const wall = param('tubeWall', 2, {min:1.5,max:3,description:'Square tube wall thickness, mm'});
const endInset = param('supportEndInset', 30, {min:30,max:100,description:'First/last cross tube inset along Y (>= tube), mm'});
const inner = tube.subtract(wall.multiply(2));
function tubeX(len) {
  return box(len, tube, tube).subtract(box(len.add(2), inner, inner).translate(-1, wall, wall));
}
function tubeY(len) {
  return box(tube, len, tube).subtract(box(inner, len.add(2), inner).translate(wall, -1, wall));
}
const crossLen = width.subtract(tube.multiply(2));   // fits between the side rails
const sideLen = depth.subtract(tube.multiply(2));    // fits between front and back rails
const sheet = box(width, depth, sheetT).translate(0, 0, tube).finish('aluminium-brushed');
const front = tubeX(width).finish('aluminium-brushed');
const back = tubeX(width).translate(0, depth.subtract(tube), 0).finish('aluminium-brushed');
const left = tubeY(sideLen).translate(0, tube, 0).finish('aluminium-brushed');
const right = tubeY(sideLen).translate(width.subtract(tube), tube, 0).finish('aluminium-brushed');
// First cross tube at y = endInset, last one ends at depth - endInset.
const pitch = depth.subtract(endInset.multiply(2)).subtract(tube).divide(4);
const s1 = tubeX(crossLen).translate(tube, endInset, 0).finish('aluminium-brushed');
const s2 = tubeX(crossLen).translate(tube, endInset.add(pitch), 0).finish('aluminium-brushed');
const s3 = tubeX(crossLen).translate(tube, endInset.add(pitch.multiply(2)), 0).finish('aluminium-brushed');
const s4 = tubeX(crossLen).translate(tube, endInset.add(pitch.multiply(3)), 0).finish('aluminium-brushed');
const s5 = tubeX(crossLen).translate(tube, endInset.add(pitch.multiply(4)), 0).finish('aluminium-brushed');
return union(sheet,front,back,left,right,s1,s2,s3,s4,s5);

Wood dado, rabbet, and mortise-and-tenon with fit clearance

recipe · You are cutting a dado groove, a rabbet rebate, and a mortise-and-tenon in lumber, with a named fit-clearance param widening the receiving cuts (groove, rebate, mortise) while the male tenon stays nominal.

joinery · dado · rabbet · mortise · tenon · wood · subtract · assembly · parameter · dado groove across the grain · rabbet rebate along an edge · mortise and tenon · woodworking fit clearance on the receiving cuts · lumber assembly of a receiving board and a mating board

Recipe source

Recipe code
const fit = param('fitClearance', 0.2, { min: 0, max: 0.8 });
const stockX = 120;
const stockY = 40;
const stockZ = 18;

let receiving = box(stockX, stockY, stockZ);

// Dado: groove across Y on the top face. Clearance widens the groove.
const dado = box(fit.multiply(2).add(18), stockY, 6)
  .translate(fit.negate().add(40), 0, stockZ - 6);
receiving = receiving.subtract(dado);

// Rabbet: L-notch along the bottom front edge. Clearance deepens the rebate.
const rabbet = box(stockX, 8, fit.add(10))
  .translate(0, 0, -1);
receiving = receiving.subtract(rabbet);

// Mortise: pocket from the top. Clearance widens the mortise; tenon stays 8×20×12.
const mortise = box(8, fit.multiply(2).add(20), 13)
  .translate(90, fit.negate().add(10), stockZ - 12);
receiving = receiving.subtract(mortise);

const tenon = box(12, 20, 8);
const mating = box(80, stockY, stockZ).union(tenon.translate(-12, 10, 5));

const arm = assembly('wood-joinery');
arm.part('receiving-board', receiving);
arm.part('mating-board', mating, { at: [140, 0, 0] });
return arm.model();

Wrap a bitmap texture around a cylinder

recipe · You need a bitmap texture (label, decal, logo) wrapped onto a cylinder without hand-authoring UVs. Call `shape.wrapTexture(imageRef, { type: 'cylinder', axis })` so UVs are projected from final world-space vertices; `{ type: 'flat' | 'sphere' | 'box' }` cover planar and other wraps.

texture · wrap · wrap a texture around a cylinder · cylindrical UV projection wrapTexture · wrapTexture · projected bitmap texture · cylinder texture wrap · image texture on curved surface · cylindrical UV projection

Recipe source

Recipe code
const height = 60;
const radius = 20;

// 1×1 PNG data URL keeps this recipe self-contained (no sidecar file).
// Swap `path` for a real image when wrapping a production label.
// Fillet + translate AFTER wrapTexture on purpose: UVs re-derive from
// the final world-space vertices, so the wrap stays put.
const body = cylinder(height, radius)
  .wrapTexture(
    { path: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==' },
    { type: 'cylinder', axis: [0, 0, 1] },
  )
  .fillet(1)
  .translate(0, 0, 0);

return body;

DXF $INSUNITS = 4

rule · Shop check: units. Recorded rule source: direct file inspection.

units · scs.units.dxf-must-be-mm · direct file inspection

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.units.dxf-must-be-mm",
  "description": "DXF $INSUNITS = 4",
  "scope": "units",
  "appliesTo": {},
  "check": {
    "kind": "enum",
    "allowed": [
      "4"
    ]
  },
  "diagnosticCode": "dfm.units.dxf-not-mm",
  "severity": "error",
  "ruleSource": "direct file inspection"
}

material in catalog

rule · Shop check: material. Recorded rule source: catalog.json.

material · scs.material.sku-in-catalog · catalog.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.material.sku-in-catalog",
  "description": "material in catalog",
  "scope": "material",
  "appliesTo": {},
  "check": {
    "kind": "enum",
    "allowed": [
      "aluminum-6061-t6",
      "mild-steel-1018",
      "acrylic-cast",
      "delrin"
    ]
  },
  "diagnosticCode": "dfm.material.unknown-sku",
  "severity": "error",
  "repairAction": "change-material",
  "ruleSource": "catalog.json"
}

thickness in stocked gauges

rule · Shop check: thickness. Recorded rule source: catalog.json.

thickness · scs.thickness.in-stocked-gauges · catalog.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.thickness.in-stocked-gauges",
  "description": "thickness in stocked gauges",
  "scope": "thickness",
  "appliesTo": {},
  "check": {
    "kind": "expression",
    "formula": "thicknessIn IN catalog[sku].thicknessesIn"
  },
  "diagnosticCode": "dfm.thickness.not-stocked",
  "severity": "error",
  "repairAction": "change-thickness",
  "ruleSource": "catalog.json"
}

min hole = 50% of thickness

rule · Shop check: hole. Recorded rule source: /laser-cutting/.

hole · scs.laser.min-hole-50pct-thickness · /laser-cutting/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.laser.min-hole-50pct-thickness",
  "description": "min hole = 50% of thickness",
  "scope": "hole",
  "appliesTo": {
    "services": [
      "laser"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": 0.5,
    "units": "multiplier-of-thickness"
  },
  "diagnosticCode": "dfm.hole.below-minimum",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "/laser-cutting/"
}

min hole per material+thickness

rule · Shop check: hole. Recorded rule source: specs.json.

hole · scs.laser.min-hole-per-material · specs.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.laser.min-hole-per-material",
  "description": "min hole per material+thickness",
  "scope": "hole",
  "appliesTo": {
    "services": [
      "laser"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 0.0625,
        "mild-steel-1018": 0.03
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.hole.below-minimum",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "specs.json"
}

min slot width

rule · Shop check: slot. Recorded rule source: specs.json.

slot · scs.laser.min-slot-width · specs.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.laser.min-slot-width",
  "description": "min slot width",
  "scope": "slot",
  "appliesTo": {
    "services": [
      "laser"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 0.063
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.slot.below-minimum",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "specs.json"
}

min bridge width between cutouts

rule · Shop check: web. Recorded rule source: specs.json.

web · scs.laser.min-web-width · specs.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.laser.min-web-width",
  "description": "min bridge width between cutouts",
  "scope": "web",
  "appliesTo": {
    "services": [
      "laser"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 0.06
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.web.below-minimum",
  "severity": "error",
  "repairAction": "relocate",
  "ruleSource": "specs.json"
}

laser thickness 0.015-0.750 in

rule · Shop check: thickness. Recorded rule source: /laser-cutting/.

thickness · scs.laser.thickness-range · /laser-cutting/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.laser.thickness-range",
  "description": "laser thickness 0.015-0.750 in",
  "scope": "thickness",
  "appliesTo": {
    "services": [
      "laser"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": 0.015,
    "units": "in"
  },
  "diagnosticCode": "dfm.thickness.out-of-range",
  "severity": "error",
  "repairAction": "change-thickness",
  "ruleSource": "/laser-cutting/"
}

material is bendable

rule · Shop check: material. Recorded rule source: /bending/.

material · scs.bending.material-supported · /bending/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.material-supported",
  "description": "material is bendable",
  "scope": "material",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "enum",
    "allowed": [
      "aluminum-6061-t6",
      "mild-steel-1018"
    ]
  },
  "diagnosticCode": "dfm.bending.material-unsupported",
  "severity": "error",
  "repairAction": "change-material",
  "ruleSource": "/bending/"
}

bending thickness 0.030-0.250 in

rule · Shop check: thickness. Recorded rule source: /bending/.

thickness · scs.bending.thickness-range · /bending/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.thickness-range",
  "description": "bending thickness 0.030-0.250 in",
  "scope": "thickness",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "max",
    "threshold": 0.25,
    "units": "in"
  },
  "diagnosticCode": "dfm.thickness.out-of-range-for-service",
  "severity": "error",
  "repairAction": "change-thickness",
  "ruleSource": "/bending/"
}

min bend radius per material

rule · Shop check: bend. Recorded rule source: specs.json.

bend · scs.bending.min-radius-per-thickness · specs.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.min-radius-per-thickness",
  "description": "min bend radius per material",
  "scope": "bend",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 0.094,
        "mild-steel-1018": 0.063
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.bend.radius-below-minimum",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "specs.json"
}

|angle| <= 130 deg

rule · Shop check: bend. Recorded rule source: /bending/.

bend · scs.bending.max-angle · /bending/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.max-angle",
  "description": "|angle| <= 130 deg",
  "scope": "bend",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "max",
    "threshold": 130,
    "units": "mm"
  },
  "diagnosticCode": "dfm.bend.angle-too-acute",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "/bending/"
}

bend length <= 44 in

rule · Shop check: bend. Recorded rule source: /bending/.

bend · scs.bending.max-length · /bending/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.max-length",
  "description": "bend length <= 44 in",
  "scope": "bend",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "max",
    "threshold": 44,
    "units": "in"
  },
  "diagnosticCode": "dfm.bend.length-exceeds-max",
  "severity": "warn",
  "ruleSource": "/bending/"
}

min flange before bend

rule · Shop check: flange. Recorded rule source: specs.json.

flange · scs.bending.flange-before-bend · specs.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.flange-before-bend",
  "description": "min flange before bend",
  "scope": "flange",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 0.5
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.bend.flange-too-short",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "specs.json"
}

min flange after bend

rule · Shop check: flange. Recorded rule source: specs.json.

flange · scs.bending.flange-after-bend · specs.json

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.flange-after-bend",
  "description": "min flange after bend",
  "scope": "flange",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 0.5
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.bend.flange-too-short",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "specs.json"
}

channel base >= 2x flange

rule · Shop check: flange. Recorded rule source: /bending/.

flange · scs.bending.channel-ratio · /bending/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.bending.channel-ratio",
  "description": "channel base >= 2x flange",
  "scope": "flange",
  "appliesTo": {
    "services": [
      "bending"
    ]
  },
  "check": {
    "kind": "expression",
    "formula": "channelBase >= 2 * flange"
  },
  "diagnosticCode": "dfm.bend.channel-ratio-too-low",
  "severity": "warn",
  "ruleSource": "/bending/"
}

<= 44 x 30 in

rule · Shop check: sheet-size. Recorded rule source: /materials/min-max/.

sheet-size · scs.size.max-instant-quote · /materials/min-max/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.size.max-instant-quote",
  "description": "<= 44 x 30 in",
  "scope": "sheet-size",
  "appliesTo": {},
  "check": {
    "kind": "max",
    "threshold": {
      "perMaterial": {
        "*": 44
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.size.exceeds-instant-quote",
  "severity": "warn",
  "ruleSource": "/materials/min-max/"
}

<= 56 x 30 in (Al) / vendor max

rule · Shop check: sheet-size. Recorded rule source: /materials/min-max/.

sheet-size · scs.size.max-custom-quote · /materials/min-max/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.size.max-custom-quote",
  "description": "<= 56 x 30 in (Al) / vendor max",
  "scope": "sheet-size",
  "appliesTo": {},
  "check": {
    "kind": "max",
    "threshold": {
      "perMaterial": {
        "aluminum-6061-t6": 56,
        "*": 44
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.size.exceeds-max",
  "severity": "error",
  "repairAction": "relocate",
  "ruleSource": "/materials/min-max/"
}

>= category minimum

rule · Shop check: sheet-size. Recorded rule source: /materials/min-max/.

sheet-size · scs.size.min-part-size · /materials/min-max/

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.size.min-part-size",
  "description": ">= category minimum",
  "scope": "sheet-size",
  "appliesTo": {},
  "check": {
    "kind": "min",
    "threshold": {
      "perMaterial": {
        "*": 0.25
      }
    },
    "units": "in"
  },
  "diagnosticCode": "dfm.size.below-minimum",
  "severity": "error",
  "repairAction": "enlarge",
  "ruleSource": "/materials/min-max/"
}

DXF cut layer is LWPOLYLINE only

rule · Shop check: units. Recorded rule source: direct file inspection.

units · scs.dxf.no-splines · direct file inspection

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.dxf.no-splines",
  "description": "DXF cut layer is LWPOLYLINE only",
  "scope": "units",
  "appliesTo": {},
  "check": {
    "kind": "enum",
    "allowed": [
      "LWPOLYLINE"
    ]
  },
  "diagnosticCode": "dfm.dxf.spline-present",
  "severity": "error",
  "ruleSource": "direct file inspection"
}

BEND layer exists when bend lines

rule · Shop check: bend. Recorded rule source: Slice A contract item 3.

bend · scs.dxf.bend-layer-present · Slice A contract item 3

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Recorded rule and limits
{
  "id": "scs.dxf.bend-layer-present",
  "description": "BEND layer exists when bend lines",
  "scope": "bend",
  "appliesTo": {},
  "check": {
    "kind": "expression",
    "formula": "if source.bendLines.length > 0 then dxf.layers includes BEND"
  },
  "diagnosticCode": "dfm.bend.layer-missing",
  "severity": "error",
  "ruleSource": "Slice A contract item 3"
}

6061 T6 Aluminum

material · Catalog snapshot: laser, bending; stocked thicknesses in inches.

aluminum-6061-t6 · metal · laser · bending

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Stocked thicknesses and services (snapshot)
{
  "displayName": "6061 T6 Aluminum",
  "category": "metal",
  "thicknessesIn": [
    0.04,
    0.063,
    0.08,
    0.09,
    0.1,
    0.125,
    0.187,
    0.25,
    0.313,
    0.375,
    0.5,
    0.75
  ],
  "services": [
    "laser",
    "bending"
  ]
}

Mild Steel A36 / 1008 / 1018

material · Catalog snapshot: laser, bending; stocked thicknesses in inches.

mild-steel-1018 · metal · laser · bending

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Stocked thicknesses and services (snapshot)
{
  "displayName": "Mild Steel A36 / 1008 / 1018",
  "category": "metal",
  "thicknessesIn": [
    0.03,
    0.06,
    0.09,
    0.125,
    0.187,
    0.25,
    0.313,
    0.375,
    0.5
  ],
  "services": [
    "laser",
    "bending"
  ]
}

Cast Acrylic

material · Catalog snapshot: laser; stocked thicknesses in inches.

acrylic-cast · plastic · laser

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Stocked thicknesses and services (snapshot)
{
  "displayName": "Cast Acrylic",
  "category": "plastic",
  "thicknessesIn": [
    0.063,
    0.09,
    0.118,
    0.187,
    0.236,
    0.313,
    0.375,
    0.5
  ],
  "services": [
    "laser"
  ]
}

Delrin (POM)

material · Catalog snapshot: cnc-router; stocked thicknesses in inches.

delrin · plastic · cnc-router

Versioned catalog source · Snapshot: 2026-05-23. Recheck the vendor before ordering.

Snapshot source pages: https://sendcutsend.com/materials/ · https://sendcutsend.com/laser-cutting/ · https://sendcutsend.com/bending/ · https://sendcutsend.com/materials/min-max/

Stocked thicknesses and services (snapshot)
{
  "displayName": "Delrin (POM)",
  "category": "plastic",
  "thicknessesIn": [
    0.06,
    0.09,
    0.125,
    0.187,
    0.25
  ],
  "services": [
    "cnc-router"
  ]
}