From 3dd8313844179454a2697c03c0181f6817530d5d Mon Sep 17 00:00:00 2001 From: Florian Vahl Date: Fri, 22 Dec 2023 00:44:07 +0100 Subject: [PATCH] Make ci happy --- .../soccer_field_map_generator/cli.py | 49 ++- .../soccer_field_map_generator/generator.py | 125 +++--- .../soccer_field_map_generator/gui.py | 359 +++++++++--------- .../soccer_field_map_generator/tooltip.py | 12 +- 4 files changed, 294 insertions(+), 251 deletions(-) diff --git a/soccer_field_map_generator/soccer_field_map_generator/cli.py b/soccer_field_map_generator/soccer_field_map_generator/cli.py index d3f5166..161a0be 100644 --- a/soccer_field_map_generator/soccer_field_map_generator/cli.py +++ b/soccer_field_map_generator/soccer_field_map_generator/cli.py @@ -1,42 +1,57 @@ -import sys -import os -import cv2 -import yaml +# Copyright (c) 2023 Hamburg Bit-Bots +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import argparse +import os +import sys +import cv2 from soccer_field_map_generator.generator import ( generate_map_image, generate_metadata, load_config_file, ) +import yaml def main(): - parser = argparse.ArgumentParser(description="Generate maps for localization") - parser.add_argument("output", help="Output file name") + parser = argparse.ArgumentParser(description='Generate maps for localization') + parser.add_argument('output', help='Output file name') parser.add_argument( - "config", - help="Config file for the generator that specifies the parameters for the map generation", + 'config', + help='Config file for the generator that specifies the parameters for the map generation', ) parser.add_argument( - "--metadata", + '--metadata', help="Also generates a 'map_server.yaml' file with the metadata for the map", - action="store_true", + action='store_true', ) args = parser.parse_args() # Check if the config file exists if not os.path.isfile(args.config): - print("Config file does not exist") + print('Config file does not exist') sys.exit(1) # Load config file - with open(args.config, "r") as config_file: + with open(args.config, 'r') as config_file: parameters = load_config_file(config_file) # Check if the config file is valid if parameters is None: - print("Invalid config file") + print('Invalid config file') sys.exit(1) # Generate the map image @@ -47,7 +62,7 @@ def main(): # Check if the output folder exists if not os.path.isdir(os.path.dirname(output_path)): - print("Output folder does not exist") + print('Output folder does not exist') sys.exit(1) # Save the image @@ -57,11 +72,11 @@ def main(): if args.metadata: metadata = generate_metadata(parameters, os.path.basename(output_path)) metadata_file_name = os.path.join( - os.path.dirname(output_path), "map_server.yaml" + os.path.dirname(output_path), 'map_server.yaml' ) - with open(metadata_file_name, "w") as metadata_file: + with open(metadata_file_name, 'w') as metadata_file: yaml.dump(metadata, metadata_file, sort_keys=False) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/soccer_field_map_generator/soccer_field_map_generator/generator.py b/soccer_field_map_generator/soccer_field_map_generator/generator.py index aba38a8..2bcb768 100755 --- a/soccer_field_map_generator/soccer_field_map_generator/generator.py +++ b/soccer_field_map_generator/soccer_field_map_generator/generator.py @@ -1,33 +1,46 @@ -#!/usr/bin/env python3 +# Copyright (c) 2023 Hamburg Bit-Bots +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import Enum import math -import yaml +from typing import Optional import cv2 import numpy as np -from typing import Optional from scipy import ndimage -from enum import Enum +import yaml class MapTypes(Enum): - LINE = "line" - POSTS = "posts" - FIELD_BOUNDARY = "field_boundary" - FIELD_FEATURES = "field_features" - CORNERS = "corners" - TCROSSINGS = "tcrossings" - CROSSES = "crosses" + LINE = 'line' + POSTS = 'posts' + FIELD_BOUNDARY = 'field_boundary' + FIELD_FEATURES = 'field_features' + CORNERS = 'corners' + TCROSSINGS = 'tcrossings' + CROSSES = 'crosses' class MarkTypes(Enum): - POINT = "point" - CROSS = "cross" + POINT = 'point' + CROSS = 'cross' class FieldFeatureStyles(Enum): - EXACT = "exact" - BLOB = "blob" + EXACT = 'exact' + BLOB = 'blob' def drawCross(img, point, color, width=5, length=15): @@ -62,30 +75,30 @@ def drawDistance(image, decay_factor): def generate_map_image(parameters): - target = MapTypes(parameters["map_type"]) - mark_type = MarkTypes(parameters["mark_type"]) - field_feature_style = FieldFeatureStyles(parameters["field_feature_style"]) - - penalty_mark = parameters["penalty_mark"] - center_point = parameters["center_point"] - goal_back = parameters["goal_back"] # Draw goal back area - - stroke_width = parameters["stroke_width"] - field_length = parameters["field_length"] - field_width = parameters["field_width"] - goal_depth = parameters["goal_depth"] - goal_width = parameters["goal_width"] - goal_area_length = parameters["goal_area_length"] - goal_area_width = parameters["goal_area_width"] - penalty_mark_distance = parameters["penalty_mark_distance"] - center_circle_diameter = parameters["center_circle_diameter"] - border_strip_width = parameters["border_strip_width"] - penalty_area_length = parameters["penalty_area_length"] - penalty_area_width = parameters["penalty_area_width"] - field_feature_size = parameters["field_feature_size"] - distance_map = parameters["distance_map"] - distance_decay = parameters["distance_decay"] - invert = parameters["invert"] + target = MapTypes(parameters['map_type']) + mark_type = MarkTypes(parameters['mark_type']) + field_feature_style = FieldFeatureStyles(parameters['field_feature_style']) + + penalty_mark = parameters['penalty_mark'] + center_point = parameters['center_point'] + goal_back = parameters['goal_back'] # Draw goal back area + + stroke_width = parameters['stroke_width'] + field_length = parameters['field_length'] + field_width = parameters['field_width'] + goal_depth = parameters['goal_depth'] + goal_width = parameters['goal_width'] + goal_area_length = parameters['goal_area_length'] + goal_area_width = parameters['goal_area_width'] + penalty_mark_distance = parameters['penalty_mark_distance'] + center_circle_diameter = parameters['center_circle_diameter'] + border_strip_width = parameters['border_strip_width'] + penalty_area_length = parameters['penalty_area_length'] + penalty_area_width = parameters['penalty_area_width'] + field_feature_size = parameters['field_feature_size'] + distance_map = parameters['distance_map'] + distance_decay = parameters['distance_decay'] + invert = parameters['invert'] # Color of the lines and marks color = (255, 255, 255) # white @@ -197,7 +210,7 @@ def generate_map_image(parameters): elif mark_type == MarkTypes.CROSS: drawCross(img, middle_point, color, stroke_width) else: - raise NotImplementedError("Mark type not implemented") + raise NotImplementedError('Mark type not implemented') # Draw penalty marks (point or cross) if penalty_mark: @@ -208,7 +221,7 @@ def generate_map_image(parameters): drawCross(img, penalty_mark_left, color, stroke_width) drawCross(img, penalty_mark_right, color, stroke_width) else: - raise NotImplementedError("Mark type not implemented") + raise NotImplementedError('Mark type not implemented') # Draw goal area img = cv2.rectangle( @@ -657,10 +670,10 @@ def generate_map_image(parameters): def generate_metadata(parameters: dict, image_name: str) -> dict: # Get the field dimensions in cm field_dimensions = np.array( - [parameters["field_length"], parameters["field_width"], 0] + [parameters['field_length'], parameters['field_width'], 0] ) # Add the border strip - field_dimensions[:2] += 2 * parameters["border_strip_width"] + field_dimensions[:2] += 2 * parameters['border_strip_width'] # Get the origin origin = -field_dimensions / 2 # Convert to meters @@ -668,12 +681,12 @@ def generate_metadata(parameters: dict, image_name: str) -> dict: # Generate the metadata return { - "image": image_name, - "resolution": 0.01, - "origin": origin.tolist(), - "occupied_thresh": 0.99, - "free_thresh": 0.196, - "negate": int(parameters["invert"]), + 'image': image_name, + 'resolution': 0.01, + 'origin': origin.tolist(), + 'occupied_thresh': 0.99, + 'free_thresh': 0.196, + 'negate': int(parameters['invert']), } @@ -682,11 +695,11 @@ def load_config_file(file) -> Optional[dict]: config_file = yaml.load(file, Loader=yaml.FullLoader) # Check if the file is valid (has the correct fields) if ( - "header" in config_file - and "type" in config_file["header"] - and "version" in config_file["header"] - and "parameters" in config_file - and config_file["header"]["version"] == "1.0" - and config_file["header"]["type"] == "map_generator_config" + 'header' in config_file + and 'type' in config_file['header'] + and 'version' in config_file['header'] + and 'parameters' in config_file + and config_file['header']['version'] == '1.0' + and config_file['header']['type'] == 'map_generator_config' ): - return config_file["parameters"] + return config_file['parameters'] diff --git a/soccer_field_map_generator/soccer_field_map_generator/gui.py b/soccer_field_map_generator/soccer_field_map_generator/gui.py index 53fc0ac..b6eec78 100644 --- a/soccer_field_map_generator/soccer_field_map_generator/gui.py +++ b/soccer_field_map_generator/soccer_field_map_generator/gui.py @@ -1,21 +1,36 @@ -import cv2 +# Copyright (c) 2023 Hamburg Bit-Bots +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from enum import Enum import os import tkinter as tk -import yaml -from enum import Enum -from PIL import Image, ImageTk from tkinter import filedialog from tkinter import ttk +import cv2 +from PIL import Image, ImageTk from soccer_field_map_generator.generator import ( - MapTypes, - MarkTypes, FieldFeatureStyles, generate_map_image, generate_metadata, load_config_file, + MapTypes, + MarkTypes, ) from soccer_field_map_generator.tooltip import Tooltip +import yaml class MapGeneratorParamInput(tk.Frame): @@ -32,36 +47,36 @@ def __init__( # Generate GUI elements for all parameters for parameter_name, parameter_definition in parameter_definitions.items(): # Create GUI elements - label = ttk.Label(self, text=parameter_definition["label"]) - if parameter_definition["type"] == bool: - variable = tk.BooleanVar(value=parameter_definition["default"]) + label = ttk.Label(self, text=parameter_definition['label']) + if parameter_definition['type'] == bool: + variable = tk.BooleanVar(value=parameter_definition['default']) ui_element = ttk.Checkbutton( self, command=update_hook, variable=variable ) - elif parameter_definition["type"] == int: - variable = tk.IntVar(value=parameter_definition["default"]) + elif parameter_definition['type'] == int: + variable = tk.IntVar(value=parameter_definition['default']) ui_element = ttk.Entry(self, textvariable=variable) - ui_element.bind("", update_hook) - elif parameter_definition["type"] == float: - variable = tk.DoubleVar(value=parameter_definition["default"]) + ui_element.bind('', update_hook) + elif parameter_definition['type'] == float: + variable = tk.DoubleVar(value=parameter_definition['default']) ui_element = ttk.Entry(self, textvariable=variable) - ui_element.bind("", update_hook) - elif issubclass(parameter_definition["type"], Enum): - variable = tk.StringVar(value=parameter_definition["default"].value) - values = [enum.value for enum in parameter_definition["type"]] + ui_element.bind('', update_hook) + elif issubclass(parameter_definition['type'], Enum): + variable = tk.StringVar(value=parameter_definition['default'].value) + values = [enum.value for enum in parameter_definition['type']] ui_element = ttk.Combobox(self, values=values, textvariable=variable) - ui_element.bind("<>", update_hook) + ui_element.bind('<>', update_hook) else: - raise NotImplementedError("Parameter type not implemented") + raise NotImplementedError('Parameter type not implemented') # Add tooltips - Tooltip(label, text=parameter_definition["tooltip"]) - Tooltip(ui_element, text=parameter_definition["tooltip"]) + Tooltip(label, text=parameter_definition['tooltip']) + Tooltip(ui_element, text=parameter_definition['tooltip']) # Add ui elements to the dict self.parameter_ui_elements[parameter_name] = { - "label": label, - "ui_element": ui_element, + 'label': label, + 'ui_element': ui_element, } # Store variable for later state access @@ -69,11 +84,11 @@ def __init__( # Create layout for i, parameter_name in enumerate(parameter_definitions.keys()): - self.parameter_ui_elements[parameter_name]["label"].grid( - row=i, column=0, sticky="e" + self.parameter_ui_elements[parameter_name]['label'].grid( + row=i, column=0, sticky='e' ) - self.parameter_ui_elements[parameter_name]["ui_element"].grid( - row=i, column=1, sticky="w" + self.parameter_ui_elements[parameter_name]['ui_element'].grid( + row=i, column=1, sticky='w' ) def get_parameters(self): @@ -90,18 +105,18 @@ class MapGeneratorGUI: def __init__(self, root: tk.Tk): # Set ttk theme s = ttk.Style() - s.theme_use("clam") + s.theme_use('clam') # Set window title and size self.root = root - self.root.title("Map Generator GUI") + self.root.title('Map Generator GUI') self.root.resizable(False, False) # Create GUI elements # Title self.title = ttk.Label( - self.root, text="Soccer Map Generator", font=("TkDefaultFont", 16) + self.root, text='Soccer Map Generator', font=('TkDefaultFont', 16) ) # Parameter Input @@ -109,158 +124,158 @@ def __init__(self, root: tk.Tk): self.root, self.update_map, { - "map_type": { - "type": MapTypes, - "default": MapTypes.LINE, - "label": "Map Type", - "tooltip": "Type of the map we want to generate", + 'map_type': { + 'type': MapTypes, + 'default': MapTypes.LINE, + 'label': 'Map Type', + 'tooltip': 'Type of the map we want to generate', }, - "penalty_mark": { - "type": bool, - "default": True, - "label": "Penalty Mark", - "tooltip": "Whether or not to draw the penalty mark", + 'penalty_mark': { + 'type': bool, + 'default': True, + 'label': 'Penalty Mark', + 'tooltip': 'Whether or not to draw the penalty mark', }, - "center_point": { - "type": bool, - "default": True, - "label": "Center Point", - "tooltip": "Whether or not to draw the center point", + 'center_point': { + 'type': bool, + 'default': True, + 'label': 'Center Point', + 'tooltip': 'Whether or not to draw the center point', }, - "goal_back": { - "type": bool, - "default": True, - "label": "Goal Back", - "tooltip": "Whether or not to draw the back area of the goal", + 'goal_back': { + 'type': bool, + 'default': True, + 'label': 'Goal Back', + 'tooltip': 'Whether or not to draw the back area of the goal', }, - "stroke_width": { - "type": int, - "default": 5, - "label": "Stoke Width", - "tooltip": "Width (in px) of the shapes we draw", + 'stroke_width': { + 'type': int, + 'default': 5, + 'label': 'Stoke Width', + 'tooltip': 'Width (in px) of the shapes we draw', }, - "field_length": { - "type": int, - "default": 900, - "label": "Field Length", - "tooltip": "Length of the field in cm", + 'field_length': { + 'type': int, + 'default': 900, + 'label': 'Field Length', + 'tooltip': 'Length of the field in cm', }, - "field_width": { - "type": int, - "default": 600, - "label": "Field Width", - "tooltip": "Width of the field in cm", + 'field_width': { + 'type': int, + 'default': 600, + 'label': 'Field Width', + 'tooltip': 'Width of the field in cm', }, - "goal_depth": { - "type": int, - "default": 60, - "label": "Goal Depth", - "tooltip": "Depth of the goal in cm", + 'goal_depth': { + 'type': int, + 'default': 60, + 'label': 'Goal Depth', + 'tooltip': 'Depth of the goal in cm', }, - "goal_width": { - "type": int, - "default": 260, - "label": "Goal Width", - "tooltip": "Width of the goal in cm", + 'goal_width': { + 'type': int, + 'default': 260, + 'label': 'Goal Width', + 'tooltip': 'Width of the goal in cm', }, - "goal_area_length": { - "type": int, - "default": 100, - "label": "Goal Area Length", - "tooltip": "Length of the goal area in cm", + 'goal_area_length': { + 'type': int, + 'default': 100, + 'label': 'Goal Area Length', + 'tooltip': 'Length of the goal area in cm', }, - "goal_area_width": { - "type": int, - "default": 300, - "label": "Goal Area Width", - "tooltip": "Width of the goal area in cm", + 'goal_area_width': { + 'type': int, + 'default': 300, + 'label': 'Goal Area Width', + 'tooltip': 'Width of the goal area in cm', }, - "penalty_mark_distance": { - "type": int, - "default": 150, - "label": "Penalty Mark Distance", - "tooltip": "Distance of the penalty mark from the goal line in cm", + 'penalty_mark_distance': { + 'type': int, + 'default': 150, + 'label': 'Penalty Mark Distance', + 'tooltip': 'Distance of the penalty mark from the goal line in cm', }, - "center_circle_diameter": { - "type": int, - "default": 150, - "label": "Center Circle Diameter", - "tooltip": "Diameter of the center circle in cm", + 'center_circle_diameter': { + 'type': int, + 'default': 150, + 'label': 'Center Circle Diameter', + 'tooltip': 'Diameter of the center circle in cm', }, - "border_strip_width": { - "type": int, - "default": 100, - "label": "Border Strip Width", - "tooltip": "Width of the border strip around the field in cm", + 'border_strip_width': { + 'type': int, + 'default': 100, + 'label': 'Border Strip Width', + 'tooltip': 'Width of the border strip around the field in cm', }, - "penalty_area_length": { - "type": int, - "default": 200, - "label": "Penalty Area Length", - "tooltip": "Length of the penalty area in cm", + 'penalty_area_length': { + 'type': int, + 'default': 200, + 'label': 'Penalty Area Length', + 'tooltip': 'Length of the penalty area in cm', }, - "penalty_area_width": { - "type": int, - "default": 500, - "label": "Penalty Area Width", - "tooltip": "Width of the penalty area in cm", + 'penalty_area_width': { + 'type': int, + 'default': 500, + 'label': 'Penalty Area Width', + 'tooltip': 'Width of the penalty area in cm', }, - "field_feature_size": { - "type": int, - "default": 30, - "label": "Field Feature Size", - "tooltip": "Size of the field features in cm", + 'field_feature_size': { + 'type': int, + 'default': 30, + 'label': 'Field Feature Size', + 'tooltip': 'Size of the field features in cm', }, - "mark_type": { - "type": MarkTypes, - "default": MarkTypes.CROSS, - "label": "Mark Type", - "tooltip": "Type of the marks (penalty mark, center point)", + 'mark_type': { + 'type': MarkTypes, + 'default': MarkTypes.CROSS, + 'label': 'Mark Type', + 'tooltip': 'Type of the marks (penalty mark, center point)', }, - "field_feature_style": { - "type": FieldFeatureStyles, - "default": FieldFeatureStyles.EXACT, - "label": "Field Feature Style", - "tooltip": "Style of the field features", + 'field_feature_style': { + 'type': FieldFeatureStyles, + 'default': FieldFeatureStyles.EXACT, + 'label': 'Field Feature Style', + 'tooltip': 'Style of the field features', }, - "distance_map": { - "type": bool, - "default": False, - "label": "Distance Map", - "tooltip": "Whether or not to draw the distance map", + 'distance_map': { + 'type': bool, + 'default': False, + 'label': 'Distance Map', + 'tooltip': 'Whether or not to draw the distance map', }, - "distance_decay": { - "type": float, - "default": 0.0, - "label": "Distance Decay", - "tooltip": "Exponential decay applied to the distance map", + 'distance_decay': { + 'type': float, + 'default': 0.0, + 'label': 'Distance Decay', + 'tooltip': 'Exponential decay applied to the distance map', }, - "invert": { - "type": bool, - "default": True, - "label": "Invert", - "tooltip": "Invert the final image", + 'invert': { + 'type': bool, + 'default': True, + 'label': 'Invert', + 'tooltip': 'Invert the final image', }, }, ) # Generate Map Button self.save_map_button = ttk.Button( - self.root, text="Save Map", command=self.save_map + self.root, text='Save Map', command=self.save_map ) # Save metadata checkbox self.save_metadata = tk.BooleanVar(value=True) self.save_metadata_checkbox = ttk.Checkbutton( - self.root, text="Save Metadata", variable=self.save_metadata + self.root, text='Save Metadata', variable=self.save_metadata ) # Load and save config buttons self.load_config_button = ttk.Button( - self.root, text="Load Config", command=self.load_config + self.root, text='Load Config', command=self.load_config ) self.save_config_button = ttk.Button( - self.root, text="Save Config", command=self.save_config + self.root, text='Save Config', command=self.save_config ) # Canvas to display the generated map @@ -289,16 +304,16 @@ def __init__(self, root: tk.Tk): def load_config(self): # Prompt the user to select a file (force yaml) file = filedialog.askopenfile( - mode="r", - defaultextension=".yaml", - filetypes=(("yaml file", "*.yaml"), ("All Files", "*.*")), + mode='r', + defaultextension='.yaml', + filetypes=(('yaml file', '*.yaml'), ('All Files', '*.*')), ) if file: # Load the config file config = load_config_file(file) if config is None: # Show error box and return if the file is invalid - tk.messagebox.showerror("Error", "Invalid config file") + tk.messagebox.showerror('Error', 'Invalid config file') return # Set the parameters in the gui for parameter_name, parameter_value in config.items(): @@ -313,33 +328,33 @@ def save_config(self): parameters = self.parameter_input.get_parameters() # Open a file dialog to select the file file = filedialog.asksaveasfile( - mode="w", - defaultextension=".yaml", - filetypes=(("yaml file", "*.yaml"), ("All Files", "*.*")), + mode='w', + defaultextension='.yaml', + filetypes=(('yaml file', '*.yaml'), ('All Files', '*.*')), ) if file: # Add header - file.write("# Map Generator Config\n") - file.write("# This file was generated by the map generator GUI\n\n") + file.write('# Map Generator Config\n') + file.write('# This file was generated by the map generator GUI\n\n') # Save the parameters in this format: yaml.dump( { - "header": {"version": "1.0", "type": "map_generator_config"}, - "parameters": parameters, + 'header': {'version': '1.0', 'type': 'map_generator_config'}, + 'parameters': parameters, }, file, sort_keys=False, ) - print(f"Saved config to {file.name}") + print(f'Saved config to {file.name}') def save_map(self): file = filedialog.asksaveasfile( - mode="w", - defaultextension=".png", - filetypes=(("png file", "*.png"), ("All Files", "*.*")), + mode='w', + defaultextension='.png', + filetypes=(('png file', '*.png'), ('All Files', '*.*')), ) if file: - print(f"Saving map to {file.name}") + print(f'Saving map to {file.name}') # Generate and save the map parameters = self.parameter_input.get_parameters() @@ -353,28 +368,28 @@ def save_map(self): ) # Save metadata in the same folder as the map metadata_file = os.path.join( - os.path.dirname(file.name), "map_server.yaml" + os.path.dirname(file.name), 'map_server.yaml' ) - with open(metadata_file, "w") as f: + with open(metadata_file, 'w') as f: yaml.dump(metadata, f, sort_keys=False) - print(f"Saved metadata to {metadata_file}") + print(f'Saved metadata to {metadata_file}') # Show success box and ask if we want to open it with the default image viewer if tk.messagebox.askyesno( - "Success", "Map saved successfully. Do you want to open it?" + 'Success', 'Map saved successfully. Do you want to open it?' ): import platform import subprocess - if platform.system() == "Windows": - subprocess.Popen(["start", file.name], shell=True) - elif platform.system() == "Darwin": - subprocess.Popen(["open", file.name]) + if platform.system() == 'Windows': + subprocess.Popen(['start', file.name], shell=True) + elif platform.system() == 'Darwin': + subprocess.Popen(['open', file.name]) else: - subprocess.Popen(["xdg-open", file.name]) + subprocess.Popen(['xdg-open', file.name]) else: # Show error box - tk.messagebox.showerror("Error", "Could not save map to file") + tk.messagebox.showerror('Error', 'Could not save map to file') def update_map(self, *args): # Generate and display the map on the canvas @@ -403,5 +418,5 @@ def main(): root.mainloop() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/soccer_field_map_generator/soccer_field_map_generator/tooltip.py b/soccer_field_map_generator/soccer_field_map_generator/tooltip.py index b92970f..c71ac0a 100644 --- a/soccer_field_map_generator/soccer_field_map_generator/tooltip.py +++ b/soccer_field_map_generator/soccer_field_map_generator/tooltip.py @@ -36,9 +36,9 @@ def __init__( self, widget, *, - bg="#FFFFEA", + bg='#FFFFEA', pad=(5, 3, 5, 3), - text="widget info", + text='widget info', waittime=400, wraplength=250 ): @@ -46,9 +46,9 @@ def __init__( self.wraplength = wraplength # in pixels, originally 180 self.widget = widget self.text = text - self.widget.bind("", self.onEnter) - self.widget.bind("", self.onLeave) - self.widget.bind("", self.onLeave) + self.widget.bind('', self.onEnter) + self.widget.bind('', self.onLeave) + self.widget.bind('', self.onLeave) self.bg = bg self.pad = pad self.id = None @@ -142,7 +142,7 @@ def tip_pos_calculator(widget, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): x, y = tip_pos_calculator(widget, label) - self.tw.wm_geometry("+%d+%d" % (x, y)) + self.tw.wm_geometry('+%d+%d' % (x, y)) def hide(self): tw = self.tw