Deploy web Studio / deploy (push) Successful in 3m27s
House parts (stdlib isokit/house): house_wall drawn along a line, with ends that adapt at corners, T-junctions and straight runs so no two walls overlap; window and door that sit on a wall and cut its opening (door styles panel, glass, double, sliding, opening; swing and hinge by rotation); floor (boards, tiles, carpet, stone); roof (hip, gable, flat, optional solar); stairs. Furniture (stdlib isokit/furniture): chair, sofa / armchair, table with chairs, desk with monitor and chair, TV, bed, cupboard, bookshelf, kitchen run, bathtub, shower, toilet, washbasin, plant, rug, floor lamp. Cut-open levels: on gf / 1f the walls in front of a room are cut low (rooms are found by their floors), the roof becomes an outline on the floor, and storeys above are left out. A part's storey comes from z (storey_h, default 3 m). Scripting: objects_of, objects_at, objects_on, mount_of, mounted, frame, to_world, to_local, local_of. Prefab meta: shape (line / rect), mount, storey. Studio: drag to draw lines and rectangles (walls, roads, fences, hedges, floors, areas, buildings, roofs). The Room tool draws a floor plus walls, without doubling walls that already stand on an edge. Windows and doors snap onto walls with a hover preview, and they move, turn, copy and delete with their wall in one undo step. New parts land on the storey the view shows. Palette categories collapse, and the palette scrolls apart from the object list. The MCP instructions explain house building, and list_prefabs shows sits_on. examples/house is a furnished bungalow; the web Studio gets a House demo button. Tests: wall corners and junctions, zero depth-sort cycles in every view, cut levels, and the house demo in every view; check_studio covers the room / window / door / storey flow, check_web the House demo.
225 lines
9.9 KiB
Plaintext
225 lines
9.9 KiB
Plaintext
// isokit/building: buildings with storeys, windows, roofs and cut-open
|
|
// levels, plus carports and sheds.
|
|
//
|
|
// Levels: "gf", "1f", "2f", ... cut a building open at that storey (lower
|
|
// storeys closed, the storey's floor shown, walls facing the viewer cut low);
|
|
// any other level (e.g. "roof", "site") shows it closed. A level above the
|
|
// building's top storey shows it ghosted.
|
|
|
|
fn wall_materials() { ["render", "render2", "wall", "wall2", "office", "yardb", "plinth", "wallp", "concb", "firewall"] }
|
|
fn roof_materials() { ["roof", "roof2", "roof3", "rooft", "roofcap", "canopy"] }
|
|
fn floor_materials() { ["z-floor", "z-living", "z-offices", "z-kitchen", "z-shop", "z-lab", "z-wh", "z-plant", "z-garage", "z-hall", "z-sport"] }
|
|
|
|
fn meta_building() {
|
|
#{ label: "Building", category: "Buildings", icon: "🏢", shape: "rect",
|
|
help: "Storeys with windows, a door on the front (local -y) face, a flat or gable roof and optional solar. The GF / 1F levels cut it open.",
|
|
props: [
|
|
#{ key: "w", type: "float", def: 20.0, min: 2.0, max: 300.0, step: 0.5, label: "Width (m)" },
|
|
#{ key: "d", type: "float", def: 12.0, min: 2.0, max: 300.0, step: 0.5, label: "Depth (m)" },
|
|
#{ key: "floors", type: "int", def: 2, min: 1, max: 20, step: 1, label: "Storeys" },
|
|
#{ key: "floor_h", type: "float", def: 3.4, min: 2.4, max: 12.0, step: 0.1, label: "Storey height (m)" },
|
|
#{ key: "plinth", type: "float", def: 0.4, min: 0.0, max: 1.5, step: 0.05, label: "Plinth (m)" },
|
|
#{ key: "mat", type: "material", def: "render", choices: wall_materials(), label: "Walls" },
|
|
#{ key: "roof", type: "choice", def: "flat", choices: ["flat", "gable"], label: "Roof" },
|
|
#{ key: "roof_mat", type: "material", def: "roof2", choices: roof_materials(), label: "Roof colour" },
|
|
#{ key: "rise", type: "float", def: 2.2, min: 0.5, max: 8.0, step: 0.1, label: "Gable rise (m)" },
|
|
#{ key: "windows", type: "bool", def: true, label: "Windows" },
|
|
#{ key: "lit", type: "bool", def: true, label: "Lit windows" },
|
|
#{ key: "door", type: "bool", def: true, label: "Front door" },
|
|
#{ key: "pv", type: "bool", def: false, label: "Solar panels" },
|
|
#{ key: "ac", type: "bool", def: false, label: "Roof AC units" },
|
|
#{ key: "floor_mat", type: "material", def: "z-floor", choices: floor_materials(), label: "Cut-open floor" },
|
|
] }
|
|
}
|
|
|
|
fn prefab_building(o) {
|
|
let p = o.p;
|
|
let w = p.w / 2.0;
|
|
let d = p.d / 2.0;
|
|
let n = p.floors;
|
|
let H = p.floor_h;
|
|
let z0 = p.plinth;
|
|
let k = floor_of(o.level);
|
|
if k < 0 || k >= n {
|
|
let s = if k >= n { sprite(o.id, "dim") } else { sprite() };
|
|
s.box(-w, -d, w, d, 0.0, z0, "plinth");
|
|
s.box(-w, -d, w, d, z0, z0 + n * H, p.mat, #{ top: p.roof_mat });
|
|
building_windows(s, p, 0, n);
|
|
if p.roof == "gable" { gable_roof(s, p, z0 + n * H); } else { flat_roof_kit(s, p, z0 + n * H); }
|
|
return;
|
|
}
|
|
// cut open at storey k: lower storeys closed, the floor of storey k, cut walls
|
|
let zf = z0 + k * H;
|
|
if k == 0 { layer("floor"); }
|
|
let f = sprite();
|
|
f.box(-w, -d, w, d, 0.0, z0, "plinth");
|
|
if k > 0 {
|
|
f.box(-w, -d, w, d, z0, zf, p.mat, #{ top: p.floor_mat });
|
|
building_windows(f, p, 0, k);
|
|
}
|
|
f.flat(rect(-w, -d, w, d), zf + 0.005, p.floor_mat + " t");
|
|
layer("main");
|
|
// walls start just above the floor so the depth sort draws them over it
|
|
cut_walls(-w, -d, w, d, zf + 0.02, 0.9, fmin(H - 0.4, 2.6), 0.25, p.mat, []);
|
|
}
|
|
|
|
/// Windows (and the front door) on all four faces for storeys from..to.
|
|
fn building_windows(s, p, from, to) {
|
|
let w = p.w / 2.0;
|
|
let d = p.d / 2.0;
|
|
let sd = sides(-w, -d, w, d);
|
|
for f in ["S", "E", "N", "W"] {
|
|
let e = sd[f];
|
|
let L = if f == "S" || f == "N" { p.w } else { p.d };
|
|
let dark = [];
|
|
let lit = [];
|
|
if p.windows {
|
|
for k in from..to {
|
|
let z = p.plinth + k * p.floor_h;
|
|
let gap = if f == "S" && k == 0 && p.door { L / 2.0 } else { -1.0 };
|
|
let i = 0;
|
|
for q in win_list(L, z, 0.9, fmin(1.5, p.floor_h - 1.3), 1.6, 3.4, gap) {
|
|
if p.lit && hash(i * 7.3 + k * 3.1 + L + f.len()) > 0.55 { lit.push(q); } else { dark.push(q); }
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
if dark.len() > 0 { s.open(e[0], e[1], dark, "win"); }
|
|
if lit.len() > 0 { s.open(e[0], e[1], lit, "win lit"); }
|
|
if f == "S" && p.door && from == 0 {
|
|
s.open(e[0], e[1], [[L / 2.0 - 0.9, L / 2.0 + 0.9, p.plinth, p.plinth + 2.3]], "door");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gable roof with the ridge along local x, eaves on the -y and +y sides;
|
|
/// solar modules on the -y slope.
|
|
fn gable_roof(s, p, ze) {
|
|
let a = p.w / 2.0;
|
|
let w = a + 0.4;
|
|
let d = p.d / 2.0;
|
|
let e = 0.6;
|
|
let r = p.rise;
|
|
let k = r / d;
|
|
let za = ze - e * k;
|
|
let zr = ze + r;
|
|
// gable ends first, then the two roof slabs over them
|
|
s.face([[a, -d, ze], [a, d, ze], [a, 0.0, zr - 0.05]], p.mat);
|
|
s.face([[-a, d, ze], [-a, -d, ze], [-a, 0.0, zr - 0.05]], p.mat);
|
|
s.prism(rect(-w, -d - e, w, 0.0), [za - 0.2, za - 0.2, zr - 0.2, zr - 0.2], [za, za, zr, zr], p.roof_mat, #{ side: "fascia" });
|
|
s.prism(rect(-w, 0.0, w, d + e), [zr - 0.2, zr - 0.2, za - 0.2, za - 0.2], [zr, zr, za, za], p.roof_mat, #{ side: "fascia" });
|
|
if p.pv {
|
|
let cw = 1.13;
|
|
let rh = 1.7;
|
|
let cols = ((p.w - 1.0) / cw).floor().to_int();
|
|
let rows = ((d - 0.4) / rh).floor().to_int();
|
|
let u0 = -cols * cw / 2.0;
|
|
let mods = [];
|
|
for i in 0..rows {
|
|
for j in 0..cols {
|
|
let va = -(0.3 + i * rh + 0.03);
|
|
let vb = -(0.3 + (i + 1) * rh - 0.03);
|
|
let ua = u0 + j * cw + 0.03;
|
|
let ub = u0 + (j + 1) * cw - 0.03;
|
|
let zA = zr + va * k + 0.08;
|
|
let zB = zr + vb * k + 0.08;
|
|
mods.push([[ua, va, zA], [ub, va, zA], [ub, vb, zB], [ua, vb, zB]]);
|
|
}
|
|
}
|
|
if mods.len() > 0 { s.poly(mods, "pv"); }
|
|
}
|
|
}
|
|
|
|
/// Parapet, solar rows and AC units on a flat roof at height zt, drawn back
|
|
/// to front.
|
|
fn flat_roof_kit(s, p, zt) {
|
|
let w = p.w / 2.0;
|
|
let d = p.d / 2.0;
|
|
let t = 0.2;
|
|
let ph = 0.5;
|
|
let mat = p.mat;
|
|
let items = [
|
|
[0.0, d, |q| q.box(-w, d - t, w, d, zt, zt + ph, mat, #{ top: "cut" })],
|
|
[-w, 0.0, |q| q.box(-w, -d + t, -w + t, d - t, zt, zt + ph, mat, #{ top: "cut" })],
|
|
[w, 0.0, |q| q.box(w - t, -d + t, w, d - t, zt, zt + ph, mat, #{ top: "cut" })],
|
|
[0.0, -d, |q| q.box(-w, -d, w, -d + t, zt, zt + ph, mat, #{ top: "cut" })],
|
|
];
|
|
if p.pv && p.w > 4.0 && p.d > 4.0 {
|
|
let y = -d + 1.2;
|
|
while y + 1.0 < d - 1.2 {
|
|
let y0 = y;
|
|
let x0 = -w + 1.0;
|
|
let x1 = w - 1.0;
|
|
items.push([0.0, y0 + 0.5, |q| q.poly([[[x0, y0, zt + 0.35], [x1, y0, zt + 0.35], [x1, y0 + 1.0, zt + 0.85], [x0, y0 + 1.0, zt + 0.85]]], "pv")]);
|
|
y += 2.2;
|
|
}
|
|
}
|
|
if p.ac && p.w > 6.0 && p.d > 4.0 {
|
|
for i in 0..2 {
|
|
let x = w - 2.2 - i * 2.0;
|
|
let y = d - 1.4;
|
|
items.push([x, y, |q| { q.box(x - 0.7, y - 0.5, x + 0.7, y + 0.5, zt, zt + 0.9, "ac"); q.fan(x, y, zt + 0.9, 0.38); }]);
|
|
}
|
|
}
|
|
s.seq(items);
|
|
}
|
|
|
|
fn meta_carport() {
|
|
#{ label: "Carport", category: "Buildings", icon: "⌂", shape: "rect",
|
|
help: "A canopy on posts with cars parked under it.",
|
|
props: [
|
|
#{ key: "w", type: "float", def: 6.0, min: 2.5, max: 60.0, step: 0.5, label: "Width (m)" },
|
|
#{ key: "d", type: "float", def: 5.5, min: 2.5, max: 12.0, step: 0.5, label: "Depth (m)" },
|
|
#{ key: "h", type: "float", def: 2.6, min: 2.2, max: 5.0, step: 0.1, label: "Height (m)" },
|
|
#{ key: "cars", type: "int", def: 2, min: 0, max: 20, step: 1, label: "Cars" },
|
|
#{ key: "pv", type: "bool", def: false, label: "Solar on the canopy" },
|
|
] }
|
|
}
|
|
fn prefab_carport(o) {
|
|
let p = o.p;
|
|
let w = p.w / 2.0;
|
|
let d = p.d / 2.0;
|
|
let h = p.h;
|
|
let s = sprite();
|
|
// cars and posts back to front inside the canopy's sprite, then the roof over all of them
|
|
let items = [];
|
|
for c in [[-w + 0.1, -d + 0.1], [w - 0.1, -d + 0.1], [w - 0.1, d - 0.1], [-w + 0.1, d - 0.1]] {
|
|
let x = c[0];
|
|
let y = c[1];
|
|
items.push([x, y, |q| q.box(x - 0.08, y - 0.08, x + 0.08, y + 0.08, 0.0, h, "steel")]);
|
|
}
|
|
let cols = ["car1", "car2", "car3", "car4"];
|
|
let n = p.cars;
|
|
for i in 0..n {
|
|
let x = -w + (i + 0.5) * p.w / n;
|
|
let col = cols[(i + (hash(o.x + o.y) * 4.0).floor().to_int()) % 4];
|
|
items.push([x, 0.0, |q| q.car(x - 0.9, -2.2, x + 0.9, 2.2, col)]);
|
|
}
|
|
s.seq(items);
|
|
s.box(-w, -d, w, d, h, h + 0.15, "canopy");
|
|
if p.pv {
|
|
s.poly([[[-w + 0.2, -d + 0.2, h + 0.2], [w - 0.2, -d + 0.2, h + 0.2], [w - 0.2, d - 0.2, h + 0.2], [-w + 0.2, d - 0.2, h + 0.2]]], "pv");
|
|
}
|
|
}
|
|
|
|
fn meta_shed() {
|
|
#{ label: "Shed / kiosk", category: "Buildings", icon: "▣", shape: "rect",
|
|
props: [
|
|
#{ key: "w", type: "float", def: 4.0, min: 1.0, max: 30.0, step: 0.5, label: "Width (m)" },
|
|
#{ key: "d", type: "float", def: 3.0, min: 1.0, max: 30.0, step: 0.5, label: "Depth (m)" },
|
|
#{ key: "h", type: "float", def: 2.8, min: 1.8, max: 8.0, step: 0.1, label: "Height (m)" },
|
|
#{ key: "mat", type: "material", def: "render2", choices: wall_materials(), label: "Walls" },
|
|
#{ key: "roof_mat", type: "material", def: "roof3", choices: roof_materials(), label: "Roof" },
|
|
] }
|
|
}
|
|
fn prefab_shed(o) {
|
|
let p = o.p;
|
|
let w = p.w / 2.0;
|
|
let d = p.d / 2.0;
|
|
let s = sprite();
|
|
s.box(-w, -d, w, d, 0.0, 0.15, "plinth");
|
|
s.box(-w, -d, w, d, 0.15, p.h, p.mat, #{ top: p.roof_mat });
|
|
let sd = sides(-w, -d, w, d);
|
|
s.open(sd.S[0], sd.S[1], [[p.w / 2.0 - 0.5, p.w / 2.0 + 0.5, 0.15, 2.2]], "door2");
|
|
}
|