import os
from vidigi.utils import EventPosition, create_event_position_df
from vidigi.prep import reshape_for_animations, generate_animation_df
from vidigi.animation import generate_animation, animate_activity_log
import pandas as pd
import plotly.io as pio
= "notebook" pio.renderers.default
Precalculated pathing
import simpy
import random
from typing import Tuple
from vidigi.logging import EventLogger
# ---------------------------
# Warehouse Layout (scaled)
# ---------------------------
# Coordinates in pixels for animation space
= {
LAYOUT # Packing & maintenance
"packing": (270, 270),
"maintenance": (600, 300),
# 8 pickup points
"pickup_1": (25, 500),
"pickup_2": (180, 500),
"pickup_3": (70, 500),
"pickup_4": (550, 500),
"pickup_5": (25, 100),
"pickup_6": (180, 100),
"pickup_7": (370, 100),
"pickup_8": (550, 100),
}
= list(name for name in LAYOUT if name.startswith("pickup"))
PICKUP_POINTS
= 100.0 # pixels per minute
SPEED = 5
PACKING_TIME = (1, 5)
PACKAGES_PER_BATCH = (2, 4)
OTHER_TASK_TIME = 0.3
OTHER_TASK_PROB = 60*24
SIM_DURATION
def travel_time(pos_a: Tuple[int, int], pos_b: Tuple[int, int]) -> float:
"""Calculate travel time between two coordinates."""
= ((pos_a[0] - pos_b[0]) ** 2 + (pos_a[1] - pos_b[1]) ** 2) ** 0.5
dist return dist / SPEED
class PackingRobot:
def __init__(self, env, name, logger):
self.env = env
self.name = name
self.logger = logger
self.pos = LAYOUT["packing"] # start at packing station
self.env.process(self.poll_position())
self.logger.log_arrival(entity_id=self.name,
=self.pos[0], y=self.pos[1])
xself.logger.log_queue(entity_id=self.name, event="packing",
=self.pos[0], y=self.pos[1])
x
def poll_position(self):
"""Logs position every 1 sim time unit, even if idle."""
while True:
self.logger.log_custom_event(entity_id=self.name,
="position_poll",
event_type="position",
event=self.pos[0], y=self.pos[1])
xyield self.env.timeout(1)
def move_to(self, location_name, pathway, outbound=True):
"""Move robot to a location using Manhattan path.
outbound=True: horizontal then vertical
outbound=False: retrace return path (vertical then horizontal)
"""
= LAYOUT[location_name]
destination = self.pos
start_x, start_y = destination
dest_x, dest_y
if outbound:
= [("x", dest_x - start_x), ("y", dest_y - start_y)]
sequence else:
= [("y", dest_y - start_y), ("x", dest_x - start_x)]
sequence
for axis, delta in sequence:
if delta != 0:
= abs(delta) / SPEED
travel_time = int(travel_time)
steps = travel_time - steps
remaining = delta / travel_time
move_per_unit
for _ in range(steps):
yield self.env.timeout(1)
if axis == "x":
self.pos = (self.pos[0] + move_per_unit, self.pos[1])
else:
self.pos = (self.pos[0], self.pos[1] + move_per_unit)
if remaining > 0:
yield self.env.timeout(remaining)
if axis == "x":
self.pos = (self.pos[0] + move_per_unit * remaining, self.pos[1])
else:
self.pos = (self.pos[0], self.pos[1] + move_per_unit * remaining)
def pickup_packages(self, count, pickup_name):
yield self.env.process(self.move_to(pickup_name, "to_pickup", outbound=True))
# self.logger.log_custom_event(entity_id=self.name, event_type="action",
# event=f"picked_up_{count}_packages",
# x=self.pos[0], y=self.pos[1],
# location=pickup_name)
yield self.env.process(self.move_to("packing", "to_packing", outbound=False))
self.logger.log_queue(entity_id=self.name,
=pickup_name,
event=self.pos[0], y=self.pos[1],
x=count)
package_countfor i in range(count):
yield self.env.timeout(PACKING_TIME)
if random.random() < OTHER_TASK_PROB:
yield self.env.process(self.other_task())
# Go back to the packing station
self.logger.log_queue(entity_id=self.name,
="packing",
event=self.pos[0], y=self.pos[1])
x
def other_task(self):
yield self.env.process(self.move_to("maintenance", "to_maintenance"))
= random.randint(*OTHER_TASK_TIME)
task_time self.logger.log_queue(entity_id=self.name,
="maintenance",
event=self.pos[0], y=self.pos[1],
x=task_time
task_duration_mins
)yield self.env.timeout(task_time)
yield self.env.process(self.move_to("packing", "return_from_maintenance"))
# Logging of return to packing location will be handled in pickup_packages process
def package_arrival(env, robot):
while True:
yield env.timeout(random.randint(4, 8))
= random.randint(*PACKAGES_PER_BATCH)
num_packages = random.choice(PICKUP_POINTS)
pickup_name yield env.process(robot.pickup_packages(num_packages, pickup_name))
# ---------------------------
# Running the simulation
# ---------------------------
if __name__ == "__main__":
= simpy.Environment()
env = EventLogger(env=env)
logger = PackingRobot(env, "RoboPack-1", logger)
robot
env.process(package_arrival(env, robot))=SIM_DURATION)
env.run(until=robot.name,
logger.log_departure(entity_id=robot.pos[0], y=robot.pos[1])
x
"robot_log.csv") logger.to_csv(
# Define positions for animation
= create_event_position_df([
event_positions ='arrival', x=0, y=550, label="Entrance"),
EventPosition(event
='pickup_1', x=40, y=500, label="Pickup 1"),
EventPosition(event='pickup_2', x=170, y=500, label="Pickup 2"),
EventPosition(event='pickup_3', x=350, y=500, label="Pickup 3"),
EventPosition(event='pickup_4', x=500, y=500, label="Pickup 4"),
EventPosition(event='pickup_5', x=40, y=60, label="Pickup 5"),
EventPosition(event='pickup_6', x=170, y=60, label="Pickup 6"),
EventPosition(event='pickup_7', x=350, y=60, label="Pickup 7"),
EventPosition(event='pickup_8', x=500, y=60, label="Pickup 8"),
EventPosition(event
='packing', x=300, y=300, label="Packing"),
EventPosition(event='maintenance', x=600, y=300, label="Maintenance"),
EventPosition(event
='depart', x=650, y=50, label="Exit")
EventPosition(event ])
= pd.read_csv("robot_log.csv")
event_log_df event_log_df.head()
entity_id | event_type | event | time | pathway | run_number | timestamp | resource_id | x | y | package_count | task_duration_mins | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | RoboPack-1 | arrival_departure | arrival | 0.0 | NaN | NaN | NaN | NaN | 270.0 | 270.0 | NaN | NaN |
1 | RoboPack-1 | queue | packing | 0.0 | NaN | NaN | NaN | NaN | 270.0 | 270.0 | NaN | NaN |
2 | RoboPack-1 | position_poll | position | 0.0 | NaN | NaN | NaN | NaN | 270.0 | 270.0 | NaN | NaN |
3 | RoboPack-1 | position_poll | position | 1.0 | NaN | NaN | NaN | NaN | 270.0 | 270.0 | NaN | NaN |
4 | RoboPack-1 | position_poll | position | 2.0 | NaN | NaN | NaN | NaN | 270.0 | 270.0 | NaN | NaN |
= 999
STEP_SNAPSHOT_MAX = int(max(event_log_df[event_log_df["event_type"]!="position_poll"]['time']))
LIMIT_DURATION = 999 WRAP_QUEUES_AT
= event_log_df[~event_log_df["event_type"].isin(["position_poll", "action"])][['entity_id', 'event_type', 'event', 'time', 'pathway']]
event_log_df_filtered 10) event_log_df_filtered.head(
entity_id | event_type | event | time | pathway | |
---|---|---|---|---|---|
0 | RoboPack-1 | arrival_departure | arrival | 0.0 | NaN |
1 | RoboPack-1 | queue | packing | 0.0 | NaN |
13 | RoboPack-1 | queue | pickup_2 | 10.4 | NaN |
23 | RoboPack-1 | queue | maintenance | 19.0 | NaN |
29 | RoboPack-1 | queue | packing | 24.6 | NaN |
46 | RoboPack-1 | queue | pickup_8 | 40.6 | NaN |
67 | RoboPack-1 | queue | packing | 60.6 | NaN |
86 | RoboPack-1 | queue | pickup_1 | 78.1 | NaN |
97 | RoboPack-1 | queue | packing | 88.1 | NaN |
114 | RoboPack-1 | queue | pickup_8 | 104.1 | NaN |
We’ll first run this while allowing vidigi to handle the pathing. This means that the path between each step will be interpolated.
animate_activity_log(=event_log_df_filtered,
event_log=event_positions,
event_position_df=WRAP_QUEUES_AT,
wrap_queues_at=LIMIT_DURATION,
limit_duration=STEP_SNAPSHOT_MAX,
step_snapshot_max=1,
every_x_time_units=True,
debug_mode=["🤖"]
custom_entity_icon_list )
Animation function called at 16:38:11
Iteration through time-unit-by-time-unit logs complete 16:38:17
Snapshot df concatenation complete at 16:38:17
Reshaped animation dataframe finished construction at 16:38:17
Placement dataframe finished construction at 16:38:17
Output animation generation complete at 16:38:22
Total Time Elapsed: 10.60 seconds
If we were to put in a background illustrating the paths the robots should follow, this will look bad:
animate_activity_log(=event_log_df_filtered,
event_log=event_positions,
event_position_df=WRAP_QUEUES_AT,
wrap_queues_at=LIMIT_DURATION,
limit_duration=STEP_SNAPSHOT_MAX,
step_snapshot_max=1,
every_x_time_units=True,
debug_mode=False,
display_stage_labels=["🤖"],
custom_entity_icon_list="https://raw.githubusercontent.com/hsma-tools/vidigi/refs/heads/main/examples/example_16_packing_robot/warehouse.png",
add_background_image=1, # New parameter in 1.1.0
background_image_opacity=650,
override_x_max=550,
override_y_max=1300,
plotly_width=800,
plotly_height )
Animation function called at 16:38:22
Iteration through time-unit-by-time-unit logs complete 16:38:28
Snapshot df concatenation complete at 16:38:28
Reshaped animation dataframe finished construction at 16:38:28
Placement dataframe finished construction at 16:38:28
Output animation generation complete at 16:38:32
Total Time Elapsed: 10.22 seconds
However, in this particular model, we have been polling the location of the robot after every step.
This might be imporant to visualise accurately in certain models - for example, to demonstrate why a robot might have to wait for another robot to move out of the way, and to assure stakeholders that such a thing has been recorded accurately.
Let’s see how we can combine vidigi with these polled locations.
= reshape_for_animations(
full_entity_df =event_log_df[~event_log_df["event_type"].isin(["position_poll", "action"])][['entity_id', 'event_type', 'event', 'time', 'pathway']],
event_log=LIMIT_DURATION,
limit_duration=STEP_SNAPSHOT_MAX,
step_snapshot_max=1,
every_x_time_units=True
debug_mode
)
full_entity_df
Iteration through time-unit-by-time-unit logs complete 16:38:38
Snapshot df concatenation complete at 16:38:38
index | entity_id | event_type | event | time | pathway | rank | snapshot_time | |
---|---|---|---|---|---|---|---|---|
0 | 1 | RoboPack-1 | queue | packing | 0.0 | NaN | 1.0 | 0 |
1 | 1 | RoboPack-1 | queue | packing | 0.0 | NaN | 1.0 | 1 |
2 | 1 | RoboPack-1 | queue | packing | 0.0 | NaN | 1.0 | 2 |
3 | 1 | RoboPack-1 | queue | packing | 0.0 | NaN | 1.0 | 3 |
4 | 1 | RoboPack-1 | queue | packing | 0.0 | NaN | 1.0 | 4 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
1436 | 1535 | RoboPack-1 | queue | packing | 1428.4 | NaN | 1.0 | 1436 |
1437 | 1535 | RoboPack-1 | queue | packing | 1428.4 | NaN | 1.0 | 1437 |
1438 | 1535 | RoboPack-1 | queue | packing | 1428.4 | NaN | 1.0 | 1438 |
1439 | 1535 | RoboPack-1 | queue | packing | 1428.4 | NaN | 1.0 | 1439 |
1440 | 1547 | RoboPack-1 | arrival_departure | depart | 1440.0 | NaN | 1.0 | 1440 |
1441 rows × 8 columns
= generate_animation_df(
full_entity_df_plus_pos =full_entity_df,
full_entity_df=event_positions,
event_position_df=WRAP_QUEUES_AT,
wrap_queues_at=STEP_SNAPSHOT_MAX,
step_snapshot_max=True,
debug_mode=["🤖"]
custom_entity_icon_list
)
full_entity_df_plus_pos
Placement dataframe finished construction at 16:38:38
index | entity_id | event_type | event | time | pathway | rank | snapshot_time | x | y_final | label | resource | x_final | row | y | icon | opacity | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 19 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
1 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 20 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
2 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 21 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
3 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 22 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
4 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 23 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1435 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1385 | 500 | 60.0 | Pickup 8 | None | 500.0 | 0.0 | NaN | 🤖 | 1.0 |
1436 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1386 | 500 | 60.0 | Pickup 8 | None | 500.0 | 0.0 | NaN | 🤖 | 1.0 |
1437 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1387 | 500 | 60.0 | Pickup 8 | None | 500.0 | 0.0 | NaN | 🤖 | 1.0 |
1438 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1388 | 500 | 60.0 | Pickup 8 | None | 500.0 | 0.0 | NaN | 🤖 | 1.0 |
1439 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1389 | 500 | 60.0 | Pickup 8 | None | 500.0 | 0.0 | NaN | 🤖 | 1.0 |
1440 rows × 17 columns
Now we can replace our calculated x_final and y_final coordinates with the values from the polling.
First, we pull this data out of our event logs and rename the columns to match the relevant columns in our transformed event logging dataset.
= event_log_df[event_log_df["event_type"]=="position_poll"][['entity_id', 'time', 'x', 'y']].reset_index(drop=True)
entity_position_df = entity_position_df.rename(columns={"x": "x_final", "y": "y_final", "time": "snapshot_time"})
entity_position_df entity_position_df
entity_id | snapshot_time | x_final | y_final | |
---|---|---|---|---|
0 | RoboPack-1 | 0.0 | 270.0 | 270.0 |
1 | RoboPack-1 | 1.0 | 270.0 | 270.0 |
2 | RoboPack-1 | 2.0 | 270.0 | 270.0 |
3 | RoboPack-1 | 3.0 | 270.0 | 270.0 |
4 | RoboPack-1 | 4.0 | 270.0 | 270.0 |
... | ... | ... | ... | ... |
1435 | RoboPack-1 | 1435.0 | 25.0 | 270.0 |
1436 | RoboPack-1 | 1436.0 | 25.0 | 370.0 |
1437 | RoboPack-1 | 1437.0 | 25.0 | 470.0 |
1438 | RoboPack-1 | 1438.0 | 25.0 | 500.0 |
1439 | RoboPack-1 | 1439.0 | 25.0 | 400.0 |
1440 rows × 4 columns
We then drop our original x_final and y_final columns from our transformed dataset, replacing them with those from the polling in our model.
= (
full_entity_df_plus_pos_manual_locations
full_entity_df_plus_pos=["x_final", "y_final"])
.drop(columns=["entity_id", "snapshot_time"])
.merge(entity_position_df, on
)
full_entity_df_plus_pos_manual_locations
index | entity_id | event_type | event | time | pathway | rank | snapshot_time | x | label | resource | row | y | icon | opacity | x_final | y_final | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 19 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 270.0 |
1 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 20 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
2 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 21 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
3 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 22 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 500.0 | 300.0 |
4 | 23 | RoboPack-1 | queue | maintenance | 19.0 | NaN | 1.0 | 23 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 400.0 | 300.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1435 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1385 | 500 | Pickup 8 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
1436 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1386 | 500 | Pickup 8 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
1437 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1387 | 500 | Pickup 8 | None | 0.0 | NaN | 🤖 | 1.0 | 370.0 | 270.0 |
1438 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1388 | 500 | Pickup 8 | None | 0.0 | NaN | 🤖 | 1.0 | 470.0 | 270.0 |
1439 | 1472 | RoboPack-1 | queue | pickup_8 | 1370.7 | NaN | 1.0 | 1389 | 500 | Pickup 8 | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 270.0 |
1440 rows × 17 columns
Finally, we then generate our animation as usual - making sure to refer to our updated dataframe.
= generate_animation(
fig =full_entity_df_plus_pos_manual_locations.sort_values(['entity_id', 'snapshot_time']),
full_entity_df_plus_pos= event_positions,
event_position_df="seconds",
simulation_time_unit=False,
display_stage_labels=False,
setup_mode="07:00:00",
start_time="%H:%M:%S",
time_display_units=True,
debug_mode="https://raw.githubusercontent.com/hsma-tools/vidigi/refs/heads/main/examples/example_16_packing_robot/warehouse.png",
add_background_image=1, # New parameter in 1.1.0
background_image_opacity=650,
override_x_max=550,
override_y_max=1300,
plotly_width=800,
plotly_height=50,
entity_icon_size=200,
frame_duration=300
frame_transition_duration
)
fig
Output animation generation complete at 16:38:43
Let’s finally repeat this with an instance with multiple packers.
In this very simplistic model, blocking of corridors is not implemented - but is the kind of thing that would visualise well with this kind of pre-calculated movement.
= pd.read_csv("robot_log_multiple.csv")
event_log_df_multiple
= reshape_for_animations(
full_entity_df_multiple =event_log_df_multiple[~event_log_df_multiple["event_type"].isin(["position_poll", "action"])][['entity_id', 'event_type', 'event', 'time', 'pathway']],
event_log=LIMIT_DURATION,
limit_duration=STEP_SNAPSHOT_MAX,
step_snapshot_max=1,
every_x_time_units=True
debug_mode
)
= generate_animation_df(
full_entity_df_plus_pos_multiple =full_entity_df_multiple,
full_entity_df=event_positions,
event_position_df=WRAP_QUEUES_AT,
wrap_queues_at=STEP_SNAPSHOT_MAX,
step_snapshot_max=True,
debug_mode=["🤖"]
custom_entity_icon_list# custom_entity_icon_list=["🟦", "🟫", "🟪", "🟧", "🟥", "🟨", "🟩", "◻️"]
)
full_entity_df_plus_pos_multiple
Iteration through time-unit-by-time-unit logs complete 16:38:51
Snapshot df concatenation complete at 16:38:51
Placement dataframe finished construction at 16:38:51
index | entity_id | event_type | event | time | pathway | rank | snapshot_time | x | y_final | label | resource | x_final | row | y | icon | opacity | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 25 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
1 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 26 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
2 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 27 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
3 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 28 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
4 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 29 | 600 | 300.0 | Maintenance | None | 600.0 | 0.0 | NaN | 🤖 | 1.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
11515 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 597 | 350 | 60.0 | Pickup 7 | None | 350.0 | 0.0 | NaN | 🤖 | 1.0 |
11516 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 598 | 350 | 60.0 | Pickup 7 | None | 350.0 | 0.0 | NaN | 🤖 | 1.0 |
11517 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 599 | 350 | 60.0 | Pickup 7 | None | 350.0 | 0.0 | NaN | 🤖 | 1.0 |
11518 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 600 | 350 | 60.0 | Pickup 7 | None | 350.0 | 0.0 | NaN | 🤖 | 1.0 |
11519 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 601 | 350 | 60.0 | Pickup 7 | None | 350.0 | 0.0 | NaN | 🤖 | 1.0 |
11520 rows × 17 columns
= (
entity_position_df_multiple "event_type"]=="position_poll"]
event_log_df_multiple[event_log_df_multiple['entity_id', 'time', 'x', 'y']]
[[=True)
.reset_index(drop={"x": "x_final", "y": "y_final", "time": "snapshot_time"})
.rename(columns
)
# entity_position_df_multiple["snapshot_time"] = entity_position_df_multiple["snapshot_time"].astype('int')
entity_position_df_multiple
entity_id | snapshot_time | x_final | y_final | |
---|---|---|---|---|
0 | RoboPack-1 | 0.0 | 270.0 | 270.0 |
1 | RoboPack-2 | 0.0 | 285.0 | 270.0 |
2 | RoboPack-3 | 0.0 | 300.0 | 270.0 |
3 | RoboPack-4 | 0.0 | 315.0 | 270.0 |
4 | RoboPack-5 | 0.0 | 270.0 | 300.0 |
... | ... | ... | ... | ... |
11515 | RoboPack-4 | 1439.0 | 300.0 | 300.0 |
11516 | RoboPack-5 | 1439.0 | 270.0 | 300.0 |
11517 | RoboPack-6 | 1439.0 | 300.0 | 300.0 |
11518 | RoboPack-7 | 1439.0 | 300.0 | 300.0 |
11519 | RoboPack-8 | 1439.0 | 315.0 | 300.0 |
11520 rows × 4 columns
= (
full_entity_df_plus_pos_manual_locations_multiple
full_entity_df_plus_pos_multiple=["x_final", "y_final"])
.drop(columns=["entity_id", "snapshot_time"])
.merge(entity_position_df_multiple, on
)
full_entity_df_plus_pos_manual_locations_multiple
index | entity_id | event_type | event | time | pathway | rank | snapshot_time | x | label | resource | row | y | icon | opacity | x_final | y_final | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 25 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
1 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 26 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
2 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 27 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
3 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 28 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
4 | 224 | RoboPack-8 | queue | maintenance | 24.9 | NaN | 1.0 | 29 | 600 | Maintenance | None | 0.0 | NaN | 🤖 | 1.0 | 600.0 | 300.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
11515 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 597 | 350 | Pickup 7 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
11516 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 598 | 350 | Pickup 7 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
11517 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 599 | 350 | Pickup 7 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
11518 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 600 | 350 | Pickup 7 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
11519 | 4934 | RoboPack-1 | queue | pickup_7 | 576.6 | NaN | 1.0 | 601 | 350 | Pickup 7 | None | 0.0 | NaN | 🤖 | 1.0 | 270.0 | 270.0 |
11520 rows × 17 columns
= generate_animation(
fig_multiple =full_entity_df_plus_pos_manual_locations_multiple.sort_values(['entity_id', 'snapshot_time']),
full_entity_df_plus_pos= event_positions,
event_position_df="seconds",
simulation_time_unit=False,
display_stage_labels=False,
setup_mode="07:00:00",
start_time="%H:%M:%S",
time_display_units=True,
debug_mode="https://raw.githubusercontent.com/hsma-tools/vidigi/refs/heads/main/examples/example_16_packing_robot/warehouse.png",
add_background_image=1, # New parameter in 1.1.0
background_image_opacity=650,
override_x_max=550,
override_y_max=1300,
plotly_width=800,
plotly_height=30
entity_icon_size
)
fig_multiple
Output animation generation complete at 16:38:56