예제 #1
0
    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")
예제 #2
0
        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))
예제 #3
0
    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")),
            )
예제 #4
0
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)
예제 #6
0
    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,
        )
예제 #7
0
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
예제 #8
0
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]
예제 #9
0
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
예제 #10
0
 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)
예제 #11
0
    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()
예제 #12
0
    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)
예제 #13
0
    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()
예제 #14
0
    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)
예제 #16
0
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
예제 #17
0
    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
예제 #18
0
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