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
@@ -24,6 +24,7 @@ on:
|
|||||||
- "examples/factory/**"
|
- "examples/factory/**"
|
||||||
- "examples/streets/**"
|
- "examples/streets/**"
|
||||||
- "examples/country/**"
|
- "examples/country/**"
|
||||||
|
- "examples/datacentre/**"
|
||||||
- "tools/build_web.py"
|
- "tools/build_web.py"
|
||||||
- "Cargo.toml"
|
- "Cargo.toml"
|
||||||
- "Cargo.lock"
|
- "Cargo.lock"
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ isokit build C:/dev/myproject/site-view # dist/index.html, no server needed
|
|||||||
|
|
||||||
Try the demos: `isokit studio examples/demo` (a works site),
|
Try the demos: `isokit studio examples/demo` (a works site),
|
||||||
`isokit studio examples/house` (a furnished house built from parts),
|
`isokit studio examples/house` (a furnished house built from parts),
|
||||||
`isokit studio examples/factory` (presses, conveyors and robot arms) and
|
`isokit studio examples/factory` (presses, conveyors and robot arms),
|
||||||
`isokit studio examples/streets` (sidewalks, curves, junctions, a roundabout).
|
`isokit studio examples/streets` (sidewalks, curves, junctions, a roundabout) and
|
||||||
|
`isokit studio examples/datacentre` (a three-storey data centre).
|
||||||
|
|
||||||
### Studio (for everyone)
|
### Studio (for everyone)
|
||||||
|
|
||||||
@@ -102,6 +103,30 @@ rounded, and the movers pass in front of and behind trees and houses as they
|
|||||||
go. The streets demo has traffic round the block, a bus, motorbikes, cyclists
|
go. The streets demo has traffic round the block, a bus, motorbikes, cyclists
|
||||||
and people strolling.
|
and people strolling.
|
||||||
|
|
||||||
|
**Data centres** (palette groups Data centre, Cooling, Fire safety, Power):
|
||||||
|
the DC building is the shell of a multi-storey data centre (storeys of the
|
||||||
|
project's `storey_h`, 5 m in the demo); GF, 1F, 2F cut it open, and parts on
|
||||||
|
closed storeys are not drawn at all. On each storey a Raised floor (0.6 m
|
||||||
|
tiles over the supply plenum) carries Rack rows and Rack pods: cabinets of
|
||||||
|
any width, depth and height in U (servers, storage, GPU, network or empty,
|
||||||
|
mesh, glass or solid doors, status LEDs), in-row coolers between groups of
|
||||||
|
racks, and pods with cold aisle containment (vent tiles, glass roof, sliding
|
||||||
|
end doors, cold air rising) or hot aisle containment (backs to the aisle,
|
||||||
|
optional duct to the ceiling); Cages for colocation. Cooling: CRAH / CRAC
|
||||||
|
units, chillers and dry or adiabatic coolers with fan rows, cooling towers
|
||||||
|
with a plume, flow and return pipe runs. Fire safety: detector grids with
|
||||||
|
their coverage, VESDA sampling pipes with the detector unit, pre-action
|
||||||
|
sprinkler and gas discharge pipes, clean-agent cylinder banks (Novec 1230,
|
||||||
|
FM-200, Inergen), alarm and release panels, call points, abort switches,
|
||||||
|
beacons, extinguishers. Power: UPS, battery, PDU, switchgear and RPP
|
||||||
|
line-ups, busways with tap-off boxes, cable trays and fibre ducts,
|
||||||
|
transformers. The tooltips do the sums: IT load per pod, floor and building,
|
||||||
|
kW/m², cooling against load per storey (N+1 and so on), UPS loading,
|
||||||
|
battery runtime, the agent a gas bank needs for its halls (a warning label
|
||||||
|
when it is short). `examples/datacentre` (Data centre demo in the web Studio)
|
||||||
|
has a power room, a hall with cold aisle pods, a GPU hall with hot aisle pods
|
||||||
|
and cages, and chillers on the roof.
|
||||||
|
|
||||||
**Fences and walls** around the plot: the Fence has six designs (mesh, wooden
|
**Fences and walls** around the plot: the Fence has six designs (mesh, wooden
|
||||||
boards, steel bars, mesh on a stone base, cobble wall, brick wall with
|
boards, steel bars, mesh on a stone base, cobble wall, brick wall with
|
||||||
pillars). Fences and site walls whose ends meet join like house walls: corners
|
pillars). Fences and site walls whose ends meet join like house walls: corners
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ pub struct Rendered {
|
|||||||
pub nodes: usize,
|
pub nodes: usize,
|
||||||
/// Depth-sort cycles that had to be broken (0 is ideal).
|
/// Depth-sort cycles that had to be broken (0 is ideal).
|
||||||
pub cycles: usize,
|
pub cycles: usize,
|
||||||
|
/// Where they were broken: "a before b, c" (sprite ids; a was drawn
|
||||||
|
/// before b and c, which it should have followed).
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub cycle_at: Vec<String>,
|
||||||
pub warnings: Vec<String>,
|
pub warnings: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub order: Vec<OrderItem>,
|
pub order: Vec<OrderItem>,
|
||||||
@@ -714,6 +718,7 @@ pub fn render(scene: &Scene, rot: u8, opts: &RenderOpts) -> Rendered {
|
|||||||
let mut frame = [1e9f64, 1e9, -1e9, -1e9];
|
let mut frame = [1e9f64, 1e9, -1e9, -1e9];
|
||||||
let mut sprites = 0;
|
let mut sprites = 0;
|
||||||
let mut cycles = 0;
|
let mut cycles = 0;
|
||||||
|
let mut cycle_at = vec![];
|
||||||
let mut order = vec![];
|
let mut order = vec![];
|
||||||
let mut warnings = scene.warnings.clone();
|
let mut warnings = scene.warnings.clone();
|
||||||
|
|
||||||
@@ -748,6 +753,10 @@ pub fn render(scene: &Scene, rot: u8, opts: &RenderOpts) -> Rendered {
|
|||||||
.collect();
|
.collect();
|
||||||
let o = sort::sort_hulls(&pairs, &hulls, layer.opts.zfirst);
|
let o = sort::sort_hulls(&pairs, &hulls, layer.opts.zfirst);
|
||||||
cycles += o.cycles;
|
cycles += o.cycles;
|
||||||
|
for (b, before) in &o.broken {
|
||||||
|
let ids: Vec<&str> = before.iter().map(|&i| live[i].id.as_str()).collect();
|
||||||
|
cycle_at.push(format!("{} before {}", live[*b].id, ids.join(", ")));
|
||||||
|
}
|
||||||
o.order
|
o.order
|
||||||
} else {
|
} else {
|
||||||
(0..live.len()).collect()
|
(0..live.len()).collect()
|
||||||
@@ -876,6 +885,7 @@ pub fn render(scene: &Scene, rot: u8, opts: &RenderOpts) -> Rendered {
|
|||||||
sprites,
|
sprites,
|
||||||
nodes,
|
nodes,
|
||||||
cycles,
|
cycles,
|
||||||
|
cycle_at,
|
||||||
warnings,
|
warnings,
|
||||||
order,
|
order,
|
||||||
html,
|
html,
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ fn rect_of(b: &Bb) -> Hull {
|
|||||||
pub struct Order {
|
pub struct Order {
|
||||||
pub order: Vec<usize>,
|
pub order: Vec<usize>,
|
||||||
pub cycles: usize,
|
pub cycles: usize,
|
||||||
|
/// Each broken cycle: the sprite drawn early to break it and the sprites
|
||||||
|
/// not drawn yet that should have come before it.
|
||||||
|
pub broken: Vec<(usize, Vec<usize>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sort(boxes: &[(Bb, Sb)], zf: bool) -> Order {
|
pub fn sort(boxes: &[(Bb, Sb)], zf: bool) -> Order {
|
||||||
@@ -203,6 +206,7 @@ pub fn sort_hulls(boxes: &[(Bb, Sb)], hulls: &[Option<Hull>], zf: bool) -> Order
|
|||||||
let mut out = Order {
|
let mut out = Order {
|
||||||
order: Vec::with_capacity(n),
|
order: Vec::with_capacity(n),
|
||||||
cycles: 0,
|
cycles: 0,
|
||||||
|
broken: vec![],
|
||||||
};
|
};
|
||||||
for _ in 0..n {
|
for _ in 0..n {
|
||||||
let mut best: Option<usize> = None;
|
let mut best: Option<usize> = None;
|
||||||
@@ -227,6 +231,12 @@ pub fn sort_hulls(boxes: &[(Bb, Sb)], hulls: &[Option<Hull>], zf: bool) -> Order
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let b = best.expect("n > 0");
|
let b = best.expect("n > 0");
|
||||||
|
if indeg[b] > 0 {
|
||||||
|
let before: Vec<usize> = (0..n)
|
||||||
|
.filter(|&i| !done[i] && adj[i].contains(&b))
|
||||||
|
.collect();
|
||||||
|
out.broken.push((b, before));
|
||||||
|
}
|
||||||
done[b] = true;
|
done[b] = true;
|
||||||
out.order.push(b);
|
out.order.push(b);
|
||||||
for &j in &adj[b] {
|
for &j in &adj[b] {
|
||||||
@@ -245,6 +255,24 @@ mod tests {
|
|||||||
([x0, y0, x1, y1, z0, z1], [0.0, 0.0, 10.0, 10.0])
|
([x0, y0, x1, y1, z0, z1], [0.0, 0.0, 10.0, 10.0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn broken_cycles_name_the_sprites() {
|
||||||
|
// a before b (x), b before c (y), c before a (z): no order fits all three
|
||||||
|
let o = sort(
|
||||||
|
&[
|
||||||
|
bx(0.0, 0.0, 1.0, 10.0, 5.0, 6.0),
|
||||||
|
bx(2.0, 0.0, 3.0, 1.0, 0.0, 5.0),
|
||||||
|
bx(0.0, 2.0, 3.0, 3.0, 0.0, 1.0),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert_eq!(o.cycles, 1);
|
||||||
|
assert_eq!(o.broken.len(), 1);
|
||||||
|
let (b, before) = &o.broken[0];
|
||||||
|
assert_eq!(before.len(), 1);
|
||||||
|
assert_ne!(*b, before[0]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn separated_on_x_draws_far_first() {
|
fn separated_on_x_draws_far_first() {
|
||||||
let o = sort(
|
let o = sort(
|
||||||
|
|||||||
@@ -137,7 +137,17 @@ pub fn check(p: &Project) -> Vec<Finding> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
if v.cycles > 0 {
|
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
|
for cls in v
|
||||||
.html
|
.html
|
||||||
|
|||||||
@@ -201,6 +201,9 @@ pub struct View {
|
|||||||
pub sprites: usize,
|
pub sprites: usize,
|
||||||
pub nodes: usize,
|
pub nodes: usize,
|
||||||
pub cycles: 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>,
|
pub warnings: Vec<String>,
|
||||||
/// Object id -> world box [x0, y0, z0, x1, y1, z1].
|
/// Object id -> world box [x0, y0, z0, x1, y1, z1].
|
||||||
pub boxes: BTreeMap<String, [f64; 6]>,
|
pub boxes: BTreeMap<String, [f64; 6]>,
|
||||||
@@ -428,6 +431,7 @@ impl Project {
|
|||||||
sprites: r.sprites,
|
sprites: r.sprites,
|
||||||
nodes: r.nodes,
|
nodes: r.nodes,
|
||||||
cycles: r.cycles,
|
cycles: r.cycles,
|
||||||
|
cycle_at: r.cycle_at,
|
||||||
warnings: r.warnings,
|
warnings: r.warnings,
|
||||||
boxes: built.boxes,
|
boxes: built.boxes,
|
||||||
ms: (t0.elapsed().as_secs_f64() * 1e4).round() / 10.0,
|
ms: (t0.elapsed().as_secs_f64() * 1e4).round() / 10.0,
|
||||||
|
|||||||
@@ -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 \
|
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. \
|
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 \
|
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 {
|
fn tool(name: &str, description: &str, props: Value, required: &[&str]) -> Value {
|
||||||
json!({ "name": name, "description": description,
|
json!({ "name": name, "description": description,
|
||||||
|
|||||||
@@ -94,6 +94,45 @@ fn country_demo_renders_every_view_cleanly() {
|
|||||||
example_renders_every_view_cleanly("country");
|
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) {
|
fn example_renders_every_view_cleanly(name: &str) {
|
||||||
let p = Project::load(
|
let p = Project::load(
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ pub const MODULES: &[(&str, &str)] = &[
|
|||||||
("isokit/house", include_str!("../stdlib/house.rhai")),
|
("isokit/house", include_str!("../stdlib/house.rhai")),
|
||||||
("isokit/furniture", include_str!("../stdlib/furniture.rhai")),
|
("isokit/furniture", include_str!("../stdlib/furniture.rhai")),
|
||||||
("isokit/industry", include_str!("../stdlib/industry.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 r = Runtime::new(&Bundle::default()).unwrap();
|
||||||
let mut doc = Doc::default();
|
let mut doc = Doc::default();
|
||||||
for (i, m) in r.catalog().iter().enumerate() {
|
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
|
// switch every bool prop on to exercise all branches
|
||||||
for p in &m.props {
|
for p in &m.props {
|
||||||
if p.kind == "bool" {
|
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");
|
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 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.
|
// 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 wood_choices() { ["wood", "woodd", "furnw", "steel", "kitchenc"] }
|
||||||
fn fabric_choices() { ["uph1", "uph2", "uph3", "uph4", "sofa", "bed", "cloth"] }
|
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 }
|
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_cut_h() { 1.0 }
|
||||||
fn wall_types() { ["house_wall"] }
|
fn wall_types() { ["house_wall"] }
|
||||||
fn opening_types() { ["window", "door"] }
|
fn opening_types() { ["window", "door"] }
|
||||||
fn room_types() { ["floor"] }
|
fn room_types() { ["floor", "raised_floor"] }
|
||||||
fn house_wall_materials() { ["render", "render2", "brick", "wood", "wall", "wall2", "wallp", "concb", "office", "furnw"] }
|
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 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"] }
|
fn house_roof_materials() { ["rooft", "roofr", "roof", "roof2", "roof3", "roofcap", "canopy"] }
|
||||||
|
|
||||||
@@ -116,7 +131,7 @@ fn meta_house_wall() {
|
|||||||
|
|
||||||
fn prefab_house_wall(o) {
|
fn prefab_house_wall(o) {
|
||||||
let p = o.p;
|
let p = o.p;
|
||||||
if house_view(o) == 2 { return; }
|
if part_hidden(o) { return; }
|
||||||
let top = wall_top(o);
|
let top = wall_top(o);
|
||||||
let cap = if top < p.h { "cut" } else { p.mat };
|
let cap = if top < p.h { "cut" } else { p.mat };
|
||||||
let t = p.t / 2.0;
|
let t = p.t / 2.0;
|
||||||
@@ -203,7 +218,7 @@ fn meta_window() {
|
|||||||
fn prefab_window(o) {
|
fn prefab_window(o) {
|
||||||
let c = opening_at(o);
|
let c = opening_at(o);
|
||||||
let w = c.wall;
|
let w = c.wall;
|
||||||
if house_view(w) == 2 { return; }
|
if part_hidden(w) { return; }
|
||||||
let H = w.p.h;
|
let H = w.p.h;
|
||||||
let t = w.p.t / 2.0;
|
let t = w.p.t / 2.0;
|
||||||
let top = wall_top(w);
|
let top = wall_top(w);
|
||||||
@@ -247,7 +262,7 @@ fn meta_door() {
|
|||||||
fn prefab_door(o) {
|
fn prefab_door(o) {
|
||||||
let c = opening_at(o);
|
let c = opening_at(o);
|
||||||
let w = c.wall;
|
let w = c.wall;
|
||||||
if house_view(w) == 2 { return; }
|
if part_hidden(w) { return; }
|
||||||
let p = o.p;
|
let p = o.p;
|
||||||
let H = w.p.h;
|
let H = w.p.h;
|
||||||
let t = w.p.t / 2.0;
|
let t = w.p.t / 2.0;
|
||||||
@@ -314,7 +329,7 @@ fn meta_floor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prefab_floor(o) {
|
fn prefab_floor(o) {
|
||||||
if house_view(o) == 2 { return; }
|
if part_hidden(o) { return; }
|
||||||
let p = o.p;
|
let p = o.p;
|
||||||
let w = p.w / 2.0;
|
let w = p.w / 2.0;
|
||||||
let d = p.d / 2.0;
|
let d = p.d / 2.0;
|
||||||
@@ -495,7 +510,7 @@ fn meta_stairs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prefab_stairs(o) {
|
fn prefab_stairs(o) {
|
||||||
if house_view(o) == 2 { return; }
|
if part_hidden(o) { return; }
|
||||||
let p = o.p;
|
let p = o.p;
|
||||||
let w = p.w / 2.0;
|
let w = p.w / 2.0;
|
||||||
let n = fmax(2.0, (p.rise / 0.18).round()).to_int();
|
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 people_colours() { ["hivis", "guest1", "guest2", "guest3", "guest4", "guide", "suit"] }
|
||||||
|
|
||||||
fn meta_person() {
|
fn meta_person() {
|
||||||
#{ label: "Person", category: "People", icon: "🧍",
|
#{ label: "Person", category: "People", icon: "🧍", storey: true,
|
||||||
props: [
|
props: [
|
||||||
#{ key: "cls", type: "choice", def: "hivis", choices: people_colours(), label: "Clothes" },
|
#{ key: "cls", type: "choice", def: "hivis", choices: people_colours(), label: "Clothes" },
|
||||||
#{ key: "seated", type: "bool", def: false, label: "Seated" },
|
#{ 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) {
|
fn prefab_person(o) {
|
||||||
|
if part_hidden(o) { return; }
|
||||||
|
let z = dc_lift(o);
|
||||||
let s = sprite();
|
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() {
|
fn meta_crowd() {
|
||||||
#{ label: "Crowd", category: "People", icon: "👥",
|
#{ label: "Crowd", category: "People", icon: "👥", storey: true,
|
||||||
props: [
|
props: [
|
||||||
#{ key: "n", type: "int", def: 5, min: 2, max: 40, step: 1, label: "People" },
|
#{ 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)" },
|
#{ key: "r", type: "float", def: 2.0, min: 0.6, max: 15.0, step: 0.1, label: "Spread (m)" },
|
||||||
] }
|
] }
|
||||||
}
|
}
|
||||||
fn prefab_crowd(o) {
|
fn prefab_crowd(o) {
|
||||||
|
if part_hidden(o) { return; }
|
||||||
|
let z = dc_lift(o);
|
||||||
let cols = ["guest1", "guest2", "guest3", "guest4", "hivis"];
|
let cols = ["guest1", "guest2", "guest3", "guest4", "hivis"];
|
||||||
for i in 0..o.p.n {
|
for i in 0..o.p.n {
|
||||||
// one sprite each, scattered deterministically in a disc
|
// one sprite each, scattered deterministically in a disc
|
||||||
let a = hash(i * 1.7 + o.x) * 6.2832;
|
let a = hash(i * 1.7 + o.x) * 6.2832;
|
||||||
let r = o.p.r * hash(i * 3.3 + o.y).sqrt();
|
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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,6 +318,66 @@ walk = "#555c6b"
|
|||||||
kerbf = "#7d8494"
|
kerbf = "#7d8494"
|
||||||
apron = "#4a4840"
|
apron = "#4a4840"
|
||||||
|
|
||||||
|
# data centre
|
||||||
|
dccab = "#262b34"
|
||||||
|
dccabt = "#3a414d"
|
||||||
|
dcsrv = "#5b6678"
|
||||||
|
dcstor = "#6f7f96"
|
||||||
|
dcgpu = "#414b5c"
|
||||||
|
dcnet = "#2f7396"
|
||||||
|
dcpatch = "#c9ced6"
|
||||||
|
dcslot = "#2a3039"
|
||||||
|
dcinrow = "#aab4c2"
|
||||||
|
dcrf = "#9aa3b0"
|
||||||
|
dcrf2 = "#7f93ad"
|
||||||
|
dcrf3 = "#a69e90"
|
||||||
|
dcrfs = "#5a616d"
|
||||||
|
dcperf = "#5f86b8"
|
||||||
|
dccold = "#8fcaff"
|
||||||
|
dchot = "#ff8a5a"
|
||||||
|
dcalu = "#b9c2ce"
|
||||||
|
dcglass = "#8fc8ff"
|
||||||
|
dcglassh = "#ffb08a"
|
||||||
|
dccrah = "#c4ccd6"
|
||||||
|
dcups = "#b8c0cb"
|
||||||
|
dcbatt = "#3a414d"
|
||||||
|
dcpdu = "#5d6f8a"
|
||||||
|
dcswg = "#8a96a6"
|
||||||
|
dcbus = "#9ea4ad"
|
||||||
|
dctap = "#c46a24"
|
||||||
|
dctray = "#8e98a6"
|
||||||
|
dcfibre = "#e3b62b"
|
||||||
|
dcbasket = "#a7b0bc"
|
||||||
|
dccable = "#1b1f26"
|
||||||
|
dcfibrec = "#f0d060"
|
||||||
|
dccablec = "#3f78c0"
|
||||||
|
dcdet = "#e9edf2"
|
||||||
|
dcdeth = "#f2e2b6"
|
||||||
|
dcvesda = "#e0574a"
|
||||||
|
dcfirel = "#e0574a"
|
||||||
|
dcgaspipe = "#aab3be"
|
||||||
|
dcpanel = "#c03c35"
|
||||||
|
dcyel = "#e0b030"
|
||||||
|
dcwhite = "#f2f4f7"
|
||||||
|
dcgreen = "#3f9a5a"
|
||||||
|
dcscreen = "#58d0e0"
|
||||||
|
dctower = "#9fb0c2"
|
||||||
|
dctowert = "#7e8fa2"
|
||||||
|
dcpipeg = "#3f9a5a"
|
||||||
|
dcpipeg2 = "#2c7344"
|
||||||
|
dcpipev = "#8a5fc0"
|
||||||
|
dccoil = "#6f8193"
|
||||||
|
dcpad = "#8fa58a"
|
||||||
|
dcband = "#6d7480"
|
||||||
|
dcvoid = "#1a1e25"
|
||||||
|
dcleg = "#7a828e"
|
||||||
|
dcmeshp = "#9aa6b5"
|
||||||
|
dcpdu0 = "#b8413a"
|
||||||
|
dcrear = "#1e2229"
|
||||||
|
dcbay = "#15181d"
|
||||||
|
dcdoor = "#30363f"
|
||||||
|
dcbrk = "#2b2f37"
|
||||||
|
dcpanelb = "#9aa4b2"
|
||||||
[palette.light]
|
[palette.light]
|
||||||
sky1 = "#a9d4fb"
|
sky1 = "#a9d4fb"
|
||||||
sky2 = "#e8f4ff"
|
sky2 = "#e8f4ff"
|
||||||
@@ -626,6 +686,66 @@ walk = "#d9d6cf"
|
|||||||
kerbf = "#a5a9b1"
|
kerbf = "#a5a9b1"
|
||||||
apron = "#b9ad98"
|
apron = "#b9ad98"
|
||||||
|
|
||||||
|
# data centre
|
||||||
|
dccab = "#3b424e"
|
||||||
|
dccabt = "#5a6371"
|
||||||
|
dcsrv = "#7d899b"
|
||||||
|
dcstor = "#94a3b8"
|
||||||
|
dcgpu = "#5d6878"
|
||||||
|
dcnet = "#3f8fb5"
|
||||||
|
dcpatch = "#eef1f5"
|
||||||
|
dcslot = "#454c58"
|
||||||
|
dcinrow = "#dde2e8"
|
||||||
|
dcrf = "#d3d8df"
|
||||||
|
dcrf2 = "#b6c6da"
|
||||||
|
dcrf3 = "#d8d0c2"
|
||||||
|
dcrfs = "#8d95a2"
|
||||||
|
dcperf = "#8eb0da"
|
||||||
|
dccold = "#3f86e0"
|
||||||
|
dchot = "#e0582a"
|
||||||
|
dcalu = "#a3adbb"
|
||||||
|
dcglass = "#5aa6e6"
|
||||||
|
dcglassh = "#f08a5a"
|
||||||
|
dccrah = "#eef1f4"
|
||||||
|
dcups = "#e3e7ec"
|
||||||
|
dcbatt = "#5d6573"
|
||||||
|
dcpdu = "#7f93ad"
|
||||||
|
dcswg = "#b2bcc8"
|
||||||
|
dcbus = "#c3c8cf"
|
||||||
|
dctap = "#dd7f2f"
|
||||||
|
dctray = "#aeb7c3"
|
||||||
|
dcfibre = "#e8c22e"
|
||||||
|
dcbasket = "#c3cad3"
|
||||||
|
dccable = "#2a2f38"
|
||||||
|
dcfibrec = "#f5dc70"
|
||||||
|
dccablec = "#4f8fd8"
|
||||||
|
dcdet = "#ffffff"
|
||||||
|
dcdeth = "#f7ecc8"
|
||||||
|
dcvesda = "#d24a3c"
|
||||||
|
dcfirel = "#d0453f"
|
||||||
|
dcgaspipe = "#9aa3ad"
|
||||||
|
dcpanel = "#d0453f"
|
||||||
|
dcyel = "#e8c22e"
|
||||||
|
dcwhite = "#ffffff"
|
||||||
|
dcgreen = "#2fae68"
|
||||||
|
dcscreen = "#2aa8bf"
|
||||||
|
dctower = "#c8d3df"
|
||||||
|
dctowert = "#a9b6c4"
|
||||||
|
dcpipeg = "#2fae68"
|
||||||
|
dcpipeg2 = "#23864f"
|
||||||
|
dcpipev = "#9b6fd8"
|
||||||
|
dccoil = "#8fa1b4"
|
||||||
|
dcpad = "#a9c0a2"
|
||||||
|
dcband = "#9aa1ab"
|
||||||
|
dcvoid = "#3a404b"
|
||||||
|
dcleg = "#a3abb6"
|
||||||
|
dcmeshp = "#b9c4d2"
|
||||||
|
dcpdu0 = "#c9473a"
|
||||||
|
dcrear = "#2f353f"
|
||||||
|
dcbay = "#22262d"
|
||||||
|
dcdoor = "#4a525f"
|
||||||
|
dcbrk = "#3a404b"
|
||||||
|
dcpanelb = "#c3cad3"
|
||||||
[materials]
|
[materials]
|
||||||
warn = "#e0412f"
|
warn = "#e0412f"
|
||||||
suit = "suit"
|
suit = "suit"
|
||||||
@@ -903,6 +1023,56 @@ f-carpet = "fcarpet"
|
|||||||
f-stone = "fstone"
|
f-stone = "fstone"
|
||||||
f-conc = "conc"
|
f-conc = "conc"
|
||||||
|
|
||||||
|
# data centre
|
||||||
|
dccab = "dccab"
|
||||||
|
dccabt = "dccabt"
|
||||||
|
dcsrv = "dcsrv"
|
||||||
|
dcstor = "dcstor"
|
||||||
|
dcgpu = "dcgpu"
|
||||||
|
dcnet = "dcnet"
|
||||||
|
dcpatch = "dcpatch"
|
||||||
|
dcslot = "dcslot"
|
||||||
|
dcinrow = "dcinrow"
|
||||||
|
dcrf = "dcrf"
|
||||||
|
dcrf2 = "dcrf2"
|
||||||
|
dcrf3 = "dcrf3"
|
||||||
|
dcrfs = "dcrfs"
|
||||||
|
dcalu = "dcalu"
|
||||||
|
dccrah = "dccrah"
|
||||||
|
dcups = "dcups"
|
||||||
|
dcbatt = "dcbatt"
|
||||||
|
dcpdu = "dcpdu"
|
||||||
|
dcswg = "dcswg"
|
||||||
|
dcbus = "dcbus"
|
||||||
|
dctap = "dctap"
|
||||||
|
dctray = "dctray"
|
||||||
|
dcfibre = "dcfibre"
|
||||||
|
dcbasket = "dcbasket"
|
||||||
|
dccable = "dccable"
|
||||||
|
dcfibrec = "dcfibrec"
|
||||||
|
dccablec = "dccablec"
|
||||||
|
dcvesda = "dcvesda"
|
||||||
|
dcgaspipe = "dcgaspipe"
|
||||||
|
dcpanel = "dcpanel"
|
||||||
|
dcyel = "dcyel"
|
||||||
|
dcwhite = "dcwhite"
|
||||||
|
dcgreen = "dcgreen"
|
||||||
|
dctower = "dctower"
|
||||||
|
dctowert = "dctowert"
|
||||||
|
dcpipeg = "dcpipeg"
|
||||||
|
dcpipeg2 = "dcpipeg2"
|
||||||
|
dcpipev = "dcpipev"
|
||||||
|
dccoil = "dccoil"
|
||||||
|
dcpad = "dcpad"
|
||||||
|
dcband = "dcband"
|
||||||
|
dcvoid = "dcvoid"
|
||||||
|
dcleg = "dcleg"
|
||||||
|
dcpdu0 = "dcpdu0"
|
||||||
|
dcrear = "dcrear"
|
||||||
|
dcbay = "dcbay"
|
||||||
|
dcdoor = "dcdoor"
|
||||||
|
dcbrk = "dcbrk"
|
||||||
|
dcpanelb = "dcpanelb"
|
||||||
[rules]
|
[rules]
|
||||||
".iso-ground .iso-dirt" = "stroke: var(--iso-edge); stroke-width: .4"
|
".iso-ground .iso-dirt" = "stroke: var(--iso-edge); stroke-width: .4"
|
||||||
".iso-svg .iso-grid" = "fill: none; stroke: var(--iso-grid); stroke-width: .5"
|
".iso-svg .iso-grid" = "fill: none; stroke: var(--iso-grid); stroke-width: .5"
|
||||||
@@ -1029,3 +1199,33 @@ f-conc = "conc"
|
|||||||
".iso-svg .iso-hobr" = "fill: none; stroke: #6a707b; stroke-width: .3"
|
".iso-svg .iso-hobr" = "fill: none; stroke: #6a707b; stroke-width: .3"
|
||||||
".iso-svg .iso-metal" = "fill: var(--iso-steel); stroke: none"
|
".iso-svg .iso-metal" = "fill: var(--iso-steel); stroke: none"
|
||||||
".iso-svg .iso-keyb" = "fill: #2b2f37; stroke: none"
|
".iso-svg .iso-keyb" = "fill: #2b2f37; stroke: none"
|
||||||
|
# data centre
|
||||||
|
".iso-svg .iso-dctile" = "fill: none; stroke: var(--iso-edge); stroke-width: .2; opacity: .3"
|
||||||
|
".iso-svg .iso-dcperf" = "fill: var(--iso-dcperf); stroke: none; opacity: .85"
|
||||||
|
".iso-svg .iso-dcglass, .iso-svg .iso-dcglass.iso-t" = "fill-opacity: .2; --b: var(--iso-dcglass); stroke-opacity: .55"
|
||||||
|
".iso-svg .iso-dcglassh, .iso-svg .iso-dcglassh.iso-t" = "fill-opacity: .2; --b: var(--iso-dcglassh); stroke-opacity: .55"
|
||||||
|
".iso-svg .iso-dcglassd" = "fill: var(--iso-dcglass); fill-opacity: .14; stroke: none"
|
||||||
|
".iso-svg .iso-dcmesh" = "fill: #000; fill-opacity: .3; stroke: none"
|
||||||
|
".iso-svg .iso-dcmeshp, .iso-svg .iso-dcmeshp.iso-t" = "fill-opacity: .25; --b: var(--iso-dcmeshp); stroke-opacity: .45"
|
||||||
|
".iso-svg .iso-dcmeshd, .iso-svg .iso-dcmeshd.iso-t" = "fill-opacity: .45; --b: var(--iso-dcmeshp); stroke-opacity: .6"
|
||||||
|
".iso-svg .iso-dcframe" = "fill: none; stroke: var(--iso-dcalu); stroke-width: .3"
|
||||||
|
".iso-svg path.iso-dcsrv, .iso-svg path.iso-dcstor, .iso-svg path.iso-dcgpu, .iso-svg path.iso-dcnet, .iso-svg path.iso-dcpatch, .iso-svg path.iso-dcslot, .iso-svg path.iso-dcbay, .iso-svg path.iso-dcrear, .iso-svg path.iso-dcpdu0, .iso-svg path.iso-dcvoid, .iso-svg path.iso-dcleg, .iso-svg path.iso-dcband, .iso-svg path.iso-dcbrk, .iso-svg path.iso-dcdoor" = "stroke: none"
|
||||||
|
".iso-svg .iso-dcstrip" = "fill: var(--iso-led-g); stroke: none; opacity: .9"
|
||||||
|
".iso-svg .iso-dcscr" = "fill: var(--iso-dcscreen); stroke: none"
|
||||||
|
".iso-svg .iso-dccov" = "fill: var(--iso-dchot); fill-opacity: .025; stroke: var(--iso-dchot); stroke-width: .22; stroke-dasharray: 1.2 2.2; opacity: .45; pointer-events: none"
|
||||||
|
".iso-svg .iso-dcdetd" = "fill: var(--iso-dcdet); stroke: var(--iso-edge); stroke-width: .12"
|
||||||
|
".iso-svg .iso-dcdethd" = "fill: var(--iso-dcdeth); stroke: var(--iso-edge); stroke-width: .12"
|
||||||
|
".iso-svg .iso-dcvesl" = "fill: none; stroke: var(--iso-dcvesda); stroke-width: .5; stroke-linecap: round"
|
||||||
|
".iso-svg .iso-dcsprl" = "fill: none; stroke: var(--iso-dcfirel); stroke-width: .8; stroke-linecap: round"
|
||||||
|
".iso-svg .iso-dcgasl" = "fill: none; stroke: var(--iso-dcgaspipe); stroke-width: .9; stroke-linecap: round"
|
||||||
|
".iso-svg .iso-dchole" = "fill: var(--iso-dcwhite); stroke: var(--iso-dcvesda); stroke-width: .2"
|
||||||
|
".iso-svg .iso-dcspr" = "fill: var(--iso-dcfirel); stroke: none"
|
||||||
|
".iso-svg .iso-dcsprh" = "fill: var(--iso-dcyel); stroke: none"
|
||||||
|
".iso-svg .iso-dcdled" = "fill: var(--iso-led-r); stroke: none"
|
||||||
|
".iso-svg .iso-dcflash" = "animation: iso-dcflash 4s linear infinite"
|
||||||
|
"@keyframes iso-dcflash" = "0%, 86%, 100% { opacity: .3 } 90% { opacity: 1 }"
|
||||||
|
".iso-svg .iso-dcblink" = "animation: iso-blink 1.9s ease-in-out infinite"
|
||||||
|
".iso-svg .iso-dcair, .iso-svg .iso-dcheat" = "transform-box: fill-box; transform-origin: center; animation: iso-dcrise 3s ease-out infinite; opacity: 0; pointer-events: none; stroke: none"
|
||||||
|
".iso-svg .iso-dcair" = "fill: var(--iso-dccold)"
|
||||||
|
".iso-svg .iso-dcheat" = "fill: var(--iso-dchot)"
|
||||||
|
"@keyframes iso-dcrise" = "0% { transform: translate(0, 0) scale(.5); opacity: 0 } 25% { opacity: .6 } 100% { transform: translate(0, -7px) scale(1.3); opacity: 0 }"
|
||||||
@@ -71,6 +71,31 @@ Each phase ends with tests, a Studio/viewer check in Edge, commit and push.
|
|||||||
- [x] Diagonal walls (any angle, mitred corners, windows and doors on them)
|
- [x] Diagonal walls (any angle, mitred corners, windows and doors on them)
|
||||||
- [ ] Openings across two walls; floor openings for stairs
|
- [ ] Openings across two walls; floor openings for stairs
|
||||||
|
|
||||||
|
## Data centres (done)
|
||||||
|
|
||||||
|
- [x] `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, parts on
|
||||||
|
closed storeys inside it are left out (`part_hidden`, also for house parts and people)
|
||||||
|
- [x] Raised floor (tiles, void with pedestals); racks, pods, units, cabinets and people stand on it
|
||||||
|
- [x] Rack row and Rack pod: cabinets by U, width, depth, equipment (servers, storage, GPU, network,
|
||||||
|
empty), fill, doors, status LEDs; in-row coolers; cold / hot aisle containment (vent tiles,
|
||||||
|
roof, end doors, ceiling duct, airflow); colocation Cage
|
||||||
|
- [x] Cooling: CRAH / CRAC units, chillers, dry and adiabatic coolers, cooling towers, pipe runs
|
||||||
|
- [x] Fire safety: detector grids with coverage, VESDA / sprinkler / gas discharge pipes, clean-agent
|
||||||
|
cylinder banks sized for their halls (Novec 1230, FM-200, Inergen), panels, call points, abort
|
||||||
|
switches, beacons, extinguishers
|
||||||
|
- [x] Power: UPS, battery, PDU, switchgear and RPP line-ups, busways, cable trays, transformers
|
||||||
|
- [x] Tooltips do the sums: IT load and kW/m², cooling redundancy per storey, UPS load, battery
|
||||||
|
runtime, plant cooling, agent quantity (warning label when short)
|
||||||
|
- [x] Overhead services in their own `ceiling` layer (z first) after the floor; `isokit check` names
|
||||||
|
where the depth sort broke a cycle
|
||||||
|
- [x] `examples/datacentre` (three storeys and a roof), Data centre demo in the web Studio
|
||||||
|
- [ ] Next: floor openings and a lift / stair core through the slabs; cold aisle airflow from the
|
||||||
|
CRAHs drawn under the raised floor (section cut, Phase 4); generator yard and fuel system
|
||||||
|
parts (day tanks, ATS); security (CCTV, access control, mantraps); leak detection; liquid
|
||||||
|
cooling (CDUs, rear-door heat exchangers, manifolds); a row-by-row power and cooling budget
|
||||||
|
table on the building
|
||||||
|
|
||||||
## Backlog (2026-10)
|
## Backlog (2026-10)
|
||||||
|
|
||||||
1. ~~Web performance~~ (done, see below)
|
1. ~~Web performance~~ (done, see below)
|
||||||
|
|||||||
@@ -293,6 +293,48 @@ prefabs work together through the document, not through a parent object:
|
|||||||
- Ground-level floors and rugs go to the `floor` layer; upper floors are slabs
|
- Ground-level floors and rugs go to the `floor` layer; upper floors are slabs
|
||||||
whose top is at the object's z. Furniture starts 0.02 m above its z.
|
whose top is at the object's z. Furniture starts 0.02 m above its z.
|
||||||
|
|
||||||
|
## Data centres
|
||||||
|
|
||||||
|
`datacentre.rhai` lays out multi-storey data centres from parts that read
|
||||||
|
each other through the document, like house parts:
|
||||||
|
|
||||||
|
- `dc_building` is the shell: `storeys` of `storey_h` (the project
|
||||||
|
parameter; 5 m suits data halls), outer walls split into pieces of at most
|
||||||
|
15 m, a slab per storey and a flat roof with a parapet. On level `kf` the
|
||||||
|
storeys below are closed, the walls facing the viewer are cut 1.2 m above
|
||||||
|
storey k, the others reach its ceiling, and everything above is left out.
|
||||||
|
- `part_hidden(o)` (house.rhai) leaves out a part above the storey a level
|
||||||
|
cuts open, and a part on a closed storey inside a `dc_building`: the shell
|
||||||
|
hides it, and drawing it would only make sort cycles with what stands on
|
||||||
|
the slab above. House walls, doors, floors, stairs, furniture, people and
|
||||||
|
every data centre part use it (`dc_skip` is the same rule).
|
||||||
|
- `dc_lift(o)`: the top of a `raised_floor` under o on its storey; racks,
|
||||||
|
pods, CRAH units, cabinets, gas banks, devices and people stand on it.
|
||||||
|
- A rack line is one sprite with a box per cabinet (`seq`) and the faces
|
||||||
|
painted with `open()` per class (equipment, LED strips, rear PDUs), so a
|
||||||
|
pod of 20 racks costs a few dozen nodes. In-row coolers (`inrow`) are
|
||||||
|
0.3 m units between groups of racks. A pod is three sprites (two rows and
|
||||||
|
the aisle between them: vent tiles, airflow puffs, end doors, roof, duct),
|
||||||
|
which never overlap.
|
||||||
|
- Overhead services (`cable_tray`, `busway`, `fire_pipe`, `smoke_detectors`)
|
||||||
|
draw in the layer `ceiling` (`dc_ceiling_layer()`: sorted, z first) after
|
||||||
|
everything on the floor, with sort boxes as tight as the run itself
|
||||||
|
(hangers do not count): a tray beside a pod with a busway over the pod and
|
||||||
|
under the tray has no valid order as boxes in one layer. Keep runs that
|
||||||
|
cross or lie side by side at heights that do not overlap. The project
|
||||||
|
parameter `ceiling` = false leaves them out.
|
||||||
|
- Totals: `dc_it(storey)` (IT load, racks; every storey for -1),
|
||||||
|
`dc_cool(storey)` (CRAH duty and standby, in-row coolers),
|
||||||
|
`dc_redundancy(cooling, kw)` ("N+1, 140 kW spare"), `dc_plant()` (chillers
|
||||||
|
and towers), `dc_agent(agent, V, t, litres)` (Novec 1230 at 4.5 %, FM-200
|
||||||
|
at 7 % by NFPA 2001 / ISO 14520, Inergen at 39.9 %), `dc_gas_volume(o)`.
|
||||||
|
A gas bank that is short puts a `warntag` on the view.
|
||||||
|
- Wall devices facing away from the viewer on the storey cut open hang on
|
||||||
|
the far side of a wall that is cut low: they draw ghosted.
|
||||||
|
|
||||||
|
`isokit check` names where the depth sort broke a cycle ("a before b, c":
|
||||||
|
sprite ids, `Rendered.cycle_at`), which points at the sprites to split.
|
||||||
|
|
||||||
## Rules that keep the depth sort right
|
## Rules that keep the depth sort right
|
||||||
|
|
||||||
These are lessons from the CNX/Mooban views, and the engine relies on them:
|
These are lessons from the CNX/Mooban views, and the engine relies on them:
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# isokit data centre demo: a three-storey data centre laid out from parts.
|
||||||
|
# Open it with `isokit studio examples/datacentre`.
|
||||||
|
title = "isokit data centre"
|
||||||
|
note = "A three-storey data centre: power and plant on the ground floor, a hall with cold aisle containment on 1F, GPU pods with hot aisle containment and colocation cages on 2F, chillers on the roof. GF, 1F and 2F cut the building open; tap racks, coolers, gas banks and detectors for their numbers."
|
||||||
|
default_level = "1f"
|
||||||
|
default_rotation = 1
|
||||||
|
prelude = []
|
||||||
|
theme = []
|
||||||
|
|
||||||
|
[[level]]
|
||||||
|
id = "site"
|
||||||
|
label = "Site"
|
||||||
|
title = "The data centre closed, with its roof plant"
|
||||||
|
|
||||||
|
[[level]]
|
||||||
|
id = "gf"
|
||||||
|
label = "GF"
|
||||||
|
title = "Ground floor: lobby and NOC, staging, power room"
|
||||||
|
|
||||||
|
[[level]]
|
||||||
|
id = "1f"
|
||||||
|
label = "1F"
|
||||||
|
title = "Data hall A: cold aisle containment"
|
||||||
|
|
||||||
|
[[level]]
|
||||||
|
id = "2f"
|
||||||
|
label = "2F"
|
||||||
|
title = "Data hall B: GPU pods (hot aisle) and colocation cages"
|
||||||
|
|
||||||
|
[[param]]
|
||||||
|
name = "storey_h"
|
||||||
|
type = "float"
|
||||||
|
default = 5.0
|
||||||
|
label = "Storey height (m)"
|
||||||
|
|
||||||
|
[[param]]
|
||||||
|
name = "ceiling"
|
||||||
|
type = "bool"
|
||||||
|
default = true
|
||||||
|
label = "Ceiling services"
|
||||||
|
|
||||||
|
[[param]]
|
||||||
|
name = "labels"
|
||||||
|
type = "bool"
|
||||||
|
default = true
|
||||||
|
label = "Labels"
|
||||||
|
|
||||||
|
[[param]]
|
||||||
|
name = "running"
|
||||||
|
type = "bool"
|
||||||
|
default = true
|
||||||
|
label = "Animation"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
out = "dist/index.html"
|
||||||
|
budget = 12000
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"objects": [
|
||||||
|
{"id": "site", "type": "site", "x": 0, "y": 2, "props": {"w": 92, "d": 74, "mat": "g-grass"}, "name": "Site", "locked": true},
|
||||||
|
{"id": "apron", "type": "area", "x": 0, "y": 1, "props": {"w": 64, "d": 44, "mat": "g-conc"}, "name": "Yard apron", "locked": true},
|
||||||
|
{"id": "road", "type": "road", "x": 0, "y": -29, "props": {"w": 92, "d": 7}, "name": "Main road"},
|
||||||
|
{"id": "access", "type": "road", "x": 0, "y": -21.5, "rot": 90, "props": {"w": 15, "d": 6, "dashed": false}, "name": "Access road"},
|
||||||
|
{"id": "parking", "type": "parking", "x": -22, "y": -17, "props": {"n": 8, "bay_w": 2.5, "bay_d": 5}, "name": "Staff parking"},
|
||||||
|
{"id": "car-0", "type": "car", "x": -30.75, "y": -17, "rot": 90, "props": {"colour": "car1"}, "name": "Car"},
|
||||||
|
{"id": "car-1", "type": "car", "x": -28.25, "y": -17, "rot": 90, "props": {"colour": "car3"}, "name": "Car"},
|
||||||
|
{"id": "car-3", "type": "car", "x": -23.25, "y": -17, "rot": 90, "props": {"colour": "car4"}, "name": "Car"},
|
||||||
|
{"id": "car-4", "type": "car", "x": -20.75, "y": -17, "rot": 90, "props": {"colour": "car1"}, "name": "Car"},
|
||||||
|
{"id": "truck", "type": "truck", "x": 0, "y": -18.5, "rot": 90, "props": {"len": 8}, "name": "Delivery truck", "info": "Racks arriving at the loading dock"},
|
||||||
|
{"id": "f-s1", "type": "fence", "x": -31.5, "y": -24.0, "props": {"w": 21.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-s2", "type": "fence", "x": -12.5, "y": -24.0, "props": {"w": 17.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-s3", "type": "fence", "x": 12.5, "y": -24.0, "props": {"w": 17.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-s4", "type": "fence", "x": 31.5, "y": -24.0, "props": {"w": 21.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-e1", "type": "fence", "x": 42.0, "y": -9.75, "rot": 90.0, "props": {"w": 28.5, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-e2", "type": "fence", "x": 42.0, "y": 18.75, "rot": 90.0, "props": {"w": 28.5, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-n1", "type": "fence", "x": 28.0, "y": 33.0, "rot": 180.0, "props": {"w": 28.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-n2", "type": "fence", "x": 0.0, "y": 33.0, "rot": 180.0, "props": {"w": 28.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-n3", "type": "fence", "x": -28.0, "y": 33.0, "rot": 180.0, "props": {"w": 28.0, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-w1", "type": "fence", "x": -42.0, "y": 18.75, "rot": 270.0, "props": {"w": 28.5, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "f-w2", "type": "fence", "x": -42.0, "y": -9.75, "rot": 270.0, "props": {"w": 28.5, "h": 2.4, "style": "mesh"}, "name": "Security fence"},
|
||||||
|
{"id": "guard", "type": "shed", "x": 8, "y": -21, "props": {"w": 3, "d": 2.4, "h": 2.7}, "name": "Gatehouse", "tag": "Security"},
|
||||||
|
{"id": "guard-p", "type": "person", "x": 6.2, "y": -21.6, "props": {"cls": "suit"}, "name": "Guard"},
|
||||||
|
{"id": "gen-1", "type": "generator", "x": -15, "y": 19, "props": {"running": true}, "name": "Generator 1", "info": "2.5 MVA diesel genset, N+1"},
|
||||||
|
{"id": "gen-2", "type": "generator", "x": -7, "y": 19, "props": {"running": false}, "name": "Generator 2", "info": "2.5 MVA diesel genset, N+1"},
|
||||||
|
{"id": "gen-3", "type": "generator", "x": 1, "y": 19, "props": {"running": false}, "name": "Generator 3", "info": "2.5 MVA diesel genset, N+1"},
|
||||||
|
{"id": "gen-4", "type": "generator", "x": 9, "y": 19, "props": {"running": false}, "name": "Generator 4", "info": "2.5 MVA diesel genset, N+1"},
|
||||||
|
{"id": "fuel", "type": "tank", "x": 19, "y": 19.5, "props": {"r": 1.8, "h": 3.4, "mat": "tank"}, "name": "Diesel tank", "info": "48 h of fuel at full load"},
|
||||||
|
{"id": "tx-1", "type": "transformer", "x": 29, "y": 7, "rot": 90, "props": {"kva": 3150}, "name": "Transformer T1", "tag": "T1"},
|
||||||
|
{"id": "tx-2", "type": "transformer", "x": 29, "y": 0.5, "rot": 90, "props": {"kva": 3150}, "name": "Transformer T2", "tag": "T2"},
|
||||||
|
{"id": "ct", "type": "cooling_tower", "x": 31, "y": -12, "props": {"cells": 2, "kw": 750}, "name": "Cooling tower", "tag": "Condenser water"},
|
||||||
|
{"id": "ct-pipe", "type": "pipe_run", "x": 25.0, "y": -12, "props": {"w": 4.0, "kind": "condenser", "dn": 0.3, "h": 1.0}, "name": "Condenser pipes"},
|
||||||
|
{"id": "tree-0", "type": "tree", "x": -30, "y": 28, "props": {"r": 2.2}, "name": "Tree"},
|
||||||
|
{"id": "tree-1", "type": "tree", "x": -22, "y": 28, "props": {"r": 2.2}, "name": "Tree"},
|
||||||
|
{"id": "tree-2", "type": "tree", "x": 22, "y": 28, "props": {"r": 2.2}, "name": "Tree"},
|
||||||
|
{"id": "tree-3", "type": "tree", "x": 34, "y": 28, "props": {"r": 2.2}, "name": "Tree"},
|
||||||
|
{"id": "trees-w", "type": "tree_row", "x": -37, "y": 4, "rot": 90, "props": {"w": 40, "spacing": 7, "r": 1.8}, "name": "Trees"},
|
||||||
|
{"id": "lamp-0", "type": "lamp", "x": -12, "y": -23, "props": {"h": 6}, "name": "Lamp"},
|
||||||
|
{"id": "lamp-1", "type": "lamp", "x": 12, "y": -23, "props": {"h": 6}, "name": "Lamp"},
|
||||||
|
{"id": "lamp-2", "type": "lamp", "x": -26, "y": 12, "props": {"h": 6}, "name": "Lamp"},
|
||||||
|
{"id": "lamp-3", "type": "lamp", "x": 26, "y": 14, "props": {"h": 6}, "name": "Lamp"},
|
||||||
|
{"id": "dc", "type": "dc_building", "x": 0, "y": 0, "props": {"w": 42, "d": 26, "storeys": 3, "door_at": 0.16666666666666666, "dock_at": 0.5}, "name": "Data centre CNX-1", "info": "Three storeys: power and plant on the ground floor, two data halls above, chillers on the roof"},
|
||||||
|
{"id": "gf-w1", "type": "house_wall", "x": -7, "y": 0, "rot": 90, "props": {"w": 25.2, "h": 4.7, "t": 0.2, "mat": "wall2"}, "name": "GF partition (lobby)"},
|
||||||
|
{"id": "gf-w1-door", "type": "door", "x": -7, "y": -9, "rot": 90, "props": {"w": 1.0, "h": 2.2, "style": "panel", "mat": "doorg"}, "name": "Lobby door"},
|
||||||
|
{"id": "gf-e1", "type": "house_wall", "x": 7, "y": 0, "rot": 90, "props": {"w": 25.2, "h": 4.7, "t": 0.2, "mat": "firewall"}, "name": "GF fire wall (power room)"},
|
||||||
|
{"id": "gf-e1-door", "type": "door", "x": 7, "y": -9, "rot": 90, "props": {"w": 1.2, "h": 2.2, "style": "panel", "mat": "doorg"}, "name": "Power room door"},
|
||||||
|
{"id": "gf-stairs", "type": "stairs", "x": -19.4, "y": 7.5, "props": {"w": 1.4, "run": 7, "rise": 4.7, "mat": "concb"}, "name": "Stairs to 1F"},
|
||||||
|
{"id": "noc-1", "type": "desk", "x": -15, "y": -3, "props": {"w": 1.6}, "name": "NOC desk"},
|
||||||
|
{"id": "noc-2", "type": "desk", "x": -12.6, "y": -3, "props": {"w": 1.6, "chair": false}, "name": "NOC desk"},
|
||||||
|
{"id": "noc-3", "type": "desk", "x": -15, "y": -6.5, "props": {"w": 1.6}, "name": "NOC desk"},
|
||||||
|
{"id": "noc-p", "type": "person", "x": -12.6, "y": -3.8, "props": {"cls": "suit", "seated": true}, "name": "NOC operator"},
|
||||||
|
{"id": "gf-facp", "type": "fire_device", "x": -17.5, "y": -12.53, "rot": 180, "props": {"kind": "alarm panel", "label": "Main fire alarm panel"}, "name": "Fire alarm panel", "tag": "FACP"},
|
||||||
|
{"id": "gf-cp-1", "type": "fire_device", "x": -12.4, "y": -12.53, "rot": 180, "props": {"kind": "call point", "label": "Lobby"}, "name": "Call point"},
|
||||||
|
{"id": "gf-bc-1", "type": "fire_device", "x": -7.27, "y": 2, "rot": 270, "props": {"kind": "beacon"}, "name": "Sounder-beacon"},
|
||||||
|
{"id": "gf-ext-1", "type": "fire_device", "x": -8, "y": -12.4, "rot": 180, "props": {"kind": "extinguisher"}, "name": "Extinguisher"},
|
||||||
|
{"id": "staging", "type": "rack_row", "x": 0, "y": 5.5, "props": {"n": 6, "kind": "empty", "u": 48, "doors": "none"}, "name": "Racks waiting for install", "tag": "Staging"},
|
||||||
|
{"id": "staging-p", "type": "person", "x": 1.8, "y": 3.6, "props": {"cls": "hivis"}, "name": "Technician"},
|
||||||
|
{"id": "gf-ext-2", "type": "fire_device", "x": 6.73, "y": -6, "rot": 90, "props": {"kind": "extinguisher"}, "name": "Extinguisher"},
|
||||||
|
{"id": "ups", "type": "power_cab", "x": 13.5, "y": 10.9, "props": {"kind": "UPS", "n": 6, "kw": 800}, "name": "UPS line-up", "tag": "UPS 6 × 800 kW"},
|
||||||
|
{"id": "batt-1", "type": "power_cab", "x": 13.5, "y": 7.6, "props": {"kind": "battery", "n": 12, "kwh": 60}, "name": "Battery string A"},
|
||||||
|
{"id": "batt-2", "type": "power_cab", "x": 13.5, "y": 4.6, "props": {"kind": "battery", "n": 12, "kwh": 60}, "name": "Battery string B"},
|
||||||
|
{"id": "swg", "type": "power_cab", "x": 13.5, "y": -3.5, "props": {"kind": "switchgear", "n": 7}, "name": "LV switchgear", "tag": "Switchgear"},
|
||||||
|
{"id": "gf-gas", "type": "gas_bank", "x": 20.45, "y": -8, "rot": 270, "props": {"agent": "Inergen", "n": 21, "litres": 140, "vol": 1620}, "name": "Inergen bank (power room)", "tag": "Inergen"},
|
||||||
|
{"id": "gf-cp-2", "type": "fire_device", "x": 7.27, "y": -7.6, "rot": 90, "props": {"kind": "call point", "label": "Power room"}, "name": "Call point"},
|
||||||
|
{"id": "gf-bc-2", "type": "fire_device", "x": 20.53, "y": 2, "rot": 270, "props": {"kind": "beacon"}, "name": "Sounder-beacon"},
|
||||||
|
{"id": "gf-det-w", "type": "smoke_detectors", "x": -13.85, "y": 0, "props": {"w": 13.4, "d": 25.2, "spacing": 6.5, "h": 4.4, "zone": "GF lobby"}, "name": "Detectors GF lobby"},
|
||||||
|
{"id": "gf-det-m", "type": "smoke_detectors", "x": 0, "y": 0, "props": {"w": 13.8, "d": 25.2, "spacing": 6.5, "h": 4.4, "zone": "GF staging"}, "name": "Detectors GF staging"},
|
||||||
|
{"id": "gf-det-e", "type": "smoke_detectors", "x": 13.85, "y": 0, "props": {"w": 13.4, "d": 25.2, "spacing": 6.5, "h": 4.4, "kind": "multi-sensor", "zone": "GF power room"}, "name": "Detectors GF power room"},
|
||||||
|
{"id": "1f-p1", "type": "house_wall", "x": -17.3, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 25.2, "h": 4.7, "t": 0.2, "mat": "firewall"}, "name": "1F fire wall (hall)"},
|
||||||
|
{"id": "1f-p2", "type": "house_wall", "x": -18.95, "y": 2.9, "z": 5.0, "props": {"w": 3.3, "h": 4.7, "t": 0.2, "mat": "wall2"}, "name": "1F core wall"},
|
||||||
|
{"id": "1f-p1-d1", "type": "door", "x": -17.3, "y": 8.5, "z": 5.0, "rot": 90, "props": {"w": 1.2, "h": 2.2, "style": "double", "mat": "doorg"}, "name": "Hall doors"},
|
||||||
|
{"id": "1f-p1-d2", "type": "door", "x": -17.3, "y": -1.0, "z": 5.0, "rot": 90, "props": {"w": 1.0, "h": 2.1, "style": "panel", "mat": "doorg"}, "name": "Gas room door"},
|
||||||
|
{"id": "1f-rf", "type": "raised_floor", "x": 1.8, "y": 0, "z": 5.0, "props": {"w": 37.8, "d": 25.2, "h": 0.6, "mat": "dcrf"}, "name": "1F raised floor"},
|
||||||
|
{"id": "1f-det", "type": "smoke_detectors", "x": 1.8, "y": 0, "z": 5.0, "props": {"w": 37.8, "d": 25.2, "spacing": 6.0, "h": 4.55, "zone": "1F hall"}, "name": "Detectors 1F hall"},
|
||||||
|
{"id": "1f-det-g", "type": "smoke_detectors", "x": -19.05, "y": -4.9, "z": 5.0, "props": {"w": 3.1, "d": 15.4, "spacing": 6.0, "h": 4.4, "coverage": false, "zone": "1F gas room"}, "name": "Detectors 1F gas room"},
|
||||||
|
{"id": "1f-vesda-1", "type": "fire_pipe", "x": 1.75, "y": -9, "z": 5.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 1"},
|
||||||
|
{"id": "1f-vesda-2", "type": "fire_pipe", "x": 1.75, "y": -3, "z": 5.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 2"},
|
||||||
|
{"id": "1f-vesda-3", "type": "fire_pipe", "x": 1.75, "y": 3, "z": 5.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 3"},
|
||||||
|
{"id": "1f-vesda-4", "type": "fire_pipe", "x": 1.75, "y": 9, "z": 5.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 4"},
|
||||||
|
{"id": "1f-tray", "type": "cable_tray", "x": 2, "y": 0, "z": 5.0, "props": {"w": 36, "kind": "ladder", "width": 0.6, "h": 4.0, "fill": 60}, "name": "Main power tray"},
|
||||||
|
{"id": "1f-rel", "type": "fire_device", "x": -17.03, "y": 5.4, "z": 5.0, "rot": 90, "props": {"kind": "release panel", "label": "1F hall"}, "name": "Release panel"},
|
||||||
|
{"id": "1f-abort", "type": "fire_device", "x": -17.03, "y": 6.4, "z": 5.0, "rot": 90, "props": {"kind": "abort switch"}, "name": "Abort switch"},
|
||||||
|
{"id": "1f-cp", "type": "fire_device", "x": -17.03, "y": 10.2, "z": 5.0, "rot": 90, "props": {"kind": "call point", "label": "1F hall"}, "name": "Call point"},
|
||||||
|
{"id": "1f-bc-1", "type": "fire_device", "x": -17.03, "y": -6, "z": 5.0, "rot": 90, "props": {"kind": "beacon"}, "name": "Sounder-beacon"},
|
||||||
|
{"id": "1f-bc-2", "type": "fire_device", "x": 20.53, "y": 5, "z": 5.0, "rot": 270, "props": {"kind": "beacon"}, "name": "Sounder-beacon"},
|
||||||
|
{"id": "1f-ext", "type": "fire_device", "x": -16.8, "y": 1.2, "z": 5.0, "rot": 90, "props": {"kind": "extinguisher"}, "name": "Extinguisher"},
|
||||||
|
{"id": "1f-stairs", "type": "stairs", "x": -19.4, "y": 7.9, "z": 5.0, "props": {"w": 1.4, "run": 6.4, "rise": 4.7, "mat": "concb"}, "name": "Stairs 1F"},
|
||||||
|
{"id": "1f-crah-n1", "type": "crah", "x": -13, "y": 11.95, "z": 5.0, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-N1"},
|
||||||
|
{"id": "1f-crah-s1", "type": "crah", "x": -13, "y": -11.95, "z": 5.0, "rot": 180, "props": {"kw": 150, "standby": true}, "name": "CRAH 1-S1"},
|
||||||
|
{"id": "1f-crah-n2", "type": "crah", "x": -6.5, "y": 11.95, "z": 5.0, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-N2"},
|
||||||
|
{"id": "1f-crah-s2", "type": "crah", "x": -6.5, "y": -11.95, "z": 5.0, "rot": 180, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-S2"},
|
||||||
|
{"id": "1f-crah-n3", "type": "crah", "x": 0, "y": 11.95, "z": 5.0, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-N3"},
|
||||||
|
{"id": "1f-crah-s3", "type": "crah", "x": 0, "y": -11.95, "z": 5.0, "rot": 180, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-S3"},
|
||||||
|
{"id": "1f-crah-n4", "type": "crah", "x": 6.5, "y": 11.95, "z": 5.0, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-N4"},
|
||||||
|
{"id": "1f-crah-s4", "type": "crah", "x": 6.5, "y": -11.95, "z": 5.0, "rot": 180, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-S4"},
|
||||||
|
{"id": "1f-crah-n5", "type": "crah", "x": 13, "y": 11.95, "z": 5.0, "props": {"kw": 150, "standby": true}, "name": "CRAH 1-N5"},
|
||||||
|
{"id": "1f-crah-s5", "type": "crah", "x": 13, "y": -11.95, "z": 5.0, "rot": 180, "props": {"kw": 150, "standby": false}, "name": "CRAH 1-S5"},
|
||||||
|
{"id": "1f-pod-A1", "type": "dc_pod", "x": -11.5, "y": 4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "servers", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod A1", "tag": "Pod A1"},
|
||||||
|
{"id": "1f-pod-A2", "type": "dc_pod", "x": -11.5, "y": -4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "servers", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod A2", "tag": "Pod A2"},
|
||||||
|
{"id": "1f-bus-AL", "type": "busway", "x": -12.7, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-bus-AR", "type": "busway", "x": -10.3, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-fibre-A", "type": "cable_tray", "x": -11.5, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 40}, "name": "Fibre duct"},
|
||||||
|
{"id": "1f-pod-B1", "type": "dc_pod", "x": -5.5, "y": 4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "servers", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod B1", "tag": "Pod B1"},
|
||||||
|
{"id": "1f-pod-B2", "type": "dc_pod", "x": -5.5, "y": -4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "servers", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod B2", "tag": "Pod B2"},
|
||||||
|
{"id": "1f-bus-BL", "type": "busway", "x": -6.7, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-bus-BR", "type": "busway", "x": -4.3, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-fibre-B", "type": "cable_tray", "x": -5.5, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 40}, "name": "Fibre duct"},
|
||||||
|
{"id": "1f-pod-C1", "type": "dc_pod", "x": 0.5, "y": 4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "storage", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod C1", "tag": "Pod C1"},
|
||||||
|
{"id": "1f-pod-C2", "type": "dc_pod", "x": 0.5, "y": -4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "storage", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod C2", "tag": "Pod C2"},
|
||||||
|
{"id": "1f-bus-CL", "type": "busway", "x": -0.7, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-bus-CR", "type": "busway", "x": 1.7, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-fibre-C", "type": "cable_tray", "x": 0.5, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 40}, "name": "Fibre duct"},
|
||||||
|
{"id": "1f-pod-D1", "type": "dc_pod", "x": 6.5, "y": 4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "servers", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod D1", "tag": "Pod D1"},
|
||||||
|
{"id": "1f-pod-D2", "type": "dc_pod", "x": 6.5, "y": -4.3, "z": 5.0, "rot": 90, "props": {"n": 10, "kind": "servers", "kw": 7, "fill": 75, "u": 42, "contain": "cold"}, "name": "Pod D2", "tag": "Pod D2"},
|
||||||
|
{"id": "1f-bus-DL", "type": "busway", "x": 5.3, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-bus-DR", "type": "busway", "x": 7.7, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "h": 3.15, "amps": 630}, "name": "Busway"},
|
||||||
|
{"id": "1f-fibre-D", "type": "cable_tray", "x": 6.5, "y": 0, "z": 5.0, "rot": 90, "props": {"w": 15, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 40}, "name": "Fibre duct"},
|
||||||
|
{"id": "1f-net", "type": "rack_row", "x": 13.6, "y": 0, "z": 5.0, "rot": 270, "props": {"n": 8, "kind": "network", "kw": 4, "fill": 80, "doors": "glass"}, "name": "Network row (meet-me)", "tag": "Meet-me room"},
|
||||||
|
{"id": "1f-pdu", "type": "power_cab", "x": 17.2, "y": 0, "z": 5.0, "rot": 270, "props": {"kind": "PDU", "n": 6, "kw": 300}, "name": "PDUs 1F"},
|
||||||
|
{"id": "1f-gaspipe-1", "type": "fire_pipe", "x": 1.75, "y": -6, "z": 5.0, "props": {"w": 37.5, "kind": "gas", "spacing": 6.2, "h": 4.25}, "name": "Gas discharge pipe"},
|
||||||
|
{"id": "1f-gaspipe-2", "type": "fire_pipe", "x": 1.75, "y": 6, "z": 5.0, "props": {"w": 37.5, "kind": "gas", "spacing": 6.2, "h": 4.25}, "name": "Gas discharge pipe"},
|
||||||
|
{"id": "1f-gas", "type": "gas_bank", "x": -20.45, "y": -5.4, "z": 5.0, "rot": 90, "props": {"agent": "Novec 1230", "n": 18, "litres": 140}, "name": "Novec 1230 bank (hall A)", "tag": "Novec 1230"},
|
||||||
|
{"id": "1f-tech", "type": "person", "x": 3.5, "y": 0.6, "z": 5.0, "props": {"cls": "hivis"}, "name": "Technician"},
|
||||||
|
{"id": "2f-p1", "type": "house_wall", "x": -17.3, "y": 0, "z": 10.0, "rot": 90, "props": {"w": 25.2, "h": 4.7, "t": 0.2, "mat": "firewall"}, "name": "2F fire wall (hall)"},
|
||||||
|
{"id": "2f-p2", "type": "house_wall", "x": -18.95, "y": 2.9, "z": 10.0, "props": {"w": 3.3, "h": 4.7, "t": 0.2, "mat": "wall2"}, "name": "2F core wall"},
|
||||||
|
{"id": "2f-p1-d1", "type": "door", "x": -17.3, "y": 8.5, "z": 10.0, "rot": 90, "props": {"w": 1.2, "h": 2.2, "style": "double", "mat": "doorg"}, "name": "Hall doors"},
|
||||||
|
{"id": "2f-p1-d2", "type": "door", "x": -17.3, "y": -1.0, "z": 10.0, "rot": 90, "props": {"w": 1.0, "h": 2.1, "style": "panel", "mat": "doorg"}, "name": "Gas room door"},
|
||||||
|
{"id": "2f-rf", "type": "raised_floor", "x": 1.8, "y": 0, "z": 10.0, "props": {"w": 37.8, "d": 25.2, "h": 0.6, "mat": "dcrf2"}, "name": "2F raised floor"},
|
||||||
|
{"id": "2f-det", "type": "smoke_detectors", "x": 1.8, "y": 0, "z": 10.0, "props": {"w": 37.8, "d": 25.2, "spacing": 6.0, "h": 4.55, "zone": "2F hall"}, "name": "Detectors 2F hall"},
|
||||||
|
{"id": "2f-det-g", "type": "smoke_detectors", "x": -19.05, "y": -4.9, "z": 10.0, "props": {"w": 3.1, "d": 15.4, "spacing": 6.0, "h": 4.4, "coverage": false, "zone": "2F gas room"}, "name": "Detectors 2F gas room"},
|
||||||
|
{"id": "2f-vesda-1", "type": "fire_pipe", "x": 1.75, "y": -9, "z": 10.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 1"},
|
||||||
|
{"id": "2f-vesda-2", "type": "fire_pipe", "x": 1.75, "y": -3, "z": 10.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 2"},
|
||||||
|
{"id": "2f-vesda-3", "type": "fire_pipe", "x": 1.75, "y": 3, "z": 10.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 3"},
|
||||||
|
{"id": "2f-vesda-4", "type": "fire_pipe", "x": 1.75, "y": 9, "z": 10.0, "props": {"w": 37.5, "kind": "VESDA", "spacing": 4.0, "h": 4.4}, "name": "VESDA pipe 4"},
|
||||||
|
{"id": "2f-tray", "type": "cable_tray", "x": 2, "y": 0, "z": 10.0, "props": {"w": 36, "kind": "ladder", "width": 0.6, "h": 4.0, "fill": 60}, "name": "Main power tray"},
|
||||||
|
{"id": "2f-rel", "type": "fire_device", "x": -17.03, "y": 5.4, "z": 10.0, "rot": 90, "props": {"kind": "release panel", "label": "2F hall"}, "name": "Release panel"},
|
||||||
|
{"id": "2f-abort", "type": "fire_device", "x": -17.03, "y": 6.4, "z": 10.0, "rot": 90, "props": {"kind": "abort switch"}, "name": "Abort switch"},
|
||||||
|
{"id": "2f-cp", "type": "fire_device", "x": -17.03, "y": 10.2, "z": 10.0, "rot": 90, "props": {"kind": "call point", "label": "2F hall"}, "name": "Call point"},
|
||||||
|
{"id": "2f-bc-1", "type": "fire_device", "x": -17.03, "y": -6, "z": 10.0, "rot": 90, "props": {"kind": "beacon"}, "name": "Sounder-beacon"},
|
||||||
|
{"id": "2f-bc-2", "type": "fire_device", "x": 20.53, "y": 5, "z": 10.0, "rot": 270, "props": {"kind": "beacon"}, "name": "Sounder-beacon"},
|
||||||
|
{"id": "2f-ext", "type": "fire_device", "x": -16.8, "y": 1.2, "z": 10.0, "rot": 90, "props": {"kind": "extinguisher"}, "name": "Extinguisher"},
|
||||||
|
{"id": "2f-stairs", "type": "stairs", "x": -19.4, "y": 7.9, "z": 10.0, "props": {"w": 1.4, "run": 6.4, "rise": 4.7, "mat": "concb"}, "name": "Stairs 2F"},
|
||||||
|
{"id": "2f-gpu-11", "type": "dc_pod", "x": -11.5, "y": 4.2, "z": 10.0, "rot": 90, "props": {"n": 8, "kind": "GPU", "kw": 30, "fill": 85, "u": 48, "contain": "hot", "inrow": 2, "inrow_kw": 90, "duct": true, "duct_h": 4.2, "doors": "glass"}, "name": "GPU pod 11", "tag": "GPU 11"},
|
||||||
|
{"id": "2f-gpu-12", "type": "dc_pod", "x": -11.5, "y": -4.2, "z": 10.0, "rot": 90, "props": {"n": 8, "kind": "GPU", "kw": 30, "fill": 85, "u": 48, "contain": "hot", "inrow": 2, "inrow_kw": 90, "duct": true, "duct_h": 4.2, "doors": "glass"}, "name": "GPU pod 12", "tag": "GPU 12"},
|
||||||
|
{"id": "2f-bus-1L", "type": "busway", "x": -12.7, "y": 0, "z": 10.0, "rot": 90, "props": {"w": 14, "h": 3.2, "amps": 1250}, "name": "Busway"},
|
||||||
|
{"id": "2f-bus-1R", "type": "busway", "x": -10.3, "y": 0, "z": 10.0, "rot": 90, "props": {"w": 14, "h": 3.2, "amps": 1250}, "name": "Busway"},
|
||||||
|
{"id": "2f-gpu-21", "type": "dc_pod", "x": -5.5, "y": 4.2, "z": 10.0, "rot": 90, "props": {"n": 8, "kind": "GPU", "kw": 30, "fill": 85, "u": 48, "contain": "hot", "inrow": 2, "inrow_kw": 90, "duct": false, "duct_h": 4.2, "doors": "glass"}, "name": "GPU pod 21", "tag": "GPU 21"},
|
||||||
|
{"id": "2f-gpu-22", "type": "dc_pod", "x": -5.5, "y": -4.2, "z": 10.0, "rot": 90, "props": {"n": 8, "kind": "GPU", "kw": 30, "fill": 85, "u": 48, "contain": "hot", "inrow": 2, "inrow_kw": 90, "duct": false, "duct_h": 4.2, "doors": "glass"}, "name": "GPU pod 22", "tag": "GPU 22"},
|
||||||
|
{"id": "2f-bus-2L", "type": "busway", "x": -6.7, "y": 0, "z": 10.0, "rot": 90, "props": {"w": 14, "h": 3.2, "amps": 1250}, "name": "Busway"},
|
||||||
|
{"id": "2f-bus-2R", "type": "busway", "x": -4.3, "y": 0, "z": 10.0, "rot": 90, "props": {"w": 14, "h": 3.2, "amps": 1250}, "name": "Busway"},
|
||||||
|
{"id": "2f-cage-1", "type": "dc_cage", "x": 4.6, "y": 4.5, "z": 10.0, "props": {"w": 7.2, "d": 5.7}, "name": "Cage 1", "tag": "Cage 1"},
|
||||||
|
{"id": "2f-colo-1", "type": "dc_pod", "x": 4.6, "y": 4.5, "z": 10.0, "props": {"n": 8, "kind": "servers", "kw": 8, "fill": 55, "contain": "none", "doors": "solid"}, "name": "Cage 1 racks"},
|
||||||
|
{"id": "2f-fibre-1", "type": "cable_tray", "x": 4.6, "y": 4.5, "z": 10.0, "props": {"w": 7.2, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 30}, "name": "Fibre duct"},
|
||||||
|
{"id": "2f-cage-2", "type": "dc_cage", "x": 4.6, "y": -4.5, "z": 10.0, "props": {"w": 7.2, "d": 5.7}, "name": "Cage 2", "tag": "Cage 2"},
|
||||||
|
{"id": "2f-colo-2", "type": "dc_pod", "x": 4.6, "y": -4.5, "z": 10.0, "props": {"n": 8, "kind": "network", "kw": 8, "fill": 65, "contain": "none", "doors": "mesh"}, "name": "Cage 2 racks"},
|
||||||
|
{"id": "2f-fibre-2", "type": "cable_tray", "x": 4.6, "y": -4.5, "z": 10.0, "props": {"w": 7.2, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 30}, "name": "Fibre duct"},
|
||||||
|
{"id": "2f-cage-3", "type": "dc_cage", "x": 13.6, "y": 4.5, "z": 10.0, "props": {"w": 7.2, "d": 5.7}, "name": "Cage 3", "tag": "Cage 3"},
|
||||||
|
{"id": "2f-colo-3", "type": "dc_pod", "x": 13.6, "y": 4.5, "z": 10.0, "props": {"n": 8, "kind": "storage", "kw": 8, "fill": 75, "contain": "none", "doors": "solid"}, "name": "Cage 3 racks"},
|
||||||
|
{"id": "2f-fibre-3", "type": "cable_tray", "x": 13.6, "y": 4.5, "z": 10.0, "props": {"w": 7.2, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 30}, "name": "Fibre duct"},
|
||||||
|
{"id": "2f-cage-4", "type": "dc_cage", "x": 13.6, "y": -4.5, "z": 10.0, "props": {"w": 7.2, "d": 5.7}, "name": "Cage 4", "tag": "Cage 4"},
|
||||||
|
{"id": "2f-colo-4", "type": "dc_pod", "x": 13.6, "y": -4.5, "z": 10.0, "props": {"n": 8, "kind": "servers", "kw": 8, "fill": 85, "contain": "none", "doors": "mesh"}, "name": "Cage 4 racks"},
|
||||||
|
{"id": "2f-fibre-4", "type": "cable_tray", "x": 13.6, "y": -4.5, "z": 10.0, "props": {"w": 7.2, "kind": "fibre", "width": 0.3, "h": 3.65, "fill": 30}, "name": "Fibre duct"},
|
||||||
|
{"id": "2f-crah-n1", "type": "crah", "x": 2, "y": 11.95, "z": 10.0, "props": {"kw": 150, "standby": false}, "name": "CRAH 2-N1"},
|
||||||
|
{"id": "2f-crah-s1", "type": "crah", "x": 2, "y": -11.95, "z": 10.0, "rot": 180, "props": {"kw": 150}, "name": "CRAH 2-S1"},
|
||||||
|
{"id": "2f-crah-n2", "type": "crah", "x": 9, "y": 11.95, "z": 10.0, "props": {"kw": 150, "standby": false}, "name": "CRAH 2-N2"},
|
||||||
|
{"id": "2f-crah-s2", "type": "crah", "x": 9, "y": -11.95, "z": 10.0, "rot": 180, "props": {"kw": 150}, "name": "CRAH 2-S2"},
|
||||||
|
{"id": "2f-crah-n3", "type": "crah", "x": 16, "y": 11.95, "z": 10.0, "props": {"kw": 150, "standby": true}, "name": "CRAH 2-N3"},
|
||||||
|
{"id": "2f-crah-s3", "type": "crah", "x": 16, "y": -11.95, "z": 10.0, "rot": 180, "props": {"kw": 150}, "name": "CRAH 2-S3"},
|
||||||
|
{"id": "2f-spr-1", "type": "fire_pipe", "x": 9.5, "y": -8.5, "z": 10.0, "props": {"w": 21, "kind": "sprinkler", "spacing": 3.0, "h": 4.25, "unit": false}, "name": "Pre-action sprinkler pipe"},
|
||||||
|
{"id": "2f-spr-2", "type": "fire_pipe", "x": 9.5, "y": 8.5, "z": 10.0, "props": {"w": 21, "kind": "sprinkler", "spacing": 3.0, "h": 4.25, "unit": false}, "name": "Pre-action sprinkler pipe"},
|
||||||
|
{"id": "2f-gas", "type": "gas_bank", "x": -20.45, "y": -5.4, "z": 10.0, "rot": 90, "props": {"agent": "FM-200", "n": 18, "litres": 140}, "name": "FM-200 bank (hall B)", "tag": "FM-200"},
|
||||||
|
{"id": "2f-tech", "type": "person", "x": -2.0, "y": 1.0, "z": 10.0, "props": {"cls": "guest2"}, "name": "Engineer"},
|
||||||
|
{"id": "chiller-1", "type": "chiller", "x": -12, "y": -7, "z": 15.0, "props": {"w": 9, "kw": 1000}, "name": "Chiller 1", "tag": "CH1"},
|
||||||
|
{"id": "chiller-2", "type": "chiller", "x": -1, "y": -7, "z": 15.0, "props": {"w": 9, "kw": 1000}, "name": "Chiller 2"},
|
||||||
|
{"id": "chiller-3", "type": "chiller", "x": 10, "y": -7, "z": 15.0, "props": {"w": 9, "kw": 1000}, "name": "Chiller 3"},
|
||||||
|
{"id": "chiller-4", "type": "chiller", "x": -6.5, "y": 6, "z": 15.0, "props": {"w": 9, "kw": 1000}, "name": "Chiller 4"},
|
||||||
|
{"id": "chiller-5", "type": "chiller", "x": 4.5, "y": 6, "z": 15.0, "props": {"w": 9, "kw": 1000}, "name": "Chiller 5"},
|
||||||
|
{"id": "dry", "type": "chiller", "x": 15.5, "y": 6, "z": 15.0, "props": {"w": 8, "kw": 600, "kind": "adiabatic"}, "name": "Adiabatic cooler (GPU loop)"},
|
||||||
|
{"id": "roof-pipes", "type": "pipe_run", "x": 0, "y": -0.5, "z": 15.0, "props": {"w": 38, "dn": 0.35, "h": 0.9}, "name": "Chilled water headers"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -151,6 +151,7 @@ def main():
|
|||||||
(web / "factory.json").write_text(json.dumps(demo_files("factory"), ensure_ascii=False), encoding="utf-8")
|
(web / "factory.json").write_text(json.dumps(demo_files("factory"), ensure_ascii=False), encoding="utf-8")
|
||||||
(web / "streets.json").write_text(json.dumps(demo_files("streets"), ensure_ascii=False), encoding="utf-8")
|
(web / "streets.json").write_text(json.dumps(demo_files("streets"), ensure_ascii=False), encoding="utf-8")
|
||||||
(web / "country.json").write_text(json.dumps(demo_files("country"), ensure_ascii=False), encoding="utf-8")
|
(web / "country.json").write_text(json.dumps(demo_files("country"), ensure_ascii=False), encoding="utf-8")
|
||||||
|
(web / "datacentre.json").write_text(json.dumps(demo_files("datacentre"), ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
h = hashlib.sha256()
|
h = hashlib.sha256()
|
||||||
for p in sorted((out / "assets").rglob("*")):
|
for p in sorted((out / "assets").rglob("*")):
|
||||||
|
|||||||
@@ -404,6 +404,34 @@ try:
|
|||||||
check(len(g) == 1 and members == ["bush-1", "bush-2"] and hid == 0 and not any(o["type"] == "group" for o in doc(pg)["objects"]),
|
check(len(g) == 1 and members == ["bush-1", "bush-2"] and hid == 0 and not any(o["type"] == "group" for o in doc(pg)["objects"]),
|
||||||
f"web Studio groups: {members} in {g[0]['id'] if g else None}, hidden with it, undone")
|
f"web Studio groups: {members} in {g[0]['id'] if g else None}, hidden with it, undone")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------- the data centre demo: storeys, racks, cooling, fire safety
|
||||||
|
pg.locator("#wb-projects").click()
|
||||||
|
pg.locator("#wb-ndc").click()
|
||||||
|
pg.wait_for_load_state()
|
||||||
|
ready(pg)
|
||||||
|
pg.wait_for_timeout(800)
|
||||||
|
nd = len(doc(pg)["objects"])
|
||||||
|
ids_in = lambda sel: pg.evaluate(f"() => [...new Set([...document.querySelectorAll(`{sel}`)].map(e => e.dataset.obj))]")
|
||||||
|
pods = [i for i in ids_in(".iso-svg [data-obj]") if i.startswith("1f-pod")]
|
||||||
|
overhead = ids_in(".iso-svg [data-layer='ceiling'] [data-obj]")
|
||||||
|
# it opens on 1F: 8 pods; trays, busways, VESDA and gas pipes, detectors sort in the ceiling layer
|
||||||
|
check(title(pg) == "isokit data centre" and nd == 179 and len(pods) == 8 and len(overhead) == 21
|
||||||
|
and pg.evaluate("() => __studio.S.warnings.length") == 0,
|
||||||
|
f"Data centre demo opens on 1F ({nd} objects, {len(pods)} pods, {len(overhead)} overhead runs, no warnings)")
|
||||||
|
irow = lambda id_, k: pg.evaluate(f"() => Object.fromEntries((__studio.view.info('{id_}') || {{rows: []}}).rows)['{k}'] || ''")
|
||||||
|
red, gas = irow("1f-crah-n1", "Redundancy"), irow("1f-gas", "Status")
|
||||||
|
check(red == "N+2, 348 kW spare" and gas == "enough, 3 % spare", f"1F cooling {red}; Novec bank {gas}")
|
||||||
|
shot(pg, "web-datacentre.jpg")
|
||||||
|
# GF cut open: the power room shows, the halls above are left out
|
||||||
|
pg.evaluate("__studio.view.setLevel('gf')")
|
||||||
|
pg.wait_for_timeout(1200)
|
||||||
|
gf = ids_in(".iso-svg [data-obj]")
|
||||||
|
run = irow("batt-1", "Runtime")
|
||||||
|
check("ups" in gf and not any(i.startswith("1f-") for i in gf) and run == "about 21.7 min at 3,584 kW IT",
|
||||||
|
f"GF shows the power room and none of 1F; batteries {run}")
|
||||||
|
pg.evaluate("__studio.view.setLevel('1f')")
|
||||||
|
pg.wait_for_timeout(600)
|
||||||
|
|
||||||
# ---------------------------------------------------------------- a system asking for less motion
|
# ---------------------------------------------------------------- a system asking for less motion
|
||||||
# (Windows: Animation effects off): the view starts still, but the button stays and turns it on
|
# (Windows: Animation effects off): the view starts still, but the button stays and turns it on
|
||||||
rctx = b.new_context(viewport={"width": 1200, "height": 800}, reduced_motion="reduce")
|
rctx = b.new_context(viewport={"width": 1200, "height": 800}, reduced_motion="reduce")
|
||||||
|
|||||||
+2
-1
@@ -367,7 +367,7 @@ function injectUI() {
|
|||||||
'<h3>Projects</h3>' +
|
'<h3>Projects</h3>' +
|
||||||
'<div class="wb-cur" id="wb-cur"></div>' +
|
'<div class="wb-cur" id="wb-cur"></div>' +
|
||||||
'<ul class="wb-plist" id="wb-plist"></ul>' +
|
'<ul class="wb-plist" id="wb-plist"></ul>' +
|
||||||
'<div class="wb-new"><input id="wb-ntitle" placeholder="Title of a new project" autocomplete="off"><button type="button" id="wb-ncreate">New project</button><button type="button" id="wb-ndemo" title="A fresh copy of the demo scene">Demo copy</button><button type="button" id="wb-nhouse" title="A house built from parts: walls, windows, doors, floors, furniture">House demo</button><button type="button" id="wb-nfactory" title="Presses making goods at a set rate, conveyors carrying them">Factory demo</button><button type="button" id="wb-nstreets" title="Streets with sidewalks, a rounded corner, junctions and a roundabout">Streets demo</button><button type="button" id="wb-ncountry" title="Elephants, deer, boar, beehives and a wildflower meadow; a lane, fence and hedgerow at an angle">Country demo</button></div>' +
|
'<div class="wb-new"><input id="wb-ntitle" placeholder="Title of a new project" autocomplete="off"><button type="button" id="wb-ncreate">New project</button><button type="button" id="wb-ndemo" title="A fresh copy of the demo scene">Demo copy</button><button type="button" id="wb-nhouse" title="A house built from parts: walls, windows, doors, floors, furniture">House demo</button><button type="button" id="wb-nfactory" title="Presses making goods at a set rate, conveyors carrying them">Factory demo</button><button type="button" id="wb-nstreets" title="Streets with sidewalks, a rounded corner, junctions and a roundabout">Streets demo</button><button type="button" id="wb-ncountry" title="Elephants, deer, boar, beehives and a wildflower meadow; a lane, fence and hedgerow at an angle">Country demo</button><button type="button" id="wb-ndc" title="A three-storey data centre: power room, a hall with cold aisle containment, GPU pods with hot aisle containment, cages, cooling, fire detection and suppression">Data centre demo</button></div>' +
|
||||||
'<div class="wb-drop" id="wb-drop">Import: drop a project folder or .zip here, or <button type="button" id="wb-izip" title="Import a project .zip into this browser">.zip…</button><button type="button" id="wb-idir" title="Copy a project folder into this browser">folder…</button>' +
|
'<div class="wb-drop" id="wb-drop">Import: drop a project folder or .zip here, or <button type="button" id="wb-izip" title="Import a project .zip into this browser">.zip…</button><button type="button" id="wb-idir" title="Copy a project folder into this browser">folder…</button>' +
|
||||||
(canOpenFolder ? '<span class="st-grow"></span><button type="button" id="wb-ofolder" title="Edit the files in the folder on disk directly; changes made there show up live">Open folder live…</button>' : "") + "</div>" +
|
(canOpenFolder ? '<span class="st-grow"></span><button type="button" id="wb-ofolder" title="Edit the files in the folder on disk directly; changes made there show up live">Open folder live…</button>' : "") + "</div>" +
|
||||||
'<p class="st-help">Projects are kept in this browser. ' + (canOpenFolder ? "A folder opened live is edited on disk, so agents (<b>isokit mcp</b>), editors and git see the same files." : "To edit a folder on disk directly, use Chrome or Edge.") + "</p>" +
|
'<p class="st-help">Projects are kept in this browser. ' + (canOpenFolder ? "A folder opened live is edited on disk, so agents (<b>isokit mcp</b>), editors and git see the same files." : "To edit a folder on disk directly, use Chrome or Edge.") + "</p>" +
|
||||||
@@ -421,6 +421,7 @@ function wireProjects() {
|
|||||||
$("wb-nfactory").addEventListener("click", async function () { openProject((await createProject(await demoFiles("factory.json"))).id); });
|
$("wb-nfactory").addEventListener("click", async function () { openProject((await createProject(await demoFiles("factory.json"))).id); });
|
||||||
$("wb-nstreets").addEventListener("click", async function () { openProject((await createProject(await demoFiles("streets.json"))).id); });
|
$("wb-nstreets").addEventListener("click", async function () { openProject((await createProject(await demoFiles("streets.json"))).id); });
|
||||||
$("wb-ncountry").addEventListener("click", async function () { openProject((await createProject(await demoFiles("country.json"))).id); });
|
$("wb-ncountry").addEventListener("click", async function () { openProject((await createProject(await demoFiles("country.json"))).id); });
|
||||||
|
$("wb-ndc").addEventListener("click", async function () { openProject((await createProject(await demoFiles("datacentre.json"))).id); });
|
||||||
$("wb-izip").addEventListener("click", function () { $("wb-fzip").click(); });
|
$("wb-izip").addEventListener("click", function () { $("wb-fzip").click(); });
|
||||||
$("wb-idir").addEventListener("click", function () { $("wb-fdir").click(); });
|
$("wb-idir").addEventListener("click", function () { $("wb-fdir").click(); });
|
||||||
$("wb-fzip").addEventListener("change", function () { var f = this.files[0]; if (f) importZip(f); this.value = ""; });
|
$("wb-fzip").addEventListener("change", function () { var f = this.files[0]; if (f) importZip(f); this.value = ""; });
|
||||||
|
|||||||
Reference in new issue
Block a user