diff --git a/hardware/case/.gitignore b/hardware/case/.gitignore new file mode 100644 index 0000000..587b2a0 --- /dev/null +++ b/hardware/case/.gitignore @@ -0,0 +1,2 @@ +# generated mesh exports — regenerate with `just stl` +*.stl diff --git a/hardware/case/README.md b/hardware/case/README.md new file mode 100644 index 0000000..4273e39 --- /dev/null +++ b/hardware/case/README.md @@ -0,0 +1,168 @@ +# Enclosure — typewriter body (concept) + +Part of [**Typoena**](../../README.md) — the distraction-free DIY writing +machine. This page covers the enclosure only; the project root README covers +the whole appliance (hardware, software stack, roadmap). + +A 3D-printable case for Typoena. The e-paper strip sits on a reclined **deck** +where a typewriter's sheet of paper would be; the keyboard you bring rests in +front. There is **no platen part** (it complicates the print) — the rounded +back-top edge is a subtle roll that nods to one for free. + +> **Status: v0 concept, not yet printed.** Outer form and the screen-retention +> / board-mounting strategy are worked out and render cleanly. Board hole +> positions and port offsets are placeholders marked `<< MEASURE >>` in the +> `.scad` — confirm them against the real board before printing a final. + +![The deck: bezel lip framing the e-paper aperture, screen ghosted in](renders/assembled.png) +![Assembled body, three-quarter — reclined deck and the back wall with its port cutouts](renders/front34.png) + +## Files + +| File | What | +| --- | --- | +| [`typoena-case.scad`](typoena-case.scad) | The parametric model. All dimensions live at the top. | +| [`concept.html`](concept.html) | Dimensioned side/front/top drawing (open in a browser). | +| `renders/` | PNG previews (regenerated by the commands below). | + +## Render / preview + +Needs [OpenSCAD](https://openscad.org). Open `typoena-case.scad` in the GUI and +flip the `show` variable, or from the CLI: + +```sh +cd hardware/case +# assembled, coloured, screen ghosted in +openscad -o renders/assembled.png --imgsize=1100,825 --colorscheme=Tomorrow \ + --camera=0,0,0,62,0,22,0 --viewall --autocenter \ + -D 'show="assembled"' typoena-case.scad + +# export a printable part to STL (body | bracket | baseplate) +openscad -o body.stl -D 'show="body"' typoena-case.scad +``` + +`show` accepts `assembled` · `body` · `bracket` · `baseplate` · `print_plate`. + +## Dimensions + +From the datasheets, baked into the model: + +- **Panel — GDEY0579T93:** glass outline **150.92 × 56.94 × 1.0 mm**, active + area **139.00 × 47.74 mm**, pitch 0.1755 mm. Strip aspect ~2.9:1. +- **Board — ESP32-S3-DevKitC-1:** ~**70 × 28 mm**, USB-C ×2 + reset/boot on one + short edge (that edge faces the back wall). +- **Body (default):** 176 W × 104 D, 24 mm front → 58 mm back, deck reclined + **~21°**. Walls 2.4 mm, deck 2.6 mm, corner radius 8 mm. + +The deck angle is the one knob worth tuning first — see below. + +## How the hardware goes in — glueless + +The whole design avoids glue on the fragile 1 mm glass and keeps every part +serviceable. + +![The bare body shell — screen recess cut through the deck, FPC slot on the up-slope edge](renders/body.png) + +### Screen (bezel lip + foam + screwed bracket) + +``` + front face the sandwich, front → back: + ┌───────────────┐ 1. deck BEZEL LIP (overlaps ~4–5 mm of the + │ ┌─────────┐ │ glass's inactive border only, never the + │ │ active │ │ ← lip active area — lip_t = 1.4 mm of material) + │ └─────────┘ │ 2. GLASS drops into the recess from behind; + └───────────────┘ the recess walls locate it in X/Y + 3. FOAM gasket (non-adhesive closed-cell, + [lip][glass][foam][bracket] foam_t ≈ 1 mm) spreads the clamp load + ↑ screwed to 4 bosses so you never point-crack the glass + 4. printed BRACKET, screwed to 4 bosses, + presses the stack forward +``` + +- The through-**aperture** is a hair larger than the active area and stays + *inside* the glass-minus-lip envelope, so the lip covers only dead border. +- The recess opens straight into the cavity, and an **FPC slot** on the + up-slope edge lets the ribbon fold back to the DESPI-C579 breakout. +- Foam does three jobs: cushions the glass, takes up print tolerance + (±0.2–0.5 mm), and removes any need for adhesive. Cut it from a plain EVA/ + PORON sheet — no sticky backing. +- Alternative if you want no screws: replace the bracket with printed + cantilever clips. It works, but clips point-load the glass edge; the + foam+bracket route is gentler and I'd default to it. + +![The screwed bracket — four corner holes, window clears the active area](renders/bracket.png) + +### Boards (the baseplate is the chassis) + +Mount everything to the **baseplate** on the bench, then drop it in and close +from below — far easier than fishing screws inside a shell. + +- ESP32 + DESPI-C579 sit on printed **standoffs** (M2.5 self-tap). Positions in + `esp_holes` / `brk_holes` are placeholders — set them to your board's holes. + No mounting holes on your board rev? Switch to slide-in edge rails. +- The baseplate screws **up into 4 corner posts** in the shell. +- A **cable relief** notch at the back lets the keyboard's USB-C cable exit and + route around to the front. + +![The baseplate — standoff bosses for the ESP32 + breakout; mount the boards at the bench, then close from below](renders/baseplate.png) + +### Assembly order + +1. Lay glass into the deck recess (from inside), add the foam gasket, screw the + bracket down onto the 4 bosses. +2. Screw ESP32 + breakout to the baseplate standoffs. +3. Connect the FPC (screen → breakout) through the slot. +4. Screw the baseplate up into the corner posts. + +## Tune first + +- **`Hb` (back height) → deck angle.** 18–22° is typewriter-shallow; raise `Hb` + toward ~28–35° if the screen reads too edge-on when you're sitting close. +- **`<< MEASURE >>` items:** `esp_holes`, `brk_holes`, `port_x`, `port_z`, + `screen_off` (the real panel's active area is offset toward the FPC edge). + +## Print notes + +![The three printed parts laid out — body, bracket, baseplate](renders/print.png) + +- **Material:** PLA/PETG. Print the body in matte **indigo** (`#130f40`), the + bracket/base in cream or brass — two-tone reads unmistakably "typewriter" for + the price of a filament swap. +- **Make the engrave read:** on a body this dark the recessed `TYPOENA` is + near-invisible until you give it contrast — a swipe of paint pen in the recess, + or a 3–4 layer filament swap across the nameplate band mid-print. +- **Shell, not solid:** 2.4 mm walls + open bottom keep material low despite the + chunky body form. +- **Orientation:** body deck-up (or on its back) needs little/no support; the + bezel lip is a small overhang. Bracket and baseplate print flat. + +## Nameplate font + +The `TYPOENA` engrave on the deck (recessed, faces the user) is set via the +`name_font` parameter. Current pick: **Monaspace Krypton** (GitHub's mechanical +mono). OpenSCAD only renders fonts installed on the system: + +```sh +# current: Monaspace Krypton (installs the whole family) +brew install --cask font-monaspace + +# alternative tried: Cutive Mono (typewriter slab-serif) +curl -sL -o ~/Library/Fonts/CutiveMono-Regular.ttf \ + https://github.com/google/fonts/raw/main/ofl/cutivemono/CutiveMono-Regular.ttf + +fc-cache -f # so the OpenSCAD CLI picks either up +``` + +To audition other faces: Google Fonts, filtered to monospace and previewing the +name — + + +## Open questions / TODO + +- [ ] Confirm the GDEY0579T93 active-area **offset** and which edge the **FPC** + exits; adjust `screen_off` / the FPC slot. +- [ ] Real ESP32-S3-DevKitC-1 mounting-hole + port coordinates. +- [ ] Optional **hinged lid** over the deck (portable-typewriter-case echo, + protects the glass in a bag) — `docs/hardware.md` calls for one; not yet + modelled. +- [ ] Decide feet: printed (modelled) vs. stick-on rubber bumpers. diff --git a/hardware/case/concept.html b/hardware/case/concept.html new file mode 100644 index 0000000..bd3411d --- /dev/null +++ b/hardware/case/concept.html @@ -0,0 +1,255 @@ +Typoena — Enclosure Concept + + +
+
+ +
+
+
Typoena
+
Enclosure concept · typewriter body
+
+
Panel
GDEY0579T93
Board
ESP32-S3
+
Units
mm
Scale
to-fit
+
Rev
v0 · concept
Date
2026-07-11
+
+ +
+

