Skip to content
Kiln0.10Get Kiln

Pipe ranks, wooden casework and keyboard console

Earlier examples from earlier Kiln versions.

Historical gallery render of Pipe organ; see the poster provenance below.

Drag to orbit after opening. The model starts stationary.

For your engine. The download is a standard glTF 2.0 binary (GLB) with PBR metallic-roughness materials. glTF stores materials, not lighting or tone mapping, so your engine decides how they read. This 3D view tone-maps with Review Neutral, the Khronos PBR Neutral construction with a smaller glare offset (0.015 instead of 0.04), at exposure 0.9. Its Tone mapping control also shows ACES and Linear, for comparison.

Build measurements

Triangles
10,340
Estimated draws
513
Materials
12
Textures
0
Animation clips
0
Bounds X × Y × Z
3.53 × 6.72 × 4.4 m
Build warnings
0

Measurements come from this build.

Download this build

Runtime: 256,848 bytes. Original: 256,620 bytes.

These GLBs are build outputs of the MIT-licensed example source; no separate terms are stated for the builds.

Runtime provenance metadata
SHA-256 download hashes
Runtime GLB
f5ac34e2d0933e378758f0973bb088b17e99d4888bf0b4735bd36e00a8556d5f
Original GLB
13d38789cb1ed4365661c1cd404fd60089f6a7b8615892e5075898e7448112ea
Source
4e4ed8e01e007891a84f3c13117c1367264570311f233c711206f2c18629db41

The example source is part of the Kiln repository. Repository licence: MIT.

These GLBs are build outputs of the MIT-licensed example source; no separate terms are stated for the builds.

Recorded authorship

Gemini 3.8 Flash through Antigravity CLI (agy)

Source-header credit

Source access
Source header declares no repository implementation or finished examples supplied
Inherited context
Not independently recorded; source-header declarations only
Starting example
None supplied, according to source header
Human input
Header declares no hand-authored source; other intervention not recorded
Authoring review
Six-view review declared; renderer fidelity not recorded

Gallery GPU render of this exact source. Source, artifact, image hashes and camera settings are recorded alongside the poster; the artifact hash names the GLB bytes the image was rendered from, which the downloadable rebuild reproduces byte for byte only on the platform that recorded it.

Poster camera and render record

The source behind this build

pipe-organ.kiln.js
// Authored by: gemini-3.8-flash-high, via agy.
//
// Written by the model itself through the Kiln MCP tools: it wrote the
// program, rendered it, looked at its own six-view contact sheet, and
// revised. Not a line of it is hand-authored.
//
// Dispatched into a clean directory containing only the brief and the Kiln
// skills, with no access to this repository or to any finished example.

const meta = { name: 'PipeOrgan', category: 'prop' };

