Data centres: multi-storey shell, racks and pods, cooling, fire safety, power
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:
grabowski committed 2026-10-27 17:08:39 +07:00
1 parent c8becc2e59
commit 2810477f1b
22 files changed
+2281 -18

No files matched your search

+11 -1
View File
@@ -137,7 +137,17 @@ pub fn check(p: &Project) -> Vec<Finding> {
));
}
if v.cycles > 0 {
out.push(f("info", format!("{}/{rot}: {} depth-sort cycle(s) broken; split large sprites if something draws in front of what it stands behind", l.id, v.cycles), None));
let mut at = v
.cycle_at
.iter()
.take(3)
.cloned()
.collect::<Vec<_>>()
.join("; ");
if v.cycle_at.len() > 3 {
at.push_str("; ...");
}
out.push(f("info", format!("{}/{rot}: {} depth-sort cycle(s) broken ({at}); split large sprites if something draws in front of what it stands behind", l.id, v.cycles), None));
}
for cls in v
.html
+4
View File
@@ -201,6 +201,9 @@ pub struct View {
pub sprites: usize,
pub nodes: usize,
pub cycles: usize,
/// Where the depth sort broke its cycles ("a before b, c": sprite ids).
#[serde(skip_serializing_if = "Vec::is_empty")]
pub cycle_at: Vec<String>,
pub warnings: Vec<String>,
/// Object id -> world box [x0, y0, z0, x1, y1, z1].
pub boxes: BTreeMap<String, [f64; 6]>,
@@ -428,6 +431,7 @@ impl Project {
sprites: r.sprites,
nodes: r.nodes,
cycles: r.cycles,
cycle_at: r.cycle_at,
warnings: r.warnings,
boxes: built.boxes,
ms: (t0.elapsed().as_secs_f64() * 1e4).round() / 10.0,
+11 -1
View File
@@ -46,7 +46,17 @@ props.walk none/both/left/right adds raised sidewalks walk_w wide) ending on ano
junction there (at any angle); a road_curve starts at its x, y heading along its rot, bends props.angle degrees \
left or right on radius r and ends where the next road starts; two roads ending at one point make a mitred corner. \
A roundabout (props.r ring radius, arm_e/n/w/s, arm_len) has its arm ends r + arm_len from its centre: put the \
roads' ends exactly there, with the same width as road_d.";
roads' ends exactly there, with the same width as road_d. \
Data centres (categories Data centre, Cooling, Fire safety, Power): a dc_building (w x d, props.storeys) is the shell; \
its storeys are the project parameter storey_h apart (5 m suits data halls) and levels gf, 1f, 2f cut it open. Put \
every part on its storey (z = storey x storey_h) inside the walls (inner faces at w/2 - t and d/2 - t): a \
raised_floor (w x d) per hall, then rack_row (fronts to local -y) and dc_pod objects (two rows round a contained aisle: \
props.contain cold, hot or none; props.inrow adds an in-row cooler every n racks) on it, which stand on its top by \
themselves; crah units along the walls; power_cab line-ups (UPS, battery, PDU, switchgear, RPP). Overhead runs \
(cable_tray, busway, fire_pipe: VESDA, sprinkler or gas) take props.h above the slab: keep crossing runs at different \
heights (busway 3.2, fibre 3.6, ladder 4.0, gas or sprinkler 4.25, VESDA 4.4, smoke_detectors 4.5 for 5 m storeys). \
A gas_bank sizes its agent for the raised-floor halls on its storey; tooltips report IT load, cooling redundancy, \
UPS load and battery runtime. Chillers and cooling towers go on the roof (z = storeys x storey_h) or in the yard.";
fn tool(name: &str, description: &str, props: Value, required: &[&str]) -> Value {
json!({ "name": name, "description": description,
+39
View File
@@ -94,6 +94,45 @@ fn country_demo_renders_every_view_cleanly() {
example_renders_every_view_cleanly("country");
}
#[test]
fn datacentre_demo_renders_every_view_cleanly() {
let p = Project::load(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/datacentre"))
.unwrap();
assert!(p.runtime.is_ok(), "{:?}", p.runtime.as_ref().err());
// storeys of 5 m; the building covers |x| <= 21, |y| <= 13 and has three
// storeys, the plant on its roof (storey 3)
let storey = |z: f64| ((z + 0.5) / 5.0).floor() as i64;
for l in p.manifest.levels() {
let k: i64 = match l.id.as_str() {
"gf" => 0,
"1f" => 1,
"2f" => 2,
_ => -1,
};
for rot in 0..4 {
let v = p.render(&l.id, rot, None, false).unwrap();
assert!(v.warnings.is_empty(), "{}/{rot}: {:?}", l.id, v.warnings);
assert_eq!(v.cycles, 0, "{}/{rot}: {:?}", l.id, v.cycle_at);
assert!(!v.html.contains("NaN"));
for o in p.doc.objects.iter().filter(|o| o.kind != "group") {
let s = storey(o.z);
let ground = o.kind == "site" || o.kind == "area";
let inside = !ground && o.x.abs() < 21.0 && o.y.abs() < 13.0;
// the cut storey shows, closed storeys inside the shell and storeys above do not
let shown = o.kind == "dc_building" || !inside || s == k || (k < 0 && s >= 3);
assert_eq!(
v.boxes.contains_key(&o.id),
shown,
"{} on {}/{rot}",
o.id,
l.id
);
}
}
}
assert!(check::check(&p).is_empty(), "{:?}", check::check(&p));
}
fn example_renders_every_view_cleanly(name: &str) {
let p = Project::load(
Path::new(env!("CARGO_MANIFEST_DIR"))