From f02fb288a4ae67a4e0e3cf64cc257378a67acc71 Mon Sep 17 00:00:00 2001 From: Valdas2000 <35214527+Valdas2000@users.noreply.github.com> Date: Thu, 11 Jan 2018 23:28:12 +0300 Subject: [PATCH 1/3] Add files via upload The commitment introducethe following major changes: 1- the project migrated on python3 2- the project migrated to QT5 Enhansments: ------------- 1 - Templates got Align line on contrast background 2 - Thin markers in editor 3 - Updated PNG Known Issues: -------------- 1 - Export 3DS does not work propertly Installetion: -------------- 1. Download fresh python setup from https://www.python.org/downloads/ (now it is 3.6.4) do not forget instaletion path 2. Setup libraries pip install pyQT5 pip install future pip install Pillow --- README.md | 23 ++++ mac.spec | 4 +- qt_config.py | 186 ++++++++++++++++--------------- qt_driver.py | 308 +++++++++++++++++++++++++++++---------------------- qt_fig.py | 74 +++++++++---- qt_test.py | 9 +- qt_utils.py | 30 ++--- serialize.py | 26 +++-- 8 files changed, 387 insertions(+), 273 deletions(-) diff --git a/README.md b/README.md index 1ecc0b1..7e29e1a 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,26 @@ joints. For more information, [see the pyRouterJig website](http://lowrie.github.io/pyRouterJig/) +The commitment introducethe following major changes: +1- the project migrated on python3 +2- the project migrated to QT5 + +Enhansments: +------------- +1 - Templates got Align line on contrast background +2 - Thin markers in editor +3 - Updated PNG + +Known Issues: +-------------- +1 - Export 3DS does not work propertly + +Installetion: +-------------- +1. Download fresh python setup from https://www.python.org/downloads/ (now it is 3.6.4) +do not forget instaletion path +2. Setup libraries +pip install pyQT5 +pip install future +pip install Pillow + diff --git a/mac.spec b/mac.spec index 5f553ec..2fc76f6 100644 --- a/mac.spec +++ b/mac.spec @@ -15,8 +15,8 @@ a = Analysis(['pyRouterJig.py'], cipher=block_cipher) # Explicitly add libraries that pyinstaller misses -a.binaries += [('libQtCore.4.dylib', '/anaconda/lib/libQtCore.4.dylib', 'BINARY') ] -a.binaries += [('libQtGui.4.dylib', '/anaconda/lib/libQtGui.4.dylib', 'BINARY') ] +a.binaries += [('libQtCore.5.dylib', '/anaconda/lib/libQtCore.5.dylib', 'BINARY') ] +a.binaries += [('libQtGui.5.dylib', '/anaconda/lib/libQtGui.5.dylib', 'BINARY') ] a.binaries += [('libpng16.16.dylib', '/anaconda/lib/libpng16.16.dylib', 'BINARY') ] # Remove various unused files from distribution diff --git a/qt_config.py b/qt_config.py index 3cd635d..807f8a6 100644 --- a/qt_config.py +++ b/qt_config.py @@ -24,7 +24,7 @@ from __future__ import print_function import os, sys -from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets import config_file import qt_utils @@ -38,7 +38,7 @@ def form_line(label, widget=None, tooltip=None): and returns the QLayout ''' - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() line = qt_utils.create_hline() line.setMinimumWidth(20) if tooltip is not None: @@ -59,12 +59,12 @@ def is_positive(v): def is_nonnegative(v): return v >= 0 -class Color_Button(QtGui.QPushButton): +class Color_Button(QtWidgets.QPushButton): ''' A QPushButton that is simply a rectangle of a single color. ''' def __init__(self, color, parent): - QtGui.QPushButton.__init__(self, parent) + QtWidgets.QPushButton.__init__(self, parent) size = QtCore.QSize(80, 20) self.setFixedSize(size) self.set_color(color) @@ -83,15 +83,15 @@ def add_color_to_dialog(color): ''' Adds color to the QColorDialog custom colors. ''' - count = QtGui.QColorDialog.customCount() - cprev = QtGui.QColorDialog.customColor(0) + count = QtWidgets.QColorDialog.customCount() + cprev = QtWidgets.QColorDialog.customColor(0) # shift all the current colors by one index for i in range(1, count): - c = QtGui.QColorDialog.customColor(i) - QtGui.QColorDialog.setCustomColor(i, cprev) + c = QtWidgets.QColorDialog.customColor(i) + QtWidgets.QColorDialog.setCustomColor(i, cprev) cprev = c # add the new color to the first index - QtGui.QColorDialog.setCustomColor(0, color.rgba()) + QtWidgets.QColorDialog.setCustomColor(0, color.rgba()) class Misc_Value(object): ''' @@ -115,12 +115,12 @@ def set_value_from_string(self, s): raise router.Router_Exception(msg) self.value = value -class Config_Window(QtGui.QDialog): +class Config_Window(QtWidgets.QDialog): ''' Qt interface to config file parameters ''' def __init__(self, config, units, parent=None): - QtGui.QDialog.__init__(self, parent) + QtWidgets.QDialog.__init__(self, parent) self.config = config self.new_config = self.config.__dict__.copy() self.line_edit_width = 80 @@ -135,12 +135,12 @@ def __init__(self, config, units, parent=None): self.board = router.Board(self.bit, width=board_width) # Form the tabs and their contents - title_label = QtGui.QLabel('pyRouterJig Preferences') + title_label = QtWidgets.QLabel('pyRouterJig Preferences') title_label.setAlignment(QtCore.Qt.AlignHCenter) - vbox = QtGui.QVBoxLayout() + vbox = QtWidgets.QVBoxLayout() vbox.addWidget(title_label) - tabs = QtGui.QTabWidget() + tabs = QtWidgets.QTabWidget() tabs.addTab(self.create_output(), 'Output') tabs.addTab(self.create_boards(), 'Boards') @@ -158,25 +158,25 @@ def __init__(self, config, units, parent=None): def create_units(self): '''Creates the layout for units preferences''' - w = QtGui.QWidget() - vbox = QtGui.QVBoxLayout() + w = QtWidgets.QWidget() + vbox = QtWidgets.QVBoxLayout() - mesg = QtGui.QLabel('WARNING:' + mesg = QtWidgets.QLabel('WARNING:' ' Changing any Units settings will require pyRouterJig' ' to restart and your present joint will be lost.') mesg.setWordWrap(True) vbox.addWidget(mesg) - self.cb_units_label = QtGui.QLabel('Unit System:') - self.cb_units = QtGui.QComboBox(self) + self.cb_units_label = QtWidgets.QLabel('Unit System:') + self.cb_units = QtWidgets.QComboBox(self) self.cb_units.addItem('Metric') self.cb_units.addItem('English') self.cb_units.activated.connect(self._on_units) grid = form_line(self.cb_units_label, self.cb_units) vbox.addLayout(grid) - self.le_num_incr_label = QtGui.QLabel(self.units_label(self.config.metric)) - self.le_num_incr = QtGui.QLineEdit(w) + self.le_num_incr_label = QtWidgets.QLabel(self.units_label(self.config.metric)) + self.le_num_incr = QtWidgets.QLineEdit(w) self.le_num_incr.setFixedWidth(self.line_edit_width) self.le_num_incr.editingFinished.connect(self._on_num_incr) tt = 'The number of increments per unit length.' @@ -192,7 +192,8 @@ def set_wood_combobox(self): Sets the entries for the wood combox box, and resets the default wood. ''' (woods, patterns) = qt_utils.create_wood_dict(self.new_config['wood_images']) - woodnames = woods.keys() + #python 3 + woodnames = list( woods.keys() ) woodnames.extend(patterns.keys()) self.cb_wood.clear() self.cb_wood.addItems(woodnames) @@ -205,20 +206,20 @@ def set_wood_combobox(self): def create_boards(self): '''Creates the layout for boards preferences''' - w = QtGui.QWidget() - vbox = QtGui.QVBoxLayout() + w = QtWidgets.QWidget() + vbox = QtWidgets.QVBoxLayout() us = self.units.units_string(withParens=True) - self.le_board_width_label = QtGui.QLabel('Initial Board Width{}:'.format(us)) - self.le_board_width = QtGui.QLineEdit(w) + self.le_board_width_label = QtWidgets.QLabel('Initial Board Width{}:'.format(us)) + self.le_board_width = QtWidgets.QLineEdit(w) self.le_board_width.setFixedWidth(self.line_edit_width) self.le_board_width.editingFinished.connect(self._on_board_width) tt = 'The initial board width when pyRouterJig starts.' grid = form_line(self.le_board_width_label, self.le_board_width, tt) vbox.addLayout(grid) - self.le_db_thick_label = QtGui.QLabel('Initial Double Board Thickness{}:'.format(us)) - self.le_db_thick = QtGui.QLineEdit(w) + self.le_db_thick_label = QtWidgets.QLabel('Initial Double Board Thickness{}:'.format(us)) + self.le_db_thick = QtWidgets.QLineEdit(w) self.le_db_thick.setFixedWidth(self.line_edit_width) self.le_db_thick.editingFinished.connect(self._on_db_thick) tt = 'The initial double-board thickness when pyRouterJig starts.' @@ -226,22 +227,23 @@ def create_boards(self): vbox.addLayout(grid) (woods, patterns) = qt_utils.create_wood_dict(self.config.wood_images) - woodnames = woods.keys() + #python 3 + woodnames = list(woods.keys()) woodnames.extend(patterns.keys()) - self.cb_wood_label = QtGui.QLabel('Default Wood Fill:') - self.cb_wood = QtGui.QComboBox(self) + self.cb_wood_label = QtWidgets.QLabel('Default Wood Fill:') + self.cb_wood = QtWidgets.QComboBox(self) self.set_wood_combobox() self.cb_wood.activated.connect(self._on_wood) tt = 'The default wood fill for each board.' grid = form_line(self.cb_wood_label, self.cb_wood, tt) vbox.addLayout(grid) - self.le_wood_images_label = QtGui.QLabel('Wood Images Folder:') - self.le_wood_images = QtGui.QLineEdit(w) + self.le_wood_images_label = QtWidgets.QLabel('Wood Images Folder:') + self.le_wood_images = QtWidgets.QLineEdit(w) self.le_wood_images.editingFinished.connect(self._on_wood_images) tt = 'Location of wood images.' self.le_wood_images.setToolTip(tt) - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() grid.addWidget(qt_utils.create_vline(), 0, 0, 4, 1) grid.addWidget(qt_utils.create_vline(), 0, 3, 4, 1) grid.addWidget(qt_utils.create_hline(), 0, 0, 1, 4) @@ -257,28 +259,28 @@ def create_boards(self): def create_bit(self): '''Creates the layout for bit preferences''' - w = QtGui.QWidget() - vbox = QtGui.QVBoxLayout() + w = QtWidgets.QWidget() + vbox = QtWidgets.QVBoxLayout() us = self.units.units_string(withParens=True) - self.le_bit_width_label = QtGui.QLabel('Initial Bit Width{}:'.format(us)) - self.le_bit_width = QtGui.QLineEdit(w) + self.le_bit_width_label = QtWidgets.QLabel('Initial Bit Width{}:'.format(us)) + self.le_bit_width = QtWidgets.QLineEdit(w) self.le_bit_width.setFixedWidth(self.line_edit_width) self.le_bit_width.editingFinished.connect(self._on_bit_width) tt = 'The initial bit width when pyRouterJig starts.' grid = form_line(self.le_bit_width_label, self.le_bit_width, tt) vbox.addLayout(grid) - self.le_bit_depth_label = QtGui.QLabel('Initial Bit Depth{}:'.format(us)) - self.le_bit_depth = QtGui.QLineEdit(w) + self.le_bit_depth_label = QtWidgets.QLabel('Initial Bit Depth{}:'.format(us)) + self.le_bit_depth = QtWidgets.QLineEdit(w) self.le_bit_depth.setFixedWidth(self.line_edit_width) self.le_bit_depth.editingFinished.connect(self._on_bit_depth) tt = 'The initial bit depth when pyRouterJig starts.' grid = form_line(self.le_bit_depth_label, self.le_bit_depth, tt) vbox.addLayout(grid) - self.le_bit_angle_label = QtGui.QLabel('Initial Bit Angle (deg.):') - self.le_bit_angle = QtGui.QLineEdit(w) + self.le_bit_angle_label = QtWidgets.QLabel('Initial Bit Angle (deg.):') + self.le_bit_angle = QtWidgets.QLineEdit(w) self.le_bit_angle.setFixedWidth(self.line_edit_width) self.le_bit_angle.editingFinished.connect(self._on_bit_angle) tt = 'The initial bit angle when pyRouterJig starts.' @@ -291,21 +293,21 @@ def create_bit(self): def create_colors(self): '''Creates the layout for color preferences''' - w = QtGui.QWidget() - vbox = QtGui.QVBoxLayout() + w = QtWidgets.QWidget() + vbox = QtWidgets.QVBoxLayout() - self.cb_print_color = QtGui.QCheckBox('Print in Color', w) + self.cb_print_color = QtWidgets.QCheckBox('Print in Color', w) self.cb_print_color.stateChanged.connect(self._on_print_color) self.cb_print_color.setToolTip('If true, print in color. Otherwise, converts to black and white.') vbox.addWidget(self.cb_print_color) - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() flag_label = QtCore.Qt.AlignRight flag_color = QtCore.Qt.AlignLeft row = 0 col = 0 - self.btn_canvas_background_label = QtGui.QLabel('Canvas Background') + self.btn_canvas_background_label = QtWidgets.QLabel('Canvas Background') self.btn_canvas_background = Color_Button(self.new_config['canvas_background'], w) self.btn_canvas_background.clicked.connect(lambda: self._on_set_color('canvas_background', self.btn_canvas_background)) tt = 'Sets the background color of the canvas.' @@ -314,7 +316,7 @@ def create_colors(self): grid.addWidget(self.btn_canvas_background, row, col+1, flag_color) row += 1 - self.btn_canvas_foreground_label = QtGui.QLabel('Canvas Foreground') + self.btn_canvas_foreground_label = QtWidgets.QLabel('Canvas Foreground') self.btn_canvas_foreground = Color_Button(self.new_config['canvas_foreground'], w) self.btn_canvas_foreground.clicked.connect(lambda: self._on_set_color('canvas_foreground', self.btn_canvas_foreground)) tt = 'Sets the foreground color of the canvas.' @@ -323,7 +325,7 @@ def create_colors(self): grid.addWidget(self.btn_canvas_foreground, row, col+1, flag_color) row += 1 - self.btn_board_background_label = QtGui.QLabel('Board Background') + self.btn_board_background_label = QtWidgets.QLabel('Board Background') self.btn_board_background = Color_Button(self.new_config['board_background'], w) self.btn_board_background.clicked.connect(lambda: self._on_set_color('board_background', self.btn_board_background)) tt = 'Sets the top board background color.' @@ -332,7 +334,7 @@ def create_colors(self): grid.addWidget(self.btn_board_background, row, col+1, flag_color) row += 1 - self.btn_board_foreground_label = QtGui.QLabel('Board Foreground') + self.btn_board_foreground_label = QtWidgets.QLabel('Board Foreground') self.btn_board_foreground = Color_Button(self.new_config['board_foreground'], w) self.btn_board_foreground.clicked.connect(lambda: self._on_set_color('board_foreground', self.btn_board_foreground)) tt = 'Sets the top board foreground color.' @@ -343,7 +345,7 @@ def create_colors(self): max_row = row row = 0 col += 2 - self.btn_pass_color_label = QtGui.QLabel('Pass') + self.btn_pass_color_label = QtWidgets.QLabel('Pass') self.btn_pass_color = Color_Button(self.new_config['pass_color'], w) self.btn_pass_color.clicked.connect(lambda: self._on_set_color('pass_color', self.btn_pass_color)) tt = 'Sets the template foreground color for each pass.' @@ -352,7 +354,7 @@ def create_colors(self): grid.addWidget(self.btn_pass_color, row, col+1, flag_color) row += 1 - self.btn_pass_alt_color_label = QtGui.QLabel('Pass-Alt') + self.btn_pass_alt_color_label = QtWidgets.QLabel('Pass-Alt') self.btn_pass_alt_color = Color_Button(self.new_config['pass_alt_color'], w) self.btn_pass_alt_color.clicked.connect(lambda: self._on_set_color('pass_alt_color', self.btn_pass_alt_color)) tt = 'Sets the template foreground alternate color for each pass.' @@ -361,7 +363,7 @@ def create_colors(self): grid.addWidget(self.btn_pass_alt_color, row, col+1, flag_color) row += 1 - self.btn_center_color_label = QtGui.QLabel('Center Pass') + self.btn_center_color_label = QtWidgets.QLabel('Center Pass') self.btn_center_color = Color_Button(self.new_config['center_color'], w) self.btn_center_color.clicked.connect(lambda: self._on_set_color('center_color', self.btn_center_color)) tt = 'Sets the template foreground color for the center pass.' @@ -370,7 +372,7 @@ def create_colors(self): grid.addWidget(self.btn_center_color, row, col+1, flag_color) row += 1 - self.btn_watermark_color_label = QtGui.QLabel('Watermark') + self.btn_watermark_color_label = QtWidgets.QLabel('Watermark') self.btn_watermark_color = Color_Button(self.new_config['watermark_color'], w) self.btn_watermark_color.clicked.connect(lambda: self._on_set_color('watermark_color', self.btn_watermark_color)) tt = 'Sets the watermark color.' @@ -379,11 +381,11 @@ def create_colors(self): grid.addWidget(self.btn_watermark_color, row, col+1, flag_color) vbox.addLayout(grid) - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() row = 0 col = 0 - self.btn_template_margin_background_label = QtGui.QLabel('Template Margin Background') + self.btn_template_margin_background_label = QtWidgets.QLabel('Template Margin Background') self.btn_template_margin_background = Color_Button(self.new_config['template_margin_background'], w) self.btn_template_margin_background.clicked.connect(lambda: self._on_set_color('template_margin_background', self.btn_template_margin_background)) tt = 'Sets the template margin background color.' @@ -392,7 +394,7 @@ def create_colors(self): grid.addWidget(self.btn_template_margin_background, row, col+1, flag_color) row += 1 - self.btn_template_margin_foreground_label = QtGui.QLabel('Template Margin Foreground') + self.btn_template_margin_foreground_label = QtWidgets.QLabel('Template Margin Foreground') self.btn_template_margin_foreground = Color_Button(self.new_config['template_margin_foreground'], w) self.btn_template_margin_foreground.clicked.connect(lambda: self._on_set_color('template_margin_foreground', self.btn_template_margin_foreground)) tt = 'Sets the template margin foreground color.' @@ -408,52 +410,52 @@ def create_colors(self): def create_output(self): '''Creates the layout for output preferences''' - w = QtGui.QWidget() - vbox = QtGui.QVBoxLayout() + w = QtWidgets.QWidget() + vbox = QtWidgets.QVBoxLayout() - self.cb_show_caul = QtGui.QCheckBox('Show Caul Template', w) + self.cb_show_caul = QtWidgets.QCheckBox('Show Caul Template', w) self.cb_show_caul.stateChanged.connect(self._on_show_caul) self.cb_show_caul.setToolTip('Display the template for clamping cauls') vbox.addWidget(self.cb_show_caul) - self.cb_show_finger_widths = QtGui.QCheckBox('Show Finger Widths', w) + self.cb_show_finger_widths = QtWidgets.QCheckBox('Show Finger Widths', w) self.cb_show_finger_widths.stateChanged.connect(self._on_show_finger_widths) self.cb_show_finger_widths.setToolTip('Display the width of each finger') vbox.addWidget(self.cb_show_finger_widths) - self.cb_show_fit = QtGui.QCheckBox('Show Fit', w) + self.cb_show_fit = QtWidgets.QCheckBox('Show Fit', w) self.cb_show_fit.stateChanged.connect(self._on_show_fit) self.cb_show_fit.setToolTip('Display fit of joint') vbox.addWidget(self.cb_show_fit) - self.cb_rpid = QtGui.QCheckBox('Show Router Pass Identifiers', w) + self.cb_rpid = QtWidgets.QCheckBox('Show Router Pass Identifiers', w) self.cb_rpid.stateChanged.connect(self._on_rpid) self.cb_rpid.setToolTip('On each router pass, label its identifier') vbox.addWidget(self.cb_rpid) - self.cb_rploc = QtGui.QCheckBox('Show Router Pass Locations', w) + self.cb_rploc = QtWidgets.QCheckBox('Show Router Pass Locations', w) self.cb_rploc.stateChanged.connect(self._on_rploc) self.cb_rploc.setToolTip('On each router pass, label its distance from the right edge') vbox.addWidget(self.cb_rploc) - self.le_printsf_label = QtGui.QLabel('Print Scale Factor:') - self.le_printsf = QtGui.QLineEdit(w) + self.le_printsf_label = QtWidgets.QLabel('Print Scale Factor:') + self.le_printsf = QtWidgets.QLineEdit(w) self.le_printsf.setFixedWidth(self.line_edit_width) self.le_printsf.editingFinished.connect(self._on_printsf) tt = 'Scale output by this factor when printing.' grid = form_line(self.le_printsf_label, self.le_printsf, tt) vbox.addLayout(grid) - self.le_min_image_label = QtGui.QLabel('Min Image Width (pixels):') - self.le_min_image = QtGui.QLineEdit(w) + self.le_min_image_label = QtWidgets.QLabel('Min Image Width (pixels):') + self.le_min_image = QtWidgets.QLineEdit(w) self.le_min_image.setFixedWidth(self.line_edit_width) self.le_min_image.editingFinished.connect(self._on_min_image) tt = 'On save image, minimum width of image.' grid = form_line(self.le_min_image_label, self.le_min_image, tt) vbox.addLayout(grid) - self.le_max_image_label = QtGui.QLabel('Max Image Width (pixels):') - self.le_max_image = QtGui.QLineEdit(w) + self.le_max_image_label = QtWidgets.QLabel('Max Image Width (pixels):') + self.le_max_image = QtWidgets.QLineEdit(w) self.le_max_image.setFixedWidth(self.line_edit_width) self.le_max_image.editingFinished.connect(self._on_max_image) tt = 'On save image, maximum width of image.' @@ -465,36 +467,36 @@ def create_output(self): def create_misc(self): '''Creates the layout for misc preferences''' - w = QtGui.QWidget() - vbox = QtGui.QVBoxLayout() + w = QtWidgets.QWidget() + vbox = QtWidgets.QVBoxLayout() us = self.units.units_string(withParens=True) - self.le_min_finger_width_label = QtGui.QLabel('Min Finger Width{}:'.format(us)) - self.le_min_finger_width = QtGui.QLineEdit(w) + self.le_min_finger_width_label = QtWidgets.QLabel('Min Finger Width{}:'.format(us)) + self.le_min_finger_width = QtWidgets.QLineEdit(w) self.le_min_finger_width.setFixedWidth(self.line_edit_width) self.le_min_finger_width.editingFinished.connect(self._on_min_finger_width) tt = 'The minimum allowable finger width. Currently, only enforced for Equal Spacing.' grid = form_line(self.le_min_finger_width_label, self.le_min_finger_width, tt) vbox.addLayout(grid) - self.le_caul_trim_label = QtGui.QLabel('Caul Trim{}:'.format(us)) - self.le_caul_trim = QtGui.QLineEdit(w) + self.le_caul_trim_label = QtWidgets.QLabel('Caul Trim{}:'.format(us)) + self.le_caul_trim = QtWidgets.QLineEdit(w) self.le_caul_trim.setFixedWidth(self.line_edit_width) self.le_caul_trim.editingFinished.connect(self._on_caul_trim) tt = 'The distance from the edge of each finger to the edge of the corresponding caul finger.' grid = form_line(self.le_caul_trim_label, self.le_caul_trim, tt) vbox.addLayout(grid) - self.le_warn_gap_label = QtGui.QLabel('Warning gap{}:'.format(us)) - self.le_warn_gap = QtGui.QLineEdit(w) + self.le_warn_gap_label = QtWidgets.QLabel('Warning gap{}:'.format(us)) + self.le_warn_gap = QtWidgets.QLineEdit(w) self.le_warn_gap.setFixedWidth(self.line_edit_width) self.le_warn_gap.editingFinished.connect(self._on_warn_gap) tt = 'If the gap in the joint exceeds this value, warn the user.' grid = form_line(self.le_warn_gap_label, self.le_warn_gap, tt) vbox.addLayout(grid) - self.le_warn_overlap_label = QtGui.QLabel('Warning overlap{}:'.format(us)) - self.le_warn_overlap = QtGui.QLineEdit(w) + self.le_warn_overlap_label = QtWidgets.QLabel('Warning overlap{}:'.format(us)) + self.le_warn_overlap = QtWidgets.QLineEdit(w) self.le_warn_overlap.setFixedWidth(self.line_edit_width) self.le_warn_overlap.editingFinished.connect(self._on_warn_overlap) tt = 'If the overlap in the joint exceeds this value, warn the user.' @@ -508,16 +510,16 @@ def create_misc(self): def create_buttons(self): '''Creates the layout for the buttons''' - hbox_btns = QtGui.QHBoxLayout() + hbox_btns = QtWidgets.QHBoxLayout() - btn_cancel = QtGui.QPushButton('Cancel', self) + btn_cancel = QtWidgets.QPushButton('Cancel', self) btn_cancel.clicked.connect(self._on_cancel) btn_cancel.setAutoDefault(False) btn_cancel.setFocusPolicy(QtCore.Qt.ClickFocus) btn_cancel.setToolTip('Discard any preference changes and continue.') hbox_btns.addWidget(btn_cancel) - self.btn_save = QtGui.QPushButton('Save', self) + self.btn_save = QtWidgets.QPushButton('Save', self) self.btn_save.clicked.connect(self._on_save) self.btn_save.setAutoDefault(False) self.btn_save.setFocusPolicy(QtCore.Qt.ClickFocus) @@ -602,9 +604,9 @@ def _on_save(self): do_restart = False if self.change_state == 2: # Units were changes, so ask for a restart - box = QtGui.QMessageBox(self) + box = QtWidgets.QMessageBox(self) box.setTextFormat(QtCore.Qt.RichText) - box.setIcon(QtGui.QMessageBox.NoIcon) + box.setIcon(QtWidgets.QMessageBox.NoIcon) box.setText('Warning!') question = 'You have changed a Units setting, which'\ ' requires pyRouterJig to restart to take effect.'\ @@ -613,8 +615,8 @@ def _on_save(self): ' Press Cancel to discard the changes to preferences that'\ ' you have made.' box.setInformativeText(question) - buttonRestart = box.addButton('Restart', QtGui.QMessageBox.AcceptRole) - buttonCancel = box.addButton('Cancel', QtGui.QMessageBox.AcceptRole) + buttonRestart = box.addButton('Restart', QtWidgets.QMessageBox.AcceptRole) + buttonCancel = box.addButton('Cancel', QtWidgets.QMessageBox.AcceptRole) box.setDefaultButton(buttonCancel) box.raise_() box.exec_() @@ -804,7 +806,7 @@ def _on_printsf(self): else: msg = 'Unable to set Print Scale Factor to: {}

