diff --git a/scripts/.pylintrc b/scripts/.pylintrc index 6262e5ddd5bbbd19ecdd7bd226c74675c1477091..c8188147c11376e615f2f1329cef71a496bb407b 100644 --- a/scripts/.pylintrc +++ b/scripts/.pylintrc @@ -60,6 +60,14 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". +# +# NOTE about C0330: +# Ignoring pylint's C0330 "Wrong hanging indentation before block" error +# here because it erronously reports indentation issues, and conflicts with +# the black tool. See this issue on the black gitHub +# page: https://github.com/psf/black/issues/48, and this issue on pylints +# GitHub page referenced by the first issue: +# https://github.com/PyCQA/pylint/issues/289 disable=print-statement, parameter-unpacking, unpacking-in-except, @@ -142,7 +150,8 @@ disable=print-statement, E0611, I1101, E1101, - R0901 + R0901, + C0330 # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/soitool/inline_editable_soi_view.py b/soitool/inline_editable_soi_view.py index e17c16ddecb6392c0120a018c721560c0f9a0886..1d1a459fdc521b14d39c3db54dc65a12ccad0820 100644 --- a/soitool/inline_editable_soi_view.py +++ b/soitool/inline_editable_soi_view.py @@ -40,7 +40,7 @@ class InlineEditableSOIView(QScrollArea): self.reorganize_soi_and_update_pages() def reorganize_soi_and_update_pages(self): - """Wrapper to reorganize SOI and update pages in one go.""" + """Reorganize SOI and update pages in one go.""" try: self.soi.reorganize() except ModuleLargerThanBinError: @@ -49,7 +49,9 @@ class InlineEditableSOIView(QScrollArea): "En av modulene er for store for å passe på én side!" ) error_message.setInformativeText( - "Programmet kan desverre ikke fikse dette for deg. Se igjennom modulene og sjekk at alle moduler er mindre enn én enkelt side." + "Programmet kan desverre ikke fikse dette for deg. Se " + "igjennom modulene og sjekk at alle moduler er mindre " + "enn én enkelt side." ) error_message.setIcon(QMessageBox.Warning) error_message.exec_() @@ -141,8 +143,7 @@ class InlineEditableSOIView(QScrollArea): self.number_of_pages = required_pages def draw_pages(self): - """draw self.number_of_pages pages.""" - + """Draw self.number_of_pages pages.""" for i in range(self.number_of_pages): x = 0 @@ -197,15 +198,20 @@ class InlineEditableSOIView(QScrollArea): # classification classification = QLabel(self.soi.classification) - classification.setStyleSheet("background-color: rgba(0,0,0,0%); " - "color: red") + classification.setStyleSheet( + "background-color: rgba(0,0,0,0%); " "color: red" + ) classification.setFont(QFont("Times New Roman", 50)) # source: https://stackoverflow.com/a/8638114/3545896 # CAUTION: does not work if font is set through stylesheet - label_width = (classification.fontMetrics() - .boundingRect(classification.text()).width()) - x_pos = x + self.soi.CONTENT_WIDTH - label_width - \ - self.soi.HEADER_HEIGHT + label_width = ( + classification.fontMetrics() + .boundingRect(classification.text()) + .width() + ) + x_pos = ( + x + self.soi.CONTENT_WIDTH - label_width - self.soi.HEADER_HEIGHT + ) classification.move(x_pos, y) self.scene.addWidget(classification) diff --git a/soitool/soi.py b/soitool/soi.py index 65b6fd5a5cfe2da98fcd4a59cb9683ae8d44b283..b8ac743b4c1a68649022a7ff9ea74aec22aab9ed 100644 --- a/soitool/soi.py +++ b/soitool/soi.py @@ -5,20 +5,35 @@ from PySide2.QtCore import QPoint from rectpack import ( newPacker, SORT_NONE, - SORT_SSIDE, SORT_AREA, PackingMode, PackingBin, maxrects, + skyline, + guillotine, ) from soitool.modules.module_table import TableModule +RECTPACK_STRING_TO_ALGORITHM_BIN = { + "BFF": PackingBin.BFF, + "BBF": PackingBin.BBF, +} + +RECTPACK_STRING_TO_ALGORITHM_PACK = { + "MaxRectsBl": maxrects.MaxRectsBl, + "SkylineBl": skyline.SkylineBl, + "GuillotineBssfSas": guillotine.GuillotineBssfSas, +} + +RECTPACK_STRING_TO_ALGORITHM_SORT = { + "SORT_NONE": SORT_NONE, + "SORT_AREA": SORT_AREA, +} + class ModuleLargerThanBinError(Exception): """At least one module is too large for the bin during rectpack packing.""" - pass - class ModuleType(Enum): """Enumerate with types of modules.""" @@ -27,11 +42,16 @@ class ModuleType(Enum): ATTACHMENT_MODULE = 1 -class SOI(): +class SOI: """Datastructure for SOI. Holds all info about an SOI necessary to view and edit it. + This class relies heavily on the rectpack library for optimal placement of + modules when placement_strategy is 'auto'. Refer to the rectpack + documentation for details beyond what is provided in this class: + https://github.com/secnot/rectpack + Parameters ---------- title : string @@ -48,11 +68,18 @@ class SOI(): placement_strategy : string must be one of 'manual' and 'auto' algorithm_bin : string - TODO: document when rectpack is added + 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. algorithm_pack : string - TODO: document when rectpack is added + 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. algorithm_sort : string - TODO: document when rectpack is added + 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. """ @@ -82,25 +109,27 @@ class SOI(): pass raise NotImplementedError() - # ignoring pylints "Too many arguments" error here to keep this as simple - # as possible. The proper thing would probably be to put the properties in - # separate data classes (auto-placement stuff in one class, styling in - # another, etc). + # Ignoring pylint's r0913 "Too many arguments" error here to keep this as + # simple as possible. The proper thing would probably be to put the + # properties in separate data classes (auto-placement stuff in one class, + # styling in another, etc). # pylint: disable=r0913 - def __init__(self, - title="Default SOI title", - description="Default SOI description", - version="1", - date=None, - valid_from=None, - valid_to=None, - icon="soitool/media/HVlogo.png", - classification="ugradert", - orientation="landscape", - placement_strategy="auto", - algorithm_bin="BFF", - algorithm_pack="MaxRectsBI", - algorithm_sort="SORT_AREA"): + def __init__( + self, + title="Default SOI title", + description="Default SOI description", + version="1", + date=None, + valid_from=None, + valid_to=None, + icon="soitool/media/HVlogo.png", + classification="ugradert", + orientation="landscape", + placement_strategy="auto", + algorithm_bin="BFF", + algorithm_pack="MaxRectsBl", + algorithm_sort="SORT_NONE", + ): # populate date-related arguments if they are not supplied by the user if date is None: @@ -191,67 +220,100 @@ class SOI(): module["widget"].set_pos(QPoint(new_x, new_y)) + def get_module_with_name(self, name): + """Return module with given name. + + Parameters + ---------- + name : str + name of module to look for + + Returns + ------- + module in self.modules + """ + for module in self.modules: + if module["meta"]["name"] == name: + return module + return None + def reorganize(self): - self.reorganize_rectpack() + """Reorganize modules using chosen strategy.""" + if self.placement_strategy == "auto": + self.reorganize_rectpack() + else: + raise Exception( + "Unknown placement strategy: {}".format( + self.placement_strategy + ) + ) - def reorganize_rectpack(self): + def get_rectpack_packer(self): + """Return rectpack packer set up for this SOI. + + Returns + ------- + packer : rectpack packer + """ + # based on string stored in self.algorithm_... variables fetch real + # implementations of chosen algorithms + chosen_algorithm_bin = RECTPACK_STRING_TO_ALGORITHM_BIN[ + self.algorithm_bin + ] + chosen_algorithm_pack = RECTPACK_STRING_TO_ALGORITHM_PACK[ + self.algorithm_pack + ] + chosen_algorithm_sort = RECTPACK_STRING_TO_ALGORITHM_SORT[ + self.algorithm_sort + ] packer = newPacker( rotation=False, mode=PackingMode.Offline, - bin_algo=PackingBin.BBF, - sort_algo=SORT_NONE, - pack_algo=maxrects.MaxRectsBl, + bin_algo=chosen_algorithm_bin, + sort_algo=chosen_algorithm_sort, + pack_algo=chosen_algorithm_pack, ) + return packer + + def reorganize_rectpack(self): + """Reorganize modules optimally using the rectpack library.""" + packer = self.get_rectpack_packer() + for module in self.modules: module_width, module_height = module["widget"].get_size() - print("size is:", str(module_width), str(module_height)) packer.add_rect( module_width, module_height, module["meta"]["name"] ) + # float("inf") to add infinite bins. See https://github.com/secnot/rectpack/blob/master/README.md#api packer.add_bin( - self.CONTENT_WIDTH, self.CONTENT_HEIGHT - self.HEADER_HEIGHT - ) - packer.add_bin( - self.CONTENT_WIDTH, self.CONTENT_HEIGHT - self.HEADER_HEIGHT + self.CONTENT_WIDTH, + self.CONTENT_HEIGHT - self.HEADER_HEIGHT, + float("inf"), ) packer.pack() - packed_rects = packer.rect_list() + # explode if rectpack failed to pack all rects if len(packed_rects) != len(self.modules): raise ModuleLargerThanBinError() - def get_module_with_name(name): - """Returns module with given name. """ - for module in self.modules: - if module["meta"]["name"] == name: - return module - return None - + # update modules based on packed rects returned from rectpack for packed_rect in packed_rects: - print("packed rect: ", packed_rect) - packed_rect_bin = packed_rect[0] packed_rect_x = packed_rect[1] packed_rect_y = packed_rect[2] - packed_rect_w = packed_rect[3] - packed_rect_h = packed_rect[4] packed_rect_id = packed_rect[5] - corresponding_module = get_module_with_name(packed_rect_id) - if not corresponding_module: + corresponding_module = self.get_module_with_name(packed_rect_id) + if corresponding_module is None: raise Exception("Module was lost during packing!") corresponding_module["meta"]["x"] = packed_rect_x corresponding_module["meta"]["y"] = packed_rect_y corresponding_module["meta"]["page"] = packed_rect_bin + 1 self.update_module_widget_position(corresponding_module) - - print("---MODULES AFTER PACKING---") - for module in self.modules: - print(module)