Robot arms pass goods from belt to belt; native beam primitive
Deploy web Studio / deploy (push) Successful in 1m3s
Deploy web Studio / deploy (push) Successful in 1m3s
- Robot arm (category Industry): picks goods off the end of the nearest conveyor within its reach and puts them on the start of another conveyor within reach, or on a pallet a quarter turn on (pick / put: auto or a side). One move per item coming in (cycle 0) or a set cycle; waits while the line before it stands. The belt it feeds carries its goods at its pace and starts its loop when they land (feeds carry a time offset `ph`), so arm and belts stay in step. - Drawn as a 16-pose flipbook: two-link inverse kinematics per pose, each pose shown for a sixteenth of the cycle by a stepped CSS animation (iso-flip; pose 0 alone when unanimated). Poses pointing away from the viewer are drawn before the base, the others after it. - New sprite method beam(a, b, n, w, mat): a square bar between two 3D points, six faces culled and shaded like face(). Native, so the factory demo renders in 4.7 ms instead of 18.8 ms. - Factory demo: a palletiser at the end of the carton line, a loading robot passing drums from the incline onto the loading belt into the truck. MCP instructions on robot arms. Checks: 70 Rust tests (arms and belts in step, waiting arm, beam faces from every side), Studio 109/109, web 90/90.
This commit is contained in:
1 parent
18dd324543
commit
f49798583e
14 files changed
+460
-31
No files matched your search
@@ -297,6 +297,14 @@ pub fn register_sprite(e: &mut Engine) {
|
||||
ok(s)
|
||||
},
|
||||
);
|
||||
e.register_fn(
|
||||
"beam",
|
||||
|s: &mut SpriteH, a: D, b: D, nn: D, w: D, mat: &str| -> RR<SpriteH> {
|
||||
s.0.borrow_mut()
|
||||
.beam(p3(&a)?, p3(&b)?, p3(&nn)?, n(&w)?, &classes(mat));
|
||||
ok(s)
|
||||
},
|
||||
);
|
||||
e.register_fn("face", |s: &mut SpriteH, p: D, mat: &str| -> RR<SpriteH> {
|
||||
s.0.borrow_mut().face(pts3(&p)?, &classes(mat), None, false);
|
||||
ok(s)
|
||||
|
||||
@@ -643,6 +643,95 @@ fn conveyors_carry_what_the_producer_makes() {
|
||||
assert_eq!(row("belt3", "Speed"), "0.8 m/s");
|
||||
}
|
||||
|
||||
fn robot_doc(belt1_running: bool) -> Doc {
|
||||
let mut doc = Doc::default();
|
||||
for (id, kind, x, y, rot, props) in [
|
||||
("press1", "producer", -6.0, -4.0, 0.0, json!({"rate": 15})),
|
||||
(
|
||||
"belt1",
|
||||
"conveyor",
|
||||
-1.4,
|
||||
-4.0,
|
||||
0.0,
|
||||
json!({"w": 6, "running": belt1_running}),
|
||||
),
|
||||
("robot1", "robot_arm", 3.2, -4.0, 0.0, json!({})),
|
||||
("belt2", "conveyor", 3.2, 1.0, 90.0, json!({"w": 6})),
|
||||
(
|
||||
"robot2",
|
||||
"robot_arm",
|
||||
3.2,
|
||||
5.8,
|
||||
0.0,
|
||||
json!({"colour": "mach2"}),
|
||||
),
|
||||
(
|
||||
"robot3",
|
||||
"robot_arm",
|
||||
-6.0,
|
||||
5.0,
|
||||
0.0,
|
||||
json!({"running": false, "pick": "+x", "put": "-y", "goods": "drum"}),
|
||||
),
|
||||
] {
|
||||
let mut o = obj(id, kind, x, y, props);
|
||||
o.rot = rot;
|
||||
doc.objects.push(o);
|
||||
}
|
||||
doc
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn robot_arms_pass_goods_from_belt_to_belt() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
let doc = robot_doc(true);
|
||||
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"));
|
||||
// two running arms with 16 poses each, one shown at a time; the stopped one stands still
|
||||
assert_eq!(
|
||||
out.html.matches("iso-a iso-flip").count(),
|
||||
32,
|
||||
"view {view}"
|
||||
);
|
||||
assert_eq!(
|
||||
out.html.matches("iso-flip iso-pose0").count(),
|
||||
2,
|
||||
"view {view}"
|
||||
);
|
||||
}
|
||||
let b = r.build("site", 1, &cfg(), &doc).unwrap();
|
||||
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("robot1", "Moves"), "cartons from belt1 to belt2");
|
||||
assert_eq!(row("robot1", "Cycle"), "4 s");
|
||||
// the belt after the arm carries its goods at its pace, starting its loop when they land
|
||||
assert_eq!(
|
||||
row("belt2", "Carries"),
|
||||
"15 cartons a minute, 2 m apart (from robot1)"
|
||||
);
|
||||
assert!(out
|
||||
.html
|
||||
.contains("animation-duration:4.0s;animation-delay:-1.75s"));
|
||||
assert_eq!(row("robot2", "Moves"), "cartons from belt2 to a pallet");
|
||||
assert_eq!(row("robot3", "Cycle"), "4 s (stopped)");
|
||||
// a stopped belt before it: the arm waits
|
||||
let b = r.build("site", 1, &cfg(), &robot_doc(false)).unwrap();
|
||||
let out = render(&b.scene, 1, &RenderOpts::default());
|
||||
assert_eq!(out.html.matches("iso-a iso-flip").count(), 0);
|
||||
assert!(out.info["robot1"].rows[1].1.contains("waiting"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn site_walls_join_at_corners() {
|
||||
let r = Runtime::new(&Bundle::default()).unwrap();
|
||||
|
||||
Reference in new issue
Block a user