The screen is the sheet of paper. A shallow sage wedge with a reclined deck; the e-paper strip sits where a typewriter's page would be, a decorative platen roller and two knobs ride the back edge, and the keyboard you bring rests in front. Every dimension below is from the real datasheets — this is the shape to react to before I cut the parametric model.

+
+ body + platen / deck / feet + e-paper + dimensions +
+
+ +
+
+

Side profile — the silhouette

+

The money view: shallow deck reclined ~18°, platen & knob at the back, ports on the tail, your keyboard ghosted in front.

+ +
+
+
+

Front elevation — width

+

Letterbox screen across the deck; knobs poke past the sides.

+ +
+
+

Top plan — deck layout

+

Screen forward, platen band aft, ports along the back edge.

+ +
+
+
+ +
+

01 Cheap-ish body

You picked the higher-material form, but it stays a shell: ~2.4 mm walls, open bottom, a screwed baseplate. Hollow inside, no infill spent on a solid block.

+

02 Color does the work

Print the body in matte sage, platen & knobs in cream or black. Two-tone via a filament change reads unmistakably "typewriter" for free.

+

03 Decorative cues

Platen roller + two knobs are cosmetic (your call) — printed separately, no wiring, no GPIO. Round feet finish the machined look.

+

04 Fragile glass

