Conveyors and producers: goods flow from presses down the belts
Deploy web Studio / deploy (push) Successful in 1m4s
Deploy web Studio / deploy (push) Successful in 1m4s
- Producer (category Industry): a press making cartons, crates, drums, sacks or parts at a set rate per minute; its ram goes down once per item, a green / red light shows running / stopped. - Conveyor: drawn the way it runs (new prefab meta `directed`: the Studio turns the piece from where the drag starts), belt width, height, rise (inclines), speed, goods and spacing; legs, rails and a drive motor. Corners join without overlap (the receiving belt runs through, the feeding one stops at its side). - A conveyor starting on a producer's output side carries its goods at its rate; one starting at another's end carries on with what that one carries. The gap is speed / rate. A stopped press or belt stops the goods after it. Tooltips show speed, rate and source. - The goods ride a group moving one gap per loop (no reordering), first fading in, last out. - Script API: world_of(o, u, v), info_row(key, value); objects_on falls back to half of p.d. - Theme: industry colours, LED colours (the marker's LED had none). - examples/factory + Factory demo in the web Studio; MCP instructions on production lines. Checks: 68 Rust tests (conveyor joints, feed and spacing, every view of the factory demo), Studio 108/108 (draw direction, feed from a producer, rate change), web 89/89.
This commit is contained in:
1 parent
a202283a60
commit
18dd324543
20 files changed
+617
-18
No files matched your search
@@ -23,7 +23,11 @@ then windows and doors with x, y on a wall line (they sit on that wall and cut t
|
||||
the wall's +y side, rot + 180 swings it the other way), a roof (w x d = outer size of the walls) and furniture \
|
||||
(fronts face local -y, backs go against a wall). Level gf shows the house cut open; a part's storey is its z / 3 m. \
|
||||
Fences (props.style: mesh, wood, bars, half stone, cobble, brick) and site walls join where their centre-line ends \
|
||||
meet, so for a plot boundary put each piece's ends exactly on the next piece's end or line.";
|
||||
meet, so for a plot boundary put each piece's ends exactly on the next piece's end or line. \
|
||||
Production lines (category Industry): a producer (props.rate per minute, product) puts goods out on its local +x \
|
||||
side; a conveyor (rot 0 runs towards +x, 90 +y, 180 -x, 270 -y; w = length, props.speed m/s) starting exactly \
|
||||
at that side carries them, and a conveyor whose start is at another's end carries on with them (corners join). \
|
||||
The gap between goods is speed / rate, so set the rate on the producer and the speeds on the belts.";
|
||||
|
||||
fn tool(name: &str, description: &str, props: Value, required: &[&str]) -> Value {
|
||||
json!({ "name": name, "description": description,
|
||||
|
||||
@@ -72,12 +72,25 @@ fn demo_passes_the_check() {
|
||||
assert!(f.is_empty(), "{f:?}");
|
||||
}
|
||||
|
||||
/// The house demo: every view draws every object without warnings or depth-sort cycles
|
||||
/// (walls must not overlap), and the project passes the check.
|
||||
/// The house and factory demos: every view draws every object without warnings or
|
||||
/// depth-sort cycles (walls and belts must not overlap), and the projects pass the check.
|
||||
#[test]
|
||||
fn house_demo_renders_every_view_cleanly() {
|
||||
let p =
|
||||
Project::load(Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/house")).unwrap();
|
||||
example_renders_every_view_cleanly("house");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factory_demo_renders_every_view_cleanly() {
|
||||
example_renders_every_view_cleanly("factory");
|
||||
}
|
||||
|
||||
fn example_renders_every_view_cleanly(name: &str) {
|
||||
let p = Project::load(
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../examples")
|
||||
.join(name),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(p.runtime.is_ok(), "{:?}", p.runtime.as_ref().err());
|
||||
for l in p.manifest.levels() {
|
||||
for rot in 0..4 {
|
||||
|
||||
@@ -652,6 +652,16 @@ pub(crate) fn register_scene(e: &mut Engine, st: &Shared) {
|
||||
tag(&s, x, y, z, t, n(&p)?, Some(id), k)
|
||||
},
|
||||
);
|
||||
// a row more in the tooltip of the object being drawn
|
||||
let s = st.clone();
|
||||
e.register_fn("info_row", move |k: D, v: D| {
|
||||
let mut b = s.borrow_mut();
|
||||
if let Some(id) = b.obj.clone() {
|
||||
if let Some(i) = b.scene.info.get_mut(&id) {
|
||||
i.rows.push((k.to_string(), v.to_string()));
|
||||
}
|
||||
}
|
||||
});
|
||||
let s = st.clone();
|
||||
e.register_fn("info", move |id: &str, name: &str| {
|
||||
s.borrow_mut().scene.info(id, name, "")
|
||||
@@ -933,7 +943,8 @@ fn register_objects(e: &mut Engine, st: &Shared) {
|
||||
let m = d.read_lock::<Map>()?;
|
||||
let l = xf_of(&m).inv(x, y);
|
||||
let half = prop(&m, "w").unwrap_or(1.0) / 2.0;
|
||||
let lim = prop(&m, "t").unwrap_or(0.0) / 2.0 + tol;
|
||||
// half the thickness (walls), else half the width (conveyors)
|
||||
let lim = prop(&m, "t").or_else(|| prop(&m, "d")).unwrap_or(0.0) / 2.0 + tol;
|
||||
(l[0].abs() <= half + 1e-6 && l[1].abs() <= lim + 1e-6)
|
||||
.then(|| with_uv(d, l[0], l[1]))
|
||||
})
|
||||
@@ -1008,4 +1019,9 @@ fn register_objects(e: &mut Engine, st: &Shared) {
|
||||
let p = xf_of(&o).inv(n(&x)?, n(&y)?);
|
||||
Ok(vec![Dynamic::from_float(p[0]), Dynamic::from_float(p[1])])
|
||||
});
|
||||
// the world plan point of (u, v) in object o's frame
|
||||
e.register_fn("world_of", |o: Map, u: D, v: D| -> RR<Array> {
|
||||
let p = xf_of(&o).p(n(&u)?, n(&v)?);
|
||||
Ok(vec![Dynamic::from_float(p[0]), Dynamic::from_float(p[1])])
|
||||
});
|
||||
}
|
||||
@@ -119,6 +119,10 @@ pub struct PrefabMeta {
|
||||
/// it at z = 1 x storey height (parameter `storey_h`, default 3 m).
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub storey: bool,
|
||||
/// A line whose way matters (conveyors run along local +x): the Studio
|
||||
/// turns it to point from where the drag starts to where it ends.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub directed: bool,
|
||||
#[serde(default)]
|
||||
pub props: Vec<PropDef>,
|
||||
/// Module the prefab comes from ("isokit/site", "lib/kiosk.rhai").
|
||||
|
||||
@@ -15,4 +15,5 @@ pub const MODULES: &[(&str, &str)] = &[
|
||||
("isokit/equipment", include_str!("../stdlib/equipment.rhai")),
|
||||
("isokit/house", include_str!("../stdlib/house.rhai")),
|
||||
("isokit/furniture", include_str!("../stdlib/furniture.rhai")),
|
||||
("isokit/industry", include_str!("../stdlib/industry.rhai")),
|
||||
];
|
||||
@@ -542,6 +542,107 @@ fn ponds_have_fish_swimming_in_every_shape() {
|
||||
.contains("iso-swim"));
|
||||
}
|
||||
|
||||
fn factory_doc() -> Doc {
|
||||
let mut doc = Doc::default();
|
||||
for (id, kind, x, y, rot, props) in [
|
||||
("press1", "producer", -6.0, -4.0, 0.0, json!({"rate": 20})),
|
||||
("belt1", "conveyor", -1.4, -4.0, 0.0, json!({"w": 6})),
|
||||
("belt2", "conveyor", 1.6, -1.0, 90.0, json!({"w": 6})),
|
||||
(
|
||||
"belt3",
|
||||
"conveyor",
|
||||
1.6,
|
||||
5.0,
|
||||
90.0,
|
||||
json!({"w": 6, "speed": 0.8, "rise": 1.6}),
|
||||
),
|
||||
(
|
||||
"belt5",
|
||||
"conveyor",
|
||||
8.0,
|
||||
6.0,
|
||||
0.0,
|
||||
json!({"w": 8, "item": "crate", "gap": 2}),
|
||||
),
|
||||
(
|
||||
"press3",
|
||||
"producer",
|
||||
-6.0,
|
||||
8.0,
|
||||
0.0,
|
||||
json!({"rate": 6, "product": "part", "running": false}),
|
||||
),
|
||||
("belt6", "conveyor", -2.0, 8.0, 0.0, json!({"w": 5})),
|
||||
// a closed ring: following the feed back ends after a few belts
|
||||
("r1", "conveyor", 30.0, -3.0, 0.0, json!({"w": 6})),
|
||||
("r2", "conveyor", 33.0, 0.0, 90.0, json!({"w": 6})),
|
||||
("r3", "conveyor", 30.0, 3.0, 180.0, json!({"w": 6})),
|
||||
("r4", "conveyor", 27.0, 0.0, 270.0, json!({"w": 6})),
|
||||
] {
|
||||
let mut o = obj(id, kind, x, y, props);
|
||||
o.rot = rot;
|
||||
doc.objects.push(o);
|
||||
}
|
||||
doc
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conveyors_carry_what_the_producer_makes() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
assert!(r.prefab("conveyor").unwrap().directed);
|
||||
let doc = factory_doc();
|
||||
for view in 0..4u8 {
|
||||
let b = r.build("site", view, &cfg(), &doc).unwrap();
|
||||
assert!(b.scene.warnings.is_empty(), "{:?}", b.scene.warnings);
|
||||
let out = render(&b.scene, view, &RenderOpts::default());
|
||||
assert_eq!(out.cycles, 0, "view {view}: depth-sort cycles");
|
||||
assert!(!out.html.contains("NaN"));
|
||||
// goods move on the running belts (not behind the stopped press), the running press's ram goes
|
||||
assert_eq!(
|
||||
out.html.matches("iso-a iso-ride\"").count(),
|
||||
8,
|
||||
"view {view}"
|
||||
);
|
||||
assert_eq!(out.html.matches("iso-a iso-ram").count(), 1, "view {view}");
|
||||
}
|
||||
let b = r.build("site", 1, &cfg(), &doc).unwrap();
|
||||
let near = |a: f64, e: f64| (a - e).abs() < 1e-6;
|
||||
// the first belt starts flush at the press and stops at the side of the belt it feeds,
|
||||
// which runs through the corner; in line the belts just meet
|
||||
let (b1, b2, b3) = (b.boxes["belt1"], b.boxes["belt2"], b.boxes["belt3"]);
|
||||
assert!(near(b1[0], -4.4) && near(b1[3], 1.2), "belt1 {b1:?}");
|
||||
assert!(near(b2[1], -4.4) && near(b2[4], 2.0), "belt2 {b2:?}");
|
||||
assert!(near(b3[1], 2.0), "belt3 {b3:?}");
|
||||
// goods and their spacing come down the line: 20 a minute at 0.8 m/s is 2.4 m apart
|
||||
let out = render(&b.scene, 1, &RenderOpts::default());
|
||||
let row = |id: &str, k: &str| {
|
||||
out.info[id]
|
||||
.rows
|
||||
.iter()
|
||||
.find(|r| r.0 == k)
|
||||
.map(|r| r.1.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
assert_eq!(
|
||||
row("belt1", "Carries"),
|
||||
"20 cartons a minute, 1.5 m apart (from press1)"
|
||||
);
|
||||
assert_eq!(
|
||||
row("belt3", "Carries"),
|
||||
"20 cartons a minute, 2.4 m apart (from belt2)"
|
||||
);
|
||||
assert_eq!(row("belt5", "Carries"), "15 crates a minute, 2 m apart");
|
||||
assert_eq!(
|
||||
row("belt6", "Carries"),
|
||||
"6 parts a minute, 5 m apart (from press3)"
|
||||
);
|
||||
assert_eq!(
|
||||
row("press3", "Makes"),
|
||||
"6 parts a minute, one every 10 s (stopped)"
|
||||
);
|
||||
assert_eq!(row("belt3", "Speed"), "0.8 m/s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn site_walls_join_at_corners() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
// isokit/industry: conveyors and producers (machines that make goods at a
|
||||
// set rate). Goods flow from a producer onto the conveyor starting at its
|
||||
// output and on along the conveyors that start where another one ends: each
|
||||
// belt carries them at its own speed, so the gap between them is speed / rate.
|
||||
|
||||
fn goods_kinds() { ["carton", "crate", "drum", "sack", "part"] }
|
||||
|
||||
/// x for a tooltip: whole numbers as they are, others to one decimal.
|
||||
fn num1(x) {
|
||||
let r = round1(x);
|
||||
if (r - r.round()).abs() < 1e-9 { `${r.round().to_int()}` } else { `${r}` }
|
||||
}
|
||||
|
||||
/// Half the length of one of the goods along the belt (at full size).
|
||||
fn goods_half(kind) {
|
||||
if kind == "crate" || kind == "sack" { 0.3 } else if kind == "part" { 0.22 } else { 0.25 }
|
||||
}
|
||||
|
||||
/// Height of one of the goods (at full size).
|
||||
fn goods_h(kind) {
|
||||
if kind == "crate" { 0.36 } else if kind == "drum" { 0.6 } else if kind == "sack" { 0.16 } else if kind == "part" { 0.26 } else { 0.32 }
|
||||
}
|
||||
|
||||
/// One of the goods standing at (x, y, z), its size scaled by k.
|
||||
fn goods(kind, x, y, z, k) {
|
||||
if kind == "crate" {
|
||||
this.box(x - 0.3 * k, y - 0.25 * k, x + 0.3 * k, y + 0.25 * k, z, z + 0.36 * k, "crate");
|
||||
} else if kind == "drum" {
|
||||
this.cyl(x, y, 0.24 * k, z, z + 0.6 * k, "drum");
|
||||
} else if kind == "sack" {
|
||||
this.box(x - 0.3 * k, y - 0.2 * k, x + 0.3 * k, y + 0.2 * k, z, z + 0.16 * k, "sacks");
|
||||
} else if kind == "part" {
|
||||
this.box(x - 0.22 * k, y - 0.22 * k, x + 0.22 * k, y + 0.22 * k, z, z + 0.1 * k, "steel");
|
||||
this.cyl(x, y, 0.1 * k, z + 0.1 * k, z + 0.26 * k, "steel");
|
||||
} else {
|
||||
this.box(x - 0.25 * k, y - 0.2 * k, x + 0.25 * k, y + 0.2 * k, z, z + 0.32 * k, "carton");
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
/// Height of conveyor p's belt top at u along it (it rises by p.rise from
|
||||
/// its start to its end).
|
||||
fn belt_z(p, u) { p.h + p.rise * (u + p.w / 2.0) / p.w }
|
||||
|
||||
/// How far conveyor o reaches past its end e (-1 start, 1 end). Alone or on
|
||||
/// a straight run it ends there. Where another conveyor crosses the end
|
||||
/// point, the corner square goes to the one that carries on: a belt feeding
|
||||
/// another one's start stops at that belt's side, the belt taking goods
|
||||
/// from another's end runs through; at a T it stops at the side.
|
||||
fn conveyor_ext(o, e) {
|
||||
let p = o.p;
|
||||
let u = e * p.w / 2.0;
|
||||
let pt = world_of(o, u, 0.0);
|
||||
let zt = o.z + belt_z(p, u);
|
||||
for c in objects_on(["conveyor"], pt[0], pt[1], 0.05) {
|
||||
if c.id == o.id || along_world_x(c.rot) == along_world_x(o.rot) { continue; }
|
||||
if (c.z + belt_z(c.p, c.u) - zt).abs() > 0.3 { continue; }
|
||||
let at_start = (c.u + c.p.w / 2.0).abs() < 0.05;
|
||||
let at_end = (c.u - c.p.w / 2.0).abs() < 0.05;
|
||||
let half = c.p.d / 2.0;
|
||||
if !at_start && !at_end { return -half; }
|
||||
if e > 0.0 && at_start { return -half; }
|
||||
if e < 0.0 && at_end { return half; }
|
||||
// two ends or two starts meet: the one along world x runs through
|
||||
return if along_world_x(o.rot) { half } else { -half };
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
/// What reaches conveyor o at its start: #{ rate, item, running, from }
|
||||
/// (rate in goods per second) from the producer whose output side it starts
|
||||
/// at, or else from the conveyor ending there (followed back up to 12 belts);
|
||||
/// () when nothing feeds it.
|
||||
fn conveyor_feed(o, hops) {
|
||||
if hops > 12 { return (); }
|
||||
let p = o.p;
|
||||
let a = world_of(o, -p.w / 2.0 - 0.15, 0.0);
|
||||
for m in objects_at(["producer"], a[0], a[1]) {
|
||||
if (m.z - o.z).abs() > 0.5 { continue; }
|
||||
if local_of(m, a[0], a[1])[0] > m.p.w / 2.0 - 1.0 {
|
||||
return #{ rate: m.p.rate / 60.0, item: m.p.product, running: m.p.running, from: m.id };
|
||||
}
|
||||
}
|
||||
let s = world_of(o, -p.w / 2.0, 0.0);
|
||||
let zs = o.z + belt_z(p, -p.w / 2.0);
|
||||
for c in objects_of("conveyor") {
|
||||
if c.id == o.id { continue; }
|
||||
let e = world_of(c, c.p.w / 2.0, 0.0);
|
||||
let lim = fmax(p.d, c.p.d) / 2.0 + 0.05;
|
||||
if (e[0] - s[0]).abs() > lim || (e[1] - s[1]).abs() > lim { continue; }
|
||||
if (c.z + belt_z(c.p, c.p.w / 2.0) - zs).abs() > 0.3 { continue; }
|
||||
let f = conveyor_feed(c, hops + 1);
|
||||
if type_of(f) == "map" {
|
||||
return #{ rate: f.rate, item: f.item, running: f.running && c.p.running, from: c.id };
|
||||
}
|
||||
return #{ rate: c.p.speed / c.p.gap, item: c.p.item, running: c.p.running, from: c.id };
|
||||
}
|
||||
()
|
||||
}
|
||||
|
||||
fn meta_conveyor() {
|
||||
#{ label: "Conveyor", category: "Industry", icon: "⇉", shape: "line", directed: true,
|
||||
help: "Carries goods along local +x (drawn from start to end; turn it 180° to reverse). Started at a producer's output it carries what the producer makes, as fast as it makes them; a conveyor starting where another ends carries on with its goods.",
|
||||
props: [
|
||||
#{ key: "w", type: "float", def: 6.0, min: 1.0, max: 30.0, step: 0.5, label: "Length (m)" },
|
||||
#{ key: "d", type: "float", def: 0.8, min: 0.3, max: 2.5, step: 0.05, label: "Belt width (m)" },
|
||||
#{ key: "h", type: "float", def: 0.8, min: 0.2, max: 4.0, step: 0.05, label: "Height (m)" },
|
||||
#{ key: "rise", type: "float", def: 0.0, min: -6.0, max: 6.0, step: 0.1, label: "Rise (m)" },
|
||||
#{ key: "running", type: "bool", def: true, label: "Running" },
|
||||
#{ key: "speed", type: "float", def: 0.5, min: 0.05, max: 3.0, step: 0.05, label: "Speed (m/s)" },
|
||||
#{ key: "item", type: "choice", def: "carton", choices: goods_kinds() + ["none"], label: "Goods" },
|
||||
#{ key: "gap", type: "float", def: 1.5, min: 0.5, max: 20.0, step: 0.1, label: "Spacing (m)" },
|
||||
] }
|
||||
}
|
||||
fn prefab_conveyor(o) {
|
||||
let p = o.p;
|
||||
let x0 = -p.w / 2.0 - conveyor_ext(o, -1.0);
|
||||
let x1 = p.w / 2.0 + conveyor_ext(o, 1.0);
|
||||
if x1 - x0 < 0.1 { return; }
|
||||
let W = p.d / 2.0;
|
||||
let th = 0.14;
|
||||
let za = belt_z(p, x0);
|
||||
let zb = belt_z(p, x1);
|
||||
// the side turned to the viewer (-1: -y), drawn after the goods
|
||||
let ny = if vis(0.0, -1.0) { -1.0 } else { 1.0 };
|
||||
let s = sprite();
|
||||
// legs in pairs, at most 2.5 m apart; the far row first
|
||||
let n = fmax(1.0, ((x1 - x0) / 2.5).ceiling()).to_int();
|
||||
for side in [-ny, ny] {
|
||||
let y = side * (W - 0.06);
|
||||
for i in 0..(n + 1) {
|
||||
let u = fmin(fmax(x0 + i * (x1 - x0) / n, x0 + 0.08), x1 - 0.08);
|
||||
let zl = belt_z(p, u) - th;
|
||||
if zl > 0.05 { s.box(u - 0.04, y - 0.04, u + 0.04, y + 0.04, 0.0, zl, "convleg"); }
|
||||
}
|
||||
}
|
||||
// the drive motor under the head end, on the -y side
|
||||
let motor = |q| q.box(x1 - 0.55, -W - 0.32, x1 - 0.12, -W, zb - th - 0.36, zb - th + 0.04, "motor");
|
||||
if ny > 0.0 && zb - th > 0.45 { motor.call(s); }
|
||||
// frame and belt
|
||||
s.prism([[x0, -W], [x1, -W], [x1, W], [x0, W]], [za - th, zb - th, zb - th, za - th], [za, zb, zb, za], "conv", #{ top: "belt" });
|
||||
let rail = |q, y0, y1| q.prism([[x0, y0], [x1, y0], [x1, y1], [x0, y1]], [za, zb, zb, za], [za + 0.07, zb + 0.07, zb + 0.07, za + 0.07], "conv");
|
||||
let far = if ny < 0.0 { [W - 0.04, W] } else { [-W, -W + 0.04] };
|
||||
let near = if ny < 0.0 { [-W, -W + 0.04] } else { [W - 0.04, W] };
|
||||
rail.call(s, far[0], far[1]);
|
||||
// goods: what the feed brings (or the belt's own choice), spaced speed / rate
|
||||
let feed = conveyor_feed(o, 0);
|
||||
let fed = type_of(feed) == "map";
|
||||
let kind = if fed { feed.item } else { p.item };
|
||||
let rate = if fed { feed.rate } else { p.speed / p.gap };
|
||||
let moving = p.running && (!fed || feed.running);
|
||||
let stopped = if p.running { "" } else { " (stopped)" };
|
||||
info_row("Speed", `${num1(p.speed)} m/s${stopped}`);
|
||||
if kind != "none" && rate > 0.0 {
|
||||
let k = fmin(1.0, (p.d - 0.1) / 0.5);
|
||||
let hl = goods_half(kind) * k;
|
||||
let want = p.speed / rate;
|
||||
let gap = fmax(want, 2.0 * hl + 0.12);
|
||||
let src = if fed { ` (from ${feed.from})` } else { "" };
|
||||
info_row("Carries", `${num1(rate * 60.0)} ${kind}s a minute, ${num1(gap)} m apart${src}`);
|
||||
if want < gap - 1e-6 { info_row("Backs up", "goods come faster than this belt can space them"); }
|
||||
let lift = (p.rise / p.w).abs() * hl + 0.01;
|
||||
let u0 = x0 + hl + 0.06;
|
||||
let span = x1 - hl - 0.06 - u0;
|
||||
let cnt = (span / gap).floor().to_int();
|
||||
let step = if cnt < 1 { span } else { gap };
|
||||
cnt = if cnt < 1 { 1 } else { cnt };
|
||||
if span > 0.0 {
|
||||
// back to front along the belt
|
||||
let order = [];
|
||||
for i in 0..cnt { order.push(i); }
|
||||
if !vis(1.0, 0.0) { order.reverse(); }
|
||||
if moving {
|
||||
let per = step / p.speed;
|
||||
let v = dv(step, 0.0, p.rise * step / p.w);
|
||||
s.begin("a ride", `--dx:${v[0]}px;--dy:${v[1]}px;animation-duration:${per}s`);
|
||||
for i in order {
|
||||
let u = u0 + i * step;
|
||||
let fade = if cnt == 1 { "rideio" } else if i == 0 { "ridein" } else if i == cnt - 1 { "rideout" } else { "" };
|
||||
if fade != "" { s.begin("a " + fade, `animation-duration:${per}s`); }
|
||||
s.goods(kind, u, 0.0, belt_z(p, u) + lift, k);
|
||||
if fade != "" { s.end(); }
|
||||
}
|
||||
s.end();
|
||||
} else {
|
||||
for i in order {
|
||||
let u = u0 + (i + 0.5) * step;
|
||||
if u < x1 - hl - 0.05 { s.goods(kind, u, 0.0, belt_z(p, u) + lift, k); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rail.call(s, near[0], near[1]);
|
||||
if ny < 0.0 && zb - th > 0.45 { motor.call(s); }
|
||||
}
|
||||
|
||||
fn meta_producer() {
|
||||
#{ label: "Producer", category: "Industry", icon: "⚙", shape: "rect",
|
||||
help: "A press that makes one item every 60 / rate seconds and puts it out on its +x side. A conveyor starting there carries the goods away, spaced by its speed and this rate.",
|
||||
props: [
|
||||
#{ key: "rate", type: "float", def: 12.0, min: 0.5, max: 240.0, step: 0.5, label: "Rate (per min)" },
|
||||
#{ key: "product", type: "choice", def: "carton", choices: goods_kinds(), label: "Product" },
|
||||
#{ key: "running", type: "bool", def: true, label: "Running" },
|
||||
#{ key: "w", type: "float", def: 3.2, min: 1.6, max: 20.0, step: 0.1, label: "Length (m)" },
|
||||
#{ key: "d", type: "float", def: 2.2, min: 1.2, max: 12.0, step: 0.1, label: "Width (m)" },
|
||||
#{ key: "h", type: "float", def: 2.6, min: 1.6, max: 8.0, step: 0.1, label: "Height (m)" },
|
||||
#{ key: "out", type: "float", def: 0.8, min: 0.2, max: 4.0, step: 0.05, label: "Output height" },
|
||||
#{ key: "colour", type: "choice", def: "mach1", choices: ["mach1", "mach2", "mach3", "mach4"], label: "Colour" },
|
||||
] }
|
||||
}
|
||||
fn prefab_producer(o) {
|
||||
let p = o.p;
|
||||
let L = p.w / 2.0;
|
||||
let D = p.d / 2.0;
|
||||
let t = fmin(p.out, p.h - 1.2);
|
||||
let per = 60.0 / p.rate;
|
||||
let mat = p.colour;
|
||||
// the press sits over the table at the output end, the control cabinet behind it
|
||||
let cx = -L + fmin(1.0, p.w * 0.3);
|
||||
let px = (cx + L) / 2.0;
|
||||
let ct = p.h - 0.35;
|
||||
let k = fmin(1.0, (fmin(L - cx, p.d) - 0.4) / 0.6);
|
||||
// the ram hangs under the crown and comes down to just above the item
|
||||
let rh = fmin(0.3, (ct - t) * 0.25);
|
||||
let rest = ct - 0.04 - rh;
|
||||
let stroke = fmax(0.0, rest - (t + goods_h(p.product) * k) - 0.05);
|
||||
let stopped = if p.running { "" } else { " (stopped)" };
|
||||
info_row("Makes", `${num1(p.rate)} ${p.product}s a minute, one every ${num1(per)} s${stopped}`);
|
||||
let s = sprite();
|
||||
s.box(-L, -D, L, D, 0.0, t, mat);
|
||||
s.box(-L, -D, cx, D, t, p.h, mat);
|
||||
s.circle(cx - 0.25, -D + 0.25, p.h + 0.02, 1.2, if p.running { "led ledg" } else { "led ledr" });
|
||||
// columns: the far pair, then the ram over the item, then the near pair
|
||||
let ny = if vis(0.0, -1.0) { -1.0 } else { 1.0 };
|
||||
let post = |q, y| {
|
||||
let yc = y * (D - 0.12);
|
||||
q.box(cx + 0.02, yc - 0.1, cx + 0.22, yc + 0.1, t, ct, "steel");
|
||||
q.box(L - 0.22, yc - 0.1, L - 0.02, yc + 0.1, t, ct, "steel");
|
||||
};
|
||||
post.call(s, -ny);
|
||||
s.goods(p.product, px, 0.0, t, k);
|
||||
if p.running {
|
||||
let v = dv(0.0, 0.0, -stroke);
|
||||
s.begin("a ram", `--dx:${v[0]}px;--dy:${v[1]}px;animation-duration:${per}s`);
|
||||
}
|
||||
// the rod runs up into the hydraulic cylinder on the crown
|
||||
s.cyl(px, 0.0, 0.07, rest + rh, p.h - 0.05 + stroke, "steel");
|
||||
s.box(px - 0.32, -0.32, px + 0.32, 0.32, rest, rest + rh, "press");
|
||||
if p.running { s.end(); }
|
||||
post.call(s, ny);
|
||||
s.box(cx, -D, L, D, ct, p.h, mat);
|
||||
s.cyl(px, 0.0, 0.2, p.h, p.h + stroke + 0.15, "press");
|
||||
}
|
||||
@@ -262,6 +262,16 @@ koi = "#e8742a"
|
||||
koiw = "#e9e4da"
|
||||
koig = "#d9a63a"
|
||||
ripple = "#8cc3d8"
|
||||
# industry: conveyors, machines, robot arms, goods
|
||||
conv = "#4f6f93"
|
||||
crate = "#8a6a3e"
|
||||
drum = "#2f62a8"
|
||||
mach1 = "#4f7d62"
|
||||
mach2 = "#3f6390"
|
||||
mach3 = "#5b6370"
|
||||
mach4 = "#b8692b"
|
||||
robot = "#e07a1f"
|
||||
robotb = "#3a3f48"
|
||||
|
||||
[palette.light]
|
||||
sky1 = "#a9d4fb"
|
||||
@@ -519,6 +529,15 @@ koi = "#f08a3c"
|
||||
koiw = "#fbf8f2"
|
||||
koig = "#efc35a"
|
||||
ripple = "#d6f1fa"
|
||||
conv = "#6f93bd"
|
||||
crate = "#b48d55"
|
||||
drum = "#3d7cd0"
|
||||
mach1 = "#6fa384"
|
||||
mach2 = "#5b86bd"
|
||||
mach3 = "#8a93a1"
|
||||
mach4 = "#e08a43"
|
||||
robot = "#f08c2b"
|
||||
robotb = "#5a616d"
|
||||
|
||||
[materials]
|
||||
warn = "#e0412f"
|
||||
@@ -724,6 +743,17 @@ z-living = "color-mix(in srgb, var(--iso-floor) 64%, #c8a070)"
|
||||
z-bed = "color-mix(in srgb, var(--iso-floor) 70%, #d8b88a)"
|
||||
z-garage = "color-mix(in srgb, var(--iso-floor) 88%, #8d96a8)"
|
||||
genset = "genset"
|
||||
conv = "conv"
|
||||
convleg = "steel"
|
||||
crate = "crate"
|
||||
drum = "drum"
|
||||
mach1 = "mach1"
|
||||
mach2 = "mach2"
|
||||
mach3 = "mach3"
|
||||
mach4 = "mach4"
|
||||
robot = "robot"
|
||||
robotb = "robotb"
|
||||
grip = "#2b2f37"
|
||||
# house parts and furniture
|
||||
brick = "brick"
|
||||
woodd = "woodd"
|
||||
@@ -779,6 +809,9 @@ f-conc = "conc"
|
||||
".iso-svg .iso-win" = "fill: var(--iso-win); stroke: none"
|
||||
".iso-svg .iso-school" = "pointer-events: none"
|
||||
".iso-svg .iso-swimbox" = "fill: none; stroke: none"
|
||||
".iso-svg .iso-ledr" = "fill: var(--iso-led-r); stroke: none"
|
||||
".iso-svg .iso-ledg" = "fill: var(--iso-led-g); stroke: none"
|
||||
".iso-svg .iso-ledo" = "fill: var(--iso-led-o); stroke: none"
|
||||
".iso-svg .iso-fish" = "stroke: none; fill-opacity: .92"
|
||||
".iso-svg .iso-koi" = "fill: var(--iso-koi)"
|
||||
".iso-svg .iso-koiw" = "fill: var(--iso-koiw)"
|
||||
|
||||
Reference in new issue
Block a user