From b54b0bbfe2326392f728cc1384bada10cbf98aa9 Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Tue, 17 Mar 2026 17:39:44 +0100 Subject: [PATCH 01/23] redo_visuals --- lineflow/simulation/connectors.py | 7 +- lineflow/simulation/line.py | 51 +++++- lineflow/simulation/movable_objects.py | 11 ++ lineflow/simulation/stations.py | 37 +++- lineflow/simulation/visualization.py | 230 +++++++++++++++++++++++++ 5 files changed, 325 insertions(+), 11 deletions(-) diff --git a/lineflow/simulation/connectors.py b/lineflow/simulation/connectors.py index 0b16264..5c1b7ba 100644 --- a/lineflow/simulation/connectors.py +++ b/lineflow/simulation/connectors.py @@ -113,7 +113,7 @@ def setup_draw(self): ) self._positions_arrow[i] = arrowhead - + def _draw(self, screen): pygame.draw.line( @@ -136,6 +136,11 @@ def _draw(self, screen): for carrier in self.carriers.values(): carrier.draw(screen) + def _add(self, data_list): + data_list.append(dict(type='connector',start=self._position_input,end=self._position_output,n_slots=self.capacity)) + for carrier in self.carriers.values(): + carrier._add(data_list) + def _sample_put_time(self): return self.put_time + self.random.exponential(scale=self.put_std) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index 914c1ea..a97bb10 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -4,6 +4,7 @@ import numpy as np import logging from tqdm import tqdm +from multiprocessing import Process, Queue, Event from lineflow.simulation.stationary_objects import StationaryObject from lineflow.simulation.states import LineStates @@ -12,7 +13,7 @@ Station, Sink, ) -from lineflow.simulation.visualization import Viewpoint +from lineflow.simulation.visualization import start_visualization, Viewpoint logger = logging.getLogger(__name__) @@ -49,6 +50,14 @@ def __init__( self.reset(random_state=random_state) + self.stop_event = Event() + self.connection = Queue() + self.visualization_process = Process( + target=start_visualization, + args=(self.connection, self.stop_event) + ) + self.data = [] + @property def name(self): return self.__class__.__name__ @@ -221,6 +230,29 @@ def _draw_objects_of_type(self, object_type): if isinstance(obj, object_type): obj._draw(self.viewpoint.paper) + def give_data(self): + if not self.stop_event.is_set(): + self.data = [] + self._add_connectors() + self._add_stations() + self.connection.put(self.data) + + def setup_connectors(self): + for _, obj in self._objects.items(): + if isinstance(obj, Connector): + obj.setup_draw() + + def _add_stations(self): + self._add_objects_of_type(Station) + + def _add_connectors(self): + self._add_objects_of_type(Connector) + + def _add_objects_of_type(self, object_type): + for _, obj in self._objects.items(): + if isinstance(obj, object_type): + obj._add(self.data) + def _get_object_positions(self): x = [] y = [] @@ -311,10 +343,10 @@ def run( visualize (bool): If true, line visualization is opened capture_screen (bool): Captures last Time frame when screen should be recorded """ - + self.setup_connectors() if visualize: - self.setup_draw() - + self.visualization_process.start() + #self.setup_draw() # Register objects when simulation is initially started if len(self.env._queue) == 0: @@ -342,13 +374,16 @@ def run( self.apply(actions) if visualize: - self._draw(actions) + self.give_data() + #self._draw(actions) - if capture_screen and visualize: - pygame.image.save(self.viewpoint.screen, f"{self.name}.png") + #if capture_screen and visualize: + # pygame.image.save(self.viewpoint.screen, f"{self.name}.png") if visualize: - self.viewpoint.teardown() + #self.viewpoint.teardown() + self.stop_event.set() + self.visualization_process.join() def get_observations(self, object_name=None): """ diff --git a/lineflow/simulation/movable_objects.py b/lineflow/simulation/movable_objects.py index 3ab7d48..945b986 100644 --- a/lineflow/simulation/movable_objects.py +++ b/lineflow/simulation/movable_objects.py @@ -37,9 +37,17 @@ def draw(self, screen, with_text=True): text = font.render(self.name, True, 'blue') screen.blit(text, text.get_rect(center=self._position + (0, -1.3*self._height))) + def _add(self, data_list, with_text=True): + self._add_shape(data_list) + if with_text: + data_list[-1]['name'] = self.name + def _draw_shape(self, screen): raise NotImplementedError() + def _add_shape(self, data_list): + raise NotImplementedError() + def move(self, position): if not isinstance(position, pygame.Vector2): raise ValueError('Expect pygame vector as position') @@ -196,6 +204,9 @@ def _draw_shape(self, screen): height=self._height_part, ) + def _add_shape(self, data_list): + data_list.append(dict(type='carrier',position=self._position,fill=self.capacity/len(self.parts))) + def move(self, position): """ """ diff --git a/lineflow/simulation/stations.py b/lineflow/simulation/stations.py index 3f1a3c6..3ee53fe 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -179,8 +179,13 @@ def setup_draw(self): self._height, ) - font = pygame.font.SysFont(None, 20) - self._text = font.render(self.name, True, 'black') + #font = pygame.font.SysFont(None, 20) + #self._text = font.render(self.name, True, 'black') + + def _add(self, data_list): + data = dict(type='station',name=self.name,position=self.position,mode=self.state['mode'].to_str()) + self._add_info(data) + data_list.append(data) def _draw(self, screen): pygame.draw.rect(screen, self._color, self._rect, border_radius=5) @@ -193,6 +198,9 @@ def _draw(self, screen): def _draw_info(self, screen): pass + def _add_info(self, data_dict): + pass + def _draw_n_workers(self, screen): if not self.is_automatic: font = pygame.font.SysFont(None, 14) @@ -206,6 +214,10 @@ def _draw_n_workers(self, screen): text.get_rect(center=self.position), ) + def _add_n_workers(self, data_dict): + if not self.is_automatic: + data_dict['worker_skill'] = self.worker_skill + def _draw_n_carriers(self, screen): font = pygame.font.SysFont(None, 14) text = font.render( @@ -218,6 +230,9 @@ def _draw_n_carriers(self, screen): text.get_rect(center=self.position), ) + def _add_n_carriers(self, data_dict): + data_dict['magazine'] = self.state['carriers_in_magazine'] + def get_performance_coefficient(self): return compute_performance_coefficient(self.worker_skill) @@ -426,6 +441,9 @@ def _has_invalid_components_on_carrier(self, carrier): def _draw_info(self, screen): self._draw_n_workers(screen) + def _add_info(self, data_dict): + self._add_n_workers(data_dict) + def run(self): while True: @@ -549,6 +567,9 @@ def init_state(self): def _draw_info(self, screen): self._draw_n_workers(screen) + def _add_info(self, data_dict): + self._add_n_workers(data_dict) + def run(self): while True: @@ -969,6 +990,15 @@ def _draw_info(self, screen): for pos in [pos_in, pos_out]: pygame.draw.line(screen, "gray", self.position, pos, width=5) + def _add_info(self, data_dict): + pos_buffer_in = self._get_buffer_in_position() + pos_buffer_out = self._get_buffer_out_position() + + pos_in = pos_buffer_in + 0.5*(self.position - pos_buffer_in) + pos_out = pos_buffer_out + 0.5*(self.position - pos_buffer_out) + + data_dict['pos_in_out'] = [pos_in,pos_out] + def _connect_to_input(self, buffer): self.buffer_in.append(buffer.connect_to_output(self)) @@ -1201,6 +1231,9 @@ def _update_magazine(self): def _draw_info(self, screen): self._draw_n_carriers(screen) + def _add_info(self, data_dict): + self._add_n_carriers(data_dict) + def get_carrier(self): # First check if Magazine is allowed to create unlimited carriers if self.unlimited_carriers: diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 5745e1a..f579125 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -1,4 +1,5 @@ import pygame +from queue import Empty class Viewpoint: """ @@ -64,3 +65,232 @@ def _draw(self): def teardown(self): pygame.quit() + +class Visualization: + + def __init__( + self, + size=None, + viewpoint=None, + points=None, + connection=None, + stop_event=None, + ): + + if size is None: + size = (1280,720) + self.size = size + if viewpoint is None: + viewpoint = (0,0,1) + self.viewpoint = pygame.Vector3(viewpoint) + + self.connection = connection + self.stop_event = stop_event + + self.center = pygame.Vector2(self.size[0]/2, self.size[1]/2) + + self.connection_data = [] + self.stations = [] + self.connectors = [] + self.carriers = [] + + def teardown(self): + self.running = False + + def clear(self): + self.screen.fill('white') + + def get_from_connection(self): + #if not self.connection.empty(): + # self.connection_data = self.connection.get() + while True: + try: + self.connection_data = self.connection.get_nowait() + except Empty: + break + + def sort_connection_data(self): + self.carriers = [] + self.connectors = [] + self.stations = [] + for item in self.connection_data: + if item['type'] == 'carrier': + self.carriers.append(item) + elif item['type'] == 'connector': + self.connectors.append(item) + elif item['type'] == 'station': + self.stations.append(item) + + def check_connection(self): + self.get_from_connection() + self.sort_connection_data() + + def draw_connectors(self): + for connector in self.connectors: + pygame.draw.line( + self.screen, + 'gray', + (self.viewpoint.x,self.viewpoint.y) + self.center + connector['start']/self.viewpoint.z, + (self.viewpoint.x,self.viewpoint.y) + self.center + connector['end']/self.viewpoint.z, + width=int(10/self.viewpoint.z) + ) + length = connector['end']/self.viewpoint.z-connector['start']/self.viewpoint.z + snippet = length/(connector['n_slots']+1) + for n in range(connector['n_slots']): + pygame.draw.circle( + self.screen, + 'gray', + (self.viewpoint.x,self.viewpoint.y) + self.center + connector['start']/self.viewpoint.z + snippet*(n+1), + int(10/self.viewpoint.z) + ) + + def draw_stations(self): + width = 30 + height = 30 + font = pygame.font.SysFont(None,int(20/self.viewpoint.z)) + for station in self.stations: + color = 'black' + if not 'mode' in station: + pass + elif station['mode'] == 'working': + color = 'green' + elif station['mode'] == 'waiting': + color = 'yellow' + elif station['mode'] == 'failing': + color = 'red' + elif station['mode'] == 'off': + color = 'gray' + + pygame.draw.rect( + self.screen, + color, + pygame.Rect( + self.viewpoint.x + self.center.x + (station['position'].x-width/2)/self.viewpoint.z, + self.viewpoint.y + self.center.y + (station['position'].y-height/2)/self.viewpoint.z, + width/self.viewpoint.z, + height/self.viewpoint.z + ), + border_radius=int(max(1,8/self.viewpoint.z)) + ) + name_text = font.render(station['name'],True,'black') + self.screen.blit( + name_text, + name_text.get_rect( + center=(self.viewpoint.x,self.viewpoint.y) + self.center + (station['position']+(0,-0.7*height))/self.viewpoint.z + ) + ) + if 'worker_skill' in station or 'magazine' in station: + font = pygame.font.SysFont(None,int(14/self.viewpoint.z)) + if 'worker_skill' in station: + info_text = font.render('W=' + str(station['worker_skill']),False,'black') + else: + info_text = font.render('C=' + str(station['magazine']),False,'black') + self.screen.blit( + info_text, + info_text.get_rect( + center=(self.viewpoint.x,self.viewpoint.y) + self.center + station['position']/self.viewpoint.z + ) + ) + if 'pos_in_out' in station: + pygame.draw.circle( + self.screen, + 'gray', + (self.viewpoint.x,self.viewpoint.y) + self.center + station['position']/self.viewpoint.z, + 6/self.viewpoint.z + ) + for pos in station['pos_in_out']: + pygame.draw.line( + self.screen, + 'gray', + (self.viewpoint.x,self.viewpoint.y) + self.center + station['position']/self.viewpoint.z, + (self.viewpoint.x,self.viewpoint.y) + self.center + pos/self.viewpoint.z, + width=int(5/self.viewpoint.z) + ) + + def draw_carriers(self): + height = 10 + width = 30 + for carrier in self.carriers: + pygame.draw.rect( + self.screen, + 'black', + pygame.Rect( + self.viewpoint.x + self.center.x + (carrier['position'].x-width/2)/self.viewpoint.z, + self.viewpoint.y + self.center.y + (carrier['position'].y-height/2)/self.viewpoint.z, + width/self.viewpoint.z, + height/self.viewpoint.z + ) + ) + pygame.draw.rect( + self.screen, + 'orange', + pygame.Rect( + self.viewpoint.x + self.center.x + (carrier['position'].x-width*0.8/2)/self.viewpoint.z, + self.viewpoint.y + self.center.y + (carrier['position'].y-height*0.8/2)/self.viewpoint.z, + width*0.8*carrier['fill']/self.viewpoint.z, + height*0.8/self.viewpoint.z + ) + ) + if 'name' in carrier: + font = pygame.font.SysFont(None, int(12/self.viewpoint.z)) + text = font.render(carrier['name'],False,'blue') + self.screen.blit( + text, + text.get_rect( + center=(self.viewpoint.x,self.viewpoint.y) + self.center + carrier['position']/self.viewpoint.z + (0,-1.3*height/self.viewpoint.z) + ) + ) + + def draw_user_input(self): + font = pygame.font.SysFont(None,24) + text = font.render("W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out",True,'black') + self.screen.blit(text,text.get_rect(left=200,top=50)) + + def check_user_input(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.teardown() + keys = pygame.key.get_pressed() + if keys[pygame.K_q]: + self.viewpoint.z -= 3*self.dt + if keys[pygame.K_e]: + self.viewpoint.z += 3*self.dt + if keys[pygame.K_w]: + self.viewpoint.y -= 300*self.dt + if keys[pygame.K_s]: + self.viewpoint.y += 300*self.dt + if keys[pygame.K_a]: + self.viewpoint.x -= 300*self.dt + if keys[pygame.K_d]: + self.viewpoint.x += 300*self.dt + self.viewpoint.z = max(0.5,min(10,self.viewpoint.z)) + + def run(self): + + pygame.init() + self.screen = pygame.display.set_mode(self.size) + self.clock = pygame.time.Clock() + self.running = True + while self.running: + if self.stop_event.is_set(): + self.teardown() + + self.check_user_input() + self.check_connection() + self.clear() + self.draw_connectors() + self.draw_stations() + self.draw_carriers() + self.draw_user_input() + + + pygame.display.flip() + + self.dt = self.clock.tick(60)/1000 + + pygame.quit() + self.stop_event.set() + +def start_visualization(connection, stop_event): + visualization = Visualization(connection=connection, stop_event=stop_event) + visualization.run() From fd3fe4ee9056c582cb918a16fd3ae3831cacb5f6 Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Mon, 23 Mar 2026 09:11:13 +0100 Subject: [PATCH 02/23] improved_zoom_behavior --- lineflow/simulation/stations.py | 4 ++-- lineflow/simulation/visualization.py | 36 ++++++++++++++++------------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/lineflow/simulation/stations.py b/lineflow/simulation/stations.py index 3ee53fe..abe9c5a 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -179,8 +179,8 @@ def setup_draw(self): self._height, ) - #font = pygame.font.SysFont(None, 20) - #self._text = font.render(self.name, True, 'black') + font = pygame.font.SysFont(None, 20) + self._text = font.render(self.name, True, 'black') def _add(self, data_list): data = dict(type='station',name=self.name,position=self.position,mode=self.state['mode'].to_str()) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index f579125..411a99d 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -83,6 +83,7 @@ def __init__( if viewpoint is None: viewpoint = (0,0,1) self.viewpoint = pygame.Vector3(viewpoint) + self.view = pygame.Vector2(self.viewpoint.x, self.viewpoint.y) self.connection = connection self.stop_event = stop_event @@ -130,8 +131,8 @@ def draw_connectors(self): pygame.draw.line( self.screen, 'gray', - (self.viewpoint.x,self.viewpoint.y) + self.center + connector['start']/self.viewpoint.z, - (self.viewpoint.x,self.viewpoint.y) + self.center + connector['end']/self.viewpoint.z, + self.center + (self.view + connector['start'])/self.viewpoint.z, + self.center + (self.view + connector['end'])/self.viewpoint.z, width=int(10/self.viewpoint.z) ) length = connector['end']/self.viewpoint.z-connector['start']/self.viewpoint.z @@ -140,7 +141,7 @@ def draw_connectors(self): pygame.draw.circle( self.screen, 'gray', - (self.viewpoint.x,self.viewpoint.y) + self.center + connector['start']/self.viewpoint.z + snippet*(n+1), + self.view/self.viewpoint.z + self.center + connector['start']/self.viewpoint.z + snippet*(n+1), int(10/self.viewpoint.z) ) @@ -165,8 +166,8 @@ def draw_stations(self): self.screen, color, pygame.Rect( - self.viewpoint.x + self.center.x + (station['position'].x-width/2)/self.viewpoint.z, - self.viewpoint.y + self.center.y + (station['position'].y-height/2)/self.viewpoint.z, + self.center.x + (self.viewpoint.x + station['position'].x-width/2)/self.viewpoint.z, + self.center.y + (self.viewpoint.y + station['position'].y-height/2)/self.viewpoint.z, width/self.viewpoint.z, height/self.viewpoint.z ), @@ -176,7 +177,7 @@ def draw_stations(self): self.screen.blit( name_text, name_text.get_rect( - center=(self.viewpoint.x,self.viewpoint.y) + self.center + (station['position']+(0,-0.7*height))/self.viewpoint.z + center=self.center + (self.view + station['position']+(0,-0.7*height))/self.viewpoint.z ) ) if 'worker_skill' in station or 'magazine' in station: @@ -188,22 +189,22 @@ def draw_stations(self): self.screen.blit( info_text, info_text.get_rect( - center=(self.viewpoint.x,self.viewpoint.y) + self.center + station['position']/self.viewpoint.z + center=self.center + (self.view + station['position'])/self.viewpoint.z ) ) if 'pos_in_out' in station: pygame.draw.circle( self.screen, 'gray', - (self.viewpoint.x,self.viewpoint.y) + self.center + station['position']/self.viewpoint.z, + self.center + (self.view + station['position'])/self.viewpoint.z, 6/self.viewpoint.z ) for pos in station['pos_in_out']: pygame.draw.line( self.screen, 'gray', - (self.viewpoint.x,self.viewpoint.y) + self.center + station['position']/self.viewpoint.z, - (self.viewpoint.x,self.viewpoint.y) + self.center + pos/self.viewpoint.z, + self.center + (self.view + station['position'])/self.viewpoint.z, + self.center + (self.view + pos)/self.viewpoint.z, width=int(5/self.viewpoint.z) ) @@ -215,8 +216,8 @@ def draw_carriers(self): self.screen, 'black', pygame.Rect( - self.viewpoint.x + self.center.x + (carrier['position'].x-width/2)/self.viewpoint.z, - self.viewpoint.y + self.center.y + (carrier['position'].y-height/2)/self.viewpoint.z, + self.center.x + (self.viewpoint.x + carrier['position'].x-width/2)/self.viewpoint.z, + self.center.y + (self.viewpoint.y + carrier['position'].y-height/2)/self.viewpoint.z, width/self.viewpoint.z, height/self.viewpoint.z ) @@ -225,8 +226,8 @@ def draw_carriers(self): self.screen, 'orange', pygame.Rect( - self.viewpoint.x + self.center.x + (carrier['position'].x-width*0.8/2)/self.viewpoint.z, - self.viewpoint.y + self.center.y + (carrier['position'].y-height*0.8/2)/self.viewpoint.z, + self.center.x + (self.viewpoint.x + carrier['position'].x-width*0.8/2)/self.viewpoint.z, + self.center.y + (self.viewpoint.y + carrier['position'].y-height*0.8/2)/self.viewpoint.z, width*0.8*carrier['fill']/self.viewpoint.z, height*0.8/self.viewpoint.z ) @@ -237,7 +238,7 @@ def draw_carriers(self): self.screen.blit( text, text.get_rect( - center=(self.viewpoint.x,self.viewpoint.y) + self.center + carrier['position']/self.viewpoint.z + (0,-1.3*height/self.viewpoint.z) + center=self.center + (self.view + carrier['position'])/self.viewpoint.z + (0,-1.3*height/self.viewpoint.z) ) ) @@ -246,6 +247,9 @@ def draw_user_input(self): text = font.render("W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out",True,'black') self.screen.blit(text,text.get_rect(left=200,top=50)) + def draw_cursor(self): + pygame.draw.circle(self.screen,'blue',self.center,10,1) + def check_user_input(self): for event in pygame.event.get(): if event.type == pygame.QUIT: @@ -264,6 +268,7 @@ def check_user_input(self): if keys[pygame.K_d]: self.viewpoint.x += 300*self.dt self.viewpoint.z = max(0.5,min(10,self.viewpoint.z)) + self.view = pygame.Vector2(self.viewpoint.x, self.viewpoint.y) def run(self): @@ -282,6 +287,7 @@ def run(self): self.draw_stations() self.draw_carriers() self.draw_user_input() + self.draw_cursor() pygame.display.flip() From ef4c521a3a34117fec5f727275f7352c87b56af1 Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Mon, 23 Mar 2026 18:47:43 +0100 Subject: [PATCH 03/23] bugfixes_and_more_infos_onscreen --- lineflow/simulation/line.py | 24 ++++++++-- lineflow/simulation/movable_objects.py | 8 +++- lineflow/simulation/stations.py | 4 +- lineflow/simulation/visualization.py | 65 +++++++++++++++++++++++--- 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index a97bb10..680db3c 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -51,10 +51,11 @@ def __init__( self.reset(random_state=random_state) self.stop_event = Event() + self.halt_event = Event() self.connection = Queue() self.visualization_process = Process( target=start_visualization, - args=(self.connection, self.stop_event) + args=(self.connection, self.stop_event, self.halt_event) ) self.data = [] @@ -230,11 +231,14 @@ def _draw_objects_of_type(self, object_type): if isinstance(obj, object_type): obj._draw(self.viewpoint.paper) - def give_data(self): + def give_data(self, actions=None): if not self.stop_event.is_set(): self.data = [] self._add_connectors() self._add_stations() + self._add_info() + if actions is not None: + self._add_actions(actions) self.connection.put(self.data) def setup_connectors(self): @@ -253,6 +257,18 @@ def _add_objects_of_type(self, object_type): if isinstance(obj, object_type): obj._add(self.data) + def _add_actions(self, actions): + self.data.append(dict(type="actions", actions=actions)) + + def _add_info(self): + self.data.append( + dict( + type="info", + time=self.env.now, + n_parts=self.get_n_parts_produced() + ) + ) + def _get_object_positions(self): x = [] y = [] @@ -361,6 +377,8 @@ def run( ) while self.env.now < simulation_end: + if self.halt_event.is_set(): + break pbar.update(self.env.now - now) now = self.env.now try: @@ -374,7 +392,7 @@ def run( self.apply(actions) if visualize: - self.give_data() + self.give_data(actions) #self._draw(actions) #if capture_screen and visualize: diff --git a/lineflow/simulation/movable_objects.py b/lineflow/simulation/movable_objects.py index 52522a4..97b5ced 100644 --- a/lineflow/simulation/movable_objects.py +++ b/lineflow/simulation/movable_objects.py @@ -214,7 +214,13 @@ def _draw_shape(self, screen): ) def _add_shape(self, data_list): - data_list.append(dict(type='carrier',position=self._position,fill=self.capacity/len(self.parts))) + parts = len(self.parts) + if self.capacity is np.inf and parts != 0: + fill = 1 + else: + fill = parts/self.capacity + + data_list.append(dict(type='carrier',position=self._position,fill=fill)) def move(self, position): """ diff --git a/lineflow/simulation/stations.py b/lineflow/simulation/stations.py index 5611cda..0111594 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -222,7 +222,7 @@ def _draw_n_workers(self, screen): def _add_n_workers(self, data_dict): if not self.is_automatic: - data_dict['worker_skill'] = self.worker_skill + data_dict['worker_skill'] = str(self.worker_skill) def _draw_n_carriers(self, screen): font = pygame.font.SysFont(None, 14) @@ -237,7 +237,7 @@ def _draw_n_carriers(self, screen): ) def _add_n_carriers(self, data_dict): - data_dict['magazine'] = self.state['carriers_in_magazine'] + data_dict['magazine'] = self.state['carriers_in_magazine'].to_str() def get_performance_coefficient(self): return compute_performance_coefficient(self.worker_skill) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 411a99d..d0048a3 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -75,6 +75,7 @@ def __init__( points=None, connection=None, stop_event=None, + halt_event=None, ): if size is None: @@ -87,6 +88,7 @@ def __init__( self.connection = connection self.stop_event = stop_event + self.halt_event = halt_event self.center = pygame.Vector2(self.size[0]/2, self.size[1]/2) @@ -94,6 +96,8 @@ def __init__( self.stations = [] self.connectors = [] self.carriers = [] + self.info = None + self.actions = None def teardown(self): self.running = False @@ -114,6 +118,8 @@ def sort_connection_data(self): self.carriers = [] self.connectors = [] self.stations = [] + self.info = None + self.actions = None for item in self.connection_data: if item['type'] == 'carrier': self.carriers.append(item) @@ -121,7 +127,10 @@ def sort_connection_data(self): self.connectors.append(item) elif item['type'] == 'station': self.stations.append(item) - + elif item['type'] == 'info': + self.info = item + elif item['type'] == 'actions': + self.actions = item def check_connection(self): self.get_from_connection() self.sort_connection_data() @@ -242,10 +251,50 @@ def draw_carriers(self): ) ) + def draw_info(self): + if self.info is not None: + font = pygame.font.SysFont(None, 20) + time = font.render( + 'T={:.2f}'.format(self.info['time']), + True, + 'black', + 'white' + ) + n_parts = font.render( + f'#Parts={self.info['n_parts']}', + True, + 'black', + 'white' + ) + self.screen.blit(time, time.get_rect(center=(30, 30))) + self.screen.blit(n_parts, n_parts.get_rect(center=(30, 50))) + + def draw_actions(self): + if self.actions is not None: + font = pygame.font.SysFont(None, 20) + for n, actor_actions in enumerate(self.actions['actions'].items()): + actor = actor_actions[0] + if len(actor_actions[1]) == 1: + actions = "".join(f"{action[0]}={action[1]}" for action in actor_actions[1].items()) + else: + actions = "".join(f"{action[0]}={action[1]}, " for action in actor_actions[1].items()) + text = font.render( + f'{actor}: {actions}', + True, + 'black', + 'white' + ) + self.screen.blit(text, text.get_rect(center=(self.center.x, 30+n*22))) + def draw_user_input(self): font = pygame.font.SysFont(None,24) - text = font.render("W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out",True,'black') - self.screen.blit(text,text.get_rect(left=200,top=50)) + text = font.render( + "W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out, Shift+H: Halt Simulation", + True, + 'black', + 'white' + ) + self.screen.blit(text,text.get_rect(left=50,top=self.size[1]-40)) def draw_cursor(self): pygame.draw.circle(self.screen,'blue',self.center,10,1) @@ -267,6 +316,8 @@ def check_user_input(self): self.viewpoint.x -= 300*self.dt if keys[pygame.K_d]: self.viewpoint.x += 300*self.dt + if keys[pygame.K_h] and keys[pygame.K_LSHIFT]: + self.halt_event.set() self.viewpoint.z = max(0.5,min(10,self.viewpoint.z)) self.view = pygame.Vector2(self.viewpoint.x, self.viewpoint.y) @@ -279,7 +330,7 @@ def run(self): while self.running: if self.stop_event.is_set(): self.teardown() - + self.check_user_input() self.check_connection() self.clear() @@ -287,6 +338,8 @@ def run(self): self.draw_stations() self.draw_carriers() self.draw_user_input() + self.draw_info() + self.draw_actions() self.draw_cursor() @@ -297,6 +350,6 @@ def run(self): pygame.quit() self.stop_event.set() -def start_visualization(connection, stop_event): - visualization = Visualization(connection=connection, stop_event=stop_event) +def start_visualization(connection, stop_event, halt_event): + visualization = Visualization(connection=connection, stop_event=stop_event, halt_event=halt_event) visualization.run() From e696603863b8b758350dfff28b17a7d8826d194c Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Mon, 23 Mar 2026 19:37:04 +0100 Subject: [PATCH 04/23] fix test --- lineflow/simulation/line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index 680db3c..ca6e528 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -359,9 +359,9 @@ def run( visualize (bool): If true, line visualization is opened capture_screen (bool): Captures last Time frame when screen should be recorded """ - self.setup_connectors() if visualize: self.visualization_process.start() + self.setup_connectors() #self.setup_draw() # Register objects when simulation is initially started From ac8fe7d72e4da1fcd07c76c62a3837b9e1c21cb6 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 13:53:25 +0100 Subject: [PATCH 05/23] fix f-string for py3.11 --- lineflow/simulation/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index d0048a3..e881134 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -261,7 +261,7 @@ def draw_info(self): 'white' ) n_parts = font.render( - f'#Parts={self.info['n_parts']}', + f"#Parts={self.info['n_parts']}", True, 'black', 'white' From 947f24c4f431da1bf4f9ff7676339cdfae0214af Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 14:03:40 +0100 Subject: [PATCH 06/23] only create threading objects when needed --- lineflow/simulation/line.py | 75 +++++-------------------------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index ca6e528..0582175 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -49,7 +49,12 @@ def __init__( self._info = info self.reset(random_state=random_state) + self.data = [] + def _init_visualization(self): + """ + Initializes the visualization process and the communication channels. + """ self.stop_event = Event() self.halt_event = Event() self.connection = Queue() @@ -57,7 +62,6 @@ def __init__( target=start_visualization, args=(self.connection, self.stop_event, self.halt_event) ) - self.data = [] @property def name(self): @@ -185,53 +189,7 @@ def _register_objects_at_env(self): for o in self._objects.values(): o.register(self.env) - def _draw(self, actions=None): - - self.viewpoint.check_user_input() - - self.viewpoint.clear() - - # Draw objects, first connectors, then stations - self._draw_connectors() - self._draw_stations() - - self.viewpoint._draw() - - if actions is not None: - self._draw_actions(actions) - - self._draw_info() - - pygame.display.flip() - - def _draw_info(self): - - font = pygame.font.SysFont(None, 20) - - time = font.render('T={:.2f}'.format(self.env.now), True, 'black') - n_parts = font.render( - f'#Parts={self.get_n_parts_produced()}', True, 'black' - ) - self.viewpoint.screen.blit(time, time.get_rect(center=(30, 30))) - self.viewpoint.screen.blit(n_parts, n_parts.get_rect(center=(30, 50))) - - def _draw_actions(self, actions): - font = pygame.font.SysFont(None, 20) - actions = font.render(f'{actions}', True, 'black') - self.viewpoint.screen.blit(actions, actions.get_rect(center=(500, 30))) - - def _draw_stations(self): - self._draw_objects_of_type(Station) - - def _draw_connectors(self): - self._draw_objects_of_type(Connector) - - def _draw_objects_of_type(self, object_type): - for _, obj in self._objects.items(): - if isinstance(obj, object_type): - obj._draw(self.viewpoint.paper) - - def give_data(self, actions=None): + def _send_data_to_visualization(self, actions=None): if not self.stop_event.is_set(): self.data = [] self._add_connectors() @@ -295,15 +253,6 @@ def _adjust_positions(self): x, y = self._get_object_positions() return max(x), max(y) - def setup_draw(self): - pygame.init() - - max_x, max_y = self._adjust_positions() - for o in self._objects.values(): - o.setup_draw() - - self.viewpoint = Viewpoint(size=(max_x+100, max_y+100)) - def apply(self, values): for object_name in values.keys(): self._objects[object_name].apply(values[object_name]) @@ -347,7 +296,6 @@ def run( agent=None, show_status=True, visualize=False, - capture_screen=False, ): """ Args: @@ -360,9 +308,9 @@ def run( capture_screen (bool): Captures last Time frame when screen should be recorded """ if visualize: + self._init_visualization() self.visualization_process.start() self.setup_connectors() - #self.setup_draw() # Register objects when simulation is initially started if len(self.env._queue) == 0: @@ -377,7 +325,7 @@ def run( ) while self.env.now < simulation_end: - if self.halt_event.is_set(): + if visualize and self.halt_event.is_set(): break pbar.update(self.env.now - now) now = self.env.now @@ -392,14 +340,9 @@ def run( self.apply(actions) if visualize: - self.give_data(actions) - #self._draw(actions) - - #if capture_screen and visualize: - # pygame.image.save(self.viewpoint.screen, f"{self.name}.png") + self._send_data_to_visualization(actions) if visualize: - #self.viewpoint.teardown() self.stop_event.set() self.visualization_process.join() From a8354256da64608a541d8d5fd782711275be6602 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 14:08:07 +0100 Subject: [PATCH 07/23] remove capture_screen from API --- lineflow/examples/carrier_magazin.py | 2 +- lineflow/examples/complex_line.py | 2 +- lineflow/examples/component_assembly.py | 4 ++-- lineflow/examples/multi_sink.py | 2 +- lineflow/examples/showcase_line.py | 2 +- lineflow/examples/simple_line.py | 2 +- lineflow/examples/worker_assignment.py | 2 +- lineflow/simulation/line.py | 4 ++-- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lineflow/examples/carrier_magazin.py b/lineflow/examples/carrier_magazin.py index e2b79da..10fba0f 100644 --- a/lineflow/examples/carrier_magazin.py +++ b/lineflow/examples/carrier_magazin.py @@ -54,4 +54,4 @@ def build(self): if __name__ == '__main__': line = SimplestLineWithReturnForPartCarriers() - line.run(simulation_end=200, visualize=True, capture_screen=True) + line.run(simulation_end=200, visualize=True) diff --git a/lineflow/examples/complex_line.py b/lineflow/examples/complex_line.py index 6b2cfe8..27bef49 100644 --- a/lineflow/examples/complex_line.py +++ b/lineflow/examples/complex_line.py @@ -265,7 +265,7 @@ def build(self): get_max_reward=False ) - line.run(simulation_end=4000, agent=agent, capture_screen=False, show_status=True, visualize=False) + line.run(simulation_end=4000, agent=agent, show_status=True, visualize=False) print("Produced: ", line.get_n_parts_produced()) print("Scrap: ", line.get_n_scrap_parts()) print("Reward: ", line.get_n_parts_produced() - line.get_n_scrap_parts()*scrap_factor) diff --git a/lineflow/examples/component_assembly.py b/lineflow/examples/component_assembly.py index 089dee6..c9ded1a 100644 --- a/lineflow/examples/component_assembly.py +++ b/lineflow/examples/component_assembly.py @@ -149,5 +149,5 @@ def build(self): if __name__ == '__main__': - line = ComponentAssembly(realtime=True, factor=0.01) - line.run(simulation_end=1000, visualize=True, capture_screen=True) + line = ComponentAssembly(realtime=True, factor=0.5) + line.run(simulation_end=10_000, visualize=True) diff --git a/lineflow/examples/multi_sink.py b/lineflow/examples/multi_sink.py index 564f0e8..600ff17 100644 --- a/lineflow/examples/multi_sink.py +++ b/lineflow/examples/multi_sink.py @@ -62,5 +62,5 @@ def build(self): if __name__ == '__main__': line = MultiSink(realtime=False, n_sinks=5, alternate=False) agent = make_greedy_policy(5) - line.run(simulation_end=200, agent=agent, visualize=True, capture_screen=False) + line.run(simulation_end=200, agent=agent, visualize=True) print('Produced parts: ', line.get_n_parts_produced()) diff --git a/lineflow/examples/showcase_line.py b/lineflow/examples/showcase_line.py index a93057d..b11096a 100644 --- a/lineflow/examples/showcase_line.py +++ b/lineflow/examples/showcase_line.py @@ -87,4 +87,4 @@ def build(self): if __name__ == '__main__': line = ShowCase(realtime=True, factor=0.1) - line.run(simulation_end=150, visualize=True, capture_screen=True) + line.run(simulation_end=150, visualize=True) diff --git a/lineflow/examples/simple_line.py b/lineflow/examples/simple_line.py index ef8413f..bc2a2ae 100644 --- a/lineflow/examples/simple_line.py +++ b/lineflow/examples/simple_line.py @@ -40,4 +40,4 @@ def build(self): if __name__ == '__main__': line = SimpleLine() - line.run(simulation_end=3, visualize=True, capture_screen=True) + line.run(simulation_end=3_000, visualize=True) diff --git a/lineflow/examples/worker_assignment.py b/lineflow/examples/worker_assignment.py index b62313a..98943b5 100644 --- a/lineflow/examples/worker_assignment.py +++ b/lineflow/examples/worker_assignment.py @@ -113,4 +113,4 @@ def build(self): line = WorkerAssignment(with_rework=True, realtime=False, n_assemblies=7, step_size=2) agent = make_random_agent(7) - line.run(simulation_end=1000, agent=agent, visualize=True, capture_screen=False) + line.run(simulation_end=2_000, agent=agent, visualize=True) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index 0582175..31551c5 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -13,7 +13,7 @@ Station, Sink, ) -from lineflow.simulation.visualization import start_visualization, Viewpoint +from lineflow.simulation.visualization import start_visualization logger = logging.getLogger(__name__) @@ -305,7 +305,6 @@ def run( class. show_status (bool): Show progress bar for each simulation episode visualize (bool): If true, line visualization is opened - capture_screen (bool): Captures last Time frame when screen should be recorded """ if visualize: self._init_visualization() @@ -327,6 +326,7 @@ def run( while self.env.now < simulation_end: if visualize and self.halt_event.is_set(): break + pbar.update(self.env.now - now) now = self.env.now try: From e3dac59119b56bf4cbb1035ca6295c9dad2b7008 Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Tue, 24 Mar 2026 15:23:40 +0100 Subject: [PATCH 08/23] implementation_in_environment --- lineflow/simulation/environment.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lineflow/simulation/environment.py b/lineflow/simulation/environment.py index ceccdbf..f9ede70 100644 --- a/lineflow/simulation/environment.py +++ b/lineflow/simulation/environment.py @@ -192,7 +192,9 @@ def reset(self, seed=None, options=None): observation = self._get_observations_as_tensor(state) if self.render_mode == "human": - self.line.setup_draw() + self.line._init_visualization() + self.line.visualization_process.start() + self.line.setup_connectors() self.render() return observation, self._get_info() @@ -201,11 +203,11 @@ def features(self): return self.line.state.observable_features def render(self): - self.line._draw() + self.line._send_data_to_visualization() def close(self): if self.render_mode == 'human': - self.line.viewpoint.teardown() + self.line.stop_event.set() def _get_observations_as_tensor(self, state): From e100862e5854e2d73e9094706320aa6c1b32572b Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Tue, 24 Mar 2026 16:01:33 +0100 Subject: [PATCH 09/23] remove_redundant_draw --- lineflow/simulation/connectors.py | 24 +-------- lineflow/simulation/line.py | 2 +- lineflow/simulation/movable_objects.py | 34 ------------- lineflow/simulation/stations.py | 68 -------------------------- 4 files changed, 2 insertions(+), 126 deletions(-) diff --git a/lineflow/simulation/connectors.py b/lineflow/simulation/connectors.py index 5c1b7ba..c0c16c0 100644 --- a/lineflow/simulation/connectors.py +++ b/lineflow/simulation/connectors.py @@ -77,7 +77,7 @@ def init_state(self): def n_carriers(self): return len(self.carriers) - def setup_draw(self): + def setup_positions(self): vec_direction = np.array( [ @@ -114,28 +114,6 @@ def setup_draw(self): self._positions_arrow[i] = arrowhead - def _draw(self, screen): - - pygame.draw.line( - screen, - self.color, - self._position_input, - self._position_output, - width=10, - ) - - # Draw slots - for i, slot in enumerate(self._positions_slots): - pygame.draw.circle(screen, 'gray', slot, 10) - - # Draw arrowheads - for i, arrow in enumerate(self._positions_arrow[:-1]): - pygame.draw.polygon(screen, 'black', arrow) - - # Draw carriers - for carrier in self.carriers.values(): - carrier.draw(screen) - def _add(self, data_list): data_list.append(dict(type='connector',start=self._position_input,end=self._position_output,n_slots=self.capacity)) for carrier in self.carriers.values(): diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index 31551c5..f568107 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -202,7 +202,7 @@ def _send_data_to_visualization(self, actions=None): def setup_connectors(self): for _, obj in self._objects.items(): if isinstance(obj, Connector): - obj.setup_draw() + obj.setup_positions() def _add_stations(self): self._add_objects_of_type(Station) diff --git a/lineflow/simulation/movable_objects.py b/lineflow/simulation/movable_objects.py index 97b5ced..a560aee 100644 --- a/lineflow/simulation/movable_objects.py +++ b/lineflow/simulation/movable_objects.py @@ -29,22 +29,11 @@ def name(self): def creation_time(self): return self['creation_time'] - def draw(self, screen, with_text=True): - - self._draw_shape(screen) - if with_text: - font = pygame.font.SysFont(None, 12) - text = font.render(self.name, True, 'blue') - screen.blit(text, text.get_rect(center=self._position + (0, -1.3*self._height))) - def _add(self, data_list, with_text=True): self._add_shape(data_list) if with_text: data_list[-1]['name'] = self.name - def _draw_shape(self, screen): - raise NotImplementedError() - def _add_shape(self, data_list): raise NotImplementedError() @@ -150,10 +139,6 @@ def create(self, position): raise ValueError('Expect pygame vector as position') self.move(position) - def _draw(self, screen, x, y, width, height): - _part_rect = pygame.Rect(x, y, width, height) - pygame.draw.rect(screen, self._color, _part_rect, border_radius=1) - def get_processing_time(self, station): return self.specs.get(station, {}).get("extra_processing_time", 0) @@ -194,25 +179,6 @@ def assemble(self, part): self.parts[part.name] = part - def _draw_shape(self, screen): - - self._rect = pygame.Rect( - self._position.x - self._width / 2, - self._position.y - self._height / 2, - self._width, - self._height, - ) - pygame.draw.rect(screen, self._color, self._rect, border_radius=2) - - for i, part in enumerate(self): - part._draw( - screen, - x=self._position.x+0.1*self._width - self._width / 2 + i*(self._width_part), - y=self._position.y - self._height_part / 2, - width=self._width_part, - height=self._height_part, - ) - def _add_shape(self, data_list): parts = len(self.parts) if self.capacity is np.inf and parts != 0: diff --git a/lineflow/simulation/stations.py b/lineflow/simulation/stations.py index 0111594..f969b0c 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -170,34 +170,11 @@ def worker_skill(self): else: return 1.0 - def setup_draw(self): - - self._rect = pygame.Rect( - self.position.x - self._width / 2, - self.position.y - self._height / 2, - self._width, - self._height, - ) - - font = pygame.font.SysFont(None, 20) - self._text = font.render(self.name, True, 'black') - def _add(self, data_list): data = dict(type='station',name=self.name,position=self.position,mode=self.state['mode'].to_str()) self._add_info(data) data_list.append(data) - def _draw(self, screen): - pygame.draw.rect(screen, self._color, self._rect, border_radius=5) - self._draw_info(screen) - screen.blit( - self._text, - self._text.get_rect(center=self.position + (0, -0.6 * self._height)), - ) - - def _draw_info(self, screen): - pass - def _add_info(self, data_dict): pass @@ -207,35 +184,10 @@ def _is_nok_part(self, component): p=[component.nok_probability, 1 - component.nok_probability], ) - def _draw_n_workers(self, screen): - if not self.is_automatic: - font = pygame.font.SysFont(None, 14) - text = font.render( - "W=" + str(self.worker_skill), - True, - 'black', - ) - screen.blit( - text, - text.get_rect(center=self.position), - ) - def _add_n_workers(self, data_dict): if not self.is_automatic: data_dict['worker_skill'] = str(self.worker_skill) - def _draw_n_carriers(self, screen): - font = pygame.font.SysFont(None, 14) - text = font.render( - "C=" + self.state['carriers_in_magazine'].to_str(), - True, - 'black', - ) - screen.blit( - text, - text.get_rect(center=self.position), - ) - def _add_n_carriers(self, data_dict): data_dict['magazine'] = self.state['carriers_in_magazine'].to_str() @@ -459,9 +411,6 @@ def _has_invalid_components_on_carrier(self, carrier): return True return False - def _draw_info(self, screen): - self._draw_n_workers(screen) - def _add_info(self, data_dict): self._add_n_workers(data_dict) @@ -600,9 +549,6 @@ def init_state(self): self.state['processing_time'].update(self.processing_time) self.state['n_workers'].update(self.n_workers) - def _draw_info(self, screen): - self._draw_n_workers(screen) - def _add_info(self, data_dict): self._add_n_workers(data_dict) @@ -1204,17 +1150,6 @@ def _get_buffer_out_position(self): self.state['index_buffer_out'].value ].__self__._positions_slots[0] - def _draw_info(self, screen): - pos_buffer_in = self._get_buffer_in_position() - pos_buffer_out = self._get_buffer_out_position() - - pos_in = pos_buffer_in + 0.5*(self.position - pos_buffer_in) - pos_out = pos_buffer_out + 0.5*(self.position - pos_buffer_out) - - pygame.draw.circle(screen, 'gray', self.position, 6) - for pos in [pos_in, pos_out]: - pygame.draw.line(screen, "gray", self.position, pos, width=5) - def _add_info(self, data_dict): pos_buffer_in = self._get_buffer_in_position() pos_buffer_out = self._get_buffer_out_position() @@ -1446,9 +1381,6 @@ def _update_magazine(self): for i in range(abs(diff)): carrier = yield self.magazine.get() - def _draw_info(self, screen): - self._draw_n_carriers(screen) - def _add_info(self, data_dict): self._add_n_carriers(data_dict) From 649776af61e5a0f7760cb1bd6b4c02a41a16ed26 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 19:45:17 +0100 Subject: [PATCH 10/23] allow arrow keys --- lineflow/simulation/visualization.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index e881134..135e99c 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -304,18 +304,18 @@ def check_user_input(self): if event.type == pygame.QUIT: self.teardown() keys = pygame.key.get_pressed() - if keys[pygame.K_q]: + if keys[pygame.K_q] or keys[pygame.K_PLUS]: self.viewpoint.z -= 3*self.dt - if keys[pygame.K_e]: + if keys[pygame.K_e] or keys[pygame.K_MINUS]: self.viewpoint.z += 3*self.dt - if keys[pygame.K_w]: - self.viewpoint.y -= 300*self.dt - if keys[pygame.K_s]: + if keys[pygame.K_w] or keys[pygame.K_UP]: self.viewpoint.y += 300*self.dt - if keys[pygame.K_a]: - self.viewpoint.x -= 300*self.dt - if keys[pygame.K_d]: + if keys[pygame.K_s] or keys[pygame.K_DOWN]: + self.viewpoint.y -= 300*self.dt + if keys[pygame.K_a] or keys[pygame.K_LEFT]: self.viewpoint.x += 300*self.dt + if keys[pygame.K_d] or keys[pygame.K_RIGHT]: + self.viewpoint.x -= 300*self.dt if keys[pygame.K_h] and keys[pygame.K_LSHIFT]: self.halt_event.set() self.viewpoint.z = max(0.5,min(10,self.viewpoint.z)) From c405536da4b19d8e8d37b769427b02aec5634bcb Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 19:54:02 +0100 Subject: [PATCH 11/23] clean up --- lineflow/simulation/connectors.py | 3 +++ lineflow/simulation/line.py | 11 ++++---- lineflow/simulation/visualization.py | 40 +++------------------------- 3 files changed, 12 insertions(+), 42 deletions(-) diff --git a/lineflow/simulation/connectors.py b/lineflow/simulation/connectors.py index c0c16c0..9184b2c 100644 --- a/lineflow/simulation/connectors.py +++ b/lineflow/simulation/connectors.py @@ -28,6 +28,9 @@ def connect_to_output(self, station): self._position_output = station.position return self.get + def setup_positions(self): + raise NotImplementedError() + class Buffer(Connector): """ diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index f568107..dd89f45 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -63,6 +63,11 @@ def _init_visualization(self): args=(self.connection, self.stop_event, self.halt_event) ) + for _, obj in self._objects.items(): + if isinstance(obj, Connector): + obj.setup_positions() + + @property def name(self): return self.__class__.__name__ @@ -199,11 +204,6 @@ def _send_data_to_visualization(self, actions=None): self._add_actions(actions) self.connection.put(self.data) - def setup_connectors(self): - for _, obj in self._objects.items(): - if isinstance(obj, Connector): - obj.setup_positions() - def _add_stations(self): self._add_objects_of_type(Station) @@ -309,7 +309,6 @@ def run( if visualize: self._init_visualization() self.visualization_process.start() - self.setup_connectors() # Register objects when simulation is initially started if len(self.env._queue) == 0: diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 135e99c..a18737c 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -26,33 +26,6 @@ def __init__( self._view = pygame.Vector3(position[0], position[1], zoom) - def check_user_input(self): - - if pygame.key.get_pressed()[pygame.K_PLUS]: - self._view.z += 0.1 - - if pygame.key.get_pressed()[pygame.K_MINUS]: - self._view.z -=0.1 - - if pygame.key.get_pressed()[pygame.K_UP]: - self._view.y += 10 - - if pygame.key.get_pressed()[pygame.K_DOWN]: - self._view.y -= 10 - - if pygame.key.get_pressed()[pygame.K_LEFT]: - self._view.x += 10 - - if pygame.key.get_pressed()[pygame.K_RIGHT]: - self._view.x -= 10 - - for event in pygame.event.get(): - if event.type == pygame.QUIT: - self.teardown() - - self._view.z = max(self._view.z, 0.1) - self._view.z = min(self._view.z, 5) - def clear(self): self.screen.fill('white') self.paper.fill('white') @@ -72,17 +45,16 @@ def __init__( self, size=None, viewpoint=None, - points=None, connection=None, stop_event=None, halt_event=None, ): if size is None: - size = (1280,720) + size = (1280, 720) self.size = size if viewpoint is None: - viewpoint = (0,0,1) + viewpoint = (0, 0, 1) self.viewpoint = pygame.Vector3(viewpoint) self.view = pygame.Vector2(self.viewpoint.x, self.viewpoint.y) @@ -92,7 +64,6 @@ def __init__( self.center = pygame.Vector2(self.size[0]/2, self.size[1]/2) - self.connection_data = [] self.stations = [] self.connectors = [] self.carriers = [] @@ -106,8 +77,6 @@ def clear(self): self.screen.fill('white') def get_from_connection(self): - #if not self.connection.empty(): - # self.connection_data = self.connection.get() while True: try: self.connection_data = self.connection.get_nowait() @@ -304,9 +273,9 @@ def check_user_input(self): if event.type == pygame.QUIT: self.teardown() keys = pygame.key.get_pressed() - if keys[pygame.K_q] or keys[pygame.K_PLUS]: + if keys[pygame.K_q]: self.viewpoint.z -= 3*self.dt - if keys[pygame.K_e] or keys[pygame.K_MINUS]: + if keys[pygame.K_e]: self.viewpoint.z += 3*self.dt if keys[pygame.K_w] or keys[pygame.K_UP]: self.viewpoint.y += 300*self.dt @@ -342,7 +311,6 @@ def run(self): self.draw_actions() self.draw_cursor() - pygame.display.flip() self.dt = self.clock.tick(60)/1000 From 249c553b055845c0617ea18b0ecb6ec1398d12a0 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 20:22:31 +0100 Subject: [PATCH 12/23] refactor handling of visualization data --- lineflow/examples/worker_assignment.py | 4 +- lineflow/simulation/connectors.py | 17 +++++- lineflow/simulation/line.py | 79 +++++++------------------- lineflow/simulation/movable_objects.py | 15 +++-- lineflow/simulation/stations.py | 50 ++++++++-------- 5 files changed, 69 insertions(+), 96 deletions(-) diff --git a/lineflow/examples/worker_assignment.py b/lineflow/examples/worker_assignment.py index 98943b5..5d3ba91 100644 --- a/lineflow/examples/worker_assignment.py +++ b/lineflow/examples/worker_assignment.py @@ -110,7 +110,7 @@ def build(self): if __name__ == '__main__': - line = WorkerAssignment(with_rework=True, realtime=False, n_assemblies=7, step_size=2) + line = WorkerAssignment(with_rework=True, realtime=True, n_assemblies=7, step_size=2, factor=0.2) agent = make_random_agent(7) - line.run(simulation_end=2_000, agent=agent, visualize=True) + line.run(simulation_end=10_000, agent=agent, visualize=True) diff --git a/lineflow/simulation/connectors.py b/lineflow/simulation/connectors.py index 9184b2c..871a689 100644 --- a/lineflow/simulation/connectors.py +++ b/lineflow/simulation/connectors.py @@ -117,10 +117,21 @@ def setup_positions(self): self._positions_arrow[i] = arrowhead - def _add(self, data_list): - data_list.append(dict(type='connector',start=self._position_input,end=self._position_output,n_slots=self.capacity)) + def get_visualization_data(self): + data = [] + data.append( + dict( + type='connector', + start=self._position_input, + end=self._position_output, + n_slots=self.capacity, + ) + ) + for carrier in self.carriers.values(): - carrier._add(data_list) + data.append(carrier.get_visualization_data(with_text=True)) + + return data def _sample_put_time(self): return self.put_time + self.random.exponential(scale=self.put_std) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index dd89f45..84b095d 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -67,6 +67,12 @@ def _init_visualization(self): if isinstance(obj, Connector): obj.setup_positions() + self.visualization_process.start() + + def _close_visualization(self): + self.stop_event.set() + self.visualization_process.join() + @property def name(self): @@ -196,62 +202,25 @@ def _register_objects_at_env(self): def _send_data_to_visualization(self, actions=None): if not self.stop_event.is_set(): - self.data = [] - self._add_connectors() - self._add_stations() - self._add_info() - if actions is not None: - self._add_actions(actions) - self.connection.put(self.data) + data = [] - def _add_stations(self): - self._add_objects_of_type(Station) + for _, obj in self._objects.items(): + if isinstance(obj, Station): + data.append(obj.get_visualization_data()) - def _add_connectors(self): - self._add_objects_of_type(Connector) + if isinstance(obj, Connector): + data.extend(obj.get_visualization_data()) - def _add_objects_of_type(self, object_type): - for _, obj in self._objects.items(): - if isinstance(obj, object_type): - obj._add(self.data) - - def _add_actions(self, actions): - self.data.append(dict(type="actions", actions=actions)) - - def _add_info(self): - self.data.append( - dict( - type="info", - time=self.env.now, - n_parts=self.get_n_parts_produced() + data.append( + dict( + type="info", + time=self.env.now, + n_parts=self.get_n_parts_produced() + ) ) - ) - - def _get_object_positions(self): - x = [] - y = [] - for o in self._objects.values(): - if hasattr(o, "position"): - x.append(o.position[0]) - y.append(o.position[1]) - return x, y - - def _adjust_positions(self): - x, y = self._get_object_positions() - - if min(x) < 100: - delta_x = 100 - min(x) - for o in self._objects.values(): - if hasattr(o, "position"): - o.position[0] += delta_x - if min(y) < 100: - delta_y = 100 - min(y) - for o in self._objects.values(): - if hasattr(o, "position"): - o.position[1] += delta_y - - x, y = self._get_object_positions() - return max(x), max(y) + if actions is not None: + data.append(dict(type="actions", actions=actions)) + self.connection.put(data) def apply(self, values): for object_name in values.keys(): @@ -308,7 +277,6 @@ def run( """ if visualize: self._init_visualization() - self.visualization_process.start() # Register objects when simulation is initially started if len(self.env._queue) == 0: @@ -342,12 +310,9 @@ def run( self._send_data_to_visualization(actions) if visualize: - self.stop_event.set() - self.visualization_process.join() + self._close_visualization() def get_observations(self, object_name=None): - """ - """ df = self.state.df() diff --git a/lineflow/simulation/movable_objects.py b/lineflow/simulation/movable_objects.py index a560aee..db6b8f4 100644 --- a/lineflow/simulation/movable_objects.py +++ b/lineflow/simulation/movable_objects.py @@ -29,12 +29,7 @@ def name(self): def creation_time(self): return self['creation_time'] - def _add(self, data_list, with_text=True): - self._add_shape(data_list) - if with_text: - data_list[-1]['name'] = self.name - - def _add_shape(self, data_list): + def get_visualization_data(self): raise NotImplementedError() def move(self, position): @@ -179,14 +174,18 @@ def assemble(self, part): self.parts[part.name] = part - def _add_shape(self, data_list): + def get_visualization_data(self, with_text=True): parts = len(self.parts) if self.capacity is np.inf and parts != 0: fill = 1 else: fill = parts/self.capacity - data_list.append(dict(type='carrier',position=self._position,fill=fill)) + data = dict(type='carrier', position=self._position, fill=fill) + + if with_text: + data['name'] = self.name + return data def move(self, position): """ diff --git a/lineflow/simulation/stations.py b/lineflow/simulation/stations.py index f969b0c..82c8510 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -170,13 +170,20 @@ def worker_skill(self): else: return 1.0 - def _add(self, data_list): - data = dict(type='station',name=self.name,position=self.position,mode=self.state['mode'].to_str()) - self._add_info(data) - data_list.append(data) + def get_visualization_data(self): + data = dict( + type='station', + name=self.name, + position=self.position, + mode=self.state['mode'].to_str() + ) + data = self._add_visualization_info(data) + return data - def _add_info(self, data_dict): - pass + def _add_visualization_info(self, data): + if not self.is_automatic: + data['worker_skill'] = str(self.worker_skill) + return data def _is_nok_part(self, component): return self.random.choice( @@ -184,13 +191,6 @@ def _is_nok_part(self, component): p=[component.nok_probability, 1 - component.nok_probability], ) - def _add_n_workers(self, data_dict): - if not self.is_automatic: - data_dict['worker_skill'] = str(self.worker_skill) - - def _add_n_carriers(self, data_dict): - data_dict['magazine'] = self.state['carriers_in_magazine'].to_str() - def get_performance_coefficient(self): return compute_performance_coefficient(self.worker_skill) @@ -411,9 +411,6 @@ def _has_invalid_components_on_carrier(self, carrier): return True return False - def _add_info(self, data_dict): - self._add_n_workers(data_dict) - def run(self): while True: @@ -549,9 +546,6 @@ def init_state(self): self.state['processing_time'].update(self.processing_time) self.state['n_workers'].update(self.n_workers) - def _add_info(self, data_dict): - self._add_n_workers(data_dict) - def run(self): while True: @@ -1150,14 +1144,16 @@ def _get_buffer_out_position(self): self.state['index_buffer_out'].value ].__self__._positions_slots[0] - def _add_info(self, data_dict): + def _add_visualization_info(self, data): + data = super()._add_visualization_info(data) pos_buffer_in = self._get_buffer_in_position() pos_buffer_out = self._get_buffer_out_position() pos_in = pos_buffer_in + 0.5*(self.position - pos_buffer_in) pos_out = pos_buffer_out + 0.5*(self.position - pos_buffer_out) - data_dict['pos_in_out'] = [pos_in,pos_out] + data['pos_in_out'] = [pos_in,pos_out] + return data def _connect_to_input(self, buffer): self.buffer_in.append(buffer.connect_to_output(self)) @@ -1366,9 +1362,9 @@ def add_carrier_to_magazine(self, carrier): self.state['carriers_in_magazine'].increment() def _update_magazine(self): - ''' + """ update the magazine according to state - ''' + """ should = self.state['carriers_in_magazine'].value current = len(self.magazine.items) diff = should - current @@ -1378,11 +1374,13 @@ def _update_magazine(self): self.magazine.put(carrier) if diff < 0: - for i in range(abs(diff)): + for _ in range(abs(diff)): carrier = yield self.magazine.get() - def _add_info(self, data_dict): - self._add_n_carriers(data_dict) + def _add_visualization_info(self, data): + data = super()._add_visualization_info(data) + data['magazine'] = self.state['carriers_in_magazine'].to_str() + return data def get_carrier(self): # First check if Magazine is allowed to create unlimited carriers From e53d9892c26365a0999fdf90a776a642650929aa Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 20:32:01 +0100 Subject: [PATCH 13/23] fix interface to gym env --- lineflow/simulation/environment.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lineflow/simulation/environment.py b/lineflow/simulation/environment.py index f9ede70..bf67a96 100644 --- a/lineflow/simulation/environment.py +++ b/lineflow/simulation/environment.py @@ -193,8 +193,6 @@ def reset(self, seed=None, options=None): if self.render_mode == "human": self.line._init_visualization() - self.line.visualization_process.start() - self.line.setup_connectors() self.render() return observation, self._get_info() @@ -207,7 +205,7 @@ def render(self): def close(self): if self.render_mode == 'human': - self.line.stop_event.set() + self.line._close_visualization() def _get_observations_as_tensor(self, state): From 3c91b35ca15fc1dd570413f8320871035288b18c Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 20:37:01 +0100 Subject: [PATCH 14/23] tiny --- lineflow/simulation/environment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lineflow/simulation/environment.py b/lineflow/simulation/environment.py index bf67a96..a7ae9dc 100644 --- a/lineflow/simulation/environment.py +++ b/lineflow/simulation/environment.py @@ -208,6 +208,5 @@ def close(self): self.line._close_visualization() def _get_observations_as_tensor(self, state): - X = state.get_observations(lookback=1, include_time=False) return np.array(X, dtype=np.float32) From bcb8e624f2e860f4f12f18c04019c9fe40650c8b Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 20:48:01 +0100 Subject: [PATCH 15/23] allow multiple subsequent visualizations --- lineflow/simulation/connectors.py | 5 ++--- lineflow/simulation/environment.py | 1 + lineflow/simulation/line.py | 5 +++-- lineflow/simulation/visualization.py | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lineflow/simulation/connectors.py b/lineflow/simulation/connectors.py index 871a689..afb4073 100644 --- a/lineflow/simulation/connectors.py +++ b/lineflow/simulation/connectors.py @@ -118,15 +118,14 @@ def setup_positions(self): self._positions_arrow[i] = arrowhead def get_visualization_data(self): - data = [] - data.append( + data = [ dict( type='connector', start=self._position_input, end=self._position_output, n_slots=self.capacity, ) - ) + ] for carrier in self.carriers.values(): data.append(carrier.get_visualization_data(with_text=True)) diff --git a/lineflow/simulation/environment.py b/lineflow/simulation/environment.py index a7ae9dc..c2a2a0f 100644 --- a/lineflow/simulation/environment.py +++ b/lineflow/simulation/environment.py @@ -192,6 +192,7 @@ def reset(self, seed=None, options=None): observation = self._get_observations_as_tensor(state) if self.render_mode == "human": + self.line._close_visualization() self.line._init_visualization() self.render() return observation, self._get_info() diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index 84b095d..a376fa2 100644 --- a/lineflow/simulation/line.py +++ b/lineflow/simulation/line.py @@ -70,8 +70,9 @@ def _init_visualization(self): self.visualization_process.start() def _close_visualization(self): - self.stop_event.set() - self.visualization_process.join() + if hasattr(self, 'visualization_process'): + self.stop_event.set() + self.visualization_process.join() @property diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index a18737c..1e8ed30 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -69,6 +69,7 @@ def __init__( self.carriers = [] self.info = None self.actions = None + self.connection_data = [] def teardown(self): self.running = False From f554f5c74f559df5035befa4d981353b1ee83dd0 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 21:01:30 +0100 Subject: [PATCH 16/23] tiny clean ups --- lineflow/simulation/movable_objects.py | 2 +- lineflow/simulation/stations.py | 2 +- lineflow/simulation/visualization.py | 17 ++++++----------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/lineflow/simulation/movable_objects.py b/lineflow/simulation/movable_objects.py index db6b8f4..2335d5e 100644 --- a/lineflow/simulation/movable_objects.py +++ b/lineflow/simulation/movable_objects.py @@ -176,7 +176,7 @@ def assemble(self, part): def get_visualization_data(self, with_text=True): parts = len(self.parts) - if self.capacity is np.inf and parts != 0: + if np.isinf(self.capacity) and parts != 0: fill = 1 else: fill = parts/self.capacity diff --git a/lineflow/simulation/stations.py b/lineflow/simulation/stations.py index 82c8510..1758705 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -1152,7 +1152,7 @@ def _add_visualization_info(self, data): pos_in = pos_buffer_in + 0.5*(self.position - pos_buffer_in) pos_out = pos_buffer_out + 0.5*(self.position - pos_buffer_out) - data['pos_in_out'] = [pos_in,pos_out] + data['pos_in_out'] = [pos_in, pos_out] return data def _connect_to_input(self, buffer): diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 1e8ed30..51a0249 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -13,12 +13,9 @@ def __init__( zoom=1, ): - if size is None: size = (1410, 1000) self.paper = pygame.Surface(size) - - self.screen = pygame.display.set_mode((1280, 720)) if position is None: @@ -48,7 +45,7 @@ def __init__( connection=None, stop_event=None, halt_event=None, - ): + ): if size is None: size = (1280, 720) @@ -71,9 +68,6 @@ def __init__( self.actions = None self.connection_data = [] - def teardown(self): - self.running = False - def clear(self): self.screen.fill('white') @@ -101,6 +95,7 @@ def sort_connection_data(self): self.info = item elif item['type'] == 'actions': self.actions = item + def check_connection(self): self.get_from_connection() self.sort_connection_data() @@ -257,7 +252,7 @@ def draw_actions(self): self.screen.blit(text, text.get_rect(center=(self.center.x, 30+n*22))) def draw_user_input(self): - font = pygame.font.SysFont(None,24) + font = pygame.font.SysFont(None, 24) text = font.render( "W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out, Shift+H: Halt Simulation", True, @@ -296,10 +291,10 @@ def run(self): pygame.init() self.screen = pygame.display.set_mode(self.size) self.clock = pygame.time.Clock() - self.running = True - while self.running: + + while True: if self.stop_event.is_set(): - self.teardown() + break self.check_user_input() self.check_connection() From 096807a625b467e17dcd1903810a4053d7037b9f Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Tue, 24 Mar 2026 21:02:24 +0100 Subject: [PATCH 17/23] fix raise --- lineflow/simulation/visualization.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 51a0249..6d9051b 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -267,7 +267,8 @@ def draw_cursor(self): def check_user_input(self): for event in pygame.event.get(): if event.type == pygame.QUIT: - self.teardown() + raise StopIteration() + keys = pygame.key.get_pressed() if keys[pygame.K_q]: self.viewpoint.z -= 3*self.dt From 3513435bfb4eb73cb8c563e9ac01283a563afe1a Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Wed, 25 Mar 2026 09:10:06 +0100 Subject: [PATCH 18/23] rename --- lineflow/simulation/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 6d9051b..d0159af 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -254,7 +254,7 @@ def draw_actions(self): def draw_user_input(self): font = pygame.font.SysFont(None, 24) text = font.render( - "W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out, Shift+H: Halt Simulation", + "W: up, S: down, A: left, D: right, Q: zoom in, E: zoom out, Shift+H: Exit", True, 'black', 'white' From 07a4c097947afe9464d4c664cbcfd8affe0c0196 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Wed, 25 Mar 2026 09:11:19 +0100 Subject: [PATCH 19/23] tiny --- lineflow/simulation/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index d0159af..bd46403 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -262,7 +262,7 @@ def draw_user_input(self): self.screen.blit(text,text.get_rect(left=50,top=self.size[1]-40)) def draw_cursor(self): - pygame.draw.circle(self.screen,'blue',self.center,10,1) + pygame.draw.circle(self.screen, 'blue', self.center, 10, 1) def check_user_input(self): for event in pygame.event.get(): From 985cf76dfad44bdefd9af36f7afcf61672eefc61 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Wed, 25 Mar 2026 09:19:19 +0100 Subject: [PATCH 20/23] improve stop iteration --- lineflow/simulation/visualization.py | 46 +++++++++++++++------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index bd46403..50a24a7 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -267,8 +267,8 @@ def draw_cursor(self): def check_user_input(self): for event in pygame.event.get(): if event.type == pygame.QUIT: - raise StopIteration() - + return False + keys = pygame.key.get_pressed() if keys[pygame.K_q]: self.viewpoint.z -= 3*self.dt @@ -286,6 +286,7 @@ def check_user_input(self): self.halt_event.set() self.viewpoint.z = max(0.5,min(10,self.viewpoint.z)) self.view = pygame.Vector2(self.viewpoint.x, self.viewpoint.y) + return True def run(self): @@ -293,27 +294,30 @@ def run(self): self.screen = pygame.display.set_mode(self.size) self.clock = pygame.time.Clock() - while True: - if self.stop_event.is_set(): - break + try: + while True: + if self.stop_event.is_set(): + break - self.check_user_input() - self.check_connection() - self.clear() - self.draw_connectors() - self.draw_stations() - self.draw_carriers() - self.draw_user_input() - self.draw_info() - self.draw_actions() - self.draw_cursor() + if not self.check_user_input(): + break - pygame.display.flip() - - self.dt = self.clock.tick(60)/1000 - - pygame.quit() - self.stop_event.set() + self.check_connection() + self.clear() + self.draw_connectors() + self.draw_stations() + self.draw_carriers() + self.draw_user_input() + self.draw_info() + self.draw_actions() + self.draw_cursor() + + pygame.display.flip() + + self.dt = self.clock.tick(60)/1000 + finally: + pygame.quit() + self.stop_event.set() def start_visualization(connection, stop_event, halt_event): visualization = Visualization(connection=connection, stop_event=stop_event, halt_event=halt_event) From 5da7de2f82b51ab1061cadafc222ea439f90f442 Mon Sep 17 00:00:00 2001 From: Tobias Windisch Date: Wed, 25 Mar 2026 21:18:31 +0100 Subject: [PATCH 21/23] clean up --- lineflow/simulation/visualization.py | 40 ++++++---------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 50a24a7..6e753e9 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -1,40 +1,10 @@ import pygame +import logging from queue import Empty -class Viewpoint: - """ - A class to manage the viewpoint for rendering a 2D surface with zoom and pan capabilities. - """ - def __init__( - self, - size=None, - position=None, - zoom=1, - ): +logger = logging.getLogger(__name__) - if size is None: - size = (1410, 1000) - self.paper = pygame.Surface(size) - self.screen = pygame.display.set_mode((1280, 720)) - - if position is None: - position = (0, 0) - - self._view = pygame.Vector3(position[0], position[1], zoom) - - def clear(self): - self.screen.fill('white') - self.paper.fill('white') - - def _draw(self): - self.screen.blit( - pygame.transform.smoothscale_by(self.paper, self._view.z), - (self._view.x,self._view.y), - ) - - def teardown(self): - pygame.quit() class Visualization: @@ -95,6 +65,12 @@ def sort_connection_data(self): self.info = item elif item['type'] == 'actions': self.actions = item + else: + logger.warning( + f"Unknown item type: {item['type']}" + "Will not be visualized." + ) + def check_connection(self): self.get_from_connection() From 04147c9dc74da17e4c63c470136d44eeb4b84893 Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Thu, 26 Mar 2026 12:25:18 +0100 Subject: [PATCH 22/23] auto_set_initial_viewpoint --- lineflow/simulation/visualization.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 6e753e9..839830b 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -38,6 +38,30 @@ def __init__( self.actions = None self.connection_data = [] + self.initial_view_data = False + self.has_set_initial_view = False + + def set_initial_viewpoint(self): + x_positions = [] + y_positions = [] + for station in self.stations: + x_positions.append(station['position'].x) + y_positions.append(station['position'].y) + line_width = max(x_positions)-min(x_positions) + line_height = max(y_positions)-min(y_positions) + line_center = pygame.Vector2(min(x_positions)+line_width/2,min(y_positions)+line_height/2) + self.viewpoint.x = -line_center.x + self.viewpoint.y = -line_center.y + x_scalar = line_width / (self.size[0]-100) + y_scalar = line_height / (self.size[1]-100) + scalar = max(x_scalar,y_scalar) + if scalar < 1: + self.viewpoint.z = 1 + else: + self.viewpoint.z = round(scalar,1) + print(self.viewpoint) + self.has_set = True + def clear(self): self.screen.fill('white') @@ -45,6 +69,8 @@ def get_from_connection(self): while True: try: self.connection_data = self.connection.get_nowait() + if not self.has_set_initial_view: + self.initial_view_data = True except Empty: break @@ -279,6 +305,8 @@ def run(self): break self.check_connection() + if not self.has_set_initial_view and self.initial_view_data: + self.set_initial_viewpoint() self.clear() self.draw_connectors() self.draw_stations() From e861b1a6179e5ccae625b14ae43973ac3897d33e Mon Sep 17 00:00:00 2001 From: HSMarieK Date: Thu, 26 Mar 2026 12:52:43 +0100 Subject: [PATCH 23/23] feedback_for_user_on_start_and_end --- lineflow/simulation/visualization.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 839830b..94204a8 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -59,8 +59,7 @@ def set_initial_viewpoint(self): self.viewpoint.z = 1 else: self.viewpoint.z = round(scalar,1) - print(self.viewpoint) - self.has_set = True + self.has_set_initial_view = True def clear(self): self.screen.fill('white') @@ -263,6 +262,16 @@ def draw_user_input(self): ) self.screen.blit(text,text.get_rect(left=50,top=self.size[1]-40)) + def draw_loading(self): + font = pygame.font.SysFont(None, 48) + text = font.render("Loading Scene...", True, 'black') + self.screen.blit(text, text.get_rect(center=self.center)) + + def draw_shutdown(self): + font = pygame.font.SysFont(None, 48) + text = font.render("Shutting down...", True, 'black') + self.screen.blit(text, text.get_rect(center=self.center)) + def draw_cursor(self): pygame.draw.circle(self.screen, 'blue', self.center, 10, 1) @@ -305,8 +314,10 @@ def run(self): break self.check_connection() + if not self.has_set_initial_view and self.initial_view_data: self.set_initial_viewpoint() + self.clear() self.draw_connectors() self.draw_stations() @@ -314,10 +325,17 @@ def run(self): self.draw_user_input() self.draw_info() self.draw_actions() - self.draw_cursor() + if not self.has_set_initial_view: + self.draw_loading() + else: + self.draw_cursor() + + if self.halt_event.is_set(): + self.clear() + self.draw_shutdown() pygame.display.flip() - + self.dt = self.clock.tick(60)/1000 finally: pygame.quit()