← Skills

SIP Openings Construction

Native Read-only

Full-depth timber buck construction for windows and doors, framing member sizing, panel zone helper, and multiple-opening layout patterns.

/skills/sip_openings.md

Estimated tokens
3596
Characters
14383
Source
Native

Markdown

# SIP Openings Construction Skill

## Wall Openings — Windows and Doors

### Construction Principle — Full-Depth Timber Buck

In SIP construction, every window and door opening is framed with a **full-depth timber buck**: a structural timber frame whose depth equals the full wall thickness (`TOTAL_THICKNESS`). The SIP panels stop at the face of the king stud and butt flush against solid timber across the entire wall section. There is no EPS foam in the framing zone — it is entirely replaced by engineered timber.

```
Plan view (top-down cross-section through wall at opening):

 ←─── SIP panel (left) ───→ ←── FRAME_ZONE_W ──────────────→ ←── SIP panel (right) ───→
 [OSB][  EPS core  ][OSB]   [KNG][TRM][  clear void  ][TRM][KNG]   [OSB][  EPS core  ][OSB]
 ←── TOTAL_THICKNESS ──→    ←──── TOTAL_THICKNESS each member ────→  ←── TOTAL_THICKNESS ──→
```

This is why king studs must be **45 × TOTAL_THICKNESS** in cross-section — not 45 × 90mm. A 90mm-deep king stud leaves the SIP panel foam unsupported on either side, creates a thermal bridge gap, and provides no bearing surface for the panel OSB faces.

### Opening Coordinate System

`OPENING_X` is always **the left face of the left king stud** — the X position where SIP panels stop.

```
X:  0 ─────── [panels] ──── OPENING_X
                              ├── king_left   (KING_W = 45mm wide)
                              ├── trimmer_left (TRIMMER_W = 45mm wide)
                              ├── [clear void] (CLEAR_W wide)
                              ├── trimmer_right
                              ├── king_right
                             OPENING_X + FRAME_ZONE_W ──── [panels] ──── WALL_LENGTH
```

### Sizing Calculations

```python
KING_W      = 45                        # king stud face width (X)
KING_D      = TOTAL_THICKNESS           # king stud FULL WALL DEPTH (Y) — not 90mm
TRIMMER_W   = 45                        # trimmer stud face width (X)
TRIMMER_D   = TOTAL_THICKNESS           # trimmer stud full depth (Y)
SILL_H      = 90                        # sill plate height for windows (Z)

CLEAR_W     = FRAME_W                   # clear glazing/door width inside frame
CLEAR_H     = FRAME_H                   # clear glazing/door height inside frame
RO_W        = CLEAR_W + 2 * TRIMMER_W  # rough opening width (trimmer outer faces)
HEADER_SPAN = RO_W + 2 * KING_W        # king-to-king span = total framing width
FRAME_ZONE_W = HEADER_SPAN             # X width replaced by framing (no SIP panels here)

HEADER_DEPTH = max(150, RO_W // 10)    # LVL header depth (Z); min 150mm

# OPENING_X must produce a clean left-panel width: ideally a multiple of 1200mm
# or leave a remainder panel ≥ 300mm. Align to nearest panel seam where possible.
OPENING_X = 1200   # example: one full 1200mm panel to the left, then the frame

# Vertical positions
if IS_DOOR:
    CLEAR_BASE_Z = BOTTOM_PLATE_H           # door clear void starts at bottom plate top
    TRIMMER_H    = CLEAR_H                  # trimmers height = clear door height
else:
    CLEAR_BASE_Z = BOTTOM_PLATE_H + SILL_H  # window void starts above sill
    TRIMMER_H    = SILL_H + CLEAR_H         # trimmers from bottom plate to header

HEADER_BASE_Z = BOTTOM_PLATE_H + TRIMMER_H  # Z of header underside
```

### Framing Member Summary

| Member | Width (X) | Depth (Y) | Height (Z) | Notes |
|---|---|---|---|---|
| King stud | 45mm | **TOTAL_THICKNESS** | PANEL_HEIGHT | Full height, full depth — primary load path |
| Trimmer stud | 45mm | **TOTAL_THICKNESS** | TRIMMER_H | Bears on bottom plate; carries header |
| LVL header | HEADER_SPAN | **TOTAL_THICKNESS** | HEADER_DEPTH | Spans king-to-king; min depth 150mm |
| Window sill | CLEAR_W | **TOTAL_THICKNESS** | SILL_H | Windows only; sits on trimmer tops at base of void |
| Cripple SIP | CLEAR_W | TOTAL_THICKNESS | cripple_h | Short SIP section above header to top plate |