'\ 'Set to a positive number.'.format(s) - QtGui.QMessageBox.warning(self, 'Error', msg) + QtWidgets.QMessageBox.warning(self, 'Error', msg) self.le_printsf.setText(str(self.new_config['print_scale_factor'])) @QtCore.pyqtSlot() @@ -827,7 +829,7 @@ def _on_min_image(self): else: msg = 'Unable to set Min Image Width to: {}

'\ 'Set to a positive integer.'.format(s) - QtGui.QMessageBox.warning(self, 'Error', msg) + QtWidgets.QMessageBox.warning(self, 'Error', msg) self.le_min_image.setText(str(self.new_config['min_image_width'])) @QtCore.pyqtSlot() @@ -851,7 +853,7 @@ def _on_max_image(self): else: msg = 'Unable to set Max Image Width to: {}

'\ 'Set to a positive integer >= Min Image Width ({})'.format(s, min_value) - QtGui.QMessageBox.warning(self, 'Error', msg) + QtWidgets.QMessageBox.warning(self, 'Error', msg) self.le_max_image.setText(str(self.new_config['max_image_width'])) @QtCore.pyqtSlot() @@ -909,8 +911,8 @@ def _on_set_color(self, name, btn): if self.config.debug: print('qt_config:set_color {}'.format(name)) init_color = QtGui.QColor(*self.new_config[name]) - flags = QtGui.QColorDialog.ShowAlphaChannel | QtGui.QColorDialog.DontUseNativeDialog - color = QtGui.QColorDialog.getColor(init_color, self, 'Select {}'.format(name), flags) + flags = QtWidgets.QColorDialog.ShowAlphaChannel | QtWidgets.QColorDialog.DontUseNativeDialog + color = QtWidgets.QColorDialog.getColor(init_color, self, 'Select {}'.format(name), flags) if color.isValid(): self.new_config[name] = color.getRgb() self.update_state(name) diff --git a/qt_driver.py b/qt_driver.py index 07dd0b7..576bba2 100644 --- a/qt_driver.py +++ b/qt_driver.py @@ -24,6 +24,12 @@ from __future__ import print_function from future.utils import lrange from builtins import str +from PIL import Image +from PIL import PngImagePlugin +try: + from StringIO import BytesIO +except ImportError: + from io import BytesIO import os, sys, traceback, webbrowser, copy, shutil @@ -38,14 +44,14 @@ import serialize import threeDS -from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets -class Driver(QtGui.QMainWindow): +class Driver(QtWidgets.QMainWindow): ''' Qt driver for pyRouterJig ''' def __init__(self, parent=None): - QtGui.QMainWindow.__init__(self, parent) + QtWidgets.QMainWindow.__init__(self, parent) sys.excepthook = self.exception_hook self.except_handled = False @@ -135,9 +141,9 @@ def load_config(self): if r == 1: # The config file does not exist. Ask the user whether they want metric or english # units - box = QtGui.QMessageBox(self) + box = QtWidgets.QMessageBox(self) box.setTextFormat(QtCore.Qt.RichText) - box.setIcon(QtGui.QMessageBox.NoIcon) + box.setIcon(QtWidgets.QMessageBox.NoIcon) box.setText('Welcome to pyRouterJig !') question = 'Please select a unit system below.'\ ' The configuration file

{}

'\ @@ -146,8 +152,8 @@ def load_config(self): ' may be changed later by selecting Preferences under'\ ' the {} menu.'.format(c.filename, tools) box.setInformativeText(question) - buttonMetric = box.addButton('Metric (millimeters)', QtGui.QMessageBox.AcceptRole) - buttonEnglish = box.addButton('English (inches)', QtGui.QMessageBox.AcceptRole) + buttonMetric = box.addButton('Metric (millimeters)', QtWidgets.QMessageBox.AcceptRole) + buttonEnglish = box.addButton('English (inches)', QtWidgets.QMessageBox.AcceptRole) box.setDefaultButton(buttonEnglish) box.raise_() box.exec_() @@ -161,9 +167,9 @@ def load_config(self): shutil.move(c.filename, backup) metric = c.config.metric rc = c.create_config(metric) - box = QtGui.QMessageBox(self) + box = QtWidgets.QMessageBox(self) box.setTextFormat(QtCore.Qt.RichText) - box.setIcon(QtGui.QMessageBox.Warning) + box.setIcon(QtWidgets.QMessageBox.Warning) box.setText('Welcome to pyRouterJig !') warning = 'A new configuration file

{}

'\ 'has been created. The old version was saved'\ @@ -193,8 +199,8 @@ def load_config(self): def center(self): '''Centers the app in the screen''' frameGm = self.frameGeometry() - screen = QtGui.QApplication.desktop().screenNumber(QtGui.QApplication.desktop().cursor().pos()) - centerPoint = QtGui.QApplication.desktop().screenGeometry(screen).center() + screen = QtWidgets.QApplication.desktop().screenNumber(QtWidgets.QApplication.desktop().cursor().pos()) + centerPoint = QtWidgets.QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) @@ -212,7 +218,7 @@ def exception_hook(self, etype, value, trace): tmp = traceback.format_exception_only(etype, value) exception = '\n'.join(tmp) - QtGui.QMessageBox.warning(self, 'Error', exception) + QtWidgets.QMessageBox.warning(self, 'Error', exception) self.except_handled = False def create_menu(self): @@ -228,13 +234,13 @@ def create_menu(self): file_menu = self.menubar.addMenu('File') - open_action = QtGui.QAction('&Open File...', self) + open_action = QtWidgets.QAction('&Open File...', self) open_action.setShortcut('Ctrl+O') open_action.setStatusTip('Opens a previously saved image of joint') open_action.triggered.connect(self._on_open) file_menu.addAction(open_action) - save_action = QtGui.QAction('&Save File...', self) + save_action = QtWidgets.QAction('&Save File...', self) save_action.setShortcut('Ctrl+S') save_action.setStatusTip('Saves an image of the joint to a file') save_action.triggered.connect(self._on_save) @@ -242,7 +248,7 @@ def create_menu(self): file_menu.addSeparator() - print_action = QtGui.QAction('&Print...', self) + print_action = QtWidgets.QAction('&Print...', self) print_action.setShortcut('Ctrl+P') print_action.setStatusTip('Print the figure') print_action.triggered.connect(self._on_print) @@ -250,7 +256,7 @@ def create_menu(self): file_menu.addSeparator() - exit_action = QtGui.QAction('&Quit pyRouterJig', self) + exit_action = QtWidgets.QAction('&Quit pyRouterJig', self) exit_action.setShortcut('Ctrl+Q') exit_action.setStatusTip('Quit pyRouterJig') exit_action.triggered.connect(self._on_exit) @@ -258,7 +264,7 @@ def create_menu(self): file_menu.addAction(exit_action) # comment out for now... - #table_action = QtGui.QAction('Print Table...', self) + #table_action = QtWidgets.QAction('Print Table...', self) #table_action.setStatusTip('Print a table of router pass locations') #table_action.triggered.connect(self._on_print_table) #file_menu.addAction(table_action) @@ -267,25 +273,25 @@ def create_menu(self): view_menu = self.menubar.addMenu('View') - self.caul_action = QtGui.QAction('Caul Template', self, checkable=True) + self.caul_action = QtWidgets.QAction('Caul Template', self, checkable=True) self.caul_action.setStatusTip('Toggle caul template') self.caul_action.triggered.connect(self._on_caul) view_menu.addAction(self.caul_action) self.caul_action.setChecked(self.config.show_caul) - self.finger_size_action = QtGui.QAction('Finger Widths', self, checkable=True) + self.finger_size_action = QtWidgets.QAction('Finger Widths', self, checkable=True) self.finger_size_action.setStatusTip('Toggle viewing finger sizes') self.finger_size_action.triggered.connect(self._on_finger_sizes) view_menu.addAction(self.finger_size_action) self.finger_size_action.setChecked(self.config.show_finger_widths) - self.fit_action = QtGui.QAction('Fit', self, checkable=True) + self.fit_action = QtWidgets.QAction('Fit', self, checkable=True) self.fit_action.setStatusTip('Toggle showing fit of joint') self.fit_action.triggered.connect(self._on_fit) view_menu.addAction(self.fit_action) self.fit_action.setChecked(self.config.show_fit) - self.zoom_action = QtGui.QAction('Zoom Mode', self, checkable=True) + self.zoom_action = QtWidgets.QAction('Zoom Mode', self, checkable=True) self.zoom_action.setStatusTip('Toggle zoom mode') self.zoom_action.triggered.connect(self._on_zoom) view_menu.addAction(self.zoom_action) @@ -295,13 +301,13 @@ def create_menu(self): view_menu.addSeparator() pass_menu = view_menu.addMenu('Router Passes') - self.pass_id_action = QtGui.QAction('Identifiers', self, checkable=True) + self.pass_id_action = QtWidgets.QAction('Identifiers', self, checkable=True) self.pass_id_action.setStatusTip('Toggle viewing router pass identifiers') self.pass_id_action.triggered.connect(self._on_pass_id) pass_menu.addAction(self.pass_id_action) self.pass_id_action.setChecked(self.config.show_router_pass_identifiers) - self.pass_location_action = QtGui.QAction('Locations', self, checkable=True) + self.pass_location_action = QtWidgets.QAction('Locations', self, checkable=True) self.pass_location_action.setStatusTip('Toggle viewing router pass locations') self.pass_location_action.triggered.connect(self._on_pass_location) pass_menu.addAction(self.pass_location_action) @@ -310,7 +316,7 @@ def create_menu(self): # The Mac automatically adds full screen to the View menu, but do so for other platforms #if not utils.isMac(): view_menu.addSeparator() - fullscreen_action = QtGui.QAction('Full Screen Mode', self, checkable=True) + fullscreen_action = QtWidgets.QAction('Full Screen Mode', self, checkable=True) fullscreen_action.setShortcut('Ctrl+F') fullscreen_action.setStatusTip('Toggle full-screen mode') fullscreen_action.triggered.connect(self._on_fullscreen) @@ -320,7 +326,7 @@ def create_menu(self): tools_menu = self.menubar.addMenu('Tools') - screenshot_action = QtGui.QAction('Screenshot...', self) + screenshot_action = QtWidgets.QAction('Screenshot...', self) screenshot_action.setShortcut('Ctrl+W') screenshot_action.setStatusTip('Saves an image of the pyRouterJig window to a file') screenshot_action.triggered.connect(self._on_screenshot) @@ -329,7 +335,7 @@ def create_menu(self): # We need to make this action persistent, so that we can # enable and disable it (until all of its functionality is # written) - self.threeDS_action = QtGui.QAction('&Export 3DS...', self) + self.threeDS_action = QtWidgets.QAction('&Export 3DS...', self) self.threeDS_action.setShortcut('Ctrl+E') self.threeDS_action.setStatusTip('Export the joint to a 3DS file') self.threeDS_action.triggered.connect(self._on_3ds) @@ -338,7 +344,7 @@ def create_menu(self): tools_menu.addSeparator() - pref_action = QtGui.QAction('Preferences...', self) + pref_action = QtWidgets.QAction('Preferences...', self) pref_action.setShortcut('Ctrl+,') pref_action.setStatusTip('Open preferences') pref_action.triggered.connect(self._on_preferences) @@ -348,7 +354,7 @@ def create_menu(self): help_menu = self.menubar.addMenu('Help') - about_action = QtGui.QAction('&About pyRouterJig', self) + about_action = QtWidgets.QAction('&About pyRouterJig', self) about_action.setShortcut('Ctrl+A') about_action.setStatusTip('About this program') about_action.triggered.connect(self._on_about) @@ -356,7 +362,7 @@ def create_menu(self): view_menu.addSeparator() - doclink_action = QtGui.QAction('&Documentation...', self) + doclink_action = QtWidgets.QAction('&Documentation...', self) doclink_action.setStatusTip('Opens documentation page in web browser') doclink_action.triggered.connect(self._on_doclink) help_menu.addAction(doclink_action) @@ -403,7 +409,7 @@ def create_widgets(self): ''' Creates all of the widgets in the main panel ''' - self.main_frame = QtGui.QWidget() + self.main_frame = QtWidgets.QWidget() lineEditWidth = 80 us = self.units.units_string(withParens=True) @@ -412,34 +418,34 @@ def create_widgets(self): self.fig = qt_fig.Qt_Fig(self.template, self.boards, self.config) self.fig.canvas.setParent(self.main_frame) self.fig.canvas.setFocusPolicy(QtCore.Qt.StrongFocus) - self.fig.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.fig.canvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) self.fig.canvas.setFocus() # Board width line edit - self.le_board_width_label = QtGui.QLabel('Board Width{}'.format(us)) - self.le_board_width = QtGui.QLineEdit(self.main_frame) + self.le_board_width_label = QtWidgets.QLabel('Board Width{}'.format(us)) + self.le_board_width = QtWidgets.QLineEdit(self.main_frame) self.le_board_width.setFixedWidth(lineEditWidth) self.le_board_width.setText(self.units.increments_to_string(self.boards[0].width)) self.le_board_width.editingFinished.connect(self._on_board_width) - self.le_board_width.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.le_board_width.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) # Bit width line edit - self.le_bit_width_label = QtGui.QLabel('Bit Width{}'.format(us)) - self.le_bit_width = QtGui.QLineEdit(self.main_frame) + self.le_bit_width_label = QtWidgets.QLabel('Bit Width{}'.format(us)) + self.le_bit_width = QtWidgets.QLineEdit(self.main_frame) self.le_bit_width.setFixedWidth(lineEditWidth) self.le_bit_width.setText(self.units.increments_to_string(self.bit.width)) self.le_bit_width.editingFinished.connect(self._on_bit_width) # Bit depth line edit - self.le_bit_depth_label = QtGui.QLabel('Bit Depth{}'.format(us)) - self.le_bit_depth = QtGui.QLineEdit(self.main_frame) + self.le_bit_depth_label = QtWidgets.QLabel('Bit Depth{}'.format(us)) + self.le_bit_depth = QtWidgets.QLineEdit(self.main_frame) self.le_bit_depth.setFixedWidth(lineEditWidth) self.le_bit_depth.setText(self.units.increments_to_string(self.bit.depth)) self.le_bit_depth.editingFinished.connect(self._on_bit_depth) # Bit angle line edit - self.le_bit_angle_label = QtGui.QLabel('Bit Angle (deg.)') - self.le_bit_angle = QtGui.QLineEdit(self.main_frame) + self.le_bit_angle_label = QtWidgets.QLabel('Bit Angle (deg.)') + self.le_bit_angle = QtWidgets.QLineEdit(self.main_frame) self.le_bit_angle.setFixedWidth(lineEditWidth) self.le_bit_angle.setText('%g' % self.bit.angle) self.le_bit_angle.editingFinished.connect(self._on_bit_angle) @@ -448,8 +454,8 @@ def create_widgets(self): self.le_boardm_label = [] self.le_boardm = [] for i in lrange(2): - self.le_boardm_label.append(QtGui.QLabel('Thickness{}'.format(us))) - self.le_boardm.append(QtGui.QLineEdit(self.main_frame)) + self.le_boardm_label.append(QtWidgets.QLabel('Thickness{}'.format(us))) + self.le_boardm.append(QtWidgets.QLineEdit(self.main_frame)) self.le_boardm[i].setFixedWidth(lineEditWidth) s = self.units.increments_to_string(self.boards[i+2].dheight) self.le_boardm[i].setText(s) @@ -468,10 +474,10 @@ def create_widgets(self): self.cb_wood.append(self.create_wood_combo_box(woods, patterns, True)) self.cb_wood.append(self.create_wood_combo_box(woods, patterns, True)) self.cb_wood_label = [] - self.cb_wood_label.append(QtGui.QLabel('Top Board')) - self.cb_wood_label.append(QtGui.QLabel('Bottom Board')) - self.cb_wood_label.append(QtGui.QLabel('Double Board')) - self.cb_wood_label.append(QtGui.QLabel('Double-Double Board')) + self.cb_wood_label.append(QtWidgets.QLabel('Top Board')) + self.cb_wood_label.append(QtWidgets.QLabel('Bottom Board')) + self.cb_wood_label.append(QtWidgets.QLabel('Double Board')) + self.cb_wood_label.append(QtWidgets.QLabel('Double-Double Board')) # Disable double* boards, for now self.le_boardm[0].setEnabled(False) @@ -490,33 +496,33 @@ def create_widgets(self): # ...first slider p = params['Spacing'] - self.es_slider0_label = QtGui.QLabel(labels[0]) - self.es_slider0 = QtGui.QSlider(QtCore.Qt.Horizontal, self.main_frame) + self.es_slider0_label = QtWidgets.QLabel(labels[0]) + self.es_slider0 = QtWidgets.QSlider(QtCore.Qt.Horizontal, self.main_frame) self.es_slider0.setFocusPolicy(QtCore.Qt.StrongFocus) self.es_slider0.setMinimum(p.vMin) self.es_slider0.setMaximum(p.vMax) self.es_slider0.setValue(p.v) - self.es_slider0.setTickPosition(QtGui.QSlider.TicksBelow) + self.es_slider0.setTickPosition(QtWidgets.QSlider.TicksBelow) utils.set_slider_tick_interval(self.es_slider0) self.es_slider0.valueChanged.connect(self._on_es_slider0) - self.es_slider0.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) + self.es_slider0.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) # ...second slider p = params['Width'] - self.es_slider1_label = QtGui.QLabel(labels[1]) - self.es_slider1 = QtGui.QSlider(QtCore.Qt.Horizontal, self.main_frame) + self.es_slider1_label = QtWidgets.QLabel(labels[1]) + self.es_slider1 = QtWidgets.QSlider(QtCore.Qt.Horizontal, self.main_frame) self.es_slider1.setFocusPolicy(QtCore.Qt.StrongFocus) self.es_slider1.setMinimum(p.vMin) self.es_slider1.setMaximum(p.vMax) self.es_slider1.setValue(p.v) - self.es_slider1.setTickPosition(QtGui.QSlider.TicksBelow) + self.es_slider1.setTickPosition(QtWidgets.QSlider.TicksBelow) utils.set_slider_tick_interval(self.es_slider1) self.es_slider1.valueChanged.connect(self._on_es_slider1) - self.es_slider1.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) + self.es_slider1.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) # ...check box for centering p = params['Centered'] - self.cb_es_centered = QtGui.QCheckBox(labels[2], self.main_frame) + self.cb_es_centered = QtWidgets.QCheckBox(labels[2], self.main_frame) self.cb_es_centered.setChecked(True) self.cb_es_centered.stateChanged.connect(self._on_cb_es_centered) @@ -527,71 +533,71 @@ def create_widgets(self): # ...combox box for fingers p = params['Fingers'] - self.cb_vsfingers_label = QtGui.QLabel(labels[0]) + self.cb_vsfingers_label = QtWidgets.QLabel(labels[0]) self.cb_vsfingers = qt_utils.PreviewComboBox(self.main_frame) self.cb_vsfingers.setFocusPolicy(QtCore.Qt.StrongFocus) self.update_cb_vsfingers(p.vMin, p.vMax, p.v) # Edit spacing widgets - edit_btn_undo = QtGui.QPushButton('Undo', self.main_frame) + edit_btn_undo = QtWidgets.QPushButton('Undo', self.main_frame) edit_btn_undo.clicked.connect(self._on_edit_undo) edit_btn_undo.setToolTip('Undo the last change') - edit_btn_add = QtGui.QPushButton('Add', self.main_frame) + edit_btn_add = QtWidgets.QPushButton('Add', self.main_frame) edit_btn_add.clicked.connect(self._on_edit_add) edit_btn_add.setToolTip('Add a cut (if there is space to add cuts)') - edit_btn_del = QtGui.QPushButton('Delete', self.main_frame) + edit_btn_del = QtWidgets.QPushButton('Delete', self.main_frame) edit_btn_del.clicked.connect(self._on_edit_del) edit_btn_del.setToolTip('Delete the active cuts') - edit_move_label = QtGui.QLabel('Move') + edit_move_label = QtWidgets.QLabel('Move') edit_move_label.setToolTip('Moves the active cuts') - edit_btn_moveL = QtGui.QToolButton(self.main_frame) + edit_btn_moveL = QtWidgets.QToolButton(self.main_frame) edit_btn_moveL.setArrowType(QtCore.Qt.LeftArrow) edit_btn_moveL.clicked.connect(self._on_edit_moveL) edit_btn_moveL.setToolTip('Move active cuts to left 1 increment') - edit_btn_moveR = QtGui.QToolButton(self.main_frame) + edit_btn_moveR = QtWidgets.QToolButton(self.main_frame) edit_btn_moveR.setArrowType(QtCore.Qt.RightArrow) edit_btn_moveR.clicked.connect(self._on_edit_moveR) edit_btn_moveR.setToolTip('Move active cuts to right 1 increment') - edit_widen_label = QtGui.QLabel('Widen') + edit_widen_label = QtWidgets.QLabel('Widen') edit_widen_label.setToolTip('Widens the active cuts') - edit_btn_widenL = QtGui.QToolButton(self.main_frame) + edit_btn_widenL = QtWidgets.QToolButton(self.main_frame) edit_btn_widenL.setArrowType(QtCore.Qt.LeftArrow) edit_btn_widenL.clicked.connect(self._on_edit_widenL) edit_btn_widenL.setToolTip('Widen active cuts 1 increment on left side') - edit_btn_widenR = QtGui.QToolButton(self.main_frame) + edit_btn_widenR = QtWidgets.QToolButton(self.main_frame) edit_btn_widenR.setArrowType(QtCore.Qt.RightArrow) edit_btn_widenR.clicked.connect(self._on_edit_widenR) edit_btn_widenR.setToolTip('Widen active cuts 1 increment on right side') - edit_trim_label = QtGui.QLabel('Trim') + edit_trim_label = QtWidgets.QLabel('Trim') edit_trim_label.setToolTip('Trims the active cuts') - edit_btn_trimL = QtGui.QToolButton(self.main_frame) + edit_btn_trimL = QtWidgets.QToolButton(self.main_frame) edit_btn_trimL.setArrowType(QtCore.Qt.LeftArrow) edit_btn_trimL.clicked.connect(self._on_edit_trimL) edit_btn_trimL.setToolTip('Trim active cuts 1 increment on left side') - edit_btn_trimR = QtGui.QToolButton(self.main_frame) + edit_btn_trimR = QtWidgets.QToolButton(self.main_frame) edit_btn_trimR.setArrowType(QtCore.Qt.RightArrow) edit_btn_trimR.clicked.connect(self._on_edit_trimR) edit_btn_trimR.setToolTip('Trim active cuts 1 increment on right side') - edit_btn_toggle = QtGui.QPushButton('Toggle', self.main_frame) + edit_btn_toggle = QtWidgets.QPushButton('Toggle', self.main_frame) edit_btn_toggle.clicked.connect(self._on_edit_toggle) edit_btn_toggle.setToolTip('Toggles the cut at cursor between active and deactive') - edit_btn_cursorL = QtGui.QToolButton(self.main_frame) + edit_btn_cursorL = QtWidgets.QToolButton(self.main_frame) edit_btn_cursorL.setArrowType(QtCore.Qt.LeftArrow) edit_btn_cursorL.clicked.connect(self._on_edit_cursorL) edit_btn_cursorL.setToolTip('Move cut cursor to left') - edit_btn_cursorR = QtGui.QToolButton(self.main_frame) + edit_btn_cursorR = QtWidgets.QToolButton(self.main_frame) edit_btn_cursorR.setArrowType(QtCore.Qt.RightArrow) edit_btn_cursorR.clicked.connect(self._on_edit_cursorR) edit_btn_cursorR.setToolTip('Move cut cursor to right') - edit_btn_activate_all = QtGui.QPushButton('All', self.main_frame) + edit_btn_activate_all = QtWidgets.QPushButton('All', self.main_frame) edit_btn_activate_all.clicked.connect(self._on_edit_activate_all) edit_btn_activate_all.setToolTip('Set all cuts to be active') - edit_btn_deactivate_all = QtGui.QPushButton('None', self.main_frame) + edit_btn_deactivate_all = QtWidgets.QPushButton('None', self.main_frame) edit_btn_deactivate_all.clicked.connect(self._on_edit_deactivate_all) edit_btn_deactivate_all.setToolTip('Set no cuts to be active') @@ -607,17 +613,17 @@ def create_widgets(self): # vbox contains all of the widgets in the main frame, positioned # vertically - vbox = QtGui.QVBoxLayout() + vbox = QtWidgets.QVBoxLayout() # Add the figure canvas to the top vbox.addWidget(self.fig.canvas) # hbox contains all of the control widgets # (everything but the canvas) - hbox = QtGui.QHBoxLayout() + hbox = QtWidgets.QHBoxLayout() # this grid contains all the lower-left input stuff - grid = QtGui.QGridLayout() + grid = QtWidgets.QGridLayout() grid.addWidget(qt_utils.create_hline(), 0, 0, 2, 9, QtCore.Qt.AlignTop) grid.addWidget(qt_utils.create_vline(), 0, 0, 9, 1) @@ -668,14 +674,14 @@ def create_widgets(self): hbox.addLayout(grid) # Create the layout of the Equal spacing controls - hbox_es = QtGui.QHBoxLayout() + hbox_es = QtWidgets.QHBoxLayout() - vbox_es_slider0 = QtGui.QVBoxLayout() + vbox_es_slider0 = QtWidgets.QVBoxLayout() vbox_es_slider0.addWidget(self.es_slider0_label) vbox_es_slider0.addWidget(self.es_slider0) hbox_es.addLayout(vbox_es_slider0) - vbox_es_slider1 = QtGui.QVBoxLayout() + vbox_es_slider1 = QtWidgets.QVBoxLayout() vbox_es_slider1.addWidget(self.es_slider1_label) vbox_es_slider1.addWidget(self.es_slider1) hbox_es.addLayout(vbox_es_slider1) @@ -685,18 +691,18 @@ def create_widgets(self): # Create the layout of the Variable spacing controls. Given only one # item, this is overkill, but the coding allows us to add additional # controls later. - hbox_vs = QtGui.QHBoxLayout() + hbox_vs = QtWidgets.QHBoxLayout() hbox_vs.addWidget(self.cb_vsfingers_label) hbox_vs.addWidget(self.cb_vsfingers) hbox_vs.addStretch(1) # Create the layout of the edit spacing controls - hbox_edit = QtGui.QHBoxLayout() - grid_edit = QtGui.QGridLayout() + hbox_edit = QtWidgets.QHBoxLayout() + grid_edit = QtWidgets.QGridLayout() grid_edit.addWidget(qt_utils.create_hline(), 0, 0, 2, 16, QtCore.Qt.AlignTop) grid_edit.addWidget(qt_utils.create_hline(), 2, 0, 2, 16, QtCore.Qt.AlignTop) grid_edit.addWidget(qt_utils.create_vline(), 0, 0, 6, 1) - label_active_cut_select = QtGui.QLabel('Active Cut Select') + label_active_cut_select = QtWidgets.QLabel('Active Cut Select') label_active_cut_select.setToolTip('Tools that select the active cuts') grid_edit.addWidget(label_active_cut_select, 1, 1, 1, 3, QtCore.Qt.AlignHCenter) grid_edit.addWidget(edit_btn_toggle, 3, 1, 1, 2, QtCore.Qt.AlignHCenter) @@ -705,7 +711,7 @@ def create_widgets(self): grid_edit.addWidget(edit_btn_activate_all, 3, 3) grid_edit.addWidget(edit_btn_deactivate_all, 4, 3) grid_edit.addWidget(qt_utils.create_vline(), 0, 4, 6, 1) - label_active_cut_ops = QtGui.QLabel('Active Cut Operators') + label_active_cut_ops = QtWidgets.QLabel('Active Cut Operators') label_active_cut_ops.setToolTip('Edit operations applied to active cuts') grid_edit.addWidget(label_active_cut_ops, 1, 5, 1, 10, QtCore.Qt.AlignHCenter) grid_edit.addWidget(edit_move_label, 3, 5, 1, 2, QtCore.Qt.AlignHCenter) @@ -731,14 +737,14 @@ def create_widgets(self): hbox_edit.addWidget(edit_btn_undo) # Add the spacing layouts as Tabs - self.tabs_spacing = QtGui.QTabWidget() - tab_es = QtGui.QWidget() + self.tabs_spacing = QtWidgets.QTabWidget() + tab_es = QtWidgets.QWidget() tab_es.setLayout(hbox_es) self.tabs_spacing.addTab(tab_es, 'Equal') - tab_vs = QtGui.QWidget() + tab_vs = QtWidgets.QWidget() tab_vs.setLayout(hbox_vs) self.tabs_spacing.addTab(tab_vs, 'Variable') - tab_edit = QtGui.QWidget() + tab_edit = QtWidgets.QWidget() tab_edit.setLayout(hbox_edit) self.tabs_spacing.addTab(tab_edit, 'Editor') self.tabs_spacing.currentChanged.connect(self._on_tabs_spacing) @@ -754,7 +760,7 @@ def create_widgets(self): self.tabs_spacing.setCurrentIndex(self.spacing_index) # either add the spacing Tabs to the right of the line edits - vbox_tabs = QtGui.QVBoxLayout() + vbox_tabs = QtWidgets.QVBoxLayout() vbox_tabs.addWidget(self.tabs_spacing) vbox_tabs.addWidget(self.le_description) vbox_tabs.addStretch(1) @@ -858,25 +864,25 @@ def create_status_bar(self): fmL = QtGui.QFontMetrics(fontL) fit_text = 'Fit:' - fit = QtGui.QLabel(fit_text) + fit = QtWidgets.QLabel(fit_text) fit.setFont(fontL) fit.setFixedWidth(fmL.width(fit_text)) fit.setToolTip(tt_fit) status_text = 'Status:' - status = QtGui.QLabel(status_text) + status = QtWidgets.QLabel(status_text) status.setFont(fontL) status.setFixedWidth(fmL.width(status_text)) status.setToolTip(tt_message) # Create the label widgets that will change their text - style = QtGui.QFrame.Panel | QtGui.QFrame.Raised - self.status_message_label = QtGui.QLabel('MESSAGE') + style = QtWidgets.QFrame.Panel | QtWidgets.QFrame.Raised + self.status_message_label = QtWidgets.QLabel('MESSAGE') self.status_message_label.setFont(font) self.status_message_label.setFrameStyle(style) self.status_message_label.setToolTip(tt_message) - self.status_fit_label = QtGui.QLabel('FIT') + self.status_fit_label = QtWidgets.QLabel('FIT') w = fm.width('Max gap = 0.0000 mm Max overlap = 0.0000 mm') self.status_fit_label.setFixedWidth(w) self.status_fit_label.setFont(font) @@ -1056,11 +1062,11 @@ def _on_tabs_spacing(self, index): msg = 'You are exiting the Editor, which will discard'\ ' any changes made in the Editor.'\ '\n\nAre you sure you want to do this?' - reply = QtGui.QMessageBox.question(self, 'Message', msg, - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + reply = QtWidgets.QMessageBox.question(self, 'Message', msg, + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) - if reply == QtGui.QMessageBox.No: + if reply == QtWidgets.QMessageBox.No: self.tabs_spacing.setCurrentIndex(self.spacing_index) return self.reinit_spacing() @@ -1207,8 +1213,6 @@ def _on_save(self, do_screenshot=False): # Form the default filename prefix prefix = 'pyrouterjig' -# if self.description is not None: -# prefix = self.description suffix = 'png' if self.screenshot_index is None: self.screenshot_index = utils.get_file_index(self.working_dir, prefix, suffix) @@ -1224,14 +1228,15 @@ def _on_save(self, do_screenshot=False): else: # This is the simple approach to set the filename, but doesn't allow # us to update the working_dir, if the user changes it. - #filename = QtGui.QFileDialog.getSaveFileName(self, 'Save file', \ + #filename = QtWidgets.QFileDialog.getSaveFileName(self, 'Save file', \ # defname, 'Portable Network Graphics (*.png)') # ... so here is now we do it: - dialog = QtGui.QFileDialog(self, 'Save file', defname, \ - 'Portable Network Graphics (*.png)') + dialog = QtWidgets.QFileDialog(self, 'Save file', defname, \ + 'Portable Network Graphics (*.png)') + dialog.setDefaultSuffix(suffix) - dialog.setFileMode(QtGui.QFileDialog.AnyFile) - dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + dialog.setFileMode(QtWidgets.QFileDialog.AnyFile) + dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) filename = None if dialog.exec_(): filenames = dialog.selectedFiles() @@ -1246,16 +1251,43 @@ def _on_save(self, do_screenshot=False): return # Save the file with metadata + if do_screenshot: - image = QtGui.QPixmap.grabWindow(self.winId()).toImage() + p_screen=QtWidgets.QApplication.primaryScreen() + scr_rect = p_screen.geometry(); + image = QtGui.QScreen.grabWindow(p_screen, scr_rect.x(), scr_rect.y(), scr_rect.width(), scr_rect.height()).toImage() + #, scr_rect.x, scr_rect.y, scr_rect.width, scr_rect.height) + #.toImage() else: image = self.fig.image(self.template, self.boards, self.bit, self.spacing, self.woods, self.description) s = serialize.serialize(self.bit, self.boards, self.spacing, self.config) - image.setText('pyRouterJig', s) - r = image.save(filename, 'png') + + #we using UUEC encoding so need more attributes in the image file + #QT5 does not work propertly with PNG text use PIL as workaround + + #Save QT image into stream and get it back into PIL to avoid native pil conversion risks + buffer = QtCore.QBuffer() + buffer.open(QtCore.QIODevice.ReadWrite) + image.save(buffer,"PNG") + pio=BytesIO() + pio.write(buffer.data()) + pio.seek(0) + buffer.close() + pilimg = Image.open(pio) + + info = PngImagePlugin.PngInfo() + info.add_text('pyRouterJig',s) + info.add_text('pyRouterJig_v',utils.VERSION) + + r = True + try: + pilimg.save(filename,'png',pnginfo=info) + except OSError: + r = False + if r: self.status_message('Saved to file %s' % filename) if self.screenshot_index is not None: @@ -1280,31 +1312,38 @@ def _on_open(self): msg = 'Current joint not saved.'\ ' Opening a new file will overwrite the current joint.'\ '\n\nAre you sure you want to do this?' - reply = QtGui.QMessageBox.question(self, 'Message', msg, - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + reply = QtWidgets.QMessageBox.question(self, 'Message', msg, + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) - if reply == QtGui.QMessageBox.No: + if reply == QtWidgets.QMessageBox.No: return # Get the file name - filename = QtGui.QFileDialog.getOpenFileName(self, 'Open file', \ - self.working_dir, \ - 'Portable Network Graphics (*.png)') - filename = str(filename).strip() + filename, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', \ + self.working_dir, \ + 'Portable Network Graphics (*.png)') + + + #filename = str(filename).strip() if len(filename) == 0: self.status_message('File open aborted', warning=True) return # From the image file, parse the metadata. - image = QtGui.QImage(filename) - s = image.text('pyRouterJig') # see setText in _on_save + #image = QtGui.QImage + #image.Load(filename) + image = Image.open(filename) + s=image.info['pyRouterJig']; + if len(s) == 0: msg = 'File %s does not contain pyRouterJig data. The PNG file'\ ' must have been saved using pyRouterJig.' % filename - QtGui.QMessageBox.warning(self, 'Error', msg) + QtWidgets.QMessageBox.warning(self, 'Error', msg) return - (self.bit, self.boards, sp, sp_type) = serialize.unserialize(s, self.config) + + #backword compatimility + (self.bit, self.boards, sp, sp_type) = serialize.unserialize(s, self.config, ('pyRouterJig_v' in image.info.keys()) ) # Reset the dependent data self.units = self.bit.units @@ -1314,11 +1353,15 @@ def _on_open(self): # ... set the wood selection for each board. If the wood does not # exist, set to a wood we know exists. This can happen if the wood # image files don't exist across users. + # self.boards[i].wood is newstr type use str(self.boards[i].wood) is for old files compatibility for i in lrange(4): if self.boards[i].wood is None: self.boards[i].set_wood('NONE') - elif self.boards[i].wood not in self.woods.keys(): + elif str(self.boards[i].wood) not in self.woods.keys(): self.boards[i].set_wood('DiagCrossPattern') + + #bacword compatibility fix + self.boards[i].wood = str(self.boards[i].wood) j = self.cb_wood[i].findText(self.boards[i].wood) self.cb_wood[i].setCurrentIndex(j) @@ -1384,10 +1427,10 @@ def _on_3ds(self): # Get the file name defname = os.path.join(self.working_dir, fname) - dialog = QtGui.QFileDialog(self, 'Export joint', defname, \ + dialog = QtWidgets.QFileDialog(self, 'Export joint', defname, \ 'Autodesk 3DS file (*.3ds)') - dialog.setFileMode(QtGui.QFileDialog.AnyFile) - dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + dialog.setFileMode(QtWidgets.QFileDialog.AnyFile) + dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) filename = None if dialog.exec_(): filenames = dialog.selectedFiles() @@ -1441,7 +1484,7 @@ def _on_about(self): if self.config.debug: print('_on_about') - box = QtGui.QMessageBox(self) + box = QtWidgets.QMessageBox(self) s = 'Welcome to pyRouterJig !' s += '

Version: %s

' % utils.VERSION box.setText(s + self.doc.short_desc() + self.doc.license()) @@ -1495,15 +1538,16 @@ def _on_exit(self): if self.config.debug: print('_on_exit') if self.file_saved: - QtGui.qApp.quit() + #QtGui.qApp.quit() + QtWidgets.qApp.quit() else: msg = 'Figure was changed but not saved. Are you sure you want to quit?' - reply = QtGui.QMessageBox.question(self, 'Message', msg, - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + reply = QtWidgets.QMessageBox.question(self, 'Message', msg, + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: - QtGui.qApp.quit() + if reply == QtWidgets.QMessageBox.Yes: + QtWidgets.qApp.quit() @QtCore.pyqtSlot() def _on_doclink(self): @@ -1943,7 +1987,7 @@ def run(): # QtGui.QApplication.setStyle('motif') # QtGui.QApplication.setStyle('cde') - app = QtGui.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) driver = Driver() driver.show() driver.center() diff --git a/qt_fig.py b/qt_fig.py index 18c262f..008d6ff 100644 --- a/qt_fig.py +++ b/qt_fig.py @@ -30,7 +30,7 @@ import router import utils -from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets def paint_text(painter, text, coord, flags, shift=(0, 0), angle=0, fill_color=None): ''' @@ -82,14 +82,14 @@ def paint_text(painter, text, coord, flags, shift=(0, 0), angle=0, fill_color=No painter.setTransform(transform) return rect -class Qt_Fig(QtGui.QWidget): +class Qt_Fig(QtWidgets.QWidget): ''' Interface to the qt_driver, using Qt to draw the boards and template. The attribute "canvas" is self, to mimic the old interface to matplotlib, which this class replaced. ''' def __init__(self, template, boards, config): - QtGui.QWidget.__init__(self) + QtWidgets.QWidget.__init__(self) self.canvas = self self.config = config self.fig_width = -1 @@ -233,10 +233,10 @@ def print(self, template, boards, bit, spacing, woods, description): self.config) # Print through the preview dialog - printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) - printer.setOrientation(QtGui.QPrinter.Landscape) - printer.setPageMargins(0, 0, 0, 0, QtGui.QPrinter.Inch) - pdialog = QtGui.QPrintPreviewDialog(printer) + printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution) + printer.setOrientation(QtPrintSupport.QPrinter.Landscape) + printer.setPageMargins(0, 0, 0, 0, QtPrintSupport.QPrinter.Inch) + pdialog = QtPrintSupport.QPrintPreviewDialog(printer) pdialog.setModal(True) pdialog.paintRequested.connect(self.preview_requested) return pdialog.exec_() @@ -358,10 +358,12 @@ def paint_all(self, painter, dpi=None): painter.translate(x, y) self.transform = painter.transform() + # draw the objects self.draw_boards(painter) self.draw_template(painter) self.draw_title(painter) + self.draw_finger_sizes(painter) if self.config.show_finger_widths: self.draw_finger_sizes(painter) @@ -474,9 +476,15 @@ def draw_alignment(self, painter): # draw the alignment lines on both templates x = board_T.xR() + self.geom.bit.width // 2 + pen = QtGui.QPen(QtCore.Qt.SolidLine) pen.setColor(self.colors['template_margin_foreground']) - painter.setPen(pen) + pen.setWidthF(0) + + bg_pen=QtGui.QPen(QtCore.Qt.SolidLine) + bg_pen.setColor(QtGui.QColor('White')) + bg_pen.setWidthF(0) + self.set_font_size(painter, 'template') label = 'ALIGN' flags = QtCore.Qt.AlignTop | QtCore.Qt.AlignHCenter @@ -484,8 +492,12 @@ def draw_alignment(self, painter): if b is not None: y1 = b.yB() y2 = b.yT() + painter.setPen(pen) painter.drawLine(x, y1, x, y2) paint_text(painter, label, (x, (y1 + y2) // 2), flags, (0, 0), -90) + painter.setPen(bg_pen) + painter.drawLine(QtCore.QPointF(x-0.5, y1+0.5),QtCore.QPointF( x-0.5, y2-0.5) ) + painter.drawLine(QtCore.QPointF(x+0.5, y1+0.5),QtCore.QPointF(x+0.5, y2-0.5) ) def draw_template_rectangle(self, painter, r, b): ''' @@ -527,12 +539,17 @@ def draw_template(self, painter): pen_canvas = QtGui.QPen(QtCore.Qt.SolidLine) pen_canvas.setColor(self.colors['canvas_foreground']) + pen_canvas.setWidthF(0) penA = QtGui.QPen(QtCore.Qt.SolidLine) penA.setColor(self.colors['pass_color']) + penA.setWidthF(0) penB = QtGui.QPen(QtCore.Qt.DashLine) penB.setColor(self.colors['pass_alt_color']) + penB.setWidthF(0) + painter.setPen(pen_canvas) self.draw_template_rectangle(painter, rect_T, board_T) + if boards[3].active: rect_TDD = self.geom.rect_TDD board_TDD = self.geom.board_TDD @@ -670,6 +687,7 @@ def draw_template(self, painter): else: pen = QtGui.QPen(QtCore.Qt.DashLine) pen.setColor(self.colors['center_color']) + pen.setWidthF(0) painter.setPen(pen) painter.drawLine(xMid, rect_caul.yB(), xMid, rect_caul.yT()) painter.setPen(self.colors['template_margin_foreground']) @@ -679,6 +697,7 @@ def draw_template(self, painter): # Label the templates pen = QtGui.QPen(QtCore.Qt.DashLine) pen.setColor(self.colors['center_color']) + pen.setWidthF(0) self.set_font_size(painter, 'template_labels') if len(centerline) > 0: label_bottom += '\nCenter: ' + centerline[0] @@ -735,7 +754,9 @@ def draw_one_board(self, painter, board, bit, fill_color): color = self.colors['board_foreground'] brush = QtGui.QBrush(color, icon) (inverted, invertable) = self.transform.inverted() - brush.setMatrix(inverted.toAffine()) + #setMatrix is not offered anymore + #brush.setMatrix(inverted.toAffine()) + brush.setTransform(inverted) painter.setBrush(brush) painter.drawPolygon(poly) painter.restore() @@ -755,6 +776,7 @@ def draw_boards(self, painter): self.set_font_size(painter, 'boards') pen = QtGui.QPen(QtCore.Qt.SolidLine) pen.setColor(self.colors['canvas_foreground']) + pen.setWidthF(0) painter.setPen(pen) x1 = self.geom.boards[0].xL() - self.geom.bit.width // 2 @@ -822,17 +844,17 @@ def draw_active_cuts(self, painter): If the spacing supports it, highlight the active cuts and draw their limits ''' - # draw the perimeter of the cursor cut + f = self.geom.spacing.cursor_cut if f is None: return - poly = self.cut_polygon(self.geom.boards[0].bottom_cuts[f]) - painter.save() - pen = QtGui.QPen(QtCore.Qt.blue) - pen.setWidth(1) - painter.setPen(pen) - painter.drawPolyline(poly) - painter.restore() + + pen = QtGui.QPen() + pen.setWidthF(0) + + # get the perimeter of the cursor + cursor_poly = self.cut_polygon(self.geom.boards[0].bottom_cuts[f]) + # initialize limits xminG = self.geom.boards[0].width @@ -842,7 +864,7 @@ def draw_active_cuts(self, painter): painter.save() brush = QtGui.QBrush(QtGui.QColor(255, 0, 0, 75)) painter.setBrush(brush) - pen = QtGui.QPen(QtCore.Qt.red) + pen.setColor(QtCore.Qt.red) painter.setPen(pen) fcolor = QtGui.QColor(self.colors['board_background']) fcolor.setAlphaF(1.0) @@ -863,16 +885,26 @@ def draw_active_cuts(self, painter): painter.restore() # draw the limits + # using QPointF to avoid border marks overlap with cursor painter.save() xminG += self.geom.boards[0].xL() xmaxG += self.geom.boards[0].xL() yB = self.geom.boards[0].yB() yT = self.geom.boards[0].yT() - painter.setPen(QtCore.Qt.green) - painter.drawLine(xminG, yB, xminG, yT) - painter.drawLine(xmaxG, yB, xmaxG, yT) + pen.setColor(QtCore.Qt.green) + painter.setPen(pen) + painter.drawLine(QtCore.QPointF(xminG - 0.5, yB), QtCore.QPointF(xminG - 0.5, yT) ) + painter.drawLine(QtCore.QPointF(xmaxG + 0.5, yB), QtCore.QPointF(xmaxG + 0.5, yT) ) painter.restore() + # draw the perimeter of the cursor cut at the end to get it always visible + painter.save() + pen.setColor(QtCore.Qt.blue) + painter.setPen(pen) + painter.drawPolyline(cursor_poly) + painter.restore() + + def draw_title(self, painter): ''' Draws the title diff --git a/qt_test.py b/qt_test.py index c898b28..8a894a0 100644 --- a/qt_test.py +++ b/qt_test.py @@ -28,11 +28,12 @@ import unittest from qt_driver import Driver import utils -from PyQt4 import QtGui -from PyQt4 import QtCore -from PyQt4.QtTest import QTest +from PyQt5 import QtGui +from PyQt5 import QtCore +from PyQt5 import QtWidgets +from PyQt5.QtTest import QTest -app = QtGui.QApplication(sys.argv) +app = QtWidgets.QApplication(sys.argv) class Case(object): def __init__(self, angle, width, depth, spacing, board_width=7): diff --git a/qt_utils.py b/qt_utils.py index 4ea258b..717465c 100644 --- a/qt_utils.py +++ b/qt_utils.py @@ -29,7 +29,7 @@ import utils import router -from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets def set_router_value(line_edit, obj, attr, setter, is_float=False, bit=None): # With editingFinished, we also need to check whether the @@ -61,13 +61,13 @@ def set_router_value(line_edit, obj, attr, setter, is_float=False, bit=None): except router.Router_Exception as e: # Notify the user of the error, and set the line editor back to its # original value - QtGui.QMessageBox.warning(line_edit.parentWidget(), 'Error', e.msg) + QtWidgets.QMessageBox.warning(line_edit.parentWidget(), 'Error', e.msg) old_value = getattr(obj, attr) text = units.increments_to_string(old_value) line_edit.setText(text) return None -class PreviewComboBox(QtGui.QComboBox): +class PreviewComboBox(QtWidgets.QComboBox): ''' This comboxbox emits "activated" when hidePopup is called. This allows for a combobox with a preview mode, so that as each selection is @@ -77,20 +77,20 @@ class PreviewComboBox(QtGui.QComboBox): ''' def __init__(self, parent): - QtGui.QComboBox.__init__(self, parent) + QtWidgets.QComboBox.__init__(self, parent) def hidePopup(self): - QtGui.QComboBox.hidePopup(self) + QtWidgets.QComboBox.hidePopup(self) #print('hidePopup') self.activated.emit(self.currentIndex()) -class ShadowTextLineEdit(QtGui.QLineEdit): +class ShadowTextLineEdit(QtWidgets.QLineEdit): ''' This line edit sets a grayed shadow text, until focus is received and text is entered. Shadow text is the text displayed when the line edit is empty. ''' def __init__(self, parent, shadow_text): - QtGui.QLineEdit.__init__(self, parent) + QtWidgets.QLineEdit.__init__(self, parent) self.shadow_text = shadow_text self.initialize_shadow() @@ -100,14 +100,14 @@ def initialize_shadow(self): self.has_real_text = False def focusInEvent(self, event): - QtGui.QLineEdit.focusInEvent(self, event) + QtWidgets.QLineEdit.focusInEvent(self, event) # If no real text, clear the shadow text and darken the text if not self.has_real_text: self.clear() self.setStyleSheet('color: black;') def focusOutEvent(self, event): - QtGui.QLineEdit.focusOutEvent(self, event) + QtWidgets.QLineEdit.focusOutEvent(self, event) # If there's no text, set it back to the shadow if len(str(self.text())) == 0: self.initialize_shadow() @@ -116,22 +116,22 @@ def focusOutEvent(self, event): def set_line_style(line): '''Sets the style for create_vline() and create_hline()''' - line.setFrameShadow(QtGui.QFrame.Raised) + line.setFrameShadow(QtWidgets.QFrame.Raised) line.setLineWidth(1) line.setMidLineWidth(1) - line.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + line.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) def create_vline(): '''Creates a vertical line''' - vline = QtGui.QFrame() - vline.setFrameStyle(QtGui.QFrame.VLine) + vline = QtWidgets.QFrame() + vline.setFrameStyle(QtWidgets.QFrame.VLine) set_line_style(vline) return vline def create_hline(): '''Creates a horizontal line''' - hline = QtGui.QFrame() - hline.setFrameStyle(QtGui.QFrame.HLine) + hline = QtWidgets.QFrame() + hline.setFrameStyle(QtWidgets.QFrame.HLine) set_line_style(hline) return hline diff --git a/serialize.py b/serialize.py index 0ce6bf3..4b374f6 100644 --- a/serialize.py +++ b/serialize.py @@ -24,10 +24,10 @@ from __future__ import print_function from future.utils import lrange try: - from StringIO import StringIO + from StringIO import StringIO, BytesIO except ImportError: - from io import StringIO - + from io import StringIO, BytesIO +import binascii import pickle import router import utils @@ -38,7 +38,9 @@ def serialize(bit, boards, sp, config): Serializes the arguments. Returns the serialized string, which can later be used to reconstruct the arguments using unserialize() ''' - out = StringIO() + #out = StringIO() + out = BytesIO() + p = pickle.Pickler(out) # Save code version p.dump(utils.VERSION) @@ -72,13 +74,23 @@ def serialize(bit, boards, sp, config): out.close() if config.debug: print('size of pickle', len(s)) - return s + ret = binascii.b2a_qp(s).decode('utf-8') + #binascii.b2a_base64(s).decode("utf-8", "strict") + return ret -def unserialize(s, config): +def unserialize(s, config, newFormat=False): ''' Unserializes the string s, and returns the tuple (bit, boards, spacing) ''' - inp = StringIO(s) + inp = ''; + # new format uue encoding support + if newFormat: + s = binascii.a2b_qp(s) + else: + s = s.encode() + + inp = BytesIO(s) + u = pickle.Unpickler(inp) version = u.load() if config.debug: From bd9be0e881810109d64924b347509430d82e257b Mon Sep 17 00:00:00 2001 From: Valdas2000 <35214527+Valdas2000@users.noreply.github.com> Date: Fri, 12 Jan 2018 11:27:22 +0300 Subject: [PATCH 2/3] [~] Fix Printer issue. The import got not contain QtPrintSupport Now print function works with no failures --- qt_fig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_fig.py b/qt_fig.py index 008d6ff..3d5ea1f 100644 --- a/qt_fig.py +++ b/qt_fig.py @@ -30,7 +30,7 @@ import router import utils -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport def paint_text(painter, text, coord, flags, shift=(0, 0), angle=0, fill_color=None): ''' From d36409a58f22e7402c26734887988adbc0e69f55 Mon Sep 17 00:00:00 2001 From: Valdas <35214527+Valdas2000@users.noreply.github.com> Date: Thu, 8 Feb 2018 16:24:48 +0300 Subject: [PATCH 3/3] [+] Imperial bits in Metric scales Changes: 1 - Midline used everywhere for calculations 2 - Allow even and odd bits for dovetail joins 3 - min_finger_width respected across all functionality 4 - gap calculation optimized 5 - Midline depended on bit height ( so usually there are 2 optimal height values for a dovetail bit) 6 - qt_test got back (QT5 migration) ---------------- Fixes: 1 - Screenshot got back (QT5 fix) 2 - Mouse events for zooming got back (QT5 fix) 3 - Save configuration fixed (typo in initial version) 4 - Application Works fine on multi-screen (up to 4 screen configuration verified) TODO: 1 - optimize router cuts ( cuts more that half bit diameter are not welcomed), edge cuts must be delicate 2 - show optimal bit height for dovetails (bit.height_0) 3 - update signs for templates stripes showing bit info and optimal height 4 - allow reverse in variable spacing (probably it's better to put a slider for fine tune d value) 5 - Disallow fractional diameters for straight bits in metric Limitations: 1 - Metric users must care. Fractional values allowed only for dovetails. Fractional in dovetails causes error 2 - works well on 1mm precision (other values are not well tested) 3 - Alignment line fixed to 0 (it will be fine to adopt it to edge point) --- qt_driver.py | 10 +- qt_fig.py | 24 ++-- qt_test.py | 4 +- router.py | 231 +++++++++++++++----------------- spacing.py | 365 +++++++++++++++++++++++++++++++++------------------ utils.py | 20 ++- 6 files changed, 378 insertions(+), 276 deletions(-) diff --git a/qt_driver.py b/qt_driver.py index 576bba2..ac5a732 100644 --- a/qt_driver.py +++ b/qt_driver.py @@ -24,6 +24,7 @@ from __future__ import print_function from future.utils import lrange from builtins import str +from decimal import * from PIL import Image from PIL import PngImagePlugin try: @@ -31,7 +32,7 @@ except ImportError: from io import BytesIO -import os, sys, traceback, webbrowser, copy, shutil +import os, sys, traceback, webbrowser, copy, shutil, math import qt_fig import qt_config @@ -1254,10 +1255,8 @@ def _on_save(self, do_screenshot=False): if do_screenshot: p_screen=QtWidgets.QApplication.primaryScreen() - scr_rect = p_screen.geometry(); - image = QtGui.QScreen.grabWindow(p_screen, scr_rect.x(), scr_rect.y(), scr_rect.width(), scr_rect.height()).toImage() - #, scr_rect.x, scr_rect.y, scr_rect.width, scr_rect.height) - #.toImage() + image = p_screen.grabWindow(self.winId()) + else: image = self.fig.image(self.template, self.boards, self.bit, self.spacing, self.woods, self.description) @@ -1980,6 +1979,7 @@ def run(): ''' Sets up and runs the application ''' + getcontext().prec = 4 # QtGui.QApplication.setStyle('plastique') # QtGui.QApplication.setStyle('windows') # QtGui.QApplication.setStyle('windowsxp') diff --git a/qt_fig.py b/qt_fig.py index 3d5ea1f..bdc2471 100644 --- a/qt_fig.py +++ b/qt_fig.py @@ -23,6 +23,7 @@ ''' from __future__ import print_function from __future__ import division +from decimal import Decimal from future.utils import lrange import time @@ -363,7 +364,7 @@ def paint_all(self, painter, dpi=None): self.draw_boards(painter) self.draw_template(painter) self.draw_title(painter) - self.draw_finger_sizes(painter) + #self.draw_finger_sizes(painter) if self.config.show_finger_widths: self.draw_finger_sizes(painter) @@ -826,9 +827,9 @@ def cut_polygon(self, c): xLB = xLT xRB = xRT if c.xmin > 0: - xLB += self.geom.bit.offset + xLB += self.geom.bit.overhang if c.xmax < self.geom.boards[0].width: - xRB -= self.geom.bit.offset + xRB -= self.geom.bit.overhang yB = boards[0].yB() yT = yB + self.geom.bit.depth poly = QtGui.QPolygonF() @@ -893,8 +894,9 @@ def draw_active_cuts(self, painter): yT = self.geom.boards[0].yT() pen.setColor(QtCore.Qt.green) painter.setPen(pen) - painter.drawLine(QtCore.QPointF(xminG - 0.5, yB), QtCore.QPointF(xminG - 0.5, yT) ) - painter.drawLine(QtCore.QPointF(xmaxG + 0.5, yB), QtCore.QPointF(xmaxG + 0.5, yT) ) + half = Decimal(0.5) + painter.drawLine(QtCore.QPointF(xminG - half, yB), QtCore.QPointF(xminG - half, yT) ) + painter.drawLine(QtCore.QPointF(xmaxG + half, yB), QtCore.QPointF(xmaxG + half, yT) ) painter.restore() # draw the perimeter of the cursor cut at the end to get it always visible @@ -919,6 +921,7 @@ def draw_title(self, painter): def draw_finger_sizes(self, painter): ''' Annotates the finger sizes on each board + with 3 digit precision ''' units = self.geom.bit.units self.set_font_size(painter, 'template') @@ -941,7 +944,7 @@ def draw_finger_sizes(self, painter): for c in bcuts: x = self.geom.boards[1].xL() + (c.xmin + c.xmax) // 2 y = self.geom.boards[1].yT() - label = units.increments_to_string(c.xmax - c.xmin) + label = units.increments_to_string( round(c.xmax - c.xmin, 3) ) p = (x, y) paint_text(painter, label, p, flags, shift, fill_color=fcolor) # ... do the A cuts @@ -950,7 +953,7 @@ def draw_finger_sizes(self, painter): for c in acuts: x = self.geom.boards[0].xL() + (c.xmin + c.xmax) // 2 y = self.geom.boards[0].yB() - label = units.increments_to_string(c.xmax - c.xmin) + label = units.increments_to_string( round(c.xmax - c.xmin, 3) ) p = (x, y) paint_text(painter, label, p, flags, shift, fill_color=fcolor) @@ -1016,8 +1019,9 @@ def wheelEvent(self, event): return if self.config.debug: - print('qt_fig.wheelEvent', event.delta()) - self.scaling *= 1 + 0.05 * event.delta() / 120 + print('qt_fig.wheelEvent', event.angleDelta()) + ppp = event.angleDelta() / 120 + self.scaling *= 1 + 0.05 * ppp.y() self.update() def mousePressEvent(self, event): @@ -1065,7 +1069,7 @@ def mouseMoveEvent(self, event): event.ignore() return - pos = self.base_transform.map(event.posF()) + pos = self.base_transform.map(event.localPos()) if self.config.debug: print('mouse moved here: {} {}'.format(pos.x(), pos.y())) diffx = (pos.x() - self.mouse_pos.x()) diff --git a/qt_test.py b/qt_test.py index 8a894a0..6d9d8a0 100644 --- a/qt_test.py +++ b/qt_test.py @@ -67,7 +67,7 @@ def setUp(self): self.d.show() self.d.raise_() self.debug = self.d.config.debug - QTest.qWaitForWindowShown(self.d) + QTest.qWaitForWindowExposed(self.d) if not utils.isMac(): self.d.working_dir = 'Z:\Windows\pyRouterJig\images' def test_options(self): @@ -78,7 +78,7 @@ def test_defaults(self): self.assertEqual(str(self.d.le_bit_depth.text()), '3/4') self.assertEqual(str(self.d.le_bit_angle.text()), '0') def screenshot(self, do_screenshot=True): - QTest.qWaitForWindowShown(self.d) + QTest.qWaitForWindowExposed(self.d) QTest.qWait(100) self.d._on_save(do_screenshot) def test_screenshots(self): diff --git a/router.py b/router.py index 488eded..6ee6936 100644 --- a/router.py +++ b/router.py @@ -26,6 +26,7 @@ from future.utils import lrange import math +from decimal import * import utils class Router_Exception(Exception): @@ -82,6 +83,14 @@ class Router_Bit(object): neck: width of bit at board surface. + midline: disstance between cuts in incremental units + + depth_0: optimal depth with perfect fit + + width_f: perfect width with no rounding + + gap: the calculated disstance to perfect fit value + halfwidth: half of width ''' def __init__(self, units, width, depth, angle=0): @@ -89,6 +98,13 @@ def __init__(self, units, width, depth, angle=0): self.width = width self.depth = depth self.angle = angle + + self.midline = Decimal('0') + self.depth_0 = Decimal('0') + self.width_f = Decimal(repr(width)) + self.overhang = (self.width_f - self.midline) / 2 + self.gap= Decimal('0') + self.reinit() def set_width_from_string(self, s): ''' @@ -101,19 +117,21 @@ def set_width_from_string(self, s): msg = 'Unable to set Bit Width to: {}

