def getFilename(self, robot_id): from ev3sim.simulation.loader import StateHandler if StateHandler.WORKSPACE_FOLDER: log_dir = find_abs_directory("workspace/logs/", create=True) else: log_dir = find_abs_directory("package/logs/", create=True) return os.path.join(log_dir, f"{robot_id}_log.txt")
def action(): import yaml """Go through and try fixing the bots.""" for bot in old_bots: dirpath = os.path.join(path, bot[:-4]) # Folder if os.path.isdir(dirpath): import shutil shutil.rmtree(dirpath) os.mkdir(dirpath) # config.bot with open(os.path.join(path, bot), "r") as f: config = yaml.safe_load(f) bot_script = config.get("script", "code.py") preview_image = config.get("preview_path", "preview.png") for keyword in ["script", "preview_path"]: if keyword in config: del config[keyword] with open(os.path.join(dirpath, "config.bot"), "w") as f: f.write(yaml.dump(config)) # code.py try: code_path = os.path.join( find_abs_directory("workspace/code/"), bot_script) with open(code_path, "r") as f: code = f.read() except: code = "" with open(os.path.join(dirpath, "code.py"), "w") as f: f.write(code) # preview.png try: preview = find_abs(preview_image, asset_locations()) with open(preview, "rb") as f: preview_data = f.read() except: preview_data = bytes() with open(os.path.join(dirpath, "preview.png"), "wb") as f: f.write(preview_data) # Remove the old bot os.remove(os.path.join(path, bot)) # Additionally, we need to update all sim files to no longer use the .bot prefix actual_dir = find_abs_directory("workspace/sims/") for file in os.listdir(actual_dir): if os.path.isfile(os.path.join( actual_dir, file)) and file.endswith(".sim"): with open(os.path.join(actual_dir, file), "r") as f: config = yaml.safe_load(f) for x in range(len(config.get("bots", []))): if config["bots"][x].endswith(".bot"): config["bots"][x] = config["bots"][x][:-4] with open(os.path.join(actual_dir, file), "w") as f: f.write(yaml.dump(config))
def clickCode(self): from ev3sim.utils import open_file, APP_VSCODE, APP_MINDSTORMS # Shouldn't happen but lets be safe. if self.batch_index == -1: return with open( os.path.join(self.available_batches[self.batch_index][2], "bot", "config.bot")) as f: conf = yaml.safe_load(f) if conf.get("type", "python") == "mindstorms": script_location = conf.get("script", "program.ev3") open_file( os.path.join(self.available_batches[self.batch_index][2], "bot", script_location), APP_MINDSTORMS) else: script_location = conf.get("script", "code.py") open_file( os.path.join(self.available_batches[self.batch_index][2], "bot", script_location), APP_VSCODE, folder=os.path.join(find_abs_directory("workspace")), )
def fill_workspace(): """Always ensure workspace has the necessary folders.""" from ev3sim.simulation.loader import StateHandler if StateHandler.WORKSPACE_FOLDER: ensure_workspace_filled(find_abs_directory("workspace")) return None
def loadMap(self, map_path): # load map custom_dir = find_abs_directory("workspace/custom/") full_path = join(custom_dir, "Arrow Maze", map_path) with open(full_path, "r") as f: conf = yaml.safe_load(f) # Despawn old stuff for arrow in self.arrows: ScreenObjectManager.instance.unregisterVisual(arrow.key) for child in arrow.children: ScreenObjectManager.instance.unregisterVisual(child.key) self.arrows = [] self.dimensions = conf["dimensions"] self.spawn = conf["spawn"] self.grid = conf["grid"] self.rotation = conf.get("rotation", 0) * math.pi / 180 # Spawn colours for x in range(self.dimensions[0]): for y in range(self.dimensions[1]): arrow_obj = { "name": "Image", "image_path": 'custom/Arrow Maze/ui/arrow.png', "hAlignment": "m", "vAlignment": "m", "scale": (self.width - self.margin) / 200, "zPos": 1, } children = [ { "visual": arrow_obj, } ] if self.grid[y][x] == "L": children[0]["rotation"] = math.pi elif self.grid[y][x] == "R": children[0]["rotation"] = 0 elif self.grid[y][x] == "U": children[0]["rotation"] = math.pi / 2 elif self.grid[y][x] == "D": children[0]["rotation"] = - math.pi / 2 else: children = [] c_obj = objectFactory( visual={ "name": "Rectangle", "width": self.width - self.margin, "height": self.width - self.margin, "fill": "#ffffff", "stroke_width": 0, "zPos": 0.5 }, children=children, key=f"sq-{x}-{y}", position=[ self.width * (x-self.offset[0]), -self.width * (y-self.offset[1]), ], ) ScreenObjectManager.instance.registerObject(c_obj, c_obj.key) self.arrows.append(c_obj)
def handlePressed(self, idx): assert idx == 2, f"{idx} expected to be 2." def onComplete(path): if self.is_directory: if not path: return self.current = path self.filename.set_text(self.current) return if not path: return for pathname in self.relative_paths: dirpath = find_abs_directory(pathname, create=True) if path.startswith(dirpath): actual_path = path[len(dirpath):] break else: msg = '<font color="#DD4045">This file must be contained in one of the following directories:</font>' for pathname in self.relative_paths: dirpath = find_abs_directory(pathname, create=True) msg = msg + "<br><br>" + dirpath msg = msg + "</font>" self.menu.addErrorDialog(msg) return self.current = actual_path self.filename.set_text(self.current) self.menu.addFileDialog( "Select " + self.title, find_abs_directory(self.relative_paths[0], create=True) if self.relative_paths else None, self.is_directory, onComplete, )
def batch_locations(): """Batch files can also be in the custom folders.""" import os from ev3sim.file_helper import find_abs_directory from ev3sim.simulation.loader import StateHandler locations = ["package/presets/"] if StateHandler.WORKSPACE_FOLDER: custom_path = find_abs_directory("workspace/custom/", create=True) for name in os.listdir(custom_path): if os.path.isdir(os.path.join(custom_path, name)): locations.append(f"workspace/custom/{name}/") return locations
def code_locations(bot_path): from ev3sim.file_helper import find_abs_directory # First, match the bot path to a bot_location. for location in bot_locations(): actual_dir = find_abs_directory(location) if bot_path.startswith(actual_dir): break else: raise ValueError( f"Bot path {bot_path} does not appear in any valid bot location.") relative = location + bot_path[len(actual_dir):] return [relative]
def bot_locations(): import os from ev3sim.file_helper import find_abs_directory from ev3sim.simulation.loader import StateHandler locations = ["workspace/robots/", "package/examples/robots/", "workspace"] if StateHandler.WORKSPACE_FOLDER: custom_path = find_abs_directory("workspace/custom/", create=True) for name in os.listdir(custom_path): if os.path.isdir(os.path.join(custom_path, name)): # Favour custom locations over the catch-all workspace entry. locations.insert(2, f"workspace/custom/{name}/") return locations
def onComplete(path): if self.is_directory: if not path: return self.current = path self.filename.set_text(self.current) return if not path: return for pathname in self.relative_paths: dirpath = find_abs_directory(pathname, create=True) if path.startswith(dirpath): actual_path = path[len(dirpath):] break else: msg = '<font color="#DD4045">This file must be contained in one of the following directories:</font>' for pathname in self.relative_paths: dirpath = find_abs_directory(pathname, create=True) msg = msg + "<br><br>" + dirpath msg = msg + "</font>" self.menu.addErrorDialog(msg) return self.current = actual_path self.filename.set_text(self.current)
def generateObjects(self): # First, find all batch files. if not self.in_error: self.available_batches = [] error_batches = [] for rel_dir in batch_locations(): # Only show custom sims. if not rel_dir.startswith("workspace/custom/"): continue try: actual_dir = find_abs_directory(rel_dir) except: continue for batch in BatchValidator.all_valid_in_dir(actual_dir): # Show the dir name. try: with open(os.path.join(actual_dir, batch), "r") as f: config = yaml.safe_load(f) if not config.get("hidden", False): rest = rel_dir directory = "" while not directory: rest, directory = os.path.split(rest) self.available_batches.append( (directory, os.path.join(actual_dir, batch), actual_dir, batch)) except Exception as e: sentry_sdk.capture_exception(e) error_batches.append(os.path.join(actual_dir, batch)) if self.first_launch and error_batches: self.first_launch = False self.in_error = True self.addErrorDialog( 'A problem occured loading the following batches:<br><br><font color="#cc0000">' + "<br>".join(batch for batch in error_batches) + "</font>") return for i, batch in enumerate(self.available_batches): if i == self.batch_index: try: with open(batch[1], "r") as f: config = yaml.safe_load(f) # Update batch information preset_path = find_abs(config["preset_file"], allowed_areas=["workspace"]) with open(preset_path, "r") as f: preset_config = yaml.safe_load(f) preset_preview = find_abs(preset_config["preview_path"], allowed_areas=["workspace"]) self.preview_image_source = pygame.image.load( preset_preview) except Exception as e: sentry_sdk.capture_exception(e) self.setBatchIndex(-1) self.addErrorDialog( '<font color="#cc0000">The task you have selected has some internal errors EV3Sim cannot resolve.</font><br><br>' + "This can be caused by moving/renaming a bot as well as a few other things.<br><br>" + "If you'd like to fix this, then try manually editing the sim file in a text editor or contact the developers." ) return # Draw Background self.bg = pygame_gui.elements.UIPanel( relative_rect=pygame.Rect(0, 0, *self._size), starting_layer_height=-1, manager=self, object_id=pygame_gui.core.ObjectID("background"), ) self._all_objs.append(self.bg) # Scrolling container old_y = getattr(getattr(self, "scrolling_container", None), "cur_y", 0) self.scrolling_container = CustomScroll( relative_rect=pygame.Rect(0, 0, self._size[0], 5 * self._size[1]), manager=self, object_id=pygame_gui.core.ObjectID("scroll_container"), ) picture_width = 360 self.scrolling_container.elems_size = picture_width * 0.4 self.scrolling_container.span_elems = 4 self.scrolling_container.num_elems = len(self.available_batches) scrolling_size = (self._size[0] / 4 + self._size[0] / 5, self._size[1] * 0.95 - min(self._size[1] / 6, 90) + 20) # Setting dimensions and positions on a UIScrollingContainer seems buggy. This works. self.scrolling_container.set_dimensions(scrolling_size) self.scrolling_container.set_position(scrolling_size) self.scrolling_container.cur_y = old_y self.scrolling_container.set_scroll(old_y) self._all_objs.append(self.scrolling_container) # The batch buttons button_size = self._size[0] / 4, 60 info_size = self._size[0] / 4 - 20, 15 batch_rect = lambda i: (self._size[0] / 10, self._size[1] / 10 + i * button_size[1] * 1.5) info_rect = lambda b_r: ( b_r[0] + button_size[0] - info_size[0] - 10, b_r[1] + button_size[1] - info_size[1] - 5, ) self.batch_buttons = [] for i, (show, batch, actual_dir, filename) in enumerate(self.available_batches): self.batch_buttons.append( pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*batch_rect(i), *button_size), text=show, manager=self, container=self.scrolling_container, object_id=pygame_gui.core.ObjectID(show + "-" + str(i), "list_button"), )) self.addButtonEvent(show + "-" + str(i), self.setBatchIndex, i) self._all_objs.extend(self.batch_buttons) # Remove remove_size = self._size[0] / 8, min(self._size[1] / 6, 90) remove_icon_size = remove_size[1] * 0.6, remove_size[1] * 0.6 remove_batch_pos = (self._size[0] / 3 - 2 * remove_size[0] - 15, self._size[1] * 0.95 - remove_size[1]) self.remove_batch = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*remove_batch_pos, *remove_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("remove_batch", "cancel-changes"), ) self.addButtonEvent("remove_batch", self.clickRemove) if not self.remove_enable: self.remove_batch.disable() remove_batch_path = find_abs("ui/bin.png", allowed_areas=asset_locations()) self.remove_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(remove_batch_pos, remove_size, remove_icon_size), *remove_icon_size), image_surface=pygame.image.load(remove_batch_path), manager=self, object_id=pygame_gui.core.ObjectID("remove_batch-icon"), ) self._all_objs.append(self.remove_batch) self._all_objs.append(self.remove_icon) preview_size = self._size[0] / 4, self._size[1] / 4 preview_size = ( min(preview_size[0], (preview_size[1] * 4) // 3), min(preview_size[1], (preview_size[0] * 3) // 4), ) preview_image_pos = (self._size[0] * 0.9 - preview_size[0], self._size[1] * 0.1) self.preview_image = pygame_gui.elements.UIImage( relative_rect=pygame.Rect(*preview_image_pos, *preview_size), image_surface=pygame.Surface(self._size), manager=self, object_id=pygame_gui.core.ObjectID("preview-image"), ) self._all_objs.append(self.preview_image) code_size = preview_size[0] * 0.4, preview_size[1] * 0.4 code_button_pos = ( self._size[0] * 0.9 - code_size[0] - 10, self._size[1] * 0.1 + preview_size[1] + 10, ) code_icon_size = code_size[1] * 0.6, code_size[1] * 0.6 self.code_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*code_button_pos, *code_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("bot-code", "settings_buttons"), ) self.addButtonEvent("bot-code", self.clickCode) if not self.code_enable: self.code_button.disable() code_icon_path = find_abs("ui/code.png", allowed_areas=asset_locations()) self.code_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(code_button_pos, code_size, code_icon_size), *code_icon_size), image_surface=pygame.image.load(code_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("code-icon"), ) self._all_objs.append(self.code_button) self._all_objs.append(self.code_icon) bots_button_pos = ( self._size[0] * 0.9 - preview_size[0] + 10, self._size[1] * 0.1 + preview_size[1] + 10, ) self.bots_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*bots_button_pos, *code_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("bot-bots", "settings_buttons"), ) self.addButtonEvent("bot-bots", self.clickBots) if not self.bots_enable: self.bots_button.disable() bots_icon_path = find_abs("ui/bot.png", allowed_areas=asset_locations()) self.bots_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(bots_button_pos, code_size, code_icon_size), *code_icon_size), image_surface=pygame.image.load(bots_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("bots-icon"), ) self._all_objs.append(self.bots_button) self._all_objs.append(self.bots_icon) start_size = self._size[0] / 4, min(self._size[1] / 4, 120) start_icon_size = start_size[1] * 0.6, start_size[1] * 0.6 start_button_pos = (self._size[0] * 0.9 - start_size[0], self._size[1] * 0.9 - start_size[1]) self.start_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*start_button_pos, *start_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("start-sim", "action_button"), ) self.addButtonEvent("start-sim", self.clickStart) if not self.start_enable: self.start_button.disable() start_icon_path = find_abs("ui/start_sim.png", allowed_areas=asset_locations()) self.start_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(start_button_pos, start_size, start_icon_size), *start_icon_size), image_surface=pygame.image.load(start_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("start-sim-icon"), ) self._all_objs.append(self.start_button) self._all_objs.append(self.start_icon) self.changeSelectedTheming() super().generateObjects()
def startProcess(self, robot_id, kill_recent=True): if robot_id in self.processes and self.processes[robot_id] is not None: if kill_recent: self.killProcess(robot_id) else: raise ValueError("Did not expect an existing process!") if self.scriptnames[robot_id] is not None: from os.path import join, split, dirname from ev3sim.attach_bot import attach_bot if self.scriptnames[robot_id].endswith(".ev3"): actual_script = join(dirname(self.scriptnames[robot_id]), ".compiled.py") try: from mindpile import from_ev3 with open(actual_script, "w") as f: f.write( from_ev3(self.scriptnames[robot_id], ev3sim_support=True)) except Exception as e: with open(actual_script, "w") as f: f.write(f'print("Mindstorms compilation failed! {e}")') else: actual_script = self.scriptnames[robot_id] format_filename = join(actual_script) # This ensures that as long as the code sits in the bot directory, relative imports will work fine. possible_locations = [ "workspace/robots/", "workspace", "package/examples/robots" ] extra_dirs = [] for loc in possible_locations: loc_path = find_abs_directory(loc, create=True) if format_filename.startswith(loc_path): while format_filename.startswith(loc_path): format_filename, file = split(format_filename) extra_dirs.append(format_filename) break else: raise ValueError( f"Expected code to be in one of the following locations: {possible_locations}" ) self.processes[robot_id] = Process( target=attach_bot, args=( robot_id, actual_script, extra_dirs[::-1], StateHandler.instance.shared_info["result_queue"], StateHandler.instance.shared_info["result_queue"]. _internal_size, self.queues[robot_id][self.SEND], self.queues[robot_id][self.SEND]._internal_size, self.queues[robot_id][self.RECV], self.queues[robot_id][self.RECV]._internal_size, ), ) self.processes[robot_id].start() Logger.instance.beginLog(robot_id)
def generateObjects(self): from ev3sim.visual.manager import ScreenObjectManager self.show_custom = False # First, check if there are any valid batches in the custom folder. for rel_dir in batch_locations(): # Only consider custom sims. if not rel_dir.startswith("workspace/custom/"): continue try: actual_dir = find_abs_directory(rel_dir) except: continue for _ in BatchValidator.all_valid_in_dir(actual_dir): self.show_custom = True break # In order to respect theme changes, objects must be built in initWithKwargs self.bg = pygame_gui.elements.UIPanel( relative_rect=pygame.Rect(0, 0, *self._size), starting_layer_height=-1, manager=self, object_id=pygame_gui.core.ObjectID("background"), ) self._all_objs.append(self.bg) self.title = pygame_gui.elements.UITextBox( relative_rect=pygame.Rect(0, 0, -1, -1), html_text="EV3<i>Sim</i>", manager=self, object_id=pygame_gui.core.ObjectID("title"), ) self.title.set_position( ((self._size[0] - self.title.rect.width) / 2, 50)) self._all_objs.append(self.title) self.button_size = ((self._size[0] / 4, self._size[1] / 10) if self.show_custom else (self._size[0] / 4, self._size[1] / 8)) settings_size = self.button_size[0] * 0.3, self.button_size[1] bot_size = settings_size settings_icon_size = settings_size[1] * 0.6, settings_size[1] * 0.6 bot_icon_size = bot_size[1] * 0.6, bot_size[1] * 0.6 settings_icon_path = find_abs("ui/settings.png", allowed_areas=asset_locations()) bot_icon_path = find_abs("ui/bot.png", allowed_areas=asset_locations()) self.soccer_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*self.buttonPos(0), *self.button_size), text="Soccer", manager=self, object_id=pygame_gui.core.ObjectID("soccer_button", "menu_button"), ) self.addButtonEvent("soccer_button", lambda: self.playSim("soccer.yaml")) self._all_objs.append(self.soccer_button) soccer_settings_button_pos = [ self.buttonPos(0)[0] + self.button_size[0] + 20, self.buttonPos(0)[1] ] self.soccer_settings_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*soccer_settings_button_pos, *settings_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("soccer-settings", "settings_buttons"), ) self.addButtonEvent("soccer-settings", self.clickSimSettings, "soccer.yaml") self.soccer_settings_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(soccer_settings_button_pos, settings_size, settings_icon_size), *settings_icon_size), image_surface=pygame.image.load(settings_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("soccer-settings-icon"), ) self._all_objs.append(self.soccer_settings_button) self._all_objs.append(self.soccer_settings_icon) soccer_bot_button_pos = [ self.buttonPos(0)[0] + self.button_size[0] + settings_size[0] + 40, self.buttonPos(0)[1], ] self.soccer_bot_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*soccer_bot_button_pos, *bot_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("soccer-bot", "settings_buttons"), ) self.addButtonEvent("soccer-bot", self.clickSimBots, "soccer.yaml") self.soccer_bot_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(soccer_bot_button_pos, bot_size, bot_icon_size), *bot_icon_size), image_surface=pygame.image.load(bot_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("soccer-bot-icon"), ) self._all_objs.append(self.soccer_bot_button) self._all_objs.append(self.soccer_bot_icon) self.rescue_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*self.buttonPos(1), *self.button_size), text="Rescue", manager=self, object_id=pygame_gui.core.ObjectID("rescue_button", "menu_button"), ) self.addButtonEvent("rescue_button", lambda: self.playSim("rescue.yaml")) self._all_objs.append(self.rescue_button) rescue_settings_button_pos = [ self.buttonPos(1)[0] + self.button_size[0] + 20, self.buttonPos(1)[1] ] self.rescue_settings_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*rescue_settings_button_pos, *settings_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("rescue-settings", "settings_buttons"), ) self.addButtonEvent("rescue-settings", self.clickSimSettings, "rescue.yaml") self.rescue_settings_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(rescue_settings_button_pos, settings_size, settings_icon_size), *settings_icon_size), image_surface=pygame.image.load(settings_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("rescue-settings-icon"), ) self._all_objs.append(self.rescue_settings_button) self._all_objs.append(self.rescue_settings_icon) rescue_bot_button_pos = [ self.buttonPos(1)[0] + self.button_size[0] + settings_size[0] + 40, self.buttonPos(1)[1], ] self.rescue_bot_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*rescue_bot_button_pos, *bot_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("rescue-bot", "settings_buttons"), ) self.addButtonEvent("rescue-bot", self.clickSimBots, "rescue.yaml") self.rescue_bot_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(rescue_bot_button_pos, bot_size, bot_icon_size), *bot_icon_size), image_surface=pygame.image.load(bot_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("rescue-bot-icon"), ) self._all_objs.append(self.rescue_bot_button) self._all_objs.append(self.rescue_bot_icon) if self.show_custom: self.custom_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*self.buttonPos(2), *self.button_size), text="Custom", manager=self, object_id=pygame_gui.core.ObjectID("custom_button", "menu_button"), ) self.addButtonEvent("custom_button", self.clickCustom) self._all_objs.append(self.custom_button) self.bot_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect( *self.buttonPos(3 if self.show_custom else 2), *self.button_size), text="Bots", manager=self, object_id=pygame_gui.core.ObjectID("bots_button", "menu_button"), ) self.addButtonEvent( "bots_button", lambda: ScreenObjectManager.instance.pushScreen(ScreenObjectManager .SCREEN_BOTS), ) self._all_objs.append(self.bot_button) self.settings_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect( *self.buttonPos(4 if self.show_custom else 3), *self.button_size), text="Settings", manager=self, object_id=pygame_gui.core.ObjectID("main_settings_button", "menu_button"), ) def clickSettings(): ScreenObjectManager.instance.pushScreen( ScreenObjectManager.SCREEN_SETTINGS, file=find_abs("user_config.yaml", config_locations()), settings=main_settings, ) ScreenObjectManager.instance.screens[ ScreenObjectManager.SCREEN_SETTINGS].clearEvents() self.addButtonEvent("main_settings_button", clickSettings) self._all_objs.append(self.settings_button) super().generateObjects()
def generateObjects(self): # First, find all bot files. if not self.in_error: self.available_bots = [] error_bots = [] for rel_dir in bot_locations(): try: actual_dir = find_abs_directory(rel_dir) except: continue for bot in BotValidator.all_valid_in_dir(actual_dir): try: # Show everything except dir and .bot with open(os.path.join(actual_dir, bot, "config.bot"), "r") as f: config = yaml.safe_load(f) # If we are hidden, or in edit mode with hidden_edit, then don't show. if not config.get("hidden", False) and not ( config.get("hidden_edit", False) and len(self.bot_keys) == 0): self.available_bots.append( (bot, os.path.join(actual_dir, bot), rel_dir, bot)) except Exception as e: sentry_sdk.capture_exception(e) error_bots.append(os.path.join(actual_dir, bot)) if self.first_launch and error_bots: self.first_launch = False self.in_error = True self.addErrorDialog( 'A problem occured loading the following bots:<br><br><font color="#cc0000">' + "<br>".join(bot for bot in error_bots) + "</font>") return self.bg = pygame_gui.elements.UIPanel( relative_rect=pygame.Rect(0, 0, *self._size), starting_layer_height=-1, manager=self, object_id=pygame_gui.core.ObjectID("background"), ) self._all_objs.append(self.bg) # Scrolling container old_y = getattr(getattr(self, "scrolling_container", None), "cur_y", 0) self.scrolling_container = CustomScroll( relative_rect=pygame.Rect(0, 0, *self._size), manager=self, object_id=pygame_gui.core.ObjectID("scroll_container"), ) self.scrolling_container.num_elems = len(self.available_bots) scrolling_size = (self._size[0] / 4 + self._size[0] / 5, self._size[1] * 0.9 - min(self._size[1] / 6, 90)) # Setting dimensions and positions on a UIScrollingContainer seems buggy. This works. self.scrolling_container.set_dimensions(scrolling_size) self.scrolling_container.set_position(scrolling_size) self.scrolling_container.cur_y = old_y self.scrolling_container.set_scroll(old_y) self._all_objs.append(self.scrolling_container) button_size = self._size[0] / 4, 60 info_size = self._size[0] / 4 - 20, 15 bot_rect = lambda i: (self._size[0] / 10, self._size[1] / 10 + i * button_size[1] * 1.5) info_rect = lambda b_r: ( b_r[0] + button_size[0] - info_size[0] - 10, b_r[1] + button_size[1] - info_size[1] - 5, ) self.bot_buttons = [] self.bot_descriptions = [] for i, (show, bot, rel_dir, filename) in enumerate(self.available_bots): self.bot_buttons.append( pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*bot_rect(i), *button_size), text=show, manager=self, container=self.scrolling_container, object_id=pygame_gui.core.ObjectID( show + "-" + str(i), "list_button_highlighted" if i == self.bot_index else "list_button"), )) self.addButtonEvent(show + "-" + str(i), self.setBotIndex, i) self.bot_descriptions.append( pygame_gui.elements.UILabel( relative_rect=pygame.Rect(*info_rect(bot_rect(i)), *info_size), text=rel_dir, manager=self, container=self.scrolling_container, object_id=pygame_gui.core.ObjectID( show + "-dir-" + str(i), "button_info_selected" if i == self.bot_index else "button_info"), )) self._all_objs.extend(self.bot_buttons) self._all_objs.extend(self.bot_descriptions) preview_size = self._size[0] / 4, self._size[1] / 4 preview_size = ( min(preview_size[0], (preview_size[1] * 4) // 3), min(preview_size[1], (preview_size[0] * 3) // 4), ) try: if self.bot_index >= len(self.available_bots): self.bot_index = -1 if self.bot_index == -1: image = pygame.Surface(preview_size) image.fill(pygame.Color(self.bg.background_colour)) else: with open( os.path.join(self.available_bots[self.bot_index][1], "config.bot"), "r") as f: config = yaml.safe_load(f) bot_preview = os.path.join( self.available_bots[self.bot_index][1], config.get("preview_path", "preview.png")) image = pygame.image.load(bot_preview) except Exception as e: sentry_sdk.capture_exception(e) self.setBotIndex(-1) self.addErrorDialog( '<font color="#cc0000">The bot you have selected has some internal errors EV3Sim cannot resolve.</font><br><br>' + "If you'd like to fix this, then try manually editing the bot file in a text editor." ) return if image.get_size() != preview_size: image = pygame.transform.smoothscale( image, [int(v) for v in preview_size]) self.preview_image = pygame_gui.elements.UIImage( relative_rect=pygame.Rect(self._size[0] * 0.9 - preview_size[0], self._size[1] * 0.1, *preview_size), image_surface=image, manager=self, object_id=pygame_gui.core.ObjectID("preview-image"), ) self._all_objs.append(self.preview_image) if len(self.bot_keys) == 0: code_size = preview_size[0] * 0.4, preview_size[1] * 0.4 code_button_pos = ( self._size[0] * 0.9 - code_size[0] - 10, self._size[1] * 0.1 + preview_size[1] + 10, ) code_icon_size = code_size[1] * 0.6, code_size[1] * 0.6 self.code_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*code_button_pos, *code_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("bot-code", "settings_buttons"), ) self.addButtonEvent("bot-code", self.clickCode) if not self.code_enable: self.code_button.disable() code_icon_path = find_abs("ui/code.png", allowed_areas=asset_locations()) self.code_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(code_button_pos, code_size, code_icon_size), *code_icon_size), image_surface=pygame.image.load(code_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("code-icon"), ) self._all_objs.append(self.code_button) self._all_objs.append(self.code_icon) edit_button_pos = ( self._size[0] * 0.9 - preview_size[0], self._size[1] * 0.1 + preview_size[1] + 10, ) self.edit_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*edit_button_pos, *code_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("bot-edit", "settings_buttons"), ) self.addButtonEvent("bot-edit", self.clickEdit) if not self.edit_enable: self.edit_button.disable() edit_icon_path = find_abs("ui/edit.png", allowed_areas=asset_locations()) self.edit_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(edit_button_pos, code_size, code_icon_size), *code_icon_size), image_surface=pygame.image.load(edit_icon_path), manager=self, object_id=pygame_gui.core.ObjectID("edit-icon"), ) self._all_objs.append(self.edit_button) self._all_objs.append(self.edit_icon) new_size = self._size[0] / 8, min(self._size[1] / 6, 90) new_icon_size = new_size[1] * 0.6, new_size[1] * 0.6 new_bot_pos = (bot_rect(0)[0] + button_size[0] - new_size[0], self._size[1] * 0.9 - new_size[1]) self.new_bot = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*new_bot_pos, *new_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("new_bot", "action_button"), ) self.addButtonEvent("new_bot", self.clickNew) new_bot_path = find_abs("ui/add.png", allowed_areas=asset_locations()) self.new_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(new_bot_pos, new_size, new_icon_size), *new_icon_size), image_surface=pygame.image.load(new_bot_path), manager=self, object_id=pygame_gui.core.ObjectID("new_bot-icon"), ) self._all_objs.append(self.new_bot) self._all_objs.append(self.new_icon) remove_bot_pos = (bot_rect(0)[0], self._size[1] * 0.9 - new_size[1]) self.remove_bot = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*remove_bot_pos, *new_size), text="", manager=self, object_id=pygame_gui.core.ObjectID("remove_bot", "cancel-changes"), ) self.addButtonEvent("remove_bot", self.clickRemove) if not self.remove_enable: self.remove_bot.disable() remove_bot_path = find_abs("ui/bin.png", allowed_areas=asset_locations()) self.remove_icon = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( *self.iconPos(remove_bot_pos, new_size, new_icon_size), *new_icon_size), image_surface=pygame.image.load(remove_bot_path), manager=self, object_id=pygame_gui.core.ObjectID("remove_bot-icon"), ) self._all_objs.append(self.remove_bot) self._all_objs.append(self.remove_icon) super().generateObjects() else: # Bot key locations, for selecting bots in batch files. self.bot_loc_spots = [] for i in range(len(self.bot_keys)): if i == self.key_index: self.bot_loc_spots.append( self.createBotImage(i, bg=pygame.Color("#80b918"))) else: self.bot_loc_spots.append(self.createBotImage(i)) self.sizeBotImage(i, big_mode=len(self.bot_keys) == 1) img = self.preview_images[i] if img is None: continue if img.get_size() != self.bot_loc_spots[i].rect.size: img = pygame.transform.smoothscale( img, (self.bot_loc_spots[i].rect.width, self.bot_loc_spots[i].rect.height)) self.bot_loc_spots[i].set_image(img) self._all_objs.extend(self.bot_loc_spots) select_size = (self._size[0] / 4 - 20) / 2, min( self._size[1] / 4, 120) select_button_pos = (self._size[0] * 0.9 - select_size[0] * 2 - 15, self._size[1] * 0.9 - select_size[1]) self.select_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*select_button_pos, *select_size), text="SELECT", manager=self, object_id=pygame_gui.core.ObjectID("select-bot", "action_button"), ) self.addButtonEvent("select-bot", self.clickSelect) if not self.select_enable: self.select_button.disable() self._all_objs.append(self.select_button) done_size = (self._size[0] / 4 - 20) / 2, min( self._size[1] / 4, 120) done_button_pos = (self._size[0] * 0.9 - select_size[0] - 5, self._size[1] * 0.9 - select_size[1]) self.done_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect(*done_button_pos, *done_size), text="DONE", manager=self, object_id=pygame_gui.core.ObjectID("select-done", "action_button"), ) self.addButtonEvent("select-done", self.clickDone) if self.key_index == 0: self.done_button.disable() self._all_objs.append(self.done_button) super().generateObjects()
def loadMap(self, map_path): # load map custom_dir = find_abs_directory("workspace/custom/") full_path = join(custom_dir, "Memory Maze", map_path) with open(full_path, "r") as f: conf = yaml.safe_load(f) # Despawn old stuff for col in self.colours: ScreenObjectManager.instance.unregisterVisual(col.key) self.colours = [] for wall in self.walls: ScreenObjectManager.instance.unregisterVisual(wall.key) World.instance.unregisterObject(wall) self.walls = [] self.dimensions = conf["dimensions"] self.spawn = conf["spawn"] self.passcode = conf["passcode"] self.colour_map = conf["colours"] self.wall_map = conf["walls"] self.rotation = conf.get("rotation", 0) * math.pi / 180 # Spawn colours for x in range(self.dimensions[0]): for y in range(self.dimensions[1]): index = self.colour_map[y * self.dimensions[0] + x] if index == "_": continue c_key = f"movement_bot_colour-{x}-{y}" c_obj = visualFactory( name="Rectangle", width=self.width - self.margin, height=self.width - self.margin, position=[ self.width * (x - self.offset[0]), -self.width * (y - self.offset[1]), ], fill=self.COLOURS[index], sensorVisible=True, stroke_width=0, zPos=0.5, ) c_obj.key = c_key ScreenObjectManager.instance.registerVisual(c_obj, c_key) self.colours.append(c_obj) # Spawn walls # First, vertical for x in range(self.dimensions[0] + 1): for y in range(self.dimensions[1]): if self.wall_map[(y + 1) * self.dimensions[0] + y * (self.dimensions[0] + 1) + x] == "*": wall_key = f"movement_bot_wall-vert-{x}-{y}" wall_obj = objectFactory( visual={ "name": "Rectangle", "height": self.width, "width": 2, "stroke_width": 0, "fill": "#000000", "zPos": 0.7, }, position=[ (x - 0.5 - self.offset[0]) * self.width, -self.width * (y - self.offset[1]), ], rotation=0, physics=True, static=True, key=wall_key) self.walls.append(wall_obj) World.instance.registerObject(wall_obj) ScreenObjectManager.instance.registerObject( wall_obj, wall_key) # Next, horizontal for x in range(self.dimensions[0]): for y in range(self.dimensions[1] + 1): if self.wall_map[y * (2 * self.dimensions[0] + 1) + x] == "*": wall_key = f"movement_bot_wall-horizontal-{x}-{y}" wall_obj = objectFactory( visual={ "name": "Rectangle", "height": 2, "width": self.width, "stroke_width": 0, "fill": "#000000", "zPos": 0.7, }, position=[ (x - self.offset[0]) * self.width, -self.width * (y - self.offset[1] - 0.5), ], rotation=0, physics=True, static=True, key=wall_key) self.walls.append(wall_obj) World.instance.registerObject(wall_obj) ScreenObjectManager.instance.registerObject( wall_obj, wall_key)
def main(passed_args=None): if passed_args is None: args = parser.parse_args(sys.argv[1:]) else: # Just give some generic input args = parser.parse_args(["blank.yaml"]) args.__dict__.update(passed_args) # Useful for a few things. ev3sim_folder = find_abs_directory(config_locations()) # Step 1: Handling helper commands if args.version: import ev3sim print(f"Running ev3sim version {ev3sim.__version__}") return if args.open_config: from ev3sim.utils import open_file, APP_EXPLORER fname = join(ev3sim_folder, "user_config.yaml") open_file(fname, APP_EXPLORER) return # Step 2: Safely load configs and set up error reporting try: try: conf_file = find_abs("user_config.yaml", allowed_areas=config_locations()) except ValueError: # Config file can't be found # Can happen when this is a fresh install import shutil conf_dir = find_abs_directory(config_locations()) default_conf_file = find_abs("default_config.yaml", allowed_areas=["package/presets/"]) conf_file = join(conf_dir, "user_config.yaml") shutil.copyfile(default_conf_file, conf_file) with open(conf_file, "r") as f: conf = yaml.safe_load(f) handler = StateHandler() can_update = canUpdate() handler.setConfig(**conf) # If no workspace has been set, place it in the package directory. if not handler.WORKSPACE_FOLDER: handler.setConfig( **{ "app": { "workspace_folder": find_abs_directory(workspace_locations(), create=True), } }) except Exception as e: import traceback as tb error = "".join(tb.format_exception(None, e, e.__traceback__)) with open(join(ev3sim_folder, "error_log.txt"), "w") as f: f.write(error) print( f"An error occurred before sentry could load, check {join(ev3sim_folder, 'error_log.txt')}" ) sys.exit(1) if handler.SEND_CRASH_REPORTS: import sentry_sdk sentry_sdk.init( "https://[email protected]/5633878", release=__version__, ) # Step 3: Identify what screens we need to show and start up any other processes pushed_screens = [] pushed_kwargss = [{}] if args.elem: # We have been given a path of some sort as an argument, figure out what it is and run with it. # First, figure out what type it is. if args.elem[-1] in "/\\": args.elem = args.elem[:-1] if args.custom_url: import time import requests import zipfile from urllib.parse import urlparse zip_url = args.elem.replace("ev3simc://", "https://") # Save the temp file here. c_path = dirname(__file__) fn = basename(urlparse(zip_url).path) fn = fn if fn.strip() else f"dload{str(int(time.time()))[:5]}" zip_path = c_path + sep + fn r = requests.get(zip_url, verify=True) with open(zip_path, "wb") as f: f.write(r.content) extract_path = find_abs_directory("workspace/custom/") with zipfile.ZipFile(zip_path, "r") as zip_ref: name = zip_ref.namelist()[0].split("\\")[0].split("/")[0] zip_ref.extractall(extract_path) if isfile(zip_path): remove(zip_path) found = True pushed_screens = [ ScreenObjectManager.SCREEN_MENU, ScreenObjectManager.SCREEN_BATCH, ScreenObjectManager.SCREEN_UPDATE, ] def action(result): if not result: # Delete import shutil shutil.rmtree(join(extract_path, name)) pushed_kwargss = [ {}, { "selected": join(extract_path, name, "sim.sim") }, { "panels": [{ "type": "boolean", "text": ("Custom tasks downloaded from the internet can do <i>anything</i> to your computer." " Only use custom tasks from a developer you <b>trust</b>." ), "button_yes": "Accept", "button_no": "Delete", "action": action, }] }, ] elif not exists(args.elem): pushed_screens, pushed_kwargss = raise_error( f"Unknown path {args.elem}.") elif args.elem.endswith(".py") or args.elem.endswith(".ev3"): # Python file, either in bot or completely separate. folder = dirname(args.elem) config_path = join(folder, "config.bot") if exists(config_path): # We are in a bot directory. pushed_screens, pushed_kwargss = run_bot(folder, basename(args.elem), edit=args.edit) else: pushed_screens, pushed_kwargss = run_code(args.elem) elif args.elem.endswith(".bot"): # Bot file. folder = dirname(args.elem) pushed_screens, pushed_kwargss = run_bot(folder, edit=args.edit) elif args.elem.endswith(".sim"): pushed_screens, pushed_kwargss = run_sim(args.elem, edit=args.edit) else: # Some sort of folder. Either a bot folder, or custom task folder. config_path = join(args.elem, "config.bot") sim_path = join(args.elem, "sim.sim") if exists(config_path): pushed_screens, pushed_kwargss = run_bot(args.elem, edit=args.edit) elif exists(sim_path): pushed_screens, pushed_kwargss = run_sim(sim_path, edit=args.edit) else: pushed_screens, pushed_kwargss = raise_error( f"EV3Sim does not know how to open {args.elem}{' for editing' if args.edit else ''}." ) if can_update: saved_screens = pushed_screens saved_kwargs = pushed_kwargss def maybe_complete_update(should): if should: # Start the update process. import subprocess import sys import shutil lib = dirname(dirname(ev3sim_folder)) # First, save the user_config and workspace if appropriate. if exists(join(ev3sim_folder, "user_config.yaml")): shutil.copy(join(ev3sim_folder, "user_config.yaml"), join(lib, "user_config.yaml")) if exists(join(ev3sim_folder, "workspace")): if exists(join(lib, "workspace")): shutil.rmtree(join(lib, "workspace")) shutil.copytree(join(ev3sim_folder, "workspace"), join(lib, "workspace")) try: subprocess.check_call([ sys.executable, "-m", "pip", "install", "--trusted-host", "pypi.org", "--trusted-host", "files.pythonhosted.org", "ev3sim", ]) except: pass finally: # Move the user_config and workspace back. if exists(join(lib, "user_config.yaml")): shutil.move(join(lib, "user_config.yaml"), join(ev3sim_folder, "user_config.yaml")) if exists(join(lib, "workspace")): if exists(join(ev3sim_folder, "workspace")): shutil.rmtree(join(ev3sim_folder, "workspace")) shutil.move(join(lib, "workspace"), ev3sim_folder) else: # Revert to original start up. ScreenObjectManager.instance.screen_stack = [] for screen, kwargs in zip(saved_screens, saved_kwargs): ScreenObjectManager.instance.pushScreen(screen, **kwargs) if not saved_screens: ScreenObjectManager.instance.pushScreen( ScreenObjectManager.SCREEN_MENU) ScreenObjectManager.instance.screen_stack.append( ScreenObjectManager.SCREEN_UPDATE) pushed_screens = [ScreenObjectManager.SCREEN_UPDATE] pushed_kwargss = [{ "panels": [{ "text": "This installation of EV3Sim is outdated. It is <b>highly</b> recommend you update to ensure your experience is bug-free and contains the best features. Would you like to update?", "type": "boolean", "action": maybe_complete_update, }] }] try: StateHandler.instance.startUp(push_screens=pushed_screens, push_kwargss=pushed_kwargss) except WorkspaceError: pass updates = handle_updates() if updates: ScreenObjectManager.instance.pushScreen( ScreenObjectManager.instance.SCREEN_UPDATE, panels=updates, ) if args.debug: try: import debugpy debugpy.listen(15995) except RuntimeError as e: print("Warning: Couldn't start the debugger") # Step 4: Mainloop try: handler.mainLoop() except KeyboardInterrupt: pass except Exception as e: import sentry_sdk import traceback as tb error = "".join(tb.format_exception(None, e, e.__traceback__)) with open(join(ev3sim_folder, "error_log.txt"), "w") as f: f.write(error) print( f"An error occurred, check {join(ev3sim_folder, 'error_log.txt')} for details." ) sentry_sdk.capture_exception(e) pygame.quit() handler.is_running = False try: handler.closeProcesses() except: pass
def clickSave(self): if not self.creating: with open(self.filename, "r") as f: json_obj = yaml.safe_load(f) else: json_obj = self.starting_data current_filepath = None rel_file = None if not self.creating: current_filepath = self.filename for group in self.settings_obj: for obj in group["objects"]: if obj.json_keys == "__filename__" and isinstance( obj, TextEntry): if self.creating: # Make sure the name can be used. creation_dir = find_abs_directory(self.creation_area, create=True) current_filepath = os.path.join( creation_dir, f"{obj.obj.text}.{self.extension}") rel_file = f"{obj.obj.text}.{self.extension}" if os.path.exists(current_filepath): self.addErrorDialog( '<font color="#DD4045">A file with this name already exists.</font>' ) return False else: # Make sure the name can be used. end, front = os.path.split(self.filename) current_filepath = os.path.join( end, f"{obj.obj.text}.{self.extension}") if current_filepath != self.filename and os.path.exists( current_filepath): self.addErrorDialog( '<font color="#DD4045">A file with this name already exists.</font>' ) return False if current_filepath != self.filename: # Remove the previous file. os.remove(self.filename) else: obj.setToJson(json_obj) try: # If we are editing loaded settings, then apply the changes. settings = {} cur = settings prev = None for key in obj.json_keys: cur[key] = {} prev = cur cur = cur[key] prev[obj.json_keys[-1]] = obj.current SettingsManager.instance.setMany(settings) except: pass string = yaml.dump(json_obj) with open(current_filepath, "w") as f: f.write(string) from ev3sim.visual.manager import ScreenObjectManager ScreenObjectManager.instance.popScreen() if self.onSave is not None: self.onSave(rel_file) return True
def check_for_bot_files(): """v2.0.1 -> v2.1.0. Bots no longer config files, but folders with all information.""" import os from ev3sim.file_helper import find_abs_directory from ev3sim.search_locations import asset_locations try: old_bots = [] path = find_abs_directory("workspace/robots/") for file in os.listdir(path): if os.path.isfile(os.path.join(path, file)) and file.endswith(".bot"): # Bad time old_bots.append(file) def action(): import yaml """Go through and try fixing the bots.""" for bot in old_bots: dirpath = os.path.join(path, bot[:-4]) # Folder if os.path.isdir(dirpath): import shutil shutil.rmtree(dirpath) os.mkdir(dirpath) # config.bot with open(os.path.join(path, bot), "r") as f: config = yaml.safe_load(f) bot_script = config.get("script", "code.py") preview_image = config.get("preview_path", "preview.png") for keyword in ["script", "preview_path"]: if keyword in config: del config[keyword] with open(os.path.join(dirpath, "config.bot"), "w") as f: f.write(yaml.dump(config)) # code.py try: code_path = os.path.join( find_abs_directory("workspace/code/"), bot_script) with open(code_path, "r") as f: code = f.read() except: code = "" with open(os.path.join(dirpath, "code.py"), "w") as f: f.write(code) # preview.png try: preview = find_abs(preview_image, asset_locations()) with open(preview, "rb") as f: preview_data = f.read() except: preview_data = bytes() with open(os.path.join(dirpath, "preview.png"), "wb") as f: f.write(preview_data) # Remove the old bot os.remove(os.path.join(path, bot)) # Additionally, we need to update all sim files to no longer use the .bot prefix actual_dir = find_abs_directory("workspace/sims/") for file in os.listdir(actual_dir): if os.path.isfile(os.path.join( actual_dir, file)) and file.endswith(".sim"): with open(os.path.join(actual_dir, file), "r") as f: config = yaml.safe_load(f) for x in range(len(config.get("bots", []))): if config["bots"][x].endswith(".bot"): config["bots"][x] = config["bots"][x][:-4] with open(os.path.join(actual_dir, file), "w") as f: f.write(yaml.dump(config)) if old_bots: return { "text": ('Since you\'ve last used EV3Sim, the <font color="#4cc9f0">bot format</font> has changed.<br><br>' + 'EV3Sim will now fix your current bots to use the new format (code and images will now appear in the <font color="#4cc9f0">bots</font> folder).' ), "type": "accept", "button": "Convert", "action": action, } except: pass return None