### Panel Zone Helper and Opening Layout

Before building panels, divide the wall into solid SIP zones and framing zones. Panels must never be placed where the frame is — they stop at the king stud face.

```python
def make_panel_zone(x_start, zone_length):
    """Build SIP panels + block splines filling zone_length starting at x_start."""
    zone_parts = []
    if zone_length <= 0:
        return zone_parts
    x = x_start
    full = int(zone_length) // 1200
    rem  = int(zone_length) % 1200
    widths = [1200] * full + ([rem] if rem > 0 else [])
    for i, pw in enumerate(widths):
        f1 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(x, 0,                         BOTTOM_PLATE_H))
        co = Part.makeBox(pw, CORE_THICKNESS, PANEL_HEIGHT, Vector(x, FACE_THICKNESS,             BOTTOM_PLATE_H))
        f2 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(x, FACE_THICKNESS+CORE_THICKNESS, BOTTOM_PLATE_H))
        zone_parts.extend([f1, co, f2])
        x += pw
        if i < len(widths) - 1:
            sp = Part.makeBox(45, 90, PANEL_HEIGHT,
                              Vector(x - 22, (TOTAL_THICKNESS - 90) / 2, BOTTOM_PLATE_H))
            zone_parts.append(sp)
    return zone_parts

# Single opening — panel zones on each side
parts.extend(make_panel_zone(0, OPENING_X))                              # left of frame
parts.extend(make_panel_zone(OPENING_X + FRAME_ZONE_W,                  # right of frame
                             WALL_LENGTH - OPENING_X - FRAME_ZONE_W))
```

### Complete Wall-with-Opening Code

