Data centres: multi-storey shell, racks and pods, cooling, fire safety, power
Deploy web Studio / deploy (push) Successful in 1m4s
Deploy web Studio / deploy (push) Successful in 1m4s
datacentre.rhai (palette groups Data centre, Cooling, Fire safety, Power): - dc_building: multi-storey shell (storeys of storey_h), walls in pieces, slabs, roof with parapet, louvres, front door and loading dock; levels cut it open storey by storey - raised_floor; rack_row and dc_pod (cabinets by U, width, depth, equipment, fill, doors, LEDs; in-row coolers; cold / hot aisle containment with vent tiles, roof, end doors, ceiling duct, airflow); dc_cage for colocation - crah, chiller (air-cooled, dry, adiabatic), cooling_tower, pipe_run - smoke_detectors (grid + coverage), fire_pipe (VESDA, sprinkler, gas), gas_bank (Novec 1230, FM-200, Inergen sized for its halls), fire_device (panels, call points, abort switches, beacons, extinguishers) - power_cab (UPS, battery, PDU, switchgear, RPP), busway, cable_tray, transformer - tooltips sum IT load, kW/m2, cooling redundancy per storey, UPS load, battery runtime, plant cooling and agent quantity (warning when short) Engine and stdlib: - part_hidden(o) in house.rhai: parts on closed storeys inside a dc_building are not drawn (they only made sort cycles under the slab above); house parts, furniture and people use it. Existing examples render byte-identical. - overhead services sort in their own "ceiling" layer with tight boxes - sort records which sprites each broken cycle involved; isokit check names them (Rendered.cycle_at) examples/datacentre (three storeys and a roof plant), Data centre demo in the web Studio, tests, SCRIPTING/README/ROADMAP, MCP instructions.
This commit is contained in:
1 parent
c8becc2e59
commit
2810477f1b
22 files changed
+2281
-18
No files matched your search
@@ -19,4 +19,8 @@ pub const MODULES: &[(&str, &str)] = &[
|
||||
("isokit/house", include_str!("../stdlib/house.rhai")),
|
||||
("isokit/furniture", include_str!("../stdlib/furniture.rhai")),
|
||||
("isokit/industry", include_str!("../stdlib/industry.rhai")),
|
||||
(
|
||||
"isokit/datacentre",
|
||||
include_str!("../stdlib/datacentre.rhai"),
|
||||
),
|
||||
];
|
||||
@@ -221,7 +221,8 @@ fn every_library_prefab_renders_cleanly_in_every_rotation() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let mut doc = Doc::default();
|
||||
for (i, m) in r.catalog().iter().enumerate() {
|
||||
let mut o = obj(&format!("o{i}"), &m.kind, i as f64 * 15.0, 0.0, json!({}));
|
||||
// 45 m apart: a data centre shell (40 m) leaves out what stands inside it
|
||||
let mut o = obj(&format!("o{i}"), &m.kind, i as f64 * 45.0, 0.0, json!({}));
|
||||
// switch every bool prop on to exercise all branches
|
||||
for p in &m.props {
|
||||
if p.kind == "bool" {
|
||||
@@ -1379,3 +1380,207 @@ fn object_queries_and_frames() {
|
||||
);
|
||||
assert_eq!(m[4], 1.0, "one opening on the east wall");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- data centres
|
||||
|
||||
fn dc_cfg() -> Map<String, Value> {
|
||||
let mut m = Map::new();
|
||||
m.insert("storey_h".into(), json!(5.0));
|
||||
m
|
||||
}
|
||||
|
||||
fn on_storey(mut o: Object, z: f64) -> Object {
|
||||
o.z = z;
|
||||
o
|
||||
}
|
||||
|
||||
/// A two-storey shell: power on GF, a hall on 1F.
|
||||
fn dc_doc() -> Doc {
|
||||
Doc {
|
||||
objects: vec![
|
||||
obj(
|
||||
"dc",
|
||||
"dc_building",
|
||||
0.0,
|
||||
0.0,
|
||||
json!({ "w": 30, "d": 20, "storeys": 2 }),
|
||||
),
|
||||
obj(
|
||||
"ups",
|
||||
"power_cab",
|
||||
0.0,
|
||||
0.0,
|
||||
json!({ "kind": "UPS", "n": 2, "kw": 200 }),
|
||||
),
|
||||
obj(
|
||||
"batt",
|
||||
"power_cab",
|
||||
0.0,
|
||||
-3.0,
|
||||
json!({ "kind": "battery", "n": 4, "kwh": 50 }),
|
||||
),
|
||||
obj("wall", "house_wall", 6.0, 0.0, json!({ "w": 8, "h": 4.7 })),
|
||||
on_storey(
|
||||
obj(
|
||||
"rf",
|
||||
"raised_floor",
|
||||
0.0,
|
||||
0.0,
|
||||
json!({ "w": 28.8, "d": 19.2 }),
|
||||
),
|
||||
5.0,
|
||||
),
|
||||
on_storey(
|
||||
obj("pod", "dc_pod", -4.0, 0.0, json!({ "n": 10, "kw": 8 })),
|
||||
5.0,
|
||||
),
|
||||
on_storey(
|
||||
obj(
|
||||
"row",
|
||||
"rack_row",
|
||||
6.0,
|
||||
5.0,
|
||||
json!({ "n": 6, "kw": 5, "inrow": 3, "inrow_kw": 30 }),
|
||||
),
|
||||
5.0,
|
||||
),
|
||||
on_storey(obj("crah1", "crah", -8.0, 8.5, json!({ "kw": 100 })), 5.0),
|
||||
on_storey(obj("crah2", "crah", 0.0, 8.5, json!({ "kw": 100 })), 5.0),
|
||||
on_storey(
|
||||
obj(
|
||||
"crah3",
|
||||
"crah",
|
||||
8.0,
|
||||
8.5,
|
||||
json!({ "kw": 100, "standby": true }),
|
||||
),
|
||||
5.0,
|
||||
),
|
||||
on_storey(obj("gas", "gas_bank", -12.0, -8.0, json!({ "n": 4 })), 5.0),
|
||||
on_storey(
|
||||
obj(
|
||||
"det",
|
||||
"smoke_detectors",
|
||||
0.0,
|
||||
0.0,
|
||||
json!({ "w": 28.8, "d": 19.2, "spacing": 6 }),
|
||||
),
|
||||
5.0,
|
||||
),
|
||||
on_storey(
|
||||
obj("tray", "cable_tray", 0.0, -5.0, json!({ "w": 20 })),
|
||||
5.0,
|
||||
),
|
||||
on_storey(obj("tech", "person", 0.0, -5.0, json!({})), 5.0),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_centre_parts_stand_on_the_raised_floor_and_add_up_their_load() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let doc = dc_doc();
|
||||
let b = r.build("1f", 0, &dc_cfg(), &doc).unwrap();
|
||||
assert!(b.scene.warnings.is_empty(), "{:?}", b.scene.warnings);
|
||||
let near = |a: f64, e: f64| (a - e).abs() < 1e-6;
|
||||
// racks, coolers and people stand on the 0.6 m raised floor of their storey
|
||||
for id in ["pod", "row", "crah1", "tech"] {
|
||||
assert!(near(b.boxes[id][2], 5.6), "{id} {:?}", b.boxes[id]);
|
||||
}
|
||||
// a 42U cabinet is 2 m high
|
||||
assert!(near(b.boxes["row"][5], 5.6 + 42.0 * 0.04445 + 0.13));
|
||||
let out = render(&b.scene, 0, &RenderOpts::default());
|
||||
assert_eq!(out.cycles, 0, "{:?}", out.cycle_at);
|
||||
let row = |id: &str, k: &str| {
|
||||
out.info[id]
|
||||
.rows
|
||||
.iter()
|
||||
.find(|r| r.0 == k)
|
||||
.map(|r| r.1.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
// 20 racks at 8 kW + 6 at 5 kW; one in-row cooler after the first three racks
|
||||
assert_eq!(row("pod", "IT load"), "8 kW per rack, 160 kW in all");
|
||||
assert_eq!(
|
||||
row("pod", "Containment"),
|
||||
"cold aisle, 1.2 m, roof and end doors"
|
||||
);
|
||||
assert_eq!(row("row", "In-row cooling"), "1 × 30 kW = 30 kW");
|
||||
// cooling 200 kW duty + 30 kW in-row + 100 kW standby against 190 kW IT: one unit spare
|
||||
assert_eq!(
|
||||
row("crah1", "This storey"),
|
||||
"190 kW IT in 26 racks; 230 kW duty + 100 kW standby cooling"
|
||||
);
|
||||
assert_eq!(row("crah1", "Redundancy"), "N+1, 140 kW spare");
|
||||
assert_eq!(row("rf", "On it"), "26 racks, 190 kW IT");
|
||||
// Novec 1230 for the hall (28.8 x 19.2 m, 4.7 m slab to slab): 1,704 kg; 4 x 168 kg is short
|
||||
assert_eq!(row("gas", "Needed"), "1,704 kg");
|
||||
assert_eq!(row("gas", "Status"), "short: 7 more cylinders");
|
||||
assert!(out.html.contains(">Gas: 7 cylinders short<"));
|
||||
// overhead services sort in their own layer after everything on the floor
|
||||
let ceiling = b.scene.layers.iter().find(|l| l.name == "ceiling").unwrap();
|
||||
let ids: Vec<&str> = ceiling.sprites.iter().map(|s| s.id.as_str()).collect();
|
||||
assert!(
|
||||
ids.contains(&"tray") && ids.contains(&"det") && !ids.contains(&"pod"),
|
||||
"{ids:?}"
|
||||
);
|
||||
let off = r
|
||||
.build(
|
||||
"1f",
|
||||
0,
|
||||
&{
|
||||
let mut c = dc_cfg();
|
||||
c.insert("ceiling".into(), json!(false));
|
||||
c
|
||||
},
|
||||
&doc,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(!off.boxes.contains_key("tray") && !off.boxes.contains_key("det"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_centre_storeys_draw_only_what_can_be_seen() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let doc = dc_doc();
|
||||
let drawn = |level: &str| {
|
||||
let b = r.build(level, 1, &dc_cfg(), &doc).unwrap();
|
||||
assert!(
|
||||
b.scene.warnings.is_empty(),
|
||||
"{level}: {:?}",
|
||||
b.scene.warnings
|
||||
);
|
||||
let out = render(&b.scene, 1, &RenderOpts::default());
|
||||
assert_eq!(out.cycles, 0, "{level}: {:?}", out.cycle_at);
|
||||
(b.boxes, out)
|
||||
};
|
||||
// closed: the shell hides every storey, so nothing inside it is drawn
|
||||
let (site, _) = drawn("site");
|
||||
assert!(site.contains_key("dc"));
|
||||
for id in ["ups", "wall", "rf", "pod", "crah1", "tech"] {
|
||||
assert!(
|
||||
!site.contains_key(id),
|
||||
"{id} drawn inside the closed building"
|
||||
);
|
||||
}
|
||||
// GF cut open: the power room shows, the hall above is left out
|
||||
let (gf, out) = drawn("gf");
|
||||
for id in ["ups", "batt", "wall"] {
|
||||
assert!(gf.contains_key(id), "{id} missing on GF");
|
||||
}
|
||||
assert!(!gf.contains_key("pod") && !gf.contains_key("rf"));
|
||||
// batteries give their runtime at the IT load of every storey
|
||||
let runtime = out.info["batt"]
|
||||
.rows
|
||||
.iter()
|
||||
.find(|r| r.0 == "Runtime")
|
||||
.unwrap()
|
||||
.1
|
||||
.clone();
|
||||
assert_eq!(runtime, "about 56.8 min at 190 kW IT");
|
||||
// 1F cut open: the hall shows, the closed ground floor's rooms do not
|
||||
let (f1, _) = drawn("1f");
|
||||
assert!(f1.contains_key("pod") && f1.contains_key("rf") && f1.contains_key("dc"));
|
||||
assert!(!f1.contains_key("ups") && !f1.contains_key("wall"));
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -3,7 +3,7 @@
|
||||
// on its object's z, so it can sit on any storey (see isokit/house). Pieces
|
||||
// on storeys above the one a level cuts open are left out.
|
||||
|
||||
fn furniture_hidden(o) { house_view(o) == 2 }
|
||||
fn furniture_hidden(o) { part_hidden(o) }
|
||||
fn wood_choices() { ["wood", "woodd", "furnw", "steel", "kitchenc"] }
|
||||
fn fabric_choices() { ["uph1", "uph2", "uph3", "uph4", "sofa", "bed", "cloth"] }
|
||||
|
||||
|
||||
@@ -27,11 +27,26 @@ fn house_view(o) {
|
||||
if s < k { 0 } else if s == k { 1 } else { 2 }
|
||||
}
|
||||
|
||||
/// Leave part o out? On a storey above the one a level cuts open, or on a
|
||||
/// closed storey inside a data centre shell (dc_building), whose walls and
|
||||
/// slabs hide it anyway (and would sort badly against what stands above).
|
||||
fn part_hidden(o) {
|
||||
let hv = house_view(o);
|
||||
if hv == 2 { return true; }
|
||||
if hv == 1 { return false; }
|
||||
let s = storey_of(o.z);
|
||||
for b in objects_at(["dc_building"], o.x, o.y) {
|
||||
let s0 = storey_of(b.z);
|
||||
if s >= s0 && s < s0 + b.p.storeys { return true; }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn wall_cut_h() { 1.0 }
|
||||
fn wall_types() { ["house_wall"] }
|
||||
fn opening_types() { ["window", "door"] }
|
||||
fn room_types() { ["floor"] }
|
||||
fn house_wall_materials() { ["render", "render2", "brick", "wood", "wall", "wall2", "wallp", "concb", "office", "furnw"] }
|
||||
fn room_types() { ["floor", "raised_floor"] }
|
||||
fn house_wall_materials() { ["render", "render2", "brick", "wood", "wall", "wall2", "wallp", "concb", "office", "furnw", "firewall"] }
|
||||
fn flooring_materials() { ["f-oak", "f-wood", "f-tile", "f-stone", "f-carpet", "f-conc", "z-floor", "z-living", "z-kitchen", "z-bed", "z-wc"] }
|
||||
fn house_roof_materials() { ["rooft", "roofr", "roof", "roof2", "roof3", "roofcap", "canopy"] }
|
||||
|
||||
@@ -116,7 +131,7 @@ fn meta_house_wall() {
|
||||
|
||||
fn prefab_house_wall(o) {
|
||||
let p = o.p;
|
||||
if house_view(o) == 2 { return; }
|
||||
if part_hidden(o) { return; }
|
||||
let top = wall_top(o);
|
||||
let cap = if top < p.h { "cut" } else { p.mat };
|
||||
let t = p.t / 2.0;
|
||||
@@ -203,7 +218,7 @@ fn meta_window() {
|
||||
fn prefab_window(o) {
|
||||
let c = opening_at(o);
|
||||
let w = c.wall;
|
||||
if house_view(w) == 2 { return; }
|
||||
if part_hidden(w) { return; }
|
||||
let H = w.p.h;
|
||||
let t = w.p.t / 2.0;
|
||||
let top = wall_top(w);
|
||||
@@ -247,7 +262,7 @@ fn meta_door() {
|
||||
fn prefab_door(o) {
|
||||
let c = opening_at(o);
|
||||
let w = c.wall;
|
||||
if house_view(w) == 2 { return; }
|
||||
if part_hidden(w) { return; }
|
||||
let p = o.p;
|
||||
let H = w.p.h;
|
||||
let t = w.p.t / 2.0;
|
||||
@@ -314,7 +329,7 @@ fn meta_floor() {
|
||||
}
|
||||
|
||||
fn prefab_floor(o) {
|
||||
if house_view(o) == 2 { return; }
|
||||
if part_hidden(o) { return; }
|
||||
let p = o.p;
|
||||
let w = p.w / 2.0;
|
||||
let d = p.d / 2.0;
|
||||
@@ -495,7 +510,7 @@ fn meta_stairs() {
|
||||
}
|
||||
|
||||
fn prefab_stairs(o) {
|
||||
if house_view(o) == 2 { return; }
|
||||
if part_hidden(o) { return; }
|
||||
let p = o.p;
|
||||
let w = p.w / 2.0;
|
||||
let n = fmax(2.0, (p.rise / 0.18).round()).to_int();
|
||||
|
||||
@@ -3,30 +3,37 @@
|
||||
fn people_colours() { ["hivis", "guest1", "guest2", "guest3", "guest4", "guide", "suit"] }
|
||||
|
||||
fn meta_person() {
|
||||
#{ label: "Person", category: "People", icon: "🧍",
|
||||
#{ label: "Person", category: "People", icon: "🧍", storey: true,
|
||||
props: [
|
||||
#{ key: "cls", type: "choice", def: "hivis", choices: people_colours(), label: "Clothes" },
|
||||
#{ key: "seated", type: "bool", def: false, label: "Seated" },
|
||||
] }
|
||||
}
|
||||
// People are left out like house parts (on a storey above the one a level
|
||||
// cuts open, or inside a closed data centre storey); on a raised floor they
|
||||
// stand on its top.
|
||||
fn prefab_person(o) {
|
||||
if part_hidden(o) { return; }
|
||||
let z = dc_lift(o);
|
||||
let s = sprite();
|
||||
if o.p.seated { s.sit(0.0, 0.0, 0.0, o.p.cls); } else { s.person(0.0, 0.0, 0.0, o.p.cls); }
|
||||
if o.p.seated { s.sit(0.0, 0.0, z, o.p.cls); } else { s.person(0.0, 0.0, z, o.p.cls); }
|
||||
}
|
||||
|
||||
fn meta_crowd() {
|
||||
#{ label: "Crowd", category: "People", icon: "👥",
|
||||
#{ label: "Crowd", category: "People", icon: "👥", storey: true,
|
||||
props: [
|
||||
#{ key: "n", type: "int", def: 5, min: 2, max: 40, step: 1, label: "People" },
|
||||
#{ key: "r", type: "float", def: 2.0, min: 0.6, max: 15.0, step: 0.1, label: "Spread (m)" },
|
||||
] }
|
||||
}
|
||||
fn prefab_crowd(o) {
|
||||
if part_hidden(o) { return; }
|
||||
let z = dc_lift(o);
|
||||
let cols = ["guest1", "guest2", "guest3", "guest4", "hivis"];
|
||||
for i in 0..o.p.n {
|
||||
// one sprite each, scattered deterministically in a disc
|
||||
let a = hash(i * 1.7 + o.x) * 6.2832;
|
||||
let r = o.p.r * hash(i * 3.3 + o.y).sqrt();
|
||||
sprite().person(r * a.cos(), r * a.sin(), 0.0, cols[i % 5]);
|
||||
sprite().person(r * a.cos(), r * a.sin(), z, cols[i % 5]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user