The 1 mm panel drops into a rebate behind a bezel lip that overlaps ~6 mm; the FPC folds back to the DESPI-C579 breakout in the cavity.

+

05 The one tunable

Deck angle is the ergonomics dial. 18° is typewriter-shallow; 28–35° reads better seated. Say the word and I'll steepen it.

+

06 Optional lid

Your docs say "hinged lid" — a cover latching over the deck echoes a portable typewriter's case and guards the glass in a bag. Toggled off by default.

+
+ +
+ Typoena · writing appliance + Screen 150.9 × 56.9 × 1.0 · active 139.0 × 47.7 · pitch 0.1755 + Orthographic · third-angle · not for manufacture +
+ +
+
+ + diff --git a/hardware/case/renders/assembled.png b/hardware/case/renders/assembled.png new file mode 100644 index 0000000..6e06e8b Binary files /dev/null and b/hardware/case/renders/assembled.png differ diff --git a/hardware/case/renders/baseplate.png b/hardware/case/renders/baseplate.png new file mode 100644 index 0000000..88b6281 Binary files /dev/null and b/hardware/case/renders/baseplate.png differ diff --git a/hardware/case/renders/body.png b/hardware/case/renders/body.png new file mode 100644 index 0000000..90d44f5 Binary files /dev/null and b/hardware/case/renders/body.png differ diff --git a/hardware/case/renders/bracket.png b/hardware/case/renders/bracket.png new file mode 100644 index 0000000..5a33c53 Binary files /dev/null and b/hardware/case/renders/bracket.png differ diff --git a/hardware/case/renders/front34.png b/hardware/case/renders/front34.png new file mode 100644 index 0000000..9496f30 Binary files /dev/null and b/hardware/case/renders/front34.png differ diff --git a/hardware/case/renders/nameplate.png b/hardware/case/renders/nameplate.png new file mode 100644 index 0000000..750ba79 Binary files /dev/null and b/hardware/case/renders/nameplate.png differ diff --git a/hardware/case/renders/print.png b/hardware/case/renders/print.png new file mode 100644 index 0000000..1e10854 Binary files /dev/null and b/hardware/case/renders/print.png differ diff --git a/hardware/case/typoena-case.scad b/hardware/case/typoena-case.scad new file mode 100644 index 0000000..d81fb3c --- /dev/null +++ b/hardware/case/typoena-case.scad @@ -0,0 +1,277 @@ +// ============================================================================ +// Typoena — 3D-printed enclosure · "typewriter body" · rev v0 (concept) +// ---------------------------------------------------------------------------- +// A shallow sage wedge. The e-paper strip sits on a reclined deck where a +// typewriter's sheet of paper would be; the keyboard you bring rests in front. +// No platen part (keeps the print simple) — the rounded back-top edge is a +// subtle roll that nods to one for free. +// +// Everything here is PARAMETRIC. Numbers that come from a datasheet are noted; +// numbers marked << MEASURE >> are best-guesses you must confirm against the +// real board / breakout before printing a final. +// +// Units: millimetres. Render: see hardware/case/README.md +// +// Parts (set `show` below): +// "assembled" – everything in place, coloured (screen ghosted in) +// "body" – the shell only (print deck-up or on its back) +// "bracket" – the screen retaining frame (print flat) +// "baseplate" – the chassis / bottom cover (print flat) +// "print_plate" – all printed parts laid out side by side +// ============================================================================ + +show = "assembled"; +$fn = 48; + +// ---- body envelope -------------------------------------------------------- +W = 176; // width (X) — screen 150.9 + bezel + walls +D = 104; // depth (Y) — front (keyboard) .. back (ports) +Hf = 24; // height at the FRONT edge +Hb = 58; // height at the BACK edge (Hf> THE ergonomics dial. Raise Hb for a more vertical, easier-to-read screen; +// lower it for a flatter, more typewriter-like deck. 18-22 deg = shallow, +// 28-35 deg reads better when you're sitting close. + +// ---- e-paper panel : GDEY0579T93 (datasheet) ------------------------------ +G_w = 150.92; G_h = 56.94; G_t = 1.0; // glass outline W x H x thickness +A_w = 139.00; A_h = 47.74; // active area (must stay uncovered) +// NOTE: the real panel's active area is offset toward the FPC edge — this model +// centres it. << MEASURE >> your panel's border and shift screen_off if needed. +screen_off = 0; // extra X/Y active-area offset + +// ---- screen retention (glueless) ------------------------------------------ +lip_over = 4.0; // how far the front bezel lip overlaps the glass border +lip_t = 1.4; // deck material left in FRONT of the glass (the visible lip) +glass_gap = 0.5; // clearance around the glass in its pocket +foam_t = 1.0; // non-adhesive closed-cell foam gasket behind the glass +bracket_t = 2.6; // printed retaining frame thickness +fpc_w = 26; // width of the ribbon slot on the up-slope edge + +// ---- deck nameplate (engraved, faces the user) ---------------------------- +name_text = "TYPOENA"; +name_size = 6.5; // cap height in mm +name_depth = 0.8; // engrave depth — raise for a bolder, deeper cut +name_font = "Monaspace Krypton"; // install once — see README (Nameplate font) + +A_ap_w = A_w + 2; // through-aperture (a hair bigger than active) +A_ap_h = A_h + 1; // still smaller than glass minus 2*lip +P_w = G_w + glass_gap; // glass pocket (locates the glass in X/Y) +P_h = G_h + glass_gap; + +// screen placed centred on the deck (measured up the slope) +deck_L = (D - 2*corner_r) / cos(theta); // deck length along the slope +screen_cy = deck_L/2; // centre it +boss_r = 3.4; // M2 self-tap boss for the bracket + +// ---- ports on the back wall (ESP32-S3-DevKitC-1 edge) -------------------- +port_z = 7; // height of the port centres off the desk << MEASURE >> +usbc_w = 9.6; usbc_h = 3.6; // USB-C opening (with clearance) +sd_w = 12; sd_h = 2.4; // microSD slot +// X positions of the three openings along the back << MEASURE to your board >> +port_x = [W/2 - 15, W/2, W/2 + 17]; // usb-c (kbd), usb-c (power), µSD + +// ---- baseplate / chassis -------------------------------------------------- +bp_t = 2.6; // baseplate thickness +bp_gap = 0.5; // clearance so it drops into the shell +foot_r = 7; // round feet (the little typewriter feet) +foot_h = 3.5; +post_r = 4.2; // corner screw posts inside the shell (M2.5 self-tap) +post_pilot = 1.15; + +// board mounting standoffs on the baseplate << MEASURE hole positions >> +standoff_h = 6; +standoff_pilot = 1.15; +// ESP32-S3-DevKitC-1 is ~70 x 28 mm; these are PLACEHOLDER hole coords: +esp_holes = [[W/2-33, 30],[W/2+33, 30],[W/2-33, 54],[W/2+33, 54]]; +// DESPI-C579 breakout sits behind the screen — PLACEHOLDER: +brk_holes = [[W/2-20, 78],[W/2+20, 78]]; + +// ---- colours (for the assembled render) ----------------------------------- +C_body = "#130f40"; // deep indigo (chosen) +C_plate = "#C9C3B2"; +C_bracket= "#2B2B2B"; +C_screen = "#F7F4EA"; + +// =========================================================================== +// helpers +// =========================================================================== +module rrect(w, d, r) { // 2D rounded rectangle, centred + hull() for (mx=[-1,1], my=[-1,1]) + translate([mx*(w/2-r), my*(d/2-r)]) circle(r=r); +} + +// place children onto the reclined deck plane. Origin at the FRONT-TOP edge +// (world y=0, z=Hf) — where the true hull top surface actually begins; anchor +// it at the pillar centre instead and everything lands ~3mm below the surface. +// local frame: X = width, Y = up the slope, Z = out of the deck (normal). +module on_deck() { + translate([W/2, 0, Hf]) rotate([theta, 0, 0]) children(); +} + +// =========================================================================== +// body +// =========================================================================== +module body_outer() { + hull() { + translate([corner_r, corner_r, 0]) cylinder(h=Hf, r=corner_r); + translate([W-corner_r, corner_r, 0]) cylinder(h=Hf, r=corner_r); + translate([corner_r, D-corner_r, 0]) cylinder(h=Hb, r=corner_r); + translate([W-corner_r, D-corner_r, 0]) cylinder(h=Hb, r=corner_r); + } +} + +module body_cavity() { + ri = corner_r - wall; + hull() { + translate([corner_r, corner_r, -3]) cylinder(h=Hf-top_wall+3, r=ri); + translate([W-corner_r, corner_r, -3]) cylinder(h=Hf-top_wall+3, r=ri); + translate([corner_r, D-corner_r, -3]) cylinder(h=Hb-top_wall+3, r=ri); + translate([W-corner_r, D-corner_r, -3]) cylinder(h=Hb-top_wall+3, r=ri); + } +} + +// 4 corner posts the baseplate screws up into +module corner_posts() { + for (px=[corner_r+3, W-corner_r-3], py=[corner_r+3, D-corner_r-3]) { + h = (py < D/2) ? Hf-top_wall : Hb-top_wall; + translate([px, py, 0]) difference() { + cylinder(h=h, r=post_r); + translate([0,0,-1]) cylinder(h=h+2, r=post_pilot); + } + } +} + +// 4 bosses just OUTSIDE the glass pocket for the retaining bracket +module bracket_bosses() { + on_deck() for (bx=[-(P_w/2+5), P_w/2+5], + by=[screen_cy-(P_h/2+5), screen_cy+(P_h/2+5)]) { + blen = lip_t + G_t + foam_t + bracket_t + 6; + translate([bx, by, -lip_t-blen]) difference() { + cylinder(h=blen, r=boss_r); + translate([0,0,-1]) cylinder(h=blen+2, r=1.0); // M2 self-tap + } + } +} + +// deck cuts: through-aperture, glass pocket (leaves the front lip), FPC slot +module screen_cuts() { + on_deck() translate([screen_off, screen_cy + screen_off, 0]) { + // window + translate([0,0,-30]) cube([A_ap_w, A_ap_h, 66], center=true); + // glass pocket behind the lip + translate([0,0,-30-lip_t]) cube([P_w, P_h, 60], center=true); + // ribbon slot on the up-slope edge + translate([0, P_h/2, -30-lip_t]) cube([fpc_w, 12, 60], center=true); + } +} + +module port_cuts() { + // USB-C x2 + microSD through the back wall (y = D) + for (i=[0:2]) { + pw = (i==2) ? sd_w : usbc_w; + ph = (i==2) ? sd_h : usbc_h; + translate([port_x[i], D-wall-1, port_z]) + rotate([-90,0,0]) linear_extrude(wall+2) + offset(r=0.8) square([pw-1.6, ph-1.6], center=true); + } +} + +// engraved nameplate on the DECK, in the band between the front edge and the +// screen — faces the user as they write. Sits flat on the reclined deck. +module nameplate() { + name_y = (screen_cy - P_h/2) / 2; // centre of the front deck band + on_deck() translate([screen_off, name_y, -name_depth]) + linear_extrude(name_depth + 0.6) + text(name_text, size=name_size, halign="center", valign="center", + font=name_font, spacing=1.1); +} + +module case_body() { + difference() { + union() { + difference() { body_outer(); body_cavity(); } + corner_posts(); + bracket_bosses(); + } + screen_cuts(); + port_cuts(); + nameplate(); // engrave (comment out for a blank face) + } +} + +// =========================================================================== +// screen retaining bracket (printed flat, screwed to the 4 bosses) +// =========================================================================== +module bracket() { + ow = P_w + 18; oh = P_h + 18; + difference() { + linear_extrude(bracket_t) + difference() { rrect(ow, oh, 4); rrect(A_ap_w+2, A_ap_h+2, 2); } + for (bx=[-(P_w/2+5), P_w/2+5], by=[-(P_h/2+5), P_h/2+5]) + translate([bx, by, -1]) cylinder(h=bracket_t+2, r=1.45); // M2 clear + } +} + +// =========================================================================== +// baseplate / chassis +// =========================================================================== +module baseplate() { + iw = W - 2*wall - bp_gap; + id = D - 2*wall - bp_gap; + difference() { + union() { + // plate (centred on the footprint) + translate([W/2, D/2, 0]) linear_extrude(bp_t) rrect(iw, id, corner_r-wall); + // round feet underneath + for (fx=[corner_r+6, W-corner_r-6], fy=[corner_r+6, D-corner_r-6]) + translate([fx, fy, -foot_h]) cylinder(h=foot_h+0.1, r=foot_r); + // board standoffs on top + for (h = concat(esp_holes, brk_holes)) + translate([h[0], h[1], bp_t]) cylinder(h=standoff_h, r=3); + } + // corner screw clearance (into the body posts) + for (px=[corner_r+3, W-corner_r-3], py=[corner_r+3, D-corner_r-3]) + translate([px, py, -foot_h-1]) cylinder(h=bp_t+foot_h+2, r=1.6); + // standoff pilot holes + for (h = concat(esp_holes, brk_holes)) + translate([h[0], h[1], bp_t-1]) cylinder(h=standoff_h+2, r=standoff_pilot); + // cable / connector relief at the back + translate([W/2, D-wall-3, -1]) cube([30, 8, bp_t+2], center=false); + } +} + +// =========================================================================== +// assemblies +// =========================================================================== +module ghost_screen() { + on_deck() translate([screen_off, screen_cy+screen_off, -lip_t-G_t/2]) + color(C_screen) cube([G_w, G_h, G_t], center=true); +} +module placed_bracket() { + on_deck() translate([screen_off, screen_cy+screen_off, + -lip_t-G_t-foam_t-bracket_t]) + color(C_bracket) bracket(); +} + +if (show == "assembled") { + color(C_body) case_body(); + ghost_screen(); + placed_bracket(); + translate([0,0,-0.01]) color(C_plate) baseplate(); +} else if (show == "body") { + color(C_body) case_body(); +} else if (show == "bracket") { + color(C_bracket) bracket(); +} else if (show == "baseplate") { + color(C_plate) baseplate(); +} else if (show == "print_plate") { + color(C_body) case_body(); + translate([W+30, 0, 0]) color(C_plate) baseplate(); + translate([W+30, D+30, foot_h]) color(C_bracket) bracket(); +} diff --git a/hardware/justfile b/hardware/justfile new file mode 100644 index 0000000..4c510ac --- /dev/null +++ b/hardware/justfile @@ -0,0 +1,25 @@ +# Typoena enclosure — 3D-printed case +# Run from the hardware/ directory (just sets cwd here automatically). + +scad := "case/typoena-case.scad" + +# list available recipes +default: + @just --list + +# open the case model in OpenSCAD +open: + open -a OpenSCAD {{scad}} + +# regenerate the preview PNGs in case/renders/ +render: + cd case && openscad -o renders/assembled.png --imgsize=1100,825 --colorscheme=Tomorrow \ + --camera=0,0,0,62,0,22,0 --viewall --autocenter -D 'show="assembled"' typoena-case.scad + cd case && openscad -o renders/body.png --imgsize=1100,825 --colorscheme=Tomorrow \ + --camera=0,0,0,62,0,22,0 --viewall --autocenter -D 'show="body"' typoena-case.scad + +# export STLs for the three printable parts +stl: + cd case && openscad -o body.stl -D 'show="body"' typoena-case.scad + cd case && openscad -o bracket.stl -D 'show="bracket"' typoena-case.scad + cd case && openscad -o baseplate.stl -D 'show="baseplate"' typoena-case.scad