# MCM Teak Desk

![Rendered model](assets/rendered_model.png)

Parametric recreation of a real mid-century modern teak writing desk, built from photographs of the actual piece. This example demonstrates applying **real wood photography** — not generic procedural textures — to a complex parametric model in Fusion 360.

![Reference photo](assets/reference_photo.jpg)

## Why This Example Matters

Most CAD appearance workflows use tiled procedural wood textures. This project uses a **photograph of the real desk top** as the primary texture, matched with color-graded teak veneers on the frame. The challenge: mapping a flat photo onto a body with filleted edges without distortion artifacts (Box projection's 45° grain-swap on fillets, cylindrical projection's barrel distortion on flat faces).

The solution — **thin ear bodies** with cylindrical projection on the fillet edges, Box projection everywhere else — is documented here as a reusable technique.

## Status
**Validation passed** — 39 bodies (25 structural + 2 ears + 5 bowties + 4 loft transitions + axis marker + 2 surface bodies), teak appearance with real photography on top face, ear fillet wrapping, color-graded frame, darkened endgrain.

## Components

| Component | Bodies | Description |
|-----------|--------|-------------|
| top | 1 + 2 ears | 470×1080×17mm slab, 15mm bottom fillet + 3.75mm top fillet, thin ear shells on long edges |
| legs | 4 | 15-point entasis spline revolve, 3° splay, foot fillet, through-mortises |
| aprons | 2 + 5 bowties | Arched bottom + scalloped ends, rosewood bowtie inlays |
| outer_slats | 2 | Cross-rails at ±leg_dist_y/2 with stepped tenons |
| inner_slats | 2 | Cross-rails at ±leg_dist_y/6 with stepped tenons |
| side_stretchers | 2 + 4 loft transitions | Round bars r=13mm with lofted end transitions |
| dovetail_blocks | 12 | 3×4 grid under top |

## Appearance Pipeline

### Real Photography on Desk Top
The desk top face uses a photograph of the actual desk surface (`teak_desk_top.jpg`, 2868×6528px = 47×108cm). Box projection maps it 1:1 onto the flat top face via a face-level appearance override. The body-level teak veneer shows on side faces and the bottom.

### Ear Bodies for Fillet Wrapping
Thin 0.01mm shell bodies sit on the long-edge fillets, created by:
1. `CopyPasteBodies` — duplicate the desk body
2. `SurfaceDeleteFaceFeatures` — keep only the 2 long-edge cylindrical fillet faces
3. `ThickenFeatures` — offset 0.01mm outward → thin curved shell

Cylindrical projection on the ears wraps the desk photo smoothly around the fillet curve. The axis runs along Y (desk length) at the bottom fillet center (x=±22, z=71.8), equidistant from the fillet surface.

### Color-Graded Frame Veneers
All structural bodies (legs, aprons, slats, stretchers) use `teak_e_top_tone.jpg` — the teak e veneer color-graded to match the desk top photo's tone. Generated by `scripts/wood_texture_color_grade.py`.

### Darkened Endgrain
End-grain faces use `teak_endgrain_dark.jpg` — the endgrain texture darkened to 75% brightness to contrast with side grain. Generated via PIL: `ImageEnhance.Brightness(img).enhance(0.75)`.

### Apron Face Matching
Box projection mirrors the texture between +X and -X face normals. Per-face appearance copies with half-period offset on +X faces ensure all 4 large apron side faces show the same grain pattern.

## Parameters

### Envelope
| Parameter | Value |
|-----------|-------|
| `total_height` | 720mm |
| `top_thickness` × `top_width` × `top_length` | 17 × 470 × 1080mm |
| `leg_dist_x` × `leg_dist_y` | 380 × 850mm |
| `leg_length` | 700mm |
| `angle_y` (splay) | 3° |

### Apron
| Parameter | Value |
|-----------|-------|
| `stretcher_width × _height × _thickness` | 1050 × 75 × 18mm |
| `stretcher_to_top` | 10mm |
| `apron_edge_fillet` | 3mm |

### Slats
| Parameter | Value |
|-----------|-------|
| `slat_thickness` | 30mm (Y) |
| `slat_outer_h` / `slat_inner_h` | 54 / 25mm (Z) |
| `slat_tenon_y` | 12mm |
| `slat_outer_tenon_z` / `slat_inner_tenon_z` | 28 / 20mm |

### Side Stretchers
| Parameter | Value |
|-----------|-------|
| `side_stretcher_r` | 13mm |
| `side_stretcher_z` | 250mm |

### Dovetails
| Parameter | Value |
|-----------|-------|
| `dovetail_width` × `_bottom_width` × `_thickness` × `_length` | 30 × 25 × 15 × 80mm |
| `dovetail_quantity` | 3 per row |

## Design Fidelity
- **Leg entasis**: 15 fit points (r=11mm foot, 16.8mm waist, 14mm top)
- **Apron arch**: 15 fit points per half (center peak Z=643mm, shoulder Z=618mm)
- **Apron scallop**: 15 fit points per end, curves from shoulder up to Y=±507mm
- **Side stretcher** Y shift parametric with splay angle

## Joinery (Phase 7)
- Leg mortises: each leg CUT by aprons + outer slats + side stretchers
- Apron mortises: aprons CUT by outer + inner slats
- Dovetail sockets: slats CUT by dovetail blocks
- Rosewood bowtie inlays on front apron

## Files

| File | Description | Source |
|------|-------------|--------|
| `teak_desk.py` | Build script (~1970 lines, 8 phases) | — |
| `assets/reference_photo.jpg` | Photo of the real desk | Original photography |
| `assets/teak_desk_top.jpg` | Desk top surface photo (2868×6528 = 47×108cm) | Cropped from high-res scan of the real desk top |
| `assets/teak_desk_top_rot90.jpg` | Rotated 90° for cylindrical ear projection | `PIL Image.transpose(ROTATE_90)` on teak_desk_top.jpg |
| `assets/teak_e_top_tone.jpg` | Color-graded teak e veneer for frame | `scripts/wood_texture_color_grade.py` matching teak_e → desk top tone |
| `assets/teak_endgrain_dark.jpg` | Darkened endgrain (75% brightness) | `PIL ImageEnhance.Brightness(endgrain).enhance(0.75)` |
| `assets/teak_endgrain_top_tone.jpg` | Color-graded endgrain (original brightness) | `scripts/wood_texture_color_grade.py` matching endgrain → desk top tone |

## Build Notes
- `addCenterPointRectangle` with center at (0,0,0) doesn't anchor — use `addTwoPointRectangle` + diagonal construction line + `addMidPoint(origin, diag)`.
- Distance dimensions in Fusion can't take negative expressions. Offset the sketch plane so the origin sits where you need it.
- `rectangularPatternFeatures.createInput` defaults to patterning in BOTH directions. Explicitly set `quantityTwo = VI("1")` and `distanceTwo = VI("0 cm")`.
- Revolve profile axis line must NOT be `isConstruction=True`.
- `sp.make_comp()` returns an Occurrence, not a Component — use `.component`.

## Known Deviations
1. **Housed dovetail apron→leg**: apron end should taper 8° inside the leg. Currently rectangular mortise.
2. **DT block flange+tongue shape**: should have parallelogram cross-section with rounded end caps. Currently rectangular prisms.
