> Canonical HTML: https://kerfscript.mylasertools.com/models/wave-cathedral/

# Wave Cathedral

Wave Cathedral: a vault made of tides. Seventeen flat arches, each a little different, slot onto two notched rails on a base. Two crests run along the roof and the crown drifts from side to side, so the flat ribs read as a rolling wave.

This design has 20 parts on 5 sheets. Customize it in the [playground](https://kerfscript.mylasertools.com/playground/?model=wave-cathedral).

## Downloads

- [KerfScript source](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral.kerf)
- [wave-cathedral-sheet-1.svg](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-1.svg)
- [wave-cathedral-sheet-1.dxf](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-1.dxf)
- [wave-cathedral-sheet-2.svg](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-2.svg)
- [wave-cathedral-sheet-2.dxf](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-2.dxf)
- [wave-cathedral-sheet-3.svg](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-3.svg)
- [wave-cathedral-sheet-3.dxf](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-3.dxf)
- [wave-cathedral-sheet-4.svg](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-4.svg)
- [wave-cathedral-sheet-4.dxf](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-4.dxf)
- [wave-cathedral-sheet-5.svg](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-5.svg)
- [wave-cathedral-sheet-5.dxf](https://kerfscript.mylasertools.com/models/wave-cathedral/wave-cathedral-sheet-5.dxf)

## Parts

| Part | Quantity | Size |
| --- | ---: | --- |
| Rib 1 | 1 | 224 × 113 mm |
| Rib 2 | 1 | 224 × 127 mm |
| Rib 3 | 1 | 224 × 143.9 mm |
| Rib 4 | 1 | 224 × 156.1 mm |
| Rib 5 | 1 | 224 × 156.4 mm |
| Rib 6 | 1 | 224 × 144.6 mm |
| Rib 7 | 1 | 224 × 127.9 mm |
| Rib 8 | 1 | 224 × 114.4 mm |
| Rib 9 | 1 | 224 × 107.9 mm |
| Rib 10 | 1 | 224 × 108.4 mm |
| Rib 11 | 1 | 224 × 114.3 mm |
| Rib 12 | 1 | 224 × 123.3 mm |
| Rib 13 | 1 | 224 × 131.9 mm |
| Rib 14 | 1 | 224 × 136 mm |
| Rib 15 | 1 | 224 × 133.4 mm |
| Rib 16 | 1 | 224 × 125.5 mm |
| Rib 17 | 1 | 224 × 116 mm |
| Rail L | 1 | 304 × 27 mm |
| Rail R | 1 | 304 × 27 mm |
| Base | 1 | 252 × 320 mm |

## Settings

| Name | Default | Range or choices |
| --- | --- | --- |
| `length` | `320` | 280 to 340 |
| `wave` | `1` | 0.8 to 1.2 |
| `output` | `"all"` | All parts, Ribs 1 to 4, Ribs 5 to 8, Ribs 9 to 12, Ribs 13 to 16, Rib 17, Base, Rails, Fit test |
| `score_detail` | `true` | on or off |
| `$thickness` | `3` | 2.8 to 3.2 |
| `clearance` | `0.15` | 0.1 to 0.3 |
| `$kerf` | `0.15` | 0 to 0.25 |

## Source

```kerf
// Wave Cathedral: a vault made of tides. Seventeen flat arches, each a little
// different, slot onto two notched rails on a base. Two crests run along the
// roof and the crown drifts from side to side, so the flat ribs read as a
// rolling wave.

/* [Pavilion] */
// Length of the base
length = 320; // [280:10:340]
// Height of the roof waves and how far the crown drifts
wave = 1; // [0.8:0.1:1.2]
// All parts, one group at a time, or three pieces to test the slot fit
output = "all"; // [all:All parts, ribs_a:Ribs 1 to 4, ribs_b:Ribs 5 to 8, ribs_c:Ribs 9 to 12, ribs_d:Ribs 13 to 16, rib_e:Rib 17, base:Base, rails:Rails, fit:Fit test]
// Scored contour lines, rib numbers and base markings
score_detail = true;

/* [Material] */
// Sheet thickness
$thickness = 3; // [2.8:0.1:3.2]
// Added to every slot, for a glued slip fit
clearance = 0.15; // [0.10:0.05:0.30]
// Beam width
$kerf = 0.15; // [0:0.01:0.25]

/* [Hidden] */
$material = "plywood";
$sheet = [600, 400];
$gap = 6;
$fn = 120;
s = length / 320;
t = $thickness;
sw = t + clearance;
rail_x = 100 * s;
rail_h = 24 * s;
seat = 12 * s;

// Where rib i stands along the base
function station(i) = s * (16 + 18 * i);
// Roof height along the nave (v from 0 at the front to 1 at the back): two crests
function crest(v) = s * (100 + wave * (58 * exp(-pow((v - 0.22) / 0.18, 2)) + 36 * exp(-pow((v - 0.82) / 0.20, 2))));
// How far the crown leans sideways
function drift(v) = s * wave * 29 * sin(360 * v - 32);
// A point on rib i at angle a: every rib has the same feet, its own crown
function arch(i, a, inner = false) = let (v = i / 16, r = (inner ? 88 : 112) * s, h = crest(v) - (inner ? 18 * s : 0), q = max(0, sin(a)))
  [r * cos(a) + drift(v) * q * q, h * pow(q, 0.86)];
function trace(i, a) = let (v = i / 16, q = max(0, sin(a)))
  [100 * s * cos(a) + drift(v) * q * q, (crest(v) - 9 * s) * pow(q, 0.86)];

module rib(i) {
  difference() {
    polygon(concat([for (j = [0:240]) arch(i, j * 180 / 240)],
      [for (j = [240:-1:0]) arch(i, j * 180 / 240, true)]));
    // Slots from below, over the slots from above in the rails
    for (side = [-1, 1]) translate([side * rail_x - sw / 2, -1]) rect([sw, seat + 1]);
  }
  if (score_detail) score() {
    polyline([for (j = [10:230]) trace(i, j * 180 / 240)]);
    translate([-100 * s, 26 * s]) text(str(i + 1), size = 3 * s, halign = "center", valign = "center");
    polyline([[95 * s, 25 * s], [103 * s, 25 * s], [100 * s, 28 * s]]);
  }
}

module rail(label) {
  difference() {
    union() {
      translate([8 * s, 0]) rect([length - 16 * s, rail_h]);
      for (y = [40 * s, 160 * s, 280 * s]) translate([y - 6 * s, -t]) rect([12 * s, t + 0.3]);
    }
    for (i = [0:16]) translate([station(i) - sw / 2, seat]) rect([sw, rail_h - seat + 1]);
  }
  if (score_detail) score() {
    for (i = [0:16]) translate([station(i), 6 * s]) text(str(i + 1), size = 2.5 * s, halign = "center", valign = "center");
    translate([151 * s, 18 * s]) text(label, size = 3 * s, halign = "center", valign = "center");
    line([20 * s, 3 * s], [length - 20 * s, 3 * s]);
  }
}

module base() {
  difference() {
    translate([-126 * s, 0]) rect([252 * s, length], r = 8 * s);
    for (side = [-1, 1], y = [40 * s, 160 * s, 280 * s])
      translate([side * rail_x, y]) rect([sw, 12 * s + clearance], center = true);
  }
  if (score_detail) score() {
    translate([0, 7.5 * s]) text("W A V E  C A T H E D R A L", size = 3.4 * s, halign = "center", valign = "center");
    translate([0, length - 7 * s]) text("A VAULT MADE OF TIDES / XVII", size = 2.7 * s, halign = "center", valign = "center");
    for (side = [-1, 1]) polyline([for (j = [0:100]) let (v = j / 100) [side * (54 + 5 * sin(360 * v)) * s, (20 + 280 * v) * s]]);
    for (i = [0:16], side = [-1, 1]) {
      translate([side * 119 * s, station(i)]) text(str(i + 1), size = 2.4 * s, halign = "center", valign = "center");
      line([side * 114 * s, station(i)], [side * 116 * s, station(i)]);
    }
    translate([-100 * s, 7 * s]) text("L", size = 3 * s, halign = "center", valign = "center");
    translate([100 * s, 7 * s]) text("R", size = 3 * s, halign = "center", valign = "center");
    polyline([[-12 * s, 22 * s], [12 * s, 22 * s], [8 * s, 25 * s]]);
    translate([0, 28 * s]) text("+X / FRONT", size = 2.5 * s, halign = "center", valign = "center");
  }
}

module fit_test() {
  part("Fit test, base") {
    difference() {
      rect([48, 30], r = 2);
      translate([24, 17]) rect([sw, 12 * s + clearance], center = true);
    }
    if (score_detail) score() translate([24, 6]) text("BASE", size = 3, halign = "center");
  }
  part("Fit test, rail") {
    difference() {
      union() {
        rect([36, rail_h]);
        translate([18 - 6 * s, -t]) rect([12 * s, t + 0.3]);
      }
      translate([18 - sw / 2, seat]) rect([sw, rail_h - seat + 1]);
    }
    if (score_detail) score() translate([7, 6]) text("R", size = 3);
  }
  part("Fit test, rib") {
    difference() {
      rect([30, 30 * s]);
      translate([15 - sw / 2, -1]) rect([sw, seat + 1]);
    }
    if (score_detail) score() translate([6, 21 * s]) text("V", size = 3);
  }
}

// Steps are numbered in the order they first run, so name them here in build order
step("Glue the rails into the base, numbers running from the front") {}
step("Lower the ribs into their numbered notches, scored faces to the front") {}
// Assembled: the rails stand in the base, the ribs across them at their
// stations
module placed_rib(i) step("Lower the ribs into their numbered notches, scored faces to the front") part(str("Rib ", i + 1), at = [0, station(i) + t / 2, 0], rotate = [90, 0, 0]) rib(i);
if (output == "all" || output == "ribs_a") for (i = [0:3]) placed_rib(i);
if (output == "all" || output == "ribs_b") for (i = [4:7]) placed_rib(i);
if (output == "all" || output == "ribs_c") for (i = [8:11]) placed_rib(i);
if (output == "all" || output == "ribs_d") for (i = [12:15]) placed_rib(i);
if (output == "all" || output == "rib_e") placed_rib(16);
if (output == "all" || output == "rails") {
  step("Glue the rails into the base, numbers running from the front") {
    part("Rail L", at = [-rail_x - t / 2, 0, 0], rotate = [90, 0, 90]) rail("L");
    part("Rail R", at = [rail_x - t / 2, 0, 0], rotate = [90, 0, 90]) rail("R");
  }
}
if (output == "all" || output == "base") step("Glue the rails into the base, numbers running from the front") part("Base", at = [0, 0, -t]) base();
if (output == "fit") fit_test();

// Assembly: glue rail L and rail R into the base, numbers running from the
// front. Lower ribs 1 to 17 into the matching notches, scored faces to the
// front, then glue all the joints after a dry fit.
```

Set power, speed and cut order in your laser software. See the [laser software guide](https://kerfscript.mylasertools.com/docs/compatibility/index.md).