'\ 'Set to a positive value, such as: {}'.format(s, val) try: - width = self.units.string_to_increments(s) + if self.units.metric: + width = self.units.string_to_float(s) + else: + width = self.units.string_to_increments(s) except: raise Router_Exception(msg) if width <= 0: raise Router_Exception(msg) - halfwidth = width // 2 - if 2 * halfwidth != width: - msg += '

Bit Width must be an even number of increments.

'\ - 'The increment size is: {}

'\ - ''.format(self.units.increments_to_string(1, True)) - raise Router_Exception(msg) + #halfwidth = width // 2 + #if 2 * halfwidth != width: + # msg += '

Bit Width must be an even number of increments.

'\ + # 'The increment size is: {}

'\ + # ''.format(self.units.increments_to_string(1, True)) + # raise Router_Exception(msg) self.width = width - self.halfwidth = halfwidth self.reinit() def set_depth_from_string(self, s): ''' @@ -152,19 +170,19 @@ def reinit(self): Reinitializes internal attributes that are dependent on width and angle. ''' - self.halfwidth = self.width // 2 - self.offset = 0 # ensure exactly 0 for angle == 0 + self.midline = Decimal(repr(self.width)) + self.depth_0 = Decimal(repr(self.depth)) + self.width_f = Decimal(repr(self.width)) + if self.angle > 0: - self.offset = self.depth * math.tan(self.angle * math.pi / 180) - self.neck = self.width - 2 * self.offset - def dovetail_correction(self): - ''' - Correction for rounding the offset - ''' - if utils.my_round(self.neck) + 2 * utils.my_round(self.offset) < self.width: - return 1 - else: - return 0 + tan = Decimal(math.tan(self.angle * math.pi / 180)) + offset = Decimal(self.depth) * tan + self.midline = self.width_f - offset + self.midline = self.midline.to_integral_value( rounding=ROUND_HALF_DOWN) + self.depth_0 = (self.width_f - self.midline) / tan + + self.overhang = (self.width_f - self.midline) / 2 + class My_Rectangle(object): ''' @@ -297,29 +315,29 @@ def _do_cuts(self, bit, cuts, y_nocut, y_cut): x = [] y = [] if cuts[0].xmin > 0: - x = [self.xL()] - y = [y_nocut] + x = [Decimal(self.xL())] + y = [Decimal(y_nocut)] # loop through the cuts and add them to the perimeter for c in cuts: if c.xmin > 0: # on the surface, start of cut - x.append(c.xmin + x[0] + bit.offset) - y.append(y_nocut) + x.append(c.xmin + x[0] + bit.overhang) + y.append(Decimal(y_nocut)) # at the cut depth, start of cut x.append(c.xmin + self.xL()) - y.append(y_cut) + y.append(Decimal(y_cut)) # at the cut depth, end of cut x.append(c.xmax + self.xL()) - y.append(y_cut) + y.append(Decimal(y_cut)) if c.xmax < self.width: # at the surface, end of cut - x.append(c.xmax + self.xL() - bit.offset) - y.append(y_nocut) + x.append(c.xmax + self.xL() - bit.overhang) + y.append(Decimal(y_nocut)) # add the last point on the top and bottom, at the right edge, # accounting for whether the last cut includes this edge or not. if cuts[-1].xmax < self.width: x.append(self.xL() + self.width) - y.append(y_nocut) + y.append(Decimal(y_nocut)) return (x, y) def do_all_cuts(self, bit): ''' @@ -473,6 +491,7 @@ def triangulate(self, bit): class Cut(object): ''' Cut description. + The cut values in Decimals to simplify rounding Attributes: @@ -483,12 +502,17 @@ class Cut(object): on the cut ''' def __init__(self, xmin, xmax): - self.xmin = xmin - self.xmax = xmax + self.xmin = Decimal(xmin) + self.xmax = Decimal(xmax) self.passes = [] + #Presission value is about 1/64 inch (the exact 1/64 = 0.0156 so we fine for bouth mesument systems) + self.precision = Decimal('0.01') + def validate(self, bit, board): ''' Checks whether the attributes of the cut are valid. + Because we works with floating point it is yet a rounding error + Comparisions made with respect to precision ''' if self.xmin >= self.xmax: raise Router_Exception('cut xmin = %d, xmax = %d: '\ @@ -500,50 +524,46 @@ def validate(self, bit, board): raise Router_Exception('cut xmin = %d, xmax = %d:' ' Must have xmax < board width (%d)!'\ % (self.xmin, self.xmax, board.width)) - if self.xmax - self.xmin < bit.width and self.xmin > 0 and self.xmax < board.width: - raise Router_Exception('cut xmin = %d, xmax = %d: '\ - 'Bit width (%d) too large for this cut!'\ - % (self.xmin, self.xmax, bit.width)) + if ( bit.width_f - (self.xmax - self.xmin) ) > self.precision and self.xmin > 0 and self.xmax < board.width: + raise Router_Exception('cut xmin = %f, xmax = %f ): '\ + 'Bit width (%f) delta too large for this cut!'\ + % (self.xmin, self.xmax, bit.width_f)) def make_router_passes(self, bit, board): '''Computes passes for the given bit.''' - # The logic below assumes bit.width is even - if bit.width % 2 != 0: - Router_Exception('Router-bit width must be even!') + # The updated logic below assumes bit.width is even for stright bits only + self.validate(bit, board) # set current extents of the uncut region xL = self.xmin xR = self.xmax + bitwidth = Decimal(repr(bit.width)) + halfwidth = bitwidth / 2 # alternate between the left and right sides of the overall cut to make the passes remainder = xR - xL self.passes = [] while remainder > 0: - # start with a pass on the right side of cut - p = xR - bit.halfwidth - if p - bit.halfwidth >= self.xmin or self.xmin == 0: - self.passes.append(p) - xR -= bit.width - # if anything to cut remains, do a pass on the far left side + p0 = utils.math_round(xR - halfwidth) # right size cut + p1 = utils.math_round(xL + halfwidth) # left size cut + + if self.xmax <= board.width and ( self.xmin - (p0 - halfwidth) < self.precision or self.xmin == 0 ): + self.passes.append( int(p0) ) + + if p0 != p1 and ( (p1 +halfwidth) - self.xmax < self.precision or self.xmax == board.width ): + self.passes.append( int(p1) ) + + xR -= bitwidth + xL += bitwidth + remainder = xR - xL - if remainder > 0: - p = xL + bit.halfwidth - if p + bit.halfwidth <= self.xmax or self.xmax == board.width: - self.passes.append(p) - xL += bit.width - remainder = xR - xL - # at this stage, we've done the same number of left and right passes, so if - # there's only one more pass needed, center it. - if remainder > 0 and remainder <= bit.width: - p = (xL + xR) // 2 - self.passes.append(p) - remainder = 0 + # Sort the passes self.passes = sorted(self.passes) # Error checking: for p in self.passes: - if (self.xmin > 0 and p - bit.halfwidth < self.xmin) or \ - (self.xmax < board.width and p + bit.halfwidth > self.xmax): - raise Router_Exception('cut xmin = %d, xmax = %d, pass = %d: '\ - 'Bit width (%d) too large for this cut!'\ + if (self.xmin > 0 and (self.xmin - (p - halfwidth)) > self.precision) or \ + (self.xmax < board.width and ((p + halfwidth) - self.xmax ) > self.precision ): + raise Router_Exception('cut xmin = %f, xmax = %f, pass = %f: '\ + 'Bit width (%f) too large for this cut!'\ % (self.xmin, self.xmax, p, bit.width)) def adjoining_cuts(cuts, bit, board): @@ -557,27 +577,33 @@ def adjoining_cuts(cuts, bit, board): Returns an array of Cut objects ''' nc = len(cuts) + offset = bit.width_f-bit.midline adjCuts = [] # if the left-most input cut does not include the left edge, add an # adjoining cut that includes the left edge if cuts[0].xmin > 0: left = 0 - right = utils.my_round(cuts[0].xmin + bit.offset) - board.dheight + right = cuts[0].xmin + offset - board.dheight if right - left >= board.dheight: - adjCuts.append(Cut(left, right)) + adjCuts.append( Cut( left, round(right, 4) ) ) + # loop through the input cuts and form an adjoining cut, formed # by looking where the previous cut ended and the current cut starts for i in lrange(1, nc): - left = utils.my_round(cuts[i-1].xmax - bit.offset + board.dheight) - right = max(left + bit.width, utils.my_round(cuts[i].xmin + bit.offset) - board.dheight) - adjCuts.append(Cut(left, right)) + left = cuts[i-1].xmax - offset + board.dheight + right = cuts[i].xmin + offset - board.dheight + adjCuts.append( Cut( left, right ) ) + # if the right-most input cut does not include the right edge, add an # adjoining cut that includes this edge if cuts[-1].xmax < board.width: - left = utils.my_round(cuts[-1].xmax - bit.offset) + board.dheight - right = board.width + left = cuts[-1].xmax - offset + board.dheight + right = Decimal(board.width) if right - left >= board.dheight: - adjCuts.append(Cut(left, right)) + adjCuts.append(Cut( left, right)) + # for runtime debug only + #print('adjoining_cuts cuts:') + #dump_cuts(adjCuts) return adjCuts def caul_cuts(cuts, bit, board, trim): @@ -702,66 +728,21 @@ def __init__(self, template, boards, bit, spacing, margins, config): def compute_fit(self): ''' Sets the maximum gap and overlap over all joints. + The gap is already computed in the bit object ''' - # Determine the board indices for each joint: - # [board index top_cut, board index bottom_cut] - if self.boards[2].active: - joints = [[1, 2]] - if self.boards[3].active: - joints.append([2, 3]) - joints.append([3, 0]) - else: - joints.append([2, 0]) - else: - joints = [[1, 0]] - - # Load up the coordinates for the boards - coords = [] - for i in range(4): - if self.boards[i].active: - coords.append(self.boards[i].do_all_cuts(self.bit)) - - # Determine the max gap and overlap self.max_gap = 0 self.max_overlap = 0 - for j in joints: - # Here, bottom and top are with respect to the joint, not the - # boards, so they're flipped. Roughly, should have ybot < ytop. - xbot = coords[j[0]][0] - ybot = coords[j[0]][1] - xtop = coords[j[1]][2] - ytop = coords[j[1]][3] - n = len(xtop) - yshift = self.boards[j[1]].yB() - self.boards[j[0]].yT() +\ - self.bit.depth - for i in range(n): - ybot[i] += yshift - for i in range(n - 1): - d = gap_overlap((xbot[i], xbot[i+1]), - (ybot[i], ybot[i+1]), - (xtop[i], xtop[i+1]), - (ytop[i], ytop[i+1])) - if d > 0: - self.max_gap = max(self.max_gap, d) - else: - self.max_overlap = max(self.max_overlap, -d) - -def gap_overlap(xbot, ybot, xtop, ytop): - ''' - Returns the distance between the midpoint of (xbot, ybot) to the - line (xtop, ytop). If positive, then a gap; otherwise, an overlap. - ''' - # find unit normal from bottom line - dxbot = xbot[1] - xbot[0] - dybot = ybot[1] - ybot[0] - norm = 1.0 / math.sqrt(dxbot * dxbot + dybot * dybot) - nx = -dybot * norm - ny = dxbot * norm - # vector connecting midpoints - dx = 0.5 * (xtop[1] + xtop[0] - xbot[1] - xbot[0]) - dy = 0.5 * (ytop[1] + ytop[0] - ybot[1] - ybot[0]) - # the dot product is our result - return dx * nx + dy * ny + if self.bit.gap > 0: + self.max_gap = self.bit.gap + else: + self.max_overlap = -self.bit.gap + +#No need such calculation anymore +#def gap_overlap(xbot, ybot, xtop, ytop): +# ''' +# Returns the distance between the midpoint of (xbot, ybot) to the +# line (xtop, ytop). If positive, then a gap; otherwise, an overlap. +# ''' def create_title(boards, bit, spacing): ''' diff --git a/spacing.py b/spacing.py index 08c7e8e..6e112a8 100644 --- a/spacing.py +++ b/spacing.py @@ -25,6 +25,7 @@ from __future__ import division from future.utils import lrange +from decimal import * import math import copy from operator import attrgetter @@ -32,9 +33,12 @@ import utils def dump_cuts(cuts): - '''Dumps the cuts to the screen...this is for debugging.''' + '''Dumps the cuts to the screen...this is for debugging + in column form. + ''' + print('Min\tMax') for c in cuts: - print(c.xmin, c.xmax) + print( '{1:3f}\t{1:3f}'.format(c.xmin, c.xmax) ) class Spacing_Exception(Exception): ''' @@ -84,6 +88,7 @@ class Base_Spacing(object): labels = [] def __init__(self, bit, boards, config): + getcontext().prec = 6 # overkilled prec (actually 4 is enough) self.description = 'NONE' self.bit = bit self.boards = boards @@ -125,8 +130,8 @@ def __init__(self, bit, boards, config): dh2 = 2 * self.dhtot t = [Spacing_Param(0, self.boards[0].width // 4 + dh2, 0),\ - Spacing_Param(self.bit.width + dh2, self.boards[0].width // 2 + dh2,\ - self.bit.width + dh2),\ + Spacing_Param(self.bit.midline + dh2, self.boards[0].width // 2 + dh2, \ + self.bit.midline + dh2),\ Spacing_Param(None, None, True)] self.params = {} for i in lrange(len(t)): @@ -136,50 +141,80 @@ def set_cuts(self): ''' Sets the cuts to make the joint ''' - spacing = self.params['Spacing'].v - 2 * self.dhtot - width = self.params['Width'].v + + # on local variables init + # we have to care about imperial values and convert them to increments before use + spacing = self.params['Spacing'].v + width = Decimal( math.floor(self.params['Width'].v) ) + + if not self.bit.units.metric and width < 1.: + spacing = self.bit.units.inches_to_increments(self.params['Spacing'].v) + width = Decimal( math.floor(self.bit.units.inches_to_increments(self.params['Width'].v ) )) + + shift = Decimal(self.bit.midline % 2) / 2 # offset to keep cut center mm count centered = self.params['Centered'].v + neck_width = width + spacing + overhang = self.bit.overhang board_width = self.boards[0].width units = self.bit.units label = units.increments_to_string(spacing, True) + + min_interior = utils.my_round(self.dhtot + self.bit.overhang) + # min_finger_width means most thin wood at the corner + min_finger_width = max(1, units.abstract_to_increments(self.config.min_finger_width)) + + if centered or \ + self.bit.angle > 0: # always symm. for dovetail + # put a cut at the center of the board with half of inctrmrnt prec. + xMid = Decimal(board_width // 2) - shift + (width % 2) / 2 + left = Decimal(max(0, xMid - width / 2)) + else: + xMid = board_width # - width / 2 + # keep corner finger and groove equality on the board edges + left = ( board_width % (width + neck_width) ) // 2 + if (left - overhang ) < min_finger_width: + left = 0 + + # Note the Width slider measures "midline" but indicates the actual cut space + # show actual maximum cut with for dovetails self.labels = self.keys[:] self.labels[0] += ': ' + label - self.labels[1] += ': ' + units.increments_to_string(width, True) + self.labels[1] += ': ' + units.increments_to_string(width + overhang * 2, True) self.description = 'Equally spaced (' + self.labels[0] + \ ', ' + self.labels[1] + ')' self.cuts = [] # return value - neck_width = width + spacing - 2 * utils.my_round(self.bit.offset) - if neck_width < 1: - raise Spacing_Exception('Specified bit paramters give a zero' - ' or negative cut width (%d increments) at' - ' the surface! Please change the' - ' bit parameters width, depth, or angle.' % neck_width) - # put a cut at the center of the board - xMid = board_width // 2 - if centered or \ - self.bit.angle > 0: # always symm. for dovetail - left = max(0, xMid - width // 2) - else: - left = max(0, (xMid // width) * width) - right = min(board_width, left + width) - self.cuts.append(router.Cut(left, right)) + + right = Decimal( min(board_width, left + width) ) + self.cuts.append( router.Cut( left - overhang, right + overhang ) ) + # do left side of board - i = left - neck_width - min_interior = utils.my_round(self.dhtot + self.bit.offset) - min_finger_width = max(1, units.abstract_to_increments(self.config.min_finger_width)) - while i > 0: - li = max(i - width, 0) - if i - li > min_finger_width and i > min_interior: - self.cuts.append(router.Cut(li, i)) - i = li - neck_width + i = left + + while left > 0: + i -= neck_width + left = i - width + + # prevent thin first cut + if left < min_finger_width: + left = 0 + if (i - overhang ) > min_finger_width and (i - left - overhang * 2) > min_interior: + self.cuts.append(router.Cut(max(0,left - overhang), i + overhang) ) + i = left + # do right side of board - i = right + neck_width - while i < board_width: - ri = min(i + width, board_width) - if ri - i > min_finger_width and board_width - i > min_interior: - self.cuts.append(router.Cut(i, ri)) - i = ri + neck_width + i = right + while right < board_width: + i += neck_width + right = i + width + # prevent thin last cut + # devetail may cut off corner finger + if (board_width - right ) < min_finger_width: + right = board_width + if (board_width - i + overhang) > min_finger_width and (right - i - overhang * 2) > min_interior: + self.cuts.append(router.Cut(i - overhang, min(board_width, right + overhang) ) ) + i = right + # If we have only one cut the entire width of the board, then # the board width is too small for the bit if self.cuts[0].xmin == 0 and self.cuts[0].xmax == board_width: @@ -198,7 +233,7 @@ class Variable_Spaced(Base_Spacing): ''' Computes variable-spaced cuts, where the center cut (always centered on board) is the widest, with each cut decreasing linearly as you move to the edge. - + arithmetical progression wirks just fine for such task Parameters that control the spacing are: Fingers: Roughly the number of full fingers on either the A or B board. @@ -207,15 +242,13 @@ class Variable_Spaced(Base_Spacing): def __init__(self, bit, boards, config): Base_Spacing.__init__(self, bit, boards, config) - # eff_width is the effective width, an average of the bit width - # and the neck width - self.eff_width = utils.my_round(self.bit.width - self.bit.offset) + 2 * self.dhtot - self.eff_width += self.bit.dovetail_correction() - self.wb = self.boards[0].width // self.eff_width - self.alpha = (self.wb + 1) % 2 + + # min and max number of fingers - self.mMin = max(3 - self.alpha, utils.my_round(math.ceil(math.sqrt(self.wb)))) - self.mMax = utils.my_round((self.wb - 1.0 + self.alpha) // 2) + self.mMin = 3 #we actually can set 2 fingers but tests does not pass this value + overhang = self.bit.overhang + self.mMax = int((self.boards[0].width // (self.bit.midline + self.dhtot ) ) // 2 + 1) + if self.mMax < self.mMin: raise Spacing_Exception('Unable to compute a variable-spaced'\ ' joint for the board and bit parameters'\ @@ -229,59 +262,97 @@ def __init__(self, bit, boards, config): def set_cuts(self): ''' Sets the cuts to make the joint - ''' - board_width = self.boards[0].width - m = self.params['Fingers'].v - self.labels = [self.keys[0] + ':'] - self.description = 'Variable Spaced (' + self.keys[0] + ': {})'.format(m) - # c is the ideal center-cut width - c = self.eff_width * ((m - 1.0) * self.wb - \ - m * (m + 1.0) + self.alpha * m) /\ - (m * m - 2.0 * m - 1.0 + self.alpha) - # d is the ideal decrease in finger width for each finger away from center finger - d = (c - self.eff_width) / (m - 1.0) + S - progresion summary (half length of the board) + n - number of fingers (actually number of parts per half of the board) + d - the difference between terms of the arithmetic progression + a1 - first cut width (it must be symmetric at center of the board) + an - the last cut or finger + because a1 is at the board center we got: + S = (n * 2*a1+(n-1) * d / 2 - a1/2 + from this equation we solve a1 + a1 = ( (2 * S) - (n - 1) * n * d ) / (2 * n - 1) + the next task is to find the best possible d (I love big numbers) + ''' + min_finger_width = Decimal( self.bit.units.abstract_to_increments(self.config.min_finger_width) + self.dhtot) + min_interior = self.bit.midline + self.dhtot * 2 + S = math.floor(self.boards[0].width / 2) # half board width + shift = Decimal( (self.bit.midline ) % 2) / 2 # offset to keep cut senter + + n = int(self.params['Fingers'].v) # number of cuts + d = -16 # d is the ideal decrease in finger width for each finger away from center finger + overhang = self.bit.overhang # offset from midline to the end of cut + a1 = 0 # center cut + an = 0 # last cut + + # Iterate to get perfect d value + # it is also possible to reverse d sometime to get cutc wider to the board corner + # the corner cut can't be too thin + # the minimum of pre-last cut should be not less than bit midline and extra bords width + while (an < min_interior or (an + d) <= min_finger_width) and d <= 0: + d += 1 + a1 = utils.math_round( ( (2 * S) - (n - 1) * n * d ) / Decimal(2 * n - 1) ) + an = a1 + Decimal(n - 2) * d + + if d > 0 : + d = 0 + a1 = min_interior + an = min_interior + delta = 0 + else: + SP = (a1 + d + an) * (n - 1) + a1 + delta = self.boards[0].width - SP + + # compute fingers on one side of the center and the center and store them # in increments. Keep a running total of sizes. - increments = [0] * (m + 1) - ivals = 0 - for i in lrange(1, m + 1): - increments[i] = max(self.bit.width, int(c - d * i)) - ivals += 2 * increments[i] - # Set the center increment. This takes up the slop in the rounding and increment - # resolution. - increments[0] = board_width - ivals - if increments[0] < increments[1]: - # The center increment is narrower than the adjacent increment, - # so reset it to the adjacent increment and get rid of a finger. - increments[0] = increments[1] - m -= 1 + increments = [Decimal(0)] * int(n) + for i in lrange(0, n): + increments[i] = a1 + d * i + if increments[i] < min_interior: + increments[i] = min_interior + + # wide last cut + if delta >= 2: + increments[-1] += delta // 2 + delta -= (delta // 2) * 2 + + # wide center cut in case the delta is a 1 increment + if delta == 1: + increments[0] += delta + delta = 0 + + if increments[-1] > increments[-2] : + increments[-1] = increments[-2] + if self.config.debug: print('v-s increments', increments) - # Adjustments for dovetails - deltaP = self.bit.width + 2 * self.dhtot - self.eff_width - deltaM = utils.my_round(self.eff_width - self.bit.neck - 2 * self.dhtot) - \ - self.bit.dovetail_correction() + # put a cut at the center of the board - xMid = board_width // 2 - width = increments[0] + deltaP - left = max(0, xMid - width // 2) - right = min(board_width, left + width) - self.cuts = [router.Cut(left, right)] - # do the remaining cuts + xMid = S + shift - Decimal(increments[0] % 2) / 2 + neck = Decimal(increments[0]) / 2 + left = xMid - neck + right = xMid + neck + self.labels = [self.keys[0] + ':'] + self.description = 'Variable Spaced (' + self.keys[0] + ': {})\nML:{} SYM:{} SP[0]:{} PD: {}'.format(n, self.bit.midline, xMid, increments[0], self.bit.depth_0) + + self.cuts = [router.Cut(left - overhang, right + overhang)] + do_cut = False - for i in lrange(1, m + 1): - if do_cut: - width = increments[i] + deltaP - farLeft = max(0, left - width) - self.cuts.append(router.Cut(farLeft, left)) - farRight = min(board_width, right + width) - self.cuts.append(router.Cut(right, farRight)) - else: - width = increments[i] - deltaM - farLeft = max(0, left - width) - farRight = min(board_width, right + width) - left = farLeft - right = farRight + for i in lrange(1, n): + if do_cut : + # cut width + l_left = left - increments[i] - overhang + r_right = right + increments[i] + overhang + #prevent thin cuts + if l_left < min_finger_width: + l_left = 0 + if (self.boards[0].width - r_right) < min_finger_width: + r_right = self.boards[0].width + self.cuts.append(router.Cut(max(0, l_left) , left + overhang)) + self.cuts.append(router.Cut(right - overhang, r_right)) + + left -= increments[i] + right +=increments[i] do_cut = (not do_cut) # sort the cuts in increasing x self.cuts = sorted(self.cuts, key=attrgetter('xmin')) @@ -323,11 +394,12 @@ def get_limits(self, f): ''' xmin = 0 xmax = self.boards[0].width - neck_width = utils.my_round(self.bit.neck) + midline = utils.my_round(self.bit.midline) + overhang = self.bit.overhang if f > 0: - xmin = self.cuts[f - 1].xmax + neck_width + xmin = self.cuts[f - 1].xmax + midline - overhang * 2 if f < len(self.cuts) - 1: - xmax = self.cuts[f + 1].xmin - neck_width + xmax = self.cuts[f + 1].xmin - midline + overhang * 2 return (xmin, xmax) def check_limits(self, f): @@ -347,23 +419,27 @@ def undo(self): def cut_move_left(self): ''' Moves the active cuts 1 increment to the left + with min finger with respect ''' cuts_save = copy.deepcopy(self.cuts) op = [] noop = [] + min_finger_width = self.bit.units.abstract_to_increments(self.config.min_finger_width) delete_cut = False for f in self.active_cuts: c = self.cuts[f] - c.xmin = max(0, c.xmin - 1) - if c.xmax == 1: + c.xmin -= 1 + if c.xmin <= min_finger_width: + c.xmin = 0 + if (c.xmax - self.bit.overhang) <= min_finger_width: # note its possible for only one cut to be deleted delete_cut = True elif c.xmax == self.boards[0].width: # if on the right end, create a new finger if it gets too wide - wNew = self.bit.width + 2 * self.dhtot + wNew = self.bit.midline + (self.dhtot + self.bit.overhang) * 2 w = c.xmax - c.xmin - if w > wNew: - c.xmax -= 1 + if (w - wNew) >= min_finger_width: + c.xmax = c.xmin + (self.dhtot * 2 + self.bit.width_f) else: c.xmax -= 1 msg = '' @@ -390,23 +466,28 @@ def cut_move_left(self): def cut_move_right(self): ''' Moves the active cuts 1 increment to the right + with min finger with respect ''' cuts_save = copy.deepcopy(self.cuts) op = [] noop = [] delete_cut = False + min_finger_width = self.bit.units.abstract_to_increments(self.config.min_finger_width) + for f in self.active_cuts: c = self.cuts[f] - c.xmax = min(self.boards[0].width, c.xmax + 1) - if c.xmin == self.boards[0].width - 1: + c.xmax += 1 + if self.boards[0].width - c.xmax < min_finger_width: + c.xmax = self.boards[0].width + if (c.xmin + self.bit.overhang + min_finger_width) >= self.boards[0].width: # note its possible for only one cut to be deleted delete_cut = True elif c.xmin == 0: # if on the left end, create a new finger if it gets too wide - wNew = self.bit.width + 2 * self.dhtot + wNew = self.bit.midline + (self.dhtot + self.bit.overhang) * 2 w = c.xmax - c.xmin - if w > wNew: - c.xmin = 1 + if (w - wNew) >= min_finger_width: + c.xmin = c.xmax - (self.dhtot * 2 + self.bit.width_f) else: c.xmin += 1 msg = '' @@ -434,6 +515,7 @@ def cut_widen_left(self): ''' Increases the active cuts width on the left side by 1 increment ''' + min_finger_width = self.bit.units.abstract_to_increments(self.config.min_finger_width) cuts_save = copy.deepcopy(self.cuts) op = [] noop = [] @@ -442,6 +524,8 @@ def cut_widen_left(self): (xmin, xmax) = self.get_limits(f) if c.xmin > xmin: c.xmin -= 1 + if c.xmin < min_finger_width: + c.xmin = 0 self.cuts[f] = c op.append(f) else: @@ -462,6 +546,7 @@ def cut_widen_right(self): ''' Increases the active cuts width on the right side by 1 increment ''' + min_finger_width = self.bit.units.abstract_to_increments(self.config.min_finger_width) cuts_save = copy.deepcopy(self.cuts) op = [] noop = [] @@ -470,6 +555,8 @@ def cut_widen_right(self): (xmin, xmax) = self.get_limits(f) if c.xmax < xmax: c.xmax += 1 + if self.boards[0].width - c.xmax < min_finger_width: + c.xmax = self.boards[0].width self.cuts[f] = c op.append(f) else: @@ -493,15 +580,21 @@ def cut_trim_left(self): cuts_save = copy.deepcopy(self.cuts) op = [] noop = [] + min_finger_width = self.bit.units.abstract_to_increments(self.config.min_finger_width) + for f in self.active_cuts: c = self.cuts[f] - wmin = self.bit.width + 2 * self.dhtot - if c.xmax == self.boards[0].width: - wmin = 1 - if c.xmax - c.xmin <= wmin: - noop.append(f) + wmin = self.bit.width_f + 2 * self.dhtot + if c.xmin == 0: + c.xmin = max(0, c.xmax - self.bit.width_f - 2 * self.dhtot) + if c.xmin < min_finger_width: + c.xmin = 0 else: c.xmin += 1 + + if c.xmax < self.boards[0].width and c.xmin > 0 and (c.xmax - c.xmin) < wmin: + noop.append(f) + else: self.cuts[f] = c op.append(f) if len(noop) > 0: @@ -523,15 +616,20 @@ def cut_trim_right(self): cuts_save = copy.deepcopy(self.cuts) op = [] noop = [] + min_finger_width = self.bit.units.abstract_to_increments(self.config.min_finger_width) + for f in self.active_cuts: c = self.cuts[f] - wmin = self.bit.width + 2 * self.dhtot - if c.xmin == 0: - wmin = 1 - if c.xmax - c.xmin <= wmin: - noop.append(f) + wmin = self.bit.width_f + 2 * self.dhtot + if c.xmax == self.boards[0].width: + c.xmax = min(self.boards[0].width, c.xmin + self.bit.width_f + 2 * self.dhtot) + if self.boards[0].width - c.xmax < min_finger_width: + c.xmax = self.boards[0].width else: c.xmax -= 1 + if c.xmax < self.boards[0].width and c.xmin > 0 and (c.xmax - c.xmin) < wmin: + noop.append(f) + else : self.cuts[f] = c op.append(f) if len(noop) > 0: @@ -632,40 +730,49 @@ def cut_add(self): Adds a cut to the first location possible, searching from the left. The active cut is set the the new cut. ''' - neck_width = utils.my_round(self.bit.neck) + overhang = self.bit.overhang + midline = self.bit.midline index = None cuts_save = copy.deepcopy(self.cuts) - if self.cuts[0].xmin > self.bit.neck: + min_finger_width = math.floor(self.bit.units.abstract_to_increments(self.config.min_finger_width)) + 1 + wadd = min_finger_width + self.dhtot + if self.cuts[0].xmin > self.bit.midline - overhang + wadd: if self.config.debug: print('add at left') index = 0 xmin = 0 - xmax = self.cuts[0].xmin - neck_width - wadd = 2 * self.bit.width + neck_width - wdelta = self.bit.width - neck_width + xmax = xmin + wadd + overhang + + wadd = 2 * (self.bit.midline + self.dhtot) + wdelta = overhang * 2 + for i in lrange(1, len(self.cuts)): - if self.cuts[i].xmin - self.cuts[i - 1].xmax + wdelta >= wadd: + if self.cuts[i].xmin - self.cuts[i - 1].xmax + wdelta >= wadd + self.bit.midline: if self.config.debug: print('add in cut') index = i - xmin = self.cuts[i - 1].xmax + neck_width - xmax = xmin + self.bit.width + xmin = self.cuts[i - 1].xmax - overhang + midline + xmax = xmin + self.bit.midline + overhang + 2 * self.dhtot + xmin -= overhang break - elif self.cuts[i].xmax - self.cuts[i].xmin >= wadd: + + elif (self.cuts[i].xmax - self.cuts[i].xmin - wdelta) >= wadd + self.bit.midline: if self.config.debug: print('add in cut') index = i + 1 - xmin = self.cuts[i].xmax - self.bit.width - xmax = self.cuts[i].xmax - self.cuts[i].xmax = self.cuts[i].xmin + self.bit.width + xmax = self.cuts[i].xmin + self.bit.midline + (overhang + self.dhtot) * 2 + xmin = xmax + self.bit.midline - 2 * overhang + t = self.cuts[i].xmax + self.cuts[i].xmax = xmax + xmax = t break if index is None and \ - self.cuts[-1].xmax < self.boards[0].width - self.bit.neck: + self.cuts[-1].xmax < self.boards[0].width - overhang: if self.config.debug: print('add at right') index = len(self.cuts) xmax = self.boards[0].width - xmin = self.cuts[-1].xmax + neck_width + xmin = self.cuts[-1].xmax - overhang if index is None: return ('Unable to add cut', True) self.undo_cuts.append(cuts_save) diff --git a/utils.py b/utils.py index b0a3a3c..90b702f 100644 --- a/utils.py +++ b/utils.py @@ -23,7 +23,7 @@ ''' from __future__ import division from __future__ import print_function - +from decimal import * import math, fractions, os, glob, platform VERSION = '0.9.3' @@ -34,6 +34,12 @@ def my_round(f): ''' return int(round(f)) +def math_round(no): + ''' + Return mathimatical round to integer + ''' + return int(no//1 + ((no%1)/Decimal('0.5'))//1) + def isMac(): return (platform.system() == 'Darwin') @@ -54,7 +60,7 @@ def __init__(self, english_separator, whole=0, numerator=0, denominator=None): else: self.sign = 1 self.whole = abs(whole) - self.numerator = abs(numerator) + self.numerator = int(abs(numerator)) self.denominator = denominator self.english_separator = english_separator def reduce(self): @@ -64,10 +70,13 @@ def reduce(self): ''' if self.denominator is None or self.numerator == 0: return + # directly convert to integer because of direct access posibility + self.numerator = int(self.numerator) + self.denominator = int(self.denominator) dwhole = self.numerator // self.denominator self.whole += dwhole - self.numerator -= dwhole * self.denominator - gcd = fractions.gcd(self.numerator, self.denominator) + self.numerator -= dwhole * self.denominator + gcd = math.gcd(self.numerator, self.denominator) #Python3 requres math.gcd self.numerator /= gcd self.denominator /= gcd def to_string(self): @@ -175,9 +184,10 @@ def increments_to_string(self, increments, with_units=False): ''' A string representation of the value increments, converted to its respective units. + metric conversion requires fixed point rounding ''' if self.metric: - r = '%g' % (increments / float(self.num_increments)) + r = '%g' % ( Decimal(increments) / Decimal(self.num_increments) ) else: allow_denoms = [1, 2, 4, 8, 16, 32, 64] if isinstance(increments, float):