```python
import FreeCAD, Part
from FreeCAD import Vector

# === PARAMETERS ===
WALL_LENGTH     = 4800
PANEL_HEIGHT    = 2700
CORE_THICKNESS  = 150
FACE_THICKNESS  = 11
TOTAL_THICKNESS = CORE_THICKNESS + 2 * FACE_THICKNESS   # 172mm for SIP-150
BOTTOM_PLATE_H  = 90
TOP_PLATE_H     = 180

IS_DOOR    = False
FRAME_W    = 1000    # clear opening width (inside frame rebates)
FRAME_H    = 1200    # clear opening height

KING_W      = 45
KING_D      = TOTAL_THICKNESS           # FULL WALL DEPTH
TRIMMER_W   = 45
TRIMMER_D   = TOTAL_THICKNESS           # FULL WALL DEPTH
SILL_H      = 90

CLEAR_W      = FRAME_W
CLEAR_H      = FRAME_H
RO_W         = CLEAR_W + 2 * TRIMMER_W
HEADER_SPAN  = RO_W + 2 * KING_W
FRAME_ZONE_W = HEADER_SPAN
HEADER_DEPTH = max(150, RO_W // 10)

# OPENING_X = left face of left king stud (where left panels end)
# Align to a panel seam: 1200, 2400, 3600, etc.
OPENING_X    = 1200

if IS_DOOR:
    CLEAR_BASE_Z = BOTTOM_PLATE_H
    TRIMMER_H    = CLEAR_H
else:
    CLEAR_BASE_Z = BOTTOM_PLATE_H + SILL_H
    TRIMMER_H    = SILL_H + CLEAR_H

HEADER_BASE_Z = BOTTOM_PLATE_H + TRIMMER_H

doc = FreeCAD.ActiveDocument or FreeCAD.newDocument("WallWithOpening")
parts = []

# === STEP 1: Bottom plate — full wall length (unbroken, even under opening) ===
# CORE_THICKNESS depth only — slots into the SIP foam channel, offset by FACE_THICKNESS.
bp = Part.makeBox(WALL_LENGTH, CORE_THICKNESS, BOTTOM_PLATE_H, Vector(0, FACE_THICKNESS, 0))
parts.append(bp)

# === STEP 2: SIP panel zones — stop at king stud faces ===
def make_panel_zone(x_start, zone_length):
    zone_parts = []
    if zone_length <= 0:
        return zone_parts
    x = x_start
    full = int(zone_length) // 1200
    rem  = int(zone_length) % 1200
    widths = [1200] * full + ([rem] if rem > 0 else [])
    for i, pw in enumerate(widths):
        f1 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(x, 0,                                  BOTTOM_PLATE_H))
        co = Part.makeBox(pw, CORE_THICKNESS, PANEL_HEIGHT, Vector(x, FACE_THICKNESS,                     BOTTOM_PLATE_H))
        f2 = Part.makeBox(pw, FACE_THICKNESS, PANEL_HEIGHT, Vector(x, FACE_THICKNESS + CORE_THICKNESS,    BOTTOM_PLATE_H))
        zone_parts.extend([f1, co, f2])
        x += pw
        if i < len(widths) - 1:
            sp = Part.makeBox(45, 90, PANEL_HEIGHT,
                              Vector(x - 22, (TOTAL_THICKNESS - 90) / 2, BOTTOM_PLATE_H))
            zone_parts.append(sp)
    return zone_parts

parts.extend(make_panel_zone(0, OPENING_X))
parts.extend(make_panel_zone(OPENING_X + FRAME_ZONE_W,
                             WALL_LENGTH - OPENING_X - FRAME_ZONE_W))

# === STEP 3: Timber buck framing (full TOTAL_THICKNESS depth throughout) ===

# King studs — full wall depth, full panel height, one each side
king_left  = Part.makeBox(KING_W, KING_D, PANEL_HEIGHT,
                           Vector(OPENING_X, 0, BOTTOM_PLATE_H))
king_right = Part.makeBox(KING_W, KING_D, PANEL_HEIGHT,
                           Vector(OPENING_X + FRAME_ZONE_W - KING_W, 0, BOTTOM_PLATE_H))
parts.extend([king_left, king_right])

# Trimmer studs — full wall depth, height from bottom plate to header underside
trimmer_left  = Part.makeBox(TRIMMER_W, TRIMMER_D, TRIMMER_H,
                              Vector(OPENING_X + KING_W, 0, BOTTOM_PLATE_H))
trimmer_right = Part.makeBox(TRIMMER_W, TRIMMER_D, TRIMMER_H,
                              Vector(OPENING_X + FRAME_ZONE_W - KING_W - TRIMMER_W, 0, BOTTOM_PLATE_H))
parts.extend([trimmer_left, trimmer_right])

# LVL header — full wall depth, spans king-to-king
header = Part.makeBox(HEADER_SPAN, TOTAL_THICKNESS, HEADER_DEPTH,
                      Vector(OPENING_X, 0, HEADER_BASE_Z))
parts.append(header)

if not IS_DOOR:
    # Window sill — full wall depth, sits on trimmers at base of clear void
    sill = Part.makeBox(CLEAR_W, TOTAL_THICKNESS, SILL_H,
                        Vector(OPENING_X + KING_W + TRIMMER_W, 0, BOTTOM_PLATE_H))
    parts.append(sill)

# Cripple SIP panels above header — short panels between header top and top plate
cripple_h = PANEL_HEIGHT - TRIMMER_H - HEADER_DEPTH
if cripple_h > 40:
    cx = OPENING_X + KING_W + TRIMMER_W
    cz = HEADER_BASE_Z + HEADER_DEPTH
    cp_f1 = Part.makeBox(CLEAR_W, FACE_THICKNESS, cripple_h, Vector(cx, 0,                                 cz))
    cp_co = Part.makeBox(CLEAR_W, CORE_THICKNESS, cripple_h, Vector(cx, FACE_THICKNESS,                    cz))
    cp_f2 = Part.makeBox(CLEAR_W, FACE_THICKNESS, cripple_h, Vector(cx, FACE_THICKNESS + CORE_THICKNESS,   cz))
    parts.extend([cp_f1, cp_co, cp_f2])

# === STEP 4: Double top plate — full wall length (unbroken) ===
# CORE_THICKNESS depth only — slots into the SIP foam channel, offset by FACE_THICKNESS.
tp = Part.makeBox(WALL_LENGTH, CORE_THICKNESS, TOP_PLATE_H,
                  Vector(0, FACE_THICKNESS, BOTTOM_PLATE_H + PANEL_HEIGHT))
parts.append(tp)

# === STEP 5: Fuse everything into one wall solid ===
wall = parts[0]
for p in parts[1:]:
    wall = wall.fuse(p)

feature = doc.addObject("Part::Feature", "WallWithOpening")
feature.Shape = wall

# === STEP 6: Window or door unit — separate feature, not fused to wall ===
# The clear void between inner trimmer faces is the visible opening.
# Add the unit as a separate Part::Feature so it can be coloured/selected independently.

UNIT_FRAME_T = 65   # window or door frame depth (sits centred in wall depth)

if IS_DOOR:
    DOOR_LEAF_T = 44
    door_frame = Part.makeBox(CLEAR_W, UNIT_FRAME_T, CLEAR_H,
                              Vector(OPENING_X + KING_W + TRIMMER_W,
                                     (TOTAL_THICKNESS - UNIT_FRAME_T) / 2,
                                     CLEAR_BASE_Z))
    door_leaf  = Part.makeBox(CLEAR_W - 10, DOOR_LEAF_T, CLEAR_H - 15,
                              Vector(OPENING_X + KING_W + TRIMMER_W + 5,
                                     (TOTAL_THICKNESS - DOOR_LEAF_T) / 2,
                                     CLEAR_BASE_Z + 10))
    unit = door_frame.fuse(door_leaf)
else:
    GLASS_T = 28
    win_frame = Part.makeBox(CLEAR_W, UNIT_FRAME_T, CLEAR_H,
                             Vector(OPENING_X + KING_W + TRIMMER_W,
                                    (TOTAL_THICKNESS - UNIT_FRAME_T) / 2,
                                    CLEAR_BASE_Z))
    glazing   = Part.makeBox(CLEAR_W - 40, GLASS_T, CLEAR_H - 40,
                             Vector(OPENING_X + KING_W + TRIMMER_W + 20,
                                    (TOTAL_THICKNESS - GLASS_T) / 2,
                                    CLEAR_BASE_Z + 20))
    unit = win_frame.fuse(glazing)

unit_obj = doc.addObject("Part::Feature", "DoorUnit" if IS_DOOR else "WindowUnit")
unit_obj.Shape = unit

doc.recompute()
if FreeCAD.GuiUp:
    FreeCAD.Gui.ActiveDocument.ActiveView.fitAll()
```

