From cebffe34b1384f713723c00bc94e0c15d84a5eed Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 28 Aug 2020 00:41:03 +0300 Subject: [PATCH] - in Tools: Transform, SUb, RulesCheck, DistanceMin, Distance - moved the Tool UI in its own class --- CHANGELOG.md | 3 +- appTools/ToolDistance.py | 293 ++++---- appTools/ToolDistanceMin.py | 383 ++++++----- appTools/ToolRulesCheck.py | 1284 ++++++++++++++++++----------------- appTools/ToolSub.py | 399 ++++++----- appTools/ToolTransform.py | 1042 ++++++++++++++-------------- 6 files changed, 1779 insertions(+), 1625 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed70bce2..5996bf6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,10 @@ CHANGELOG for FlatCAM beta - in Tool Cutout: modified the UI in preparation for adding the Mouse Bites feature - Turkish translation strings were updated by the translator, Mehmet Kaya - Film Tool - moved the Tool UI in its own class -- in Tools: Film, Image, InvertGerber, Optimal, PcbWizard - moved the Tool UI in its own class +- in Tools: Image, InvertGerber, Optimal, PcbWizard - moved the Tool UI in its own class - Tool Isolation - made sure that the app can load from Tools Database only tools marked for Isolation tool - Tool Isolation - on Tool start it will attempt to load the Preferences set tools by diameter from Tools Database. If it can't find one there it will add a default tool. +- in Tools: Transform, SUb, RulesCheck, DistanceMin, Distance - moved the Tool UI in its own class 26.08.2020 diff --git a/appTools/ToolDistance.py b/appTools/ToolDistance.py index ac48f9ce..24a7cd8d 100644 --- a/appTools/ToolDistance.py +++ b/appTools/ToolDistance.py @@ -9,7 +9,7 @@ from PyQt5 import QtWidgets, QtCore from appTool import AppTool from appGUI.VisPyVisuals import * -from appGUI.GUIElements import FCEntry, FCButton, FCCheckBox +from appGUI.GUIElements import FCEntry, FCButton, FCCheckBox, FCLabel from shapely.geometry import Point, MultiLineString, Polygon @@ -32,8 +32,6 @@ log = logging.getLogger('base') class Distance(AppTool): - toolName = _("Distance Tool") - def __init__(self, app): AppTool.__init__(self, app) @@ -43,107 +41,11 @@ class Distance(AppTool): self.canvas = self.app.plotcanvas self.units = self.app.defaults['units'].lower() - # ## Title - title_label = QtWidgets.QLabel("%s
" % self.toolName) - self.layout.addWidget(title_label) - - # ## Form Layout - grid0 = QtWidgets.QGridLayout() - grid0.setColumnStretch(0, 0) - grid0.setColumnStretch(1, 1) - self.layout.addLayout(grid0) - - self.units_label = QtWidgets.QLabel('%s:' % _("Units")) - self.units_label.setToolTip(_("Those are the units in which the distance is measured.")) - self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units])) - self.units_value.setDisabled(True) - - grid0.addWidget(self.units_label, 0, 0) - grid0.addWidget(self.units_value, 0, 1) - - self.snap_center_cb = FCCheckBox(_("Snap to center")) - self.snap_center_cb.setToolTip( - _("Mouse cursor will snap to the center of the pad/drill\n" - "when it is hovering over the geometry of the pad/drill.") - ) - grid0.addWidget(self.snap_center_cb, 1, 0, 1, 2) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 2, 0, 1, 2) - - self.start_label = QtWidgets.QLabel("%s:" % _('Start Coords')) - self.start_label.setToolTip(_("This is measuring Start point coordinates.")) - - self.start_entry = FCEntry() - self.start_entry.setReadOnly(True) - self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.start_entry.setToolTip(_("This is measuring Start point coordinates.")) - - grid0.addWidget(self.start_label, 3, 0) - grid0.addWidget(self.start_entry, 3, 1) - - self.stop_label = QtWidgets.QLabel("%s:" % _('Stop Coords')) - self.stop_label.setToolTip(_("This is the measuring Stop point coordinates.")) - - self.stop_entry = FCEntry() - self.stop_entry.setReadOnly(True) - self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates.")) - - grid0.addWidget(self.stop_label, 4, 0) - grid0.addWidget(self.stop_entry, 4, 1) - - self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx")) - self.distance_x_label.setToolTip(_("This is the distance measured over the X axis.")) - - self.distance_x_entry = FCEntry() - self.distance_x_entry.setReadOnly(True) - self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis.")) - - grid0.addWidget(self.distance_x_label, 5, 0) - grid0.addWidget(self.distance_x_entry, 5, 1) - - self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy")) - self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis.")) - - self.distance_y_entry = FCEntry() - self.distance_y_entry.setReadOnly(True) - self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis.")) - - grid0.addWidget(self.distance_y_label, 6, 0) - grid0.addWidget(self.distance_y_entry, 6, 1) - - self.angle_label = QtWidgets.QLabel('%s:' % _("Angle")) - self.angle_label.setToolTip(_("This is orientation angle of the measuring line.")) - - self.angle_entry = FCEntry() - self.angle_entry.setReadOnly(True) - self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.angle_entry.setToolTip(_("This is orientation angle of the measuring line.")) - - grid0.addWidget(self.angle_label, 7, 0) - grid0.addWidget(self.angle_entry, 7, 1) - - self.total_distance_label = QtWidgets.QLabel("%s:" % _('DISTANCE')) - self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance.")) - - self.total_distance_entry = FCEntry() - self.total_distance_entry.setReadOnly(True) - self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance.")) - - grid0.addWidget(self.total_distance_label, 8, 0) - grid0.addWidget(self.total_distance_entry, 8, 1) - - self.measure_btn = FCButton(_("Measure")) - # self.measure_btn.setFixedWidth(70) - self.layout.addWidget(self.measure_btn) - - self.layout.addStretch() + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = DistUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName # store here the first click and second click of the measurement process self.points = [] @@ -178,8 +80,9 @@ class Distance(AppTool): else: from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='measurement') - - self.measure_btn.clicked.connect(self.activate_measure_tool) + + # Signals + self.ui.measure_btn.clicked.connect(self.activate_measure_tool) def run(self, toggle=False): self.app.defaults.report_usage("ToolDistance()") @@ -224,27 +127,27 @@ class Distance(AppTool): self.app.command_active = "Distance" # initial view of the layout - self.start_entry.set_value('(0, 0)') - self.stop_entry.set_value('(0, 0)') + self.ui.start_entry.set_value('(0, 0)') + self.ui.stop_entry.set_value('(0, 0)') - self.distance_x_entry.set_value('0.0') - self.distance_y_entry.set_value('0.0') - self.angle_entry.set_value('0.0') - self.total_distance_entry.set_value('0.0') + self.ui.distance_x_entry.set_value('0.0') + self.ui.distance_y_entry.set_value('0.0') + self.ui.angle_entry.set_value('0.0') + self.ui.total_distance_entry.set_value('0.0') - self.snap_center_cb.set_value(self.app.defaults['tools_dist_snap_center']) + self.ui.snap_center_cb.set_value(self.app.defaults['tools_dist_snap_center']) # snap center works only for Gerber and Execellon Editor's if self.original_call_source == 'exc_editor' or self.original_call_source == 'grb_editor': - self.snap_center_cb.show() + self.ui.snap_center_cb.show() snap_center = self.app.defaults['tools_dist_snap_center'] self.on_snap_toggled(snap_center) - self.snap_center_cb.toggled.connect(self.on_snap_toggled) + self.ui.snap_center_cb.toggled.connect(self.on_snap_toggled) else: - self.snap_center_cb.hide() + self.ui.snap_center_cb.hide() try: - self.snap_center_cb.toggled.disconnect(self.on_snap_toggled) + self.ui.snap_center_cb.toggled.disconnect(self.on_snap_toggled) except (TypeError, AttributeError): pass @@ -270,8 +173,8 @@ class Distance(AppTool): self.active = True # disable the measuring button - self.measure_btn.setDisabled(True) - self.measure_btn.setText('%s...' % _("Working")) + self.ui.measure_btn.setDisabled(True) + self.ui.measure_btn.setText('%s...' % _("Working")) self.clicked_meas = 0 self.original_call_source = copy(self.app.call_source) @@ -335,8 +238,8 @@ class Distance(AppTool): self.points = [] # disable the measuring button - self.measure_btn.setDisabled(False) - self.measure_btn.setText(_("Measure")) + self.ui.measure_btn.setDisabled(False) + self.ui.measure_btn.setText(_("Measure")) self.app.call_source = copy(self.original_call_source) if self.original_call_source == 'app': @@ -406,7 +309,7 @@ class Distance(AppTool): if event.button == 1: pos_canvas = self.canvas.translate_coords(event_pos) - if self.snap_center_cb.get_value() is False: + if self.ui.snap_center_cb.get_value() is False: # if GRID is active we need to get the snapped positions if self.app.grid_status(): pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1]) @@ -490,14 +393,14 @@ class Distance(AppTool): def calculate_distance(self, pos): if len(self.points) == 1: - self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1])) + self.ui.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1])) self.app.inform.emit(_("MEASURING: Click on the Destination point ...")) elif len(self.points) == 2: # self.app.app_cursor.enabled = False dx = self.points[1][0] - self.points[0][0] dy = self.points[1][1] - self.points[0][1] d = math.sqrt(dx ** 2 + dy ** 2) - self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1])) + self.ui.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1])) self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format( tx1=_("MEASURING"), @@ -508,18 +411,18 @@ class Distance(AppTool): d_z='%*f' % (self.decimals, abs(d))) ) - self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx))) - self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy))) + self.ui.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx))) + self.ui.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy))) try: angle = math.degrees(math.atan2(dy, dx)) if angle < 0: angle += 360 - self.angle_entry.set_value('%.*f' % (self.decimals, angle)) + self.ui.angle_entry.set_value('%.*f' % (self.decimals, angle)) except Exception: pass - self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d))) + self.ui.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d))) self.app.ui.rel_position_label.setText( "Dx: {}   Dy: {}    ".format( '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1]) @@ -587,7 +490,7 @@ class Distance(AppTool): angle = math.degrees(math.atan2(dy, dx)) if angle < 0: angle += 360 - self.angle_entry.set_value('%.*f' % (self.decimals, angle)) + self.ui.angle_entry.set_value('%.*f' % (self.decimals, angle)) except Exception as e: log.debug("Distance.on_mouse_move_meas() -> update utility geometry -> %s" % str(e)) pass @@ -635,4 +538,134 @@ class Distance(AppTool): # def set_meas_units(self, units): # self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]") -# end of file + +class DistUI: + + toolName = _("Distance Tool") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + + # ## Title + title_label = FCLabel("%s
" % self.toolName) + self.layout.addWidget(title_label) + + # ## Form Layout + grid0 = QtWidgets.QGridLayout() + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + self.layout.addLayout(grid0) + + self.units_label = FCLabel('%s:' % _("Units")) + self.units_label.setToolTip(_("Those are the units in which the distance is measured.")) + self.units_value = FCLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units])) + self.units_value.setDisabled(True) + + grid0.addWidget(self.units_label, 0, 0) + grid0.addWidget(self.units_value, 0, 1) + + self.snap_center_cb = FCCheckBox(_("Snap to center")) + self.snap_center_cb.setToolTip( + _("Mouse cursor will snap to the center of the pad/drill\n" + "when it is hovering over the geometry of the pad/drill.") + ) + grid0.addWidget(self.snap_center_cb, 1, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 2, 0, 1, 2) + + self.start_label = FCLabel("%s:" % _('Start Coords')) + self.start_label.setToolTip(_("This is measuring Start point coordinates.")) + + self.start_entry = FCEntry() + self.start_entry.setReadOnly(True) + self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.start_entry.setToolTip(_("This is measuring Start point coordinates.")) + + grid0.addWidget(self.start_label, 3, 0) + grid0.addWidget(self.start_entry, 3, 1) + + self.stop_label = FCLabel("%s:" % _('Stop Coords')) + self.stop_label.setToolTip(_("This is the measuring Stop point coordinates.")) + + self.stop_entry = FCEntry() + self.stop_entry.setReadOnly(True) + self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates.")) + + grid0.addWidget(self.stop_label, 4, 0) + grid0.addWidget(self.stop_entry, 4, 1) + + self.distance_x_label = FCLabel('%s:' % _("Dx")) + self.distance_x_label.setToolTip(_("This is the distance measured over the X axis.")) + + self.distance_x_entry = FCEntry() + self.distance_x_entry.setReadOnly(True) + self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis.")) + + grid0.addWidget(self.distance_x_label, 5, 0) + grid0.addWidget(self.distance_x_entry, 5, 1) + + self.distance_y_label = FCLabel('%s:' % _("Dy")) + self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis.")) + + self.distance_y_entry = FCEntry() + self.distance_y_entry.setReadOnly(True) + self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis.")) + + grid0.addWidget(self.distance_y_label, 6, 0) + grid0.addWidget(self.distance_y_entry, 6, 1) + + self.angle_label = FCLabel('%s:' % _("Angle")) + self.angle_label.setToolTip(_("This is orientation angle of the measuring line.")) + + self.angle_entry = FCEntry() + self.angle_entry.setReadOnly(True) + self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.angle_entry.setToolTip(_("This is orientation angle of the measuring line.")) + + grid0.addWidget(self.angle_label, 7, 0) + grid0.addWidget(self.angle_entry, 7, 1) + + self.total_distance_label = FCLabel("%s:" % _('DISTANCE')) + self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance.")) + + self.total_distance_entry = FCEntry() + self.total_distance_entry.setReadOnly(True) + self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance.")) + + grid0.addWidget(self.total_distance_label, 8, 0) + grid0.addWidget(self.total_distance_entry, 8, 1) + + self.measure_btn = FCButton(_("Measure")) + # self.measure_btn.setFixedWidth(70) + self.layout.addWidget(self.measure_btn) + + self.layout.addStretch() + + # #################################### FINSIHED GUI ########################### + # ############################################################################# + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appTools/ToolDistanceMin.py b/appTools/ToolDistanceMin.py index 3beaf244..b21725e5 100644 --- a/appTools/ToolDistanceMin.py +++ b/appTools/ToolDistanceMin.py @@ -28,8 +28,6 @@ log = logging.getLogger('base') class DistanceMin(AppTool): - toolName = _("Minimum Distance Tool") - def __init__(self, app): AppTool.__init__(self, app) @@ -38,7 +36,196 @@ class DistanceMin(AppTool): self.units = self.app.defaults['units'].lower() self.decimals = self.app.decimals - # ## Title + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = DistMinUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName + + self.h_point = (0, 0) + + self.ui.measure_btn.clicked.connect(self.activate_measure_tool) + self.ui.jump_hp_btn.clicked.connect(self.on_jump_to_half_point) + + def run(self, toggle=False): + self.app.defaults.report_usage("ToolDistanceMin()") + + if self.app.tool_tab_locked is True: + return + + self.app.ui.notebook.setTabText(2, _("Minimum Distance Tool")) + + # if the splitter is hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + if toggle: + pass + + self.set_tool_ui() + self.app.inform.emit('MEASURING: %s' % + _("Select two objects and no more, to measure the distance between them ...")) + + def install(self, icon=None, separator=None, **kwargs): + AppTool.install(self, icon, separator, shortcut='Shift+M', **kwargs) + + def set_tool_ui(self): + # Remove anything else in the appGUI + self.app.ui.tool_scroll_area.takeWidget() + + # Put oneself in the appGUI + self.app.ui.tool_scroll_area.setWidget(self) + + # Switch notebook to tool page + self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) + + self.units = self.app.defaults['units'].lower() + + # initial view of the layout + self.ui.start_entry.set_value('(0, 0)') + self.ui.stop_entry.set_value('(0, 0)') + + self.ui.distance_x_entry.set_value('0.0') + self.ui.distance_y_entry.set_value('0.0') + self.ui.angle_entry.set_value('0.0') + self.ui.total_distance_entry.set_value('0.0') + self.ui.half_point_entry.set_value('(0, 0)') + + self.ui.jump_hp_btn.setDisabled(True) + + log.debug("Minimum Distance Tool --> tool initialized") + + def activate_measure_tool(self): + # ENABLE the Measuring TOOL + self.ui.jump_hp_btn.setDisabled(False) + + self.units = self.app.defaults['units'].lower() + + if self.app.call_source == 'app': + selected_objs = self.app.collection.get_selected() + if len(selected_objs) != 2: + self.app.inform.emit('[WARNING_NOTCL] %s %s' % + (_("Select two objects and no more. Currently the selection has objects: "), + str(len(selected_objs)))) + return + else: + if isinstance(selected_objs[0].solid_geometry, list): + try: + selected_objs[0].solid_geometry = MultiPolygon(selected_objs[0].solid_geometry) + except Exception: + selected_objs[0].solid_geometry = cascaded_union(selected_objs[0].solid_geometry) + + try: + selected_objs[1].solid_geometry = MultiPolygon(selected_objs[1].solid_geometry) + except Exception: + selected_objs[1].solid_geometry = cascaded_union(selected_objs[1].solid_geometry) + + first_pos, last_pos = nearest_points(selected_objs[0].solid_geometry, selected_objs[1].solid_geometry) + + elif self.app.call_source == 'geo_editor': + selected_objs = self.app.geo_editor.selected + if len(selected_objs) != 2: + self.app.inform.emit('[WARNING_NOTCL] %s %s' % + (_("Select two objects and no more. Currently the selection has objects: "), + str(len(selected_objs)))) + return + else: + first_pos, last_pos = nearest_points(selected_objs[0].geo, selected_objs[1].geo) + elif self.app.call_source == 'exc_editor': + selected_objs = self.app.exc_editor.selected + if len(selected_objs) != 2: + self.app.inform.emit('[WARNING_NOTCL] %s %s' % + (_("Select two objects and no more. Currently the selection has objects: "), + str(len(selected_objs)))) + return + else: + # the objects are really MultiLinesStrings made out of 2 lines in cross shape + xmin, ymin, xmax, ymax = selected_objs[0].geo.bounds + first_geo_radius = (xmax - xmin) / 2 + first_geo_center = Point(xmin + first_geo_radius, ymin + first_geo_radius) + first_geo = first_geo_center.buffer(first_geo_radius) + + # the objects are really MultiLinesStrings made out of 2 lines in cross shape + xmin, ymin, xmax, ymax = selected_objs[1].geo.bounds + last_geo_radius = (xmax - xmin) / 2 + last_geo_center = Point(xmin + last_geo_radius, ymin + last_geo_radius) + last_geo = last_geo_center.buffer(last_geo_radius) + + first_pos, last_pos = nearest_points(first_geo, last_geo) + elif self.app.call_source == 'grb_editor': + selected_objs = self.app.grb_editor.selected + if len(selected_objs) != 2: + self.app.inform.emit('[WARNING_NOTCL] %s %s' % + (_("Select two objects and no more. Currently the selection has objects: "), + str(len(selected_objs)))) + return + else: + first_pos, last_pos = nearest_points(selected_objs[0].geo['solid'], selected_objs[1].geo['solid']) + else: + first_pos, last_pos = 0, 0 + + self.ui.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, first_pos.x, self.decimals, first_pos.y)) + self.ui.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, last_pos.x, self.decimals, last_pos.y)) + + dx = first_pos.x - last_pos.x + dy = first_pos.y - last_pos.y + + self.ui.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx))) + self.ui.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy))) + + try: + angle = math.degrees(math.atan(dy / dx)) + self.ui.angle_entry.set_value('%.*f' % (self.decimals, angle)) + except Exception: + pass + + d = math.sqrt(dx ** 2 + dy ** 2) + self.ui.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d))) + + self.h_point = (min(first_pos.x, last_pos.x) + (abs(dx) / 2), min(first_pos.y, last_pos.y) + (abs(dy) / 2)) + if d != 0: + self.ui.half_point_entry.set_value( + "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]) + ) + else: + self.ui.half_point_entry.set_value( + "(%.*f, %.*f)" % (self.decimals, 0.0, self.decimals, 0.0) + ) + + if d != 0: + self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format( + tx1=_("MEASURING"), + tx2=_("Result"), + tx3=_("Distance"), + d_x='%*f' % (self.decimals, abs(dx)), + d_y='%*f' % (self.decimals, abs(dy)), + d_z='%*f' % (self.decimals, abs(d))) + ) + else: + self.app.inform.emit('[WARNING_NOTCL] %s: %s' % + (_("Objects intersects or touch at"), + "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]))) + + def on_jump_to_half_point(self): + self.app.on_jump_to(custom_location=self.h_point) + self.app.inform.emit('[success] %s: %s' % + (_("Jumped to the half point between the two selected objects"), + "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]))) + + # def set_meas_units(self, units): + # self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]") + + +class DistMinUI: + + toolName = _("Minimum Distance Tool") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + +# ## Title title_label = QtWidgets.QLabel("%s
" % self.toolName) self.layout.addWidget(title_label) @@ -57,7 +244,7 @@ class DistanceMin(AppTool): self.stop_label = QtWidgets.QLabel("%s:" % _('Second object point')) self.stop_label.setToolTip(_("This is second object point coordinates.\n" - "This is the end point for measuring distance.")) + "This is the end point for measuring distance.")) self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx")) self.distance_x_label.setToolTip(_("This is the distance measured over the X axis.")) @@ -84,7 +271,7 @@ class DistanceMin(AppTool): self.stop_entry.setReadOnly(True) self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.stop_entry.setToolTip(_("This is second object point coordinates.\n" - "This is the end point for measuring distance.")) + "This is the end point for measuring distance.")) self.distance_x_entry = FCEntry() self.distance_x_entry.setReadOnly(True) @@ -128,178 +315,22 @@ class DistanceMin(AppTool): form_layout.addRow(self.half_point_label, self.half_point_entry) self.layout.addStretch() + # #################################### FINSIHED GUI ########################### + # ############################################################################# - self.h_point = (0, 0) - - self.measure_btn.clicked.connect(self.activate_measure_tool) - self.jump_hp_btn.clicked.connect(self.on_jump_to_half_point) - - def run(self, toggle=False): - self.app.defaults.report_usage("ToolDistanceMin()") - - if self.app.tool_tab_locked is True: - return - - self.app.ui.notebook.setTabText(2, _("Minimum Distance Tool")) - - # if the splitter is hidden, display it - if self.app.ui.splitter.sizes()[0] == 0: - self.app.ui.splitter.setSizes([1, 1]) - - if toggle: - pass - - self.set_tool_ui() - self.app.inform.emit('MEASURING: %s' % - _("Select two objects and no more, to measure the distance between them ...")) - - def install(self, icon=None, separator=None, **kwargs): - AppTool.install(self, icon, separator, shortcut='Shift+M', **kwargs) - - def set_tool_ui(self): - # Remove anything else in the appGUI - self.app.ui.tool_scroll_area.takeWidget() - - # Put oneself in the appGUI - self.app.ui.tool_scroll_area.setWidget(self) - - # Switch notebook to tool page - self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab) - - self.units = self.app.defaults['units'].lower() - - # initial view of the layout - self.start_entry.set_value('(0, 0)') - self.stop_entry.set_value('(0, 0)') - - self.distance_x_entry.set_value('0.0') - self.distance_y_entry.set_value('0.0') - self.angle_entry.set_value('0.0') - self.total_distance_entry.set_value('0.0') - self.half_point_entry.set_value('(0, 0)') - - self.jump_hp_btn.setDisabled(True) - - log.debug("Minimum Distance Tool --> tool initialized") - - def activate_measure_tool(self): - # ENABLE the Measuring TOOL - self.jump_hp_btn.setDisabled(False) - - self.units = self.app.defaults['units'].lower() - - if self.app.call_source == 'app': - selected_objs = self.app.collection.get_selected() - if len(selected_objs) != 2: - self.app.inform.emit('[WARNING_NOTCL] %s %s' % - (_("Select two objects and no more. Currently the selection has objects: "), - str(len(selected_objs)))) - return - else: - if isinstance(selected_objs[0].solid_geometry, list): - try: - selected_objs[0].solid_geometry = MultiPolygon(selected_objs[0].solid_geometry) - except Exception: - selected_objs[0].solid_geometry = cascaded_union(selected_objs[0].solid_geometry) - - try: - selected_objs[1].solid_geometry = MultiPolygon(selected_objs[1].solid_geometry) - except Exception: - selected_objs[1].solid_geometry = cascaded_union(selected_objs[1].solid_geometry) - - first_pos, last_pos = nearest_points(selected_objs[0].solid_geometry, selected_objs[1].solid_geometry) - - elif self.app.call_source == 'geo_editor': - selected_objs = self.app.geo_editor.selected - if len(selected_objs) != 2: - self.app.inform.emit('[WARNING_NOTCL] %s %s' % - (_("Select two objects and no more. Currently the selection has objects: "), - str(len(selected_objs)))) - return - else: - first_pos, last_pos = nearest_points(selected_objs[0].geo, selected_objs[1].geo) - elif self.app.call_source == 'exc_editor': - selected_objs = self.app.exc_editor.selected - if len(selected_objs) != 2: - self.app.inform.emit('[WARNING_NOTCL] %s %s' % - (_("Select two objects and no more. Currently the selection has objects: "), - str(len(selected_objs)))) - return - else: - # the objects are really MultiLinesStrings made out of 2 lines in cross shape - xmin, ymin, xmax, ymax = selected_objs[0].geo.bounds - first_geo_radius = (xmax - xmin) / 2 - first_geo_center = Point(xmin + first_geo_radius, ymin + first_geo_radius) - first_geo = first_geo_center.buffer(first_geo_radius) - - # the objects are really MultiLinesStrings made out of 2 lines in cross shape - xmin, ymin, xmax, ymax = selected_objs[1].geo.bounds - last_geo_radius = (xmax - xmin) / 2 - last_geo_center = Point(xmin + last_geo_radius, ymin + last_geo_radius) - last_geo = last_geo_center.buffer(last_geo_radius) - - first_pos, last_pos = nearest_points(first_geo, last_geo) - elif self.app.call_source == 'grb_editor': - selected_objs = self.app.grb_editor.selected - if len(selected_objs) != 2: - self.app.inform.emit('[WARNING_NOTCL] %s %s' % - (_("Select two objects and no more. Currently the selection has objects: "), - str(len(selected_objs)))) - return - else: - first_pos, last_pos = nearest_points(selected_objs[0].geo['solid'], selected_objs[1].geo['solid']) + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) else: - first_pos, last_pos = 0, 0 + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) - self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, first_pos.x, self.decimals, first_pos.y)) - self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, last_pos.x, self.decimals, last_pos.y)) - - dx = first_pos.x - last_pos.x - dy = first_pos.y - last_pos.y - - self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx))) - self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy))) - - try: - angle = math.degrees(math.atan(dy / dx)) - self.angle_entry.set_value('%.*f' % (self.decimals, angle)) - except Exception as e: - pass - - d = math.sqrt(dx ** 2 + dy ** 2) - self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d))) - - self.h_point = (min(first_pos.x, last_pos.x) + (abs(dx) / 2), min(first_pos.y, last_pos.y) + (abs(dy) / 2)) - if d != 0: - self.half_point_entry.set_value( - "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]) - ) + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) else: - self.half_point_entry.set_value( - "(%.*f, %.*f)" % (self.decimals, 0.0, self.decimals, 0.0) - ) - - if d != 0: - self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format( - tx1=_("MEASURING"), - tx2=_("Result"), - tx3=_("Distance"), - d_x='%*f' % (self.decimals, abs(dx)), - d_y='%*f' % (self.decimals, abs(dy)), - d_z='%*f' % (self.decimals, abs(d))) - ) - else: - self.app.inform.emit('[WARNING_NOTCL] %s: %s' % - (_("Objects intersects or touch at"), - "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]))) - - def on_jump_to_half_point(self): - self.app.on_jump_to(custom_location=self.h_point) - self.app.inform.emit('[success] %s: %s' % - (_("Jumped to the half point between the two selected objects"), - "(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1]))) - - def set_meas_units(self, units): - self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]") - -# end of file + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appTools/ToolRulesCheck.py b/appTools/ToolRulesCheck.py index c43eb033..ff105265 100644 --- a/appTools/ToolRulesCheck.py +++ b/appTools/ToolRulesCheck.py @@ -8,7 +8,7 @@ from PyQt5 import QtWidgets, QtGui from appTool import AppTool -from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCComboBox +from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, OptionalInputSection, FCComboBox, FCLabel, FCButton from copy import deepcopy from appPool import * @@ -30,8 +30,6 @@ log = logging.getLogger('base') class RulesCheck(AppTool): - toolName = _("Check Rules") - tool_finished = QtCore.pyqtSignal(list) def __init__(self, app): @@ -39,517 +37,37 @@ class RulesCheck(AppTool): AppTool.__init__(self, app) - # ## Title - title_label = QtWidgets.QLabel("%s" % self.toolName) - title_label.setStyleSheet(""" - QLabel - { - font-size: 16px; - font-weight: bold; - } - """) - self.layout.addWidget(title_label) - - # Form Layout - self.grid_layout = QtWidgets.QGridLayout() - self.layout.addLayout(self.grid_layout) - - self.grid_layout.setColumnStretch(0, 0) - self.grid_layout.setColumnStretch(1, 3) - self.grid_layout.setColumnStretch(2, 0) - - self.gerber_title_lbl = QtWidgets.QLabel('%s:' % _("GERBER")) - self.gerber_title_lbl.setToolTip( - _("Gerber objects for which to check rules.") - ) - - self.all_obj_cb = FCCheckBox() - - self.grid_layout.addWidget(self.gerber_title_lbl, 0, 0, 1, 2) - self.grid_layout.addWidget(self.all_obj_cb, 0, 2) - - # Copper Top object - self.copper_t_object = FCComboBox() - self.copper_t_object.setModel(self.app.collection) - self.copper_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.copper_t_object.is_last = True - self.copper_t_object.obj_type = "Gerber" - - self.copper_t_object_lbl = QtWidgets.QLabel('%s:' % _("Top")) - self.copper_t_object_lbl.setToolTip( - _("The Top Gerber Copper object for which rules are checked.") - ) - - self.copper_t_cb = FCCheckBox() - - self.grid_layout.addWidget(self.copper_t_object_lbl, 1, 0) - self.grid_layout.addWidget(self.copper_t_object, 1, 1) - self.grid_layout.addWidget(self.copper_t_cb, 1, 2) - - # Copper Bottom object - self.copper_b_object = FCComboBox() - self.copper_b_object.setModel(self.app.collection) - self.copper_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.copper_b_object.is_last = True - self.copper_b_object.obj_type = "Gerber" - - self.copper_b_object_lbl = QtWidgets.QLabel('%s:' % _("Bottom")) - self.copper_b_object_lbl.setToolTip( - _("The Bottom Gerber Copper object for which rules are checked.") - ) - - self.copper_b_cb = FCCheckBox() - - self.grid_layout.addWidget(self.copper_b_object_lbl, 2, 0) - self.grid_layout.addWidget(self.copper_b_object, 2, 1) - self.grid_layout.addWidget(self.copper_b_cb, 2, 2) - - # SolderMask Top object - self.sm_t_object = FCComboBox() - self.sm_t_object.setModel(self.app.collection) - self.sm_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.sm_t_object.is_last = True - self.sm_t_object.obj_type = "Gerber" - - self.sm_t_object_lbl = QtWidgets.QLabel('%s:' % _("SM Top")) - self.sm_t_object_lbl.setToolTip( - _("The Top Gerber Solder Mask object for which rules are checked.") - ) - - self.sm_t_cb = FCCheckBox() - - self.grid_layout.addWidget(self.sm_t_object_lbl, 3, 0) - self.grid_layout.addWidget(self.sm_t_object, 3, 1) - self.grid_layout.addWidget(self.sm_t_cb, 3, 2) - - # SolderMask Bottom object - self.sm_b_object = FCComboBox() - self.sm_b_object.setModel(self.app.collection) - self.sm_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.sm_b_object.is_last = True - self.sm_b_object.obj_type = "Gerber" - - self.sm_b_object_lbl = QtWidgets.QLabel('%s:' % _("SM Bottom")) - self.sm_b_object_lbl.setToolTip( - _("The Bottom Gerber Solder Mask object for which rules are checked.") - ) - - self.sm_b_cb = FCCheckBox() - - self.grid_layout.addWidget(self.sm_b_object_lbl, 4, 0) - self.grid_layout.addWidget(self.sm_b_object, 4, 1) - self.grid_layout.addWidget(self.sm_b_cb, 4, 2) - - # SilkScreen Top object - self.ss_t_object = FCComboBox() - self.ss_t_object.setModel(self.app.collection) - self.ss_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.ss_t_object.is_last = True - self.ss_t_object.obj_type = "Gerber" - - self.ss_t_object_lbl = QtWidgets.QLabel('%s:' % _("Silk Top")) - self.ss_t_object_lbl.setToolTip( - _("The Top Gerber Silkscreen object for which rules are checked.") - ) - - self.ss_t_cb = FCCheckBox() - - self.grid_layout.addWidget(self.ss_t_object_lbl, 5, 0) - self.grid_layout.addWidget(self.ss_t_object, 5, 1) - self.grid_layout.addWidget(self.ss_t_cb, 5, 2) - - # SilkScreen Bottom object - self.ss_b_object = FCComboBox() - self.ss_b_object.setModel(self.app.collection) - self.ss_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.ss_b_object.is_last = True - self.ss_b_object.obj_type = "Gerber" - - self.ss_b_object_lbl = QtWidgets.QLabel('%s:' % _("Silk Bottom")) - self.ss_b_object_lbl.setToolTip( - _("The Bottom Gerber Silkscreen object for which rules are checked.") - ) - - self.ss_b_cb = FCCheckBox() - - self.grid_layout.addWidget(self.ss_b_object_lbl, 6, 0) - self.grid_layout.addWidget(self.ss_b_object, 6, 1) - self.grid_layout.addWidget(self.ss_b_cb, 6, 2) - - # Outline object - self.outline_object = FCComboBox() - self.outline_object.setModel(self.app.collection) - self.outline_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.outline_object.is_last = True - self.outline_object.obj_type = "Gerber" - - self.outline_object_lbl = QtWidgets.QLabel('%s:' % _("Outline")) - self.outline_object_lbl.setToolTip( - _("The Gerber Outline (Cutout) object for which rules are checked.") - ) - - self.out_cb = FCCheckBox() - - self.grid_layout.addWidget(self.outline_object_lbl, 7, 0) - self.grid_layout.addWidget(self.outline_object, 7, 1) - self.grid_layout.addWidget(self.out_cb, 7, 2) - - self.grid_layout.addWidget(QtWidgets.QLabel(""), 8, 0, 1, 3) - - self.excellon_title_lbl = QtWidgets.QLabel('%s:' % _("EXCELLON")) - self.excellon_title_lbl.setToolTip( - _("Excellon objects for which to check rules.") - ) - - self.grid_layout.addWidget(self.excellon_title_lbl, 9, 0, 1, 3) - - # Excellon 1 object - self.e1_object = FCComboBox() - self.e1_object.setModel(self.app.collection) - self.e1_object.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) - self.e1_object.is_last = True - self.e1_object.obj_type = "Excellon" - - self.e1_object_lbl = QtWidgets.QLabel('%s:' % _("Excellon 1")) - self.e1_object_lbl.setToolTip( - _("Excellon object for which to check rules.\n" - "Holds the plated holes or a general Excellon file content.") - ) - - self.e1_cb = FCCheckBox() - - self.grid_layout.addWidget(self.e1_object_lbl, 10, 0) - self.grid_layout.addWidget(self.e1_object, 10, 1) - self.grid_layout.addWidget(self.e1_cb, 10, 2) - - # Excellon 2 object - self.e2_object = FCComboBox() - self.e2_object.setModel(self.app.collection) - self.e2_object.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) - self.e2_object.is_last = True - self.e2_object.obj_type = "Excellon" - - self.e2_object_lbl = QtWidgets.QLabel('%s:' % _("Excellon 2")) - self.e2_object_lbl.setToolTip( - _("Excellon object for which to check rules.\n" - "Holds the non-plated holes.") - ) - - self.e2_cb = FCCheckBox() - - self.grid_layout.addWidget(self.e2_object_lbl, 11, 0) - self.grid_layout.addWidget(self.e2_object, 11, 1) - self.grid_layout.addWidget(self.e2_cb, 11, 2) - - self.grid_layout.addWidget(QtWidgets.QLabel(""), 12, 0, 1, 3) - - # Control All - self.all_cb = FCCheckBox('%s' % _("All Rules")) - self.all_cb.setToolTip( - _("This check/uncheck all the rules below.") - ) - self.all_cb.setStyleSheet( - """ - QCheckBox {font-weight: bold; color: green} - """ - ) - self.layout.addWidget(self.all_cb) - - # Form Layout - self.form_layout_1 = QtWidgets.QFormLayout() - self.layout.addLayout(self.form_layout_1) - - self.form_layout_1.addRow(QtWidgets.QLabel("")) - - # Trace size - self.trace_size_cb = FCCheckBox('%s:' % _("Trace Size")) - self.trace_size_cb.setToolTip( - _("This checks if the minimum size for traces is met.") - ) - self.form_layout_1.addRow(self.trace_size_cb) - - # Trace size value - self.trace_size_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.trace_size_entry.set_range(0.00001, 999.99999) - self.trace_size_entry.set_precision(self.decimals) - self.trace_size_entry.setSingleStep(0.1) - - self.trace_size_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.trace_size_lbl.setToolTip( - _("Minimum acceptable trace size.") - ) - self.form_layout_1.addRow(self.trace_size_lbl, self.trace_size_entry) - - self.ts = OptionalInputSection(self.trace_size_cb, [self.trace_size_lbl, self.trace_size_entry]) - - # Copper2copper clearance - self.clearance_copper2copper_cb = FCCheckBox('%s:' % _("Copper to Copper clearance")) - self.clearance_copper2copper_cb.setToolTip( - _("This checks if the minimum clearance between copper\n" - "features is met.") - ) - self.form_layout_1.addRow(self.clearance_copper2copper_cb) - - # Copper2copper clearance value - self.clearance_copper2copper_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_copper2copper_entry.set_range(0.00001, 999.99999) - self.clearance_copper2copper_entry.set_precision(self.decimals) - self.clearance_copper2copper_entry.setSingleStep(0.1) - - self.clearance_copper2copper_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_copper2copper_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_copper2copper_lbl, self.clearance_copper2copper_entry) - - self.c2c = OptionalInputSection( - self.clearance_copper2copper_cb, [self.clearance_copper2copper_lbl, self.clearance_copper2copper_entry]) - - # Copper2outline clearance - self.clearance_copper2ol_cb = FCCheckBox('%s:' % _("Copper to Outline clearance")) - self.clearance_copper2ol_cb.setToolTip( - _("This checks if the minimum clearance between copper\n" - "features and the outline is met.") - ) - self.form_layout_1.addRow(self.clearance_copper2ol_cb) - - # Copper2outline clearance value - self.clearance_copper2ol_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_copper2ol_entry.set_range(0.00001, 999.99999) - self.clearance_copper2ol_entry.set_precision(self.decimals) - self.clearance_copper2ol_entry.setSingleStep(0.1) - - self.clearance_copper2ol_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_copper2ol_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_copper2ol_lbl, self.clearance_copper2ol_entry) - - self.c2ol = OptionalInputSection( - self.clearance_copper2ol_cb, [self.clearance_copper2ol_lbl, self.clearance_copper2ol_entry]) - - # Silkscreen2silkscreen clearance - self.clearance_silk2silk_cb = FCCheckBox('%s:' % _("Silk to Silk Clearance")) - self.clearance_silk2silk_cb.setToolTip( - _("This checks if the minimum clearance between silkscreen\n" - "features and silkscreen features is met.") - ) - self.form_layout_1.addRow(self.clearance_silk2silk_cb) - - # Copper2silkscreen clearance value - self.clearance_silk2silk_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_silk2silk_entry.set_range(0.00001, 999.99999) - self.clearance_silk2silk_entry.set_precision(self.decimals) - self.clearance_silk2silk_entry.setSingleStep(0.1) - - self.clearance_silk2silk_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_silk2silk_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_silk2silk_lbl, self.clearance_silk2silk_entry) - - self.s2s = OptionalInputSection( - self.clearance_silk2silk_cb, [self.clearance_silk2silk_lbl, self.clearance_silk2silk_entry]) - - # Silkscreen2soldermask clearance - self.clearance_silk2sm_cb = FCCheckBox('%s:' % _("Silk to Solder Mask Clearance")) - self.clearance_silk2sm_cb.setToolTip( - _("This checks if the minimum clearance between silkscreen\n" - "features and soldermask features is met.") - ) - self.form_layout_1.addRow(self.clearance_silk2sm_cb) - - # Silkscreen2soldermask clearance value - self.clearance_silk2sm_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_silk2sm_entry.set_range(0.00001, 999.99999) - self.clearance_silk2sm_entry.set_precision(self.decimals) - self.clearance_silk2sm_entry.setSingleStep(0.1) - - self.clearance_silk2sm_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_silk2sm_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_silk2sm_lbl, self.clearance_silk2sm_entry) - - self.s2sm = OptionalInputSection( - self.clearance_silk2sm_cb, [self.clearance_silk2sm_lbl, self.clearance_silk2sm_entry]) - - # Silk2outline clearance - self.clearance_silk2ol_cb = FCCheckBox('%s:' % _("Silk to Outline Clearance")) - self.clearance_silk2ol_cb.setToolTip( - _("This checks if the minimum clearance between silk\n" - "features and the outline is met.") - ) - self.form_layout_1.addRow(self.clearance_silk2ol_cb) - - # Silk2outline clearance value - self.clearance_silk2ol_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_silk2ol_entry.set_range(0.00001, 999.99999) - self.clearance_silk2ol_entry.set_precision(self.decimals) - self.clearance_silk2ol_entry.setSingleStep(0.1) - - self.clearance_silk2ol_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_silk2ol_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_silk2ol_lbl, self.clearance_silk2ol_entry) - - self.s2ol = OptionalInputSection( - self.clearance_silk2ol_cb, [self.clearance_silk2ol_lbl, self.clearance_silk2ol_entry]) - - # Soldermask2soldermask clearance - self.clearance_sm2sm_cb = FCCheckBox('%s:' % _("Minimum Solder Mask Sliver")) - self.clearance_sm2sm_cb.setToolTip( - _("This checks if the minimum clearance between soldermask\n" - "features and soldermask features is met.") - ) - self.form_layout_1.addRow(self.clearance_sm2sm_cb) - - # Soldermask2soldermask clearance value - self.clearance_sm2sm_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_sm2sm_entry.set_range(0.00001, 999.99999) - self.clearance_sm2sm_entry.set_precision(self.decimals) - self.clearance_sm2sm_entry.setSingleStep(0.1) - - self.clearance_sm2sm_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_sm2sm_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_sm2sm_lbl, self.clearance_sm2sm_entry) - - self.sm2sm = OptionalInputSection( - self.clearance_sm2sm_cb, [self.clearance_sm2sm_lbl, self.clearance_sm2sm_entry]) - - # Ring integrity check - self.ring_integrity_cb = FCCheckBox('%s:' % _("Minimum Annular Ring")) - self.ring_integrity_cb.setToolTip( - _("This checks if the minimum copper ring left by drilling\n" - "a hole into a pad is met.") - ) - self.form_layout_1.addRow(self.ring_integrity_cb) - - # Ring integrity value - self.ring_integrity_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.ring_integrity_entry.set_range(0.00001, 999.99999) - self.ring_integrity_entry.set_precision(self.decimals) - self.ring_integrity_entry.setSingleStep(0.1) - - self.ring_integrity_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.ring_integrity_lbl.setToolTip( - _("Minimum acceptable ring value.") - ) - self.form_layout_1.addRow(self.ring_integrity_lbl, self.ring_integrity_entry) - - self.anr = OptionalInputSection( - self.ring_integrity_cb, [self.ring_integrity_lbl, self.ring_integrity_entry]) - - self.form_layout_1.addRow(QtWidgets.QLabel("")) - - # Hole2Hole clearance - self.clearance_d2d_cb = FCCheckBox('%s:' % _("Hole to Hole Clearance")) - self.clearance_d2d_cb.setToolTip( - _("This checks if the minimum clearance between a drill hole\n" - "and another drill hole is met.") - ) - self.form_layout_1.addRow(self.clearance_d2d_cb) - - # Hole2Hole clearance value - self.clearance_d2d_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.clearance_d2d_entry.set_range(0.00001, 999.99999) - self.clearance_d2d_entry.set_precision(self.decimals) - self.clearance_d2d_entry.setSingleStep(0.1) - - self.clearance_d2d_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.clearance_d2d_lbl.setToolTip( - _("Minimum acceptable clearance value.") - ) - self.form_layout_1.addRow(self.clearance_d2d_lbl, self.clearance_d2d_entry) - - self.d2d = OptionalInputSection( - self.clearance_d2d_cb, [self.clearance_d2d_lbl, self.clearance_d2d_entry]) - - # Drill holes size check - self.drill_size_cb = FCCheckBox('%s:' % _("Hole Size")) - self.drill_size_cb.setToolTip( - _("This checks if the drill holes\n" - "sizes are above the threshold.") - ) - self.form_layout_1.addRow(self.drill_size_cb) - - # Drile holes value - self.drill_size_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.drill_size_entry.set_range(0.00001, 999.99999) - self.drill_size_entry.set_precision(self.decimals) - self.drill_size_entry.setSingleStep(0.1) - - self.drill_size_lbl = QtWidgets.QLabel('%s:' % _("Min value")) - self.drill_size_lbl.setToolTip( - _("Minimum acceptable drill size.") - ) - self.form_layout_1.addRow(self.drill_size_lbl, self.drill_size_entry) - - self.ds = OptionalInputSection( - self.drill_size_cb, [self.drill_size_lbl, self.drill_size_entry]) - - # Buttons - hlay_2 = QtWidgets.QHBoxLayout() - self.layout.addLayout(hlay_2) - - # hlay_2.addStretch() - self.run_button = QtWidgets.QPushButton(_("Run Rules Check")) - self.run_button.setToolTip( - _("Panelize the specified object around the specified box.\n" - "In other words it creates multiple copies of the source object,\n" - "arranged in a 2D array of rows and columns.") - ) - self.run_button.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - hlay_2.addWidget(self.run_button) - - self.layout.addStretch() - - # ## Reset Tool - self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) - self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) - self.reset_button.setToolTip( - _("Will reset the tool parameters.") - ) - self.reset_button.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.layout.addWidget(self.reset_button) + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = RulesUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName # ####################################################### # ################ SIGNALS ############################## # ####################################################### - self.copper_t_cb.stateChanged.connect(lambda st: self.copper_t_object.setDisabled(not st)) - self.copper_b_cb.stateChanged.connect(lambda st: self.copper_b_object.setDisabled(not st)) + self.ui.copper_t_cb.stateChanged.connect(lambda st: self.ui.copper_t_object.setDisabled(not st)) + self.ui.copper_b_cb.stateChanged.connect(lambda st: self.ui.copper_b_object.setDisabled(not st)) - self.sm_t_cb.stateChanged.connect(lambda st: self.sm_t_object.setDisabled(not st)) - self.sm_b_cb.stateChanged.connect(lambda st: self.sm_b_object.setDisabled(not st)) + self.ui.sm_t_cb.stateChanged.connect(lambda st: self.ui.sm_t_object.setDisabled(not st)) + self.ui.sm_b_cb.stateChanged.connect(lambda st: self.ui.sm_b_object.setDisabled(not st)) - self.ss_t_cb.stateChanged.connect(lambda st: self.ss_t_object.setDisabled(not st)) - self.ss_b_cb.stateChanged.connect(lambda st: self.ss_b_object.setDisabled(not st)) + self.ui.ss_t_cb.stateChanged.connect(lambda st: self.ui.ss_t_object.setDisabled(not st)) + self.ui.ss_b_cb.stateChanged.connect(lambda st: self.ui.ss_b_object.setDisabled(not st)) - self.out_cb.stateChanged.connect(lambda st: self.outline_object.setDisabled(not st)) + self.ui.out_cb.stateChanged.connect(lambda st: self.ui.outline_object.setDisabled(not st)) - self.e1_cb.stateChanged.connect(lambda st: self.e1_object.setDisabled(not st)) - self.e2_cb.stateChanged.connect(lambda st: self.e2_object.setDisabled(not st)) + self.ui.e1_cb.stateChanged.connect(lambda st: self.ui.e1_object.setDisabled(not st)) + self.ui.e2_cb.stateChanged.connect(lambda st: self.ui.e2_object.setDisabled(not st)) - self.all_obj_cb.stateChanged.connect(self.on_all_objects_cb_changed) - self.all_cb.stateChanged.connect(self.on_all_cb_changed) - self.run_button.clicked.connect(self.execute) - # self.app.collection.rowsInserted.connect(self.on_object_loaded) + self.ui.all_obj_cb.stateChanged.connect(self.ui.on_all_objects_cb_changed) + self.ui.all_cb.stateChanged.connect(self.ui.on_all_cb_changed) + self.ui.run_button.clicked.connect(self.execute) + self.ui.reset_button.clicked.connect(self.set_tool_ui) + + # Custom Signals self.tool_finished.connect(self.on_tool_finished) - self.reset_button.clicked.connect(self.set_tool_ui) # list to hold the temporary objects self.objs = [] @@ -569,26 +87,6 @@ class RulesCheck(AppTool): # def on_object_loaded(self, index, row): # print(index.internalPointer().child_items[row].obj.options['name'], index.data()) - def on_all_cb_changed(self, state): - cb_items = [self.form_layout_1.itemAt(i).widget() for i in range(self.form_layout_1.count()) - if isinstance(self.form_layout_1.itemAt(i).widget(), FCCheckBox)] - - for cb in cb_items: - if state: - cb.setChecked(True) - else: - cb.setChecked(False) - - def on_all_objects_cb_changed(self, state): - cb_items = [self.grid_layout.itemAt(i).widget() for i in range(self.grid_layout.count()) - if isinstance(self.grid_layout.itemAt(i).widget(), FCCheckBox)] - - for cb in cb_items: - if state: - cb.setChecked(True) - else: - cb.setChecked(False) - def run(self, toggle=True): self.app.defaults.report_usage("ToolRulesCheck()") @@ -622,40 +120,40 @@ class RulesCheck(AppTool): def set_tool_ui(self): # all object combobox default as disabled - self.copper_t_object.setDisabled(True) - self.copper_b_object.setDisabled(True) + self.ui.copper_t_object.setDisabled(True) + self.ui.copper_b_object.setDisabled(True) - self.sm_t_object.setDisabled(True) - self.sm_b_object.setDisabled(True) + self.ui.sm_t_object.setDisabled(True) + self.ui.sm_b_object.setDisabled(True) - self.ss_t_object.setDisabled(True) - self.ss_b_object.setDisabled(True) + self.ui.ss_t_object.setDisabled(True) + self.ui.ss_b_object.setDisabled(True) - self.outline_object.setDisabled(True) + self.ui.outline_object.setDisabled(True) - self.e1_object.setDisabled(True) - self.e2_object.setDisabled(True) + self.ui.e1_object.setDisabled(True) + self.ui.e2_object.setDisabled(True) - self.trace_size_cb.set_value(self.app.defaults["tools_cr_trace_size"]) - self.trace_size_entry.set_value(float(self.app.defaults["tools_cr_trace_size_val"])) - self.clearance_copper2copper_cb.set_value(self.app.defaults["tools_cr_c2c"]) - self.clearance_copper2copper_entry.set_value(float(self.app.defaults["tools_cr_c2c_val"])) - self.clearance_copper2ol_cb.set_value(self.app.defaults["tools_cr_c2o"]) - self.clearance_copper2ol_entry.set_value(float(self.app.defaults["tools_cr_c2o_val"])) - self.clearance_silk2silk_cb.set_value(self.app.defaults["tools_cr_s2s"]) - self.clearance_silk2silk_entry.set_value(float(self.app.defaults["tools_cr_s2s_val"])) - self.clearance_silk2sm_cb.set_value(self.app.defaults["tools_cr_s2sm"]) - self.clearance_silk2sm_entry.set_value(float(self.app.defaults["tools_cr_s2sm_val"])) - self.clearance_silk2ol_cb.set_value(self.app.defaults["tools_cr_s2o"]) - self.clearance_silk2ol_entry.set_value(float(self.app.defaults["tools_cr_s2o_val"])) - self.clearance_sm2sm_cb.set_value(self.app.defaults["tools_cr_sm2sm"]) - self.clearance_sm2sm_entry.set_value(float(self.app.defaults["tools_cr_sm2sm_val"])) - self.ring_integrity_cb.set_value(self.app.defaults["tools_cr_ri"]) - self.ring_integrity_entry.set_value(float(self.app.defaults["tools_cr_ri_val"])) - self.clearance_d2d_cb.set_value(self.app.defaults["tools_cr_h2h"]) - self.clearance_d2d_entry.set_value(float(self.app.defaults["tools_cr_h2h_val"])) - self.drill_size_cb.set_value(self.app.defaults["tools_cr_dh"]) - self.drill_size_entry.set_value(float(self.app.defaults["tools_cr_dh_val"])) + self.ui.trace_size_cb.set_value(self.app.defaults["tools_cr_trace_size"]) + self.ui.trace_size_entry.set_value(float(self.app.defaults["tools_cr_trace_size_val"])) + self.ui.clearance_copper2copper_cb.set_value(self.app.defaults["tools_cr_c2c"]) + self.ui.clearance_copper2copper_entry.set_value(float(self.app.defaults["tools_cr_c2c_val"])) + self.ui.clearance_copper2ol_cb.set_value(self.app.defaults["tools_cr_c2o"]) + self.ui.clearance_copper2ol_entry.set_value(float(self.app.defaults["tools_cr_c2o_val"])) + self.ui.clearance_silk2silk_cb.set_value(self.app.defaults["tools_cr_s2s"]) + self.ui.clearance_silk2silk_entry.set_value(float(self.app.defaults["tools_cr_s2s_val"])) + self.ui.clearance_silk2sm_cb.set_value(self.app.defaults["tools_cr_s2sm"]) + self.ui.clearance_silk2sm_entry.set_value(float(self.app.defaults["tools_cr_s2sm_val"])) + self.ui.clearance_silk2ol_cb.set_value(self.app.defaults["tools_cr_s2o"]) + self.ui.clearance_silk2ol_entry.set_value(float(self.app.defaults["tools_cr_s2o_val"])) + self.ui.clearance_sm2sm_cb.set_value(self.app.defaults["tools_cr_sm2sm"]) + self.ui.clearance_sm2sm_entry.set_value(float(self.app.defaults["tools_cr_sm2sm_val"])) + self.ui.ring_integrity_cb.set_value(self.app.defaults["tools_cr_ri"]) + self.ui.ring_integrity_entry.set_value(float(self.app.defaults["tools_cr_ri_val"])) + self.ui.clearance_d2d_cb.set_value(self.app.defaults["tools_cr_h2h"]) + self.ui.clearance_d2d_entry.set_value(float(self.app.defaults["tools_cr_h2h_val"])) + self.ui.drill_size_cb.set_value(self.app.defaults["tools_cr_dh"]) + self.ui.drill_size_entry.set_value(float(self.app.defaults["tools_cr_dh_val"])) self.reset_fields() @@ -1128,30 +626,30 @@ class RulesCheck(AppTool): self.app.proc_container.new(_("Working...")) # RULE: Check Trace Size - if self.trace_size_cb.get_value(): + if self.ui.trace_size_cb.get_value(): copper_list = [] - copper_name_1 = self.copper_t_object.currentText() - if copper_name_1 != '' and self.copper_t_cb.get_value(): + copper_name_1 = self.ui.copper_t_object.currentText() + if copper_name_1 != '' and self.ui.copper_t_cb.get_value(): elem_dict = {} elem_dict['name'] = deepcopy(copper_name_1) elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_1).apertures) copper_list.append(elem_dict) - copper_name_2 = self.copper_b_object.currentText() - if copper_name_2 != '' and self.copper_b_cb.get_value(): + copper_name_2 = self.ui.copper_b_object.currentText() + if copper_name_2 != '' and self.ui.copper_b_cb.get_value(): elem_dict = {} elem_dict['name'] = deepcopy(copper_name_2) elem_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_name_2).apertures) copper_list.append(elem_dict) - trace_size = float(self.trace_size_entry.get_value()) + trace_size = float(self.ui.trace_size_entry.get_value()) self.results.append(self.pool.apply_async(self.check_traces_size, args=(copper_list, trace_size))) # RULE: Check Copper to Copper Clearance - if self.clearance_copper2copper_cb.get_value(): + if self.ui.clearance_copper2copper_cb.get_value(): try: - copper_copper_clearance = float(self.clearance_copper2copper_entry.get_value()) + copper_copper_clearance = float(self.ui.clearance_copper2copper_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1160,7 +658,7 @@ class RulesCheck(AppTool): return if self.copper_t_cb.get_value(): - copper_t_obj = self.copper_t_object.currentText() + copper_t_obj = self.ui.copper_t_object.currentText() copper_t_dict = {} if copper_t_obj != '': @@ -1171,8 +669,8 @@ class RulesCheck(AppTool): args=(copper_t_dict, copper_copper_clearance, _("TOP -> Copper to Copper clearance")))) - if self.copper_b_cb.get_value(): - copper_b_obj = self.copper_b_object.currentText() + if self.ui.copper_b_cb.get_value(): + copper_b_obj = self.ui.copper_b_object.currentText() copper_b_dict = {} if copper_b_obj != '': copper_b_dict['name'] = deepcopy(copper_b_obj) @@ -1183,35 +681,35 @@ class RulesCheck(AppTool): copper_copper_clearance, _("BOTTOM -> Copper to Copper clearance")))) - if self.copper_t_cb.get_value() is False and self.copper_b_cb.get_value() is False: + if self.ui.copper_t_cb.get_value() is False and self.ui.copper_b_cb.get_value() is False: self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( _("Copper to Copper clearance"), _("At least one Gerber object has to be selected for this rule but none is selected."))) return # RULE: Check Copper to Outline Clearance - if self.clearance_copper2ol_cb.get_value() and self.out_cb.get_value(): + if self.ui.clearance_copper2ol_cb.get_value() and self.ui.out_cb.get_value(): top_dict = {} bottom_dict = {} outline_dict = {} - copper_top = self.copper_t_object.currentText() - if copper_top != '' and self.copper_t_cb.get_value(): + copper_top = self.ui.copper_t_object.currentText() + if copper_top != '' and self.ui.copper_t_cb.get_value(): top_dict['name'] = deepcopy(copper_top) top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures) - copper_bottom = self.copper_b_object.currentText() - if copper_bottom != '' and self.copper_b_cb.get_value(): + copper_bottom = self.ui.copper_b_object.currentText() + if copper_bottom != '' and self.ui.copper_b_cb.get_value(): bottom_dict['name'] = deepcopy(copper_bottom) bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures) - copper_outline = self.outline_object.currentText() - if copper_outline != '' and self.out_cb.get_value(): + copper_outline = self.ui.outline_object.currentText() + if copper_outline != '' and self.ui.out_cb.get_value(): outline_dict['name'] = deepcopy(copper_outline) outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures) try: - copper_outline_clearance = float(self.clearance_copper2ol_entry.get_value()) + copper_outline_clearance = float(self.ui.clearance_copper2ol_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1244,11 +742,11 @@ class RulesCheck(AppTool): _("Copper to Outline clearance")))) # RULE: Check Silk to Silk Clearance - if self.clearance_silk2silk_cb.get_value(): + if self.ui.clearance_silk2silk_cb.get_value(): silk_dict = {} try: - silk_silk_clearance = float(self.clearance_silk2silk_entry.get_value()) + silk_silk_clearance = float(self.ui.clearance_silk2silk_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1257,7 +755,7 @@ class RulesCheck(AppTool): return if self.ss_t_cb.get_value(): - silk_obj = self.ss_t_object.currentText() + silk_obj = self.ui.ss_t_object.currentText() if silk_obj != '': silk_dict['name'] = deepcopy(silk_obj) silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures) @@ -1266,8 +764,8 @@ class RulesCheck(AppTool): args=(silk_dict, silk_silk_clearance, _("TOP -> Silk to Silk clearance")))) - if self.ss_b_cb.get_value(): - silk_obj = self.ss_b_object.currentText() + if self.ui.ss_b_cb.get_value(): + silk_obj = self.ui.ss_b_object.currentText() if silk_obj != '': silk_dict['name'] = deepcopy(silk_obj) silk_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_obj).apertures) @@ -1277,14 +775,14 @@ class RulesCheck(AppTool): silk_silk_clearance, _("BOTTOM -> Silk to Silk clearance")))) - if self.ss_t_cb.get_value() is False and self.ss_b_cb.get_value() is False: + if self.ui.ss_t_cb.get_value() is False and self.ui.ss_b_cb.get_value() is False: self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( _("Silk to Silk clearance"), _("At least one Gerber object has to be selected for this rule but none is selected."))) return # RULE: Check Silk to Solder Mask Clearance - if self.clearance_silk2sm_cb.get_value(): + if self.ui.clearance_silk2sm_cb.get_value(): silk_t_dict = {} sm_t_dict = {} silk_b_dict = {} @@ -1295,32 +793,32 @@ class RulesCheck(AppTool): top_sm = False bottom_sm = False - silk_top = self.ss_t_object.currentText() - if silk_top != '' and self.ss_t_cb.get_value(): + silk_top = self.ui.ss_t_object.currentText() + if silk_top != '' and self.ui.ss_t_cb.get_value(): silk_t_dict['name'] = deepcopy(silk_top) silk_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures) top_ss = True - silk_bottom = self.ss_b_object.currentText() - if silk_bottom != '' and self.ss_b_cb.get_value(): + silk_bottom = self.ui.ss_b_object.currentText() + if silk_bottom != '' and self.ui.ss_b_cb.get_value(): silk_b_dict['name'] = deepcopy(silk_bottom) silk_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures) bottom_ss = True - sm_top = self.sm_t_object.currentText() - if sm_top != '' and self.sm_t_cb.get_value(): + sm_top = self.ui.sm_t_object.currentText() + if sm_top != '' and self.ui.sm_t_cb.get_value(): sm_t_dict['name'] = deepcopy(sm_top) sm_t_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_top).apertures) top_sm = True - sm_bottom = self.sm_b_object.currentText() - if sm_bottom != '' and self.sm_b_cb.get_value(): + sm_bottom = self.ui.sm_b_object.currentText() + if sm_bottom != '' and self.ui.sm_b_cb.get_value(): sm_b_dict['name'] = deepcopy(sm_bottom) sm_b_dict['apertures'] = deepcopy(self.app.collection.get_by_name(sm_bottom).apertures) bottom_sm = True try: - silk_sm_clearance = float(self.clearance_silk2sm_entry.get_value()) + silk_sm_clearance = float(self.ui.clearance_silk2sm_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1353,28 +851,28 @@ class RulesCheck(AppTool): return # RULE: Check Silk to Outline Clearance - if self.clearance_silk2ol_cb.get_value(): + if self.ui.clearance_silk2ol_cb.get_value(): top_dict = {} bottom_dict = {} outline_dict = {} - silk_top = self.ss_t_object.currentText() - if silk_top != '' and self.ss_t_cb.get_value(): + silk_top = self.ui.ss_t_object.currentText() + if silk_top != '' and self.ui.ss_t_cb.get_value(): top_dict['name'] = deepcopy(silk_top) top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_top).apertures) - silk_bottom = self.ss_b_object.currentText() - if silk_bottom != '' and self.ss_b_cb.get_value(): + silk_bottom = self.ui.ss_b_object.currentText() + if silk_bottom != '' and self.ui.ss_b_cb.get_value(): bottom_dict['name'] = deepcopy(silk_bottom) bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(silk_bottom).apertures) - copper_outline = self.outline_object.currentText() - if copper_outline != '' and self.out_cb.get_value(): + copper_outline = self.ui.outline_object.currentText() + if copper_outline != '' and self.ui.out_cb.get_value(): outline_dict['name'] = deepcopy(copper_outline) outline_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_outline).apertures) try: - copper_outline_clearance = float(self.clearance_copper2ol_entry.get_value()) + copper_outline_clearance = float(self.ui.clearance_copper2ol_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1408,11 +906,11 @@ class RulesCheck(AppTool): _("Silk to Outline Clearance")))) # RULE: Check Minimum Solder Mask Sliver - if self.clearance_silk2silk_cb.get_value(): + if self.ui.clearance_silk2silk_cb.get_value(): sm_dict = {} try: - sm_sm_clearance = float(self.clearance_sm2sm_entry.get_value()) + sm_sm_clearance = float(self.ui.clearance_sm2sm_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1420,8 +918,8 @@ class RulesCheck(AppTool): _("Value is not valid."))) return - if self.sm_t_cb.get_value(): - solder_obj = self.sm_t_object.currentText() + if self.ui.sm_t_cb.get_value(): + solder_obj = self.ui.sm_t_object.currentText() if solder_obj != '': sm_dict['name'] = deepcopy(solder_obj) sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures) @@ -1430,8 +928,8 @@ class RulesCheck(AppTool): args=(sm_dict, sm_sm_clearance, _("TOP -> Minimum Solder Mask Sliver")))) - if self.sm_b_cb.get_value(): - solder_obj = self.sm_b_object.currentText() + if self.ui.sm_b_cb.get_value(): + solder_obj = self.ui.sm_b_object.currentText() if solder_obj != '': sm_dict['name'] = deepcopy(solder_obj) sm_dict['apertures'] = deepcopy(self.app.collection.get_by_name(solder_obj).apertures) @@ -1441,43 +939,43 @@ class RulesCheck(AppTool): sm_sm_clearance, _("BOTTOM -> Minimum Solder Mask Sliver")))) - if self.sm_t_cb.get_value() is False and self.sm_b_cb.get_value() is False: + if self.ui.sm_t_cb.get_value() is False and self.ui.sm_b_cb.get_value() is False: self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( _("Minimum Solder Mask Sliver"), _("At least one Gerber object has to be selected for this rule but none is selected."))) return # RULE: Check Minimum Annular Ring - if self.ring_integrity_cb.get_value(): + if self.ui.ring_integrity_cb.get_value(): top_dict = {} bottom_dict = {} exc_1_dict = {} exc_2_dict = {} - copper_top = self.copper_t_object.currentText() - if copper_top != '' and self.copper_t_cb.get_value(): + copper_top = self.ui.copper_t_object.currentText() + if copper_top != '' and self.ui.copper_t_cb.get_value(): top_dict['name'] = deepcopy(copper_top) top_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_top).apertures) - copper_bottom = self.copper_b_object.currentText() - if copper_bottom != '' and self.copper_b_cb.get_value(): + copper_bottom = self.ui.copper_b_object.currentText() + if copper_bottom != '' and self.ui.copper_b_cb.get_value(): bottom_dict['name'] = deepcopy(copper_bottom) bottom_dict['apertures'] = deepcopy(self.app.collection.get_by_name(copper_bottom).apertures) - excellon_1 = self.e1_object.currentText() - if excellon_1 != '' and self.e1_cb.get_value(): + excellon_1 = self.ui.e1_object.currentText() + if excellon_1 != '' and self.ui.e1_cb.get_value(): exc_1_dict['name'] = deepcopy(excellon_1) exc_1_dict['tools'] = deepcopy( self.app.collection.get_by_name(excellon_1).tools) - excellon_2 = self.e2_object.currentText() - if excellon_2 != '' and self.e2_cb.get_value(): + excellon_2 = self.ui.e2_object.currentText() + if excellon_2 != '' and self.ui.e2_cb.get_value(): exc_2_dict['name'] = deepcopy(excellon_2) exc_2_dict['tools'] = deepcopy( self.app.collection.get_by_name(excellon_2).tools) try: - ring_val = float(self.ring_integrity_entry.get_value()) + ring_val = float(self.ui.ring_integrity_entry.get_value()) except Exception as e: log.debug("RulesCheck.execute.worker_job() --> %s" % str(e)) self.app.inform.emit('[ERROR_NOTCL] %s. %s' % ( @@ -1513,43 +1011,43 @@ class RulesCheck(AppTool): _("Minimum Annular Ring")))) # RULE: Check Hole to Hole Clearance - if self.clearance_d2d_cb.get_value(): + if self.ui.clearance_d2d_cb.get_value(): exc_list = [] - exc_name_1 = self.e1_object.currentText() - if exc_name_1 != '' and self.e1_cb.get_value(): + exc_name_1 = self.ui.e1_object.currentText() + if exc_name_1 != '' and self.ui.e1_cb.get_value(): elem_dict = {} elem_dict['name'] = deepcopy(exc_name_1) elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_1).tools) exc_list.append(elem_dict) - exc_name_2 = self.e2_object.currentText() - if exc_name_2 != '' and self.e2_cb.get_value(): + exc_name_2 = self.ui.e2_object.currentText() + if exc_name_2 != '' and self.ui.e2_cb.get_value(): elem_dict = {} elem_dict['name'] = deepcopy(exc_name_2) elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_2).tools) exc_list.append(elem_dict) - hole_clearance = float(self.clearance_d2d_entry.get_value()) + hole_clearance = float(self.ui.clearance_d2d_entry.get_value()) self.results.append(self.pool.apply_async(self.check_holes_clearance, args=(exc_list, hole_clearance))) # RULE: Check Holes Size - if self.drill_size_cb.get_value(): + if self.ui.drill_size_cb.get_value(): exc_list = [] - exc_name_1 = self.e1_object.currentText() - if exc_name_1 != '' and self.e1_cb.get_value(): + exc_name_1 = self.ui.e1_object.currentText() + if exc_name_1 != '' and self.ui.e1_cb.get_value(): elem_dict = {} elem_dict['name'] = deepcopy(exc_name_1) elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_1).tools) exc_list.append(elem_dict) - exc_name_2 = self.e2_object.currentText() - if exc_name_2 != '' and self.e2_cb.get_value(): + exc_name_2 = self.ui.e2_object.currentText() + if exc_name_2 != '' and self.ui.e2_cb.get_value(): elem_dict = {} elem_dict['name'] = deepcopy(exc_name_2) elem_dict['tools'] = deepcopy(self.app.collection.get_by_name(exc_name_2).tools) exc_list.append(elem_dict) - drill_size = float(self.drill_size_entry.get_value()) + drill_size = float(self.ui.drill_size_entry.get_value()) self.results.append(self.pool.apply_async(self.check_holes_size, args=(exc_list, drill_size))) output = [] @@ -1632,3 +1130,539 @@ class RulesCheck(AppTool): # self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) # self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) pass + + +class RulesUI: + + toolName = _("Check Rules") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + + # ## Title + title_label = FCLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + self.layout.addWidget(title_label) + + # Form Layout + self.grid_layout = QtWidgets.QGridLayout() + self.layout.addLayout(self.grid_layout) + + self.grid_layout.setColumnStretch(0, 0) + self.grid_layout.setColumnStretch(1, 3) + self.grid_layout.setColumnStretch(2, 0) + + self.gerber_title_lbl = FCLabel('%s:' % _("GERBER")) + self.gerber_title_lbl.setToolTip( + _("Gerber objects for which to check rules.") + ) + + self.all_obj_cb = FCCheckBox() + + self.grid_layout.addWidget(self.gerber_title_lbl, 0, 0, 1, 2) + self.grid_layout.addWidget(self.all_obj_cb, 0, 2) + + # Copper Top object + self.copper_t_object = FCComboBox() + self.copper_t_object.setModel(self.app.collection) + self.copper_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.copper_t_object.is_last = True + self.copper_t_object.obj_type = "Gerber" + + self.copper_t_object_lbl = FCLabel('%s:' % _("Top")) + self.copper_t_object_lbl.setToolTip( + _("The Top Gerber Copper object for which rules are checked.") + ) + + self.copper_t_cb = FCCheckBox() + + self.grid_layout.addWidget(self.copper_t_object_lbl, 1, 0) + self.grid_layout.addWidget(self.copper_t_object, 1, 1) + self.grid_layout.addWidget(self.copper_t_cb, 1, 2) + + # Copper Bottom object + self.copper_b_object = FCComboBox() + self.copper_b_object.setModel(self.app.collection) + self.copper_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.copper_b_object.is_last = True + self.copper_b_object.obj_type = "Gerber" + + self.copper_b_object_lbl = FCLabel('%s:' % _("Bottom")) + self.copper_b_object_lbl.setToolTip( + _("The Bottom Gerber Copper object for which rules are checked.") + ) + + self.copper_b_cb = FCCheckBox() + + self.grid_layout.addWidget(self.copper_b_object_lbl, 2, 0) + self.grid_layout.addWidget(self.copper_b_object, 2, 1) + self.grid_layout.addWidget(self.copper_b_cb, 2, 2) + + # SolderMask Top object + self.sm_t_object = FCComboBox() + self.sm_t_object.setModel(self.app.collection) + self.sm_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.sm_t_object.is_last = True + self.sm_t_object.obj_type = "Gerber" + + self.sm_t_object_lbl = FCLabel('%s:' % _("SM Top")) + self.sm_t_object_lbl.setToolTip( + _("The Top Gerber Solder Mask object for which rules are checked.") + ) + + self.sm_t_cb = FCCheckBox() + + self.grid_layout.addWidget(self.sm_t_object_lbl, 3, 0) + self.grid_layout.addWidget(self.sm_t_object, 3, 1) + self.grid_layout.addWidget(self.sm_t_cb, 3, 2) + + # SolderMask Bottom object + self.sm_b_object = FCComboBox() + self.sm_b_object.setModel(self.app.collection) + self.sm_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.sm_b_object.is_last = True + self.sm_b_object.obj_type = "Gerber" + + self.sm_b_object_lbl = FCLabel('%s:' % _("SM Bottom")) + self.sm_b_object_lbl.setToolTip( + _("The Bottom Gerber Solder Mask object for which rules are checked.") + ) + + self.sm_b_cb = FCCheckBox() + + self.grid_layout.addWidget(self.sm_b_object_lbl, 4, 0) + self.grid_layout.addWidget(self.sm_b_object, 4, 1) + self.grid_layout.addWidget(self.sm_b_cb, 4, 2) + + # SilkScreen Top object + self.ss_t_object = FCComboBox() + self.ss_t_object.setModel(self.app.collection) + self.ss_t_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.ss_t_object.is_last = True + self.ss_t_object.obj_type = "Gerber" + + self.ss_t_object_lbl = FCLabel('%s:' % _("Silk Top")) + self.ss_t_object_lbl.setToolTip( + _("The Top Gerber Silkscreen object for which rules are checked.") + ) + + self.ss_t_cb = FCCheckBox() + + self.grid_layout.addWidget(self.ss_t_object_lbl, 5, 0) + self.grid_layout.addWidget(self.ss_t_object, 5, 1) + self.grid_layout.addWidget(self.ss_t_cb, 5, 2) + + # SilkScreen Bottom object + self.ss_b_object = FCComboBox() + self.ss_b_object.setModel(self.app.collection) + self.ss_b_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.ss_b_object.is_last = True + self.ss_b_object.obj_type = "Gerber" + + self.ss_b_object_lbl = FCLabel('%s:' % _("Silk Bottom")) + self.ss_b_object_lbl.setToolTip( + _("The Bottom Gerber Silkscreen object for which rules are checked.") + ) + + self.ss_b_cb = FCCheckBox() + + self.grid_layout.addWidget(self.ss_b_object_lbl, 6, 0) + self.grid_layout.addWidget(self.ss_b_object, 6, 1) + self.grid_layout.addWidget(self.ss_b_cb, 6, 2) + + # Outline object + self.outline_object = FCComboBox() + self.outline_object.setModel(self.app.collection) + self.outline_object.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.outline_object.is_last = True + self.outline_object.obj_type = "Gerber" + + self.outline_object_lbl = FCLabel('%s:' % _("Outline")) + self.outline_object_lbl.setToolTip( + _("The Gerber Outline (Cutout) object for which rules are checked.") + ) + + self.out_cb = FCCheckBox() + + self.grid_layout.addWidget(self.outline_object_lbl, 7, 0) + self.grid_layout.addWidget(self.outline_object, 7, 1) + self.grid_layout.addWidget(self.out_cb, 7, 2) + + self.grid_layout.addWidget(FCLabel(""), 8, 0, 1, 3) + + self.excellon_title_lbl = FCLabel('%s:' % _("EXCELLON")) + self.excellon_title_lbl.setToolTip( + _("Excellon objects for which to check rules.") + ) + + self.grid_layout.addWidget(self.excellon_title_lbl, 9, 0, 1, 3) + + # Excellon 1 object + self.e1_object = FCComboBox() + self.e1_object.setModel(self.app.collection) + self.e1_object.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) + self.e1_object.is_last = True + self.e1_object.obj_type = "Excellon" + + self.e1_object_lbl = FCLabel('%s:' % _("Excellon 1")) + self.e1_object_lbl.setToolTip( + _("Excellon object for which to check rules.\n" + "Holds the plated holes or a general Excellon file content.") + ) + + self.e1_cb = FCCheckBox() + + self.grid_layout.addWidget(self.e1_object_lbl, 10, 0) + self.grid_layout.addWidget(self.e1_object, 10, 1) + self.grid_layout.addWidget(self.e1_cb, 10, 2) + + # Excellon 2 object + self.e2_object = FCComboBox() + self.e2_object.setModel(self.app.collection) + self.e2_object.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex())) + self.e2_object.is_last = True + self.e2_object.obj_type = "Excellon" + + self.e2_object_lbl = FCLabel('%s:' % _("Excellon 2")) + self.e2_object_lbl.setToolTip( + _("Excellon object for which to check rules.\n" + "Holds the non-plated holes.") + ) + + self.e2_cb = FCCheckBox() + + self.grid_layout.addWidget(self.e2_object_lbl, 11, 0) + self.grid_layout.addWidget(self.e2_object, 11, 1) + self.grid_layout.addWidget(self.e2_cb, 11, 2) + + self.grid_layout.addWidget(FCLabel(""), 12, 0, 1, 3) + + # Control All + self.all_cb = FCCheckBox('%s' % _("All Rules")) + self.all_cb.setToolTip( + _("This check/uncheck all the rules below.") + ) + self.all_cb.setStyleSheet( + """ + QCheckBox {font-weight: bold; color: green} + """ + ) + self.layout.addWidget(self.all_cb) + + # Form Layout + self.form_layout_1 = QtWidgets.QFormLayout() + self.layout.addLayout(self.form_layout_1) + + self.form_layout_1.addRow(FCLabel("")) + + # Trace size + self.trace_size_cb = FCCheckBox('%s:' % _("Trace Size")) + self.trace_size_cb.setToolTip( + _("This checks if the minimum size for traces is met.") + ) + self.form_layout_1.addRow(self.trace_size_cb) + + # Trace size value + self.trace_size_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.trace_size_entry.set_range(0.00001, 999.99999) + self.trace_size_entry.set_precision(self.decimals) + self.trace_size_entry.setSingleStep(0.1) + + self.trace_size_lbl = FCLabel('%s:' % _("Min value")) + self.trace_size_lbl.setToolTip( + _("Minimum acceptable trace size.") + ) + self.form_layout_1.addRow(self.trace_size_lbl, self.trace_size_entry) + + self.ts = OptionalInputSection(self.trace_size_cb, [self.trace_size_lbl, self.trace_size_entry]) + + # Copper2copper clearance + self.clearance_copper2copper_cb = FCCheckBox('%s:' % _("Copper to Copper clearance")) + self.clearance_copper2copper_cb.setToolTip( + _("This checks if the minimum clearance between copper\n" + "features is met.") + ) + self.form_layout_1.addRow(self.clearance_copper2copper_cb) + + # Copper2copper clearance value + self.clearance_copper2copper_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_copper2copper_entry.set_range(0.00001, 999.99999) + self.clearance_copper2copper_entry.set_precision(self.decimals) + self.clearance_copper2copper_entry.setSingleStep(0.1) + + self.clearance_copper2copper_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_copper2copper_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_copper2copper_lbl, self.clearance_copper2copper_entry) + + self.c2c = OptionalInputSection( + self.clearance_copper2copper_cb, [self.clearance_copper2copper_lbl, self.clearance_copper2copper_entry]) + + # Copper2outline clearance + self.clearance_copper2ol_cb = FCCheckBox('%s:' % _("Copper to Outline clearance")) + self.clearance_copper2ol_cb.setToolTip( + _("This checks if the minimum clearance between copper\n" + "features and the outline is met.") + ) + self.form_layout_1.addRow(self.clearance_copper2ol_cb) + + # Copper2outline clearance value + self.clearance_copper2ol_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_copper2ol_entry.set_range(0.00001, 999.99999) + self.clearance_copper2ol_entry.set_precision(self.decimals) + self.clearance_copper2ol_entry.setSingleStep(0.1) + + self.clearance_copper2ol_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_copper2ol_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_copper2ol_lbl, self.clearance_copper2ol_entry) + + self.c2ol = OptionalInputSection( + self.clearance_copper2ol_cb, [self.clearance_copper2ol_lbl, self.clearance_copper2ol_entry]) + + # Silkscreen2silkscreen clearance + self.clearance_silk2silk_cb = FCCheckBox('%s:' % _("Silk to Silk Clearance")) + self.clearance_silk2silk_cb.setToolTip( + _("This checks if the minimum clearance between silkscreen\n" + "features and silkscreen features is met.") + ) + self.form_layout_1.addRow(self.clearance_silk2silk_cb) + + # Copper2silkscreen clearance value + self.clearance_silk2silk_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_silk2silk_entry.set_range(0.00001, 999.99999) + self.clearance_silk2silk_entry.set_precision(self.decimals) + self.clearance_silk2silk_entry.setSingleStep(0.1) + + self.clearance_silk2silk_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_silk2silk_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_silk2silk_lbl, self.clearance_silk2silk_entry) + + self.s2s = OptionalInputSection( + self.clearance_silk2silk_cb, [self.clearance_silk2silk_lbl, self.clearance_silk2silk_entry]) + + # Silkscreen2soldermask clearance + self.clearance_silk2sm_cb = FCCheckBox('%s:' % _("Silk to Solder Mask Clearance")) + self.clearance_silk2sm_cb.setToolTip( + _("This checks if the minimum clearance between silkscreen\n" + "features and soldermask features is met.") + ) + self.form_layout_1.addRow(self.clearance_silk2sm_cb) + + # Silkscreen2soldermask clearance value + self.clearance_silk2sm_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_silk2sm_entry.set_range(0.00001, 999.99999) + self.clearance_silk2sm_entry.set_precision(self.decimals) + self.clearance_silk2sm_entry.setSingleStep(0.1) + + self.clearance_silk2sm_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_silk2sm_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_silk2sm_lbl, self.clearance_silk2sm_entry) + + self.s2sm = OptionalInputSection( + self.clearance_silk2sm_cb, [self.clearance_silk2sm_lbl, self.clearance_silk2sm_entry]) + + # Silk2outline clearance + self.clearance_silk2ol_cb = FCCheckBox('%s:' % _("Silk to Outline Clearance")) + self.clearance_silk2ol_cb.setToolTip( + _("This checks if the minimum clearance between silk\n" + "features and the outline is met.") + ) + self.form_layout_1.addRow(self.clearance_silk2ol_cb) + + # Silk2outline clearance value + self.clearance_silk2ol_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_silk2ol_entry.set_range(0.00001, 999.99999) + self.clearance_silk2ol_entry.set_precision(self.decimals) + self.clearance_silk2ol_entry.setSingleStep(0.1) + + self.clearance_silk2ol_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_silk2ol_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_silk2ol_lbl, self.clearance_silk2ol_entry) + + self.s2ol = OptionalInputSection( + self.clearance_silk2ol_cb, [self.clearance_silk2ol_lbl, self.clearance_silk2ol_entry]) + + # Soldermask2soldermask clearance + self.clearance_sm2sm_cb = FCCheckBox('%s:' % _("Minimum Solder Mask Sliver")) + self.clearance_sm2sm_cb.setToolTip( + _("This checks if the minimum clearance between soldermask\n" + "features and soldermask features is met.") + ) + self.form_layout_1.addRow(self.clearance_sm2sm_cb) + + # Soldermask2soldermask clearance value + self.clearance_sm2sm_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_sm2sm_entry.set_range(0.00001, 999.99999) + self.clearance_sm2sm_entry.set_precision(self.decimals) + self.clearance_sm2sm_entry.setSingleStep(0.1) + + self.clearance_sm2sm_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_sm2sm_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_sm2sm_lbl, self.clearance_sm2sm_entry) + + self.sm2sm = OptionalInputSection( + self.clearance_sm2sm_cb, [self.clearance_sm2sm_lbl, self.clearance_sm2sm_entry]) + + # Ring integrity check + self.ring_integrity_cb = FCCheckBox('%s:' % _("Minimum Annular Ring")) + self.ring_integrity_cb.setToolTip( + _("This checks if the minimum copper ring left by drilling\n" + "a hole into a pad is met.") + ) + self.form_layout_1.addRow(self.ring_integrity_cb) + + # Ring integrity value + self.ring_integrity_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.ring_integrity_entry.set_range(0.00001, 999.99999) + self.ring_integrity_entry.set_precision(self.decimals) + self.ring_integrity_entry.setSingleStep(0.1) + + self.ring_integrity_lbl = FCLabel('%s:' % _("Min value")) + self.ring_integrity_lbl.setToolTip( + _("Minimum acceptable ring value.") + ) + self.form_layout_1.addRow(self.ring_integrity_lbl, self.ring_integrity_entry) + + self.anr = OptionalInputSection( + self.ring_integrity_cb, [self.ring_integrity_lbl, self.ring_integrity_entry]) + + self.form_layout_1.addRow(FCLabel("")) + + # Hole2Hole clearance + self.clearance_d2d_cb = FCCheckBox('%s:' % _("Hole to Hole Clearance")) + self.clearance_d2d_cb.setToolTip( + _("This checks if the minimum clearance between a drill hole\n" + "and another drill hole is met.") + ) + self.form_layout_1.addRow(self.clearance_d2d_cb) + + # Hole2Hole clearance value + self.clearance_d2d_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.clearance_d2d_entry.set_range(0.00001, 999.99999) + self.clearance_d2d_entry.set_precision(self.decimals) + self.clearance_d2d_entry.setSingleStep(0.1) + + self.clearance_d2d_lbl = FCLabel('%s:' % _("Min value")) + self.clearance_d2d_lbl.setToolTip( + _("Minimum acceptable clearance value.") + ) + self.form_layout_1.addRow(self.clearance_d2d_lbl, self.clearance_d2d_entry) + + self.d2d = OptionalInputSection( + self.clearance_d2d_cb, [self.clearance_d2d_lbl, self.clearance_d2d_entry]) + + # Drill holes size check + self.drill_size_cb = FCCheckBox('%s:' % _("Hole Size")) + self.drill_size_cb.setToolTip( + _("This checks if the drill holes\n" + "sizes are above the threshold.") + ) + self.form_layout_1.addRow(self.drill_size_cb) + + # Drile holes value + self.drill_size_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.drill_size_entry.set_range(0.00001, 999.99999) + self.drill_size_entry.set_precision(self.decimals) + self.drill_size_entry.setSingleStep(0.1) + + self.drill_size_lbl = FCLabel('%s:' % _("Min value")) + self.drill_size_lbl.setToolTip( + _("Minimum acceptable drill size.") + ) + self.form_layout_1.addRow(self.drill_size_lbl, self.drill_size_entry) + + self.ds = OptionalInputSection( + self.drill_size_cb, [self.drill_size_lbl, self.drill_size_entry]) + + # Buttons + hlay_2 = QtWidgets.QHBoxLayout() + self.layout.addLayout(hlay_2) + + # hlay_2.addStretch() + self.run_button = FCButton(_("Run Rules Check")) + self.run_button.setToolTip( + _("Panelize the specified object around the specified box.\n" + "In other words it creates multiple copies of the source object,\n" + "arranged in a 2D array of rows and columns.") + ) + self.run_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + hlay_2.addWidget(self.run_button) + + self.layout.addStretch() + + # ## Reset Tool + self.reset_button = FCButton(_("Reset Tool")) + self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.layout.addWidget(self.reset_button) + + # #################################### FINSIHED GUI ########################### + # ############################################################################# + def on_all_cb_changed(self, state): + cb_items = [self.form_layout_1.itemAt(i).widget() for i in range(self.form_layout_1.count()) + if isinstance(self.form_layout_1.itemAt(i).widget(), FCCheckBox)] + + for cb in cb_items: + if state: + cb.setChecked(True) + else: + cb.setChecked(False) + + def on_all_objects_cb_changed(self, state): + cb_items = [self.grid_layout.itemAt(i).widget() for i in range(self.grid_layout.count()) + if isinstance(self.grid_layout.itemAt(i).widget(), FCCheckBox)] + + for cb in cb_items: + if state: + cb.setChecked(True) + else: + cb.setChecked(False) + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appTools/ToolSub.py b/appTools/ToolSub.py index 5101b12e..cb35383d 100644 --- a/appTools/ToolSub.py +++ b/appTools/ToolSub.py @@ -37,159 +37,17 @@ class ToolSub(AppTool): # meaning geometry that was deformed aperture_processing_finished = QtCore.pyqtSignal(str, list) - toolName = _("Subtract Tool") - def __init__(self, app): self.app = app self.decimals = self.app.decimals AppTool.__init__(self, app) - self.tools_frame = QtWidgets.QFrame() - self.tools_frame.setContentsMargins(0, 0, 0, 0) - self.layout.addWidget(self.tools_frame) - self.tools_box = QtWidgets.QVBoxLayout() - self.tools_box.setContentsMargins(0, 0, 0, 0) - self.tools_frame.setLayout(self.tools_box) - - # Title - title_label = QtWidgets.QLabel("%s" % self.toolName) - title_label.setStyleSheet(""" - QLabel - { - font-size: 16px; - font-weight: bold; - } - """) - self.tools_box.addWidget(title_label) - - # Form Layout - form_layout = QtWidgets.QFormLayout() - self.tools_box.addLayout(form_layout) - - self.gerber_title = QtWidgets.QLabel("%s" % _("GERBER")) - form_layout.addRow(self.gerber_title) - - # Target Gerber Object - self.target_gerber_combo = FCComboBox() - self.target_gerber_combo.setModel(self.app.collection) - self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - # self.target_gerber_combo.setCurrentIndex(1) - self.target_gerber_combo.is_last = True - self.target_gerber_combo.obj_type = "Gerber" - - self.target_gerber_label = QtWidgets.QLabel('%s:' % _("Target")) - self.target_gerber_label.setToolTip( - _("Gerber object from which to subtract\n" - "the subtractor Gerber object.") - ) - - form_layout.addRow(self.target_gerber_label, self.target_gerber_combo) - - # Substractor Gerber Object - self.sub_gerber_combo = FCComboBox() - self.sub_gerber_combo.setModel(self.app.collection) - self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.sub_gerber_combo.is_last = True - self.sub_gerber_combo.obj_type = "Gerber" - - self.sub_gerber_label = QtWidgets.QLabel('%s:' % _("Subtractor")) - self.sub_gerber_label.setToolTip( - _("Gerber object that will be subtracted\n" - "from the target Gerber object.") - ) - e_lab_1 = QtWidgets.QLabel('') - - form_layout.addRow(self.sub_gerber_label, self.sub_gerber_combo) - - self.intersect_btn = FCButton(_('Subtract Gerber')) - self.intersect_btn.setToolTip( - _("Will remove the area occupied by the subtractor\n" - "Gerber from the Target Gerber.\n" - "Can be used to remove the overlapping silkscreen\n" - "over the soldermask.") - ) - self.intersect_btn.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.tools_box.addWidget(self.intersect_btn) - self.tools_box.addWidget(e_lab_1) - - # Form Layout - form_geo_layout = QtWidgets.QFormLayout() - self.tools_box.addLayout(form_geo_layout) - - self.geo_title = QtWidgets.QLabel("%s" % _("GEOMETRY")) - form_geo_layout.addRow(self.geo_title) - - # Target Geometry Object - self.target_geo_combo = FCComboBox() - self.target_geo_combo.setModel(self.app.collection) - self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) - # self.target_geo_combo.setCurrentIndex(1) - self.target_geo_combo.is_last = True - self.target_geo_combo.obj_type = "Geometry" - - self.target_geo_label = QtWidgets.QLabel('%s:' % _("Target")) - self.target_geo_label.setToolTip( - _("Geometry object from which to subtract\n" - "the subtractor Geometry object.") - ) - - form_geo_layout.addRow(self.target_geo_label, self.target_geo_combo) - - # Substractor Geometry Object - self.sub_geo_combo = FCComboBox() - self.sub_geo_combo.setModel(self.app.collection) - self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) - self.sub_geo_combo.is_last = True - self.sub_geo_combo.obj_type = "Geometry" - - self.sub_geo_label = QtWidgets.QLabel('%s:' % _("Subtractor")) - self.sub_geo_label.setToolTip( - _("Geometry object that will be subtracted\n" - "from the target Geometry object.") - ) - e_lab_1 = QtWidgets.QLabel('') - - form_geo_layout.addRow(self.sub_geo_label, self.sub_geo_combo) - - self.close_paths_cb = FCCheckBox(_("Close paths")) - self.close_paths_cb.setToolTip(_("Checking this will close the paths cut by the Geometry subtractor object.")) - self.tools_box.addWidget(self.close_paths_cb) - - self.intersect_geo_btn = FCButton(_('Subtract Geometry')) - self.intersect_geo_btn.setToolTip( - _("Will remove the area occupied by the subtractor\n" - "Geometry from the Target Geometry.") - ) - self.intersect_geo_btn.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.tools_box.addWidget(self.intersect_geo_btn) - self.tools_box.addWidget(e_lab_1) - - self.tools_box.addStretch() - - # ## Reset Tool - self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) - self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) - self.reset_button.setToolTip( - _("Will reset the tool parameters.") - ) - self.reset_button.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.tools_box.addWidget(self.reset_button) + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = SubUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName # QTimer for periodic check self.check_thread = QtCore.QTimer() @@ -228,11 +86,14 @@ class ToolSub(AppTool): self.pool = self.app.pool self.results = [] - self.intersect_btn.clicked.connect(self.on_grb_intersection_click) - self.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click) + # Signals + self.ui.intersect_btn.clicked.connect(self.on_grb_intersection_click) + self.ui.intersect_geo_btn.clicked.connect(self.on_geo_intersection_click) + self.ui.reset_button.clicked.connect(self.set_tool_ui) + + # Custom Signals self.job_finished.connect(self.on_job_finished) self.aperture_processing_finished.connect(self.new_gerber_object) - self.reset_button.clicked.connect(self.set_tool_ui) def install(self, icon=None, separator=None, **kwargs): AppTool.install(self, icon, separator, shortcut='Alt+W', **kwargs) @@ -270,8 +131,8 @@ class ToolSub(AppTool): self.new_solid_geometry = [] self.target_options.clear() - self.tools_frame.show() - self.close_paths_cb.setChecked(self.app.defaults["tools_sub_close_paths"]) + self.ui.tools_frame.show() + self.ui.close_paths_cb.setChecked(self.app.defaults["tools_sub_close_paths"]) def on_grb_intersection_click(self): # reset previous values @@ -281,7 +142,7 @@ class ToolSub(AppTool): self.sub_type = "gerber" - self.target_grb_obj_name = self.target_gerber_combo.currentText() + self.target_grb_obj_name = self.ui.target_gerber_combo.currentText() if self.target_grb_obj_name == '': self.app.inform.emit('[ERROR_NOTCL] %s' % _("No Target object loaded.")) return @@ -296,7 +157,7 @@ class ToolSub(AppTool): self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.obj_name)) return "Could not retrieve object: %s" % self.target_grb_obj_name - self.sub_grb_obj_name = self.sub_gerber_combo.currentText() + self.sub_grb_obj_name = self.ui.sub_gerber_combo.currentText() if self.sub_grb_obj_name == '': self.app.inform.emit('[ERROR_NOTCL] %s' % _("No Subtractor object loaded.")) return @@ -485,10 +346,9 @@ class ToolSub(AppTool): self.sub_type = "geo" - self.target_geo_obj_name = self.target_geo_combo.currentText() + self.target_geo_obj_name = self.ui.target_geo_combo.currentText() if self.target_geo_obj_name == '': - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("No Target object loaded.")) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("No Target object loaded.")) return # Get target object. @@ -496,14 +356,12 @@ class ToolSub(AppTool): self.target_geo_obj = self.app.collection.get_by_name(self.target_geo_obj_name) except Exception as e: log.debug("ToolSub.on_geo_intersection_click() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Could not retrieve object"), self.target_geo_obj_name)) + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.target_geo_obj_name)) return "Could not retrieve object: %s" % self.target_grb_obj_name - self.sub_geo_obj_name = self.sub_geo_combo.currentText() + self.sub_geo_obj_name = self.ui.sub_geo_combo.currentText() if self.sub_geo_obj_name == '': - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("No Subtractor object loaded.")) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("No Subtractor object loaded.")) return # Get substractor object. @@ -511,8 +369,7 @@ class ToolSub(AppTool): self.sub_geo_obj = self.app.collection.get_by_name(self.sub_geo_obj_name) except Exception as e: log.debug("ToolSub.on_geo_intersection_click() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Could not retrieve object"), self.sub_geo_obj_name)) + self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), self.sub_geo_obj_name)) return "Could not retrieve object: %s" % self.sub_geo_obj_name if self.sub_geo_obj.multigeo: @@ -529,11 +386,8 @@ class ToolSub(AppTool): # crate the new_tools dict structure for tool in self.target_geo_obj.tools: self.new_tools[tool] = {} - for key in self.target_geo_obj.tools[tool]: - if key == 'solid_geometry': - self.new_tools[tool][key] = [] - else: - self.new_tools[tool][key] = deepcopy(self.target_geo_obj.tools[tool][key]) + for key, v in self.target_geo_obj.tools[tool]: + self.new_tools[tool][key] = [] if key == 'solid_geometry' else deepcopy(v) # add the promises if self.target_geo_obj.multigeo: @@ -550,12 +404,10 @@ class ToolSub(AppTool): if self.target_geo_obj.multigeo: for tool in self.target_geo_obj.tools: geo = self.target_geo_obj.tools[tool]['solid_geometry'] - self.app.worker_task.emit({'fcn': self.toolgeo_intersection, - 'params': [tool, geo]}) + self.app.worker_task.emit({'fcn': self.toolgeo_intersection, 'params': [tool, geo]}) else: geo = self.target_geo_obj.solid_geometry - self.app.worker_task.emit({'fcn': self.toolgeo_intersection, - 'params': ["single", geo]}) + self.app.worker_task.emit({'fcn': self.toolgeo_intersection, 'params': ["single", geo]}) def toolgeo_intersection(self, tool, geo): new_geometry = [] @@ -568,7 +420,7 @@ class ToolSub(AppTool): with self.app.proc_container.new(text): # resulting paths are closed resulting into Polygons - if self.close_paths_cb.isChecked(): + if self.ui.close_paths_cb.isChecked(): new_geo = (cascaded_union(geo)).difference(self.sub_union) if new_geo: if not new_geo.is_empty: @@ -663,14 +515,12 @@ class ToolSub(AppTool): with self.app.proc_container.new(_("Generating new object ...")): ret = self.app.app_obj.new_object('geometry', outname, obj_init, autoselected=False) if ret == 'fail': - self.app.inform.emit('[ERROR_NOTCL] %s' % - _('Generating new object failed.')) + self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.')) return # Register recent file self.app.file_opened.emit('geometry', outname) # GUI feedback - self.app.inform.emit('[success] %s: %s' % - (_("Created"), outname)) + self.app.inform.emit('[success] %s: %s' % (_("Created"), outname)) # cleanup self.new_tools.clear() @@ -732,13 +582,12 @@ class ToolSub(AppTool): """ if succcess is True: if self.sub_type == "gerber": - outname = self.target_gerber_combo.currentText() + '_sub' + outname = self.ui.target_gerber_combo.currentText() + '_sub' # intersection jobs finished, start the creation of solid_geometry - self.app.worker_task.emit({'fcn': self.new_gerber_object, - 'params': [outname]}) + self.app.worker_task.emit({'fcn': self.new_gerber_object, 'params': [outname]}) else: - outname = self.target_geo_combo.currentText() + '_sub' + outname = self.ui.target_geo_combo.currentText() + '_sub' # intersection jobs finished, start the creation of solid_geometry self.app.worker_task.emit({'fcn': self.new_geo_object, 'params': [outname]}) @@ -746,13 +595,189 @@ class ToolSub(AppTool): self.app.inform.emit('[ERROR_NOTCL] %s' % _('Generating new object failed.')) def reset_fields(self): - self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.ui.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.ui.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) - self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) + self.ui.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) + self.ui.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) @staticmethod def poly2rings(poly): return [poly.exterior] + [interior for interior in poly.interiors] -# end of file + + +class SubUI: + + toolName = _("Subtract Tool") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + + # ## Title + title_label = QtWidgets.QLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + self.layout.addWidget(title_label) + self.layout.addWidget(QtWidgets.QLabel("")) + + self.tools_frame = QtWidgets.QFrame() + self.tools_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.tools_frame) + self.tools_box = QtWidgets.QVBoxLayout() + self.tools_box.setContentsMargins(0, 0, 0, 0) + self.tools_frame.setLayout(self.tools_box) + + # Form Layout + form_layout = QtWidgets.QFormLayout() + self.tools_box.addLayout(form_layout) + + self.gerber_title = QtWidgets.QLabel("%s" % _("GERBER")) + form_layout.addRow(self.gerber_title) + + # Target Gerber Object + self.target_gerber_combo = FCComboBox() + self.target_gerber_combo.setModel(self.app.collection) + self.target_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + # self.target_gerber_combo.setCurrentIndex(1) + self.target_gerber_combo.is_last = True + self.target_gerber_combo.obj_type = "Gerber" + + self.target_gerber_label = QtWidgets.QLabel('%s:' % _("Target")) + self.target_gerber_label.setToolTip( + _("Gerber object from which to subtract\n" + "the subtractor Gerber object.") + ) + + form_layout.addRow(self.target_gerber_label, self.target_gerber_combo) + + # Substractor Gerber Object + self.sub_gerber_combo = FCComboBox() + self.sub_gerber_combo.setModel(self.app.collection) + self.sub_gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.sub_gerber_combo.is_last = True + self.sub_gerber_combo.obj_type = "Gerber" + + self.sub_gerber_label = QtWidgets.QLabel('%s:' % _("Subtractor")) + self.sub_gerber_label.setToolTip( + _("Gerber object that will be subtracted\n" + "from the target Gerber object.") + ) + e_lab_1 = QtWidgets.QLabel('') + + form_layout.addRow(self.sub_gerber_label, self.sub_gerber_combo) + + self.intersect_btn = FCButton(_('Subtract Gerber')) + self.intersect_btn.setToolTip( + _("Will remove the area occupied by the subtractor\n" + "Gerber from the Target Gerber.\n" + "Can be used to remove the overlapping silkscreen\n" + "over the soldermask.") + ) + self.intersect_btn.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.tools_box.addWidget(self.intersect_btn) + self.tools_box.addWidget(e_lab_1) + + # Form Layout + form_geo_layout = QtWidgets.QFormLayout() + self.tools_box.addLayout(form_geo_layout) + + self.geo_title = QtWidgets.QLabel("%s" % _("GEOMETRY")) + form_geo_layout.addRow(self.geo_title) + + # Target Geometry Object + self.target_geo_combo = FCComboBox() + self.target_geo_combo.setModel(self.app.collection) + self.target_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) + # self.target_geo_combo.setCurrentIndex(1) + self.target_geo_combo.is_last = True + self.target_geo_combo.obj_type = "Geometry" + + self.target_geo_label = QtWidgets.QLabel('%s:' % _("Target")) + self.target_geo_label.setToolTip( + _("Geometry object from which to subtract\n" + "the subtractor Geometry object.") + ) + + form_geo_layout.addRow(self.target_geo_label, self.target_geo_combo) + + # Substractor Geometry Object + self.sub_geo_combo = FCComboBox() + self.sub_geo_combo.setModel(self.app.collection) + self.sub_geo_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex())) + self.sub_geo_combo.is_last = True + self.sub_geo_combo.obj_type = "Geometry" + + self.sub_geo_label = QtWidgets.QLabel('%s:' % _("Subtractor")) + self.sub_geo_label.setToolTip( + _("Geometry object that will be subtracted\n" + "from the target Geometry object.") + ) + e_lab_1 = QtWidgets.QLabel('') + + form_geo_layout.addRow(self.sub_geo_label, self.sub_geo_combo) + + self.close_paths_cb = FCCheckBox(_("Close paths")) + self.close_paths_cb.setToolTip(_("Checking this will close the paths cut by the Geometry subtractor object.")) + self.tools_box.addWidget(self.close_paths_cb) + + self.intersect_geo_btn = FCButton(_('Subtract Geometry')) + self.intersect_geo_btn.setToolTip( + _("Will remove the area occupied by the subtractor\n" + "Geometry from the Target Geometry.") + ) + self.intersect_geo_btn.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.tools_box.addWidget(self.intersect_geo_btn) + self.tools_box.addWidget(e_lab_1) + + self.tools_box.addStretch() + + # ## Reset Tool + self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) + self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.tools_box.addWidget(self.reset_button) + + # #################################### FINSIHED GUI ########################### + # ############################################################################# + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) diff --git a/appTools/ToolTransform.py b/appTools/ToolTransform.py index ec61e416..dbc48f85 100644 --- a/appTools/ToolTransform.py +++ b/appTools/ToolTransform.py @@ -7,8 +7,8 @@ from PyQt5 import QtWidgets, QtGui, QtCore from appTool import AppTool -from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, FCEntry, FCComboBox, \ - NumericalEvalTupleEntry +from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, FCComboBox, \ + NumericalEvalTupleEntry, FCLabel import numpy as np @@ -23,449 +23,39 @@ if '_' not in builtins.__dict__: class ToolTransform(AppTool): - toolName = _("Object Transform") - rotateName = _("Rotate") - skewName = _("Skew/Shear") - scaleName = _("Scale") - flipName = _("Mirror (Flip)") - offsetName = _("Offset") - bufferName = _("Buffer") - def __init__(self, app): AppTool.__init__(self, app) self.decimals = self.app.decimals - # ## Title - title_label = QtWidgets.QLabel("%s" % self.toolName) - title_label.setStyleSheet(""" - QLabel - { - font-size: 16px; - font-weight: bold; - } - """) - self.layout.addWidget(title_label) - self.layout.addWidget(QtWidgets.QLabel('')) - - # ## Layout - grid0 = QtWidgets.QGridLayout() - self.layout.addLayout(grid0) - grid0.setColumnStretch(0, 0) - grid0.setColumnStretch(1, 1) - grid0.setColumnStretch(2, 0) - - grid0.addWidget(QtWidgets.QLabel('')) - - # Reference - ref_label = QtWidgets.QLabel('%s:' % _("Reference")) - ref_label.setToolTip( - _("The reference point for Rotate, Skew, Scale, Mirror.\n" - "Can be:\n" - "- Origin -> it is the 0, 0 point\n" - "- Selection -> the center of the bounding box of the selected objects\n" - "- Point -> a custom point defined by X,Y coordinates\n" - "- Object -> the center of the bounding box of a specific object") - ) - self.ref_combo = FCComboBox() - self.ref_items = [_("Origin"), _("Selection"), _("Point"), _("Object")] - self.ref_combo.addItems(self.ref_items) - - grid0.addWidget(ref_label, 0, 0) - grid0.addWidget(self.ref_combo, 0, 1, 1, 2) - - self.point_label = QtWidgets.QLabel('%s:' % _("Value")) - self.point_label.setToolTip( - _("A point of reference in format X,Y.") - ) - self.point_entry = NumericalEvalTupleEntry() - - grid0.addWidget(self.point_label, 1, 0) - grid0.addWidget(self.point_entry, 1, 1, 1, 2) - - self.point_button = FCButton(_("Add")) - self.point_button.setToolTip( - _("Add point coordinates from clipboard.") - ) - grid0.addWidget(self.point_button, 2, 0, 1, 3) - - # Type of object to be used as reference - self.type_object_label = QtWidgets.QLabel('%s:' % _("Type")) - self.type_object_label.setToolTip( - _("The type of object used as reference.") - ) - - self.type_obj_combo = FCComboBox() - self.type_obj_combo.addItem(_("Gerber")) - self.type_obj_combo.addItem(_("Excellon")) - self.type_obj_combo.addItem(_("Geometry")) - - self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png")) - self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png")) - self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png")) - - grid0.addWidget(self.type_object_label, 3, 0) - grid0.addWidget(self.type_obj_combo, 3, 1, 1, 2) - - # Object to be used as reference - self.object_combo = FCComboBox() - self.object_combo.setModel(self.app.collection) - self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.object_combo.is_last = True - - self.object_combo.setToolTip( - _("The object used as reference.\n" - "The used point is the center of it's bounding box.") - ) - grid0.addWidget(self.object_combo, 4, 0, 1, 3) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 5, 0, 1, 3) - - # ## Rotate Title - rotate_title_label = QtWidgets.QLabel("%s" % self.rotateName) - grid0.addWidget(rotate_title_label, 6, 0, 1, 3) - - self.rotate_label = QtWidgets.QLabel('%s:' % _("Angle")) - self.rotate_label.setToolTip( - _("Angle for Rotation action, in degrees.\n" - "Float number between -360 and 359.\n" - "Positive numbers for CW motion.\n" - "Negative numbers for CCW motion.") - ) - - self.rotate_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.rotate_entry.set_precision(self.decimals) - self.rotate_entry.setSingleStep(45) - self.rotate_entry.setWrapping(True) - self.rotate_entry.set_range(-360, 360) - - # self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - - self.rotate_button = FCButton(_("Rotate")) - self.rotate_button.setToolTip( - _("Rotate the selected object(s).\n" - "The point of reference is the middle of\n" - "the bounding box for all selected objects.") - ) - self.rotate_button.setMinimumWidth(90) - - grid0.addWidget(self.rotate_label, 7, 0) - grid0.addWidget(self.rotate_entry, 7, 1) - grid0.addWidget(self.rotate_button, 7, 2) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 8, 0, 1, 3) - - # ## Skew Title - skew_title_label = QtWidgets.QLabel("%s" % self.skewName) - grid0.addWidget(skew_title_label, 9, 0, 1, 2) - - self.skew_link_cb = FCCheckBox() - self.skew_link_cb.setText(_("Link")) - self.skew_link_cb.setToolTip( - _("Link the Y entry to X entry and copy its content.") - ) - - grid0.addWidget(self.skew_link_cb, 9, 2) - - self.skewx_label = QtWidgets.QLabel('%s:' % _("X angle")) - self.skewx_label.setToolTip( - _("Angle for Skew action, in degrees.\n" - "Float number between -360 and 360.") - ) - self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message) - # self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.skewx_entry.set_precision(self.decimals) - self.skewx_entry.set_range(-360, 360) - - self.skewx_button = FCButton(_("Skew X")) - self.skewx_button.setToolTip( - _("Skew/shear the selected object(s).\n" - "The point of reference is the middle of\n" - "the bounding box for all selected objects.")) - self.skewx_button.setMinimumWidth(90) - - grid0.addWidget(self.skewx_label, 10, 0) - grid0.addWidget(self.skewx_entry, 10, 1) - grid0.addWidget(self.skewx_button, 10, 2) - - self.skewy_label = QtWidgets.QLabel('%s:' % _("Y angle")) - self.skewy_label.setToolTip( - _("Angle for Skew action, in degrees.\n" - "Float number between -360 and 360.") - ) - self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message) - # self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.skewy_entry.set_precision(self.decimals) - self.skewy_entry.set_range(-360, 360) - - self.skewy_button = FCButton(_("Skew Y")) - self.skewy_button.setToolTip( - _("Skew/shear the selected object(s).\n" - "The point of reference is the middle of\n" - "the bounding box for all selected objects.")) - self.skewy_button.setMinimumWidth(90) - - grid0.addWidget(self.skewy_label, 12, 0) - grid0.addWidget(self.skewy_entry, 12, 1) - grid0.addWidget(self.skewy_button, 12, 2) - - self.ois_sk = OptionalInputSection(self.skew_link_cb, [self.skewy_label, self.skewy_entry, self.skewy_button], - logic=False) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 14, 0, 1, 3) - - # ## Scale Title - scale_title_label = QtWidgets.QLabel("%s" % self.scaleName) - grid0.addWidget(scale_title_label, 15, 0, 1, 2) - - self.scale_link_cb = FCCheckBox() - self.scale_link_cb.setText(_("Link")) - self.scale_link_cb.setToolTip( - _("Link the Y entry to X entry and copy its content.") - ) - - grid0.addWidget(self.scale_link_cb, 15, 2) - - self.scalex_label = QtWidgets.QLabel('%s:' % _("X factor")) - self.scalex_label.setToolTip( - _("Factor for scaling on X axis.") - ) - self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message) - # self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.scalex_entry.set_precision(self.decimals) - self.scalex_entry.setMinimum(-1e6) - - self.scalex_button = FCButton(_("Scale X")) - self.scalex_button.setToolTip( - _("Scale the selected object(s).\n" - "The point of reference depends on \n" - "the Scale reference checkbox state.")) - self.scalex_button.setMinimumWidth(90) - - grid0.addWidget(self.scalex_label, 17, 0) - grid0.addWidget(self.scalex_entry, 17, 1) - grid0.addWidget(self.scalex_button, 17, 2) - - self.scaley_label = QtWidgets.QLabel('%s:' % _("Y factor")) - self.scaley_label.setToolTip( - _("Factor for scaling on Y axis.") - ) - self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message) - # self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.scaley_entry.set_precision(self.decimals) - self.scaley_entry.setMinimum(-1e6) - - self.scaley_button = FCButton(_("Scale Y")) - self.scaley_button.setToolTip( - _("Scale the selected object(s).\n" - "The point of reference depends on \n" - "the Scale reference checkbox state.")) - self.scaley_button.setMinimumWidth(90) - - grid0.addWidget(self.scaley_label, 19, 0) - grid0.addWidget(self.scaley_entry, 19, 1) - grid0.addWidget(self.scaley_button, 19, 2) - - self.ois_s = OptionalInputSection(self.scale_link_cb, - [ - self.scaley_label, - self.scaley_entry, - self.scaley_button - ], logic=False) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 21, 0, 1, 3) - - # ## Flip Title - flip_title_label = QtWidgets.QLabel("%s" % self.flipName) - grid0.addWidget(flip_title_label, 23, 0, 1, 3) - - self.flipx_button = FCButton(_("Flip on X")) - self.flipx_button.setToolTip( - _("Flip the selected object(s) over the X axis.") - ) - - self.flipy_button = FCButton(_("Flip on Y")) - self.flipy_button.setToolTip( - _("Flip the selected object(s) over the X axis.") - ) - - hlay0 = QtWidgets.QHBoxLayout() - grid0.addLayout(hlay0, 25, 0, 1, 3) - - hlay0.addWidget(self.flipx_button) - hlay0.addWidget(self.flipy_button) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 27, 0, 1, 3) - - # ## Offset Title - offset_title_label = QtWidgets.QLabel("%s" % self.offsetName) - grid0.addWidget(offset_title_label, 29, 0, 1, 3) - - self.offx_label = QtWidgets.QLabel('%s:' % _("X val")) - self.offx_label.setToolTip( - _("Distance to offset on X axis. In current units.") - ) - self.offx_entry = FCDoubleSpinner(callback=self.confirmation_message) - # self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.offx_entry.set_precision(self.decimals) - self.offx_entry.setMinimum(-1e6) - - self.offx_button = FCButton(_("Offset X")) - self.offx_button.setToolTip( - _("Offset the selected object(s).\n" - "The point of reference is the middle of\n" - "the bounding box for all selected objects.\n")) - self.offx_button.setMinimumWidth(90) - - grid0.addWidget(self.offx_label, 31, 0) - grid0.addWidget(self.offx_entry, 31, 1) - grid0.addWidget(self.offx_button, 31, 2) - - self.offy_label = QtWidgets.QLabel('%s:' % _("Y val")) - self.offy_label.setToolTip( - _("Distance to offset on Y axis. In current units.") - ) - self.offy_entry = FCDoubleSpinner(callback=self.confirmation_message) - # self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.offy_entry.set_precision(self.decimals) - self.offy_entry.setMinimum(-1e6) - - self.offy_button = FCButton(_("Offset Y")) - self.offy_button.setToolTip( - _("Offset the selected object(s).\n" - "The point of reference is the middle of\n" - "the bounding box for all selected objects.\n")) - self.offy_button.setMinimumWidth(90) - - grid0.addWidget(self.offy_label, 32, 0) - grid0.addWidget(self.offy_entry, 32, 1) - grid0.addWidget(self.offy_button, 32, 2) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) - grid0.addWidget(separator_line, 34, 0, 1, 3) - - # ## Buffer Title - buffer_title_label = QtWidgets.QLabel("%s" % self.bufferName) - grid0.addWidget(buffer_title_label, 35, 0, 1, 2) - - self.buffer_rounded_cb = FCCheckBox('%s' % _("Rounded")) - self.buffer_rounded_cb.setToolTip( - _("If checked then the buffer will surround the buffered shape,\n" - "every corner will be rounded.\n" - "If not checked then the buffer will follow the exact geometry\n" - "of the buffered shape.") - ) - - grid0.addWidget(self.buffer_rounded_cb, 35, 2) - - self.buffer_label = QtWidgets.QLabel('%s:' % _("Distance")) - self.buffer_label.setToolTip( - _("A positive value will create the effect of dilation,\n" - "while a negative value will create the effect of erosion.\n" - "Each geometry element of the object will be increased\n" - "or decreased with the 'distance'.") - ) - - self.buffer_entry = FCDoubleSpinner(callback=self.confirmation_message) - self.buffer_entry.set_precision(self.decimals) - self.buffer_entry.setSingleStep(0.1) - self.buffer_entry.setWrapping(True) - self.buffer_entry.set_range(-9999.9999, 9999.9999) - - self.buffer_button = FCButton(_("Buffer D")) - self.buffer_button.setToolTip( - _("Create the buffer effect on each geometry,\n" - "element from the selected object, using the distance.") - ) - self.buffer_button.setMinimumWidth(90) - - grid0.addWidget(self.buffer_label, 37, 0) - grid0.addWidget(self.buffer_entry, 37, 1) - grid0.addWidget(self.buffer_button, 37, 2) - - self.buffer_factor_label = QtWidgets.QLabel('%s:' % _("Value")) - self.buffer_factor_label.setToolTip( - _("A positive value will create the effect of dilation,\n" - "while a negative value will create the effect of erosion.\n" - "Each geometry element of the object will be increased\n" - "or decreased to fit the 'Value'. Value is a percentage\n" - "of the initial dimension.") - ) - - self.buffer_factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%') - self.buffer_factor_entry.set_range(-100.0000, 1000.0000) - self.buffer_factor_entry.set_precision(self.decimals) - self.buffer_factor_entry.setWrapping(True) - self.buffer_factor_entry.setSingleStep(1) - - self.buffer_factor_button = FCButton(_("Buffer F")) - self.buffer_factor_button.setToolTip( - _("Create the buffer effect on each geometry,\n" - "element from the selected object, using the factor.") - ) - self.buffer_factor_button.setMinimumWidth(90) - - grid0.addWidget(self.buffer_factor_label, 38, 0) - grid0.addWidget(self.buffer_factor_entry, 38, 1) - grid0.addWidget(self.buffer_factor_button, 38, 2) - - grid0.addWidget(QtWidgets.QLabel(''), 42, 0, 1, 3) - - self.layout.addStretch() - - # ## Reset Tool - self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) - self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) - self.reset_button.setToolTip( - _("Will reset the tool parameters.") - ) - self.reset_button.setStyleSheet(""" - QPushButton - { - font-weight: bold; - } - """) - self.layout.addWidget(self.reset_button) - + # ############################################################################# + # ######################### Tool GUI ########################################## + # ############################################################################# + self.ui = TransformUI(layout=self.layout, app=self.app) + self.toolName = self.ui.toolName + # ## Signals - self.ref_combo.currentIndexChanged.connect(self.on_reference_changed) - self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed) - self.point_button.clicked.connect(self.on_add_coords) + self.ui.ref_combo.currentIndexChanged.connect(self.ui.on_reference_changed) + self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed) + self.ui.point_button.clicked.connect(self.on_add_coords) - self.rotate_button.clicked.connect(self.on_rotate) + self.ui.rotate_button.clicked.connect(self.on_rotate) - self.skewx_button.clicked.connect(self.on_skewx) - self.skewy_button.clicked.connect(self.on_skewy) + self.ui.skewx_button.clicked.connect(self.on_skewx) + self.ui.skewy_button.clicked.connect(self.on_skewy) - self.scalex_button.clicked.connect(self.on_scalex) - self.scaley_button.clicked.connect(self.on_scaley) + self.ui.scalex_button.clicked.connect(self.on_scalex) + self.ui.scaley_button.clicked.connect(self.on_scaley) - self.offx_button.clicked.connect(self.on_offx) - self.offy_button.clicked.connect(self.on_offy) + self.ui.offx_button.clicked.connect(self.on_offx) + self.ui.offy_button.clicked.connect(self.on_offy) - self.flipx_button.clicked.connect(self.on_flipx) - self.flipy_button.clicked.connect(self.on_flipy) + self.ui.flipx_button.clicked.connect(self.on_flipx) + self.ui.flipy_button.clicked.connect(self.on_flipy) - self.buffer_button.clicked.connect(self.on_buffer_by_distance) - self.buffer_factor_button.clicked.connect(self.on_buffer_by_factor) + self.ui.buffer_button.clicked.connect(self.on_buffer_by_distance) + self.ui.buffer_factor_button.clicked.connect(self.on_buffer_by_factor) - self.reset_button.clicked.connect(self.set_tool_ui) + self.ui.reset_button.clicked.connect(self.set_tool_ui) def run(self, toggle=True): self.app.defaults.report_usage("ToolTransform()") @@ -500,70 +90,44 @@ class ToolTransform(AppTool): def set_tool_ui(self): # ## Initialize form - self.ref_combo.set_value(self.app.defaults["tools_transform_reference"]) - self.type_obj_combo.set_value(self.app.defaults["tools_transform_ref_object"]) - self.point_entry.set_value(self.app.defaults["tools_transform_ref_point"]) - self.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"]) + self.ui.ref_combo.set_value(self.app.defaults["tools_transform_reference"]) + self.ui.type_obj_combo.set_value(self.app.defaults["tools_transform_ref_object"]) + self.ui.point_entry.set_value(self.app.defaults["tools_transform_ref_point"]) + self.ui.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"]) - self.skewx_entry.set_value(self.app.defaults["tools_transform_skew_x"]) - self.skewy_entry.set_value(self.app.defaults["tools_transform_skew_y"]) - self.skew_link_cb.set_value(self.app.defaults["tools_transform_skew_link"]) + self.ui.skewx_entry.set_value(self.app.defaults["tools_transform_skew_x"]) + self.ui.skewy_entry.set_value(self.app.defaults["tools_transform_skew_y"]) + self.ui.skew_link_cb.set_value(self.app.defaults["tools_transform_skew_link"]) - self.scalex_entry.set_value(self.app.defaults["tools_transform_scale_x"]) - self.scaley_entry.set_value(self.app.defaults["tools_transform_scale_y"]) - self.scale_link_cb.set_value(self.app.defaults["tools_transform_scale_link"]) + self.ui.scalex_entry.set_value(self.app.defaults["tools_transform_scale_x"]) + self.ui.scaley_entry.set_value(self.app.defaults["tools_transform_scale_y"]) + self.ui.scale_link_cb.set_value(self.app.defaults["tools_transform_scale_link"]) - self.offx_entry.set_value(self.app.defaults["tools_transform_offset_x"]) - self.offy_entry.set_value(self.app.defaults["tools_transform_offset_y"]) + self.ui.offx_entry.set_value(self.app.defaults["tools_transform_offset_x"]) + self.ui.offy_entry.set_value(self.app.defaults["tools_transform_offset_y"]) - self.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"]) - self.buffer_factor_entry.set_value(self.app.defaults["tools_transform_buffer_factor"]) - self.buffer_rounded_cb.set_value(self.app.defaults["tools_transform_buffer_corner"]) + self.ui.buffer_entry.set_value(self.app.defaults["tools_transform_buffer_dis"]) + self.ui.buffer_factor_entry.set_value(self.app.defaults["tools_transform_buffer_factor"]) + self.ui.buffer_rounded_cb.set_value(self.app.defaults["tools_transform_buffer_corner"]) # initial state is hidden - self.point_label.hide() - self.point_entry.hide() - self.point_button.hide() + self.ui.point_label.hide() + self.ui.point_entry.hide() + self.ui.point_button.hide() - self.type_object_label.hide() - self.type_obj_combo.hide() - self.object_combo.hide() + self.ui.type_object_label.hide() + self.ui.type_obj_combo.hide() + self.ui.object_combo.hide() def on_type_obj_index_changed(self, index): - self.object_combo.setRootModelIndex(self.app.collection.index(index, 0, QtCore.QModelIndex())) - self.object_combo.setCurrentIndex(0) - self.object_combo.obj_type = { + self.ui.object_combo.setRootModelIndex(self.app.collection.index(index, 0, QtCore.QModelIndex())) + self.ui.object_combo.setCurrentIndex(0) + self.ui.object_combo.obj_type = { _("Gerber"): "Gerber", _("Excellon"): "Excellon", _("Geometry"): "Geometry" - }[self.type_obj_combo.get_value()] - - def on_reference_changed(self, index): - if index == 0 or index == 1: # "Origin" or "Selection" reference - self.point_label.hide() - self.point_entry.hide() - self.point_button.hide() - - self.type_object_label.hide() - self.type_obj_combo.hide() - self.object_combo.hide() - elif index == 2: # "Point" reference - self.point_label.show() - self.point_entry.show() - self.point_button.show() - - self.type_object_label.hide() - self.type_obj_combo.hide() - self.object_combo.hide() - else: # "Object" reference - self.point_label.hide() - self.point_entry.hide() - self.point_button.hide() - - self.type_object_label.show() - self.type_obj_combo.show() - self.object_combo.show() + }[self.ui.type_obj_combo.get_value()] def on_calculate_reference(self): - ref_val = self.ref_combo.currentIndex() + ref_val = self.ui.ref_combo.currentIndex() if ref_val == 0: # "Origin" reference return 0, 0 @@ -578,7 +142,7 @@ class ToolTransform(AppTool): self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object selected.")) return "fail" elif ref_val == 2: # "Point" reference - point_val = self.point_entry.get_value() + point_val = self.uipoint_entry.get_value() try: px, py = eval('{}'.format(point_val)) return px, py @@ -586,7 +150,7 @@ class ToolTransform(AppTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("Incorrect format for Point value. Needs format X,Y")) return "fail" else: # "Object" reference - obj_name = self.object_combo.get_value() + obj_name = self.ui.object_combo.get_value() ref_obj = self.app.collection.get_by_name(obj_name) xmin, ymin, xmax, ymax = ref_obj.bounds() px = (xmax + xmin) * 0.5 @@ -595,10 +159,10 @@ class ToolTransform(AppTool): def on_add_coords(self): val = self.app.clipboard.text() - self.point_entry.set_value(val) + self.ui.point_entry.set_value(val) def on_rotate(self): - value = float(self.rotate_entry.get_value()) + value = float(self.ui.rotate_entry.get_value()) if value == 0: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate transformation can not be done for a value of 0.")) return @@ -622,12 +186,12 @@ class ToolTransform(AppTool): self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]}) def on_skewx(self): - xvalue = float(self.skewx_entry.get_value()) + xvalue = float(self.ui.skewx_entry.get_value()) if xvalue == 0: return - if self.skew_link_cb.get_value(): + if self.ui.skew_link_cb.get_value(): yvalue = xvalue else: yvalue = 0 @@ -641,7 +205,7 @@ class ToolTransform(AppTool): def on_skewy(self): xvalue = 0 - yvalue = float(self.skewy_entry.get_value()) + yvalue = float(self.ui.skewy_entry.get_value()) if yvalue == 0: return @@ -654,14 +218,14 @@ class ToolTransform(AppTool): self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]}) def on_scalex(self): - xvalue = float(self.scalex_entry.get_value()) + xvalue = float(self.ui.scalex_entry.get_value()) if xvalue == 0 or xvalue == 1: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Scale transformation can not be done for a factor of 0 or 1.")) return - if self.scale_link_cb.get_value(): + if self.ui.scale_link_cb.get_value(): yvalue = xvalue else: yvalue = 1 @@ -675,7 +239,7 @@ class ToolTransform(AppTool): def on_scaley(self): xvalue = 1 - yvalue = float(self.scaley_entry.get_value()) + yvalue = float(self.ui.scaley_entry.get_value()) if yvalue == 0 or yvalue == 1: self.app.inform.emit('[WARNING_NOTCL] %s' % @@ -690,7 +254,7 @@ class ToolTransform(AppTool): self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]}) def on_offx(self): - value = float(self.offx_entry.get_value()) + value = float(self.ui.offx_entry.get_value()) if value == 0: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0.")) return @@ -699,7 +263,7 @@ class ToolTransform(AppTool): self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) def on_offy(self): - value = float(self.offy_entry.get_value()) + value = float(self.ui.offy_entry.get_value()) if value == 0: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0.")) return @@ -708,14 +272,14 @@ class ToolTransform(AppTool): self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) def on_buffer_by_distance(self): - value = self.buffer_entry.get_value() - join = 1 if self.buffer_rounded_cb.get_value() else 2 + value = self.ui.buffer_entry.get_value() + join = 1 if self.ui.buffer_rounded_cb.get_value() else 2 self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join]}) def on_buffer_by_factor(self): - value = 1 + self.buffer_factor_entry.get_value() / 100.0 - join = 1 if self.buffer_rounded_cb.get_value() else 2 + value = 1 + self.ui.buffer_factor_entry.get_value() / 100.0 + join = 1 if self.ui.buffer_rounded_cb.get_value() else 2 # tell the buffer method to use the factor factor = True @@ -876,8 +440,7 @@ class ToolTransform(AppTool): self.app.inform.emit('[success] %s %s %s...' % (_('Offset on the'), str(axis), _('axis done'))) except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % - (_("Action was not executed, due of"), str(e))) + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed, due of"), str(e))) return def on_buffer_action(self, value, join, factor=None): @@ -912,8 +475,7 @@ class ToolTransform(AppTool): except Exception as e: self.app.log.debug("ToolTransform.on_buffer_action() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % - (_("Action was not executed, due of"), str(e))) + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed, due of"), str(e))) return @staticmethod @@ -944,4 +506,472 @@ class ToolTransform(AppTool): return bounds_rec(obj_list) -# end of file + +class TransformUI: + + toolName = _("Object Transform") + rotateName = _("Rotate") + skewName = _("Skew/Shear") + scaleName = _("Scale") + flipName = _("Mirror (Flip)") + offsetName = _("Offset") + bufferName = _("Buffer") + + def __init__(self, layout, app): + self.app = app + self.decimals = self.app.decimals + self.layout = layout + + # ## Title + title_label = FCLabel("%s" % self.toolName) + title_label.setStyleSheet(""" + QLabel + { + font-size: 16px; + font-weight: bold; + } + """) + self.layout.addWidget(title_label) + self.layout.addWidget(FCLabel("")) + + # ## Layout + grid0 = QtWidgets.QGridLayout() + self.layout.addLayout(grid0) + grid0.setColumnStretch(0, 0) + grid0.setColumnStretch(1, 1) + grid0.setColumnStretch(2, 0) + + grid0.addWidget(FCLabel('')) + + # Reference + ref_label = FCLabel('%s:' % _("Reference")) + ref_label.setToolTip( + _("The reference point for Rotate, Skew, Scale, Mirror.\n" + "Can be:\n" + "- Origin -> it is the 0, 0 point\n" + "- Selection -> the center of the bounding box of the selected objects\n" + "- Point -> a custom point defined by X,Y coordinates\n" + "- Object -> the center of the bounding box of a specific object") + ) + self.ref_combo = FCComboBox() + self.ref_items = [_("Origin"), _("Selection"), _("Point"), _("Object")] + self.ref_combo.addItems(self.ref_items) + + grid0.addWidget(ref_label, 0, 0) + grid0.addWidget(self.ref_combo, 0, 1, 1, 2) + + self.point_label = FCLabel('%s:' % _("Value")) + self.point_label.setToolTip( + _("A point of reference in format X,Y.") + ) + self.point_entry = NumericalEvalTupleEntry() + + grid0.addWidget(self.point_label, 1, 0) + grid0.addWidget(self.point_entry, 1, 1, 1, 2) + + self.point_button = FCButton(_("Add")) + self.point_button.setToolTip( + _("Add point coordinates from clipboard.") + ) + grid0.addWidget(self.point_button, 2, 0, 1, 3) + + # Type of object to be used as reference + self.type_object_label = FCLabel('%s:' % _("Type")) + self.type_object_label.setToolTip( + _("The type of object used as reference.") + ) + + self.type_obj_combo = FCComboBox() + self.type_obj_combo.addItem(_("Gerber")) + self.type_obj_combo.addItem(_("Excellon")) + self.type_obj_combo.addItem(_("Geometry")) + + self.type_obj_combo.setItemIcon(0, QtGui.QIcon(self.app.resource_location + "/flatcam_icon16.png")) + self.type_obj_combo.setItemIcon(1, QtGui.QIcon(self.app.resource_location + "/drill16.png")) + self.type_obj_combo.setItemIcon(2, QtGui.QIcon(self.app.resource_location + "/geometry16.png")) + + grid0.addWidget(self.type_object_label, 3, 0) + grid0.addWidget(self.type_obj_combo, 3, 1, 1, 2) + + # Object to be used as reference + self.object_combo = FCComboBox() + self.object_combo.setModel(self.app.collection) + self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.object_combo.is_last = True + + self.object_combo.setToolTip( + _("The object used as reference.\n" + "The used point is the center of it's bounding box.") + ) + grid0.addWidget(self.object_combo, 4, 0, 1, 3) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 5, 0, 1, 3) + + # ## Rotate Title + rotate_title_label = FCLabel("%s" % self.rotateName) + grid0.addWidget(rotate_title_label, 6, 0, 1, 3) + + self.rotate_label = FCLabel('%s:' % _("Angle")) + self.rotate_label.setToolTip( + _("Angle for Rotation action, in degrees.\n" + "Float number between -360 and 359.\n" + "Positive numbers for CW motion.\n" + "Negative numbers for CCW motion.") + ) + + self.rotate_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.rotate_entry.set_precision(self.decimals) + self.rotate_entry.setSingleStep(45) + self.rotate_entry.setWrapping(True) + self.rotate_entry.set_range(-360, 360) + + # self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + + self.rotate_button = FCButton(_("Rotate")) + self.rotate_button.setToolTip( + _("Rotate the selected object(s).\n" + "The point of reference is the middle of\n" + "the bounding box for all selected objects.") + ) + self.rotate_button.setMinimumWidth(90) + + grid0.addWidget(self.rotate_label, 7, 0) + grid0.addWidget(self.rotate_entry, 7, 1) + grid0.addWidget(self.rotate_button, 7, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 8, 0, 1, 3) + + # ## Skew Title + skew_title_label = FCLabel("%s" % self.skewName) + grid0.addWidget(skew_title_label, 9, 0, 1, 2) + + self.skew_link_cb = FCCheckBox() + self.skew_link_cb.setText(_("Link")) + self.skew_link_cb.setToolTip( + _("Link the Y entry to X entry and copy its content.") + ) + + grid0.addWidget(self.skew_link_cb, 9, 2) + + self.skewx_label = FCLabel('%s:' % _("X angle")) + self.skewx_label.setToolTip( + _("Angle for Skew action, in degrees.\n" + "Float number between -360 and 360.") + ) + self.skewx_entry = FCDoubleSpinner(callback=self.confirmation_message) + # self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.skewx_entry.set_precision(self.decimals) + self.skewx_entry.set_range(-360, 360) + + self.skewx_button = FCButton(_("Skew X")) + self.skewx_button.setToolTip( + _("Skew/shear the selected object(s).\n" + "The point of reference is the middle of\n" + "the bounding box for all selected objects.")) + self.skewx_button.setMinimumWidth(90) + + grid0.addWidget(self.skewx_label, 10, 0) + grid0.addWidget(self.skewx_entry, 10, 1) + grid0.addWidget(self.skewx_button, 10, 2) + + self.skewy_label = FCLabel('%s:' % _("Y angle")) + self.skewy_label.setToolTip( + _("Angle for Skew action, in degrees.\n" + "Float number between -360 and 360.") + ) + self.skewy_entry = FCDoubleSpinner(callback=self.confirmation_message) + # self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.skewy_entry.set_precision(self.decimals) + self.skewy_entry.set_range(-360, 360) + + self.skewy_button = FCButton(_("Skew Y")) + self.skewy_button.setToolTip( + _("Skew/shear the selected object(s).\n" + "The point of reference is the middle of\n" + "the bounding box for all selected objects.")) + self.skewy_button.setMinimumWidth(90) + + grid0.addWidget(self.skewy_label, 12, 0) + grid0.addWidget(self.skewy_entry, 12, 1) + grid0.addWidget(self.skewy_button, 12, 2) + + self.ois_sk = OptionalInputSection(self.skew_link_cb, [self.skewy_label, self.skewy_entry, self.skewy_button], + logic=False) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 14, 0, 1, 3) + + # ## Scale Title + scale_title_label = FCLabel("%s" % self.scaleName) + grid0.addWidget(scale_title_label, 15, 0, 1, 2) + + self.scale_link_cb = FCCheckBox() + self.scale_link_cb.setText(_("Link")) + self.scale_link_cb.setToolTip( + _("Link the Y entry to X entry and copy its content.") + ) + + grid0.addWidget(self.scale_link_cb, 15, 2) + + self.scalex_label = FCLabel('%s:' % _("X factor")) + self.scalex_label.setToolTip( + _("Factor for scaling on X axis.") + ) + self.scalex_entry = FCDoubleSpinner(callback=self.confirmation_message) + # self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.scalex_entry.set_precision(self.decimals) + self.scalex_entry.setMinimum(-1e6) + + self.scalex_button = FCButton(_("Scale X")) + self.scalex_button.setToolTip( + _("Scale the selected object(s).\n" + "The point of reference depends on \n" + "the Scale reference checkbox state.")) + self.scalex_button.setMinimumWidth(90) + + grid0.addWidget(self.scalex_label, 17, 0) + grid0.addWidget(self.scalex_entry, 17, 1) + grid0.addWidget(self.scalex_button, 17, 2) + + self.scaley_label = FCLabel('%s:' % _("Y factor")) + self.scaley_label.setToolTip( + _("Factor for scaling on Y axis.") + ) + self.scaley_entry = FCDoubleSpinner(callback=self.confirmation_message) + # self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.scaley_entry.set_precision(self.decimals) + self.scaley_entry.setMinimum(-1e6) + + self.scaley_button = FCButton(_("Scale Y")) + self.scaley_button.setToolTip( + _("Scale the selected object(s).\n" + "The point of reference depends on \n" + "the Scale reference checkbox state.")) + self.scaley_button.setMinimumWidth(90) + + grid0.addWidget(self.scaley_label, 19, 0) + grid0.addWidget(self.scaley_entry, 19, 1) + grid0.addWidget(self.scaley_button, 19, 2) + + self.ois_s = OptionalInputSection(self.scale_link_cb, + [ + self.scaley_label, + self.scaley_entry, + self.scaley_button + ], logic=False) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 21, 0, 1, 3) + + # ## Flip Title + flip_title_label = FCLabel("%s" % self.flipName) + grid0.addWidget(flip_title_label, 23, 0, 1, 3) + + self.flipx_button = FCButton(_("Flip on X")) + self.flipx_button.setToolTip( + _("Flip the selected object(s) over the X axis.") + ) + + self.flipy_button = FCButton(_("Flip on Y")) + self.flipy_button.setToolTip( + _("Flip the selected object(s) over the X axis.") + ) + + hlay0 = QtWidgets.QHBoxLayout() + grid0.addLayout(hlay0, 25, 0, 1, 3) + + hlay0.addWidget(self.flipx_button) + hlay0.addWidget(self.flipy_button) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 27, 0, 1, 3) + + # ## Offset Title + offset_title_label = FCLabel("%s" % self.offsetName) + grid0.addWidget(offset_title_label, 29, 0, 1, 3) + + self.offx_label = FCLabel('%s:' % _("X val")) + self.offx_label.setToolTip( + _("Distance to offset on X axis. In current units.") + ) + self.offx_entry = FCDoubleSpinner(callback=self.confirmation_message) + # self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.offx_entry.set_precision(self.decimals) + self.offx_entry.setMinimum(-1e6) + + self.offx_button = FCButton(_("Offset X")) + self.offx_button.setToolTip( + _("Offset the selected object(s).\n" + "The point of reference is the middle of\n" + "the bounding box for all selected objects.\n")) + self.offx_button.setMinimumWidth(90) + + grid0.addWidget(self.offx_label, 31, 0) + grid0.addWidget(self.offx_entry, 31, 1) + grid0.addWidget(self.offx_button, 31, 2) + + self.offy_label = FCLabel('%s:' % _("Y val")) + self.offy_label.setToolTip( + _("Distance to offset on Y axis. In current units.") + ) + self.offy_entry = FCDoubleSpinner(callback=self.confirmation_message) + # self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + self.offy_entry.set_precision(self.decimals) + self.offy_entry.setMinimum(-1e6) + + self.offy_button = FCButton(_("Offset Y")) + self.offy_button.setToolTip( + _("Offset the selected object(s).\n" + "The point of reference is the middle of\n" + "the bounding box for all selected objects.\n")) + self.offy_button.setMinimumWidth(90) + + grid0.addWidget(self.offy_label, 32, 0) + grid0.addWidget(self.offy_entry, 32, 1) + grid0.addWidget(self.offy_button, 32, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Sunken) + grid0.addWidget(separator_line, 34, 0, 1, 3) + + # ## Buffer Title + buffer_title_label = FCLabel("%s" % self.bufferName) + grid0.addWidget(buffer_title_label, 35, 0, 1, 2) + + self.buffer_rounded_cb = FCCheckBox('%s' % _("Rounded")) + self.buffer_rounded_cb.setToolTip( + _("If checked then the buffer will surround the buffered shape,\n" + "every corner will be rounded.\n" + "If not checked then the buffer will follow the exact geometry\n" + "of the buffered shape.") + ) + + grid0.addWidget(self.buffer_rounded_cb, 35, 2) + + self.buffer_label = FCLabel('%s:' % _("Distance")) + self.buffer_label.setToolTip( + _("A positive value will create the effect of dilation,\n" + "while a negative value will create the effect of erosion.\n" + "Each geometry element of the object will be increased\n" + "or decreased with the 'distance'.") + ) + + self.buffer_entry = FCDoubleSpinner(callback=self.confirmation_message) + self.buffer_entry.set_precision(self.decimals) + self.buffer_entry.setSingleStep(0.1) + self.buffer_entry.setWrapping(True) + self.buffer_entry.set_range(-9999.9999, 9999.9999) + + self.buffer_button = FCButton(_("Buffer D")) + self.buffer_button.setToolTip( + _("Create the buffer effect on each geometry,\n" + "element from the selected object, using the distance.") + ) + self.buffer_button.setMinimumWidth(90) + + grid0.addWidget(self.buffer_label, 37, 0) + grid0.addWidget(self.buffer_entry, 37, 1) + grid0.addWidget(self.buffer_button, 37, 2) + + self.buffer_factor_label = FCLabel('%s:' % _("Value")) + self.buffer_factor_label.setToolTip( + _("A positive value will create the effect of dilation,\n" + "while a negative value will create the effect of erosion.\n" + "Each geometry element of the object will be increased\n" + "or decreased to fit the 'Value'. Value is a percentage\n" + "of the initial dimension.") + ) + + self.buffer_factor_entry = FCDoubleSpinner(callback=self.confirmation_message, suffix='%') + self.buffer_factor_entry.set_range(-100.0000, 1000.0000) + self.buffer_factor_entry.set_precision(self.decimals) + self.buffer_factor_entry.setWrapping(True) + self.buffer_factor_entry.setSingleStep(1) + + self.buffer_factor_button = FCButton(_("Buffer F")) + self.buffer_factor_button.setToolTip( + _("Create the buffer effect on each geometry,\n" + "element from the selected object, using the factor.") + ) + self.buffer_factor_button.setMinimumWidth(90) + + grid0.addWidget(self.buffer_factor_label, 38, 0) + grid0.addWidget(self.buffer_factor_entry, 38, 1) + grid0.addWidget(self.buffer_factor_button, 38, 2) + + grid0.addWidget(FCLabel(''), 42, 0, 1, 3) + + self.layout.addStretch() + + # ## Reset Tool + self.reset_button = FCButton(_("Reset Tool")) + self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png')) + self.reset_button.setToolTip( + _("Will reset the tool parameters.") + ) + self.reset_button.setStyleSheet(""" + QPushButton + { + font-weight: bold; + } + """) + self.layout.addWidget(self.reset_button) + + # #################################### FINSIHED GUI ########################### + # ############################################################################# + + def on_reference_changed(self, index): + if index == 0 or index == 1: # "Origin" or "Selection" reference + self.point_label.hide() + self.point_entry.hide() + self.point_button.hide() + + self.type_object_label.hide() + self.type_obj_combo.hide() + self.object_combo.hide() + elif index == 2: # "Point" reference + self.point_label.show() + self.point_entry.show() + self.point_button.show() + + self.type_object_label.hide() + self.type_obj_combo.hide() + self.object_combo.hide() + else: # "Object" reference + self.point_label.hide() + self.point_entry.hide() + self.point_button.hide() + + self.type_object_label.show() + self.type_obj_combo.show() + self.object_combo.show() + + def confirmation_message(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"), + self.decimals, + minval, + self.decimals, + maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def confirmation_message_int(self, accepted, minval, maxval): + if accepted is False: + self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' % + (_("Edited value is out of range"), minval, maxval), False) + else: + self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)