function build() {
  const root = createRoot('PipeOrgan');

  // =========================================================================
  // MATERIALS
  // =========================================================================
  const woodDark = gameMaterial(0x381f10, { roughness: 0.68, metalness: 0.05 });
  const woodMedium = gameMaterial(0x543118, { roughness: 0.62, metalness: 0.05 });
  const woodLight = gameMaterial(0x754724, { roughness: 0.58, metalness: 0.05 });
  const pipeTin = gameMaterial(0xd8dce4, { roughness: 0.18, metalness: 0.92 });
  const pipeMouth = gameMaterial(0x18181c, { roughness: 0.7, metalness: 0.3 });
  const goldLeaf = gameMaterial(0xcaa034, { roughness: 0.32, metalness: 0.78 });
  const keyWhite = gameMaterial(0xf5f1e8, { roughness: 0.28, metalness: 0.05 });
  const keyBlack = gameMaterial(0x1a1a1c, { roughness: 0.38, metalness: 0.1 });
  const stopKnobWhite = gameMaterial(0xede7db, { roughness: 0.32, metalness: 0.05 });
  const feltRed = gameMaterial(0x82151b, { roughness: 0.9, metalness: 0.0 });
  const brassMat = gameMaterial(0xd4af37, { roughness: 0.25, metalness: 0.85 });
  const scorePaper = gameMaterial(0xfaf6ed, { roughness: 0.85, metalness: 0.0 });

  // =========================================================================
  // 0. GALLERY / DAIS PLATFORM
  // Connects the case, console, and bench into one grounded church organ loft.
  // Sits strictly on Y = 0.
  // =========================================================================
  const platform = createPivot('GalleryPlatform', [0, 0, 0], root);

  // Main case platform base
  createPart('PlatformPlinth', boxGeo(1.4, 0.05, 4.4), woodDark, {
    position: [-0.15, 0.025, 0],
    parent: platform
  });
  // Console dais running forward to carry console and bench
  createPart('ConsoleDais', boxGeo(2.1, 0.04, 2.1), woodMedium, {
    position: [1.6, 0.02, 0],
    parent: platform
  });
  // Stepped bevel trim along dais edges
  createPart('DaisTrimFront', boxGeo(0.06, 0.04, 2.14), woodDark, {
    position: [2.65, 0.02, 0],
    parent: platform
  });
  createPart('DaisTrimLeft', boxGeo(2.1, 0.04, 0.06), woodDark, {
    position: [1.6, 0.02, -1.07],
    parent: platform
  });
  createPart('DaisTrimRight', boxGeo(2.1, 0.04, 0.06), woodDark, {
    position: [1.6, 0.02, 1.07],
    parent: platform
  });

  // =========================================================================
  // 1. ORGAN CASE FAÇADE (Centred at X = 0, spanning Z = -2.1m to +2.1m)
  // Deep architectural case: back at X = -0.58m, front up to X = +0.40m
  // =========================================================================
  const caseGroup = createPivot('OrganCase', [0, 0, 0], root);

  // --- LOWER CASE (Dado / Pedestal) ---
  // Sub-base plinth
  createPart('CaseBasePlinth', boxGeo(0.95, 0.12, 4.14), woodDark, {
    position: [-0.11, 0.11, 0],
    parent: caseGroup
  });
  // Lower cabinet body
  createPart('CaseDadoBody', boxGeo(0.88, 1.38, 4.0), woodMedium, {
    position: [-0.11, 0.86, 0],
    parent: caseGroup
  });

  // Fielded panels across the lower case (7 panels with raised bolection moulding)
  const panelZs = [-1.65, -1.1, -0.55, 0, 0.55, 1.1, 1.65];
  panelZs.forEach((pz, idx) => {
    // Outer panel frame
    createPart(`DadoPanelFrame_${idx}`, boxGeo(0.03, 1.05, 0.46), woodDark, {
      position: [0.34, 0.88, pz],
      parent: caseGroup
    });
    // Raised inner field
    createPart(`DadoPanelField_${idx}`, boxGeo(0.04, 0.95, 0.38), woodLight, {
      position: [0.35, 0.88, pz],
      parent: caseGroup
    });
  });

  // Dado base rail and mid rail
  createPart('DadoBaseRail', boxGeo(0.92, 0.08, 4.06), woodDark, {
    position: [-0.09, 0.21, 0],
    parent: caseGroup
  });
  createPart('DadoMidRail', boxGeo(0.92, 0.06, 4.06), woodDark, {
    position: [-0.09, 1.44, 0],
    parent: caseGroup
  });

  // --- IMPOST (Classical shelf carrying the pipe flats) ---
  createPart('ImpostArchitrave', boxGeo(0.96, 0.1, 4.2), woodDark, {
    position: [-0.07, 1.6, 0],
    parent: caseGroup
  });
  createPart('ImpostFrieze', boxGeo(0.98, 0.08, 4.24), woodLight, {
    position: [-0.06, 1.69, 0],
    parent: caseGroup
  });
  createPart('ImpostCornice', boxGeo(1.02, 0.12, 4.3), woodDark, {
    position: [-0.04, 1.79, 0],
    parent: caseGroup
  });

  // Dentils along the Impost
  const dentilProto = createPart('ImpostDentil_0', boxGeo(0.04, 0.05, 0.04), woodLight, {
    position: [0.48, 1.76, -2.05],
    parent: caseGroup
  });
  for (let i = 1; i <= 24; i++) {
    const dZ = -2.05 + (i / 24) * 4.1;
    createInstance(`ImpostDentil_${i}`, dentilProto, {
      position: [0.48, 1.76, dZ],
      parent: caseGroup
    });
  }

  // --- CARVED SUPPORT CORBELS UNDER PIPE TOWERS ---
  // Large acanthus corbels supporting the projecting towers
  [-1.6, 0, 1.6].forEach((corbelZ, cIdx) => {
    const name = `TowerCorbel_${cIdx}`;
    // Upper scroll bracket
    createPart(`${name}_Upper`, boxGeo(0.18, 0.24, 0.16), woodDark, {
      position: [0.38, 1.56, corbelZ],
      parent: caseGroup
    });
    // Gilt acanthus carving
    createPart(`${name}_Acanthus`, coneGeo(0.08, 0.22, 6), goldLeaf, {
      position: [0.42, 1.54, corbelZ],
      rotation: [180, 0, 0],
      parent: caseGroup
    });
  });

  // --- FAÇADE PILASTERS (Dividing towers and flats) ---
  const pilasterZs = [-1.94, -1.28, -0.54, 0.54, 1.28, 1.94];
  let capitalProto = null;
  pilasterZs.forEach((pz, idx) => {
    // Pilaster shaft
    createPart(`PilasterShaft_${idx}`, boxGeo(0.1, 3.2, 0.1), woodMedium, {
      position: [0.08, 3.45, pz],
      parent: caseGroup
    });
    // Fluting detail (recessed stripe)
    createPart(`PilasterFlute_${idx}`, boxGeo(0.02, 3.0, 0.04), woodDark, {
      position: [0.135, 3.45, pz],
      parent: caseGroup
    });
    // Base plinth
    createPart(`PilasterBase_${idx}`, boxGeo(0.14, 0.14, 0.14), woodDark, {
      position: [0.09, 1.92, pz],
      parent: caseGroup
    });
    // Corinthian / Composite gilded capital
    if (!capitalProto) {
      capitalProto = createPart('CapitalProto', boxGeo(0.16, 0.14, 0.16), goldLeaf, {
        position: [0.09, 5.08, pz],
        parent: caseGroup
      });
      createPart('CapitalVolute', cylinderZGeo(0.035, 0.035, 0.18, 8), goldLeaf, {
        position: [0.12, 5.12, pz],
        parent: caseGroup
      });
    } else {
      createInstance(`Capital_${idx}`, capitalProto, {
        position: [0.09, 5.08, pz],
        parent: caseGroup
      });
    }
  });

  // --- REAR ENCLOSURE & SIDES (Deep enough to fully enclose all tower domes) ---
  // Back wall sits cleanly at X = -0.58m
  createPart('CaseBackWall', boxGeo(0.08, 5.2, 4.08), woodDark, {
    position: [-0.58, 2.7, 0],
    parent: caseGroup
  });
  createPart('CaseSideWallL', boxGeo(0.96, 5.2, 0.08), woodDark, {
    position: [-0.1, 2.7, -2.02],
    parent: caseGroup
  });
  createPart('CaseSideWallR', boxGeo(0.96, 5.2, 0.08), woodDark, {
    position: [-0.1, 2.7, 2.02],
    parent: caseGroup
  });

  // =========================================================================
  // DISPLAY PIPES WITH MITRED MOUTHS
  // =========================================================================
  function addDisplayPipe(name, posX, baseY, posZ, radius, totalHeight, parent) {
    const footLen = Math.min(0.72, totalHeight * 0.26);
    const bodyLen = totalHeight - footLen;
    const mouthY = baseY + footLen;

    // Conical Foot: tapers from toe at rackboard to mouth
    createPart(`${name}_Foot`, taperConeGeo(radius * 0.2, radius, footLen, 'y', 12), pipeTin, {
      position: [posX, baseY + footLen * 0.5, posZ],
      parent
    });

    // Resonator Body: polished tin cylinder
    createPart(`${name}_Body`, cylinderGeo(radius, radius, bodyLen, 12), pipeTin, {
      position: [posX, mouthY + bodyLen * 0.5, posZ],
      parent
    });

    // Dark Voice Slot (Languid flue opening)
    createPart(`${name}_VoiceSlot`, boxGeo(radius * 0.35, radius * 0.28, radius * 1.5), pipeMouth, {
      position: [posX + radius * 0.5, mouthY, posZ],
      parent
    });

    // Upper Mitred Lip: pointed French / bayleaf triangular leaf pointing up
    createPart(`${name}_UpperLip`, coneGeo(radius * 0.55, radius * 0.75, 4), goldLeaf, {
      position: [posX + radius * 0.62, mouthY + radius * 0.48, posZ],
      rotation: [0, 45, 0],
      parent
    });

    // Lower Mitred Lip: inverted triangular leaf pointing down into foot
    createPart(`${name}_LowerLip`, coneGeo(radius * 0.45, radius * 0.58, 4), goldLeaf, {
      position: [posX + radius * 0.58, mouthY - radius * 0.38, posZ],
      rotation: [180, 45, 0],
      parent
    });
  }

  // --- CENTRAL TOWER (Semi-circular forward bay at Z = 0) ---
  createPart('CenterTowerShelf', cylinderGeo(0.58, 0.58, 0.12, 16), woodDark, {
    position: [0.08, 1.86, 0],
    parent: caseGroup
  });
  createPart('CenterTowerShelfTrim', cylinderGeo(0.62, 0.58, 0.08, 16), woodLight, {
    position: [0.08, 1.80, 0],
    parent: caseGroup
  });

  // Central Tower: 7 graduated pipes forming a semi-circular arc
  const centerPipes = [
    { z: -0.42, x: 0.14, r: 0.075, h: 2.7 },
    { z: -0.30, x: 0.25, r: 0.088, h: 3.0 },
    { z: -0.16, x: 0.33, r: 0.098, h: 3.3 },
    { z: 0.0,   x: 0.36, r: 0.108, h: 3.65 }, // King pipe (tallest)
    { z: 0.16,  x: 0.33, r: 0.098, h: 3.3 },
    { z: 0.30,  x: 0.25, r: 0.088, h: 3.0 },
    { z: 0.42,  x: 0.14, r: 0.075, h: 2.7 },
  ];
  centerPipes.forEach((p, idx) => {
    addDisplayPipe(`CenterPipe_${idx}`, p.x, 1.92, p.z, p.r, p.h, caseGroup);
  });

  // Central Tower Entablature & Cornice (centered at X = 0.06, radius 0.62; rear is at 0.06 - 0.62 = -0.56, safely in front of back wall -0.58)
  createPart('CenterTowerFrieze', cylinderGeo(0.58, 0.58, 0.24, 16), woodDark, {
    position: [0.06, 5.58, 0],
    parent: caseGroup
  });
  createPart('CenterTowerCorniceLower', cylinderGeo(0.62, 0.58, 0.1, 16), woodLight, {
    position: [0.06, 5.72, 0],
    parent: caseGroup
  });
  createPart('CenterTowerCorniceUpper', cylinderGeo(0.66, 0.62, 0.12, 16), woodDark, {
    position: [0.06, 5.82, 0],
    parent: caseGroup
  });
  // Central Tower Pediment Dome & Golden Finial
  createPart('CenterTowerDome', coneGeo(0.64, 0.36, 16), woodDark, {
    position: [0.06, 6.02, 0],
    parent: caseGroup
  });
  createPart('CenterTowerFinialBall', sphereGeo(0.12, 10, 8), goldLeaf, {
    position: [0.06, 6.28, 0],
    parent: caseGroup
  });
  createPart('CenterTowerFinialSpire', coneGeo(0.09, 0.4, 10), goldLeaf, {
    position: [0.06, 6.52, 0],
    parent: caseGroup
  });

  // --- CARVED PIPE SHADES (Center Tower) ---
  // Arched valence drape in gold leaf
  createPart('CenterPipeShadeArch', cylinderGeo(0.54, 0.54, 0.32, 16), goldLeaf, {
    position: [0.08, 5.34, 0],
    parent: caseGroup
  });
  // Carved drop-pendants hanging between pipes
  const shadePendantProto = createPart('ShadePendantProto', coneGeo(0.035, 0.22, 6), goldLeaf, {
    position: [0.35, 5.15, -0.36],
    rotation: [180, 0, 0],
    parent: caseGroup
  });
  const pendantZs = [-0.23, -0.08, 0.08, 0.23, 0.36];
  pendantZs.forEach((pz, idx) => {
    const px = Math.sqrt(Math.max(0, 0.35 * 0.35 - pz * pz * 0.6)) + 0.06;
    createInstance(`CenterPendant_${idx}`, shadePendantProto, {
      position: [px, 5.15, pz],
      rotation: [180, 0, 0],
      parent: caseGroup
    });
  });

  // Lower Toe Shades (carved scrollwork along the rackboard)
  createPart('CenterToeShade', boxGeo(0.1, 0.16, 0.86), goldLeaf, {
    position: [0.24, 2.02, 0],
    parent: caseGroup
  });

  // --- SIDE TOWERS (Pointed V-towers at Z = -1.6m and Z = +1.6m) ---
  const sideTowerConfigs = [
    { name: 'Left', zCenter: -1.6 },
    { name: 'Right', zCenter: 1.6 },
  ];

  sideTowerConfigs.forEach((st) => {
    const side = st.name;
    const tz = st.zCenter;

    // V-shaped base pedestal bracket
    createPart(`${side}TowerShelf`, boxGeo(0.56, 0.12, 0.72), woodDark, {
      position: [0.12, 1.86, tz],
      parent: caseGroup
    });
    createPart(`${side}TowerShelfBevel`, boxGeo(0.6, 0.08, 0.76), woodLight, {
      position: [0.12, 1.80, tz],
      parent: caseGroup
    });

    // 7 pipes in pointed V-profile
    const sidePipes = [
      { zOff: -0.30, x: 0.12, r: 0.07, h: 2.4 },
      { zOff: -0.20, x: 0.22, r: 0.08, h: 2.75 },
      { zOff: -0.10, x: 0.31, r: 0.09, h: 3.1 },
      { zOff: 0.0,   x: 0.36, r: 0.10, h: 3.4 }, // Side tower king pipe
      { zOff: 0.10,  x: 0.31, r: 0.09, h: 3.1 },
      { zOff: 0.20,  x: 0.22, r: 0.08, h: 2.75 },
      { zOff: 0.30,  x: 0.12, r: 0.07, h: 2.4 },
    ];
    sidePipes.forEach((sp, idx) => {
      addDisplayPipe(`${side}Pipe_${idx}`, sp.x, 1.92, tz + sp.zOff, sp.r, sp.h, caseGroup);
    });

    // Tower Entablature & Cornice
    createPart(`${side}TowerEntablature`, boxGeo(0.58, 0.22, 0.76), woodDark, {
      position: [0.12, 5.3, tz],
      parent: caseGroup
    });
    createPart(`${side}TowerCornice`, boxGeo(0.66, 0.14, 0.84), woodLight, {
      position: [0.14, 5.46, tz],
      parent: caseGroup
    });
    // Pointed pediment / cap & finial urn
    createPart(`${side}TowerPediment`, coneGeo(0.42, 0.44, 4), woodDark, {
      position: [0.14, 5.72, tz],
      rotation: [0, 45, 0],
      parent: caseGroup
    });
    createPart(`${side}TowerFinialUrn`, cylinderGeo(0.08, 0.05, 0.26, 10), goldLeaf, {
      position: [0.14, 6.04, tz],
      parent: caseGroup
    });
    createPart(`${side}TowerFinialFlame`, coneGeo(0.09, 0.28, 10), goldLeaf, {
      position: [0.14, 6.26, tz],
      parent: caseGroup
    });

    // Pipe shades (upper tracery & pendants)
    createPart(`${side}PipeShadeValence`, boxGeo(0.16, 0.28, 0.7), goldLeaf, {
      position: [0.22, 5.08, tz],
      parent: caseGroup
    });
    [-0.22, -0.07, 0.07, 0.22].forEach((pz, pIdx) => {
      createInstance(`${side}Pendant_${pIdx}`, shadePendantProto, {
        position: [0.28, 4.9, tz + pz],
        rotation: [180, 0, 0],
        parent: caseGroup
      });
    });
    // Lower toe shade
    createPart(`${side}ToeShade`, boxGeo(0.08, 0.15, 0.68), goldLeaf, {
      position: [0.22, 2.02, tz],
      parent: caseGroup
    });
  });

  // --- INTERMEDIATE FLATS (Between center tower and side towers) ---
  const flatConfigs = [
    { name: 'LeftFlat', zCenter: -0.91 },
    { name: 'RightFlat', zCenter: 0.91 },
  ];

  flatConfigs.forEach((fl, fIdx) => {
    const fName = fl.name;
    const fz = fl.zCenter;
    const sign = fIdx === 0 ? -1 : 1;

    // 7 pipes in ascending slope toward side tower
    const flatPipes = [
      { zOff: -sign * 0.24, r: 0.055, h: 2.05 },
      { zOff: -sign * 0.16, r: 0.058, h: 2.18 },
      { zOff: -sign * 0.08, r: 0.062, h: 2.32 },
      { zOff: 0.0,          r: 0.066, h: 2.48 },
      { zOff: sign * 0.08,  r: 0.070, h: 2.64 },
      { zOff: sign * 0.16,  r: 0.074, h: 2.82 },
      { zOff: sign * 0.24,  r: 0.078, h: 3.0 },
    ];
    flatPipes.forEach((fp, idx) => {
      addDisplayPipe(`${fName}Pipe_${idx}`, 0.06, 1.92, fz + fp.zOff, fp.r, fp.h, caseGroup);
    });

    // Flat Entablature & Cornice
    createPart(`${fName}Entablature`, boxGeo(0.36, 0.18, 0.66), woodDark, {
      position: [0.06, 4.88, fz],
      parent: caseGroup
    });
    createPart(`${fName}Cornice`, boxGeo(0.42, 0.1, 0.72), woodLight, {
      position: [0.08, 5.0, fz],
      parent: caseGroup
    });

    // Pierced Tracery Pipe Shade (carved foliage valance)
    createPart(`${fName}Shade`, boxGeo(0.08, 0.36, 0.62), goldLeaf, {
      position: [0.1, 4.65, fz],
      parent: caseGroup
    });
    // Hanging drops
    [-0.2, 0.0, 0.2].forEach((pz, pIdx) => {
      createInstance(`${fName}Drop_${pIdx}`, shadePendantProto, {
        position: [0.12, 4.42, fz + pz],
        rotation: [180, 0, 0],
        parent: caseGroup
      });
    });

    // Flat roof cresting (carved decorative balustrade along top)
    createPart(`${fName}Cresting`, boxGeo(0.06, 0.22, 0.68), goldLeaf, {
      position: [0.08, 5.16, fz],
      parent: caseGroup
    });
  });

  // =========================================================================
  // 2. DETACHED CONSOLE (Centred at X = 1.48m, facing +X towards organist)
  // Complete church organ console with enclosed case, stepped manuals,
  // angled stop jambs, music desk, brass sconce lamps, and pedalboard.
  // =========================================================================
  const consoleGroup = createPivot('Console', [1.48, 0, 0], root);

  // Console plinth / base
  createPart('ConsolePlinth', boxGeo(0.82, 0.08, 1.48), woodDark, {
    position: [0, 0.08, 0],
    parent: consoleGroup
  });

  // Lower cabinet body
  createPart('ConsoleCabinet', boxGeo(0.74, 0.88, 1.4), woodMedium, {
    position: [-0.02, 0.54, 0],
    parent: consoleGroup
  });

  // Side panels with raised mouldings (full height up to Y = 1.34m)
  [-0.71, 0.71].forEach((sideZ, sIdx) => {
    const sName = sIdx === 0 ? 'Left' : 'Right';
    // Lower side panel
    createPart(`ConsoleLowerSide_${sName}`, boxGeo(0.66, 0.74, 0.04), woodDark, {
      position: [-0.02, 0.54, sideZ],
      parent: consoleGroup
    });
    // Upper side cheek case (enclosing the upper console, stop jambs, and desk)
    createPart(`ConsoleUpperSide_${sName}`, boxGeo(0.66, 0.44, 0.04), woodDark, {
      position: [-0.02, 1.13, sideZ],
      parent: consoleGroup
    });
    // Curved roll-top track moulding along upper cheek
    createPart(`ConsoleRollTrack_${sName}`, cylinderZGeo(0.05, 0.05, 0.05, 10), woodLight, {
      position: [0.28, 1.05, sideZ],
      parent: consoleGroup
    });
  });

  // Upper back panel enclosing behind the music desk up to the top lid
  createPart('ConsoleUpperBack', boxGeo(0.04, 0.44, 1.38), woodDark, {
    position: [-0.33, 1.13, 0],
    parent: consoleGroup
  });

  // Knee well / leg opening for the organist in the center
  createPart('ConsolePedestalL', boxGeo(0.68, 0.68, 0.26), woodDark, {
    position: [0.01, 0.44, -0.56],
    parent: consoleGroup
  });
  createPart('ConsolePedestalR', boxGeo(0.68, 0.68, 0.26), woodDark, {
    position: [0.01, 0.44, 0.56],
    parent: consoleGroup
  });
  createPart('KneeWellBack', boxGeo(0.04, 0.66, 0.88), woodDark, {
    position: [-0.28, 0.44, 0],
    parent: consoleGroup
  });

  // Keybed shelf (supports the manuals)
  createPart('KeybedShelf', boxGeo(0.58, 0.06, 1.18), woodDark, {
    position: [0.15, 0.73, 0],
    parent: consoleGroup
  });

  // Carved Console Cheeks (side cheeks enclosing the manuals)
  [-0.48, 0.48].forEach((cz, cIdx) => {
    const side = cIdx === 0 ? 'Left' : 'Right';
    createPart(`CheekBlock_${side}`, boxGeo(0.44, 0.26, 0.06), woodDark, {
      position: [0.14, 0.88, cz],
      parent: consoleGroup
    });
    createPart(`CheekScroll_${side}`, cylinderZGeo(0.07, 0.07, 0.07, 10), woodMedium, {
      position: [0.35, 0.82, cz],
      parent: consoleGroup
    });
  });

  // --- TWO MANUALS (Swell & Great Keyboards) ---
  // Stepped arrangement: Great (lower) at Y = 0.78, Swell (upper) at Y = 0.86
  const manuals = [
    { name: 'ManualGreat', posX: 0.22, posY: 0.78 },
    { name: 'ManualSwell', posX: 0.13, posY: 0.86 },
  ];

  let sharpKeyProto = null;
  manuals.forEach((m) => {
    // Ivory naturals keybed
    createPart(`${m.name}_Naturals`, boxGeo(0.14, 0.026, 0.84), keyWhite, {
      position: [m.posX, m.posY, 0],
      parent: consoleGroup
    });
    // Front lip bevel of natural keys
    createPart(`${m.name}_KeyLip`, boxGeo(0.02, 0.03, 0.84), keyWhite, {
      position: [m.posX + 0.07, m.posY - 0.002, 0],
      parent: consoleGroup
    });
    // Red felt strip behind keys
    createPart(`${m.name}_Felt`, boxGeo(0.024, 0.03, 0.84), feltRed, {
      position: [m.posX - 0.075, m.posY + 0.008, 0],
      parent: consoleGroup
    });

    // Black sharp keys in realistic octave pattern (groups of 2 and 3)
    const octaves = [-0.34, -0.17, 0.0, 0.17, 0.34];
    octaves.forEach((octZ, octIdx) => {
      const sharpOffsets = [-0.056, -0.028, 0.014, 0.040, 0.064];
      sharpOffsets.forEach((off, sIdx) => {
        const kZ = octZ + off;
        if (kZ < -0.40 || kZ > 0.40) return;

        if (!sharpKeyProto) {
          sharpKeyProto = createPart('SharpKeyProto', boxGeo(0.088, 0.024, 0.012), keyBlack, {
            position: [m.posX - 0.025, m.posY + 0.022, kZ],
            parent: consoleGroup
          });
        } else {
          createInstance(`${m.name}_Sharp_${octIdx}_${sIdx}`, sharpKeyProto, {
            position: [m.posX - 0.025, m.posY + 0.022, kZ],
            parent: consoleGroup
          });
        }
      });
    });
  });

  // --- STOP KNOBS DOWN EACH CHEEK ---
  // Terraced angled stop jambs (angled at 30 deg towards organist)
  let stopStemProto = null;
  let stopHeadProto = null;
  [-0.44, 0.44].forEach((jambZ, sideIdx) => {
    const side = sideIdx === 0 ? 'Left' : 'Right';
    const sign = sideIdx === 0 ? -1 : 1;

    // Angled stop jamb panel
    createPart(`StopJambPanel_${side}`, boxGeo(0.38, 0.38, 0.12), woodDark, {
      position: [0.08, 0.94, jambZ + sign * 0.16],
      rotation: [0, -sign * 25, 0],
      parent: consoleGroup
    });

    // 2 columns of 6 stop knobs on each jamb (24 stop knobs total)
    for (let col = 0; col < 2; col++) {
      for (let row = 0; row < 6; row++) {
        const kX = 0.20 - col * 0.07;
        const kY = 0.80 + row * 0.056;
        const kZ = jambZ + sign * (0.10 + col * 0.08);

        if (!stopStemProto) {
          stopStemProto = createPart('StopStemProto', cylinderXGeo(0.007, 0.007, 0.04, 6), woodDark, {
            position: [kX, kY, kZ],
            parent: consoleGroup
          });
          stopHeadProto = createPart('StopHeadProto', cylinderXGeo(0.016, 0.013, 0.018, 8), stopKnobWhite, {
            position: [kX + 0.025, kY, kZ],
            parent: consoleGroup
          });
          createPart('StopFaceLabel', decalBox(0.002, 0.02, 0.02), woodDark, {
            position: [kX + 0.035, kY, kZ],
            parent: consoleGroup
          });
        } else {
          createInstance(`StopStem_${side}_${col}_${row}`, stopStemProto, {
            position: [kX, kY, kZ],
            parent: consoleGroup
          });
          createInstance(`StopHead_${side}_${col}_${row}`, stopHeadProto, {
            position: [kX + 0.025, kY, kZ],
            parent: consoleGroup
          });
        }
      }
    }
  });

  // --- MUSIC DESK & CONSOLE TOP ---
  // Slanted music desk with slatted rack
  createPart('MusicDeskLedge', boxGeo(0.09, 0.03, 0.82), woodMedium, {
    position: [0.02, 0.98, 0],
    parent: consoleGroup
  });
  createPart('MusicDeskBack', boxGeo(0.024, 0.32, 0.8), woodDark, {
    position: [-0.04, 1.15, 0],
    rotation: [-15, 0, 0],
    parent: consoleGroup
  });
  // Carved desk grille slats
  for (let s = -3; s <= 3; s++) {
    createPart(`MusicDeskSlat_${s}`, boxGeo(0.02, 0.28, 0.025), woodLight, {
      position: [-0.028, 1.15, s * 0.11],
      rotation: [-15, 0, 0],
      parent: consoleGroup
    });
  }
  // Open Sheet Music Score on desk
  createPart('SheetMusicBook', boxGeo(0.012, 0.25, 0.36), scorePaper, {
    position: [-0.01, 1.15, 0],
    rotation: [-15, 0, 0],
    parent: consoleGroup
  });
  createPart('SheetMusicStaffL', boxGeo(0.014, 0.008, 0.13), woodDark, {
    position: [-0.002, 1.18, -0.08],
    rotation: [-15, 0, 0],
    parent: consoleGroup
  });
  createPart('SheetMusicStaffR', boxGeo(0.014, 0.008, 0.13), woodDark, {
    position: [-0.002, 1.18, 0.08],
    rotation: [-15, 0, 0],
    parent: consoleGroup
  });

  // Console Brass Desk Lamps (left and right sconces)
  [-0.45, 0.45].forEach((lampZ, lIdx) => {
    const side = lIdx === 0 ? 'Left' : 'Right';
    createPart(`DeskLampBase_${side}`, cylinderGeo(0.035, 0.04, 0.04, 8), brassMat, {
      position: [-0.02, 1.0, lampZ],
      parent: consoleGroup
    });
    createPart(`DeskLampStem_${side}`, cylinderGeo(0.012, 0.012, 0.22, 6), brassMat, {
      position: [-0.02, 1.12, lampZ],
      parent: consoleGroup
    });
    createPart(`DeskLampShade_${side}`, taperConeGeo(0.03, 0.065, 0.1, 'y', 10), brassMat, {
      position: [0.0, 1.25, lampZ],
      rotation: [25, 0, 0],
      parent: consoleGroup
    });
  });

  // Console Top Canopy / Roll-top Lid Housing (solidly rests on upper side and back panels at Y = 1.35m)
  createPart('ConsoleTopLid', boxGeo(0.74, 0.06, 1.46), woodDark, {
    position: [-0.03, 1.36, 0],
    parent: consoleGroup
  });
  createPart('ConsoleTopMoulding', boxGeo(0.78, 0.04, 1.5), woodLight, {
    position: [-0.02, 1.40, 0],
    parent: consoleGroup
  });

  // =========================================================================
  // 3. PEDALBOARD (Sits in the knee well on the dais floor Y = 0.04..0.18)
  // Concave radial 25-note pedalboard with expression shoes
  // =========================================================================
  const pedalGroup = createPivot('Pedalboard', [0, 0, 0], consoleGroup);

  // Pedal Frame (Toe rail, Heel rail, Side blocks)
  createPart('PedalFrameHeel', boxGeo(0.08, 0.06, 1.08), woodDark, {
    position: [0.46, 0.07, 0],
    parent: pedalGroup
  });
  createPart('PedalFrameToe', boxGeo(0.1, 0.09, 1.08), woodDark, {
    position: [-0.28, 0.08, 0],
    parent: pedalGroup
  });
  createPart('PedalFrameLeft', boxGeo(0.82, 0.08, 0.07), woodDark, {
    position: [0.09, 0.08, -0.54],
    parent: pedalGroup
  });
  createPart('PedalFrameRight', boxGeo(0.82, 0.08, 0.07), woodDark, {
    position: [0.09, 0.08, 0.54],
    parent: pedalGroup
  });

  // 25 Pedals: Concave curvature (middle pedals lower at Y=0.065, edge pedals Y=0.09)
  let pedalSharpProto = null;
  let pedalNatProto = null;
  const numPedals = 25;
  for (let i = 0; i < numPedals; i++) {
    const frac = i / (numPedals - 1);
    const pZ = -0.46 + frac * 0.92;
    // Slight concave arch
    const archY = Math.pow(Math.abs(frac - 0.5) * 2, 2) * 0.025;
    const isSharp = (i % 7 === 1 || i % 7 === 2 || i % 7 === 4 || i % 7 === 5 || i % 7 === 6);

    if (isSharp) {
      if (!pedalSharpProto) {
        pedalSharpProto = createPart('PedalSharpProto', boxGeo(0.38, 0.04, 0.024), woodDark, {
          position: [-0.08, 0.10 + archY, pZ],
          parent: pedalGroup
        });
        createPart('PedalSharpCapProto', boxGeo(0.38, 0.012, 0.022), keyBlack, {
          position: [-0.08, 0.125 + archY, pZ],
          parent: pedalGroup
        });
      } else {
        createInstance(`PedalSharp_${i}`, pedalSharpProto, {
          position: [-0.08, 0.10 + archY, pZ],
          parent: pedalGroup
        });
      }
    } else {
      if (!pedalNatProto) {
        pedalNatProto = createPart('PedalNatProto', boxGeo(0.72, 0.028, 0.026), woodLight, {
          position: [0.08, 0.065 + archY, pZ],
          parent: pedalGroup
        });
      } else {
        createInstance(`PedalNatural_${i}`, pedalNatProto, {
          position: [0.08, 0.065 + archY, pZ],
          parent: pedalGroup
        });
      }
    }
  }

  // Swell & Crescendo Expression Shoes (mounted into knee well)
  [-0.08, 0.08].forEach((shoeZ, sIdx) => {
    const sName = sIdx === 0 ? 'SwellShoe' : 'CrescendoShoe';
    createPart(sName, boxGeo(0.18, 0.035, 0.1), keyBlack, {
      position: [-0.18, 0.22, shoeZ],
      rotation: [16, 0, 0],
      parent: pedalGroup
    });
    createPart(`${sName}Plate`, boxGeo(0.14, 0.01, 0.08), brassMat, {
      position: [-0.18, 0.24, shoeZ],
      rotation: [16, 0, 0],
      parent: pedalGroup
    });
  });

  // Toe Studs (metal combination pistons along the toe frame)
  for (let t = -3; t <= 3; t++) {
    createPart(`ToeStud_${t}`, sphereGeo(0.016, 6, 6), brassMat, {
      position: [-0.22, 0.14, t * 0.12],
      parent: pedalGroup
    });
  }

  // =========================================================================
  // 4. ORGAN BENCH (Centred at X = 2.22m, facing -X towards console)
  // Perfectly spaced at ~0.30m from the keybed for authentic organ ergonomics.
  // =========================================================================
  const benchGroup = createPivot('OrganBench', [2.22, 0, 0], root);

  // Bench Seat (wide contoured oak plank)
  createPart('BenchSeat', boxGeo(0.36, 0.05, 1.26), woodMedium, {
    position: [0, 0.62, 0],
    parent: benchGroup
  });
  createPart('BenchSeatBevel', boxGeo(0.38, 0.02, 1.28), woodLight, {
    position: [0, 0.645, 0],
    parent: benchGroup
  });

  // Music storage shelf under the bench seat
  createPart('BenchMusicShelf', boxGeo(0.28, 0.03, 1.08), woodDark, {
    position: [0, 0.53, 0],
    parent: benchGroup
  });

  // Trestle Legs (Left & Right shaped uprights)
  [-0.52, 0.52].forEach((legZ, lIdx) => {
    const side = lIdx === 0 ? 'Left' : 'Right';
    // Main vertical leg upright
    createPart(`BenchLeg_${side}`, boxGeo(0.24, 0.54, 0.05), woodDark, {
      position: [0, 0.32, legZ],
      parent: benchGroup
    });
    // Arched cutout in leg (decorative panel)
    createPart(`BenchLegArch_${side}`, cylinderXGeo(0.07, 0.07, 0.06, 10), woodLight, {
      position: [0, 0.22, legZ],
      parent: benchGroup
    });
    // Sturdy wide foot on the dais floor
    createPart(`BenchFoot_${side}`, boxGeo(0.34, 0.07, 0.09), woodDark, {
      position: [0, 0.06, legZ],
      parent: benchGroup
    });
    // Top bracket supporting seat
    createPart(`BenchTopBracket_${side}`, boxGeo(0.32, 0.05, 0.08), woodMedium, {
      position: [0, 0.58, legZ],
      parent: benchGroup
    });
  });

  // Central Stretcher rail connecting the two legs
  createPart('BenchStretcher', boxGeo(0.06, 0.09, 1.02), woodMedium, {
    position: [0, 0.24, 0],
    parent: benchGroup
  });
  // Wedge keys pinning the stretcher through each leg
  [-0.56, 0.56].forEach((keyZ, kIdx) => {
    createPart(`BenchWedge_${kIdx}`, boxGeo(0.04, 0.12, 0.03), woodLight, {
      position: [0, 0.24, keyZ],
      parent: benchGroup
    });
  });

  return root;
}

Brief and retained revisions

No brief or earlier revisions are recorded. The source download contains the version built for this page.