From 6e5c212ad2199abce03ea3cccc97b6c9b48a53e9 Mon Sep 17 00:00:00 2001 From: thomahl <thomahl@stud.ntnu.no> Date: Fri, 13 Mar 2020 15:26:19 +0100 Subject: [PATCH] =?UTF-8?q?#54=20St=C3=B8tte=20=20for=20"listeners"=20i=20?= =?UTF-8?q?SOI,=20og=20ModuleList=20bruker=20dette?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - økte max attributes av samme grunn den ble økt i utgangspunktet: siden vårt prosjekt er tidsbegrenset og ikke særlig stort syns vi det er greit med store klasser.. - Div. oppdateringer til errortekst - InlineEdit view stoler nå på at SOI __init__ gjør reorganize - Black gjorde noen stil-endringer.. - ModuleList vil nå respektere sortering i SOI om SOI er innstilt til å gjøre sortering. Om SOI ikke instilles til dette vil drag-and-drop prioritering enda fungere - initiell sortering av moduler er nå ikke gjort av rectpack, men av oss. På den måten kan vi etterhvert støtte delvis sortering --- scripts/.pylintrc | 2 +- soitool/inline_editable_soi_view.py | 4 +- soitool/module_list.py | 59 +++++++++--- soitool/soi.py | 142 ++++++++++++++++++++++++---- 4 files changed, 175 insertions(+), 32 deletions(-) diff --git a/scripts/.pylintrc b/scripts/.pylintrc index c818814..787b301 100644 --- a/scripts/.pylintrc +++ b/scripts/.pylintrc @@ -514,7 +514,7 @@ valid-metaclass-classmethod-first-arg=cls max-args=5 # Maximum number of attributes for a class (see R0902). -max-attributes=15 +max-attributes=20 # Maximum number of boolean expressions in an if statement. max-bool-expr=5 diff --git a/soitool/inline_editable_soi_view.py b/soitool/inline_editable_soi_view.py index 1d1a459..0a71a1f 100644 --- a/soitool/inline_editable_soi_view.py +++ b/soitool/inline_editable_soi_view.py @@ -46,7 +46,7 @@ class InlineEditableSOIView(QScrollArea): except ModuleLargerThanBinError: error_message = QMessageBox() error_message.setText( - "En av modulene er for store for å passe på én side!" + "Minst én av modulene er for store for å passe på én side!" ) error_message.setInformativeText( "Programmet kan desverre ikke fikse dette for deg. Se " @@ -76,7 +76,7 @@ class InlineEditableSOIView(QScrollArea): self.setWidget(self.view) self.ensure_proxies() - self.reorganize_soi_and_update_pages() + self.update_pages() # self.launch_auto_zoom() diff --git a/soitool/module_list.py b/soitool/module_list.py index d8c8125..a6ac18c 100644 --- a/soitool/module_list.py +++ b/soitool/module_list.py @@ -1,6 +1,11 @@ """Includes functionality for displaying a prioritized list of modules.""" from PySide2.QtCore import Qt -from PySide2.QtWidgets import QListWidget, QListWidgetItem, QAbstractItemView +from PySide2.QtWidgets import ( + QListWidget, + QListWidgetItem, + QAbstractItemView, + QMessageBox, +) from soitool.soi import ModuleType import soitool @@ -26,11 +31,14 @@ class ModuleList(QListWidget): super().__init__() # full import path below to avoid circular dependency - if not isinstance(parent, - soitool.soi_workspace_widget.SOIWorkspaceWidget): - raise RuntimeError('Only soitool.SOIWorkspaceWidget is ' - 'acceptable type for parent-variable ' - 'in class Module_list.') + if not isinstance( + parent, soitool.soi_workspace_widget.SOIWorkspaceWidget + ): + raise RuntimeError( + "Only soitool.SOIWorkspaceWidget is " + "acceptable type for parent-variable " + "in class Module_list." + ) self.type = module_type self.parent = parent self.original_element_name = None @@ -39,6 +47,8 @@ class ModuleList(QListWidget): self.setup_list() self.fill_list() + self.parent.soi.add_reorganization_listener(self.update_list_order) + def setup_list(self): """Prepare list. @@ -53,20 +63,28 @@ class ModuleList(QListWidget): self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) def fill_list(self): - """Fill list with elements.""" + """Fill list with elements in order defined in SOI.""" # Get names of modules/attachments: if ModuleType(self.type) == ModuleType.MAIN_MODULE: - names = [module["meta"]["name"] for - module in self.parent.soi.modules] + names = [ + module["meta"]["name"] for module in self.parent.soi.modules + ] elif ModuleType(self.type) == ModuleType.ATTACHMENT_MODULE: - names = [attachment["meta"]["name"] for - attachment in self.parent.soi.attachments] + names = [ + attachment["meta"]["name"] + for attachment in self.parent.soi.attachments + ] for i, name in enumerate(names): item = QListWidgetItem(name) item.setFlags(item.flags() | Qt.ItemIsEditable) self.insertItem(i, item) + def update_list_order(self): + """Update order of modules in list to respect order in SOI.""" + self.clear() + self.fill_list() + def currentChanged(self, current, previous): """Save name and index of an element when it is selected. @@ -126,6 +144,9 @@ class ModuleList(QListWidget): def dropEvent(self, event): """Notify parent when an element is drag-and-dropped. + Note that if the SOI is not prepared for manual priorization an error + message will be displayed, and nothing else will be done. + https://doc.qt.io/qt-5/qabstractitemview.html#dropEvent. Parameters @@ -133,6 +154,22 @@ class ModuleList(QListWidget): event : QDropEvent Is sent to super(). """ + if self.parent.soi.algorithm_sort != "none": + error_message = QMessageBox() + error_message.setText( + "SOI er ikke innstilt for automatisk plassering av moduler!" + ) + error_message.setInformativeText( + "Enn så lenge vil manuell prioritering av moduler bare " + "fungere dersom SOI er innstilt til ikke å gjøre sortering av " + "moduler før pakking. I fremtiden vil det være mulig å låse " + "utvalgte moduler, men la resten optimalt pakkes." + ) + error_message.setIcon(QMessageBox.Warning) + error_message.exec_() + # ignorerer dropEvent ved å returnere tidlig + return + super().dropEvent(event) # Get origin and destination index of module/attachment diff --git a/soitool/soi.py b/soitool/soi.py index b8ac743..a7de02e 100644 --- a/soitool/soi.py +++ b/soitool/soi.py @@ -5,7 +5,6 @@ from PySide2.QtCore import QPoint from rectpack import ( newPacker, SORT_NONE, - SORT_AREA, PackingMode, PackingBin, maxrects, @@ -14,20 +13,98 @@ from rectpack import ( ) from soitool.modules.module_table import TableModule -RECTPACK_STRING_TO_ALGORITHM_BIN = { +# functions to sort modules by different criteria + + +def modules_sort_by_none(modules): + """Don't sort. Implemented for completeness. See SOI.sort_modules. + + Parameters + ---------- + modules : list of modules + + Returns + ------- + list of modules, untouched + """ + return modules + + +def modules_sort_by_area(modules): + """Sort modules by area. See SOI.sort_modules. + + Parameters + ---------- + modules : list of modules + + Returns + ------- + list of modules sorted by area + """ + + def module_area_key(module): + width, height = module["widget"].get_size() + return width * height + + return sorted(modules, key=module_area_key, reverse=True) + + +def modules_sort_by_width(modules): + """Sort modules by width. See SOI.sort_modules. + + Parameters + ---------- + modules : list of modules + + Returns + ------- + list of modules sorted by width + """ + + def module_width_key(module): + width, _ = module["widget"].get_size() + return width + + return sorted(modules, key=module_width_key, reverse=True) + + +def modules_sort_by_height(modules): + """Sort modules by height. See SOI.sort_modules. + + Parameters + ---------- + modules : list of modules + + Returns + ------- + list of modules sorted by height + """ + + def module_height_key(module): + _, height = module["widget"].get_size() + return height + + return sorted(modules, key=module_height_key, reverse=True) + + +# dicts that map between name of algorithm and implementation of algorithm + +STRING_TO_ALGORITHM_RECTPACK_BIN = { "BFF": PackingBin.BFF, "BBF": PackingBin.BBF, } -RECTPACK_STRING_TO_ALGORITHM_PACK = { +STRING_TO_ALGORITHM_RECTPACK_PACK = { "MaxRectsBl": maxrects.MaxRectsBl, "SkylineBl": skyline.SkylineBl, "GuillotineBssfSas": guillotine.GuillotineBssfSas, } -RECTPACK_STRING_TO_ALGORITHM_SORT = { - "SORT_NONE": SORT_NONE, - "SORT_AREA": SORT_AREA, +STRING_TO_ALGORITHM_INITIAL_SORT = { + "none": modules_sort_by_none, + "area": modules_sort_by_area, + "width": modules_sort_by_width, + "height": modules_sort_by_height, } @@ -70,16 +147,17 @@ class SOI: algorithm_bin : string which bin packing algorithm should be used for rectpack. Currently the following are implemented: 'BFF', 'BBF'. Please refer to the - RECTPACK_STRING_TO_ALGORITHM_BIN variable. + STRING_TO_ALGORITHM_RECTPACK_BIN variable. algorithm_pack : string which packing algorithm should be used for rectpack. Currently the following are implemented: 'MaxRectsBl', 'SkylineBl', 'GuillotineBssfSas'. Please refer to the - RECTPACK_STRING_TO_ALGORITHM_PACK variable. + STRING_TO_ALGORITHM_RECTPACK_PACK variable. algorithm_sort : string which sorting method should be applied to the modules before packing. - Currently the following are implemented: 'SORT_NONE', 'SORT_AREA'. - Please refer to the RECTPACK_STRING_TO_ALGORITHM_SORT variable. + Currently the following are implemented: 'none', 'area', 'width', + 'height'. Please refer to the STRING_TO_ALGORITHM_INITIAL_SORT + variable. """ @@ -128,7 +206,7 @@ class SOI: placement_strategy="auto", algorithm_bin="BFF", algorithm_pack="MaxRectsBl", - algorithm_sort="SORT_NONE", + algorithm_sort="area", ): # populate date-related arguments if they are not supplied by the user @@ -159,6 +237,9 @@ class SOI: self.algorithm_pack = algorithm_pack self.algorithm_sort = algorithm_sort + # functions to call after reorganization + self.reorganization_listeners = [] + # NOTE # * test modules, just to have something show up on screen # * not valid until reorganize has been run, as the positions must be @@ -189,6 +270,14 @@ class SOI: self.reorganize() + def add_reorganization_listener(self, function): + """Add function to list of functions to be called after reorganization. + + This is useful for users of this class to handle changes to the SOI. + As an example a class displaying an SOI can be updated after changes. + """ + self.reorganization_listeners.append(function) + def update_module_widget_position(self, module): """Update position of module widget based on meta position. @@ -238,7 +327,10 @@ class SOI: return None def reorganize(self): - """Reorganize modules using chosen strategy.""" + """Reorganize modules using chosen strategy. + + After successfull reorganization will call all listeners + """ if self.placement_strategy == "auto": self.reorganize_rectpack() else: @@ -247,6 +339,9 @@ class SOI: self.placement_strategy ) ) + # call listener functions + for listener in self.reorganization_listeners: + listener() def get_rectpack_packer(self): """Return rectpack packer set up for this SOI. @@ -257,30 +352,41 @@ class SOI: """ # based on string stored in self.algorithm_... variables fetch real # implementations of chosen algorithms - chosen_algorithm_bin = RECTPACK_STRING_TO_ALGORITHM_BIN[ + chosen_algorithm_bin = STRING_TO_ALGORITHM_RECTPACK_BIN[ self.algorithm_bin ] - chosen_algorithm_pack = RECTPACK_STRING_TO_ALGORITHM_PACK[ + chosen_algorithm_pack = STRING_TO_ALGORITHM_RECTPACK_PACK[ self.algorithm_pack ] - chosen_algorithm_sort = RECTPACK_STRING_TO_ALGORITHM_SORT[ - self.algorithm_sort - ] + # NOTE that initial sorting is done outside of the packer, so it is set + # to SORT_NONE here to respect our sorting packer = newPacker( rotation=False, mode=PackingMode.Offline, bin_algo=chosen_algorithm_bin, - sort_algo=chosen_algorithm_sort, + sort_algo=SORT_NONE, pack_algo=chosen_algorithm_pack, ) return packer + def sort_modules(self): + """Sort modules in place using chosen sorting algorithm.""" + chosen_algorithm_sort = STRING_TO_ALGORITHM_INITIAL_SORT[ + self.algorithm_sort + ] + + modules_to_sort = chosen_algorithm_sort(self.modules) + for i, _ in enumerate(self.modules): + self.modules[i] = modules_to_sort[i] + def reorganize_rectpack(self): """Reorganize modules optimally using the rectpack library.""" packer = self.get_rectpack_packer() + self.sort_modules() + for module in self.modules: module_width, module_height = module["widget"].get_size() packer.add_rect( -- GitLab