// 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"); }