### Multiple Openings in One Wall

Plan all `OPENING_X` positions before building panels. Panels fill every gap between framing zones.

```python
# Define all openings as (opening_x, frame_zone_w, is_door, clear_w, clear_h)
# OPENING_X values must be sorted and non-overlapping
openings = [
    {"x": 1200, "fzw": DOOR_FRAME_ZONE_W,   "is_door": True,  "cw": 900,  "ch": 2100},
    {"x": 3000, "fzw": WIN_FRAME_ZONE_W,    "is_door": False, "cw": 1000, "ch": 1200},
]

# Build panel zones around all opening zones
solid_zones = []
prev_end = 0
for o in sorted(openings, key=lambda o: o["x"]):
    if o["x"] > prev_end:
        solid_zones.append((prev_end, o["x"] - prev_end))
    prev_end = o["x"] + o["fzw"]
if prev_end < WALL_LENGTH:
    solid_zones.append((prev_end, WALL_LENGTH - prev_end))

for x_start, zone_len in solid_zones:
    parts.extend(make_panel_zone(x_start, zone_len))

# Add framing for each opening
for o in openings:
    ox   = o["x"]
    fzw  = o["fzw"]
    # ... king studs, trimmers, header, sill per opening using ox as OPENING_X
```

### OPENING_X Alignment Guide

Choose `OPENING_X` so the panel zones on each side are buildable widths (≥ 300mm, ideally multiples of 1200mm):

| Left zone target | OPENING_X | Left zone actual | Notes |
|---|---|---|---|
| 1 full panel | 1200 | 1200mm | Clean seam |
| 2 full panels | 2400 | 2400mm | Clean seam |
| 1.5 panels | 1800 | 600mm + 1200mm | 600mm remainder — acceptable |
| Centred in 4800mm wall, 1090mm frame | 1855 | 1855mm = 1200+655 | 655mm remainder — acceptable |

---

## ⚠ Non-Negotiable Opening Rule

3. **Openings described in the brief MUST be modelled with a full-depth timber buck AND a unit.**
   If the brief mentions a window or door on a wall, that wall component MUST: (a) use `make_panel_zone` to stop SIP panels at the king stud face — never cut a void from a continuous wall, (b) add full-depth king studs (`KING_D = TOTAL_THICKNESS`) + full-depth trimmer studs + LVL header + window sill, and (c) add a separate feature for the glazing or door unit. King studs that are only 90mm deep are wrong — they leave the SIP foam unsupported and create a structural gap.
v0.0.170