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..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=1000, agent=agent, visualize=True, capture_screen=False) + line.run(simulation_end=10_000, agent=agent, visualize=True) diff --git a/lineflow/simulation/connectors.py b/lineflow/simulation/connectors.py index 0b16264..afb4073 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): """ @@ -77,7 +80,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( [ @@ -113,28 +116,21 @@ def setup_draw(self): ) self._positions_arrow[i] = arrowhead + + def get_visualization_data(self): + data = [ + dict( + type='connector', + start=self._position_input, + end=self._position_output, + n_slots=self.capacity, + ) + ] - 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) + 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/environment.py b/lineflow/simulation/environment.py index ceccdbf..c2a2a0f 100644 --- a/lineflow/simulation/environment.py +++ b/lineflow/simulation/environment.py @@ -192,7 +192,8 @@ 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._close_visualization() + self.line._init_visualization() self.render() return observation, self._get_info() @@ -201,13 +202,12 @@ 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._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) diff --git a/lineflow/simulation/line.py b/lineflow/simulation/line.py index 914c1ea..a376fa2 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 logger = logging.getLogger(__name__) @@ -48,6 +49,31 @@ 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() + self.visualization_process = Process( + target=start_visualization, + args=(self.connection, self.stop_event, self.halt_event) + ) + + for _, obj in self._objects.items(): + if isinstance(obj, Connector): + obj.setup_positions() + + self.visualization_process.start() + + def _close_visualization(self): + if hasattr(self, 'visualization_process'): + self.stop_event.set() + self.visualization_process.join() + @property def name(self): @@ -175,86 +201,27 @@ 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) + def _send_data_to_visualization(self, actions=None): + if not self.stop_event.is_set(): + data = [] - 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) + for _, obj in self._objects.items(): + if isinstance(obj, Station): + data.append(obj.get_visualization_data()) - 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 _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) - - def setup_draw(self): - pygame.init() - - max_x, max_y = self._adjust_positions() - for o in self._objects.values(): - o.setup_draw() + if isinstance(obj, Connector): + data.extend(obj.get_visualization_data()) - self.viewpoint = Viewpoint(size=(max_x+100, max_y+100)) + data.append( + dict( + type="info", + time=self.env.now, + n_parts=self.get_n_parts_produced() + ) + ) + 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(): @@ -299,7 +266,6 @@ def run( agent=None, show_status=True, visualize=False, - capture_screen=False, ): """ Args: @@ -309,12 +275,9 @@ 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.setup_draw() - + self._init_visualization() # Register objects when simulation is initially started if len(self.env._queue) == 0: @@ -329,6 +292,9 @@ 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: @@ -342,17 +308,12 @@ def run( self.apply(actions) if visualize: - 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._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 eff7e0d..2335d5e 100644 --- a/lineflow/simulation/movable_objects.py +++ b/lineflow/simulation/movable_objects.py @@ -29,15 +29,7 @@ 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 _draw_shape(self, screen): + def get_visualization_data(self): raise NotImplementedError() def move(self, position): @@ -142,10 +134,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) @@ -186,24 +174,18 @@ def assemble(self, part): self.parts[part.name] = part - def _draw_shape(self, screen): + def get_visualization_data(self, with_text=True): + parts = len(self.parts) + if np.isinf(self.capacity) and parts != 0: + fill = 1 + else: + fill = parts/self.capacity - 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, - ) + 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 79aa7dc..1758705 100644 --- a/lineflow/simulation/stations.py +++ b/lineflow/simulation/stations.py @@ -170,28 +170,20 @@ 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 _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 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 _draw_info(self, screen): - 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( @@ -199,31 +191,6 @@ 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 _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 get_performance_coefficient(self): return compute_performance_coefficient(self.worker_skill) @@ -444,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 run(self): while True: @@ -582,9 +546,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 run(self): while True: @@ -1183,16 +1144,16 @@ def _get_buffer_out_position(self): self.state['index_buffer_out'].value ].__self__._positions_slots[0] - def _draw_info(self, screen): + 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) - 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) + 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)) @@ -1401,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 @@ -1413,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 _draw_info(self, screen): - self._draw_n_carriers(screen) + 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 diff --git a/lineflow/simulation/visualization.py b/lineflow/simulation/visualization.py index 5745e1a..94204a8 100644 --- a/lineflow/simulation/visualization.py +++ b/lineflow/simulation/visualization.py @@ -1,66 +1,346 @@ 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. - """ +logger = logging.getLogger(__name__) + + +class Visualization: + def __init__( self, size=None, - position=None, - zoom=1, + viewpoint=None, + connection=None, + stop_event=None, + halt_event=None, ): - if size is None: - size = (1410, 1000) - self.paper = pygame.Surface(size) + size = (1280, 720) + self.size = size + 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 + self.halt_event = halt_event - self.screen = pygame.display.set_mode((1280, 720)) + self.center = pygame.Vector2(self.size[0]/2, self.size[1]/2) - if position is None: - position = (0, 0) - - self._view = pygame.Vector3(position[0], position[1], zoom) + self.stations = [] + self.connectors = [] + self.carriers = [] + self.info = None + self.actions = None + self.connection_data = [] - def check_user_input(self): + 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) + self.has_set_initial_view = True + + def clear(self): + self.screen.fill('white') + + 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 + + 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) + elif item['type'] == 'connector': + 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 + else: + logger.warning( + f"Unknown item type: {item['type']}" + "Will not be visualized." + ) - 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 + 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.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 + snippet = length/(connector['n_slots']+1) + for n in range(connector['n_slots']): + pygame.draw.circle( + self.screen, + 'gray', + self.view/self.viewpoint.z + self.center + connector['start']/self.viewpoint.z + snippet*(n+1), + int(10/self.viewpoint.z) + ) - if pygame.key.get_pressed()[pygame.K_UP]: - self._view.y += 10 + 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' - if pygame.key.get_pressed()[pygame.K_DOWN]: - self._view.y -= 10 + pygame.draw.rect( + self.screen, + color, + pygame.Rect( + 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 + ), + 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.center + (self.view + 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.center + (self.view + station['position'])/self.viewpoint.z + ) + ) + if 'pos_in_out' in station: + pygame.draw.circle( + self.screen, + 'gray', + 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.center + (self.view + station['position'])/self.viewpoint.z, + self.center + (self.view + pos)/self.viewpoint.z, + width=int(5/self.viewpoint.z) + ) - if pygame.key.get_pressed()[pygame.K_LEFT]: - self._view.x += 10 + def draw_carriers(self): + height = 10 + width = 30 + for carrier in self.carriers: + pygame.draw.rect( + self.screen, + 'black', + pygame.Rect( + 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 + ) + ) + pygame.draw.rect( + self.screen, + 'orange', + pygame.Rect( + 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 + ) + ) + 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.center + (self.view + carrier['position'])/self.viewpoint.z + (0,-1.3*height/self.viewpoint.z) + ) + ) - if pygame.key.get_pressed()[pygame.K_RIGHT]: - self._view.x -= 10 + 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, Shift+H: Exit", + True, + 'black', + 'white' + ) + 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) + + def check_user_input(self): for event in pygame.event.get(): if event.type == pygame.QUIT: - self.teardown() + return False + + 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] or keys[pygame.K_UP]: + self.viewpoint.y += 300*self.dt + 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)) + self.view = pygame.Vector2(self.viewpoint.x, self.viewpoint.y) + return True - self._view.z = max(self._view.z, 0.1) - self._view.z = min(self._view.z, 5) + def run(self): - def clear(self): - self.screen.fill('white') - self.paper.fill('white') + pygame.init() + self.screen = pygame.display.set_mode(self.size) + self.clock = pygame.time.Clock() - def _draw(self): - self.screen.blit( - pygame.transform.smoothscale_by(self.paper, self._view.z), - (self._view.x,self._view.y), - ) + try: + while True: + if self.stop_event.is_set(): + break + + if not self.check_user_input(): + 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() + self.draw_carriers() + self.draw_user_input() + self.draw_info() + self.draw_actions() + 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() + self.stop_event.set() - def teardown(self): - pygame.quit() +def start_visualization(connection, stop_event, halt_event): + visualization = Visualization(connection=connection, stop_event=stop_event, halt_event=halt_event) + visualization.run()