Wadi Docs

Roof v2 guide (the unified roof object)

The roof object replaces the legacy hip_roof/gable_roof/flat_roof/ shed_roof. It is segment-based. The schema is permissive; the real validation happens in the compute pipeline, so always run validate.mjs after writing a roof (it runs the exact derivation the app uses).

Strategy: roof geometry is the subtlest part of the config. Don't hand-invent it — copy the roof from the closest example (see the shape map below), then adjust its segment coordinates/widths to your plinth, and validate.

Where the roof lives, and its height (you do NOT set a Z)

Read this first — it's the part that isn't obvious and that you must not try to reverse-engineer from the geometry.

  • A roof object goes on its OWN top floor — a floor whose floor_number is ABOVE every floor it covers, containing only the roof object(s). Name it something like "Roof" or "Loft Floor". Example: ground = 0, first = 1 → the roof floor is floor_number 2. The roof then covers the floors below it. (All the examples do this: the roof sits alone on the topmost floor.)

  • You NEVER write a Z or height on the roof. There is no Z field anywhere in the roof object. Its base sits automatically at the top of the walls below it, computed by the pipeline as:

    roof base Z = plinth.height + Σ (height of every floor BELOW the roof's floor)
    

    using each floor's own height (falling back to defaults.floor_height). slab_thickness does NOT enter this stackheight already is the full floor-to-floor rise.

  • To raise or lower the roof, change the FLOORS' height (or plinth.height), not the roof. If the roof floats too low/high, you put it on the wrong floor or a floor below it has the wrong height.

  • slope / ridge_h is the rise above that computed wall-top; segment start/end are 2D [x, y] only. Don't compute a Z — the pipeline does it entirely from the floor stack.

The core idea

A roof is a set of segments. Each segment is a line (startend) with a width measured perpendicular to it, and the segment sits at the CENTRE of that width (the roof extends width/2 to each side of the line). What the line means depends on roof_type:

  • pitched — the segment is the ridge; two symmetric slopes rise from the eaves up to it. Ends are covered per default_endpoint:
    • "closed"hip (a sloped triangular hip face at the end).
    • "open"gable (a vertical gable-end wall triangle).
  • shed — the segment is the high edge; the roof slopes down perpendicular to the far side. shed_high_side names which side is high.
  • flat — the segment's width band is a flat slab extruded down.

Segments can run in any direction (not just axis-aligned).

Centreline convention (coord_convention: "center"). A segment's start/end/width are wall centrelines, exactly like a room's x/y/width/length. On expand, each segment grows to the outer wall face — the axis is extended by ½·wall_thickness at each end and width grows by wall_thickness — then overhang extends beyond that. So author segments on the same centreline grid as the walls below (e.g. width House.W, axis endpoints on grid lines); do not hand-add ½-wall offsets, or the roof lands half a wall thickness inside the walls. (In the default "outer" convention the coordinates are literal outer-face positions and no grow happens.)

Object shape

{
  "type": "roof",
  "roof_type": "pitched" | "shed" | "flat",
  "default_endpoint": "closed" | "open",   // pitched only: closed=hip, open=gable
  "min_overhang": 25,                       // > 0 — eave overhang beyond the walls
  "slope": { "by": "height", "ridge_h": 50 },   // rise above wall top …
  //  or   { "by": "angle",  "angle_deg": 30 },  // … or a pitch angle
  "segments": [
    {
      "id": "seg0",
      "start": [x, y], "end": [x, y],       // the ridge (pitched) / high edge (shed)
      "width": 200,                          // perpendicular span, segment centred in it
      "slope": { … },                        // optional per-segment slope override
      "shed_high_side": "…",                 // shed only
      "hip_setback_start": …, "hip_setback_end": …, // optional, tune closed hip faces
      // Per-side overhang overrides (each defaults to min_overhang). Cantilever one
      // edge — e.g. big overhang_end over an entry landing — while others stay tight.
      "overhang_start": …, "overhang_end": …,  // along the ridge axis (shed; on a gable
                                                //   open end = gable_overhang_*)
      "overhang_low": …, "overhang_high": …,   // SHED eaves (down-slope / up-slope)
      "overhang_left": …, "overhang_right": …  // PITCHED eaves (left / right of ridge;
                                                //   single-segment only — see below)
    }
  ],
  "trusses": [
    { "segment_id": "seg0", "type": "fink", "positions_along": [100, 210, 320] }
    //  type: "fink" (pitched)  |  "mono_pitch" (shed)
    //  positions_along: distances along the segment where a truss sits
  ],
  "framing": { … }   // optional overrides — omit unless asked
}

slope may be given once at the roof level (applies to all segments) or per segment. by: "height"ridge_h is the ridge rise above the wall top; by: "angle"angle_deg is the pitch.

Joints (multi-segment shapes)

When two segments share an exactly coincident endpoint, the pipeline auto-resolves that end as a joint (no hip/gable face there) — this is how L, U, and closed-loop (courtyard) roofs are formed. Get the endpoints numerically equal or they won't join.

Shape → which example to copy

Want Segments Copy from
Simple hip (one ridge, hipped ends) 1, pitched, closed two_story_konkan.json / verandah_cottage.json
Simple gable (one ridge, gable ends) 1, pitched, open same, set default_endpoint: "open"
L-shape (two wings) 2, pitched, coincident inner endpoints l_shape_villa.json
Courtyard (ridge loop) 4, pitched, endpoints chained in a loop courtyard_home.json
Flat roof/terrace 1+, flat modern_flat.json
Shed / mono-pitch (lean-to, verandah) 1, shed, shed_high_side set derive from a pitched seg; add shed_high_side + slope

Rules & pitfalls

  • The roof footprint must cover the plinth footprint — segment lines + widths should span the walls they sit on. Mirror the example's relationship to its plinth.
  • min_overhang must be > 0.
  • Every segment needs an id; every truss references a real segment_id.
  • Shed segments require a slope (ridge_h or angle_deg) and a shed_high_side, or derivation throws.
  • After any roof edit: run validate.mjs — a zero-length segment, missing slope, or non-covering footprint fails there, not in the schema.