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
pio.renderers.default = "notebook"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),
}
PICKUP_POINTS = list(name for name in LAYOUT if name.startswith("pickup"))
SPEED = 100.0 # pixels per minute
PACKING_TIME = 5
PACKAGES_PER_BATCH = (1, 5)
OTHER_TASK_TIME = (2, 4)
OTHER_TASK_PROB = 0.3
SIM_DURATION = 60*24
def travel_time(pos_a: Tuple[int, int], pos_b: Tuple[int, int]) -> float:
"""Calculate travel time between two coordinates."""
dist = ((pos_a[0] - pos_b[0]) ** 2 + (pos_a[1] - pos_b[1]) ** 2) ** 0.5
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,
x=self.pos[0], y=self.pos[1])
self.logger.log_queue(entity_id=self.name, event="packing",
x=self.pos[0], y=self.pos[1])
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,
event_type="position_poll",
event="position",
x=self.pos[0], y=self.pos[1])
yield 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)
"""
destination = LAYOUT[location_name]
start_x, start_y = self.pos
dest_x, dest_y = destination
if outbound:
sequence = [("x", dest_x - start_x), ("y", dest_y - start_y)]
else:
sequence = [("y", dest_y - start_y), ("x", dest_x - start_x)]
for axis, delta in sequence:
if delta != 0:
travel_time = abs(delta) / SPEED
steps = int(travel_time)
remaining = travel_time - steps
move_per_unit = delta / travel_time
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,
event=pickup_name,
x=self.pos[0], y=self.pos[1],
package_count=count)
for 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,
event="packing",
x=self.pos[0], y=self.pos[1])
def other_task(self):
yield self.env.process(self.move_to("maintenance", "to_maintenance"))
task_time = random.randint(*OTHER_TASK_TIME)
self.logger.log_queue(entity_id=self.name,
event="maintenance",
x=self.pos[0], y=self.pos[1],
task_duration_mins=task_time
)
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))
num_packages = random.randint(*PACKAGES_PER_BATCH)
pickup_name = random.choice(PICKUP_POINTS)
yield env.process(robot.pickup_packages(num_packages, pickup_name))
# ---------------------------
# Running the simulation
# ---------------------------
if __name__ == "__main__":
env = simpy.Environment()
logger = EventLogger(env=env)
robot = PackingRobot(env, "RoboPack-1", logger)
env.process(package_arrival(env, robot))
env.run(until=SIM_DURATION)
logger.log_departure(entity_id=robot.name,
x=robot.pos[0], y=robot.pos[1])
logger.to_csv("robot_log.csv")# Define positions for animation
event_positions = create_event_position_df([
EventPosition(event='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")
])event_log_df = pd.read_csv("robot_log.csv")
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 |
STEP_SNAPSHOT_MAX = 999
LIMIT_DURATION = int(max(event_log_df[event_log_df["event_type"]!="position_poll"]['time']))
WRAP_QUEUES_AT = 999event_log_df_filtered = event_log_df[~event_log_df["event_type"].isin(["position_poll", "action"])][['entity_id', 'event_type', 'event', 'time', 'pathway']]
event_log_df_filtered.head(10)| 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=event_log_df_filtered,
event_position_df=event_positions,
wrap_queues_at=WRAP_QUEUES_AT,
limit_duration=LIMIT_DURATION,
step_snapshot_max=STEP_SNAPSHOT_MAX,
every_x_time_units=1,
debug_mode=True,
custom_entity_icon_list=["🤖"]
)Animation function called at 13:45:39
Iteration through time-unit-by-time-unit logs complete 13:45:44
Snapshot df concatenation complete at 13:45:45
Reshaped animation dataframe finished construction at 13:45:45
Placement dataframe finished construction at 13:45:45
Output animation generation complete at 13:45:50
Total Time Elapsed: 11.08 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=event_log_df_filtered,
event_position_df=event_positions,
wrap_queues_at=WRAP_QUEUES_AT,
limit_duration=LIMIT_DURATION,
step_snapshot_max=STEP_SNAPSHOT_MAX,
every_x_time_units=1,
debug_mode=True,
display_stage_labels=False,
custom_entity_icon_list=["🤖"],
add_background_image="https://raw.githubusercontent.com/hsma-tools/vidigi/refs/heads/main/examples/example_16_packing_robot/warehouse.png",
background_image_opacity=1, # New parameter in 1.1.0
override_x_max=650,
override_y_max=550,
plotly_width=1300,
plotly_height=800,
)Animation function called at 13:45:51
Iteration through time-unit-by-time-unit logs complete 13:45:56
Snapshot df concatenation complete at 13:45:56
Reshaped animation dataframe finished construction at 13:45:56
Placement dataframe finished construction at 13:45:57
Output animation generation complete at 13:46:02
Total Time Elapsed: 11.19 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.
full_entity_df = reshape_for_animations(
event_log=event_log_df[~event_log_df["event_type"].isin(["position_poll", "action"])][['entity_id', 'event_type', 'event', 'time', 'pathway']],
limit_duration=LIMIT_DURATION,
step_snapshot_max=STEP_SNAPSHOT_MAX,
every_x_time_units=1,
debug_mode=True
)
full_entity_dfIteration through time-unit-by-time-unit logs complete 13:46:08
Snapshot df concatenation complete at 13:46:08
| index | entity_id | event_type | event | time | rank | snapshot_time | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 0 |
| 1 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 1 |
| 2 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 2 |
| 3 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 3 |
| 4 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 4 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 1436 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1436 |
| 1437 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1437 |
| 1438 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1438 |
| 1439 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1439 |
| 1440 | 1547 | RoboPack-1 | arrival_departure | depart | 1440.0 | 1.0 | 1440 |
1441 rows × 7 columns
full_entity_df_plus_pos = generate_animation_df(
full_entity_df=full_entity_df,
event_position_df=event_positions,
wrap_queues_at=WRAP_QUEUES_AT,
step_snapshot_max=STEP_SNAPSHOT_MAX,
debug_mode=True,
custom_entity_icon_list=["🤖"]
)
full_entity_df_plus_posPlacement dataframe finished construction at 13:46:08
| index | entity_id | event_type | event | time | rank | snapshot_time | x | y_final | label | x_final | row | icon | opacity | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 109 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 0 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 110 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 1 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 111 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 2 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 112 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 3 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 113 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 4 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 739 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1435 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 740 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1436 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 741 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1437 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 742 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1438 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 743 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1439 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
1440 rows × 14 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.
entity_position_df = 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_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
.drop(columns=["x_final", "y_final"])
.merge(entity_position_df, on=["entity_id", "snapshot_time"])
)
full_entity_df_plus_pos_manual_locations| index | entity_id | event_type | event | time | rank | snapshot_time | x | label | row | icon | opacity | x_final | y_final | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 0 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 1 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 1 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 2 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 2 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 3 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 3 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 4 | 1 | RoboPack-1 | queue | packing | 0.0 | 1.0 | 4 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1435 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1435 | 300 | Packing | 0.0 | 🤖 | 1.0 | 25.0 | 270.0 |
| 1436 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1436 | 300 | Packing | 0.0 | 🤖 | 1.0 | 25.0 | 370.0 |
| 1437 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1437 | 300 | Packing | 0.0 | 🤖 | 1.0 | 25.0 | 470.0 |
| 1438 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1438 | 300 | Packing | 0.0 | 🤖 | 1.0 | 25.0 | 500.0 |
| 1439 | 1535 | RoboPack-1 | queue | packing | 1428.4 | 1.0 | 1439 | 300 | Packing | 0.0 | 🤖 | 1.0 | 25.0 | 400.0 |
1440 rows × 14 columns
Finally, we then generate our animation as usual - making sure to refer to our updated dataframe.
fig = generate_animation(
full_entity_df_plus_pos=full_entity_df_plus_pos_manual_locations.sort_values(['entity_id', 'snapshot_time']),
event_position_df= event_positions,
simulation_time_unit="seconds",
display_stage_labels=False,
setup_mode=False,
start_time="07:00:00",
time_display_units="%H:%M:%S",
debug_mode=True,
add_background_image="https://raw.githubusercontent.com/hsma-tools/vidigi/refs/heads/main/examples/example_16_packing_robot/warehouse.png",
background_image_opacity=1, # New parameter in 1.1.0
override_x_max=650,
override_y_max=550,
plotly_width=1300,
plotly_height=800,
entity_icon_size=50,
frame_duration=200,
frame_transition_duration=300
)
figOutput animation generation complete at 13:46:13
Multiple Packers
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.
event_log_df_multiple = pd.read_csv("robot_log_multiple.csv")
full_entity_df_multiple = reshape_for_animations(
event_log=event_log_df_multiple[~event_log_df_multiple["event_type"].isin(["position_poll", "action"])][['entity_id', 'event_type', 'event', 'time', 'pathway']],
limit_duration=LIMIT_DURATION,
step_snapshot_max=STEP_SNAPSHOT_MAX,
every_x_time_units=1,
debug_mode=True
)
full_entity_df_plus_pos_multiple = generate_animation_df(
full_entity_df=full_entity_df_multiple,
event_position_df=event_positions,
wrap_queues_at=WRAP_QUEUES_AT,
step_snapshot_max=STEP_SNAPSHOT_MAX,
debug_mode=True,
custom_entity_icon_list=["🤖"]
# custom_entity_icon_list=["🟦", "🟫", "🟪", "🟧", "🟥", "🟨", "🟩", "◻️"]
)
full_entity_df_plus_pos_multipleIteration through time-unit-by-time-unit logs complete 13:46:22
Snapshot df concatenation complete at 13:46:22
Placement dataframe finished construction at 13:46:22
| index | entity_id | event_type | event | time | rank | snapshot_time | x | y_final | label | x_final | row | icon | opacity | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10222 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 0 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 10223 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 1 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 10224 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 2 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 10225 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 3 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| 10226 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 4 | 300 | 300.0 | Packing | 300.0 | 0.0 | 🤖 | 1.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1106 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1435 | 500 | 500.0 | Pickup 4 | 500.0 | 0.0 | 🤖 | 1.0 |
| 1107 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1436 | 500 | 500.0 | Pickup 4 | 500.0 | 0.0 | 🤖 | 1.0 |
| 1108 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1437 | 500 | 500.0 | Pickup 4 | 500.0 | 0.0 | 🤖 | 1.0 |
| 1109 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1438 | 500 | 500.0 | Pickup 4 | 500.0 | 0.0 | 🤖 | 1.0 |
| 1110 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1439 | 500 | 500.0 | Pickup 4 | 500.0 | 0.0 | 🤖 | 1.0 |
11520 rows × 14 columns
entity_position_df_multiple = (
event_log_df_multiple[event_log_df_multiple["event_type"]=="position_poll"]
[['entity_id', 'time', 'x', 'y']]
.reset_index(drop=True)
.rename(columns={"x": "x_final", "y": "y_final", "time": "snapshot_time"})
)
# 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
.drop(columns=["x_final", "y_final"])
.merge(entity_position_df_multiple, on=["entity_id", "snapshot_time"])
)
full_entity_df_plus_pos_manual_locations_multiple| index | entity_id | event_type | event | time | rank | snapshot_time | x | label | row | icon | opacity | x_final | y_final | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 0 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 1 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 1 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 2 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 2 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 3 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 3 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| 4 | 1 | RoboPack-1 | queue | packing | 0.00 | 1.0 | 4 | 300 | Packing | 0.0 | 🤖 | 1.0 | 270.0 | 270.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 11515 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1435 | 500 | Pickup 4 | 0.0 | 🤖 | 1.0 | 315.0 | 300.0 |
| 11516 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1436 | 500 | Pickup 4 | 0.0 | 🤖 | 1.0 | 315.0 | 300.0 |
| 11517 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1437 | 500 | Pickup 4 | 0.0 | 🤖 | 1.0 | 315.0 | 300.0 |
| 11518 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1438 | 500 | Pickup 4 | 0.0 | 🤖 | 1.0 | 315.0 | 300.0 |
| 11519 | 12159 | RoboPack-8 | queue | pickup_4 | 1423.15 | 1.0 | 1439 | 500 | Pickup 4 | 0.0 | 🤖 | 1.0 | 315.0 | 300.0 |
11520 rows × 14 columns
fig_multiple = generate_animation(
full_entity_df_plus_pos=full_entity_df_plus_pos_manual_locations_multiple.sort_values(['entity_id', 'snapshot_time']),
event_position_df= event_positions,
simulation_time_unit="seconds",
display_stage_labels=False,
setup_mode=False,
start_time="07:00:00",
time_display_units="%H:%M:%S",
debug_mode=True,
add_background_image="https://raw.githubusercontent.com/hsma-tools/vidigi/refs/heads/main/examples/example_16_packing_robot/warehouse.png",
background_image_opacity=1, # New parameter in 1.1.0
override_x_max=650,
override_y_max=550,
plotly_width=1300,
plotly_height=800,
entity_icon_size=30
)
fig_multipleOutput animation generation complete at 13:46:27