{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shale-shaker",
  "type": "registry:component",
  "title": "Shale shaker (linear motion)",
  "description": "Linear-motion shale shaker sized on a 4-panel unit (3.3 × 2.0 m, 1.17 × 0.66 m pretensioned panels ≈ 3.1 m² of screen, first panel flat then inclined by `deckAngle`). Spring-mounted basket with wedge clamps, optional stacked scalping decks (`decks`), two counter-rotating vibrator motors on an inclined beam giving a 45° line of action; the basket vibrates when `running`. Fixed possum-belly feeder with weir, mud level, bypass gate and flanged feed nozzle, underflow pan draining through the tank-top opening, discharge lip, starter panel. Origin on the mud tank top.",
  "dependencies": [
    "three",
    "@react-three/fiber",
    "@react-three/drei"
  ],
  "registryDependencies": [
    "https://deck-twin-library.demoareeb.workers.dev/r/core.json"
  ],
  "files": [
    {
      "path": "drilling/shale-shaker.tsx",
      "type": "registry:file",
      "target": "components/deck-twin/drilling/shale-shaker.tsx",
      "content": "// Linear-motion shale shaker (API RP 13C screens): spring-mounted basket, pretensioned panels, twin vibrator motors, possum-belly feeder, underflow pan.\nimport * as THREE from 'three'\nimport { useEffect, useMemo, useRef } from 'react'\nimport { useFrame } from '@react-three/fiber'\nimport { Kit, type MatKey, type V3 } from '../lib/kit'\nimport { paint } from '../lib/materials'\nimport { KitMesh, type GroupProps } from '../lib/mesh'\nimport { flange, junctionBox, liftingLug, member, tagPlate } from '../lib/parts'\nimport { port, type Port } from '../lib/ports'\nimport { meshGuardMaterial } from '../lib/textures'\nimport { pipeOD, pipeWall } from '../lib/standards'\n\nexport interface ShaleShakerProps {\n  /** screen panels along the main deck */\n  panels?: number\n  /** stacked screen decks (1 = single deck, 2–3 add scalping decks) */\n  decks?: number\n  /** deck angle of the inclined panels, degrees */\n  deckAngle?: number\n  /** feed flange, inches */\n  feedNps?: number\n  color?: string\n  tag?: string\n  /** vibrate the basket */\n  running?: boolean\n}\n\nexport const defaults = { panels: 4, decks: 1, deckAngle: 5, feedNps: 10, color: '#2f7d4f', tag: 'SS-101', running: true }\n\ntype P = typeof defaults\n\n// typical 4-panel unit: 3.3 × 2.0 m footprint, 1.17 × 0.66 m panels (≈ 3.1 m² screen), weir 0.84 m\nfunction layout(p: Omit<P, 'tag' | 'running' | 'color'>) {\n  const np = Math.max(2, Math.min(6, Math.round(p.panels)))\n  const pl = 0.66\n  const bw = 1.17\n  const xB0 = -1.0\n  const xB1 = xB0 + np * pl + 0.04\n  const xF = xB0 - 0.66\n  const L = xB1 - xF\n  const W = 2.03\n  const yd0 = 0.52\n  const yWeir = 0.84\n  const feed: V3 = [xF - 0.22, 0.42, 0]\n  return { np, pl, bw, xB0, xB1, xF, L, W, yd0, yWeir, feed, cx: (xF + xB1) / 2 }\n}\n\nexport function ports(props: ShaleShakerProps = {}): Port[] {\n  const p = { ...defaults, ...props }\n  const l = layout(p)\n  return [port('feed', 'Feed (flowline)', [l.feed[0] - l.cx, l.feed[1], l.feed[2]], [-1, 0, 0], p.feedNps, 150)]\n}\n\nexport function ShaleShaker({ tag = defaults.tag, ...rest }: ShaleShakerProps & GroupProps) {\n  const { panels, decks, deckAngle, feedNps, color, running, ...group } = { ...defaults, ...rest }\n  const out = useMemo(() => build({ panels, decks, deckAngle, feedNps, color, tag, running }), [panels, decks, deckAngle, feedNps, color, tag])\n  useEffect(() => () => [out.fixed, out.basket].forEach((b) => b.geometry.dispose()), [out])\n  const g = useRef<THREE.Group>(null)\n  const t = useRef(0)\n  useFrame((_, dt) => {\n    if (!g.current) return\n    if (!running) return void g.current.position.set(0, 0, 0)\n    t.current += Math.min(dt, 0.05) * 2 * Math.PI * 22\n    const s = Math.sin(t.current) * 0.004\n    g.current.position.set(out.dir[0] * s, out.dir[1] * s, 0)\n  })\n  return (\n    <group {...group}>\n      <KitMesh data={out.fixed} />\n      <group ref={g}>\n        <KitMesh data={out.basket} />\n      </group>\n    </group>\n  )\n}\n\nexport default ShaleShaker\n\nfunction build(p: P) {\n  const l = layout(p)\n  const { np, pl, bw, xB0, xB1, xF, W, yd0, yWeir } = l\n  const k = new Kit()\n  const b = new Kit()\n  const C: MatKey = paint(p.color)\n  const ang = (Math.max(-3, Math.min(8, p.deckAngle)) * Math.PI) / 180\n  const nd = Math.max(1, Math.min(3, Math.round(p.decks)))\n  const sx = -l.cx\n  const zW = bw / 2 + 0.012\n  // deck surface height along x (first panel flat, the rest inclined)\n  const deckY = (x: number) => yd0 + Math.max(0, x - (xB0 + pl)) * Math.tan(ang)\n  const wallTop = 0.34 + (nd - 1) * 0.22\n\n  k.at([sx, 0, 0], undefined, () => {\n    // base frame of channels with cross members and spring posts\n    for (const s of [-1, 1]) member(k, [xF, 0.08, s * (W / 2 - 0.04)], [xB1, 0.08, s * (W / 2 - 0.04)], 'UPN 160', 'structSteelDark', [0, 1, 0])\n    for (const x of [xF + 0.04, xB0 + 0.05, (xB0 + xB1) / 2 + 0.45, xB1 - 0.04]) member(k, [x, 0.08, -W / 2 + 0.08], [x, 0.08, W / 2 - 0.08], 'UPN 160', 'structSteelDark')\n    for (const sxz of [-1, 1]) for (const sz of [-1, 1]) liftingLug(k, [sxz > 0 ? xB1 - 0.25 : xF + 0.25, 0.16, sz * (W / 2 - 0.02)], 'x', 1, 'structSteelDark')\n    for (const x of [xB0 + 0.2, xB1 - 0.2])\n      for (const s of [-1, 1]) {\n        k.box('structSteelDark', [0.24, 0.12, 0.24], [x, 0.22, s * (zW + 0.2)])\n        k.box('structSteelDark', [0.06, 0.12, 0.3], [x, 0.22, s * (zW + 0.2 + 0.0)])\n      }\n\n    // underflow pan: sloped floor to a centre trough with an outlet through the tank-top opening\n    const px0 = xB0 + 0.05\n    const px1 = xB1 - 0.08\n    const xm = (px0 + px1) / 2\n    for (const s of [-1, 1]) {\n      const x0 = s < 0 ? px0 : px1\n      const len = Math.abs(xm - x0)\n      const drop = 0.1\n      k.box('galvanised', [Math.hypot(len, drop), 0.006, bw - 0.02], [(x0 + xm) / 2, 0.29, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), s * Math.atan2(drop, len)))\n    }\n    for (const s of [-1, 1]) k.box('galvanised', [px1 - px0, 0.22, 0.006], [xm, 0.3, s * (bw / 2 - 0.01)])\n    for (const x of [px0, px1]) k.box('galvanised', [0.006, 0.22, bw - 0.02], [x, 0.3, 0])\n    k.box('galvanised', [0.3, 0.16, 0.5], [xm, 0.16, 0])\n    k.box('galvanised', [0.24, 0.16, 0.44], [xm, 0.04, 0])\n\n    // possum belly feeder (fixed): weir box, mud level, inlet nozzle, bypass gate, flared apron onto the first panel\n    const fx0 = xF + 0.02\n    const fx1 = xB0 - 0.04\n    const fW = bw + 0.1\n    for (const s of [-1, 1]) k.box(C, [fx1 - fx0, yWeir - 0.16, 0.01], [(fx0 + fx1) / 2, (yWeir + 0.16) / 2, s * fW / 2])\n    k.box(C, [0.01, yWeir - 0.16 + 0.12, fW], [fx0, (yWeir + 0.16 + 0.12) / 2, 0])\n    k.box(C, [0.01, yWeir - 0.16, fW], [fx1, (yWeir + 0.16) / 2, 0])\n    k.box(C, [fx1 - fx0, 0.01, fW], [(fx0 + fx1) / 2, 0.165, 0])\n    k.box(C, [fx1 - fx0 + 0.04, 0.04, 0.04], [(fx0 + fx1) / 2, yWeir + 0.02, fW / 2 + 0.02])\n    k.box(C, [fx1 - fx0 + 0.04, 0.04, 0.04], [(fx0 + fx1) / 2, yWeir + 0.02, -fW / 2 - 0.02])\n    k.box(paint('#5b4733', { metalness: 0, roughness: 0.25, weather: 0 }), [fx1 - fx0 - 0.02, 0.005, fW - 0.02], [(fx0 + fx1) / 2, yWeir - 0.05, 0])\n    // apron from the weir lip down onto the first panel\n    const ax1 = xB0 + 0.06\n    const ay1 = deckY(xB0) + 0.06\n    k.box('stainless', [Math.hypot(ax1 - fx1, yWeir - ay1), 0.006, bw - 0.04], [(fx1 + ax1) / 2, (yWeir + ay1) / 2, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.atan2(yWeir - ay1, ax1 - fx1)))\n    // feed nozzle and flange\n    const f = l.feed\n    const fl = flange(k, p.feedNps, 150, f, '-x', true)\n    k.pipe(C, [fx0 + 0.02, f[1], 0], [f[0] + fl, f[1], 0], pipeOD(p.feedNps), pipeWall(p.feedNps))\n    // bypass gate lever and its pivot on the side\n    k.rod('stainless', [(fx0 + fx1) / 2, 0.26, -fW / 2 - 0.01], [(fx0 + fx1) / 2, 0.26, -fW / 2 - 0.08], 0.012, 8)\n    k.rod('safetyYellow', [(fx0 + fx1) / 2, 0.26, -fW / 2 - 0.08], [(fx0 + fx1) / 2 + 0.25, 0.5, -fW / 2 - 0.08], 0.012, 8)\n    for (const s of [-1, 1]) k.box('structSteelDark', [0.06, 0.16, 0.06], [(fx0 + fx1) / 2, 0.08, s * fW / 2])\n\n    // motor starter panel on a post, cables to the motors\n    const cx = xF + 0.35\n    const cz = W / 2 - 0.12\n    k.box('structSteelDark', [0.06, 1.2, 0.06], [cx, 0.76, cz])\n    k.box('structSteelDark', [0.18, 0.02, 0.18], [cx, 0.17, cz])\n    junctionBox(k, [cx, 1.05, cz + 0.03], 'z', [0.32, 0.4, 0.18], 'stainless')\n    k.spline('cable', [[cx, 0.84, cz + 0.12], [cx + 0.4, 0.8, cz + 0.2], [0.4, 1.2, zW + 0.2], [0.42, 1.18, 0.4]], 0.012, 6, 30)\n\n    tagPlate(k, p.tag, [xF + 0.9, 0.08, W / 2 + 0.002], 'z', 0.24)\n    tagPlate(k, 'API RP 13C', [xF + 1.3, 0.08, W / 2 + 0.002], 'z', 0.2)\n  })\n\n  // ---- vibrating basket\n  b.at([sx, 0, 0], undefined, () => {\n    // side walls following the deck, with stiffeners, spring brackets and discharge lip\n    for (const s of [-1, 1]) {\n      const sh = new THREE.Shape()\n      sh.moveTo(xB0, deckY(xB0) - 0.12)\n      sh.lineTo(xB0 + pl, deckY(xB0 + pl) - 0.12)\n      sh.lineTo(xB1, deckY(xB1) - 0.12)\n      sh.lineTo(xB1, deckY(xB1) + 0.12)\n      sh.lineTo(xB0 + pl * 1.2, deckY(xB0 + pl * 1.2) + wallTop)\n      sh.lineTo(xB0, deckY(xB0) + wallTop)\n      sh.closePath()\n      b.extrude(C, sh, 0.012, new THREE.Matrix4().makeTranslation(0, 0, s > 0 ? zW - 0.012 : -zW), 2)\n      for (let i = 0; i <= np; i++) {\n        const x = xB0 + 0.02 + i * pl\n        const y0 = deckY(x) - 0.12\n        const y1 = Math.min(deckY(x) + wallTop, i === np ? deckY(xB1) + 0.12 : 9)\n        b.box(C, [0.05, y1 - y0, 0.04], [x, (y0 + y1) / 2, s * (zW + 0.02)])\n      }\n      b.box(C, [xB1 - xB0, 0.05, 0.05], [(xB0 + xB1) / 2, deckY((xB0 + xB1) / 2) - 0.1, s * (zW + 0.025)], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), ang * 0.75))\n      for (const x of [xB0 + 0.2, xB1 - 0.2]) {\n        b.box(C, [0.28, 0.02, 0.32], [x, 0.44, s * (zW + 0.2)])\n        b.box(C, [0.02, deckY(x) - 0.44, 0.3], [x, (deckY(x) + 0.44) / 2 - 0.06, s * (zW + 0.16)])\n      }\n    }\n    // spring sets between basket brackets and base posts\n    for (const x of [xB0 + 0.2, xB1 - 0.2])\n      for (const s of [-1, 1])\n        for (const dx of [-0.06, 0.06]) {\n          const pts: V3[] = []\n          for (let i = 0; i <= 96; i++) {\n            const a = (i / 96) * Math.PI * 2 * 6\n            pts.push([x + dx + Math.cos(a) * 0.045, 0.28 + (i / 96) * 0.15, s * (zW + 0.2) + Math.sin(a) * 0.045])\n          }\n          b.spline('actuatorRed', pts, 0.008, 6, 160)\n        }\n    // screen panels: frame, support grid, fine mesh; wedge clamps along the walls; then stacked scalping decks\n    const screen = (x0: number, x1: number, y0: number, y1: number) => {\n      const len = Math.hypot(x1 - x0, y1 - y0)\n      const q = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.atan2(y1 - y0, x1 - x0))\n      const c: V3 = [(x0 + x1) / 2, (y0 + y1) / 2, 0]\n      b.with(new THREE.Matrix4().compose(new THREE.Vector3(...c), q, new THREE.Vector3(1, 1, 1)), () => {\n        for (const s of [-1, 1]) {\n          b.box('galvanised', [len - 0.004, 0.03, 0.03], [0, -0.015, s * (bw / 2 - 0.015)])\n          b.box('galvanised', [0.03, 0.03, bw - 0.004], [s * (len / 2 - 0.017), -0.015, 0])\n        }\n        for (let i = 1; i < 6; i++) b.box('galvanised', [0.008, 0.025, bw - 0.06], [-len / 2 + (len * i) / 6, -0.016, 0])\n        for (let i = 1; i < 3; i++) b.box('galvanised', [len - 0.06, 0.025, 0.008], [0, -0.016, -bw / 2 + (bw * i) / 3])\n        const g = new THREE.PlaneGeometry(len - 0.04, bw - 0.04)\n        const uv = g.getAttribute('uv') as THREE.BufferAttribute\n        for (let i = 0; i < uv.count; i++) uv.setXY(i, uv.getX(i) * len * 16, uv.getY(i) * bw * 16)\n        b.add(meshGuardMaterial('#d4d7d9'), g, new THREE.Matrix4().makeRotationX(-Math.PI / 2).setPosition(0, 0.001, 0))\n        for (const s of [-1, 1]) b.box('rubberBlack', [len - 0.02, 0.012, 0.02], [0, 0.006, s * (bw / 2 - 0.01)])\n      })\n    }\n    for (let i = 0; i < np; i++) {\n      const x0 = xB0 + 0.02 + i * pl\n      const x1 = x0 + pl\n      screen(x0, x1, deckY(x0), deckY(x1))\n      for (const s of [-1, 1]) b.box('nut', [0.12, 0.06, 0.03], [x0 + pl / 2, deckY(x0 + pl / 2) + 0.04, s * (bw / 2 - 0.02)])\n    }\n    for (let d = 1; d < nd; d++) {\n      const n = Math.max(2, np - d)\n      for (let i = 0; i < n; i++) {\n        const x0 = xB0 + 0.02 + i * pl\n        const x1 = x0 + pl\n        screen(x0, x1, deckY(x0) + d * 0.22 + 0.02, deckY(x1) + d * 0.22 + (x1 - x0) * Math.tan(ang * 0.5))\n      }\n    }\n    // discharge lip and cuttings chute\n    b.box('stainless', [0.22, 0.008, bw], [xB1 + 0.1, deckY(xB1) - 0.03, 0], new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -0.35))\n    // motor beam (inclined for a 45° line of action relative to the deck) and two counter-rotating vibrator motors\n    const xm = xB0 + np * pl * 0.48\n    const ym = deckY(xm) + wallTop + 0.06\n    const qb = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), -(Math.PI / 4 - ang))\n    b.with(new THREE.Matrix4().compose(new THREE.Vector3(xm, ym, 0), qb, new THREE.Vector3(1, 1, 1)), () => {\n      b.box(C, [0.36, 0.03, bw + 0.08], [0, 0, 0])\n      b.box(C, [0.3, 0.16, 0.012], [0, -0.08, bw / 2 + 0.03])\n      b.box(C, [0.3, 0.16, 0.012], [0, -0.08, -bw / 2 - 0.03])\n      for (const s of [-1, 1]) {\n        const z = s * 0.3\n        b.box('motorPaint', [0.3, 0.05, 0.36], [0, 0.04, z])\n        b.cyl('motorPaint', 0.13, 0.34, [0, 0.19, z], 'z', 32)\n        for (let i = 0; i < 10; i++) b.box('motorPaint', [0.012, 0.03, 0.3], [Math.cos((i / 10) * Math.PI) * 0.135, 0.19 + Math.sin((i / 10) * Math.PI) * 0.135, z])\n        for (const e of [-1, 1]) {\n          b.cyl('motorPaint', 0.155, 0.1, [0, 0.19, z + e * 0.22], 'z', 32)\n          b.cyl('motorPaint', 0.12, 0.02, [0, 0.19, z + e * 0.28], 'z', 32)\n          b.around(6, 0.14, [0, 0.19, z + e * 0.27], e > 0 ? 'z' : '-z', (q) => b.hex('nut', 0.018, 0.01, q, e > 0 ? 'z' : '-z'))\n        }\n        b.rbox('motorPaint', [0.12, 0.06, 0.1], 0.01, [0, 0.34, z])\n        for (const dx of [-0.11, 0.11]) for (const dz of [-0.13, 0.13]) b.hex('nut', 0.03, 0.02, [dx, 0.075, z + dz])\n      }\n    })\n    // panel tensioning: jacking bolts along the walls; height-adjust jack at the discharge end\n    for (const s of [-1, 1]) {\n      b.cyl('stainless', 0.025, 0.14, [xB1 - 0.08, deckY(xB1) - 0.2, s * (zW + 0.04)], 'y', 12)\n      b.hex('nut', 0.05, 0.03, [xB1 - 0.08, deckY(xB1) - 0.12, s * (zW + 0.04)])\n    }\n  })\n  const dir = new THREE.Vector3(Math.cos(Math.PI / 4 + ang), Math.sin(Math.PI / 4 + ang), 0)\n  return { fixed: k.build(), basket: b.build(), dir: [dir.x, dir.y, 0] as V3 }\n}\n"
    }
  ],
  "categories": [
    "drilling"
  ],
  "meta": {
    "export": "ShaleShaker",
    "anchor": "base",
    "standards": [
      "API RP 13C",
      "ISO 13501 (screen testing)",
      "IEC 60079 (Ex motors)"
    ]
  }
}