From c1a5320315e1a53d66fb68d6ba6b5013c9efb40f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 16 May 2022 18:13:12 +0300 Subject: [PATCH 01/55] - started to lay ground for upgrading the UI in the Gerber Editor - fixed and upgraded the Buffer sub-tool in the Gerber Editor - fixed adding same aperture multiple times --- CHANGELOG.md | 3 + appEditors/AppGerberEditor.py | 487 ++------ appEditors/appGCodeEditor.py | 2 + appEditors/grb_plugins/GrbBufferPlugin.py | 249 ++++ appEditors/grb_plugins/GrbCommon.py | 165 +++ appEditors/grb_plugins/GrbCopyPlugin.py | 538 +++++++++ appEditors/grb_plugins/GrbPadArrayPlugin.py | 0 appEditors/grb_plugins/GrbPadPlugin.py | 0 .../grb_plugins/GrbSimplificationPlugin.py | 260 +++++ appEditors/grb_plugins/GrbTracePlugin.py | 149 +++ .../grb_plugins/GrbTransformationPlugin.py | 1026 +++++++++++++++++ appEditors/grb_plugins/__init__.py | 0 appGUI/MainGUI.py | 8 + 13 files changed, 2517 insertions(+), 370 deletions(-) create mode 100644 appEditors/grb_plugins/GrbBufferPlugin.py create mode 100644 appEditors/grb_plugins/GrbCommon.py create mode 100644 appEditors/grb_plugins/GrbCopyPlugin.py create mode 100644 appEditors/grb_plugins/GrbPadArrayPlugin.py create mode 100644 appEditors/grb_plugins/GrbPadPlugin.py create mode 100644 appEditors/grb_plugins/GrbSimplificationPlugin.py create mode 100644 appEditors/grb_plugins/GrbTracePlugin.py create mode 100644 appEditors/grb_plugins/GrbTransformationPlugin.py create mode 100644 appEditors/grb_plugins/__init__.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 343f2367..1552680f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ CHANGELOG for FlatCAM Evo beta - finished upgrading the UI in Geometry Editor sub-tools - fixed an issue that left some parts of the Geometry Editor UI linked to the `Move` context menu action +- started to lay ground for upgrading the UI in the Gerber Editor +- fixed and upgraded the Buffer sub-tool in the Gerber Editor +- fixed adding same aperture multiple times 15.05.2022 diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 52b9a832..b6f46142 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -5,28 +5,18 @@ # MIT Licence # # ########################################################## -from PyQt6 import QtGui, QtCore, QtWidgets -from PyQt6.QtCore import Qt - -from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Polygon, MultiPolygon, box -from shapely.ops import unary_union -import shapely.affinity as affinity - -from vispy.geometry import Rect - -from copy import copy, deepcopy -import logging +from appEditors.grb_plugins.GrbCommon import * from camlib import distance, arc, three_point_circle, flatten_shapely_geometry -from appGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, EvalEntry2, \ - FCInputDoubleSpinner, FCButton, OptionalInputSection, FCCheckBox, NumericalEvalTupleEntry, FCLabel, FCTextEdit, \ - VerticalScrollArea, GLay -from appTool import AppTool +from appGUI.GUIElements import * -import numpy as np -from numpy.linalg import norm as numpy_norm -import math -import inspect +from appTool import AppTool +from appEditors.grb_plugins.GrbBufferPlugin import BufferEditorTool +from appEditors.grb_plugins.GrbTransformationPlugin import TransformEditorTool +from appEditors.grb_plugins.GrbSimplificationPlugin import SimplificationTool +from appEditors.grb_plugins.GrbCopyPlugin import CopyEditorTool + +# import inspect # from vispy.io import read_png # import pngcanvas @@ -39,153 +29,6 @@ fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext -log = logging.getLogger('base') - - -class DrawToolShape(object): - """ - Encapsulates "shapes" under a common class. - """ - - tolerance = None - - @staticmethod - def get_pts(o): - """ - Returns a list of all points in the object, where - the object can be a Polygon, Not a polygon, or a list - of such. Search is done recursively. - - :param o: geometric object - :return: List of points - :rtype: list - """ - pts = [] - - # ## Iterable: descend into each item. - try: - for sub_o in o: - pts += DrawToolShape.get_pts(sub_o) - # Non-iterable - except TypeError: - if o is None: - return - - # DrawToolShape: descend into .geo. - if isinstance(o, DrawToolShape): - pts += DrawToolShape.get_pts(o.geo) - # ## Descend into .exerior and .interiors - elif type(o) == Polygon: - pts += DrawToolShape.get_pts(o.exterior) - for i in o.interiors: - pts += DrawToolShape.get_pts(i) - elif type(o) == MultiLineString: - for line in o: - pts += DrawToolShape.get_pts(line) - # ## Has .coords: list them. - else: - if DrawToolShape.tolerance is not None: - pts += list(o.simplify(DrawToolShape.tolerance).coords) - else: - pts += list(o.coords) - return pts - - def __init__(self, geo=None): - - # Shapely type or list of such - self.geo = geo - self.utility = False - - -class DrawToolUtilityShape(DrawToolShape): - """ - Utility shapes are temporary geometry in the editor - to assist in the creation of shapes. For example it - will show the outline of a rectangle from the first - point to the current mouse pointer before the second - point is clicked and the final geometry is created. - """ - - def __init__(self, geo=None): - super(DrawToolUtilityShape, self).__init__(geo=geo) - self.utility = True - - -class DrawTool(object): - """ - Abstract Class representing a tool in the drawing - program. Can generate geometry, including temporary - utility geometry that is updated on user clicks - and mouse motion. - """ - - def __init__(self, draw_app): - self.draw_app = draw_app - self.complete = False - self.points = [] - self.geometry = None # DrawToolShape or None - - def click(self, point): - """ - :param point: [x, y] Coordinate pair. - """ - return "" - - def click_release(self, point): - """ - :param point: [x, y] Coordinate pair. - """ - return "" - - def on_key(self, key): - # Jump to coords - if key == QtCore.Qt.Key.Key_J or key == 'J': - self.draw_app.app.on_jump_to() - - def utility_geometry(self, data=None): - return None - - @staticmethod - def bounds(obj): - def bounds_rec(o): - if type(o) is list: - minx = np.Inf - miny = np.Inf - maxx = -np.Inf - maxy = -np.Inf - - for k in o: - try: - minx_, miny_, maxx_, maxy_ = bounds_rec(k) - except Exception as e: - log.error("camlib.Gerber.bounds() --> %s" % str(e)) - return - - minx = min(minx, minx_) - miny = min(miny, miny_) - maxx = max(maxx, maxx_) - maxy = max(maxy, maxy_) - return minx, miny, maxx, maxy - else: - # it's a Shapely object, return it's bounds - if 'solid' in o.geo: - return o.geo['solid'].bounds - - return bounds_rec(obj) - - -class ShapeToolEditorGrb(DrawTool): - """ - Abstract class for tools that create a shape. - """ - - def __init__(self, draw_app): - DrawTool.__init__(self, draw_app) - self.name = None - - def make(self): - pass - class PadEditorGrb(ShapeToolEditorGrb): """ @@ -2077,13 +1920,57 @@ class BufferEditorGrb(ShapeToolEditorGrb): self.draw_app.app.inform.emit(_("Buffer the selected apertures ...")) self.origin = (0, 0) + self.buff_tool = BufferEditorTool(self.app, self.draw_app) + self.buff_tool.run() + self.app.ui.notebook.setTabText(2, _("Buffer")) + if self.draw_app.app.ui.splitter.sizes()[0] == 0: + self.draw_app.app.ui.splitter.setSizes([1, 1]) + self.activate() + + def activate(self): + try: + self.buff_tool.ui.buffer_button.clicked.disconnect() + except (TypeError, AttributeError): + pass + self.buff_tool.ui.buffer_button.clicked.connect(self.on_buffer_clicked) + + def on_buffer_clicked(self): + self.buff_tool.on_buffer() + self.deactivate() + + def deactivate(self): + try: + self.buff_tool.ui.buffer_button.clicked.disconnect() + except (TypeError, AttributeError): + pass + self.complete = True + self.draw_app.select_tool("select") + self.draw_app.hide_tool(self.name) + + def clean_up(self): + self.draw_app.selected = [] + self.draw_app.ui.apertures_table.clearSelection() + self.draw_app.plot_all() + + +class SimplifyEditorGrb(ShapeToolEditorGrb): + def __init__(self, draw_app): + ShapeToolEditorGrb.__init__(self, draw_app) + self.name = 'simplify' + + # self.shape_buffer = self.draw_app.shape_buffer + self.draw_app = draw_app + self.app = draw_app.app + + self.draw_app.app.inform.emit(_("Buffer the selected apertures ...")) + self.origin = (0, 0) + if self.draw_app.app.ui.splitter.sizes()[0] == 0: self.draw_app.app.ui.splitter.setSizes([1, 1]) self.activate_buffer() def activate_buffer(self): self.draw_app.hide_tool('all') - self.draw_app.ui.buffer_tool_frame.show() try: self.draw_app.ui.buffer_button.clicked.disconnect() @@ -2593,6 +2480,11 @@ class SelectEditorGrb(QtCore.QObject, DrawTool): self.storage = self.draw_app.storage_dict # self.selected = self.draw_app.selected + try: + QtGui.QGuiApplication.restoreOverrideCursor() + except Exception as e: + log.error("AppGerberEditor.SelectEditorGrb --> %s" % str(e)) + # here we store all shapes that were selected self.sel_storage = [] @@ -2611,15 +2503,6 @@ class SelectEditorGrb(QtCore.QObject, DrawTool): except Exception as e: log.error("FlatCAMGerbEditor.SelectEditorGrb.__init__() --> %s" % str(e)) - self.draw_app.hide_tool('all') - self.draw_app.hide_tool('select') - self.draw_app.ui.array_frame.hide() - - try: - QtGui.QGuiApplication.restoreOverrideCursor() - except Exception as e: - log.error("AppGerberEditor.SelectEditorGrb --> %s" % str(e)) - try: self.selection_triggered.disconnect() except (TypeError, AttributeError): @@ -2638,6 +2521,17 @@ class SelectEditorGrb(QtCore.QObject, DrawTool): if self.draw_app.visible is False: self.draw_app.visible = True + # make sure that the cursor text from the FCPath is deleted + if self.draw_app.app.plotcanvas.text_cursor.parent and self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + + # make sure that the Tools tab is removed + try: + self.draw_app.app.ui.notebook.removeTab(2) + except Exception: + pass + self.complete = True def set_origin(self, origin): @@ -3065,11 +2959,12 @@ class ImportEditorGrb(QtCore.QObject, DrawTool): solid_geo = geo_el['solid'] if Point(pos).within(solid_geo): if solid_geo not in self.get_selected_geos(): + o_color = self.draw_app.get_sel_color() + 'AF' + f_color = self.draw_app.get_sel_color() + 'AF' shape_id = self.app.tool_shapes.add(tolerance=obj.drawing_tolerance, layer=0, shape=solid_geo, - color=self.draw_app.get_sel_color() + 'AF', - face_color=self.draw_app.get_sel_color() + - 'AF', + color=o_color, + face_color=f_color, visible=True) new_ap_dict = {k: v for k, v in obj.tools[apid].items() if k != 'geometry'} new_ap_dict['geometry'] = [DrawToolShape(geo_el)] @@ -3451,7 +3346,8 @@ class AppGerberEditor(QtCore.QObject): self.app.ui.grb_convert_poly_menuitem.triggered.connect(self.on_poligonize) self.app.ui.grb_add_semidisc_menuitem.triggered.connect(self.on_add_semidisc) self.app.ui.grb_add_disc_menuitem.triggered.connect(self.on_disc_add) - self.app.ui.grb_add_buffer_menuitem.triggered.connect(self.on_buffer) + self.app.ui.grb_add_buffer_menuitem.triggered.connect(lambda: self.select_tool('buffer')) + self.app.ui.grb_add_buffer_menuitem.triggered.connect(self.on_simplification) self.app.ui.grb_add_scale_menuitem.triggered.connect(self.on_scale) self.app.ui.grb_add_eraser_menuitem.triggered.connect(self.on_eraser) self.app.ui.grb_add_markarea_menuitem.triggered.connect(self.on_markarea) @@ -3463,7 +3359,6 @@ class AppGerberEditor(QtCore.QObject): self.app.ui.grb_move_menuitem.triggered.connect(self.on_move_button) - self.ui.buffer_button.clicked.connect(self.on_buffer) self.ui.scale_button.clicked.connect(self.on_scale) self.app.ui.aperture_delete_btn.triggered.connect(self.on_delete_btn) @@ -3472,7 +3367,6 @@ class AppGerberEditor(QtCore.QObject): self.ui.aptype_cb.currentIndexChanged.connect(self.on_aptype_changed) self.ui.addaperture_btn.clicked.connect(lambda: self.on_aperture_add()) - self.ui.apsize_entry.returnPressed.connect(lambda: self.on_aperture_add()) self.ui.delaperture_btn.clicked.connect(lambda: self.on_aperture_delete()) self.ui.apertures_table.cellPressed.connect(self.on_row_selected) @@ -3524,6 +3418,7 @@ class AppGerberEditor(QtCore.QObject): "semidisc": {"button": self.app.ui.grb_add_semidisc_btn, "constructor": DiscSemiEditorGrb}, "disc": {"button": self.app.ui.grb_add_disc_btn, "constructor": DiscEditorGrb}, "buffer": {"button": self.app.ui.aperture_buffer_btn, "constructor": BufferEditorGrb}, + "simplify": {"button": self.app.ui.aperture_simplify_btn, "constructor": SimplifyEditorGrb}, "scale": {"button": self.app.ui.aperture_scale_btn, "constructor": ScaleEditorGrb}, "markarea": {"button": self.app.ui.aperture_markarea_btn, "constructor": MarkEditorGrb}, "import": {"button": self.app.ui.grb_import_btn, "constructor": ImportEditorGrb}, @@ -3567,7 +3462,6 @@ class AppGerberEditor(QtCore.QObject): # ############################################################################################################# # Init appGUI # ############################################################################################################# - self.ui.buffer_distance_entry.set_value(self.app.options["gerber_editor_buff_f"]) self.ui.scale_factor_entry.set_value(self.app.options["gerber_editor_scale_f"]) self.ui.ma_upper_threshold_entry.set_value(self.app.options["gerber_editor_ma_high"]) self.ui.ma_lower_threshold_entry.set_value(self.app.options["gerber_editor_ma_low"]) @@ -3624,11 +3518,7 @@ class AppGerberEditor(QtCore.QObject): self.apertures_row = 0 # aper_no = self.apertures_row + 1 - sort = [] - for k, v in list(self.storage_dict.items()): - sort.append(int(k)) - - sorted_apertures = sorted(sort) + sorted_apertures = sorted([int(k) for k in list(self.storage_dict.keys())]) # sort = [] # for k, v in list(self.gerber_obj.aperture_macros.items()): @@ -3741,7 +3631,7 @@ class AppGerberEditor(QtCore.QObject): # self.ui.apertures_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.ui.apertures_table.setSortingEnabled(False) - # self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight()) + self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight()) self.ui.apertures_table.setMaximumHeight(self.ui.apertures_table.getHeight()) # make sure no rows are selected so the user have to click the correct row, meaning selecting the correct tool @@ -3770,28 +3660,13 @@ class AppGerberEditor(QtCore.QObject): if apcode is not None: ap_code = apcode else: - try: - ap_code = int(self.ui.apcode_entry.get_value()) - except ValueError: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("Aperture code value is missing or wrong format. Add it and retry.")) - return + ap_code = int(self.ui.apcode_entry.get_value()) if ap_code == '' or ap_code is None: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Aperture code value is missing or wrong format. Add it and retry.")) return - try: - size_val = float(self.ui.apsize_entry.get_value()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - size_val = float(self.ui.apsize_entry.get_value().replace(',', '.')) - self.ui.apsize_entry.set_value(size_val) - except ValueError: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("Aperture size value is missing or wrong format. Add it and retry.")) - return + size_val = float(self.ui.apsize_entry.get_value()) if size_val == 0.0: ap_code = 0 @@ -5408,8 +5283,9 @@ class AppGerberEditor(QtCore.QObject): self.selected.append(obj) sel_aperture.add(storage) else: - self.selected.append(obj) - sel_aperture.add(storage) + if obj not in self.selected: + self.selected.append(obj) + sel_aperture.add(storage) # ############################################################################################################# # ########## select the aperture code of the selected geometry, in the tool table ########################### @@ -5629,6 +5505,7 @@ class AppGerberEditor(QtCore.QObject): sel_draw_color = self.get_sel_color() + 'FF' else: sel_draw_color = self.get_sel_color()[:-2] + 'FF' + if len(self.get_draw_color()) == 7: draw_color = self.get_draw_color() + 'FF' else: @@ -5935,65 +5812,8 @@ class AppGerberEditor(QtCore.QObject): def on_add_semidisc(self): self.select_tool('semidisc') - def on_buffer(self): - buff_value = 0.01 - self.app.log.debug("AppGerberEditor.on_buffer()") - - try: - buff_value = float(self.ui.buffer_distance_entry.get_value()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - buff_value = float(self.ui.buffer_distance_entry.get_value().replace(',', '.')) - self.ui.buffer_distance_entry.set_value(buff_value) - except ValueError: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("Buffer distance value is missing or wrong format. Add it and retry.")) - return - - # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment - # I populated the combobox such that the index coincide with the join styles value (which is really an INT) - join_style = self.ui.buffer_corner_cb.currentIndex() + 1 - - def buffer_recursion(geom_el, selection): - if type(geom_el) == list: - geoms = [] - for local_geom in geom_el: - geoms.append(buffer_recursion(local_geom, selection=selection)) - return geoms - else: - if geom_el in selection: - geometric_data = geom_el.geo - buffered_geom_el = {} - if 'solid' in geometric_data: - buffered_geom_el['solid'] = geometric_data['solid'].buffer(buff_value, join_style=join_style) - if 'follow' in geometric_data: - buffered_geom_el['follow'] = geometric_data['follow'].buffer(buff_value, join_style=join_style) - if 'clear' in geometric_data: - buffered_geom_el['clear'] = geometric_data['clear'].buffer(buff_value, join_style=join_style) - return DrawToolShape(buffered_geom_el) - else: - return geom_el - - if not self.ui.apertures_table.selectedItems(): - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("No aperture to buffer. Select at least one aperture and try again.")) - return - - for x in self.ui.apertures_table.selectedItems(): - try: - apcode = self.ui.apertures_table.item(x.row(), 1).text() - - temp_storage = deepcopy(buffer_recursion(self.storage_dict[apcode]['geometry'], self.selected)) - self.storage_dict[apcode]['geometry'] = [] - self.storage_dict[apcode]['geometry'] = temp_storage - except Exception as e: - self.app.log.error("AppGerberEditor.buffer() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s\n%s' % (_("Failed."), str(traceback.print_exc()))) - return - - self.plot_all() - self.app.inform.emit('[success] %s' % _("Done.")) + def on_simplification(self): + pass def on_scale(self): scale_factor = 1.0 @@ -6120,8 +5940,6 @@ class AppGerberEditor(QtCore.QObject): self.ui.apertures_frame.hide() if tool_name == 'select': self.ui.apertures_frame.show() - if tool_name == 'buffer' or tool_name == 'all': - self.ui.buffer_tool_frame.hide() if tool_name == 'scale' or tool_name == 'all': self.ui.scale_tool_frame.hide() if tool_name == 'markarea' or tool_name == 'all': @@ -6180,6 +5998,7 @@ class AppGerberEditorUI: name_label = FCLabel(_("Name:")) self.name_box.addWidget(name_label) self.name_entry = FCEntry() + self.name_entry.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.name_box.addWidget(self.name_entry) # Box for custom widgets @@ -6190,17 +6009,24 @@ class AppGerberEditorUI: # ############################################################################################################# # #################################### Gerber Apertures Table ################################################# # ############################################################################################################# - self.apertures_table_label = FCLabel('%s:' % _('Apertures'), bold=True) + self.apertures_table_label = FCLabel('%s' % _('Apertures'), bold=True, color='orange') self.apertures_table_label.setToolTip( _("Apertures Table for the Gerber Object.") ) self.custom_box.addWidget(self.apertures_table_label) + tw_frame = FCFrame() + self.custom_box.addWidget(tw_frame) + + # Grid Layout + ap_grid = GLay(v_spacing=5, h_spacing=3) + tw_frame.setLayout(ap_grid) + self.apertures_table = FCTable() # delegate = SpinBoxDelegate(units=self.units) # self.apertures_table.setItemDelegateForColumn(1, delegate) - self.custom_box.addWidget(self.apertures_table) + ap_grid.addWidget(self.apertures_table, 0, 0, 1, 2) self.apertures_table.setColumnCount(5) self.apertures_table.setHorizontalHeaderLabels(['#', _('Code'), _('Type'), _('Size'), _('Dim')]) @@ -6220,16 +6046,12 @@ class AppGerberEditorUI: " - (width, height) for R, O type.\n" " - (dia, nVertices) for P type")) - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.custom_box.addWidget(separator_line) - # add a frame and inside add a vertical box layout. Inside this vbox layout I add all the Apertures widgets # this way I can hide/show the frame self.apertures_frame = QtWidgets.QFrame() self.apertures_frame.setContentsMargins(0, 0, 0, 0) - self.custom_box.addWidget(self.apertures_frame) + ap_grid.addWidget(self.apertures_frame, 2, 0, 1, 2) + self.apertures_box = QtWidgets.QVBoxLayout() self.apertures_box.setContentsMargins(0, 0, 0, 0) self.apertures_frame.setLayout(self.apertures_box) @@ -6302,23 +6124,18 @@ class AppGerberEditorUI: grid1.addWidget(self.apdim_lbl, 4, 0) grid1.addWidget(self.apdim_entry, 4, 1) - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - grid1.addWidget(separator_line, 5, 0, 1, 3) - # Aperture Buttons vlay_buttons = QtWidgets.QVBoxLayout() grid1.addLayout(vlay_buttons, 1, 2, 4, 1) - self.addaperture_btn = FCButton(_('Add')) + self.addaperture_btn = QtWidgets.QToolButton() self.addaperture_btn.setSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) self.addaperture_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png')) self.addaperture_btn.setToolTip( _("Add a new aperture to the aperture list.") ) - self.delaperture_btn = FCButton(_('Delete')) + self.delaperture_btn = QtWidgets.QToolButton() # self.delaperture_btn.setSizePolicy( # QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding) @@ -6329,32 +6146,25 @@ class AppGerberEditorUI: vlay_buttons.addWidget(self.addaperture_btn) vlay_buttons.addWidget(self.delaperture_btn) + # Zoom Selection + self.geo_zoom = FCCheckBox(_("Zoom on selection")) + ap_grid.addWidget(self.geo_zoom, 4, 0, 1, 2) + # ############################################################################################################# # ############################################ Shape Properties ############################################### # ############################################################################################################# - self.shape_frame = QtWidgets.QFrame() - self.shape_frame.setContentsMargins(0, 0, 0, 0) + self.shape_frame = FCFrame() self.custom_box.addWidget(self.shape_frame) self.shape_grid = GLay(v_spacing=5, h_spacing=3) - self.shape_grid.setContentsMargins(0, 0, 0, 0) self.shape_frame.setLayout(self.shape_grid) - # Zoom Selection - self.geo_zoom = FCCheckBox(_("Zoom on selection")) - self.shape_grid.addWidget(self.geo_zoom, 0, 0, 1, 3) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.shape_grid.addWidget(separator_line, 2, 0, 1, 3) - # Parameters Title - param_title = FCLabel('%s' % _("Parameters"), bold=True) + param_title = FCLabel('%s' % _("Parameters"), bold=True, color='blue') param_title.setToolTip( _("Geometry parameters.") ) - self.shape_grid.addWidget(param_title, 4, 0, 1, 3) + self.shape_grid.addWidget(param_title, 0, 0, 1, 3) p_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 0, 0, 1, 0]) @@ -6381,20 +6191,20 @@ class AppGerberEditorUI: p_grid.addWidget(self.area_entry, 0, 3) p_grid.addWidget(area_units_lbl, 0, 4) - self.shape_grid.addLayout(p_grid, 5, 0, 1, 3) + self.shape_grid.addLayout(p_grid, 2, 0, 1, 3) # Coordinates coords_lbl = FCLabel('%s:' % _("Coordinates")) coords_lbl.setToolTip( _("The coordinates of the selected geometry element.") ) - self.shape_grid.addWidget(coords_lbl, 6, 0, 1, 3) + self.shape_grid.addWidget(coords_lbl, 4, 0, 1, 3) self.geo_coords_entry = FCTextEdit() self.geo_coords_entry.setPlaceholderText( _("The coordinates of the selected geometry element.") ) - self.shape_grid.addWidget(self.geo_coords_entry, 8, 0, 1, 3) + self.shape_grid.addWidget(self.geo_coords_entry, 6, 0, 1, 3) # Vertex Points Number vertex_lbl = FCLabel('%s:' % _("Vertex Points")) @@ -6404,20 +6214,15 @@ class AppGerberEditorUI: self.geo_vertex_entry = FCEntry(decimals=self.decimals) self.geo_vertex_entry.setReadOnly(True) - self.shape_grid.addWidget(vertex_lbl, 10, 0) - self.shape_grid.addWidget(self.geo_vertex_entry, 10, 1, 1, 2) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.shape_grid.addWidget(separator_line, 12, 0, 1, 3) + self.shape_grid.addWidget(vertex_lbl, 8, 0) + self.shape_grid.addWidget(self.geo_vertex_entry, 8, 1, 1, 2) # Simplification Title simplif_lbl = FCLabel('%s' % _("Simplification"), bold=True) simplif_lbl.setToolTip( _("Simplify a geometry by reducing its vertex points number.") ) - self.shape_grid.addWidget(simplif_lbl, 14, 0, 1, 3) + self.custom_box.addWidget(simplif_lbl) # Simplification Tolerance simplification_tol_lbl = FCLabel('%s:' % _("Tolerance")) @@ -6431,8 +6236,8 @@ class AppGerberEditorUI: self.geo_tol_entry.set_range(0.0000, 10000.0000) self.geo_tol_entry.set_value(10 ** -self.decimals) - self.shape_grid.addWidget(simplification_tol_lbl, 16, 0) - self.shape_grid.addWidget(self.geo_tol_entry, 16, 1, 1, 2) + self.custom_box.addWidget(simplification_tol_lbl) + self.custom_box.addWidget(self.geo_tol_entry) # Simplification button self.simplification_btn = FCButton(_("Simplify"), bold=True) @@ -6441,65 +6246,7 @@ class AppGerberEditorUI: _("Simplify a geometry element by reducing its vertex points number.") ) - self.shape_grid.addWidget(self.simplification_btn, 18, 0, 1, 3) - - # ############################################################################################################# - # ############################################ BUFFER TOOL #################################################### - # ############################################################################################################# - self.buffer_tool_frame = QtWidgets.QFrame() - self.buffer_tool_frame.setContentsMargins(0, 0, 0, 0) - self.custom_box.addWidget(self.buffer_tool_frame) - self.buffer_tools_box = QtWidgets.QVBoxLayout() - self.buffer_tools_box.setContentsMargins(0, 0, 0, 0) - self.buffer_tool_frame.setLayout(self.buffer_tools_box) - self.buffer_tool_frame.hide() - - # Title - buf_title_lbl = FCLabel('%s:' % _('Buffer Aperture'), bold=True) - buf_title_lbl.setToolTip( - _("Buffer a aperture in the aperture list") - ) - self.buffer_tools_box.addWidget(buf_title_lbl) - - # Grid Layout - buff_grid = GLay(v_spacing=5, h_spacing=3) - self.buffer_tools_box.addLayout(buff_grid) - - # Buffer distance - self.buffer_distance_entry = FCDoubleSpinner() - self.buffer_distance_entry.set_precision(self.decimals) - self.buffer_distance_entry.set_range(-10000.0000, 10000.0000) - - buff_grid.addWidget(FCLabel('%s:' % _("Buffer distance")), 0, 0) - buff_grid.addWidget(self.buffer_distance_entry, 0, 1) - - # Buffer Corner - self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner")) - self.buffer_corner_lbl.setToolTip( - _("There are 3 types of corners:\n" - " - 'Round': the corner is rounded.\n" - " - 'Square': the corner is met in a sharp angle.\n" - " - 'Beveled': the corner is a line that directly connects the features meeting in the corner") - ) - self.buffer_corner_cb = FCComboBox() - self.buffer_corner_cb.addItem(_("Round")) - self.buffer_corner_cb.addItem(_("Square")) - self.buffer_corner_cb.addItem(_("Beveled")) - buff_grid.addWidget(self.buffer_corner_lbl, 2, 0) - buff_grid.addWidget(self.buffer_corner_cb, 2, 1) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - buff_grid.addWidget(separator_line, 4, 0, 1, 2) - - # Buttons - hlay_buf = QtWidgets.QHBoxLayout() - self.buffer_tools_box.addLayout(hlay_buf) - - self.buffer_button = FCButton(_("Buffer")) - self.buffer_button.setIcon(QtGui.QIcon(self.app.resource_location + '/buffer16-2.png')) - hlay_buf.addWidget(self.buffer_button) + self.custom_box.addWidget(self.simplification_btn) # ############################################################################################################# # ########################################### SCALE TOOL ###################################################### diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py index bd5c651f..f55a08ba 100644 --- a/appEditors/appGCodeEditor.py +++ b/appEditors/appGCodeEditor.py @@ -9,6 +9,7 @@ from appEditors.AppTextEditor import AppTextEditor from appObjects.CNCJobObject import CNCJobObject from appGUI.GUIElements import FCTextArea, FCEntry, FCButton, FCTable, GLay, FCLabel from PyQt6 import QtWidgets, QtCore, QtGui +from PyQt6.QtCore import Qt # from io import StringIO @@ -794,6 +795,7 @@ class AppGCodeEditorUI: name_label = FCLabel(_("Name:")) self.name_box.addWidget(name_label) self.name_entry = FCEntry() + self.name_entry.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.name_box.addWidget(self.name_entry) separator_line = QtWidgets.QFrame() diff --git a/appEditors/grb_plugins/GrbBufferPlugin.py b/appEditors/grb_plugins/GrbBufferPlugin.py new file mode 100644 index 00000000..64026d24 --- /dev/null +++ b/appEditors/grb_plugins/GrbBufferPlugin.py @@ -0,0 +1,249 @@ + +from appTool import * +from appEditors.grb_plugins.GrbCommon import DrawToolShape + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class BufferEditorTool(AppToolEditor): + """ + Simple input for buffer distance. + """ + + def __init__(self, app, draw_app): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + + self.ui = BufferEditorUI(layout=self.layout, buffer_class=self) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + pass + + def run(self): + self.app.defaults.report_usage("Geo Editor ToolBuffer()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, _("Buffer")) + + def set_tool_ui(self): + # Init appGUI + self.ui.buffer_distance_entry.set_value(self.draw_app.app.options['gerber_editor_buff_f']) + + def on_tab_close(self): + self.draw_app.select_tool("select") + self.app.ui.notebook.callback_on_close = lambda: None + + def on_buffer(self): + try: + buffer_distance = float(self.ui.buffer_distance_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + buffer_distance = float(self.ui.buffer_distance_entry.get_value().replace(',', '.')) + self.ui.buffer_distance_entry.set_value(buffer_distance) + except ValueError: + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("Buffer distance value is missing or wrong format. Add it and retry.")) + return + # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment + # I populated the combobox such that the index coincide with the join styles value (which is really an INT) + join_style = self.ui.buffer_corner_cb.currentIndex() + 1 + self.buffer(buffer_distance, join_style) + + # def on_buffer_int(self): + # try: + # buffer_distance = float(self.ui.buffer_distance_entry.get_value()) + # except ValueError: + # # try to convert comma to decimal point. if it's still not working error message and return + # try: + # buffer_distance = float(self.ui.buffer_distance_entry.get_value().replace(',', '.')) + # self.ui.buffer_distance_entry.set_value(buffer_distance) + # except ValueError: + # self.app.inform.emit('[WARNING_NOTCL] %s' % + # _("Buffer distance value is missing or wrong format. Add it and retry.")) + # return + # # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment + # # I populated the combobox such that the index coincide with the join styles value (which is really an INT) + # join_style = self.ui.buffer_corner_cb.currentIndex() + 1 + # self.buffer_int(buffer_distance, join_style) + # + # def on_buffer_ext(self): + # try: + # buffer_distance = float(self.ui.buffer_distance_entry.get_value()) + # except ValueError: + # # try to convert comma to decimal point. if it's still not working error message and return + # try: + # buffer_distance = float(self.ui.buffer_distance_entry.get_value().replace(',', '.')) + # self.ui.buffer_distance_entry.set_value(buffer_distance) + # except ValueError: + # self.app.inform.emit('[WARNING_NOTCL] %s' % + # _("Buffer distance value is missing or wrong format. Add it and retry.")) + # return + # # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment + # # I populated the combobox such that the index coincide with the join styles value (which is really an INT) + # join_style = self.ui.buffer_corner_cb.currentIndex() + 1 + # self.buffer_ext(buffer_distance, join_style) + + def buffer(self, buff_value, join_style): + self.app.log.debug("AppGerberEditor.BufferEditorTool.buffer()") + + def buffer_recursion(geom_el, selection): + if type(geom_el) == list: + geoms = [] + for local_geom in geom_el: + geoms.append(buffer_recursion(local_geom, selection=selection)) + return geoms + else: + if geom_el in selection: + geometric_data = geom_el.geo + buffered_geom_el = {} + if 'solid' in geometric_data: + buffered_geom_el['solid'] = geometric_data['solid'].buffer(buff_value, join_style=join_style) + if 'follow' in geometric_data: + buffered_geom_el['follow'] = geometric_data['follow'].buffer(buff_value, join_style=join_style) + if 'clear' in geometric_data: + buffered_geom_el['clear'] = geometric_data['clear'].buffer(buff_value, join_style=join_style) + return DrawToolShape(buffered_geom_el) + else: + return geom_el + + if not self.draw_app.ui.apertures_table.selectedItems(): + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("No aperture to buffer. Select at least one aperture and try again.")) + return + + rows_list = set() + for x in self.draw_app.ui.apertures_table.selectedItems(): + rows_list.add(x.row()) + + for row in rows_list: + try: + apcode = int(self.draw_app.ui.apertures_table.item(row, 1).text()) + target_geo = self.draw_app.storage_dict[apcode]['geometry'] + buffered_geo = buffer_recursion(target_geo, self.draw_app.selected) + self.draw_app.storage_dict[apcode]['geometry'] = deepcopy(buffered_geo) + except Exception as e: + self.app.log.error( + "AppGerberEditor.BufferEditorTool.buffer() --> %s\n%s" % (str(e)), str(traceback.print_exc())) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return + + self.draw_app.plot_all() + self.app.inform.emit('[success] %s' % _("Done.")) + + def hide_tool(self): + self.ui.buffer_tool_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + + +class BufferEditorUI: + pluginName = _("Buffer") + + def __init__(self, layout, buffer_class): + self.buffer_class = buffer_class + self.decimals = self.buffer_class.app.decimals + self.layout = layout + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + title_label.setToolTip( + _("Buffer a aperture in the aperture list") + ) + self.layout.addWidget(title_label) + + self.param_label = FCLabel('%s' % _("Parameters"), color='blue', bold=True) + self.layout.addWidget(self.param_label) + + # this way I can hide/show the frame + self.buffer_tool_frame = QtWidgets.QFrame() + self.buffer_tool_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.buffer_tool_frame) + + self.buffer_tools_box = QtWidgets.QVBoxLayout() + self.buffer_tools_box.setContentsMargins(0, 0, 0, 0) + self.buffer_tool_frame.setLayout(self.buffer_tools_box) + + # ############################################################################################################# + # Tool Params Frame + # ############################################################################################################# + tool_par_frame = FCFrame() + self.buffer_tools_box.addWidget(tool_par_frame) + + # Grid Layout + param_grid = GLay(v_spacing=5, h_spacing=3) + tool_par_frame.setLayout(param_grid) + + # Buffer distance + self.buffer_distance_entry = FCDoubleSpinner() + self.buffer_distance_entry.set_precision(self.decimals) + self.buffer_distance_entry.set_range(0.0000, 10000.0000) + param_grid.addWidget(FCLabel('%s:' % _("Buffer distance")), 0, 0) + param_grid.addWidget(self.buffer_distance_entry, 0, 1) + + self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner")) + self.buffer_corner_lbl.setToolTip( + _("There are 3 types of corners:\n" + " - 'Round': the corner is rounded for exterior buffer.\n" + " - 'Square': the corner is met in a sharp angle for exterior buffer.\n" + " - 'Beveled': the corner is a line that directly connects the features meeting in the corner") + ) + self.buffer_corner_cb = FCComboBox() + self.buffer_corner_cb.addItem(_("Round")) + self.buffer_corner_cb.addItem(_("Square")) + self.buffer_corner_cb.addItem(_("Beveled")) + param_grid.addWidget(self.buffer_corner_lbl, 2, 0) + param_grid.addWidget(self.buffer_corner_cb, 2, 1) + + # Buttons + # hlay = QtWidgets.QHBoxLayout() + # self.buffer_tools_box.addLayout(hlay) + # + # self.buffer_int_button = FCButton(_("Buffer Interior")) + # hlay.addWidget(self.buffer_int_button) + # self.buffer_ext_button = FCButton(_("Buffer Exterior")) + # hlay.addWidget(self.buffer_ext_button) + + hlay1 = QtWidgets.QHBoxLayout() + self.buffer_tools_box.addLayout(hlay1) + + self.buffer_button = FCButton(_("Full Buffer")) + hlay1.addWidget(self.buffer_button) + + self.layout.addStretch(1) diff --git a/appEditors/grb_plugins/GrbCommon.py b/appEditors/grb_plugins/GrbCommon.py new file mode 100644 index 00000000..a90b867e --- /dev/null +++ b/appEditors/grb_plugins/GrbCommon.py @@ -0,0 +1,165 @@ + +# ########################################################################################### +# THE UNUSED LIBS MAY BE USED FURTHER AWAY BY IMPORTING FROM THIS FILE - DON'T REMOVE THEM +# ########################################################################################### + +from PyQt6.QtCore import Qt +from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Polygon, MultiPolygon, box +from shapely.ops import unary_union +import shapely.affinity as affinity + +import math +import numpy as np +from numpy.linalg import norm as numpy_norm + +from vispy.geometry import Rect +from copy import deepcopy + +import logging + +log = logging.getLogger('base') + + +class DrawToolShape(object): + """ + Encapsulates "shapes" under a common class. + """ + + tolerance = None + + @staticmethod + def get_pts(o): + """ + Returns a list of all points in the object, where + the object can be a Polygon, Not a polygon, or a list + of such. Search is done recursively. + + :param o: geometric object + :return: List of points + :rtype: list + """ + pts = [] + + # ## Iterable: descend into each item. + try: + for sub_o in o: + pts += DrawToolShape.get_pts(sub_o) + # Non-iterable + except TypeError: + if o is None: + return + + # DrawToolShape: descend into .geo. + if isinstance(o, DrawToolShape): + pts += DrawToolShape.get_pts(o.geo) + # ## Descend into .exerior and .interiors + elif type(o) == Polygon: + pts += DrawToolShape.get_pts(o.exterior) + for i in o.interiors: + pts += DrawToolShape.get_pts(i) + elif type(o) == MultiLineString: + for line in o: + pts += DrawToolShape.get_pts(line) + # ## Has .coords: list them. + else: + if DrawToolShape.tolerance is not None: + pts += list(o.simplify(DrawToolShape.tolerance).coords) + else: + pts += list(o.coords) + return pts + + def __init__(self, geo=None): + + # Shapely type or list of such + self.geo = geo + self.utility = False + + +class DrawToolUtilityShape(DrawToolShape): + """ + Utility shapes are temporary geometry in the editor + to assist in the creation of shapes. For example it + will show the outline of a rectangle from the first + point to the current mouse pointer before the second + point is clicked and the final geometry is created. + """ + + def __init__(self, geo=None): + super(DrawToolUtilityShape, self).__init__(geo=geo) + self.utility = True + + +class DrawTool(object): + """ + Abstract Class representing a tool in the drawing + program. Can generate geometry, including temporary + utility geometry that is updated on user clicks + and mouse motion. + """ + + def __init__(self, draw_app): + self.draw_app = draw_app + self.complete = False + self.points = [] + self.geometry = None # DrawToolShape or None + + def click(self, point): + """ + :param point: [x, y] Coordinate pair. + """ + return "" + + def click_release(self, point): + """ + :param point: [x, y] Coordinate pair. + """ + return "" + + def on_key(self, key): + # Jump to coords + if key == Qt.Key.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + + def utility_geometry(self, data=None): + return None + + @staticmethod + def bounds(obj): + def bounds_rec(o): + if type(o) is list: + minx = np.Inf + miny = np.Inf + maxx = -np.Inf + maxy = -np.Inf + + for k in o: + try: + minx_, miny_, maxx_, maxy_ = bounds_rec(k) + except Exception as e: + log.error("camlib.Gerber.bounds() --> %s" % str(e)) + return + + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + return minx, miny, maxx, maxy + else: + # it's a Shapely object, return it's bounds + if 'solid' in o.geo: + return o.geo['solid'].bounds + + return bounds_rec(obj) + + +class ShapeToolEditorGrb(DrawTool): + """ + Abstract class for tools that create a shape. + """ + + def __init__(self, draw_app): + DrawTool.__init__(self, draw_app) + self.name = None + + def make(self): + pass diff --git a/appEditors/grb_plugins/GrbCopyPlugin.py b/appEditors/grb_plugins/GrbCopyPlugin.py new file mode 100644 index 00000000..9d1e2f0d --- /dev/null +++ b/appEditors/grb_plugins/GrbCopyPlugin.py @@ -0,0 +1,538 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class CopyEditorTool(AppToolEditor): + """ + Simple input for buffer distance. + """ + + def __init__(self, app, draw_app, plugin_name): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.plugin_name = plugin_name + + self.ui = CopyEditorUI(layout=self.layout, copy_class=self, plugin_name=plugin_name) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.clear_btn.clicked.connect(self.on_clear) + + def disconnect_signals(self): + # Signals + try: + self.ui.clear_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + def run(self): + self.app.defaults.report_usage("Geo Editor CopyTool()") + AppToolEditor.run(self) + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.length = 0.0 + self.ui.mode_radio.set_value('n') + self.ui.on_copy_mode(self.ui.mode_radio.get_value()) + self.ui.array_type_radio.set_value('linear') + self.ui.on_array_type_radio(self.ui.array_type_radio.get_value()) + self.ui.axis_radio.set_value('X') + self.ui.on_linear_angle_radio(self.ui.axis_radio.get_value()) + + self.ui.array_dir_radio.set_value('CW') + + self.ui.placement_radio.set_value('s') + self.ui.on_placement_radio(self.ui.placement_radio.get_value()) + + self.ui.spacing_rows.set_value(0) + self.ui.spacing_columns.set_value(0) + self.ui.rows.set_value(1) + self.ui.columns.set_value(1) + self.ui.offsetx_entry.set_value(0) + self.ui.offsety_entry.set_value(0) + + def on_tab_close(self): + self.disconnect_signals() + self.hide_tool() + # self.app.ui.notebook.callback_on_close = lambda: None + + def on_clear(self): + self.set_tool_ui() + + @property + def length(self): + return self.ui.project_line_entry.get_value() + + @length.setter + def length(self, val): + self.ui.project_line_entry.set_value(val) + + def hide_tool(self): + self.ui.copy_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + if self.draw_app.active_tool.name != 'select': + self.draw_app.select_tool("select") + + +class CopyEditorUI: + + def __init__(self, layout, copy_class, plugin_name): + self.pluginName = plugin_name + self.copy_class = copy_class + self.decimals = self.copy_class.app.decimals + self.layout = layout + self.app = self.copy_class.app + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.copy_frame = QtWidgets.QFrame() + self.copy_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.copy_frame) + self.copy_tool_box = QtWidgets.QVBoxLayout() + self.copy_tool_box.setContentsMargins(0, 0, 0, 0) + self.copy_frame.setLayout(self.copy_tool_box) + + # Grid Layout + grid0 = GLay(v_spacing=5, h_spacing=3) + self.copy_tool_box.addLayout(grid0) + + # Project distance + self.project_line_lbl = FCLabel('%s:' % _("Projection")) + self.project_line_lbl.setToolTip( + _("Length of the current segment/move.") + ) + self.project_line_entry = NumericalEvalEntry(border_color='#0069A9') + grid0.addWidget(self.project_line_lbl, 0, 0) + grid0.addWidget(self.project_line_entry, 0, 1) + + self.clear_btn = FCButton(_("Clear")) + grid0.addWidget(self.clear_btn, 2, 0, 1, 2) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + grid0.addWidget(separator_line, 4, 0, 1, 2) + + # Type of Array + self.mode_label = FCLabel('%s:' % _("Mode"), bold=True) + self.mode_label.setToolTip( + _("Single copy or special (array of copies)") + ) + self.mode_radio = RadioSet([ + {'label': _('Single'), 'value': 'n'}, + {'label': _('Array'), 'value': 'a'} + ]) + + grid0.addWidget(self.mode_label, 6, 0) + grid0.addWidget(self.mode_radio, 6, 1) + + # ############################################################################################################# + # ######################################## Add Array ########################################################## + # ############################################################################################################# + # add a frame and inside add a grid box layout. + self.array_frame = FCFrame() + # self.array_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.array_frame) + + self.array_grid = GLay(v_spacing=5, h_spacing=3) + # self.array_grid.setContentsMargins(0, 0, 0, 0) + self.array_frame.setLayout(self.array_grid) + + # Set the number of items in the array + self.array_size_label = FCLabel('%s:' % _('Size')) + self.array_size_label.setToolTip(_("Specify how many items to be in the array.")) + + self.array_size_entry = FCSpinner(policy=False) + self.array_size_entry.set_range(1, 100000) + + self.array_grid.addWidget(self.array_size_label, 2, 0) + self.array_grid.addWidget(self.array_size_entry, 2, 1) + + # Array Type + array_type_lbl = FCLabel('%s:' % _("Type")) + array_type_lbl.setToolTip( + _("Select the type of array to create.\n" + "It can be Linear X(Y) or Circular") + ) + + self.array_type_radio = RadioSet([ + {'label': _('Linear'), 'value': 'linear'}, + {'label': _('2D'), 'value': '2D'}, + {'label': _('Circular'), 'value': 'circular'} + ]) + + self.array_grid.addWidget(array_type_lbl, 4, 0) + self.array_grid.addWidget(self.array_type_radio, 4, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.array_grid.addWidget(separator_line, 6, 0, 1, 2) + + # ############################################################################################################# + # ############################ LINEAR Array ################################################################### + # ############################################################################################################# + self.array_linear_frame = QtWidgets.QFrame() + self.array_linear_frame.setContentsMargins(0, 0, 0, 0) + self.array_grid.addWidget(self.array_linear_frame, 8, 0, 1, 2) + + self.lin_grid = GLay(v_spacing=5, h_spacing=3) + self.lin_grid.setContentsMargins(0, 0, 0, 0) + self.array_linear_frame.setLayout(self.lin_grid) + + # Linear Drill Array direction + self.axis_label = FCLabel('%s:' % _('Direction')) + self.axis_label.setToolTip( + _("Direction on which the linear array is oriented:\n" + "- 'X' - horizontal axis \n" + "- 'Y' - vertical axis or \n" + "- 'Angle' - a custom angle for the array inclination") + ) + + self.axis_radio = RadioSet([ + {'label': _('X'), 'value': 'X'}, + {'label': _('Y'), 'value': 'Y'}, + {'label': _('Angle'), 'value': 'A'} + ]) + + self.lin_grid.addWidget(self.axis_label, 0, 0) + self.lin_grid.addWidget(self.axis_radio, 0, 1) + + # Linear Array pitch distance + self.pitch_label = FCLabel('%s:' % _('Pitch')) + self.pitch_label.setToolTip( + _("Pitch = Distance between elements of the array.") + ) + + self.pitch_entry = FCDoubleSpinner(policy=False) + self.pitch_entry.set_precision(self.decimals) + self.pitch_entry.set_range(0.0000, 10000.0000) + + self.lin_grid.addWidget(self.pitch_label, 2, 0) + self.lin_grid.addWidget(self.pitch_entry, 2, 1) + + # Linear Array angle + self.linear_angle_label = FCLabel('%s:' % _('Angle')) + self.linear_angle_label.setToolTip( + _("Angle at which the linear array is placed.\n" + "The precision is of max 2 decimals.\n" + "Min value is: -360.00 degrees.\n" + "Max value is: 360.00 degrees.") + ) + + self.linear_angle_spinner = FCDoubleSpinner(policy=False) + self.linear_angle_spinner.set_precision(self.decimals) + self.linear_angle_spinner.setSingleStep(1.0) + self.linear_angle_spinner.setRange(-360.00, 360.00) + + self.lin_grid.addWidget(self.linear_angle_label, 4, 0) + self.lin_grid.addWidget(self.linear_angle_spinner, 4, 1) + + # ############################################################################################################# + # ################################ 2D Array ################################################################### + # ############################################################################################################# + self.two_dim_array_frame = QtWidgets.QFrame() + self.two_dim_array_frame.setContentsMargins(0, 0, 0, 0) + self.array_grid.addWidget(self.two_dim_array_frame, 10, 0, 1, 2) + + self.dd_grid = GLay(v_spacing=5, h_spacing=3) + self.dd_grid.setContentsMargins(0, 0, 0, 0) + self.two_dim_array_frame.setLayout(self.dd_grid) + + # 2D placement + self.place_label = FCLabel('%s:' % _('Placement')) + self.place_label.setToolTip( + _("Placement of array items:\n" + "'Spacing' - define space between rows and columns \n" + "'Offset' - each row (and column) will be placed at a multiple of a value, from origin") + ) + + self.placement_radio = RadioSet([ + {'label': _('Spacing'), 'value': 's'}, + {'label': _('Offset'), 'value': 'o'} + ]) + + self.dd_grid.addWidget(self.place_label, 0, 0) + self.dd_grid.addWidget(self.placement_radio, 0, 1) + + # Rows + self.rows = FCSpinner(callback=self.confirmation_message_int) + self.rows.set_range(0, 10000) + + self.rows_label = FCLabel('%s:' % _("Rows")) + self.rows_label.setToolTip( + _("Number of rows") + ) + self.dd_grid.addWidget(self.rows_label, 2, 0) + self.dd_grid.addWidget(self.rows, 2, 1) + + # Columns + self.columns = FCSpinner(callback=self.confirmation_message_int) + self.columns.set_range(0, 10000) + + self.columns_label = FCLabel('%s:' % _("Columns")) + self.columns_label.setToolTip( + _("Number of columns") + ) + self.dd_grid.addWidget(self.columns_label, 4, 0) + self.dd_grid.addWidget(self.columns, 4, 1) + + # ------------------------------------------------ + # ############## Spacing Frame ################# + # ------------------------------------------------ + self.spacing_frame = QtWidgets.QFrame() + self.spacing_frame.setContentsMargins(0, 0, 0, 0) + self.dd_grid.addWidget(self.spacing_frame, 6, 0, 1, 2) + + self.s_grid = GLay(v_spacing=5, h_spacing=3) + self.s_grid.setContentsMargins(0, 0, 0, 0) + self.spacing_frame.setLayout(self.s_grid) + + # Spacing Rows + self.spacing_rows = FCDoubleSpinner(callback=self.confirmation_message) + self.spacing_rows.set_range(0, 9999) + self.spacing_rows.set_precision(4) + + self.spacing_rows_label = FCLabel('%s:' % _("Spacing rows")) + self.spacing_rows_label.setToolTip( + _("Spacing between rows.\n" + "In current units.") + ) + self.s_grid.addWidget(self.spacing_rows_label, 0, 0) + self.s_grid.addWidget(self.spacing_rows, 0, 1) + + # Spacing Columns + self.spacing_columns = FCDoubleSpinner(callback=self.confirmation_message) + self.spacing_columns.set_range(0, 9999) + self.spacing_columns.set_precision(4) + + self.spacing_columns_label = FCLabel('%s:' % _("Spacing cols")) + self.spacing_columns_label.setToolTip( + _("Spacing between columns.\n" + "In current units.") + ) + self.s_grid.addWidget(self.spacing_columns_label, 2, 0) + self.s_grid.addWidget(self.spacing_columns, 2, 1) + + # ------------------------------------------------ + # ############## Offset Frame ################## + # ------------------------------------------------ + self.offset_frame = QtWidgets.QFrame() + self.offset_frame.setContentsMargins(0, 0, 0, 0) + self.dd_grid.addWidget(self.offset_frame, 8, 0, 1, 2) + + self.o_grid = GLay(v_spacing=5, h_spacing=3) + self.o_grid.setContentsMargins(0, 0, 0, 0) + self.offset_frame.setLayout(self.o_grid) + + # Offset X Value + self.offsetx_label = FCLabel('%s X:' % _("Offset")) + self.offsetx_label.setToolTip( + _("'Offset' - each row (and column) will be placed at a multiple of a value, from origin") + ) + + self.offsetx_entry = FCDoubleSpinner(policy=False) + self.offsetx_entry.set_precision(self.decimals) + self.offsetx_entry.set_range(0.0000, 10000.0000) + + self.o_grid.addWidget(self.offsetx_label, 0, 0) + self.o_grid.addWidget(self.offsetx_entry, 0, 1) + + # Offset Y Value + self.offsety_label = FCLabel('%s Y:' % _("Offset")) + self.offsety_label.setToolTip( + _("'Offset' - each row (and column) will be placed at a multiple of a value, from origin") + ) + + self.offsety_entry = FCDoubleSpinner(policy=False) + self.offsety_entry.set_precision(self.decimals) + self.offsety_entry.set_range(0.0000, 10000.0000) + + self.o_grid.addWidget(self.offsety_label, 2, 0) + self.o_grid.addWidget(self.offsety_entry, 2, 1) + + # ############################################################################################################# + # ############################ CIRCULAR Array ################################################################# + # ############################################################################################################# + self.array_circular_frame = QtWidgets.QFrame() + self.array_circular_frame.setContentsMargins(0, 0, 0, 0) + self.array_grid.addWidget(self.array_circular_frame, 12, 0, 1, 2) + + self.circ_grid = GLay(v_spacing=5, h_spacing=3) + self.circ_grid.setContentsMargins(0, 0, 0, 0) + self.array_circular_frame.setLayout(self.circ_grid) + + # Array Direction + self.array_dir_lbl = FCLabel('%s:' % _('Direction')) + self.array_dir_lbl.setToolTip( + _("Direction for circular array.\n" + "Can be CW = clockwise or CCW = counter clockwise.")) + + self.array_dir_radio = RadioSet([ + {'label': _('CW'), 'value': 'CW'}, + {'label': _('CCW'), 'value': 'CCW'}]) + + self.circ_grid.addWidget(self.array_dir_lbl, 0, 0) + self.circ_grid.addWidget(self.array_dir_radio, 0, 1) + + # Array Angle + self.array_angle_lbl = FCLabel('%s:' % _('Angle')) + self.array_angle_lbl.setToolTip(_("Angle at which each element in circular array is placed.")) + + self.angle_entry = FCDoubleSpinner(policy=False) + self.angle_entry.set_precision(self.decimals) + self.angle_entry.setSingleStep(1.0) + self.angle_entry.setRange(-360.00, 360.00) + + self.circ_grid.addWidget(self.array_angle_lbl, 2, 0) + self.circ_grid.addWidget(self.angle_entry, 2, 1) + + # Buttons + self.add_button = FCButton(_("Add")) + self.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png')) + self.layout.addWidget(self.add_button) + + GLay.set_common_column_size([ + grid0, self.array_grid, self.lin_grid, self.dd_grid, self.circ_grid, self.s_grid, self.o_grid + ], 0) + + self.layout.addStretch(1) + + # Signals + self.mode_radio.activated_custom.connect(self.on_copy_mode) + self.array_type_radio.activated_custom.connect(self.on_array_type_radio) + self.axis_radio.activated_custom.connect(self.on_linear_angle_radio) + self.placement_radio.activated_custom.connect(self.on_placement_radio) + + 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) + + 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) + + def on_copy_mode(self, val): + if val == 'n': + self.array_frame.hide() + self.app.inform.emit(_("Click on reference location ...")) + else: + self.array_frame.show() + + def on_array_type_radio(self, val): + if val == '2D': + self.array_circular_frame.hide() + self.array_linear_frame.hide() + self.two_dim_array_frame.show() + if self.placement_radio.get_value() == 's': + self.spacing_frame.show() + self.offset_frame.hide() + else: + self.spacing_frame.hide() + self.offset_frame.show() + + self.array_size_entry.setDisabled(True) + self.on_rows_cols_value_changed() + + self.rows.valueChanged.connect(self.on_rows_cols_value_changed) + self.columns.valueChanged.connect(self.on_rows_cols_value_changed) + + self.app.inform.emit(_("Click on reference location ...")) + else: + if val == 'linear': + self.array_circular_frame.hide() + self.array_linear_frame.show() + self.two_dim_array_frame.hide() + self.spacing_frame.hide() + self.offset_frame.hide() + + self.app.inform.emit(_("Click on reference location ...")) + else: # 'circular' + self.array_circular_frame.show() + self.array_linear_frame.hide() + self.two_dim_array_frame.hide() + self.spacing_frame.hide() + self.offset_frame.hide() + + self.app.inform.emit(_("Click on the circular array Center position")) + + self.array_size_entry.setDisabled(False) + try: + self.rows.valueChanged.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.columns.valueChanged.disconnect() + except (TypeError, AttributeError): + pass + + def on_rows_cols_value_changed(self): + new_size = self.rows.get_value() * self.columns.get_value() + if new_size == 0: + new_size = 1 + self.array_size_entry.set_value(new_size) + + def on_linear_angle_radio(self, val): + if val == 'A': + self.linear_angle_spinner.show() + self.linear_angle_label.show() + else: + self.linear_angle_spinner.hide() + self.linear_angle_label.hide() + + def on_placement_radio(self, val): + if val == 's': + self.spacing_frame.show() + self.offset_frame.hide() + else: + self.spacing_frame.hide() + self.offset_frame.show() diff --git a/appEditors/grb_plugins/GrbPadArrayPlugin.py b/appEditors/grb_plugins/GrbPadArrayPlugin.py new file mode 100644 index 00000000..e69de29b diff --git a/appEditors/grb_plugins/GrbPadPlugin.py b/appEditors/grb_plugins/GrbPadPlugin.py new file mode 100644 index 00000000..e69de29b diff --git a/appEditors/grb_plugins/GrbSimplificationPlugin.py b/appEditors/grb_plugins/GrbSimplificationPlugin.py new file mode 100644 index 00000000..38c064f6 --- /dev/null +++ b/appEditors/grb_plugins/GrbSimplificationPlugin.py @@ -0,0 +1,260 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class SimplificationTool(AppToolEditor): + """ + Do a shape simplification for the selected geometry. + """ + + update_ui = pyqtSignal(object, int) + + def __init__(self, app, draw_app): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.app = self.draw_app.app + + self.ui = SimplificationEditorUI(layout=self.layout, simp_class=self) + self.plugin_name = self.ui.pluginName + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.simplification_btn.clicked.connect(self.on_simplification_click) + self.update_ui.connect(self.on_update_ui) + + def run(self): + self.app.defaults.report_usage("Geo Editor SimplificationTool()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.ui.geo_tol_entry.set_value(0.01 if self.draw_app.units == 'MM' else 0.0004) + + selected_shapes_geos = [] + selected_tree_items = self.draw_app.ui.tw.selectedItems() + for sel in selected_tree_items: + for obj_shape in self.draw_app.storage.get_objects(): + try: + if id(obj_shape) == int(sel.text(0)): + selected_shapes_geos.append(obj_shape.geo) + except ValueError: + pass + if selected_shapes_geos: + # those are displayed by triggering the signal self.update_ui + self.calculate_coords_vertex(selected_shapes_geos[-1]) + + def on_tab_close(self): + self.draw_app.select_tool("select") + self.app.ui.notebook.callback_on_close = lambda: None + + def on_simplification_click(self): + self.app.log.debug("FCSimplification.on_simplification_click()") + + selected_shapes_geos = [] + tol = self.ui.geo_tol_entry.get_value() + + def task_job(self): + with self.app.proc_container.new('%s...' % _("Simplify")): + selected_shapes = self.draw_app.get_selected() + self.draw_app.interdict_selection = True + for obj_shape in selected_shapes: + selected_shapes_geos.append(obj_shape.geo.simplify(tolerance=tol)) + + if not selected_shapes: + self.app.inform.emit('%s' % _("Failed.")) + return + + for shape in selected_shapes: + self.draw_app.delete_shape(shape=shape) + + for geo in selected_shapes_geos: + self.draw_app.add_shape(geo, build_ui=False) + + self.draw_app.selected = [] + + last_sel_geo = selected_shapes_geos[-1] + self.calculate_coords_vertex(last_sel_geo) + + self.app.inform.emit('%s' % _("Done.")) + + self.draw_app.plot_all() + self.draw_app.interdict_selection = False + self.draw_app.build_ui_sig.emit() + + self.app.worker_task.emit({'fcn': task_job, 'params': [self]}) + + def calculate_coords_vertex(self, last_sel_geo): + if last_sel_geo: + if last_sel_geo.geom_type == 'MultiLineString': + coords = '' + vertex_nr = 0 + for idx, line in enumerate(last_sel_geo.geoms): + line_coords = list(line.coords) + vertex_nr += len(line_coords) + coords += 'Line %s\n' % str(idx) + coords += str(line_coords) + '\n' + elif last_sel_geo.geom_type == 'MultiPolygon': + coords = '' + vertex_nr = 0 + for idx, poly in enumerate(last_sel_geo.geoms): + poly_coords = list(poly.exterior.coords) + [list(i.coords) for i in poly.interiors] + vertex_nr += len(poly_coords) + + coords += 'Polygon %s\n' % str(idx) + coords += str(poly_coords) + '\n' + elif last_sel_geo.geom_type in ['LinearRing', 'LineString']: + coords = list(last_sel_geo.coords) + vertex_nr = len(coords) + elif last_sel_geo.geom_type == 'Polygon': + coords = list(last_sel_geo.exterior.coords) + vertex_nr = len(coords) + else: + coords = 'None' + vertex_nr = 0 + + self.update_ui.emit(coords, vertex_nr) + + def on_update_ui(self, coords, vertex_nr): + self.ui.geo_coords_entry.set_value(str(coords)) + self.ui.geo_vertex_entry.set_value(vertex_nr) + + def hide_tool(self): + self.ui.simp_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + + +class SimplificationEditorUI: + pluginName = _("Simplification") + + def __init__(self, layout, simp_class): + self.simp_class = simp_class + self.app = self.simp_class.app + self.decimals = self.app.decimals + self.layout = layout + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.simp_frame = QtWidgets.QFrame() + self.simp_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.simp_frame) + self.simp_tools_box = QtWidgets.QVBoxLayout() + self.simp_tools_box.setContentsMargins(0, 0, 0, 0) + self.simp_frame.setLayout(self.simp_tools_box) + + # Grid Layout + grid0 = GLay(v_spacing=5, h_spacing=3) + self.simp_tools_box.addLayout(grid0) + + # Coordinates + coords_lbl = FCLabel('%s' % _("Coordinates"), bold=True, color='red') + coords_lbl.setToolTip( + _("The coordinates of the selected geometry element.") + ) + grid0.addWidget(coords_lbl, 0, 0, 1, 2) + + # ############################################################################################################# + # Coordinates Frame + # ############################################################################################################# + coors_frame = FCFrame() + grid0.addWidget(coors_frame, 2, 0, 1, 2) + + coords_grid = GLay(v_spacing=5, h_spacing=3) + coors_frame.setLayout(coords_grid) + + self.geo_coords_entry = FCTextEdit() + self.geo_coords_entry.setPlaceholderText( + _("The coordinates of the selected geometry element.") + ) + coords_grid.addWidget(self.geo_coords_entry, 0, 0, 1, 2) + + # Vertex Points Number + vertex_lbl = FCLabel('%s:' % _("Vertex Points"), bold=False) + vertex_lbl.setToolTip( + _("The number of vertex points in the selected geometry element.") + ) + self.geo_vertex_entry = FCEntry(decimals=self.decimals) + self.geo_vertex_entry.setReadOnly(True) + + coords_grid.addWidget(vertex_lbl, 2, 0) + coords_grid.addWidget(self.geo_vertex_entry, 2, 1) + + # Simplification Title + par_lbl = FCLabel('%s' % _("Parameters"), bold=True, color='blue') + grid0.addWidget(par_lbl, 4, 0, 1, 2) + # ############################################################################################################# + # Parameters Frame + # ############################################################################################################# + par_frame = FCFrame() + grid0.addWidget(par_frame, 6, 0, 1, 2) + + par_grid = GLay(v_spacing=5, h_spacing=3) + par_frame.setLayout(par_grid) + + # Simplification Tolerance + simplification_tol_lbl = FCLabel('%s' % _("Tolerance"), bold=True) + simplification_tol_lbl.setToolTip( + _("All points in the simplified object will be\n" + "within the tolerance distance of the original geometry.") + ) + self.geo_tol_entry = FCDoubleSpinner() + self.geo_tol_entry.set_precision(self.decimals) + self.geo_tol_entry.setSingleStep(10 ** -self.decimals) + self.geo_tol_entry.set_range(0.0000, 10000.0000) + + par_grid.addWidget(simplification_tol_lbl, 0, 0) + par_grid.addWidget(self.geo_tol_entry, 0, 1) + + # Simplification button + self.simplification_btn = FCButton(_("Simplify"), bold=True) + self.simplification_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/simplify32.png')) + self.simplification_btn.setToolTip( + _("Simplify a geometry element by reducing its vertex points number.") + ) + + self.layout.addWidget(self.simplification_btn) + + GLay.set_common_column_size([grid0, coords_grid, par_grid], 0) + self.layout.addStretch(1) diff --git a/appEditors/grb_plugins/GrbTracePlugin.py b/appEditors/grb_plugins/GrbTracePlugin.py new file mode 100644 index 00000000..b9c92c3f --- /dev/null +++ b/appEditors/grb_plugins/GrbTracePlugin.py @@ -0,0 +1,149 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class TraceEditorTool(AppToolEditor): + """ + Simple input for buffer distance. + """ + + def __init__(self, app, draw_app, plugin_name): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.plugin_name = plugin_name + + self.ui = PathEditorUI(layout=self.layout, path_class=self, plugin_name=plugin_name) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.clear_btn.clicked.connect(self.on_clear) + + def disconnect_signals(self): + # Signals + try: + self.ui.clear_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + def run(self): + self.app.defaults.report_usage("Geo Editor ToolPath()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.length = 0.0 + + def on_tab_close(self): + self.disconnect_signals() + self.hide_tool() + # self.app.ui.notebook.callback_on_close = lambda: None + + def on_clear(self): + self.set_tool_ui() + + @property + def length(self): + return self.ui.project_line_entry.get_value() + + @length.setter + def length(self, val): + self.ui.project_line_entry.set_value(val) + + def hide_tool(self): + self.ui.path_tool_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + if self.draw_app.active_tool.name != 'select': + self.draw_app.select_tool("select") + + +class PathEditorUI: + + def __init__(self, layout, path_class, plugin_name): + self.pluginName = plugin_name + self.path_class = path_class + self.decimals = self.path_class.app.decimals + self.layout = layout + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.path_tool_frame = QtWidgets.QFrame() + self.path_tool_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.path_tool_frame) + self.path_tool_box = QtWidgets.QVBoxLayout() + self.path_tool_box.setContentsMargins(0, 0, 0, 0) + self.path_tool_frame.setLayout(self.path_tool_box) + + # Grid Layout + grid_path = GLay(v_spacing=5, h_spacing=3) + self.path_tool_box.addLayout(grid_path) + + # Project distance + self.project_line_lbl = FCLabel('%s:' % _("Projection")) + self.project_line_lbl.setToolTip( + _("Length of the current segment/move.") + ) + self.project_line_entry = NumericalEvalEntry(border_color='#0069A9') + grid_path.addWidget(self.project_line_lbl, 0, 0) + grid_path.addWidget(self.project_line_entry, 0, 1) + + # self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner")) + # self.buffer_corner_lbl.setToolTip( + # _("There are 3 types of corners:\n" + # " - 'Round': the corner is rounded for exterior buffer.\n" + # " - 'Square': the corner is met in a sharp angle for exterior buffer.\n" + # " - 'Beveled': the corner is a line that directly connects the features meeting in the corner") + # ) + # self.buffer_corner_cb = FCComboBox() + # self.buffer_corner_cb.addItem(_("Round")) + # self.buffer_corner_cb.addItem(_("Square")) + # self.buffer_corner_cb.addItem(_("Beveled")) + # grid_path.addWidget(self.buffer_corner_lbl, 2, 0) + # grid_path.addWidget(self.buffer_corner_cb, 2, 1) + + self.clear_btn = FCButton(_("Clear")) + grid_path.addWidget(self.clear_btn, 4, 0, 1, 2) + + self.layout.addStretch(1) diff --git a/appEditors/grb_plugins/GrbTransformationPlugin.py b/appEditors/grb_plugins/GrbTransformationPlugin.py new file mode 100644 index 00000000..63d3723c --- /dev/null +++ b/appEditors/grb_plugins/GrbTransformationPlugin.py @@ -0,0 +1,1026 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class TransformEditorTool(AppToolEditor): + """ + Inputs to specify how to paint the selected polygons. + """ + + def __init__(self, app, draw_app): + AppToolEditor.__init__(self, app) + + self.app = app + self.draw_app = draw_app + self.decimals = self.app.decimals + + self.ui = TransformationEditorUI(layout=self.layout, transform_class=self) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.point_button.clicked.connect(lambda: self.on_add_coords()) + + self.ui.rotate_button.clicked.connect(lambda: self.on_rotate()) + + self.ui.skewx_button.clicked.connect(lambda: self.on_skewx()) + self.ui.skewy_button.clicked.connect(lambda: self.on_skewy()) + + self.ui.scalex_button.clicked.connect(lambda: self.on_scalex()) + self.ui.scaley_button.clicked.connect(lambda: self.on_scaley()) + + self.ui.offx_button.clicked.connect(lambda: self.on_offx()) + self.ui.offy_button.clicked.connect(lambda: self.on_offy()) + + self.ui.flipx_button.clicked.connect(lambda: self.on_flipx()) + self.ui.flipy_button.clicked.connect(lambda: self.on_flipy()) + + self.ui.buffer_button.clicked.connect(lambda: self.on_buffer_by_distance()) + self.ui.buffer_factor_button.clicked.connect(lambda: self.on_buffer_by_factor()) + + def run(self, toggle=True): + self.app.defaults.report_usage("Geo Editor Transform Tool()") + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + if toggle: + try: + if self.app.ui.plugin_scroll_area.widget().objectName() == self.pluginName: + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + else: + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + except AttributeError: + pass + + super().run() + self.set_tool_ui() + + self.app.ui.notebook.setTabText(2, _("Transformation")) + + def on_tab_close(self): + self.draw_app.select_tool("select") + self.app.ui.notebook.callback_on_close = lambda: None + + def install(self, icon=None, separator=None, **kwargs): + AppTool.install(self, icon, separator, shortcut='Alt+T', **kwargs) + + def set_tool_ui(self): + # Initialize form + ref_val = self.app.options["tools_transform_reference"] + if ref_val == _("Object"): + ref_val = _("Selection") + self.ui.ref_combo.set_value(ref_val) + self.ui.point_entry.set_value(self.app.options["tools_transform_ref_point"]) + self.ui.rotate_entry.set_value(self.app.options["tools_transform_rotate"]) + + self.ui.skewx_entry.set_value(self.app.options["tools_transform_skew_x"]) + self.ui.skewy_entry.set_value(self.app.options["tools_transform_skew_y"]) + self.ui.skew_link_cb.set_value(self.app.options["tools_transform_skew_link"]) + + self.ui.scalex_entry.set_value(self.app.options["tools_transform_scale_x"]) + self.ui.scaley_entry.set_value(self.app.options["tools_transform_scale_y"]) + self.ui.scale_link_cb.set_value(self.app.options["tools_transform_scale_link"]) + + self.ui.offx_entry.set_value(self.app.options["tools_transform_offset_x"]) + self.ui.offy_entry.set_value(self.app.options["tools_transform_offset_y"]) + + self.ui.buffer_entry.set_value(self.app.options["tools_transform_buffer_dis"]) + self.ui.buffer_factor_entry.set_value(self.app.options["tools_transform_buffer_factor"]) + self.ui.buffer_rounded_cb.set_value(self.app.options["tools_transform_buffer_corner"]) + + # initial state is hidden + self.ui.point_label.hide() + self.ui.point_entry.hide() + self.ui.point_button.hide() + + def template(self): + if not self.draw_app.selected: + self.app.inform.emit('[WARNING_NOTCL] %s %s' % (_("Cancelled."), _("No shape selected."))) + return + + self.draw_app.select_tool("select") + self.app.ui.notebook.setTabText(2, "Plugins") + self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) + + self.app.ui.splitter.setSizes([0, 1]) + + def on_calculate_reference(self, ref_index=None): + if ref_index: + ref_val = ref_index + else: + ref_val = self.ui.ref_combo.currentIndex() + + if ref_val == 0: # "Origin" reference + return 0, 0 + elif ref_val == 1: # "Selection" reference + sel_list = self.draw_app.selected + if sel_list: + xmin, ymin, xmax, ymax = self.alt_bounds(sel_list) + px = (xmax + xmin) * 0.5 + py = (ymax + ymin) * 0.5 + return px, py + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("No shape selected.")) + return "fail" + elif ref_val == 2: # "Point" reference + point_val = self.ui.point_entry.get_value() + try: + px, py = eval('{}'.format(point_val)) + return px, py + except Exception: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Incorrect format for Point value. Needs format X,Y")) + return "fail" + else: + sel_list = self.draw_app.selected + if sel_list: + xmin, ymin, xmax, ymax = self.alt_bounds(sel_list) + if ref_val == 3: + return xmin, ymin # lower left corner + elif ref_val == 4: + return xmax, ymin # lower right corner + elif ref_val == 5: + return xmax, ymax # upper right corner + else: + return xmin, ymax # upper left corner + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("No shape selected.")) + return "fail" + + def on_add_coords(self): + val = self.app.clipboard.text() + self.ui.point_entry.set_value(val) + + def on_rotate(self, val=None, ref=None): + value = float(self.ui.rotate_entry.get_value()) if val is None else val + if value == 0: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate transformation can not be done for a value of 0.")) + return + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + self.app.worker_task.emit({'fcn': self.on_rotate_action, 'params': [value, point]}) + + def on_flipx(self, ref=None): + axis = 'Y' + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]}) + + def on_flipy(self, ref=None): + axis = 'X' + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]}) + + def on_skewx(self, val=None, ref=None): + xvalue = float(self.ui.skewx_entry.get_value()) if val is None else val + + if xvalue == 0: + return + + yvalue = xvalue if self.ui.skew_link_cb.get_value() else 0 + + axis = 'X' + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + + self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]}) + + def on_skewy(self, val=None, ref=None): + xvalue = 0 + yvalue = float(self.ui.skewy_entry.get_value()) if val is None else val + + if yvalue == 0: + return + + axis = 'Y' + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + + self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]}) + + def on_scalex(self, val=None, ref=None): + xvalue = float(self.ui.scalex_entry.get_value()) if val is None else val + + 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 + + yvalue = xvalue if self.ui.scale_link_cb.get_value() else 1 + + axis = 'X' + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + + self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]}) + + def on_scaley(self, val=None, ref=None): + xvalue = 1 + yvalue = float(self.ui.scaley_entry.get_value()) if val is None else val + + if yvalue == 0 or yvalue == 1: + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("Scale transformation can not be done for a factor of 0 or 1.")) + return + + axis = 'Y' + point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) + if point == 'fail': + return + + self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]}) + + def on_offx(self, val=None): + value = float(self.ui.offx_entry.get_value()) if val is None else val + if value == 0: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0.")) + return + axis = 'X' + + self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) + + def on_offy(self, val=None): + value = float(self.ui.offy_entry.get_value()) if val is None else val + if value == 0: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0.")) + return + axis = 'Y' + + self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) + + def on_buffer_by_distance(self): + 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.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 + + self.app.worker_task.emit({'fcn': self.on_buffer_action, 'params': [value, join, factor]}) + + def on_rotate_action(self, val, point): + """ + Rotate geometry + + :param val: Rotate with a known angle value, val + :param point: Reference point for rotation: tuple + :return: + """ + + with self.app.proc_container.new('%s...' % _("Rotating")): + shape_list = self.draw_app.selected + px, py = point + + if not shape_list: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected.")) + return + + try: + for sel_sha in shape_list: + sel_sha.rotate(-val, point=(px, py)) + self.draw_app.plot_all() + + self.app.inform.emit('[success] %s' % _("Done.")) + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + return + + def on_flip(self, axis, point): + """ + Mirror (flip) geometry + + :param axis: Mirror on a known axis given by the axis parameter + :param point: Mirror reference point + :return: + """ + + shape_list = self.draw_app.selected + + if not shape_list: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected.")) + return + + with self.app.proc_container.new('%s...' % _("Flipping")): + try: + px, py = point + + # execute mirroring + for sha in shape_list: + if axis == 'X': + sha.mirror('X', (px, py)) + self.app.inform.emit('[success] %s...' % _('Flip on Y axis done')) + elif axis == 'Y': + sha.mirror('Y', (px, py)) + self.app.inform.emit('[success] %s' % _('Flip on X axis done')) + self.draw_app.plot_all() + + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + return + + def on_skew(self, axis, xval, yval, point): + """ + Skew geometry + + :param point: + :param axis: Axis on which to deform, skew + :param xval: Skew value on X axis + :param yval: Skew value on Y axis + :return: + """ + + shape_list = self.draw_app.selected + + if not shape_list: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected.")) + return + + with self.app.proc_container.new('%s...' % _("Skewing")): + try: + px, py = point + for sha in shape_list: + sha.skew(xval, yval, point=(px, py)) + + self.draw_app.plot_all() + + if axis == 'X': + self.app.inform.emit('[success] %s...' % _('Skew on the X axis done')) + else: + self.app.inform.emit('[success] %s...' % _('Skew on the Y axis done')) + + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + return + + def on_scale(self, axis, xfactor, yfactor, point=None): + """ + Scale geometry + + :param axis: Axis on which to scale + :param xfactor: Factor for scaling on X axis + :param yfactor: Factor for scaling on Y axis + :param point: Point of origin for scaling + + :return: + """ + + shape_list = self.draw_app.selected + + if not shape_list: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected.")) + return + + with self.app.proc_container.new('%s...' % _("Scaling")): + try: + px, py = point + + for sha in shape_list: + sha.scale(xfactor, yfactor, point=(px, py)) + self.draw_app.plot_all() + + if str(axis) == 'X': + self.app.inform.emit('[success] %s...' % _('Scale on the X axis done')) + else: + self.app.inform.emit('[success] %s...' % _('Scale on the Y axis done')) + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + return + + def on_offset(self, axis, num): + """ + Offset geometry + + :param axis: Axis on which to apply offset + :param num: The translation factor + + :return: + """ + shape_list = self.draw_app.selected + + if not shape_list: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected.")) + return + + with self.app.proc_container.new('%s...' % _("Offsetting")): + try: + for sha in shape_list: + if axis == 'X': + sha.offset((num, 0)) + elif axis == 'Y': + sha.offset((0, num)) + self.draw_app.plot_all() + + if axis == 'X': + self.app.inform.emit('[success] %s %s' % (_('Offset on the X axis.'), _("Done."))) + else: + self.app.inform.emit('[success] %s %s' % (_('Offset on the Y axis.'), _("Done."))) + + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + return + + def on_buffer_action(self, value, join, factor=None): + shape_list = self.draw_app.selected + + if not shape_list: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No shape selected.")) + return + else: + with self.app.proc_container.new('%s...' % _("Buffering")): + try: + for sel_obj in shape_list: + sel_obj.buffer(value, join, factor) + + self.draw_app.plot_all() + + self.app.inform.emit('[success] %s...' % _('Buffer done')) + + except Exception as e: + self.app.log.error("TransformEditorTool.on_buffer_action() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s: %s.' % (_("Action was not executed"), str(e))) + return + + def on_rotate_key(self): + val_box = FCInputDoubleSpinner(title=_("Rotate ..."), + text='%s:' % _('Enter an Angle Value (degrees)'), + min=-359.9999, max=360.0000, decimals=self.decimals, + init_val=float(self.app.options['tools_transform_rotate']), + parent=self.app.ui) + val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/rotate.png')) + + val, ok = val_box.get_value() + if ok: + self.on_rotate(val=val, ref=1) + self.app.inform.emit('[success] %s...' % _("Rotate done")) + return + else: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate cancelled")) + + def on_offx_key(self): + units = self.app.app_units.lower() + + val_box = FCInputDoubleSpinner(title=_("Offset on X axis ..."), + text='%s: (%s)' % (_('Enter a distance Value'), str(units)), + min=-10000.0000, max=10000.0000, decimals=self.decimals, + init_val=float(self.app.options['tools_transform_offset_x']), + parent=self.app.ui) + val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/offsetx32.png')) + + val, ok = val_box.get_value() + if ok: + self.on_offx(val=val) + self.app.inform.emit('[success] %s %s' % (_('Offset on the X axis.'), _("Done."))) + return + else: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset X cancelled")) + + def on_offy_key(self): + units = self.app.app_units.lower() + + val_box = FCInputDoubleSpinner(title=_("Offset on Y axis ..."), + text='%s: (%s)' % (_('Enter a distance Value'), str(units)), + min=-10000.0000, max=10000.0000, decimals=self.decimals, + init_val=float(self.app.options['tools_transform_offset_y']), + parent=self.app.ui) + val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/offsety32.png')) + + val, ok = val_box.get_value() + if ok: + self.on_offx(val=val) + self.app.inform.emit('[success] %s...' % _("Offset on Y axis done")) + return + else: + self.app.inform.emit('[success] %s...' % _("Offset on the Y axis canceled")) + + def on_skewx_key(self): + val_box = FCInputDoubleSpinner(title=_("Skew on X axis ..."), + text='%s:' % _('Enter an Angle Value (degrees)'), + min=-359.9999, max=360.0000, decimals=self.decimals, + init_val=float(self.app.options['tools_transform_skew_x']), + parent=self.app.ui) + val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/skewX.png')) + + val, ok = val_box.get_value() + if ok: + self.on_skewx(val=val, ref=3) + self.app.inform.emit('[success] %s...' % _("Skew on X axis done")) + return + else: + self.app.inform.emit('[success] %s...' % _("Skew on X axis canceled")) + + def on_skewy_key(self): + val_box = FCInputDoubleSpinner(title=_("Skew on Y axis ..."), + text='%s:' % _('Enter an Angle Value (degrees)'), + min=-359.9999, max=360.0000, decimals=self.decimals, + init_val=float(self.app.options['tools_transform_skew_y']), + parent=self.app.ui) + val_box.set_icon(QtGui.QIcon(self.app.resource_location + '/skewY.png')) + + val, ok = val_box.get_value() + if ok: + self.on_skewx(val=val, ref=3) + self.app.inform.emit('[success] %s...' % _("Skew on Y axis done")) + return + else: + self.app.inform.emit('[success] %s...' % _("Skew on Y axis canceled")) + + @staticmethod + def alt_bounds(shapelist): + """ + Returns coordinates of rectangular bounds of a selection of shapes + """ + + def bounds_rec(lst): + minx = np.Inf + miny = np.Inf + maxx = -np.Inf + maxy = -np.Inf + + try: + for shp in lst: + minx_, miny_, maxx_, maxy_ = bounds_rec(shp) + minx = min(minx, minx_) + miny = min(miny, miny_) + maxx = max(maxx, maxx_) + maxy = max(maxy, maxy_) + return minx, miny, maxx, maxy + except TypeError: + # it's an object, return its bounds + return lst.bounds() + + return bounds_rec(shapelist) + + +class TransformationEditorUI: + pluginName = _("Transformation") + rotateName = _("Rotate") + skewName = _("Skew/Shear") + scaleName = _("Scale") + flipName = _("Mirror") + offsetName = _("Offset") + bufferName = _("Buffer") + + def __init__(self, layout, transform_class): + self.transform_class = transform_class + self.decimals = self.transform_class.app.decimals + self.layout = layout + + # ## Title + title_label = FCLabel("%s" % self.pluginName, size=16, bold=True) + self.layout.addWidget(title_label) + + # ############################################################################################################# + # PARAMETERS + # ############################################################################################################# + self.transform_label = FCLabel('%s' % _("Parameters"), color='blue', bold=True) + self.layout.addWidget(self.transform_label) + + # ############################################################################################################# + # Reference Frame + # ############################################################################################################# + ref_frame = FCFrame() + self.layout.addWidget(ref_frame) + + ref_grid = GLay(v_spacing=5, h_spacing=3) + ref_frame.setLayout(ref_grid) + + # 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" + "- Min Selection -> the point (minx, miny) of the bounding box of the selection") + ) + self.ref_combo = FCComboBox() + self.ref_items = [_("Origin"), _("Selection"), _("Point"), _("Minimum")] + self.ref_combo.addItems(self.ref_items) + + ref_grid.addWidget(ref_label, 0, 0) + ref_grid.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() + + ref_grid.addWidget(self.point_label, 2, 0) + ref_grid.addWidget(self.point_entry, 2, 1, 1, 2) + + self.point_button = FCButton(_("Add")) + self.point_button.setToolTip( + _("Add point coordinates from clipboard.") + ) + ref_grid.addWidget(self.point_button, 4, 0, 1, 3) + + # separator_line = QtWidgets.QFrame() + # separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) + # separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + # grid0.addWidget(separator_line, 5, 0, 1, 3) + + # ############################################################################################################# + # Rotate Frame + # ############################################################################################################# + rotate_title_lbl = FCLabel('%s' % self.rotateName, color='tomato', bold=True) + self.layout.addWidget(rotate_title_lbl) + + rot_frame = FCFrame() + self.layout.addWidget(rot_frame) + + rot_grid = GLay(v_spacing=5, h_spacing=3) + rot_frame.setLayout(rot_grid) + + # ## Rotate Title + rotate_title_label = FCLabel("%s" % self.rotateName) + rot_grid.addWidget(rotate_title_label, 0, 0, 1, 3) + + self.rotate_label = FCLabel('%s:' % _("Angle")) + self.rotate_label.setToolTip( + _("Angle, 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.transform_class.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) + + rot_grid.addWidget(self.rotate_label, 2, 0) + rot_grid.addWidget(self.rotate_entry, 2, 1) + rot_grid.addWidget(self.rotate_button, 2, 2) + + # ############################################################################################################# + # Skew Frame + # ############################################################################################################# + s_t_lay = QtWidgets.QHBoxLayout() + self.layout.addLayout(s_t_lay) + + skew_title_lbl = FCLabel('%s' % self.skewName, color='teal', bold=True) + s_t_lay.addWidget(skew_title_lbl) + + s_t_lay.addStretch() + + # ## Link Skew factors + 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.") + ) + + s_t_lay.addWidget(self.skew_link_cb) + + skew_frame = FCFrame() + self.layout.addWidget(skew_frame) + + skew_grid = GLay(v_spacing=5, h_spacing=3) + skew_frame.setLayout(skew_grid) + + 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.transform_class.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) + + skew_grid.addWidget(self.skewx_label, 0, 0) + skew_grid.addWidget(self.skewx_entry, 0, 1) + skew_grid.addWidget(self.skewx_button, 0, 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.transform_class.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) + + skew_grid.addWidget(self.skewy_label, 2, 0) + skew_grid.addWidget(self.skewy_entry, 2, 1) + skew_grid.addWidget(self.skewy_button, 2, 2) + + self.ois_sk = OptionalInputSection(self.skew_link_cb, [self.skewy_label, self.skewy_entry, self.skewy_button], + logic=False) + + # ############################################################################################################# + # Scale Frame + # ############################################################################################################# + sc_t_lay = QtWidgets.QHBoxLayout() + self.layout.addLayout(sc_t_lay) + + scale_title_lbl = FCLabel('%s' % self.scaleName, color='magenta', bold=True) + sc_t_lay.addWidget(scale_title_lbl) + + sc_t_lay.addStretch() + + # ## Link Scale factors + self.scale_link_cb = FCCheckBox(_("Link")) + self.scale_link_cb.setToolTip( + _("Link the Y entry to X entry and copy its content.") + ) + sc_t_lay.addWidget(self.scale_link_cb) + + scale_frame = FCFrame() + self.layout.addWidget(scale_frame) + + scale_grid = GLay(v_spacing=5, h_spacing=3) + scale_frame.setLayout(scale_grid) + + self.scalex_label = FCLabel('%s:' % _("X factor")) + self.scalex_label.setToolTip( + _("Factor for scaling on X axis.") + ) + self.scalex_entry = FCDoubleSpinner(callback=self.transform_class.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) + + scale_grid.addWidget(self.scalex_label, 0, 0) + scale_grid.addWidget(self.scalex_entry, 0, 1) + scale_grid.addWidget(self.scalex_button, 0, 2) + + self.scaley_label = FCLabel('%s:' % _("Y factor")) + self.scaley_label.setToolTip( + _("Factor for scaling on Y axis.") + ) + self.scaley_entry = FCDoubleSpinner(callback=self.transform_class.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) + + scale_grid.addWidget(self.scaley_label, 2, 0) + scale_grid.addWidget(self.scaley_entry, 2, 1) + scale_grid.addWidget(self.scaley_button, 2, 2) + + self.ois_s = OptionalInputSection(self.scale_link_cb, + [ + self.scaley_label, + self.scaley_entry, + self.scaley_button + ], logic=False) + + # ############################################################################################################# + # Mirror Frame + # ############################################################################################################# + flip_title_label = FCLabel('%s' % self.flipName, color='brown', bold=True) + self.layout.addWidget(flip_title_label) + + mirror_frame = FCFrame() + self.layout.addWidget(mirror_frame) + + mirror_grid = GLay(v_spacing=5, h_spacing=3) + mirror_frame.setLayout(mirror_grid) + + 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() + mirror_grid.addLayout(hlay0, 0, 0, 1, 3) + + hlay0.addWidget(self.flipx_button) + hlay0.addWidget(self.flipy_button) + + # ############################################################################################################# + # Offset Frame + # ############################################################################################################# + offset_title_lbl = FCLabel('%s' % self.offsetName, color='green', bold=True) + self.layout.addWidget(offset_title_lbl) + + off_frame = FCFrame() + self.layout.addWidget(off_frame) + + off_grid = GLay(v_spacing=5, h_spacing=3) + off_frame.setLayout(off_grid) + + 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.transform_class.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) + + off_grid.addWidget(self.offx_label, 0, 0) + off_grid.addWidget(self.offx_entry, 0, 1) + off_grid.addWidget(self.offx_button, 0, 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.transform_class.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) + + off_grid.addWidget(self.offy_label, 2, 0) + off_grid.addWidget(self.offy_entry, 2, 1) + off_grid.addWidget(self.offy_button, 2, 2) + + # ############################################################################################################# + # Buffer Frame + # ############################################################################################################# + b_t_lay = QtWidgets.QHBoxLayout() + self.layout.addLayout(b_t_lay) + + buffer_title_lbl = FCLabel('%s' % self.bufferName, color='indigo', bold=True) + b_t_lay.addWidget(buffer_title_lbl) + + b_t_lay.addStretch() + + 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.") + ) + + b_t_lay.addWidget(self.buffer_rounded_cb) + + buff_frame = FCFrame() + self.layout.addWidget(buff_frame) + + buff_grid = GLay(v_spacing=5, h_spacing=3) + buff_frame.setLayout(buff_grid) + + 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.transform_class.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(-10000.0000, 10000.0000) + + 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) + + buff_grid.addWidget(self.buffer_label, 0, 0) + buff_grid.addWidget(self.buffer_entry, 0, 1) + buff_grid.addWidget(self.buffer_button, 0, 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.transform_class.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) + + buff_grid.addWidget(self.buffer_factor_label, 2, 0) + buff_grid.addWidget(self.buffer_factor_entry, 2, 1) + buff_grid.addWidget(self.buffer_factor_button, 2, 2) + + GLay.set_common_column_size( + [ref_grid, rot_grid, skew_grid, scale_grid, mirror_grid, off_grid, buff_grid], 0) + + self.layout.addStretch() + + self.ref_combo.currentIndexChanged.connect(self.on_reference_changed) + + 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() + elif index == 2: # "Point" reference + self.point_label.show() + self.point_entry.show() + self.point_button.show() diff --git a/appEditors/grb_plugins/__init__.py b/appEditors/grb_plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index fc1e47be..b8d6d757 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -851,6 +851,10 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_add_buffer_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), '%s\t%s' % (_('Buffer'), _('B'))) + self.grb_simplification_menuitem = self.geo_editor_menu.addAction( + QtGui.QIcon(self.app.resource_location + '/simplify32.png'), + '%s\t%s' % (_("Simplification"), '') + ) self.grb_add_scale_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/scale32.png'), '%s\t%s' % (_('Scale'), _('S'))) @@ -1331,6 +1335,8 @@ class MainGUI(QtWidgets.QMainWindow): self.aperture_buffer_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _('Buffer')) + self.aperture_simplify_btn = self.grb_edit_toolbar.addAction( + QtGui.QIcon(self.app.resource_location + '/simplify32.png'), _('Simplification')) self.aperture_scale_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/scale32.png'), _('Scale')) self.aperture_markarea_btn = self.grb_edit_toolbar.addAction( @@ -2717,6 +2723,8 @@ class MainGUI(QtWidgets.QMainWindow): self.aperture_buffer_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _('Buffer')) + self.aperture_simplify_btn = self.grb_edit_toolbar.addAction( + QtGui.QIcon(self.app.resource_location + '/simplify32.png'), _('Simplification')) self.aperture_scale_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/scale32.png'), _('Scale')) self.aperture_markarea_btn = self.grb_edit_toolbar.addAction( From 77c752701f8a1e12f69ef838fae5cc250912023f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 20 May 2022 21:19:52 +0300 Subject: [PATCH 02/55] - refactored the code for the file registration and for the keywords update into its own class to make the main class smaller - when creating new files the file names are added to the beginning of the keywords list for the usage of Qt auto-completer --- CHANGELOG.md | 2 + appCommon/RegisterFileKeywords.py | 710 +++++++++++++++++++++ appEditors/AppGerberEditor.py | 10 +- appEditors/AppTextEditor.py | 2 +- appGUI/preferences/PreferencesUIManager.py | 2 +- appMain.py | 656 ++----------------- appObjects/AppObject.py | 5 - appObjects/AppObjectTemplate.py | 7 +- appObjects/ObjectCollection.py | 24 +- appPlugins/ToolShell.py | 2 - 10 files changed, 777 insertions(+), 643 deletions(-) create mode 100644 appCommon/RegisterFileKeywords.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b1a2cdc..d4ba6b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ CHANGELOG for FlatCAM Evo beta 20.05.2022 - small fix for a bug that interfere with running the 2D graphic mode +- refactored the code for the file registration and for the keywords update into its own class to make the main class smaller +- when creating new files the file names are added to the beginning of the keywords list for the usage of Qt auto-completer 19.05.2022 diff --git a/appCommon/RegisterFileKeywords.py b/appCommon/RegisterFileKeywords.py new file mode 100644 index 00000000..d09da33a --- /dev/null +++ b/appCommon/RegisterFileKeywords.py @@ -0,0 +1,710 @@ + +from PyQt6.QtCore import pyqtSignal +from PyQt6 import QtCore + +from dataclasses import dataclass +import ctypes +import winreg +from copy import deepcopy +import os +import sys +import typing + +if typing.TYPE_CHECKING: + import appMain + + +@dataclass +class KeyWords: + """ + Holds the keywords recognized by the application + """ + + tcl_commands = [ + 'add_aperture', 'add_circle', 'add_drill', 'add_poly', 'add_polygon', 'add_polyline', + 'add_rectangle', 'add_rect', 'add_slot', + 'aligndrill', 'aligndrillgrid', 'bbox', 'buffer', 'clear', 'cncjob', 'cutout', + 'del', 'drillcncjob', 'export_dxf', 'edxf', 'export_excellon', + 'export_exc', + 'export_gcode', 'export_gerber', 'export_svg', 'ext', 'exteriors', 'follow', + 'geo_union', 'geocutout', 'get_active', 'get_bounds', 'get_names', 'get_path', + 'get_sys', 'help', + 'interiors', 'isolate', 'join_excellon', + 'join_geometry', 'list_sys', 'list_pp', 'milld', 'mills', 'milldrills', 'millslots', + 'mirror', 'ncc', + 'ncr', 'new', 'new_geometry', 'new_gerber', 'new_excellon', 'non_copper_regions', + 'offset', + 'open_dxf', 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'open_svg', + 'options', 'origin', + 'paint', 'panelize', 'plot_all', 'plot_objects', 'plot_status', 'quit_app', + 'save', 'save_project', + 'save_sys', 'scale', 'set_active', 'set_origin', 'set_path', 'set_sys', + 'skew', 'subtract_poly', 'subtract_rectangle', + 'version', 'write_gcode' + ] + + # those need to be duplicated in self.options["util_autocomplete_keywords"] but within a string + default_keywords = [ + 'Berta_CNC', 'Default_no_M6', 'Desktop', 'Documents', 'FlatConfig', 'FlatPrj', + 'False', 'GRBL_11', 'GRL_11_no_M6', 'GRBL_laser', 'grbl_laser_eleks_drd', + 'GRBL_laser_z', 'ISEL_CNC', 'ISEL_ICP_CNC', + 'Line_xyz', 'Marlin', + 'Marlin_laser_FAN_pin', 'Marlin_laser_Spindle_pin', 'NCCAD9', 'Marius', 'My Documents', + 'Paste_1', 'Repetier', 'Roland_MDX_20', 'Roland_MDX_540', + 'Toolchange_Manual', 'Toolchange_Probe_MACH3', + 'True', 'Users', + 'all', 'auto', 'axis', + 'axisoffset', 'box', 'center_x', 'center_y', 'center', 'columns', 'combine', 'connect', + 'contour', 'default', + 'depthperpass', 'dia', 'diatol', 'dist', 'drilled_dias', 'drillz', 'dpp', + 'dwelltime', 'extracut_length', 'endxy', 'endz', 'f', 'factor', 'feedrate', + 'feedrate_z', 'gridoffsety', 'gridx', 'gridy', + 'has_offset', 'holes', 'hpgl', 'iso_type', 'join', 'keep_scripts', + 'las_min_pwr', 'las_power', 'margin', 'marlin', 'method', + 'milled_dias', 'minoffset', 'min_bounds', 'name', 'offset', 'opt_type', 'order', + 'outname', 'overlap', 'obj_name', + 'p_coords', 'passes', 'postamble', 'pp', 'ppname_e', 'ppname_g', + 'preamble', 'radius', 'ref', 'rest', 'rows', 'shellvar_', 'scale_factor', + 'spacing_columns', + 'spacing_rows', 'spindlespeed', 'startz', 'startxy', + 'toolchange_xy', 'toolchangez', 'travelz', + 'tooldia', 'use_threads', 'value', + 'x', 'x0', 'x1', 'x_dist', 'y', 'y0', 'y1', 'y_dist', 'z_cut', 'z_move' + ] + + tcl_keywords = [ + 'after', 'append', 'apply', 'argc', 'argv', 'argv0', 'array', 'attemptckalloc', 'attemptckrealloc', + 'auto_execok', 'auto_import', 'auto_load', 'auto_mkindex', 'auto_path', 'auto_qualify', 'auto_reset', + 'bgerror', 'binary', 'break', 'case', 'catch', 'cd', 'chan', 'ckalloc', 'ckfree', 'ckrealloc', 'clock', + 'close', 'concat', 'continue', 'coroutine', 'dde', 'dict', 'encoding', 'env', 'eof', 'error', 'errorCode', + 'errorInfo', 'eval', 'exec', 'exit', 'expr', 'fblocked', 'fconfigure', 'fcopy', 'file', 'fileevent', + 'filename', 'flush', 'for', 'foreach', 'format', 'gets', 'glob', 'global', 'history', 'http', 'if', 'incr', + 'info', 'interp', 'join', 'lappend', 'lassign', 'lindex', 'linsert', 'list', 'llength', 'load', 'lrange', + 'lrepeat', 'lreplace', 'lreverse', 'lsearch', 'lset', 'lsort', 'mathfunc', 'mathop', 'memory', 'msgcat', + 'my', 'namespace', 'next', 'nextto', 'open', 'package', 'parray', 'pid', 'pkg_mkIndex', 'platform', + 'proc', 'puts', 'pwd', 're_syntax', 'read', 'refchan', 'regexp', 'registry', 'regsub', 'rename', 'return', + 'safe', 'scan', 'seek', 'self', 'set', 'socket', 'source', 'split', 'string', 'subst', 'switch', + 'tailcall', 'Tcl', 'Tcl_Access', 'Tcl_AddErrorInfo', 'Tcl_AddObjErrorInfo', 'Tcl_AlertNotifier', + 'Tcl_Alloc', 'Tcl_AllocHashEntryProc', 'Tcl_AllocStatBuf', 'Tcl_AllowExceptions', 'Tcl_AppendAllObjTypes', + 'Tcl_AppendElement', 'Tcl_AppendExportList', 'Tcl_AppendFormatToObj', 'Tcl_AppendLimitedToObj', + 'Tcl_AppendObjToErrorInfo', 'Tcl_AppendObjToObj', 'Tcl_AppendPrintfToObj', 'Tcl_AppendResult', + 'Tcl_AppendResultVA', 'Tcl_AppendStringsToObj', 'Tcl_AppendStringsToObjVA', 'Tcl_AppendToObj', + 'Tcl_AppendUnicodeToObj', 'Tcl_AppInit', 'Tcl_AppInitProc', 'Tcl_ArgvInfo', 'Tcl_AsyncCreate', + 'Tcl_AsyncDelete', 'Tcl_AsyncInvoke', 'Tcl_AsyncMark', 'Tcl_AsyncProc', 'Tcl_AsyncReady', + 'Tcl_AttemptAlloc', 'Tcl_AttemptRealloc', 'Tcl_AttemptSetObjLength', 'Tcl_BackgroundError', + 'Tcl_BackgroundException', 'Tcl_Backslash', 'Tcl_BadChannelOption', 'Tcl_CallWhenDeleted', 'Tcl_Canceled', + 'Tcl_CancelEval', 'Tcl_CancelIdleCall', 'Tcl_ChannelBlockModeProc', 'Tcl_ChannelBuffered', + 'Tcl_ChannelClose2Proc', 'Tcl_ChannelCloseProc', 'Tcl_ChannelFlushProc', 'Tcl_ChannelGetHandleProc', + 'Tcl_ChannelGetOptionProc', 'Tcl_ChannelHandlerProc', 'Tcl_ChannelInputProc', 'Tcl_ChannelName', + 'Tcl_ChannelOutputProc', 'Tcl_ChannelProc', 'Tcl_ChannelSeekProc', 'Tcl_ChannelSetOptionProc', + 'Tcl_ChannelThreadActionProc', 'Tcl_ChannelTruncateProc', 'Tcl_ChannelType', 'Tcl_ChannelVersion', + 'Tcl_ChannelWatchProc', 'Tcl_ChannelWideSeekProc', 'Tcl_Chdir', 'Tcl_ClassGetMetadata', + 'Tcl_ClassSetConstructor', 'Tcl_ClassSetDestructor', 'Tcl_ClassSetMetadata', 'Tcl_ClearChannelHandlers', + 'Tcl_CloneProc', 'Tcl_Close', 'Tcl_CloseProc', 'Tcl_CmdDeleteProc', 'Tcl_CmdInfo', + 'Tcl_CmdObjTraceDeleteProc', 'Tcl_CmdObjTraceProc', 'Tcl_CmdProc', 'Tcl_CmdTraceProc', + 'Tcl_CommandComplete', 'Tcl_CommandTraceInfo', 'Tcl_CommandTraceProc', 'Tcl_CompareHashKeysProc', + 'Tcl_Concat', 'Tcl_ConcatObj', 'Tcl_ConditionFinalize', 'Tcl_ConditionNotify', 'Tcl_ConditionWait', + 'Tcl_Config', 'Tcl_ConvertCountedElement', 'Tcl_ConvertElement', 'Tcl_ConvertToType', + 'Tcl_CopyObjectInstance', 'Tcl_CreateAlias', 'Tcl_CreateAliasObj', 'Tcl_CreateChannel', + 'Tcl_CreateChannelHandler', 'Tcl_CreateCloseHandler', 'Tcl_CreateCommand', 'Tcl_CreateEncoding', + 'Tcl_CreateEnsemble', 'Tcl_CreateEventSource', 'Tcl_CreateExitHandler', 'Tcl_CreateFileHandler', + 'Tcl_CreateHashEntry', 'Tcl_CreateInterp', 'Tcl_CreateMathFunc', 'Tcl_CreateNamespace', + 'Tcl_CreateObjCommand', 'Tcl_CreateObjTrace', 'Tcl_CreateSlave', 'Tcl_CreateThread', + 'Tcl_CreateThreadExitHandler', 'Tcl_CreateTimerHandler', 'Tcl_CreateTrace', + 'Tcl_CutChannel', 'Tcl_DecrRefCount', 'Tcl_DeleteAssocData', 'Tcl_DeleteChannelHandler', + 'Tcl_DeleteCloseHandler', 'Tcl_DeleteCommand', 'Tcl_DeleteCommandFromToken', 'Tcl_DeleteEvents', + 'Tcl_DeleteEventSource', 'Tcl_DeleteExitHandler', 'Tcl_DeleteFileHandler', 'Tcl_DeleteHashEntry', + 'Tcl_DeleteHashTable', 'Tcl_DeleteInterp', 'Tcl_DeleteNamespace', 'Tcl_DeleteThreadExitHandler', + 'Tcl_DeleteTimerHandler', 'Tcl_DeleteTrace', 'Tcl_DetachChannel', 'Tcl_DetachPids', 'Tcl_DictObjDone', + 'Tcl_DictObjFirst', 'Tcl_DictObjGet', 'Tcl_DictObjNext', 'Tcl_DictObjPut', 'Tcl_DictObjPutKeyList', + 'Tcl_DictObjRemove', 'Tcl_DictObjRemoveKeyList', 'Tcl_DictObjSize', 'Tcl_DiscardInterpState', + 'Tcl_DiscardResult', 'Tcl_DontCallWhenDeleted', 'Tcl_DoOneEvent', 'Tcl_DoWhenIdle', + 'Tcl_DriverBlockModeProc', 'Tcl_DriverClose2Proc', 'Tcl_DriverCloseProc', 'Tcl_DriverFlushProc', + 'Tcl_DriverGetHandleProc', 'Tcl_DriverGetOptionProc', 'Tcl_DriverHandlerProc', 'Tcl_DriverInputProc', + 'Tcl_DriverOutputProc', 'Tcl_DriverSeekProc', 'Tcl_DriverSetOptionProc', 'Tcl_DriverThreadActionProc', + 'Tcl_DriverTruncateProc', 'Tcl_DriverWatchProc', 'Tcl_DriverWideSeekProc', 'Tcl_DStringAppend', + 'Tcl_DStringAppendElement', 'Tcl_DStringEndSublist', 'Tcl_DStringFree', 'Tcl_DStringGetResult', + 'Tcl_DStringInit', 'Tcl_DStringLength', 'Tcl_DStringResult', 'Tcl_DStringSetLength', + 'Tcl_DStringStartSublist', 'Tcl_DStringTrunc', 'Tcl_DStringValue', 'Tcl_DumpActiveMemory', + 'Tcl_DupInternalRepProc', 'Tcl_DuplicateObj', 'Tcl_EncodingConvertProc', 'Tcl_EncodingFreeProc', + 'Tcl_EncodingType', 'tcl_endOfWord', 'Tcl_Eof', 'Tcl_ErrnoId', 'Tcl_ErrnoMsg', 'Tcl_Eval', 'Tcl_EvalEx', + 'Tcl_EvalFile', 'Tcl_EvalObjEx', 'Tcl_EvalObjv', 'Tcl_EvalTokens', 'Tcl_EvalTokensStandard', 'Tcl_Event', + 'Tcl_EventCheckProc', 'Tcl_EventDeleteProc', 'Tcl_EventProc', 'Tcl_EventSetupProc', 'Tcl_EventuallyFree', + 'Tcl_Exit', 'Tcl_ExitProc', 'Tcl_ExitThread', 'Tcl_Export', 'Tcl_ExposeCommand', 'Tcl_ExprBoolean', + 'Tcl_ExprBooleanObj', 'Tcl_ExprDouble', 'Tcl_ExprDoubleObj', 'Tcl_ExprLong', 'Tcl_ExprLongObj', + 'Tcl_ExprObj', 'Tcl_ExprString', 'Tcl_ExternalToUtf', 'Tcl_ExternalToUtfDString', 'Tcl_FileProc', + 'Tcl_Filesystem', 'Tcl_Finalize', 'Tcl_FinalizeNotifier', 'Tcl_FinalizeThread', 'Tcl_FindCommand', + 'Tcl_FindEnsemble', 'Tcl_FindExecutable', 'Tcl_FindHashEntry', 'tcl_findLibrary', 'Tcl_FindNamespace', + 'Tcl_FirstHashEntry', 'Tcl_Flush', 'Tcl_ForgetImport', 'Tcl_Format', 'Tcl_FreeHashEntryProc', + 'Tcl_FreeInternalRepProc', 'Tcl_FreeParse', 'Tcl_FreeProc', 'Tcl_FreeResult', + 'Tcl_Free·\xa0Tcl_FreeEncoding', 'Tcl_FSAccess', 'Tcl_FSAccessProc', 'Tcl_FSChdir', + 'Tcl_FSChdirProc', 'Tcl_FSConvertToPathType', 'Tcl_FSCopyDirectory', 'Tcl_FSCopyDirectoryProc', + 'Tcl_FSCopyFile', 'Tcl_FSCopyFileProc', 'Tcl_FSCreateDirectory', 'Tcl_FSCreateDirectoryProc', + 'Tcl_FSCreateInternalRepProc', 'Tcl_FSData', 'Tcl_FSDeleteFile', 'Tcl_FSDeleteFileProc', + 'Tcl_FSDupInternalRepProc', 'Tcl_FSEqualPaths', 'Tcl_FSEvalFile', 'Tcl_FSEvalFileEx', + 'Tcl_FSFileAttrsGet', 'Tcl_FSFileAttrsGetProc', 'Tcl_FSFileAttrsSet', 'Tcl_FSFileAttrsSetProc', + 'Tcl_FSFileAttrStrings', 'Tcl_FSFileSystemInfo', 'Tcl_FSFilesystemPathTypeProc', + 'Tcl_FSFilesystemSeparatorProc', 'Tcl_FSFreeInternalRepProc', 'Tcl_FSGetCwd', 'Tcl_FSGetCwdProc', + 'Tcl_FSGetFileSystemForPath', 'Tcl_FSGetInternalRep', 'Tcl_FSGetNativePath', 'Tcl_FSGetNormalizedPath', + 'Tcl_FSGetPathType', 'Tcl_FSGetTranslatedPath', 'Tcl_FSGetTranslatedStringPath', + 'Tcl_FSInternalToNormalizedProc', 'Tcl_FSJoinPath', 'Tcl_FSJoinToPath', 'Tcl_FSLinkProc', + 'Tcl_FSLink·\xa0Tcl_FSListVolumes', 'Tcl_FSListVolumesProc', 'Tcl_FSLoadFile', 'Tcl_FSLoadFileProc', + 'Tcl_FSLstat', 'Tcl_FSLstatProc', 'Tcl_FSMatchInDirectory', 'Tcl_FSMatchInDirectoryProc', + 'Tcl_FSMountsChanged', 'Tcl_FSNewNativePath', 'Tcl_FSNormalizePathProc', 'Tcl_FSOpenFileChannel', + 'Tcl_FSOpenFileChannelProc', 'Tcl_FSPathInFilesystemProc', 'Tcl_FSPathSeparator', 'Tcl_FSRegister', + 'Tcl_FSRemoveDirectory', 'Tcl_FSRemoveDirectoryProc', 'Tcl_FSRenameFile', 'Tcl_FSRenameFileProc', + 'Tcl_FSSplitPath', 'Tcl_FSStat', 'Tcl_FSStatProc', 'Tcl_FSUnloadFile', 'Tcl_FSUnloadFileProc', + 'Tcl_FSUnregister', 'Tcl_FSUtime', 'Tcl_FSUtimeProc', 'Tcl_GetAccessTimeFromStat', 'Tcl_GetAlias', + 'Tcl_GetAliasObj', 'Tcl_GetAssocData', 'Tcl_GetBignumFromObj', 'Tcl_GetBlocksFromStat', + 'Tcl_GetBlockSizeFromStat', 'Tcl_GetBoolean', 'Tcl_GetBooleanFromObj', 'Tcl_GetByteArrayFromObj', + 'Tcl_GetChangeTimeFromStat', 'Tcl_GetChannel', 'Tcl_GetChannelBufferSize', 'Tcl_GetChannelError', + 'Tcl_GetChannelErrorInterp', 'Tcl_GetChannelHandle', 'Tcl_GetChannelInstanceData', 'Tcl_GetChannelMode', + 'Tcl_GetChannelName', 'Tcl_GetChannelNames', 'Tcl_GetChannelNamesEx', 'Tcl_GetChannelOption', + 'Tcl_GetChannelThread', 'Tcl_GetChannelType', 'Tcl_GetCharLength', 'Tcl_GetClassAsObject', + 'Tcl_GetCommandFromObj', 'Tcl_GetCommandFullName', 'Tcl_GetCommandInfo', 'Tcl_GetCommandInfoFromToken', + 'Tcl_GetCommandName', 'Tcl_GetCurrentNamespace', 'Tcl_GetCurrentThread', 'Tcl_GetCwd', + 'Tcl_GetDefaultEncodingDir', 'Tcl_GetDeviceTypeFromStat', 'Tcl_GetDouble', 'Tcl_GetDoubleFromObj', + 'Tcl_GetEncoding', 'Tcl_GetEncodingFromObj', 'Tcl_GetEncodingName', 'Tcl_GetEncodingNameFromEnvironment', + 'Tcl_GetEncodingNames', 'Tcl_GetEncodingSearchPath', 'Tcl_GetEnsembleFlags', 'Tcl_GetEnsembleMappingDict', + 'Tcl_GetEnsembleNamespace', 'Tcl_GetEnsembleParameterList', 'Tcl_GetEnsembleSubcommandList', + 'Tcl_GetEnsembleUnknownHandler', 'Tcl_GetErrno', 'Tcl_GetErrorLine', 'Tcl_GetFSDeviceFromStat', + 'Tcl_GetFSInodeFromStat', 'Tcl_GetGlobalNamespace', 'Tcl_GetGroupIdFromStat', 'Tcl_GetHashKey', + 'Tcl_GetHashValue', 'Tcl_GetHostName', 'Tcl_GetIndexFromObj', 'Tcl_GetIndexFromObjStruct', 'Tcl_GetInt', + 'Tcl_GetInterpPath', 'Tcl_GetIntFromObj', 'Tcl_GetLinkCountFromStat', 'Tcl_GetLongFromObj', + 'Tcl_GetMaster', 'Tcl_GetMathFuncInfo', 'Tcl_GetModeFromStat', 'Tcl_GetModificationTimeFromStat', + 'Tcl_GetNameOfExecutable', 'Tcl_GetNamespaceUnknownHandler', 'Tcl_GetObjectAsClass', 'Tcl_GetObjectCommand', + 'Tcl_GetObjectFromObj', 'Tcl_GetObjectName', 'Tcl_GetObjectNamespace', 'Tcl_GetObjResult', 'Tcl_GetObjType', + 'Tcl_GetOpenFile', 'Tcl_GetPathType', 'Tcl_GetRange', 'Tcl_GetRegExpFromObj', 'Tcl_GetReturnOptions', + 'Tcl_Gets', 'Tcl_GetServiceMode', 'Tcl_GetSizeFromStat', 'Tcl_GetSlave', 'Tcl_GetsObj', + 'Tcl_GetStackedChannel', 'Tcl_GetStartupScript', 'Tcl_GetStdChannel', 'Tcl_GetString', + 'Tcl_GetStringFromObj', 'Tcl_GetStringResult', 'Tcl_GetThreadData', 'Tcl_GetTime', 'Tcl_GetTopChannel', + 'Tcl_GetUniChar', 'Tcl_GetUnicode', 'Tcl_GetUnicodeFromObj', 'Tcl_GetUserIdFromStat', 'Tcl_GetVar', + 'Tcl_GetVar2', 'Tcl_GetVar2Ex', 'Tcl_GetVersion', 'Tcl_GetWideIntFromObj', 'Tcl_GlobalEval', + 'Tcl_GlobalEvalObj', 'Tcl_GlobTypeData', 'Tcl_HashKeyType', 'Tcl_HashStats', 'Tcl_HideCommand', + 'Tcl_IdleProc', 'Tcl_Import', 'Tcl_IncrRefCount', 'Tcl_Init', 'Tcl_InitCustomHashTable', + 'Tcl_InitHashTable', 'Tcl_InitMemory', 'Tcl_InitNotifier', 'Tcl_InitObjHashTable', 'Tcl_InitStubs', + 'Tcl_InputBlocked', 'Tcl_InputBuffered', 'tcl_interactive', 'Tcl_Interp', 'Tcl_InterpActive', + 'Tcl_InterpDeleted', 'Tcl_InterpDeleteProc', 'Tcl_InvalidateStringRep', 'Tcl_IsChannelExisting', + 'Tcl_IsChannelRegistered', 'Tcl_IsChannelShared', 'Tcl_IsEnsemble', 'Tcl_IsSafe', 'Tcl_IsShared', + 'Tcl_IsStandardChannel', 'Tcl_JoinPath', 'Tcl_JoinThread', 'tcl_library', 'Tcl_LimitAddHandler', + 'Tcl_LimitCheck', 'Tcl_LimitExceeded', 'Tcl_LimitGetCommands', 'Tcl_LimitGetGranularity', + 'Tcl_LimitGetTime', 'Tcl_LimitHandlerDeleteProc', 'Tcl_LimitHandlerProc', 'Tcl_LimitReady', + 'Tcl_LimitRemoveHandler', 'Tcl_LimitSetCommands', 'Tcl_LimitSetGranularity', 'Tcl_LimitSetTime', + 'Tcl_LimitTypeEnabled', 'Tcl_LimitTypeExceeded', 'Tcl_LimitTypeReset', 'Tcl_LimitTypeSet', + 'Tcl_LinkVar', 'Tcl_ListMathFuncs', 'Tcl_ListObjAppendElement', 'Tcl_ListObjAppendList', + 'Tcl_ListObjGetElements', 'Tcl_ListObjIndex', 'Tcl_ListObjLength', 'Tcl_ListObjReplace', + 'Tcl_LogCommandInfo', 'Tcl_Main', 'Tcl_MainLoopProc', 'Tcl_MakeFileChannel', 'Tcl_MakeSafe', + 'Tcl_MakeTcpClientChannel', 'Tcl_MathProc', 'TCL_MEM_DEBUG', 'Tcl_Merge', 'Tcl_MethodCallProc', + 'Tcl_MethodDeclarerClass', 'Tcl_MethodDeclarerObject', 'Tcl_MethodDeleteProc', 'Tcl_MethodIsPublic', + 'Tcl_MethodIsType', 'Tcl_MethodName', 'Tcl_MethodType', 'Tcl_MutexFinalize', 'Tcl_MutexLock', + 'Tcl_MutexUnlock', 'Tcl_NamespaceDeleteProc', 'Tcl_NewBignumObj', 'Tcl_NewBooleanObj', + 'Tcl_NewByteArrayObj', 'Tcl_NewDictObj', 'Tcl_NewDoubleObj', 'Tcl_NewInstanceMethod', 'Tcl_NewIntObj', + 'Tcl_NewListObj', 'Tcl_NewLongObj', 'Tcl_NewMethod', 'Tcl_NewObj', 'Tcl_NewObjectInstance', + 'Tcl_NewStringObj', 'Tcl_NewUnicodeObj', 'Tcl_NewWideIntObj', 'Tcl_NextHashEntry', 'tcl_nonwordchars', + 'Tcl_NotifierProcs', 'Tcl_NotifyChannel', 'Tcl_NRAddCallback', 'Tcl_NRCallObjProc', 'Tcl_NRCmdSwap', + 'Tcl_NRCreateCommand', 'Tcl_NREvalObj', 'Tcl_NREvalObjv', 'Tcl_NumUtfChars', 'Tcl_Obj', 'Tcl_ObjCmdProc', + 'Tcl_ObjectContextInvokeNext', 'Tcl_ObjectContextIsFiltering', 'Tcl_ObjectContextMethod', + 'Tcl_ObjectContextObject', 'Tcl_ObjectContextSkippedArgs', 'Tcl_ObjectDeleted', 'Tcl_ObjectGetMetadata', + 'Tcl_ObjectGetMethodNameMapper', 'Tcl_ObjectMapMethodNameProc', 'Tcl_ObjectMetadataDeleteProc', + 'Tcl_ObjectSetMetadata', 'Tcl_ObjectSetMethodNameMapper', 'Tcl_ObjGetVar2', 'Tcl_ObjPrintf', + 'Tcl_ObjSetVar2', 'Tcl_ObjType', 'Tcl_OpenCommandChannel', 'Tcl_OpenFileChannel', 'Tcl_OpenTcpClient', + 'Tcl_OpenTcpServer', 'Tcl_OutputBuffered', 'Tcl_PackageInitProc', 'Tcl_PackageUnloadProc', 'Tcl_Panic', + 'Tcl_PanicProc', 'Tcl_PanicVA', 'Tcl_ParseArgsObjv', 'Tcl_ParseBraces', 'Tcl_ParseCommand', 'Tcl_ParseExpr', + 'Tcl_ParseQuotedString', 'Tcl_ParseVar', 'Tcl_ParseVarName', 'tcl_patchLevel', 'tcl_pkgPath', + 'Tcl_PkgPresent', 'Tcl_PkgPresentEx', 'Tcl_PkgProvide', 'Tcl_PkgProvideEx', 'Tcl_PkgRequire', + 'Tcl_PkgRequireEx', 'Tcl_PkgRequireProc', 'tcl_platform', 'Tcl_PosixError', 'tcl_precision', + 'Tcl_Preserve', 'Tcl_PrintDouble', 'Tcl_PutEnv', 'Tcl_QueryTimeProc', 'Tcl_QueueEvent', 'tcl_rcFileName', + 'Tcl_Read', 'Tcl_ReadChars', 'Tcl_ReadRaw', 'Tcl_Realloc', 'Tcl_ReapDetachedProcs', 'Tcl_RecordAndEval', + 'Tcl_RecordAndEvalObj', 'Tcl_RegExpCompile', 'Tcl_RegExpExec', 'Tcl_RegExpExecObj', 'Tcl_RegExpGetInfo', + 'Tcl_RegExpIndices', 'Tcl_RegExpInfo', 'Tcl_RegExpMatch', 'Tcl_RegExpMatchObj', 'Tcl_RegExpRange', + 'Tcl_RegisterChannel', 'Tcl_RegisterConfig', 'Tcl_RegisterObjType', 'Tcl_Release', 'Tcl_ResetResult', + 'Tcl_RestoreInterpState', 'Tcl_RestoreResult', 'Tcl_SaveInterpState', 'Tcl_SaveResult', 'Tcl_ScaleTimeProc', + 'Tcl_ScanCountedElement', 'Tcl_ScanElement', 'Tcl_Seek', 'Tcl_ServiceAll', 'Tcl_ServiceEvent', + 'Tcl_ServiceModeHook', 'Tcl_SetAssocData', 'Tcl_SetBignumObj', 'Tcl_SetBooleanObj', + 'Tcl_SetByteArrayLength', 'Tcl_SetByteArrayObj', 'Tcl_SetChannelBufferSize', 'Tcl_SetChannelError', + 'Tcl_SetChannelErrorInterp', 'Tcl_SetChannelOption', 'Tcl_SetCommandInfo', 'Tcl_SetCommandInfoFromToken', + 'Tcl_SetDefaultEncodingDir', 'Tcl_SetDoubleObj', 'Tcl_SetEncodingSearchPath', 'Tcl_SetEnsembleFlags', + 'Tcl_SetEnsembleMappingDict', 'Tcl_SetEnsembleParameterList', 'Tcl_SetEnsembleSubcommandList', + 'Tcl_SetEnsembleUnknownHandler', 'Tcl_SetErrno', 'Tcl_SetErrorCode', 'Tcl_SetErrorCodeVA', + 'Tcl_SetErrorLine', 'Tcl_SetExitProc', 'Tcl_SetFromAnyProc', 'Tcl_SetHashValue', 'Tcl_SetIntObj', + 'Tcl_SetListObj', 'Tcl_SetLongObj', 'Tcl_SetMainLoop', 'Tcl_SetMaxBlockTime', + 'Tcl_SetNamespaceUnknownHandler', 'Tcl_SetNotifier', 'Tcl_SetObjErrorCode', 'Tcl_SetObjLength', + 'Tcl_SetObjResult', 'Tcl_SetPanicProc', 'Tcl_SetRecursionLimit', 'Tcl_SetResult', 'Tcl_SetReturnOptions', + 'Tcl_SetServiceMode', 'Tcl_SetStartupScript', 'Tcl_SetStdChannel', 'Tcl_SetStringObj', + 'Tcl_SetSystemEncoding', 'Tcl_SetTimeProc', 'Tcl_SetTimer', 'Tcl_SetUnicodeObj', 'Tcl_SetVar', + 'Tcl_SetVar2', 'Tcl_SetVar2Ex', 'Tcl_SetWideIntObj', 'Tcl_SignalId', 'Tcl_SignalMsg', 'Tcl_Sleep', + 'Tcl_SourceRCFile', 'Tcl_SpliceChannel', 'Tcl_SplitList', 'Tcl_SplitPath', 'Tcl_StackChannel', + 'Tcl_StandardChannels', 'tcl_startOfNextWord', 'tcl_startOfPreviousWord', 'Tcl_Stat', 'Tcl_StaticPackage', + 'Tcl_StringCaseMatch', 'Tcl_StringMatch', 'Tcl_SubstObj', 'Tcl_TakeBignumFromObj', 'Tcl_TcpAcceptProc', + 'Tcl_Tell', 'Tcl_ThreadAlert', 'Tcl_ThreadQueueEvent', 'Tcl_Time', 'Tcl_TimerProc', 'Tcl_Token', + 'Tcl_TraceCommand', 'tcl_traceCompile', 'tcl_traceEval', 'Tcl_TraceVar', 'Tcl_TraceVar2', + 'Tcl_TransferResult', 'Tcl_TranslateFileName', 'Tcl_TruncateChannel', 'Tcl_Ungets', 'Tcl_UniChar', + 'Tcl_UniCharAtIndex', 'Tcl_UniCharCaseMatch', 'Tcl_UniCharIsAlnum', 'Tcl_UniCharIsAlpha', + 'Tcl_UniCharIsControl', 'Tcl_UniCharIsDigit', 'Tcl_UniCharIsGraph', 'Tcl_UniCharIsLower', + 'Tcl_UniCharIsPrint', 'Tcl_UniCharIsPunct', 'Tcl_UniCharIsSpace', 'Tcl_UniCharIsUpper', + 'Tcl_UniCharIsWordChar', 'Tcl_UniCharLen', 'Tcl_UniCharNcasecmp', 'Tcl_UniCharNcmp', 'Tcl_UniCharToLower', + 'Tcl_UniCharToTitle', 'Tcl_UniCharToUpper', 'Tcl_UniCharToUtf', 'Tcl_UniCharToUtfDString', 'Tcl_UnlinkVar', + 'Tcl_UnregisterChannel', 'Tcl_UnsetVar', 'Tcl_UnsetVar2', 'Tcl_UnstackChannel', 'Tcl_UntraceCommand', + 'Tcl_UntraceVar', 'Tcl_UntraceVar2', 'Tcl_UpdateLinkedVar', 'Tcl_UpdateStringProc', 'Tcl_UpVar', + 'Tcl_UpVar2', 'Tcl_UtfAtIndex', 'Tcl_UtfBackslash', 'Tcl_UtfCharComplete', 'Tcl_UtfFindFirst', + 'Tcl_UtfFindLast', 'Tcl_UtfNext', 'Tcl_UtfPrev', 'Tcl_UtfToExternal', 'Tcl_UtfToExternalDString', + 'Tcl_UtfToLower', 'Tcl_UtfToTitle', 'Tcl_UtfToUniChar', 'Tcl_UtfToUniCharDString', 'Tcl_UtfToUpper', + 'Tcl_ValidateAllMemory', 'Tcl_Value', 'Tcl_VarEval', 'Tcl_VarEvalVA', 'Tcl_VarTraceInfo', + 'Tcl_VarTraceInfo2', 'Tcl_VarTraceProc', 'tcl_version', 'Tcl_WaitForEvent', 'Tcl_WaitPid', + 'Tcl_WinTCharToUtf', 'Tcl_WinUtfToTChar', 'tcl_wordBreakAfter', 'tcl_wordBreakBefore', 'tcl_wordchars', + 'Tcl_Write', 'Tcl_WriteChars', 'Tcl_WriteObj', 'Tcl_WriteRaw', 'Tcl_WrongNumArgs', 'Tcl_ZlibAdler32', + 'Tcl_ZlibCRC32', 'Tcl_ZlibDeflate', 'Tcl_ZlibInflate', 'Tcl_ZlibStreamChecksum', 'Tcl_ZlibStreamClose', + 'Tcl_ZlibStreamEof', 'Tcl_ZlibStreamGet', 'Tcl_ZlibStreamGetCommandName', 'Tcl_ZlibStreamInit', + 'Tcl_ZlibStreamPut', 'tcltest', 'tell', 'throw', 'time', 'tm', 'trace', 'transchan', 'try', 'unknown', + 'unload', 'unset', 'update', 'uplevel', 'upvar', 'variable', 'vwait', 'while', 'yield', 'yieldto', 'zlib' + ] + + +@dataclass +class Extensions: + """ + Holds the file extensions recognized by the application + """ + + grb_list = [ + 'art', 'bot', 'bsm', 'cmp', 'crc', 'crs', 'dim', 'g4', 'gb0', 'gb1', 'gb2', 'gb3', 'gb5', + 'gb6', 'gb7', 'gb8', 'gb9', 'gbd', 'gbl', 'gbo', 'gbp', 'gbr', 'gbs', 'gdo', 'ger', 'gko', + 'gml', 'gm1', 'gm2', 'gm3', 'grb', 'gtl', 'gto', 'gtp', 'gts', 'ly15', 'ly2', 'mil', 'outline', + 'pho', 'plc', 'pls', 'smb', 'smt', 'sol', 'spb', 'spt', 'ssb', 'sst', 'stc', 'sts', 'top', + 'tsm' + ] + + exc_list = ['drd', 'drl', 'drill', 'exc', 'ncd', 'tap', 'txt', 'xln'] + + gcode_list = [ + 'cnc', 'din', 'dnc', 'ecs', 'eia', 'fan', 'fgc', 'fnc', 'gc', 'gcd', 'gcode', 'h', 'hnc', + 'i', 'min', 'mpf', 'mpr', 'nc', 'ncc', 'ncg', 'ngc', 'ncp', 'out', 'ply', 'rol', + 'sbp', 'tap', 'xpi' + ] + svg_list = ['svg'] + dxf_list = ['dxf'] + pdf_list = ['pdf'] + prj_list = ['flatprj'] + conf_list = ['flatconfig'] + + +class RegisterFK(QtCore.QObject): + """ + Will associate the application with some files with certain extensions. It will also update the Qt autocompleter. + """ + + def __init__(self, + ui: 'appMain.MainGUI', + inform_sig: pyqtSignal, + options_dict: 'appMain.AppOptions', + shell: 'appMain.FCShell', + log: ('appMain.AppLogging', 'appMain.logging'), + keywords: KeyWords, + extensions: Extensions): + + super().__init__() + self.ui = ui + self.shell = shell + self.log = log + self.inform = inform_sig + self.options = options_dict + self.keywords = keywords + + self.tcl_keywords = self.keywords.tcl_keywords + self.tcl_commands_list = self.keywords.tcl_commands + self.default_keywords = self.keywords.default_keywords + + self.exc_list = extensions.exc_list + self.grb_list = extensions.grb_list + self.gcode_list = extensions.gcode_list + + autocomplete_kw_list = self.options['util_autocomplete_keywords'].replace(' ', '').split(',') + self.myKeywords = self.tcl_commands_list + autocomplete_kw_list + self.tcl_keywords + + # initial register of keywords for the shell + self.register_keywords(self.myKeywords) + + self.connect_signals() + + # ########################################################################################################### + # ##################################### Register files with FlatCAM; ####################################### + # ################################### It works only for Windows for now #################################### + # ########################################################################################################### + if sys.platform == 'win32' and self.options["first_run"] is True: + self.on_register_files() + + def connect_signals(self): + # ########################################################################################################### + # ####################################### FILE ASSOCIATIONS SIGNALS ######################################### + # ########################################################################################################### + self.ui.util_pref_form.fa_excellon_group.restore_btn.clicked.connect( + lambda: self.restore_extensions(ext_type='excellon')) + self.ui.util_pref_form.fa_gcode_group.restore_btn.clicked.connect( + lambda: self.restore_extensions(ext_type='gcode')) + self.ui.util_pref_form.fa_gerber_group.restore_btn.clicked.connect( + lambda: self.restore_extensions(ext_type='gerber')) + + self.ui.util_pref_form.fa_excellon_group.del_all_btn.clicked.connect( + lambda: self.delete_all_extensions(ext_type='excellon')) + self.ui.util_pref_form.fa_gcode_group.del_all_btn.clicked.connect( + lambda: self.delete_all_extensions(ext_type='gcode')) + self.ui.util_pref_form.fa_gerber_group.del_all_btn.clicked.connect( + lambda: self.delete_all_extensions(ext_type='gerber')) + + self.ui.util_pref_form.fa_excellon_group.add_btn.clicked.connect( + lambda: self.add_extension(ext_type='excellon')) + self.ui.util_pref_form.fa_gcode_group.add_btn.clicked.connect( + lambda: self.add_extension(ext_type='gcode')) + self.ui.util_pref_form.fa_gerber_group.add_btn.clicked.connect( + lambda: self.add_extension(ext_type='gerber')) + + self.ui.util_pref_form.fa_excellon_group.del_btn.clicked.connect( + lambda: self.del_extension(ext_type='excellon')) + self.ui.util_pref_form.fa_gcode_group.del_btn.clicked.connect( + lambda: self.del_extension(ext_type='gcode')) + self.ui.util_pref_form.fa_gerber_group.del_btn.clicked.connect( + lambda: self.del_extension(ext_type='gerber')) + + # connect the 'Apply' buttons from the Preferences/File Associations + self.ui.util_pref_form.fa_excellon_group.exc_list_btn.clicked.connect( + lambda: self.on_register_files(obj_type='excellon')) + self.ui.util_pref_form.fa_gcode_group.gco_list_btn.clicked.connect( + lambda: self.on_register_files(obj_type='gcode')) + self.ui.util_pref_form.fa_gerber_group.grb_list_btn.clicked.connect( + lambda: self.on_register_files(obj_type='gerber')) + + # ########################################################################################################### + # ########################################### KEYWORDS SIGNALS ############################################## + # ########################################################################################################### + self.ui.util_pref_form.kw_group.restore_btn.clicked.connect( + lambda: self.restore_extensions(ext_type='keyword')) + self.ui.util_pref_form.kw_group.del_all_btn.clicked.connect( + lambda: self.delete_all_extensions(ext_type='keyword')) + self.ui.util_pref_form.kw_group.add_btn.clicked.connect( + lambda: self.add_extension(ext_type='keyword')) + self.ui.util_pref_form.kw_group.del_btn.clicked.connect( + lambda: self.del_extension(ext_type='keyword')) + + def register_keywords(self, keywords=None): + if keywords is None: + self.shell.command_line().set_model_data(self.myKeywords) + return + self.myKeywords = keywords + self.shell.command_line().set_model_data(self.myKeywords) + + def prepend_keyword(self, kw, update=True): + self.myKeywords.insert(0, kw) + if update: + self.register_keywords(self.myKeywords) + + def append_keyword(self, kw, update=True): + self.myKeywords.append(kw) + if update: + self.register_keywords(self.myKeywords) + + def remove_keyword(self, kw, update=True): + self.myKeywords.remove(kw) + if update: + self.register_keywords(self.myKeywords) + + @staticmethod + def set_reg(name, root_pth, new_reg_path_par, value): + # create the keys + try: + winreg.CreateKey(root_pth, new_reg_path_par) + with winreg.OpenKey(root_pth, new_reg_path_par, 0, winreg.KEY_WRITE) as registry_key: + winreg.SetValueEx(registry_key, name, 0, winreg.REG_SZ, value) + return True + except WindowsError: + return False + + @staticmethod + def delete_reg(root_pth, reg_path, key_to_del): + # delete key in registry + key_to_del_path = reg_path + key_to_del + try: + winreg.DeleteKey(root_pth, key_to_del_path) + return True + except WindowsError: + return False + + def on_register_files(self, obj_type=None): + """ + Called whenever there is a need to register file extensions with FlatCAM. + Works only in Windows and should be called only when FlatCAM is run in Windows. + + :param obj_type: the type of object to be register for. + Can be: 'gerber', 'excellon' or 'gcode'. 'geometry' is not used for the moment. + + :return: None + """ + self.log.debug("Manufacturing files extensions are registered with FlatCAM.") + + # find if the current user is admin + try: + is_admin = os.getuid() == 0 + except AttributeError: + is_admin = ctypes.windll.shell32.IsUserAnAdmin() == 1 + + if is_admin is True: + root_path = winreg.HKEY_LOCAL_MACHINE + else: + root_path = winreg.HKEY_CURRENT_USER + + if obj_type is None or obj_type == 'excellon': + self.register_excellon_extension(root_path) + + if obj_type is None or obj_type == 'gcode': + self.register_gcode_extension(root_path) + + if obj_type is None or obj_type == 'gerber': + self.register_gerber_extension(root_path) + + def register_excellon_extension(self, root_path): + new_reg_path = 'Software\\Classes\\' + + exc_list = \ + self.ui.util_pref_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',') + exc_list = [x for x in exc_list if x != ''] + + # register all keys in the Preferences window + for ext in exc_list: + new_k = new_reg_path + '.%s' % ext + self.set_reg('', root_pth=root_path, new_reg_path_par=new_k, value='FlatCAM') + + # and unregister those that are no longer in the Preferences windows but are in the file + for ext in self.options["fa_excellon"].replace(' ', '').split(','): + if ext not in exc_list: + self.delete_reg(root_pth=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext) + + # now write the updated extensions to the self.options + # new_ext = '' + # for ext in exc_list: + # new_ext = new_ext + ext + ', ' + # self.options["fa_excellon"] = new_ext + self.inform.emit('[success] %s' % _("Extensions registered.")) # noqa + + def register_gerber_extension(self, root_path): + new_reg_path = 'Software\\Classes\\' + + grb_list = self.ui.util_pref_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',') + grb_list = [x for x in grb_list if x != ''] + + # register all keys in the Preferences window + for ext in grb_list: + new_k = new_reg_path + '.%s' % ext + self.set_reg('', root_pth=root_path, new_reg_path_par=new_k, value='FlatCAM') + + # and unregister those that are no longer in the Preferences windows but are in the file + for ext in self.options["fa_gerber"].replace(' ', '').split(','): + if ext not in grb_list: + self.delete_reg(root_pth=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext) + + self.inform.emit('[success] %s' % _("Extensions registered.")) # noqa + + def register_gcode_extension(self, root_path): + new_reg_path = 'Software\\Classes\\' + + gco_list = self.ui.util_pref_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',') + gco_list = [x for x in gco_list if x != ''] + + # register all keys in the Preferences window + for ext in gco_list: + new_k = new_reg_path + '.%s' % ext + self.set_reg('', root_pth=root_path, new_reg_path_par=new_k, value='FlatCAM') + + # and unregister those that are no longer in the Preferences windows but are in the file + for ext in self.options["fa_gcode"].replace(' ', '').split(','): + if ext not in gco_list: + self.delete_reg(root_pth=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext) + + self.inform.emit('[success] %s' % _("Extensions registered.")) # noqa + + def add_extension(self, ext_type): + """ + Add a file extension to the list for a specific object + + :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword + :return: + """ + + if ext_type == 'excellon': + self.add_excellon_extension() + if ext_type == 'gcode': + self.add_gcode_extension() + if ext_type == 'gerber': + self.add_gerber_extension() + if ext_type == 'keyword': + self.add_keyword_in_preferences_gui() + + def add_excellon_extension(self): + new_ext = self.ui.util_pref_form.fa_excellon_group.ext_entry.get_value() + if new_ext == '': + return + + old_val = self.ui.util_pref_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',') + if new_ext in old_val: + return + old_val.append(new_ext) + old_val.sort() + self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value(', '.join(old_val)) + + def add_gerber_extension(self): + new_ext = self.ui.util_pref_form.fa_gerber_group.ext_entry.get_value() + if new_ext == '': + return + + old_val = self.ui.util_pref_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',') + if new_ext in old_val: + return + old_val.append(new_ext) + old_val.sort() + self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value(', '.join(old_val)) + + def add_gcode_extension(self): + new_ext = self.ui.util_pref_form.fa_gcode_group.ext_entry.get_value() + if new_ext == '': + return + + old_val = self.ui.util_pref_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',') + if new_ext in old_val: + return + old_val.append(new_ext) + old_val.sort() + self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value(', '.join(old_val)) + + def add_keyword_in_preferences_gui(self, kw=None): + new_kw = self.ui.util_pref_form.kw_group.kw_entry.get_value() if kw is None else kw + if new_kw == '': + return + + old_val = self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') + if new_kw in old_val: + return + old_val.append(new_kw) + old_val.sort() + self.ui.util_pref_form.kw_group.kw_list_text.set_value(', '.join(old_val)) + + # update the self.myKeywords so the model is updated + autocomplete_kw_list = self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') + self.myKeywords = self.tcl_commands_list + autocomplete_kw_list + self.tcl_keywords + self.register_keywords(self.myKeywords) + + def del_extension(self, ext_type): + """ + Remove a file extension from the list for a specific object + + :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword + :return: + """ + if ext_type == 'excellon': + self.delete_excellon_extension() + if ext_type == 'gcode': + self.delete_gcode_extension() + if ext_type == 'gerber': + self.delete_gerber_extension() + if ext_type == 'keyword': + self.delete_keyword_in_preferences_gui() + + def delete_excellon_extension(self): + new_ext = self.ui.util_pref_form.fa_excellon_group.ext_entry.get_value() + if new_ext == '': + return + + old_val = self.ui.util_pref_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',') + if new_ext not in old_val: + return + old_val.remove(new_ext) + old_val.sort() + self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value(', '.join(old_val)) + + def delete_gerber_extension(self): + new_ext = self.ui.util_pref_form.fa_gerber_group.ext_entry.get_value() + if new_ext == '': + return + + old_val = self.ui.util_pref_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',') + if new_ext not in old_val: + return + old_val.remove(new_ext) + old_val.sort() + self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value(', '.join(old_val)) + + def delete_gcode_extension(self): + new_ext = self.ui.util_pref_form.fa_gcode_group.ext_entry.get_value() + if new_ext == '': + return + + old_val = self.ui.util_pref_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',') + if new_ext not in old_val: + return + old_val.remove(new_ext) + old_val.sort() + self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value(', '.join(old_val)) + + def delete_keyword_in_preferences_gui(self, kw=None): + new_kw = kw if kw is not None else self.ui.util_pref_form.kw_group.kw_entry.get_value() + if new_kw == '': + return + + old_val = self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') + if new_kw not in old_val: + return + old_val.remove(new_kw) + old_val.sort() + self.ui.util_pref_form.kw_group.kw_list_text.set_value(', '.join(old_val)) + + # update the self.myKeywords so the model is updated + autocomplete_kw_list = self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') + self.myKeywords = self.tcl_commands_list + autocomplete_kw_list + self.tcl_keywords + self.register_keywords(self.myKeywords) + + def restore_extensions(self, ext_type): + """ + Restore all file extensions associations with FlatCAM, for a specific object + + :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword + :return: + """ + + if ext_type == 'excellon': + # don't add 'txt' to the associations (too many files are .txt and not Excellon) but keep it in the list + # for the ability to open Excellon files with .txt extension + new_exc_list = deepcopy(self.exc_list) + + try: + new_exc_list.remove('txt') + except ValueError: + pass + self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value(', '.join(new_exc_list)) + if ext_type == 'gcode': + self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value(', '.join(self.gcode_list)) + if ext_type == 'gerber': + self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value(', '.join(self.grb_list)) + if ext_type == 'keyword': + self.ui.util_pref_form.kw_group.kw_list_text.set_value(', '.join(self.default_keywords)) + + # update the self.myKeywords so the model is updated + self.myKeywords = self.tcl_commands_list + self.default_keywords + self.tcl_keywords + self.shell.command_line().set_model_data(self.myKeywords) + + def delete_all_extensions(self, ext_type): + """ + Delete all file extensions associations with FlatCAM, for a specific object + + :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword + :return: + """ + + if ext_type == 'excellon': + self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value('') + if ext_type == 'gcode': + self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value('') + if ext_type == 'gerber': + self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value('') + if ext_type == 'keyword': + self.ui.util_pref_form.kw_group.kw_list_text.set_value('') + + # update the self.myKeywords so the model is updated + self.myKeywords = self.tcl_commands_list + self.tcl_keywords + self.register_keywords(self.myKeywords) diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index b6f46142..3dcb8d2e 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -2522,7 +2522,7 @@ class SelectEditorGrb(QtCore.QObject, DrawTool): self.draw_app.visible = True # make sure that the cursor text from the FCPath is deleted - if self.draw_app.app.plotcanvas.text_cursor.parent and self.draw_app.app.use_3d_engine: + if self.draw_app.app.use_3d_engine and self.draw_app.app.plotcanvas.text_cursor.parent: self.draw_app.app.plotcanvas.text_cursor.parent = None self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None @@ -5258,17 +5258,17 @@ class AppGerberEditor(QtCore.QObject): def draw_selection_area_handler(self, start_pos, end_pos, sel_type): """ - :param start_pos: mouse position when the selection LMB click was done - :param end_pos: mouse position when the left mouse button is released - :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection + :param start_pos: mouse position when the selection LMB click was done + :param end_pos: mouse position when the left mouse button is released + :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection :return: """ poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) sel_aperture = set() self.ui.apertures_table.clearSelection() - self.app.delete_selection_shape() + for storage in self.storage_dict: for obj in self.storage_dict[storage]['geometry']: if 'solid' in obj.geo: diff --git a/appEditors/AppTextEditor.py b/appEditors/AppTextEditor.py index eb1e5d6a..ec3d1084 100644 --- a/appEditors/AppTextEditor.py +++ b/appEditors/AppTextEditor.py @@ -175,7 +175,7 @@ class AppTextEditor(QtWidgets.QWidget): # self.button_copy_all.clicked.connect(self.handleCopyAll) - self.code_editor.set_model_data(self.app.myKeywords) + self.code_editor.set_model_data(self.app.regFK.myKeywords) self.code_edited = '' diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 1e2decf1..612e653e 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -1117,7 +1117,7 @@ class PreferencesUIManager(QtCore.QObject): self.ui.pref_apply_button.setStyleSheet("") self.ui.pref_apply_button.setIcon(QtGui.QIcon(self.ui.app.resource_location + '/apply32.png')) - self.inform.emit('%s' % _("Preferences applied.")) + self.inform.emit('%s' % _("Preferences applied.")) # noqa # make sure we update the self.current_defaults dict used to undo changes to self.defaults self.defaults.current_defaults.update(self.defaults) diff --git a/appMain.py b/appMain.py index d93eba3c..f4aecb28 100644 --- a/appMain.py +++ b/appMain.py @@ -55,6 +55,7 @@ from appCommon.Common import LoudDict from appCommon.Common import color_variant from appCommon.Common import ExclusionAreas from appCommon.Common import AppLogging +from appCommon.RegisterFileKeywords import RegisterFK, Extensions, KeyWords from Bookmark import BookmarkManager from appDatabase import ToolsDB2 @@ -397,23 +398,6 @@ class App(QtCore.QObject): # if Tools DB are changed/edited in the Edit -> Tools Database tab the value will be set to True self.tools_db_changed_flag = False - self.grb_list = ['art', 'bot', 'bsm', 'cmp', 'crc', 'crs', 'dim', 'g4', 'gb0', 'gb1', 'gb2', 'gb3', 'gb5', - 'gb6', 'gb7', 'gb8', 'gb9', 'gbd', 'gbl', 'gbo', 'gbp', 'gbr', 'gbs', 'gdo', 'ger', 'gko', - 'gml', 'gm1', 'gm2', 'gm3', 'grb', 'gtl', 'gto', 'gtp', 'gts', 'ly15', 'ly2', 'mil', 'outline', - 'pho', 'plc', 'pls', 'smb', 'smt', 'sol', 'spb', 'spt', 'ssb', 'sst', 'stc', 'sts', 'top', - 'tsm'] - - self.exc_list = ['drd', 'drl', 'drill', 'exc', 'ncd', 'tap', 'txt', 'xln'] - - self.gcode_list = ['cnc', 'din', 'dnc', 'ecs', 'eia', 'fan', 'fgc', 'fnc', 'gc', 'gcd', 'gcode', 'h', 'hnc', - 'i', 'min', 'mpf', 'mpr', 'nc', 'ncc', 'ncg', 'ngc', 'ncp', 'out', 'ply', 'rol', - 'sbp', 'tap', 'xpi'] - self.svg_list = ['svg'] - self.dxf_list = ['dxf'] - self.pdf_list = ['pdf'] - self.prj_list = ['flatprj'] - self.conf_list = ['flatconfig'] - # last used filters self.last_op_gerber_filter = None self.last_op_excellon_filter = None @@ -648,250 +632,6 @@ class App(QtCore.QObject): qdarksheet.STYLE_SHEET = dark_style_sheet.D_STYLE_SHEET self.qapp.setStyleSheet(qdarktheme.load_stylesheet()) - # ########################################################################################################### - # ####################################### Auto-complete KEYWORDS ############################################ - # ######################## Setup after the Defaults class was instantiated ################################## - # ########################################################################################################### - self.tcl_commands_list = ['add_aperture', 'add_circle', 'add_drill', 'add_poly', 'add_polygon', 'add_polyline', - 'add_rectangle', 'add_rect', 'add_slot', - 'aligndrill', 'aligndrillgrid', 'bbox', 'buffer', 'clear', 'cncjob', 'cutout', - 'del', 'drillcncjob', 'export_dxf', 'edxf', 'export_excellon', - 'export_exc', - 'export_gcode', 'export_gerber', 'export_svg', 'ext', 'exteriors', 'follow', - 'geo_union', 'geocutout', 'get_active', 'get_bounds', 'get_names', 'get_path', - 'get_sys', 'help', - 'interiors', 'isolate', 'join_excellon', - 'join_geometry', 'list_sys', 'list_pp', 'milld', 'mills', 'milldrills', 'millslots', - 'mirror', 'ncc', - 'ncr', 'new', 'new_geometry', 'new_gerber', 'new_excellon', 'non_copper_regions', - 'offset', - 'open_dxf', 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'open_svg', - 'options', 'origin', - 'paint', 'panelize', 'plot_all', 'plot_objects', 'plot_status', 'quit_app', - 'save', 'save_project', - 'save_sys', 'scale', 'set_active', 'set_origin', 'set_path', 'set_sys', - 'skew', 'subtract_poly', 'subtract_rectangle', - 'version', 'write_gcode' - ] - - # those need to be duplicated in self.options["util_autocomplete_keywords"] but within a string - self.default_keywords = ['Berta_CNC', 'Default_no_M6', 'Desktop', 'Documents', 'FlatConfig', 'FlatPrj', - 'False', 'GRBL_11', 'GRL_11_no_M6', 'GRBL_laser', 'grbl_laser_eleks_drd', - 'GRBL_laser_z', 'ISEL_CNC', 'ISEL_ICP_CNC', - 'Line_xyz', 'Marlin', - 'Marlin_laser_FAN_pin', 'Marlin_laser_Spindle_pin', 'NCCAD9', 'Marius', 'My Documents', - 'Paste_1', 'Repetier', 'Roland_MDX_20', 'Roland_MDX_540', - 'Toolchange_Manual', 'Toolchange_Probe_MACH3', - 'True', 'Users', - 'all', 'auto', 'axis', - 'axisoffset', 'box', 'center_x', 'center_y', 'center', 'columns', 'combine', 'connect', - 'contour', 'default', - 'depthperpass', 'dia', 'diatol', 'dist', 'drilled_dias', 'drillz', 'dpp', - 'dwelltime', 'extracut_length', 'endxy', 'endz', 'f', 'factor', 'feedrate', - 'feedrate_z', 'gridoffsety', 'gridx', 'gridy', - 'has_offset', 'holes', 'hpgl', 'iso_type', 'join', 'keep_scripts', - 'las_min_pwr', 'las_power', 'margin', 'marlin', 'method', - 'milled_dias', 'minoffset', 'min_bounds', 'name', 'offset', 'opt_type', 'order', - 'outname', 'overlap', 'obj_name', - 'p_coords', 'passes', 'postamble', 'pp', 'ppname_e', 'ppname_g', - 'preamble', 'radius', 'ref', 'rest', 'rows', 'shellvar_', 'scale_factor', - 'spacing_columns', - 'spacing_rows', 'spindlespeed', 'startz', 'startxy', - 'toolchange_xy', 'toolchangez', 'travelz', - 'tooldia', 'use_threads', 'value', - 'x', 'x0', 'x1', 'x_dist', 'y', 'y0', 'y1', 'y_dist', 'z_cut', 'z_move' - ] - - self.tcl_keywords = [ - 'after', 'append', 'apply', 'argc', 'argv', 'argv0', 'array', 'attemptckalloc', 'attemptckrealloc', - 'auto_execok', 'auto_import', 'auto_load', 'auto_mkindex', 'auto_path', 'auto_qualify', 'auto_reset', - 'bgerror', 'binary', 'break', 'case', 'catch', 'cd', 'chan', 'ckalloc', 'ckfree', 'ckrealloc', 'clock', - 'close', 'concat', 'continue', 'coroutine', 'dde', 'dict', 'encoding', 'env', 'eof', 'error', 'errorCode', - 'errorInfo', 'eval', 'exec', 'exit', 'expr', 'fblocked', 'fconfigure', 'fcopy', 'file', 'fileevent', - 'filename', 'flush', 'for', 'foreach', 'format', 'gets', 'glob', 'global', 'history', 'http', 'if', 'incr', - 'info', 'interp', 'join', 'lappend', 'lassign', 'lindex', 'linsert', 'list', 'llength', 'load', 'lrange', - 'lrepeat', 'lreplace', 'lreverse', 'lsearch', 'lset', 'lsort', 'mathfunc', 'mathop', 'memory', 'msgcat', - 'my', 'namespace', 'next', 'nextto', 'open', 'package', 'parray', 'pid', 'pkg_mkIndex', 'platform', - 'proc', 'puts', 'pwd', 're_syntax', 'read', 'refchan', 'regexp', 'registry', 'regsub', 'rename', 'return', - 'safe', 'scan', 'seek', 'self', 'set', 'socket', 'source', 'split', 'string', 'subst', 'switch', - 'tailcall', 'Tcl', 'Tcl_Access', 'Tcl_AddErrorInfo', 'Tcl_AddObjErrorInfo', 'Tcl_AlertNotifier', - 'Tcl_Alloc', 'Tcl_AllocHashEntryProc', 'Tcl_AllocStatBuf', 'Tcl_AllowExceptions', 'Tcl_AppendAllObjTypes', - 'Tcl_AppendElement', 'Tcl_AppendExportList', 'Tcl_AppendFormatToObj', 'Tcl_AppendLimitedToObj', - 'Tcl_AppendObjToErrorInfo', 'Tcl_AppendObjToObj', 'Tcl_AppendPrintfToObj', 'Tcl_AppendResult', - 'Tcl_AppendResultVA', 'Tcl_AppendStringsToObj', 'Tcl_AppendStringsToObjVA', 'Tcl_AppendToObj', - 'Tcl_AppendUnicodeToObj', 'Tcl_AppInit', 'Tcl_AppInitProc', 'Tcl_ArgvInfo', 'Tcl_AsyncCreate', - 'Tcl_AsyncDelete', 'Tcl_AsyncInvoke', 'Tcl_AsyncMark', 'Tcl_AsyncProc', 'Tcl_AsyncReady', - 'Tcl_AttemptAlloc', 'Tcl_AttemptRealloc', 'Tcl_AttemptSetObjLength', 'Tcl_BackgroundError', - 'Tcl_BackgroundException', 'Tcl_Backslash', 'Tcl_BadChannelOption', 'Tcl_CallWhenDeleted', 'Tcl_Canceled', - 'Tcl_CancelEval', 'Tcl_CancelIdleCall', 'Tcl_ChannelBlockModeProc', 'Tcl_ChannelBuffered', - 'Tcl_ChannelClose2Proc', 'Tcl_ChannelCloseProc', 'Tcl_ChannelFlushProc', 'Tcl_ChannelGetHandleProc', - 'Tcl_ChannelGetOptionProc', 'Tcl_ChannelHandlerProc', 'Tcl_ChannelInputProc', 'Tcl_ChannelName', - 'Tcl_ChannelOutputProc', 'Tcl_ChannelProc', 'Tcl_ChannelSeekProc', 'Tcl_ChannelSetOptionProc', - 'Tcl_ChannelThreadActionProc', 'Tcl_ChannelTruncateProc', 'Tcl_ChannelType', 'Tcl_ChannelVersion', - 'Tcl_ChannelWatchProc', 'Tcl_ChannelWideSeekProc', 'Tcl_Chdir', 'Tcl_ClassGetMetadata', - 'Tcl_ClassSetConstructor', 'Tcl_ClassSetDestructor', 'Tcl_ClassSetMetadata', 'Tcl_ClearChannelHandlers', - 'Tcl_CloneProc', 'Tcl_Close', 'Tcl_CloseProc', 'Tcl_CmdDeleteProc', 'Tcl_CmdInfo', - 'Tcl_CmdObjTraceDeleteProc', 'Tcl_CmdObjTraceProc', 'Tcl_CmdProc', 'Tcl_CmdTraceProc', - 'Tcl_CommandComplete', 'Tcl_CommandTraceInfo', 'Tcl_CommandTraceProc', 'Tcl_CompareHashKeysProc', - 'Tcl_Concat', 'Tcl_ConcatObj', 'Tcl_ConditionFinalize', 'Tcl_ConditionNotify', 'Tcl_ConditionWait', - 'Tcl_Config', 'Tcl_ConvertCountedElement', 'Tcl_ConvertElement', 'Tcl_ConvertToType', - 'Tcl_CopyObjectInstance', 'Tcl_CreateAlias', 'Tcl_CreateAliasObj', 'Tcl_CreateChannel', - 'Tcl_CreateChannelHandler', 'Tcl_CreateCloseHandler', 'Tcl_CreateCommand', 'Tcl_CreateEncoding', - 'Tcl_CreateEnsemble', 'Tcl_CreateEventSource', 'Tcl_CreateExitHandler', 'Tcl_CreateFileHandler', - 'Tcl_CreateHashEntry', 'Tcl_CreateInterp', 'Tcl_CreateMathFunc', 'Tcl_CreateNamespace', - 'Tcl_CreateObjCommand', 'Tcl_CreateObjTrace', 'Tcl_CreateSlave', 'Tcl_CreateThread', - 'Tcl_CreateThreadExitHandler', 'Tcl_CreateTimerHandler', 'Tcl_CreateTrace', - 'Tcl_CutChannel', 'Tcl_DecrRefCount', 'Tcl_DeleteAssocData', 'Tcl_DeleteChannelHandler', - 'Tcl_DeleteCloseHandler', 'Tcl_DeleteCommand', 'Tcl_DeleteCommandFromToken', 'Tcl_DeleteEvents', - 'Tcl_DeleteEventSource', 'Tcl_DeleteExitHandler', 'Tcl_DeleteFileHandler', 'Tcl_DeleteHashEntry', - 'Tcl_DeleteHashTable', 'Tcl_DeleteInterp', 'Tcl_DeleteNamespace', 'Tcl_DeleteThreadExitHandler', - 'Tcl_DeleteTimerHandler', 'Tcl_DeleteTrace', 'Tcl_DetachChannel', 'Tcl_DetachPids', 'Tcl_DictObjDone', - 'Tcl_DictObjFirst', 'Tcl_DictObjGet', 'Tcl_DictObjNext', 'Tcl_DictObjPut', 'Tcl_DictObjPutKeyList', - 'Tcl_DictObjRemove', 'Tcl_DictObjRemoveKeyList', 'Tcl_DictObjSize', 'Tcl_DiscardInterpState', - 'Tcl_DiscardResult', 'Tcl_DontCallWhenDeleted', 'Tcl_DoOneEvent', 'Tcl_DoWhenIdle', - 'Tcl_DriverBlockModeProc', 'Tcl_DriverClose2Proc', 'Tcl_DriverCloseProc', 'Tcl_DriverFlushProc', - 'Tcl_DriverGetHandleProc', 'Tcl_DriverGetOptionProc', 'Tcl_DriverHandlerProc', 'Tcl_DriverInputProc', - 'Tcl_DriverOutputProc', 'Tcl_DriverSeekProc', 'Tcl_DriverSetOptionProc', 'Tcl_DriverThreadActionProc', - 'Tcl_DriverTruncateProc', 'Tcl_DriverWatchProc', 'Tcl_DriverWideSeekProc', 'Tcl_DStringAppend', - 'Tcl_DStringAppendElement', 'Tcl_DStringEndSublist', 'Tcl_DStringFree', 'Tcl_DStringGetResult', - 'Tcl_DStringInit', 'Tcl_DStringLength', 'Tcl_DStringResult', 'Tcl_DStringSetLength', - 'Tcl_DStringStartSublist', 'Tcl_DStringTrunc', 'Tcl_DStringValue', 'Tcl_DumpActiveMemory', - 'Tcl_DupInternalRepProc', 'Tcl_DuplicateObj', 'Tcl_EncodingConvertProc', 'Tcl_EncodingFreeProc', - 'Tcl_EncodingType', 'tcl_endOfWord', 'Tcl_Eof', 'Tcl_ErrnoId', 'Tcl_ErrnoMsg', 'Tcl_Eval', 'Tcl_EvalEx', - 'Tcl_EvalFile', 'Tcl_EvalObjEx', 'Tcl_EvalObjv', 'Tcl_EvalTokens', 'Tcl_EvalTokensStandard', 'Tcl_Event', - 'Tcl_EventCheckProc', 'Tcl_EventDeleteProc', 'Tcl_EventProc', 'Tcl_EventSetupProc', 'Tcl_EventuallyFree', - 'Tcl_Exit', 'Tcl_ExitProc', 'Tcl_ExitThread', 'Tcl_Export', 'Tcl_ExposeCommand', 'Tcl_ExprBoolean', - 'Tcl_ExprBooleanObj', 'Tcl_ExprDouble', 'Tcl_ExprDoubleObj', 'Tcl_ExprLong', 'Tcl_ExprLongObj', - 'Tcl_ExprObj', 'Tcl_ExprString', 'Tcl_ExternalToUtf', 'Tcl_ExternalToUtfDString', 'Tcl_FileProc', - 'Tcl_Filesystem', 'Tcl_Finalize', 'Tcl_FinalizeNotifier', 'Tcl_FinalizeThread', 'Tcl_FindCommand', - 'Tcl_FindEnsemble', 'Tcl_FindExecutable', 'Tcl_FindHashEntry', 'tcl_findLibrary', 'Tcl_FindNamespace', - 'Tcl_FirstHashEntry', 'Tcl_Flush', 'Tcl_ForgetImport', 'Tcl_Format', 'Tcl_FreeHashEntryProc', - 'Tcl_FreeInternalRepProc', 'Tcl_FreeParse', 'Tcl_FreeProc', 'Tcl_FreeResult', - 'Tcl_Free·\xa0Tcl_FreeEncoding', 'Tcl_FSAccess', 'Tcl_FSAccessProc', 'Tcl_FSChdir', - 'Tcl_FSChdirProc', 'Tcl_FSConvertToPathType', 'Tcl_FSCopyDirectory', 'Tcl_FSCopyDirectoryProc', - 'Tcl_FSCopyFile', 'Tcl_FSCopyFileProc', 'Tcl_FSCreateDirectory', 'Tcl_FSCreateDirectoryProc', - 'Tcl_FSCreateInternalRepProc', 'Tcl_FSData', 'Tcl_FSDeleteFile', 'Tcl_FSDeleteFileProc', - 'Tcl_FSDupInternalRepProc', 'Tcl_FSEqualPaths', 'Tcl_FSEvalFile', 'Tcl_FSEvalFileEx', - 'Tcl_FSFileAttrsGet', 'Tcl_FSFileAttrsGetProc', 'Tcl_FSFileAttrsSet', 'Tcl_FSFileAttrsSetProc', - 'Tcl_FSFileAttrStrings', 'Tcl_FSFileSystemInfo', 'Tcl_FSFilesystemPathTypeProc', - 'Tcl_FSFilesystemSeparatorProc', 'Tcl_FSFreeInternalRepProc', 'Tcl_FSGetCwd', 'Tcl_FSGetCwdProc', - 'Tcl_FSGetFileSystemForPath', 'Tcl_FSGetInternalRep', 'Tcl_FSGetNativePath', 'Tcl_FSGetNormalizedPath', - 'Tcl_FSGetPathType', 'Tcl_FSGetTranslatedPath', 'Tcl_FSGetTranslatedStringPath', - 'Tcl_FSInternalToNormalizedProc', 'Tcl_FSJoinPath', 'Tcl_FSJoinToPath', 'Tcl_FSLinkProc', - 'Tcl_FSLink·\xa0Tcl_FSListVolumes', 'Tcl_FSListVolumesProc', 'Tcl_FSLoadFile', 'Tcl_FSLoadFileProc', - 'Tcl_FSLstat', 'Tcl_FSLstatProc', 'Tcl_FSMatchInDirectory', 'Tcl_FSMatchInDirectoryProc', - 'Tcl_FSMountsChanged', 'Tcl_FSNewNativePath', 'Tcl_FSNormalizePathProc', 'Tcl_FSOpenFileChannel', - 'Tcl_FSOpenFileChannelProc', 'Tcl_FSPathInFilesystemProc', 'Tcl_FSPathSeparator', 'Tcl_FSRegister', - 'Tcl_FSRemoveDirectory', 'Tcl_FSRemoveDirectoryProc', 'Tcl_FSRenameFile', 'Tcl_FSRenameFileProc', - 'Tcl_FSSplitPath', 'Tcl_FSStat', 'Tcl_FSStatProc', 'Tcl_FSUnloadFile', 'Tcl_FSUnloadFileProc', - 'Tcl_FSUnregister', 'Tcl_FSUtime', 'Tcl_FSUtimeProc', 'Tcl_GetAccessTimeFromStat', 'Tcl_GetAlias', - 'Tcl_GetAliasObj', 'Tcl_GetAssocData', 'Tcl_GetBignumFromObj', 'Tcl_GetBlocksFromStat', - 'Tcl_GetBlockSizeFromStat', 'Tcl_GetBoolean', 'Tcl_GetBooleanFromObj', 'Tcl_GetByteArrayFromObj', - 'Tcl_GetChangeTimeFromStat', 'Tcl_GetChannel', 'Tcl_GetChannelBufferSize', 'Tcl_GetChannelError', - 'Tcl_GetChannelErrorInterp', 'Tcl_GetChannelHandle', 'Tcl_GetChannelInstanceData', 'Tcl_GetChannelMode', - 'Tcl_GetChannelName', 'Tcl_GetChannelNames', 'Tcl_GetChannelNamesEx', 'Tcl_GetChannelOption', - 'Tcl_GetChannelThread', 'Tcl_GetChannelType', 'Tcl_GetCharLength', 'Tcl_GetClassAsObject', - 'Tcl_GetCommandFromObj', 'Tcl_GetCommandFullName', 'Tcl_GetCommandInfo', 'Tcl_GetCommandInfoFromToken', - 'Tcl_GetCommandName', 'Tcl_GetCurrentNamespace', 'Tcl_GetCurrentThread', 'Tcl_GetCwd', - 'Tcl_GetDefaultEncodingDir', 'Tcl_GetDeviceTypeFromStat', 'Tcl_GetDouble', 'Tcl_GetDoubleFromObj', - 'Tcl_GetEncoding', 'Tcl_GetEncodingFromObj', 'Tcl_GetEncodingName', 'Tcl_GetEncodingNameFromEnvironment', - 'Tcl_GetEncodingNames', 'Tcl_GetEncodingSearchPath', 'Tcl_GetEnsembleFlags', 'Tcl_GetEnsembleMappingDict', - 'Tcl_GetEnsembleNamespace', 'Tcl_GetEnsembleParameterList', 'Tcl_GetEnsembleSubcommandList', - 'Tcl_GetEnsembleUnknownHandler', 'Tcl_GetErrno', 'Tcl_GetErrorLine', 'Tcl_GetFSDeviceFromStat', - 'Tcl_GetFSInodeFromStat', 'Tcl_GetGlobalNamespace', 'Tcl_GetGroupIdFromStat', 'Tcl_GetHashKey', - 'Tcl_GetHashValue', 'Tcl_GetHostName', 'Tcl_GetIndexFromObj', 'Tcl_GetIndexFromObjStruct', 'Tcl_GetInt', - 'Tcl_GetInterpPath', 'Tcl_GetIntFromObj', 'Tcl_GetLinkCountFromStat', 'Tcl_GetLongFromObj', - 'Tcl_GetMaster', 'Tcl_GetMathFuncInfo', 'Tcl_GetModeFromStat', 'Tcl_GetModificationTimeFromStat', - 'Tcl_GetNameOfExecutable', 'Tcl_GetNamespaceUnknownHandler', 'Tcl_GetObjectAsClass', 'Tcl_GetObjectCommand', - 'Tcl_GetObjectFromObj', 'Tcl_GetObjectName', 'Tcl_GetObjectNamespace', 'Tcl_GetObjResult', 'Tcl_GetObjType', - 'Tcl_GetOpenFile', 'Tcl_GetPathType', 'Tcl_GetRange', 'Tcl_GetRegExpFromObj', 'Tcl_GetReturnOptions', - 'Tcl_Gets', 'Tcl_GetServiceMode', 'Tcl_GetSizeFromStat', 'Tcl_GetSlave', 'Tcl_GetsObj', - 'Tcl_GetStackedChannel', 'Tcl_GetStartupScript', 'Tcl_GetStdChannel', 'Tcl_GetString', - 'Tcl_GetStringFromObj', 'Tcl_GetStringResult', 'Tcl_GetThreadData', 'Tcl_GetTime', 'Tcl_GetTopChannel', - 'Tcl_GetUniChar', 'Tcl_GetUnicode', 'Tcl_GetUnicodeFromObj', 'Tcl_GetUserIdFromStat', 'Tcl_GetVar', - 'Tcl_GetVar2', 'Tcl_GetVar2Ex', 'Tcl_GetVersion', 'Tcl_GetWideIntFromObj', 'Tcl_GlobalEval', - 'Tcl_GlobalEvalObj', 'Tcl_GlobTypeData', 'Tcl_HashKeyType', 'Tcl_HashStats', 'Tcl_HideCommand', - 'Tcl_IdleProc', 'Tcl_Import', 'Tcl_IncrRefCount', 'Tcl_Init', 'Tcl_InitCustomHashTable', - 'Tcl_InitHashTable', 'Tcl_InitMemory', 'Tcl_InitNotifier', 'Tcl_InitObjHashTable', 'Tcl_InitStubs', - 'Tcl_InputBlocked', 'Tcl_InputBuffered', 'tcl_interactive', 'Tcl_Interp', 'Tcl_InterpActive', - 'Tcl_InterpDeleted', 'Tcl_InterpDeleteProc', 'Tcl_InvalidateStringRep', 'Tcl_IsChannelExisting', - 'Tcl_IsChannelRegistered', 'Tcl_IsChannelShared', 'Tcl_IsEnsemble', 'Tcl_IsSafe', 'Tcl_IsShared', - 'Tcl_IsStandardChannel', 'Tcl_JoinPath', 'Tcl_JoinThread', 'tcl_library', 'Tcl_LimitAddHandler', - 'Tcl_LimitCheck', 'Tcl_LimitExceeded', 'Tcl_LimitGetCommands', 'Tcl_LimitGetGranularity', - 'Tcl_LimitGetTime', 'Tcl_LimitHandlerDeleteProc', 'Tcl_LimitHandlerProc', 'Tcl_LimitReady', - 'Tcl_LimitRemoveHandler', 'Tcl_LimitSetCommands', 'Tcl_LimitSetGranularity', 'Tcl_LimitSetTime', - 'Tcl_LimitTypeEnabled', 'Tcl_LimitTypeExceeded', 'Tcl_LimitTypeReset', 'Tcl_LimitTypeSet', - 'Tcl_LinkVar', 'Tcl_ListMathFuncs', 'Tcl_ListObjAppendElement', 'Tcl_ListObjAppendList', - 'Tcl_ListObjGetElements', 'Tcl_ListObjIndex', 'Tcl_ListObjLength', 'Tcl_ListObjReplace', - 'Tcl_LogCommandInfo', 'Tcl_Main', 'Tcl_MainLoopProc', 'Tcl_MakeFileChannel', 'Tcl_MakeSafe', - 'Tcl_MakeTcpClientChannel', 'Tcl_MathProc', 'TCL_MEM_DEBUG', 'Tcl_Merge', 'Tcl_MethodCallProc', - 'Tcl_MethodDeclarerClass', 'Tcl_MethodDeclarerObject', 'Tcl_MethodDeleteProc', 'Tcl_MethodIsPublic', - 'Tcl_MethodIsType', 'Tcl_MethodName', 'Tcl_MethodType', 'Tcl_MutexFinalize', 'Tcl_MutexLock', - 'Tcl_MutexUnlock', 'Tcl_NamespaceDeleteProc', 'Tcl_NewBignumObj', 'Tcl_NewBooleanObj', - 'Tcl_NewByteArrayObj', 'Tcl_NewDictObj', 'Tcl_NewDoubleObj', 'Tcl_NewInstanceMethod', 'Tcl_NewIntObj', - 'Tcl_NewListObj', 'Tcl_NewLongObj', 'Tcl_NewMethod', 'Tcl_NewObj', 'Tcl_NewObjectInstance', - 'Tcl_NewStringObj', 'Tcl_NewUnicodeObj', 'Tcl_NewWideIntObj', 'Tcl_NextHashEntry', 'tcl_nonwordchars', - 'Tcl_NotifierProcs', 'Tcl_NotifyChannel', 'Tcl_NRAddCallback', 'Tcl_NRCallObjProc', 'Tcl_NRCmdSwap', - 'Tcl_NRCreateCommand', 'Tcl_NREvalObj', 'Tcl_NREvalObjv', 'Tcl_NumUtfChars', 'Tcl_Obj', 'Tcl_ObjCmdProc', - 'Tcl_ObjectContextInvokeNext', 'Tcl_ObjectContextIsFiltering', 'Tcl_ObjectContextMethod', - 'Tcl_ObjectContextObject', 'Tcl_ObjectContextSkippedArgs', 'Tcl_ObjectDeleted', 'Tcl_ObjectGetMetadata', - 'Tcl_ObjectGetMethodNameMapper', 'Tcl_ObjectMapMethodNameProc', 'Tcl_ObjectMetadataDeleteProc', - 'Tcl_ObjectSetMetadata', 'Tcl_ObjectSetMethodNameMapper', 'Tcl_ObjGetVar2', 'Tcl_ObjPrintf', - 'Tcl_ObjSetVar2', 'Tcl_ObjType', 'Tcl_OpenCommandChannel', 'Tcl_OpenFileChannel', 'Tcl_OpenTcpClient', - 'Tcl_OpenTcpServer', 'Tcl_OutputBuffered', 'Tcl_PackageInitProc', 'Tcl_PackageUnloadProc', 'Tcl_Panic', - 'Tcl_PanicProc', 'Tcl_PanicVA', 'Tcl_ParseArgsObjv', 'Tcl_ParseBraces', 'Tcl_ParseCommand', 'Tcl_ParseExpr', - 'Tcl_ParseQuotedString', 'Tcl_ParseVar', 'Tcl_ParseVarName', 'tcl_patchLevel', 'tcl_pkgPath', - 'Tcl_PkgPresent', 'Tcl_PkgPresentEx', 'Tcl_PkgProvide', 'Tcl_PkgProvideEx', 'Tcl_PkgRequire', - 'Tcl_PkgRequireEx', 'Tcl_PkgRequireProc', 'tcl_platform', 'Tcl_PosixError', 'tcl_precision', - 'Tcl_Preserve', 'Tcl_PrintDouble', 'Tcl_PutEnv', 'Tcl_QueryTimeProc', 'Tcl_QueueEvent', 'tcl_rcFileName', - 'Tcl_Read', 'Tcl_ReadChars', 'Tcl_ReadRaw', 'Tcl_Realloc', 'Tcl_ReapDetachedProcs', 'Tcl_RecordAndEval', - 'Tcl_RecordAndEvalObj', 'Tcl_RegExpCompile', 'Tcl_RegExpExec', 'Tcl_RegExpExecObj', 'Tcl_RegExpGetInfo', - 'Tcl_RegExpIndices', 'Tcl_RegExpInfo', 'Tcl_RegExpMatch', 'Tcl_RegExpMatchObj', 'Tcl_RegExpRange', - 'Tcl_RegisterChannel', 'Tcl_RegisterConfig', 'Tcl_RegisterObjType', 'Tcl_Release', 'Tcl_ResetResult', - 'Tcl_RestoreInterpState', 'Tcl_RestoreResult', 'Tcl_SaveInterpState', 'Tcl_SaveResult', 'Tcl_ScaleTimeProc', - 'Tcl_ScanCountedElement', 'Tcl_ScanElement', 'Tcl_Seek', 'Tcl_ServiceAll', 'Tcl_ServiceEvent', - 'Tcl_ServiceModeHook', 'Tcl_SetAssocData', 'Tcl_SetBignumObj', 'Tcl_SetBooleanObj', - 'Tcl_SetByteArrayLength', 'Tcl_SetByteArrayObj', 'Tcl_SetChannelBufferSize', 'Tcl_SetChannelError', - 'Tcl_SetChannelErrorInterp', 'Tcl_SetChannelOption', 'Tcl_SetCommandInfo', 'Tcl_SetCommandInfoFromToken', - 'Tcl_SetDefaultEncodingDir', 'Tcl_SetDoubleObj', 'Tcl_SetEncodingSearchPath', 'Tcl_SetEnsembleFlags', - 'Tcl_SetEnsembleMappingDict', 'Tcl_SetEnsembleParameterList', 'Tcl_SetEnsembleSubcommandList', - 'Tcl_SetEnsembleUnknownHandler', 'Tcl_SetErrno', 'Tcl_SetErrorCode', 'Tcl_SetErrorCodeVA', - 'Tcl_SetErrorLine', 'Tcl_SetExitProc', 'Tcl_SetFromAnyProc', 'Tcl_SetHashValue', 'Tcl_SetIntObj', - 'Tcl_SetListObj', 'Tcl_SetLongObj', 'Tcl_SetMainLoop', 'Tcl_SetMaxBlockTime', - 'Tcl_SetNamespaceUnknownHandler', 'Tcl_SetNotifier', 'Tcl_SetObjErrorCode', 'Tcl_SetObjLength', - 'Tcl_SetObjResult', 'Tcl_SetPanicProc', 'Tcl_SetRecursionLimit', 'Tcl_SetResult', 'Tcl_SetReturnOptions', - 'Tcl_SetServiceMode', 'Tcl_SetStartupScript', 'Tcl_SetStdChannel', 'Tcl_SetStringObj', - 'Tcl_SetSystemEncoding', 'Tcl_SetTimeProc', 'Tcl_SetTimer', 'Tcl_SetUnicodeObj', 'Tcl_SetVar', - 'Tcl_SetVar2', 'Tcl_SetVar2Ex', 'Tcl_SetWideIntObj', 'Tcl_SignalId', 'Tcl_SignalMsg', 'Tcl_Sleep', - 'Tcl_SourceRCFile', 'Tcl_SpliceChannel', 'Tcl_SplitList', 'Tcl_SplitPath', 'Tcl_StackChannel', - 'Tcl_StandardChannels', 'tcl_startOfNextWord', 'tcl_startOfPreviousWord', 'Tcl_Stat', 'Tcl_StaticPackage', - 'Tcl_StringCaseMatch', 'Tcl_StringMatch', 'Tcl_SubstObj', 'Tcl_TakeBignumFromObj', 'Tcl_TcpAcceptProc', - 'Tcl_Tell', 'Tcl_ThreadAlert', 'Tcl_ThreadQueueEvent', 'Tcl_Time', 'Tcl_TimerProc', 'Tcl_Token', - 'Tcl_TraceCommand', 'tcl_traceCompile', 'tcl_traceEval', 'Tcl_TraceVar', 'Tcl_TraceVar2', - 'Tcl_TransferResult', 'Tcl_TranslateFileName', 'Tcl_TruncateChannel', 'Tcl_Ungets', 'Tcl_UniChar', - 'Tcl_UniCharAtIndex', 'Tcl_UniCharCaseMatch', 'Tcl_UniCharIsAlnum', 'Tcl_UniCharIsAlpha', - 'Tcl_UniCharIsControl', 'Tcl_UniCharIsDigit', 'Tcl_UniCharIsGraph', 'Tcl_UniCharIsLower', - 'Tcl_UniCharIsPrint', 'Tcl_UniCharIsPunct', 'Tcl_UniCharIsSpace', 'Tcl_UniCharIsUpper', - 'Tcl_UniCharIsWordChar', 'Tcl_UniCharLen', 'Tcl_UniCharNcasecmp', 'Tcl_UniCharNcmp', 'Tcl_UniCharToLower', - 'Tcl_UniCharToTitle', 'Tcl_UniCharToUpper', 'Tcl_UniCharToUtf', 'Tcl_UniCharToUtfDString', 'Tcl_UnlinkVar', - 'Tcl_UnregisterChannel', 'Tcl_UnsetVar', 'Tcl_UnsetVar2', 'Tcl_UnstackChannel', 'Tcl_UntraceCommand', - 'Tcl_UntraceVar', 'Tcl_UntraceVar2', 'Tcl_UpdateLinkedVar', 'Tcl_UpdateStringProc', 'Tcl_UpVar', - 'Tcl_UpVar2', 'Tcl_UtfAtIndex', 'Tcl_UtfBackslash', 'Tcl_UtfCharComplete', 'Tcl_UtfFindFirst', - 'Tcl_UtfFindLast', 'Tcl_UtfNext', 'Tcl_UtfPrev', 'Tcl_UtfToExternal', 'Tcl_UtfToExternalDString', - 'Tcl_UtfToLower', 'Tcl_UtfToTitle', 'Tcl_UtfToUniChar', 'Tcl_UtfToUniCharDString', 'Tcl_UtfToUpper', - 'Tcl_ValidateAllMemory', 'Tcl_Value', 'Tcl_VarEval', 'Tcl_VarEvalVA', 'Tcl_VarTraceInfo', - 'Tcl_VarTraceInfo2', 'Tcl_VarTraceProc', 'tcl_version', 'Tcl_WaitForEvent', 'Tcl_WaitPid', - 'Tcl_WinTCharToUtf', 'Tcl_WinUtfToTChar', 'tcl_wordBreakAfter', 'tcl_wordBreakBefore', 'tcl_wordchars', - 'Tcl_Write', 'Tcl_WriteChars', 'Tcl_WriteObj', 'Tcl_WriteRaw', 'Tcl_WrongNumArgs', 'Tcl_ZlibAdler32', - 'Tcl_ZlibCRC32', 'Tcl_ZlibDeflate', 'Tcl_ZlibInflate', 'Tcl_ZlibStreamChecksum', 'Tcl_ZlibStreamClose', - 'Tcl_ZlibStreamEof', 'Tcl_ZlibStreamGet', 'Tcl_ZlibStreamGetCommandName', 'Tcl_ZlibStreamInit', - 'Tcl_ZlibStreamPut', 'tcltest', 'tell', 'throw', 'time', 'tm', 'trace', 'transchan', 'try', 'unknown', - 'unload', 'unset', 'update', 'uplevel', 'upvar', 'variable', 'vwait', 'while', 'yield', 'yieldto', 'zlib' - ] - - self.autocomplete_kw_list = self.options['util_autocomplete_keywords'].replace(' ', '').split(',') - self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords - # ############################################################################################################ # ################################### Set LOG verbosity ###################################################### # ############################################################################################################ @@ -1076,6 +816,20 @@ class App(QtCore.QObject): self.log.debug("Stardate: %s" % self.date) self.log.debug("TCL Shell has been initialized.") + # ########################################################################################################### + # ####################################### Auto-complete KEYWORDS ############################################ + # ######################## Setup after the Defaults class was instantiated ################################## + # ########################################################################################################### + self.regFK = RegisterFK( + ui=self.ui, + inform_sig=self.inform, + options_dict=self.options, + shell=self.shell, + log=self.log, + keywords=KeyWords(), + extensions=Extensions() + ) + # ########################################################################################################### # ########################################### AUTOSAVE SETUP ################################################ # ########################################################################################################### @@ -1313,13 +1067,6 @@ class App(QtCore.QObject): self.worker_task.emit({'fcn': self.version_check, 'params': []}) # self.thr2.start(QtCore.QThread.Priority.LowPriority) - # ########################################################################################################### - # ##################################### Register files with FlatCAM; ####################################### - # ################################### It works only for Windows for now #################################### - # ########################################################################################################### - if sys.platform == 'win32' and self.options["first_run"] is True: - self.on_register_files() - # ########################################################################################################### # ################################## ADDING FlatCAM EDITORS section ######################################### # ########################################################################################################### @@ -1392,7 +1139,7 @@ class App(QtCore.QObject): # if running headless always have the systray to be able to quit the app correctly self.trayIcon = AppSystemTray(app=self, icon=QtGui.QIcon(self.resource_location + - '/app32.png'), + '/app32.png'), headless=True, parent=self.parent_w) else: @@ -1502,57 +1249,6 @@ class App(QtCore.QObject): # when there are arguments at application startup this get launched self.args_at_startup[list].connect(self.on_startup_args) - # ########################################################################################################### - # ####################################### FILE ASSOCIATIONS SIGNALS ######################################### - # ########################################################################################################### - self.ui.util_pref_form.fa_excellon_group.restore_btn.clicked.connect( - lambda: self.restore_extensions(ext_type='excellon')) - self.ui.util_pref_form.fa_gcode_group.restore_btn.clicked.connect( - lambda: self.restore_extensions(ext_type='gcode')) - self.ui.util_pref_form.fa_gerber_group.restore_btn.clicked.connect( - lambda: self.restore_extensions(ext_type='gerber')) - - self.ui.util_pref_form.fa_excellon_group.del_all_btn.clicked.connect( - lambda: self.delete_all_extensions(ext_type='excellon')) - self.ui.util_pref_form.fa_gcode_group.del_all_btn.clicked.connect( - lambda: self.delete_all_extensions(ext_type='gcode')) - self.ui.util_pref_form.fa_gerber_group.del_all_btn.clicked.connect( - lambda: self.delete_all_extensions(ext_type='gerber')) - - self.ui.util_pref_form.fa_excellon_group.add_btn.clicked.connect( - lambda: self.add_extension(ext_type='excellon')) - self.ui.util_pref_form.fa_gcode_group.add_btn.clicked.connect( - lambda: self.add_extension(ext_type='gcode')) - self.ui.util_pref_form.fa_gerber_group.add_btn.clicked.connect( - lambda: self.add_extension(ext_type='gerber')) - - self.ui.util_pref_form.fa_excellon_group.del_btn.clicked.connect( - lambda: self.del_extension(ext_type='excellon')) - self.ui.util_pref_form.fa_gcode_group.del_btn.clicked.connect( - lambda: self.del_extension(ext_type='gcode')) - self.ui.util_pref_form.fa_gerber_group.del_btn.clicked.connect( - lambda: self.del_extension(ext_type='gerber')) - - # connect the 'Apply' buttons from the Preferences/File Associations - self.ui.util_pref_form.fa_excellon_group.exc_list_btn.clicked.connect( - lambda: self.on_register_files(obj_type='excellon')) - self.ui.util_pref_form.fa_gcode_group.gco_list_btn.clicked.connect( - lambda: self.on_register_files(obj_type='gcode')) - self.ui.util_pref_form.fa_gerber_group.grb_list_btn.clicked.connect( - lambda: self.on_register_files(obj_type='gerber')) - - # ########################################################################################################### - # ########################################### KEYWORDS SIGNALS ############################################## - # ########################################################################################################### - self.ui.util_pref_form.kw_group.restore_btn.clicked.connect( - lambda: self.restore_extensions(ext_type='keyword')) - self.ui.util_pref_form.kw_group.del_all_btn.clicked.connect( - lambda: self.delete_all_extensions(ext_type='keyword')) - self.ui.util_pref_form.kw_group.add_btn.clicked.connect( - lambda: self.add_extension(ext_type='keyword')) - self.ui.util_pref_form.kw_group.del_btn.clicked.connect( - lambda: self.del_extension(ext_type='keyword')) - # ########################################################################################################### # ########################################### GUI SIGNALS ################################################### # ########################################################################################################### @@ -2754,7 +2450,7 @@ class App(QtCore.QObject): msgbox = FCMessageBox(parent=self.ui) title = _("Exit Editor") txt = _("Do you want to save the changes?") - msgbox.setWindowTitle(title) # taskbar still shows it + msgbox.setWindowTitle(title) # taskbar still shows it msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/app128.png')) msgbox.setText('%s' % title) msgbox.setInformativeText(txt) @@ -4299,272 +3995,6 @@ class App(QtCore.QObject): with open(config_file, 'w') as f: f.writelines(data) - def on_register_files(self, obj_type=None): - """ - Called whenever there is a need to register file extensions with FlatCAM. - Works only in Windows and should be called only when FlatCAM is run in Windows. - - :param obj_type: the type of object to be register for. - Can be: 'gerber', 'excellon' or 'gcode'. 'geometry' is not used for the moment. - - :return: None - """ - self.log.debug("Manufacturing files extensions are registered with FlatCAM.") - - new_reg_path = 'Software\\Classes\\' - # find if the current user is admin - try: - is_admin = os.getuid() == 0 - except AttributeError: - is_admin = ctypes.windll.shell32.IsUserAnAdmin() == 1 - - if is_admin is True: - root_path = winreg.HKEY_LOCAL_MACHINE - else: - root_path = winreg.HKEY_CURRENT_USER - - # create the keys - def set_reg(name, root_pth, new_reg_path_par, value): - try: - winreg.CreateKey(root_pth, new_reg_path_par) - with winreg.OpenKey(root_pth, new_reg_path_par, 0, winreg.KEY_WRITE) as registry_key: - winreg.SetValueEx(registry_key, name, 0, winreg.REG_SZ, value) - return True - except WindowsError: - return False - - # delete key in registry - def delete_reg(root_pth, reg_path, key_to_del): - key_to_del_path = reg_path + key_to_del - try: - winreg.DeleteKey(root_pth, key_to_del_path) - return True - except WindowsError: - return False - - if obj_type is None or obj_type == 'excellon': - exc_list = \ - self.ui.util_pref_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',') - exc_list = [x for x in exc_list if x != ''] - - # register all keys in the Preferences window - for ext in exc_list: - new_k = new_reg_path + '.%s' % ext - set_reg('', root_pth=root_path, new_reg_path_par=new_k, value='FlatCAM') - - # and unregister those that are no longer in the Preferences windows but are in the file - for ext in self.options["fa_excellon"].replace(' ', '').split(','): - if ext not in exc_list: - delete_reg(root_pth=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext) - - # now write the updated extensions to the self.options - # new_ext = '' - # for ext in exc_list: - # new_ext = new_ext + ext + ', ' - # self.options["fa_excellon"] = new_ext - self.inform.emit('[success] %s' % _("Selected Excellon file extensions registered with FlatCAM.")) - - if obj_type is None or obj_type == 'gcode': - gco_list = self.ui.util_pref_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',') - gco_list = [x for x in gco_list if x != ''] - - # register all keys in the Preferences window - for ext in gco_list: - new_k = new_reg_path + '.%s' % ext - set_reg('', root_pth=root_path, new_reg_path_par=new_k, value='FlatCAM') - - # and unregister those that are no longer in the Preferences windows but are in the file - for ext in self.options["fa_gcode"].replace(' ', '').split(','): - if ext not in gco_list: - delete_reg(root_pth=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext) - - self.inform.emit('[success] %s' % - _("Selected GCode file extensions registered with FlatCAM.")) - - if obj_type is None or obj_type == 'gerber': - grb_list = self.ui.util_pref_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',') - grb_list = [x for x in grb_list if x != ''] - - # register all keys in the Preferences window - for ext in grb_list: - new_k = new_reg_path + '.%s' % ext - set_reg('', root_pth=root_path, new_reg_path_par=new_k, value='FlatCAM') - - # and unregister those that are no longer in the Preferences windows but are in the file - for ext in self.options["fa_gerber"].replace(' ', '').split(','): - if ext not in grb_list: - delete_reg(root_pth=root_path, reg_path=new_reg_path, key_to_del='.%s' % ext) - - self.inform.emit('[success] %s' % _("Selected Gerber file extensions registered with FlatCAM.")) - - def add_extension(self, ext_type): - """ - Add a file extension to the list for a specific object - - :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword - :return: - """ - - if ext_type == 'excellon': - new_ext = self.ui.util_pref_form.fa_excellon_group.ext_entry.get_value() - if new_ext == '': - return - - old_val = self.ui.util_pref_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',') - if new_ext in old_val: - return - old_val.append(new_ext) - old_val.sort() - self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value(', '.join(old_val)) - if ext_type == 'gcode': - new_ext = self.ui.util_pref_form.fa_gcode_group.ext_entry.get_value() - if new_ext == '': - return - - old_val = self.ui.util_pref_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',') - if new_ext in old_val: - return - old_val.append(new_ext) - old_val.sort() - self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value(', '.join(old_val)) - if ext_type == 'gerber': - new_ext = self.ui.util_pref_form.fa_gerber_group.ext_entry.get_value() - if new_ext == '': - return - - old_val = self.ui.util_pref_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',') - if new_ext in old_val: - return - old_val.append(new_ext) - old_val.sort() - self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value(', '.join(old_val)) - if ext_type == 'keyword': - new_kw = self.ui.util_pref_form.kw_group.kw_entry.get_value() - if new_kw == '': - return - - old_val = self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') - if new_kw in old_val: - return - old_val.append(new_kw) - old_val.sort() - self.ui.util_pref_form.kw_group.kw_list_text.set_value(', '.join(old_val)) - - # update the self.myKeywords so the model is updated - self.autocomplete_kw_list = \ - self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') - self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords - self.shell.command_line().set_model_data(self.myKeywords) - - def del_extension(self, ext_type): - """ - Remove a file extension from the list for a specific object - - :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword - :return: - """ - if ext_type == 'excellon': - new_ext = self.ui.util_pref_form.fa_excellon_group.ext_entry.get_value() - if new_ext == '': - return - - old_val = self.ui.util_pref_form.fa_excellon_group.exc_list_text.get_value().replace(' ', '').split(',') - if new_ext not in old_val: - return - old_val.remove(new_ext) - old_val.sort() - self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value(', '.join(old_val)) - if ext_type == 'gcode': - new_ext = self.ui.util_pref_form.fa_gcode_group.ext_entry.get_value() - if new_ext == '': - return - - old_val = self.ui.util_pref_form.fa_gcode_group.gco_list_text.get_value().replace(' ', '').split(',') - if new_ext not in old_val: - return - old_val.remove(new_ext) - old_val.sort() - self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value(', '.join(old_val)) - if ext_type == 'gerber': - new_ext = self.ui.util_pref_form.fa_gerber_group.ext_entry.get_value() - if new_ext == '': - return - - old_val = self.ui.util_pref_form.fa_gerber_group.grb_list_text.get_value().replace(' ', '').split(',') - if new_ext not in old_val: - return - old_val.remove(new_ext) - old_val.sort() - self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value(', '.join(old_val)) - if ext_type == 'keyword': - new_kw = self.ui.util_pref_form.kw_group.kw_entry.get_value() - if new_kw == '': - return - - old_val = self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') - if new_kw not in old_val: - return - old_val.remove(new_kw) - old_val.sort() - self.ui.util_pref_form.kw_group.kw_list_text.set_value(', '.join(old_val)) - - # update the self.myKeywords so the model is updated - self.autocomplete_kw_list = \ - self.ui.util_pref_form.kw_group.kw_list_text.get_value().replace(' ', '').split(',') - self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords - self.shell.command_line().set_model_data(self.myKeywords) - - def restore_extensions(self, ext_type): - """ - Restore all file extensions associations with FlatCAM, for a specific object - - :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword - :return: - """ - - if ext_type == 'excellon': - # don't add 'txt' to the associations (too many files are .txt and not Excellon) but keep it in the list - # for the ability to open Excellon files with .txt extension - new_exc_list = deepcopy(self.exc_list) - - try: - new_exc_list.remove('txt') - except ValueError: - pass - self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value(', '.join(new_exc_list)) - if ext_type == 'gcode': - self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value(', '.join(self.gcode_list)) - if ext_type == 'gerber': - self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value(', '.join(self.grb_list)) - if ext_type == 'keyword': - self.ui.util_pref_form.kw_group.kw_list_text.set_value(', '.join(self.default_keywords)) - - # update the self.myKeywords so the model is updated - self.autocomplete_kw_list = self.default_keywords - self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords - self.shell.command_line().set_model_data(self.myKeywords) - - def delete_all_extensions(self, ext_type): - """ - Delete all file extensions associations with FlatCAM, for a specific object - - :param ext_type: type of FlatCAM object: excellon, gerber, geometry and then 'not FlatCAM object' keyword - :return: - """ - - if ext_type == 'excellon': - self.ui.util_pref_form.fa_excellon_group.exc_list_text.set_value('') - if ext_type == 'gcode': - self.ui.util_pref_form.fa_gcode_group.gco_list_text.set_value('') - if ext_type == 'gerber': - self.ui.util_pref_form.fa_gerber_group.grb_list_text.set_value('') - if ext_type == 'keyword': - self.ui.util_pref_form.kw_group.kw_list_text.set_value('') - - # update the self.myKeywords so the model is updated - self.myKeywords = self.tcl_commands_list + self.tcl_keywords - self.shell.command_line().set_model_data(self.myKeywords) - def on_edit_join(self, name=None): """ Callback for Edit->Join. Joins the selected geometry objects into @@ -4792,7 +4222,7 @@ class App(QtCore.QObject): return if self.ui.plot_tab_area.currentWidget().objectName() == "preferences_tab": - if factor != 1: # means we had a unit change in the rest of the app + if factor != 1: # means we had a unit change in the rest of the app if self.app_units != new_units: pref_factor = 25.4 if new_units == 'MM' else 1 / 25.4 self.scale_preferences(pref_factor, new_units) @@ -5971,8 +5401,8 @@ class App(QtCore.QObject): return if dia_box.reference == 'abs': - abs_x = location[0] - bounds[0] - abs_y = location[1] - bounds[1] + abs_x = location[0] - bounds[0] + abs_y = location[1] - bounds[1] location = (abs_x, abs_y) self.options['global_jump_ref'] = dia_box.reference except Exception: @@ -6334,7 +5764,7 @@ class App(QtCore.QObject): continue minx, miny, maxx, maxy = geo.bounds - new_dia = min([maxx-minx, maxy-miny]) + new_dia = min([maxx - minx, maxy - miny]) new_drill = geo.centroid new_drill_geo = new_drill.buffer(new_dia / 2.0) @@ -6658,7 +6088,7 @@ class App(QtCore.QObject): self.log.error("Could not access tools DB file. The file may be locked,\n" "not existing or doesn't have the read permissions.\n" "Check to see if exists, it should be here: %s\n" - "It may help to reboot the app, it will try to recreate it if it's missing." % + "It may help to reboot the app, it will try to recreate it if it's missing." % self.data_path) self.inform.emit('[ERROR] %s' % _("Could not load the file.")) return 'fail' @@ -6785,7 +6215,7 @@ class App(QtCore.QObject): return if obj.kind == 'geometry': - if tool['data']['tool_target'] not in [0, 1]: # General, Milling Type + if tool['data']['tool_target'] not in [0, 1]: # General, Milling Type # close the tab and delete it for idx in range(self.ui.plot_tab_area.count()): if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"): @@ -6806,7 +6236,7 @@ class App(QtCore.QObject): self.ui.plot_tab_area.removeTab(idx) self.inform.emit('[success] %s' % _("Tool from DB added in Tool Table.")) elif obj.kind == 'gerber': - if tool['data']['tool_target'] not in [0, 3]: # General, Isolation Type + if tool['data']['tool_target'] not in [0, 3]: # General, Isolation Type # close the tab and delete it for idx in range(self.ui.plot_tab_area.count()): if self.ui.plot_tab_area.tabText(idx) == _("Tools Database"): @@ -6960,7 +6390,7 @@ class App(QtCore.QObject): self.ui.plugin_tab_layout.addWidget(self.ui.plugin_scroll_area) # def on_close_notebook_tab(self): - # self.tool_shapes.clear(update=True) + # self.tool_shapes.clear(update=True) def on_gui_coords_clicked(self): self.distance_tool.run(toggle=True) @@ -8395,6 +7825,7 @@ class App(QtCore.QObject): for plot_obj in obj_collection: if plot_obj.obj_options['plot'] is False: continue + def worker_task(obj): with self.proc_container.new("Plotting"): if obj.kind == 'cncjob': @@ -8774,10 +8205,10 @@ class App(QtCore.QObject): self.log.debug("Newer version available.") title = _("Newer Version Available") msg = '%s

>%s
%s' % ( - _("There is a newer version available for download:"), - str(data["name"]), - str(data["message"]) - ) + _("There is a newer version available for download:"), + str(data["name"]), + str(data["message"]) + ) self.message.emit(title, msg, "info") def on_plotcanvas_setup(self): @@ -9212,7 +8643,7 @@ class App(QtCore.QObject): item = sel_obj.item item_index = self.collection.index(item.row(), 0, group_index) idx = item_index.row() - new_c = (new_line_color, new_color, '%s_%d' % (_("Layer"), int(idx+1))) + new_c = (new_line_color, new_color, '%s_%d' % (_("Layer"), int(idx + 1))) try: self.options["gerber_color_list"][idx] = new_c except Exception as err_msg: @@ -9261,16 +8692,16 @@ class App(QtCore.QObject): item = sel_obj.item item_index = self.collection.index(item.row(), 0, group_gerber_index) idx = item_index.row() - new_c = (outline_color, fill_color, '%s_%d' % (_("Layer"), int(idx+1))) + new_c = (outline_color, fill_color, '%s_%d' % (_("Layer"), int(idx + 1))) try: self.options["gerber_color_list"][idx] = new_c except IndexError: for x in range(len(self.options["gerber_color_list"]), len(all_gerber_list)): self.options["gerber_color_list"].append( ( - self.options["gerber_plot_fill"], # content color - self.options["gerber_plot_line"], # outline color - '%s_%d' % (_("Layer"), int(idx+1))) # layer name + self.options["gerber_plot_fill"], # content color + self.options["gerber_plot_line"], # outline color + '%s_%d' % (_("Layer"), int(idx + 1))) # layer name ) self.options["gerber_color_list"][idx] = new_c elif sel_obj.kind == 'excellon': @@ -10748,7 +10179,7 @@ class MenuFileHandlers(QtCore.QObject): # make sure that the Excellon objeacts are drawn on top of everything excellon_objs = [obj for obj in obj_selection if obj.kind == 'excellon'] - cncjob_objs =[obj for obj in obj_selection if obj.kind == 'cncjob'] + cncjob_objs = [obj for obj in obj_selection if obj.kind == 'cncjob'] # reverse the object order such that the first selected is on top rest_objs = [obj for obj in obj_selection if obj.kind != 'excellon' and obj.kind != 'cncjob'][::-1] obj_selection = rest_objs + cncjob_objs + excellon_objs @@ -12266,8 +11697,8 @@ class MenuFileHandlers(QtCore.QObject): out1 = compressor_obj.compress(project_as_json) out2 = compressor_obj.flush() project_zipped = b"".join([out1, out2]) - except Exception as err: - self.log.error("Failed to save compressed file: %s because: %s" % (str(filename), str(err))) + except Exception as errrr: + self.log.error("Failed to save compressed file: %s because: %s" % (str(filename), str(errrr))) self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) return @@ -12315,7 +11746,7 @@ class MenuFileHandlers(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s: %s %s' % (_("Failed to parse saved project file"), str(filename), - _("Retry to save it."))) + _("Retry to save it."))) # noqa f.close() return except Exception: @@ -12323,7 +11754,7 @@ class MenuFileHandlers(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s: %s %s' % (_("Failed to parse saved project file"), str(filename), - _("Retry to save it."))) + _("Retry to save it."))) # noqa f.close() return @@ -12336,7 +11767,7 @@ class MenuFileHandlers(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s: %s %s' % (_("Failed to parse saved project file"), str(filename), - _("Retry to save it."))) + _("Retry to save it."))) # noqa tb_settings = QSettings("Open Source", "FlatCAM") lock_state = self.app.ui.lock_action.isChecked() @@ -12372,7 +11803,7 @@ class MenuFileHandlers(QtCore.QObject): if file_string.getvalue() == '': msg = _("Save cancelled because source file is empty. Try to export the file.") - self.inform.emit('[ERROR_NOTCL] %s' % msg) + self.inform.emit('[ERROR_NOTCL] %s' % msg) # noqa return 'fail' try: @@ -12388,7 +11819,7 @@ class MenuFileHandlers(QtCore.QObject): except PermissionError: self.inform.emit('[WARNING] %s' % _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) + "Most likely another app is holding the file open and not accessible.")) # noqa return 'fail' def on_file_savedefaults(self): @@ -12401,5 +11832,4 @@ class MenuFileHandlers(QtCore.QObject): self.app.defaults.update(self.app.options) self.app.preferencesUiManager.save_defaults() - # end of file diff --git a/appObjects/AppObject.py b/appObjects/AppObject.py index 3ff1091b..10d43655 100644 --- a/appObjects/AppObject.py +++ b/appObjects/AppObject.py @@ -336,11 +336,6 @@ class AppObject(QtCore.QObject): except Exception as e: self.app.log.error("AppObject.new_object() -> setting colors error. %s" % str(e)) - # ############################################################################################################# - # update the SHELL auto-completer model with the name of the new object - # ############################################################################################################# - self.app.shell.command_line().set_model_data(self.app.myKeywords) - if auto_select or self.app.ui.notebook.currentWidget() is self.app.ui.properties_tab: # select the just opened object but deselect the previous ones self.app.collection.set_all_inactive() diff --git a/appObjects/AppObjectTemplate.py b/appObjects/AppObjectTemplate.py index cecf2fdd..c198302a 100644 --- a/appObjects/AppObjectTemplate.py +++ b/appObjects/AppObjectTemplate.py @@ -331,10 +331,9 @@ class FlatCAMObj(QtCore.QObject): if new_name != old_name: # update the SHELL auto-completer model data try: - self.app.myKeywords.remove(old_name) - self.app.myKeywords.append(new_name) - self.app.shell._edit.set_model_data(self.app.myKeywords) - self.app.ui.code_editor.set_model_data(self.app.myKeywords) + self.app.regFK.remove_keyword(old_name, update=False) + self.app.regFK.prepend_keyword(new_name) + self.app.ui.code_editor.set_model_data(self.app.regFK.myKeywords) except Exception: self.app.log.debug( "on_name_activate() --> Could not remove the old object name from auto-completer model list") diff --git a/appObjects/ObjectCollection.py b/appObjects/ObjectCollection.py index cb5db7df..51b44282 100644 --- a/appObjects/ObjectCollection.py +++ b/appObjects/ObjectCollection.py @@ -363,7 +363,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.view.setFont(font) # ## GUI Events - self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change) + self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change) # noqa # self.view.activated.connect(self.on_item_activated) self.view.keyPressed.connect(self.app.ui.keyPressEvent) self.view.mouseReleased.connect(self.on_list_click_release) @@ -556,8 +556,8 @@ class ObjectCollection(QtCore.QAbstractItemModel): # update the SHELL auto-completer model data try: - self.app.myKeywords.remove(old_name) - self.app.myKeywords.append(new_name) + self.app.regFK.remove_keyword(old_name, update=False) + self.app.regFK.prepend_keyword(new_name) self.app.shell._edit.set_model_data(self.app.myKeywords) except Exception as e: self.app.log.error( @@ -567,7 +567,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): msg = "%s: %s %s: %s" % (_("Object renamed from"), old_name, _("to"), new_name) self.app.inform.emit(msg) - self.dataChanged.emit(index, index) + self.dataChanged.emit(index, index) # noqa return True else: return False @@ -620,7 +620,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): # ############################################################################################################ # update the KeyWords list with the name of the file # ############################################################################################################ - self.app.myKeywords.append(name) + self.app.regFK.prepend_keyword(name) # ############################################################################################################ # ############################# Set the Object UI (Properties Tab) ########################################### @@ -737,15 +737,15 @@ class ObjectCollection(QtCore.QAbstractItemModel): # some objects add a Tab on creation, close it here for idx in range(self.app.ui.plot_tab_area.count()): widget_name = self.app.ui.plot_tab_area.widget(idx).objectName() - if widget_name == active.obj.obj_options['name'] or widget_name == (active.obj.obj_options['name'] + "_editor_tab"): + if widget_name == active.obj.obj_options['name'] or \ + widget_name == (active.obj.obj_options['name'] + "_editor_tab"): self.app.ui.plot_tab_area.removeTab(idx) break # update the SHELL auto-completer model data name = active.obj.obj_options['name'] try: - self.app.myKeywords.remove(name) - self.app.shell._edit.set_model_data(self.app.myKeywords) + self.app.regFK.remove_keyword(name) # this is not needed any more because now the code editor is created on demand # self.app.ui.code_editor.set_model_data(self.app.myKeywords) except Exception as e: @@ -791,15 +791,15 @@ class ObjectCollection(QtCore.QAbstractItemModel): # some objects add a Tab on creation, close it here for idx in range(self.app.ui.plot_tab_area.count()): wdg_name = self.app.ui.plot_tab_area.widget(idx).objectName() - if wdg_name == deleted.obj.obj_options['name'] or wdg_name == (deleted.obj.obj_options['name'] + "_editor_tab"): + if wdg_name == deleted.obj.obj_options['name'] or \ + wdg_name == (deleted.obj.obj_options['name'] + "_editor_tab"): self.app.ui.plot_tab_area.removeTab(idx) break # update the SHELL auto-completer model data name = deleted.obj.obj_options['name'] try: - self.app.myKeywords.remove(name) - self.app.shell._edit.set_model_data(self.app.myKeywords) + self.app.regFK.remove_keyword(name) # this is not needed any more because now the code editor is created on demand # self.app.ui.code_editor.set_model_data(self.app.myKeywords) except Exception as e: @@ -1080,7 +1080,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): return obj_list def update_view(self): - self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) + self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex()) # noqa def on_row_activated(self, index): if index.isValid(): diff --git a/appPlugins/ToolShell.py b/appPlugins/ToolShell.py index b472f619..2a9df2a8 100644 --- a/appPlugins/ToolShell.py +++ b/appPlugins/ToolShell.py @@ -312,8 +312,6 @@ class FCShell(TermWidget): self.init_tcl() - self._edit.set_model_data(self.app.myKeywords) - app_icon = QtGui.QIcon() app_icon.addFile(self.app.resource_location + '/app16.png', QtCore.QSize(16, 16)) app_icon.addFile(self.app.resource_location + '/app24.png', QtCore.QSize(24, 24)) From 61325b7b5f212e985f7490d1eff2323b5f70d0ee Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 21 May 2022 01:25:14 +0300 Subject: [PATCH 03/55] - more code refactored in the appMain.py --- CHANGELOG.md | 4 + appCommon/RegisterFileKeywords.py | 4 +- appHandlers/AppIO.py | 2915 ++++++++++++++++++++++++++++ appHandlers/__init__.py | 0 appMain.py | 2923 +---------------------------- 5 files changed, 2946 insertions(+), 2900 deletions(-) create mode 100644 appHandlers/AppIO.py create mode 100644 appHandlers/__init__.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ba6b4a..8bb22aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +21.05.2022 + +- more code refactored in the appMain.py + 20.05.2022 - small fix for a bug that interfere with running the 2D graphic mode diff --git a/appCommon/RegisterFileKeywords.py b/appCommon/RegisterFileKeywords.py index d09da33a..1e2396cc 100644 --- a/appCommon/RegisterFileKeywords.py +++ b/appCommon/RegisterFileKeywords.py @@ -4,12 +4,14 @@ from PyQt6 import QtCore from dataclasses import dataclass import ctypes -import winreg from copy import deepcopy import os import sys import typing +if sys.platform == 'win32': + import winreg + if typing.TYPE_CHECKING: import appMain diff --git a/appHandlers/AppIO.py b/appHandlers/AppIO.py new file mode 100644 index 00000000..2f05a00a --- /dev/null +++ b/appHandlers/AppIO.py @@ -0,0 +1,2915 @@ + +from appEditors.AppExcEditor import AppExcEditor +from appEditors.AppGeoEditor import AppGeoEditor +from appEditors.AppGerberEditor import AppGerberEditor + +from appGUI.GUIElements import FCFileSaveDialog, FCMessageBox +from camlib import to_dict, dict2obj, ET, ParseError +from appParsers.ParseHPGL2 import HPGL2 + +from appObjects.ObjectCollection import * + +from reportlab.graphics import renderPDF +from reportlab.pdfgen import canvas +from reportlab.lib.units import inch, mm +from reportlab.lib.pagesizes import landscape, portrait +from svglib.svglib import svg2rlg +from xml.dom.minidom import parseString as parse_xml_string + +import time +import sys +import os +from copy import deepcopy +import re + +import numpy as np +from numpy import Inf + +from datetime import datetime +import simplejson as json + +from appCommon.Common import LoudDict + +from vispy.gloo.util import _screenshot +from vispy.io import write_png + +import traceback +import lzma +from io import StringIO + +# App Translation +import gettext +import appTranslation as fcTranslate +import builtins + +import typing + +if typing.TYPE_CHECKING: + import appMain + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class AppIO(QtCore.QObject): + def __init__(self, app): + """ + A class that holds all the menu -> file handlers + """ + super().__init__() + + self.app = app + self.log = self.app.log + self.inform = self.app.inform + self.splash = self.app.splash + self.worker_task = self.app.worker_task + self.options = self.app.options + self.app_units = self.app.app_units + self.pagesize = {} + + self.app.new_project_signal.connect(self.on_new_project_house_keeping) + + def on_fileopengerber(self, name=None): + """ + File menu callback for opening a Gerber. + + :param name: + :return: None + """ + + self.log.debug("on_fileopengerber()") + + _filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 " \ + "*.gko *.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil *.grb " \ + "*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb *.pho *.gdo *.art *.gbd *.outline);;" \ + "Protel Files (*.gtl *.gbl *.gts *.gbs *.gto *.gbo *.gtp *.gbp *.gml *.gm1 *.gm3 *.gko " \ + "*.outline);;" \ + "Eagle Files (*.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim " \ + "*.mil);;" \ + "OrCAD Files (*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb);;" \ + "Allegro Files (*.art);;" \ + "Mentor Files (*.pho *.gdo);;" \ + "All Files (*.*)" + + if name is None: + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Gerber"), + directory=self.app.get_last_folder(), + filter=_filter_, + initialFilter=self.app.last_op_gerber_filter) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Gerber"), filter=_filter_) + + filenames = [str(filename) for filename in filenames] + self.app.last_op_gerber_filter = _f + else: + filenames = [name] + self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" + "Canvas initialization finished in"), + '%.2f' % self.app.used_time, + _("Opening Gerber file.")), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + + if len(filenames) == 0: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_gerber, 'params': [filename]}) + + def on_fileopenexcellon(self, name=None): + """ + File menu callback for opening an Excellon file. + + :param name: + :return: None + """ + + self.log.debug("on_fileopenexcellon()") + + _filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc *.ncd);;" \ + "All Files (*.*)" + if name is None: + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Excellon"), + directory=self.app.get_last_folder(), + filter=_filter_, + initialFilter=self.app.last_op_excellon_filter) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Excellon"), filter=_filter_) + filenames = [str(filename) for filename in filenames] + self.app.last_op_excellon_filter = _f + else: + filenames = [str(name)] + self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" + "Canvas initialization finished in"), + '%.2f' % self.app.used_time, + _("Opening Excellon file.")), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + + if len(filenames) == 0: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_excellon, 'params': [filename]}) + + def on_fileopengcode(self, name=None): + """ + + File menu call back for opening gcode. + + :param name: + :return: + """ + + self.log.debug("on_fileopengcode()") + + # https://bobcadsupport.com/helpdesk/index.php?/Knowledgebase/Article/View/13/5/known-g-code-file-extensions + _filter_ = "G-Code Files (*.txt *.nc *.ncc *.tap *.gcode *.cnc *.ecs *.fnc *.dnc *.ncg *.gc *.fan *.fgc" \ + " *.din *.xpi *.hnc *.h *.i *.ncp *.min *.gcd *.rol *.knc *.mpr *.ply *.out *.eia *.sbp *.mpf);;" \ + "All Files (*.*)" + + if name is None: + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open G-Code"), + directory=self.app.get_last_folder(), + filter=_filter_, + initialFilter=self.app.last_op_gcode_filter) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open G-Code"), filter=_filter_) + + filenames = [str(filename) for filename in filenames] + self.app.last_op_gcode_filter = _f + else: + filenames = [name] + self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" + "Canvas initialization finished in"), + '%.2f' % self.app.used_time, + _("Opening G-Code file.")), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + + if len(filenames) == 0: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_gcode, 'params': [filename, None, True]}) + + def on_file_openproject(self): + """ + File menu callback for opening a project. + + :return: None + """ + + self.log.debug("on_file_openproject()") + + _filter_ = "FlatCAM Project (*.FlatPrj);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Project"), + directory=self.app.get_last_folder(), filter=_filter_) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Project"), filter=_filter_) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + # self.worker_task.emit({'fcn': self.open_project, + # 'params': [filename]}) + # The above was failing because open_project() is not + # thread safe. The new_project() + self.open_project(filename) + + def on_fileopenhpgl2(self, name=None): + """ + File menu callback for opening a HPGL2. + + :param name: + :return: None + """ + self.log.debug("on_fileopenhpgl2()") + + _filter_ = "HPGL2 Files (*.plt);;" \ + "All Files (*.*)" + + if name is None: + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"), + directory=self.app.get_last_folder(), + filter=_filter_) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"), filter=_filter_) + + filenames = [str(filename) for filename in filenames] + else: + filenames = [name] + self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" + "Canvas initialization finished in"), + '%.2f' % self.app.used_time, + _("Opening HPGL2 file.")), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + + if len(filenames) == 0: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_hpgl2, 'params': [filename]}) + + def on_file_openconfig(self): + """ + File menu callback for opening a config file. + + :return: None + """ + + self.log.debug("on_file_openconfig()") + + _filter_ = "FlatCAM Config (*.FlatConfig);;FlatCAM Config (*.json);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Configuration File"), + directory=self.app.data_path, filter=_filter_) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Configuration File"), + filter=_filter_) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + self.open_config_file(filename) + + def on_file_exportsvg(self): + """ + Callback for menu item File->Export SVG. + + :return: None + """ + self.log.debug("on_file_exportsvg()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if (not isinstance(obj, GeometryObject) + and not isinstance(obj, GerberObject) + and not isinstance(obj, CNCJobObject) + and not isinstance(obj, ExcellonObject)): + msg = _("Only Geometry, Gerber and CNCJob objects can be used.") + msgbox = FCMessageBox(parent=self.app.ui) + msgbox.setWindowTitle(msg) # taskbar still shows it + msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) + + msgbox.setInformativeText(msg) + msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/waning.png')) + + bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) + msgbox.setDefaultButton(bt_ok) + msgbox.exec() + return + + name = obj.obj_options["name"] + + _filter = "SVG File (*.svg);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export SVG"), + directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg', + ext_filter=_filter) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export SVG"), + ext_filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + self.export_svg(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("SVG", filename) + self.app.file_saved.emit("SVG", filename) + + def on_file_exportpng(self): + + self.log.debug("on_file_exportpng()") + + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + + data = None + if self.app.use_3d_engine: + image = _screenshot(alpha=False) + data = np.asarray(image) + if not data.ndim == 3 and data.shape[-1] in (3, 4): + self.inform.emit('[[WARNING_NOTCL]] %s' % _('Data must be a 3D array with last dimension 3 or 4')) + return + + filter_ = "PNG File (*.png);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export PNG Image"), + directory=self.app.get_last_save_folder() + '/png_' + date, + ext_filter=filter_) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export PNG Image"), + ext_filter=filter_) + + filename = str(filename) + + if filename == "": + self.inform.emit(_("Cancelled.")) + return + else: + if self.app.use_3d_engine: + write_png(filename, data) + else: + self.app.plotcanvas.figure.savefig(filename) + + if self.options["global_open_style"] is False: + self.app.file_opened.emit("png", filename) + self.app.file_saved.emit("png", filename) + + def on_file_savegerber(self): + """ + Callback for menu item in Project context menu. + + :return: None + """ + self.log.debug("on_file_savegerber()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, GerberObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Gerber objects can be saved as Gerber files...")) + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter = "Gerber File (*.GBR);;Gerber File (*.GRB);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption="Save Gerber source file", + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Gerber source file"), + ext_filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + self.save_source_file(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Gerber", filename) + self.app.file_saved.emit("Gerber", filename) + + def on_file_savescript(self): + """ + Callback for menu item in Project context menu. + + :return: None + """ + self.log.debug("on_file_savescript()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, ScriptObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Script objects can be saved as TCL Script files...")) + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption="Save Script source file", + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Script source file"), + ext_filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + self.save_source_file(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Script", filename) + self.app.file_saved.emit("Script", filename) + + def on_file_savedocument(self): + """ + Callback for menu item in Project context menu. + + :return: None + """ + self.log.debug("on_file_savedocument()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, ScriptObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Document objects can be saved as Document files...")) + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter = "FlatCAM Documents (*.FlatDoc);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption="Save Document source file", + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Document source file"), + ext_filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + self.save_source_file(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Document", filename) + self.app.file_saved.emit("Document", filename) + + def on_file_saveexcellon(self): + """ + Callback for menu item in project context menu. + + :return: None + """ + self.log.debug("on_file_saveexcellon()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, ExcellonObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter = "Excellon File (*.DRL);;Excellon File (*.TXT);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Excellon source file"), + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Excellon source file"), ext_filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + self.save_source_file(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Excellon", filename) + self.app.file_saved.emit("Excellon", filename) + + def on_file_exportexcellon(self): + """ + Callback for menu item File->Export->Excellon. + + :return: None + """ + self.log.debug("on_file_exportexcellon()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, ExcellonObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter = self.options["excellon_save_filters"] + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Excellon"), + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Excellon"), + ext_filter=_filter) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + used_extension = filename.rpartition('.')[2] + obj.update_filters(last_ext=used_extension, filter_string='excellon_save_filters') + + self.export_excellon(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Excellon", filename) + self.app.file_saved.emit("Excellon", filename) + + def on_file_exportgerber(self): + """ + Callback for menu item File->Export->Gerber. + + :return: None + """ + self.log.debug("on_file_exportgerber()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if not isinstance(obj, GerberObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Gerber objects can be saved as Gerber files...")) + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter_ = self.options['gerber_save_filters'] + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Gerber"), + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter_) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Gerber"), + ext_filter=_filter_) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + used_extension = filename.rpartition('.')[2] + obj.update_filters(last_ext=used_extension, filter_string='gerber_save_filters') + + self.export_gerber(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Gerber", filename) + self.app.file_saved.emit("Gerber", filename) + + def on_file_exportdxf(self): + """ + Callback for menu item File->Export DXF. + + :return: None + """ + self.log.debug("on_file_exportdxf()") + + obj = self.app.collection.get_active() + if obj is None: + self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) + return + + # Check for more compatible types and add as required + if obj.kind != 'geometry': + msg = _("Only Geometry objects can be used.") + msgbox = FCMessageBox(parent=self.app.ui) + msgbox.setWindowTitle(msg) # taskbar still shows it + msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) + + msgbox.setInformativeText(msg) + msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/waning.png')) + + msgbox.setIcon(QtWidgets.QMessageBox.Icon.Warning) + + msgbox.setInformativeText(msg) + bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) + msgbox.setDefaultButton(bt_ok) + msgbox.exec() + return + + name = self.app.collection.get_active().obj_options["name"] + + _filter_ = "DXF File .dxf (*.DXF);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export DXF"), + directory=self.app.get_last_save_folder() + '/' + name, + ext_filter=_filter_) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export DXF"), + ext_filter=_filter_) + + filename = str(filename) + + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + self.export_dxf(name, filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("DXF", filename) + self.app.file_saved.emit("DXF", filename) + + def on_file_importsvg(self, type_of_obj): + """ + Callback for menu item File->Import SVG. + :param type_of_obj: to import the SVG as Geometry or as Gerber + :type type_of_obj: str + :return: None + """ + self.log.debug("on_file_importsvg()") + + _filter_ = "SVG File .svg (*.svg);;All Files (*.*)" + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import SVG"), + directory=self.app.get_last_folder(), + filter=_filter_) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import SVG"), + filter=_filter_) + + if type_of_obj != "geometry" and type_of_obj != "gerber": + type_of_obj = "geometry" + + filenames = [str(filename) for filename in filenames] + + if len(filenames) == 0: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.import_svg, 'params': [filename, type_of_obj]}) + + def on_file_importdxf(self, type_of_obj): + """ + Callback for menu item File->Import DXF. + :param type_of_obj: to import the DXF as Geometry or as Gerber + :type type_of_obj: str + :return: None + """ + self.log.debug("on_file_importdxf()") + + _filter_ = "DXF File .dxf (*.DXF);;All Files (*.*)" + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import DXF"), + directory=self.app.get_last_folder(), + filter=_filter_) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import DXF"), + filter=_filter_) + + if type_of_obj != "geometry" and type_of_obj != "gerber": + type_of_obj = "geometry" + + filenames = [str(filename) for filename in filenames] + + if len(filenames) == 0: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.import_dxf, 'params': [filename, type_of_obj]}) + + def on_file_new_click(self): + """ + Callback for menu item File -> New. + Executed on clicking the Menu -> File -> New Project + + :return: + """ + self.log.debug("on_file_new_click()") + + if self.app.collection.get_list() and self.app.should_we_save: + msgbox = FCMessageBox(parent=self.app.ui) + title = _("Save changes") + txt = _("There are files/objects opened.\n" + "Creating a New project will delete them.\n" + "Do you want to Save the project?") + msgbox.setWindowTitle(title) # taskbar still shows it + msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) + msgbox.setText('%s' % title) + msgbox.setInformativeText(txt) + msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/save_as.png')) + + bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.ButtonRole.YesRole) + bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.ButtonRole.NoRole) + bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) + + msgbox.setDefaultButton(bt_yes) + msgbox.exec() + response = msgbox.clickedButton() + + if response == bt_yes: + self.on_file_saveprojectas(use_thread=True) + elif response == bt_cancel: + return + elif response == bt_no: + self.on_file_new_project(use_thread=True, silenced=True) + else: + self.on_file_new_project(use_thread=True, silenced=True) + + def on_file_new_project(self, cli=None, reset_tcl=True, use_thread=None, silenced=None, keep_scripts=True): + """ + Returns the application to its startup state. This method is thread-safe. + + :param cli: Boolean. If True this method was run from command line + :param reset_tcl: Boolean. If False, on new project creation the Tcl instance is not recreated, therefore it + will remember all the previous variables. If True then the Tcl is re-instantiated. + :param use_thread: Bool. If True some part of the initialization are done threaded + :param silenced: Bool or None. If True then the app will not ask to save the current parameters. + :param keep_scripts: Bool. If True the Script objects are not deleted when creating a new project + :return: None + """ + + self.log.debug("on_file_new_project()") + + t_start_proj = time.time() + + # close any editor that might be open + if self.app.call_source != 'app': + self.app.editor2object(cleanup=True) + # ## EDITOR section + self.app.geo_editor = AppGeoEditor(self.app) + self.app.exc_editor = AppExcEditor(self.app) + self.app.grb_editor = AppGerberEditor(self.app) + + for obj in self.app.collection.get_list(): + # delete shapes left drawn from mark shape_collections, if any + if isinstance(obj, GerberObject): + try: + obj.mark_shapes_storage.clear() + obj.mark_shapes.clear(update=True) + obj.mark_shapes.enabled = False + except AttributeError: + pass + + # also delete annotation shapes, if any + elif isinstance(obj, CNCJobObject): + try: + obj.text_col.enabled = False + del obj.text_col + obj.annotation.clear(update=True) + del obj.annotation + except AttributeError: + pass + + # delete the exclusion areas + self.app.exc_areas.clear_shapes() + + # delete any selection shape on canvas + self.app.delete_selection_shape() + + # delete all App objects + if keep_scripts is True: + for prj_obj in self.app.collection.get_list(): + if prj_obj.kind != 'script': + self.app.collection.delete_by_name(prj_obj.obj_options['name'], select_project=False) + else: + self.app.collection.delete_all() + + self.log.debug('%s: %s %s.' % + ("Deleted all the application objects", str(time.time() - t_start_proj), _("seconds"))) + + # add in Selected tab an initial text that describe the flow of work in FlatCAm + self.app.setup_default_properties_tab() + + # Clear project filename + self.app.project_filename = None + + default_file = self.app.defaults_path() + # Load the application options + self.options.load(filename=default_file, inform=self.inform) + + # Re-fresh project options + self.app.on_defaults2options() + + if use_thread is True: + self.app.new_project_signal.emit() + else: + t0 = time.time() + # Clear pool + self.app.clear_pool() + + # Init FlatCAMTools + if reset_tcl is True: + self.app.init_tools(init_tcl=True) + else: + self.app.init_tools(init_tcl=False) + self.log.debug( + '%s: %s %s.' % ("Initiated the MP pool and plugins in: ", str(time.time() - t0), _("seconds"))) + + # tcl needs to be reinitialized, otherwise old shell variables etc remains + # self.app.shell.init_tcl() + + # Try to close all tabs in the PlotArea but only if the appGUI is active (CLI is None) + if cli is None: + # we need to go in reverse because once we remove a tab then the index changes + # meaning that removing the first tab (idx = 0) then the tab at former idx = 1 will assume idx = 0 + # and so on. Therefore the deletion should be done in reverse + wdg_count = self.app.ui.plot_tab_area.tabBar.count() - 1 + for index in range(wdg_count, -1, -1): + try: + self.app.ui.plot_tab_area.closeTab(index) + except Exception as e: + self.log.error("App.on_file_new_project() --> %s" % str(e)) + + # # And then add again the Plot Area + self.app.ui.plot_tab_area.insertTab(0, self.app.ui.plot_tab, _("Plot Area")) + self.app.ui.plot_tab_area.protectTab(0) + + # take the focus of the Notebook on Project Tab. + self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) + + self.log.debug('%s: %s %s.' % (_("Project created in"), str(time.time() - t_start_proj), _("seconds"))) + self.app.ui.set_ui_title(name=_("New Project - Not saved")) + + self.inform.emit('[success] %s...' % _("New Project created")) + + def on_new_project_house_keeping(self): + """ + Do dome of the new project initialization in a threaded way + + :return: + :rtype: + """ + t0 = time.time() + + # Clear pool + self.log.debug("New Project: cleaning multiprocessing pool.") + self.app.clear_pool() + + # Init FlatCAMTools + self.log.debug("New Project: initializing the Tools and Tcl Shell.") + self.app.init_tools(init_tcl=True) + self.log.debug('%s: %s %s.' % ("Initiated the MP pool and plugins in: ", str(time.time() - t0), _("seconds"))) + + def on_filenewscript(self, silent=False): + """ + Will create a new script file and open it in the Code Editor + + :param silent: if True will not display status messages + :return: None + """ + self.log.debug("on_filenewscript()") + + if silent is False: + self.inform.emit('[success] %s' % _("New TCL script file created in Code Editor.")) + + # hide coordinates toolbars in the infobar while in DB + self.app.ui.coords_toolbar.hide() + self.app.ui.delta_coords_toolbar.hide() + + self.app.app_obj.new_script_object() + + def on_fileopenscript(self, name=None, silent=False): + """ + Will open a Tcl script file into the Code Editor + + :param silent: if True will not display status messages + :param name: name of a Tcl script file to open + :return: None + """ + + self.log.debug("on_fileopenscript()") + + _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ + "All Files (*.*)" + + if name: + filenames = [name] + else: + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames( + caption=_("Open TCL script"), directory=self.app.get_last_folder(), filter=_filter_) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_) + + if len(filenames) == 0: + if silent is False: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_script, 'params': [filename]}) + + def on_fileopenscript_example(self, name=None, silent=False): + """ + Will open a Tcl script file into the Code Editor + + :param silent: if True will not display status messages + :param name: name of a Tcl script file to open + :return: + """ + + self.log.debug("on_fileopenscript_example()") + + _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ + "All Files (*.*)" + + # test if the app was frozen and choose the path for the configuration file + if getattr(sys, "frozen", False) is True: + example_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\assets\\examples' + else: + example_path = os.path.dirname(os.path.realpath(__file__)) + '\\assets\\examples' + + if name: + filenames = [name] + else: + try: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames( + caption=_("Open TCL script"), directory=example_path, filter=_filter_) + except TypeError: + filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_) + + if len(filenames) == 0: + if silent is False: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + for filename in filenames: + if filename != '': + self.worker_task.emit({'fcn': self.open_script, 'params': [filename]}) + + def on_filerunscript(self, name=None, silent=False): + """ + File menu callback for loading and running a TCL script. + + :param silent: if True will not display status messages + :param name: name of a Tcl script file to be run by FlatCAM + :return: None + """ + + self.log.debug("on_file_runscript()") + + if name: + filename = name + if self.app.cmd_line_headless != 1: + self.splash.showMessage('%s: %ssec\n%s' % + (_("Canvas initialization started.\n" + "Canvas initialization finished in"), '%.2f' % self.app.used_time, + _("Executing ScriptObject file.") + ), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + else: + _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ + "All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Run TCL script"), + directory=self.app.get_last_folder(), + filter=_filter_) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Run TCL script"), filter=_filter_) + + # The Qt methods above will return a QString which can cause problems later. + # So far json.dump() will fail to serialize it. + filename = str(filename) + + if filename == "": + if silent is False: + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + if self.app.cmd_line_headless != 1: + if self.app.ui.shell_dock.isHidden(): + self.app.ui.shell_dock.show() + + try: + with open(filename, "r") as tcl_script: + cmd_line_shellfile_content = tcl_script.read() + if self.app.cmd_line_headless != 1: + self.app.shell.exec_command(cmd_line_shellfile_content) + else: + self.app.shell.exec_command(cmd_line_shellfile_content, no_echo=True) + + if silent is False: + self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor and executed.")) + except Exception as e: + self.app.error("App.on_filerunscript() -> %s" % str(e)) + sys.exit(2) + + def on_file_saveproject(self, silent=False): + """ + Callback for menu item File->Save Project. Saves the project to + ``self.project_filename`` or calls ``self.on_file_saveprojectas()`` + if set to None. The project is saved by calling ``self.save_project()``. + + :param silent: if True will not display status messages + :return: None + """ + self.log.debug("on_file_saveproject()") + + if self.app.project_filename is None: + self.on_file_saveprojectas() + else: + self.worker_task.emit({'fcn': self.save_project, 'params': [self.app.project_filename, silent]}) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("project", self.app.project_filename) + self.app.file_saved.emit("project", self.app.project_filename) + + self.app.ui.set_ui_title(name=self.app.project_filename) + + self.app.should_we_save = False + + def on_file_saveprojectas(self, make_copy=False, use_thread=True, quit_action=False): + """ + Callback for menu item File->Save Project As... Opens a file + chooser and saves the project to the given file via + ``self.save_project()``. + + :param make_copy if to be create a copy of the project; boolean + :param use_thread: if to be run in a separate thread; boolean + :param quit_action: if to be followed by quiting the application; boolean + :return: None + """ + self.log.debug("on_file_saveprojectas()") + + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + + filter_ = "FlatCAM Project .FlatPrj (*.FlatPrj);; All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Project As ..."), + directory='{l_save}/{proj}_{date}'.format(l_save=str(self.app.get_last_save_folder()), date=date, + proj=_("Project")), + ext_filter=filter_ + ) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Project As ..."), + ext_filter=filter_) + + filename = str(filename) + + if filename == '': + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + + if use_thread is True: + self.worker_task.emit({'fcn': self.save_project, 'params': [filename, quit_action]}) + else: + self.save_project(filename, quit_action) + + # self.save_project(filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("project", filename) + self.app.file_saved.emit("project", filename) + + if not make_copy: + self.app.project_filename = filename + + self.app.ui.set_ui_title(name=self.app.project_filename) + self.app.should_we_save = False + + def on_file_save_objects_pdf(self, use_thread=True): + self.log.debug("on_file_save_objects_pdf()") + + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + + try: + obj_selection = self.app.collection.get_selected() + if len(obj_selection) == 1: + obj_name = str(obj_selection[0].obj_options['name']) + else: + obj_name = _("General_print") + except AttributeError as att_err: + self.log.debug("App.on_file_save_object_pdf() --> %s" % str(att_err)) + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return + + if not obj_selection: + self.inform.emit( + '[WARNING_NOTCL] %s %s' % (_("No object is selected."), _("Print everything in the workspace."))) + obj_selection = self.app.collection.get_list() + + filter_ = "PDF File .pdf (*.PDF);; All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Object as PDF ..."), + directory='{l_save}/{obj_name}_{date}'.format(l_save=str(self.app.get_last_save_folder()), + obj_name=obj_name, + date=date), + ext_filter=filter_ + ) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Save Object as PDF ..."), + ext_filter=filter_) + + filename = str(filename) + + if filename == '': + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + + if use_thread is True: + self.app.proc_container.new(_("Printing PDF ...")) + self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_selection]}) + else: + self.save_pdf(filename, obj_selection) + + # self.save_project(filename) + if self.options["global_open_style"] is False: + self.app.file_opened.emit("pdf", filename) + self.app.file_saved.emit("pdf", filename) + + def save_pdf(self, file_name, obj_selection): + self.log.debug("save_pdf()") + + p_size = self.options['global_workspaceT'] + orientation = self.options['global_workspace_orientation'] + color = 'black' + transparency_level = 1.0 + + self.pagesize.update( + { + 'Bounds': None, + 'A0': (841 * mm, 1189 * mm), + 'A1': (594 * mm, 841 * mm), + 'A2': (420 * mm, 594 * mm), + 'A3': (297 * mm, 420 * mm), + 'A4': (210 * mm, 297 * mm), + 'A5': (148 * mm, 210 * mm), + 'A6': (105 * mm, 148 * mm), + 'A7': (74 * mm, 105 * mm), + 'A8': (52 * mm, 74 * mm), + 'A9': (37 * mm, 52 * mm), + 'A10': (26 * mm, 37 * mm), + + 'B0': (1000 * mm, 1414 * mm), + 'B1': (707 * mm, 1000 * mm), + 'B2': (500 * mm, 707 * mm), + 'B3': (353 * mm, 500 * mm), + 'B4': (250 * mm, 353 * mm), + 'B5': (176 * mm, 250 * mm), + 'B6': (125 * mm, 176 * mm), + 'B7': (88 * mm, 125 * mm), + 'B8': (62 * mm, 88 * mm), + 'B9': (44 * mm, 62 * mm), + 'B10': (31 * mm, 44 * mm), + + 'C0': (917 * mm, 1297 * mm), + 'C1': (648 * mm, 917 * mm), + 'C2': (458 * mm, 648 * mm), + 'C3': (324 * mm, 458 * mm), + 'C4': (229 * mm, 324 * mm), + 'C5': (162 * mm, 229 * mm), + 'C6': (114 * mm, 162 * mm), + 'C7': (81 * mm, 114 * mm), + 'C8': (57 * mm, 81 * mm), + 'C9': (40 * mm, 57 * mm), + 'C10': (28 * mm, 40 * mm), + + # American paper sizes + 'LETTER': (8.5 * inch, 11 * inch), + 'LEGAL': (8.5 * inch, 14 * inch), + 'ELEVENSEVENTEEN': (11 * inch, 17 * inch), + + # From https://en.wikipedia.org/wiki/Paper_size + 'JUNIOR_LEGAL': (5 * inch, 8 * inch), + 'HALF_LETTER': (5.5 * inch, 8 * inch), + 'GOV_LETTER': (8 * inch, 10.5 * inch), + 'GOV_LEGAL': (8.5 * inch, 13 * inch), + 'LEDGER': (17 * inch, 11 * inch), + } + ) + + # make sure that the Excellon objeacts are drawn on top of everything + excellon_objs = [obj for obj in obj_selection if obj.kind == 'excellon'] + cncjob_objs = [obj for obj in obj_selection if obj.kind == 'cncjob'] + # reverse the object order such that the first selected is on top + rest_objs = [obj for obj in obj_selection if obj.kind != 'excellon' and obj.kind != 'cncjob'][::-1] + obj_selection = rest_objs + cncjob_objs + excellon_objs + + # generate the SVG files from the application objects + exported_svg = [] + for obj in obj_selection: + svg_obj = obj.export_svg(scale_stroke_factor=0.0) + + if obj.kind.lower() == 'gerber' or obj.kind.lower() == 'excellon': + color = obj.fill_color[:-2] + transparency_level = obj.fill_color[-2:] + elif obj.kind.lower() == 'geometry': + color = self.options["global_draw_color"] + + # Change the attributes of the exported SVG + # We don't need stroke-width + # We set opacity to maximum + # We set the colour to WHITE + + try: + root = ET.fromstring(svg_obj) + except Exception as e: + self.log.debug("AppIO.save_pdf() -> Missing root node -> %s" % str(e)) + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed.")) + return + + for child in root: + child.set('fill', str(color)) + child.set('opacity', str(transparency_level)) + child.set('stroke', str(color)) + + exported_svg.append(ET.tostring(root)) + + xmin = Inf + ymin = Inf + xmax = -Inf + ymax = -Inf + + for obj in obj_selection: + try: + gxmin, gymin, gxmax, gymax = obj.bounds() + xmin = min([xmin, gxmin]) + ymin = min([ymin, gymin]) + xmax = max([xmax, gxmax]) + ymax = max([ymax, gymax]) + except Exception as e: + self.log.error("Tried to get bounds of empty geometry in App.save_pdf(). %s" % str(e)) + + # Determine bounding area for svg export + bounds = [xmin, ymin, xmax, ymax] + size = bounds[2] - bounds[0], bounds[3] - bounds[1] + + # This contain the measure units + uom = obj_selection[0].units.lower() + + # Define a boundary around SVG of about 1.0mm (~39mils) + if uom in "mm": + boundary = 1.0 + else: + boundary = 0.0393701 + + # Convert everything to strings for use in the xml doc + svgwidth = str(size[0] + (2 * boundary)) + svgheight = str(size[1] + (2 * boundary)) + minx = str(bounds[0] - boundary) + miny = str(bounds[1] + boundary + size[1]) + + # Add a SVG Header and footer to the svg output from shapely + # The transform flips the Y Axis so that everything renders + # properly within svg apps such as inkscape + svg_header = ' PDF output --> %s" % str(e)) + return 'fail' + + self.inform.emit('[success] %s: %s' % (_("PDF file saved to"), file_name)) + + def export_svg(self, obj_name, filename, scale_stroke_factor=0.00): + """ + Exports a Geometry Object to an SVG file. + + :param obj_name: the name of the FlatCAM object to be saved as SVG + :param filename: Path to the SVG file to save to. + :param scale_stroke_factor: factor by which to change/scale the thickness of the features + :return: + """ + if filename is None: + filename = self.app.options["global_last_save_folder"] if \ + self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] + + self.log.debug("export_svg()") + + try: + obj = self.app.collection.get_by_name(str(obj_name)) + except Exception: + return 'fail' + + with self.app.proc_container.new(_("Exporting ...")): + exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor) + + # Determine bounding area for svg export + bounds = obj.bounds() + size = obj.size() + + # Convert everything to strings for use in the xml doc + svgwidth = str(size[0]) + svgheight = str(size[1]) + minx = str(bounds[0]) + miny = str(bounds[1] - size[1]) + uom = obj.units.lower() + + # Add a SVG Header and footer to the svg output from shapely + # The transform flips the Y Axis so that everything renders + # properly within svg apps such as inkscape + svg_header = '' + svg_header += '' + svg_footer = ' ' + svg_elem = svg_header + exported_svg + svg_footer + + # Parse the xml through a xml parser just to add line feeds + # and to make it look more pretty for the output + svgcode = parse_xml_string(svg_elem) + svgcode = svgcode.toprettyxml() + + try: + with open(filename, 'w') as fp: + fp.write(svgcode) + except PermissionError: + self.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return 'fail' + + if self.options["global_open_style"] is False: + self.app.file_opened.emit("SVG", filename) + self.app.file_saved.emit("SVG", filename) + self.inform.emit('[success] %s: %s' % (_("SVG file exported to"), filename)) + + def on_import_preferences(self): + """ + Loads the application default settings from a saved file into + ``self.options`` dictionary. + + :return: None + """ + + self.log.debug("App.on_import_preferences()") + + # Show file chooser + filter_ = "Config File (*.FlatConfig);;All Files (*.*)" + try: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Preferences"), + directory=self.app.data_path, + filter=filter_) + except TypeError: + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Preferences"), + filter=filter_) + filename = str(filename) + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + + # Load in the options from the chosen file + self.options.load(filename=filename, inform=self.inform) + + self.app.preferencesUiManager.on_preferences_edited() + self.inform.emit('[success] %s: %s' % (_("Imported Defaults from"), filename)) + + def on_export_preferences(self): + """ + Save the options dictionary to a file. + + :return: None + """ + self.log.debug("on_export_preferences()") + + # defaults_file_content = None + + # Show file chooser + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + filter__ = "Config File .FlatConfig (*.FlatConfig);;All Files (*.*)" + try: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export FlatCAM Preferences"), + directory=os.path.join(self.app.data_path, 'preferences_%s' % date), + ext_filter=filter__ + ) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export FlatCAM Preferences"), ext_filter=filter__) + filename = str(filename) + if filename == "": + self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return 'fail' + + # Update options + self.app.preferencesUiManager.defaults_read_form() + self.options.propagate_defaults() + + # Save update options + try: + self.options.write(filename=filename) + except Exception: + self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename))) + return + + if self.options["global_open_style"] is False: + self.app.file_opened.emit("preferences", filename) + self.app.file_saved.emit("preferences", filename) + self.inform.emit('[success] %s: %s' % (_("Exported preferences to"), filename)) + + def export_excellon(self, obj_name, filename, local_use=None, use_thread=True): + """ + Exports a Excellon Object to an Excellon file. + + :param obj_name: the name of the FlatCAM object to be saved as Excellon + :param filename: Path to the Excellon file to save to. + :param local_use: + :param use_thread: if to be run in a separate thread + :return: + """ + + if filename is None: + if self.app.options["global_last_save_folder"]: + filename = self.app.options["global_last_save_folder"] + '/' + 'exported_excellon' + else: + filename = self.app.options["global_last_folder"] + '/' + 'exported_excellon' + + self.log.debug("export_excellon()") + + format_exc = ';FILE_FORMAT=%d:%d\n' % (self.options["excellon_exp_integer"], + self.options["excellon_exp_decimals"] + ) + + if local_use is None: + try: + obj = self.app.collection.get_by_name(str(obj_name)) + except Exception: + return "Could not retrieve object: %s" % obj_name + else: + obj = local_use + + if not isinstance(obj, ExcellonObject): + self.inform.emit('[ERROR_NOTCL] %s' % + _("Failed. Only Excellon objects can be saved as Excellon files...")) + return + + # updated units + eunits = self.options["excellon_exp_units"] + ewhole = self.options["excellon_exp_integer"] + efract = self.options["excellon_exp_decimals"] + ezeros = self.options["excellon_exp_zeros"] + eformat = self.options["excellon_exp_format"] + slot_type = self.options["excellon_exp_slot_type"] + + fc_units = self.app_units.upper() + if fc_units == 'MM': + factor = 1 if eunits == 'METRIC' else 0.03937 + else: + factor = 25.4 if eunits == 'METRIC' else 1 + + def make_excellon(): + try: + time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + + header = 'M48\n' + header += ';EXCELLON GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ + (str(self.app.version), str(self.app.version_date)) + + header += ';Filename: %s' % str(obj_name) + '\n' + header += ';Created on : %s' % time_str + '\n' + + if eformat == 'dec': + has_slots, excellon_code = obj.export_excellon(ewhole, efract, factor=factor, slot_type=slot_type) + header += eunits + '\n' + + for tool in obj.tools: + if eunits == 'METRIC': + header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['tooldia']) * factor, + tool=str(tool), + dec=2) + else: + header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['tooldia']) * factor, + tool=str(tool), + dec=4) + else: + if ezeros == 'LZ': + has_slots, excellon_code = obj.export_excellon(ewhole, efract, + form='ndec', e_zeros='LZ', factor=factor, + slot_type=slot_type) + header += '%s,%s\n' % (eunits, 'LZ') + header += format_exc + + for tool in obj.tools: + if eunits == 'METRIC': + header += "T{tool}F00S00C{:.{dec}f}\n".format( + float(obj.tools[tool]['tooldia']) * factor, + tool=str(tool), + dec=2) + else: + header += "T{tool}F00S00C{:.{dec}f}\n".format( + float(obj.tools[tool]['tooldia']) * factor, + tool=str(tool), + dec=4) + else: + has_slots, excellon_code = obj.export_excellon(ewhole, efract, + form='ndec', e_zeros='TZ', factor=factor, + slot_type=slot_type) + header += '%s,%s\n' % (eunits, 'TZ') + header += format_exc + + for tool in obj.tools: + if eunits == 'METRIC': + header += "T{tool}F00S00C{:.{dec}f}\n".format( + float(obj.tools[tool]['tooldia']) * factor, + tool=str(tool), + dec=2) + else: + header += "T{tool}F00S00C{:.{dec}f}\n".format( + float(obj.tools[tool]['tooldia']) * factor, + tool=str(tool), + dec=4) + header += '%\n' + footer = 'M30\n' + + exported_excellon = header + exported_excellon += excellon_code + exported_excellon += footer + + if local_use is None: + try: + with open(filename, 'w') as fp: + fp.write(exported_excellon) + except PermissionError: + self.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return 'fail' + + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Excellon", filename) + self.app.file_saved.emit("Excellon", filename) + self.inform.emit('[success] %s: %s' % (_("Excellon file exported to"), filename)) + else: + return exported_excellon + except Exception as e: + self.log.error("App.export_excellon.make_excellon() --> %s" % str(e)) + return 'fail' + + if use_thread is True: + + with self.app.proc_container.new(_("Exporting ...")): + + def job_thread_exc(app_obj): + ret = make_excellon() + if ret == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) + return + + self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]}) + else: + eret = make_excellon() + if eret == 'fail': + self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) + return 'fail' + if local_use is not None: + return eret + + def export_gerber(self, obj_name, filename, local_use=None, use_thread=True): + """ + Exports a Gerber Object to an Gerber file. + + :param obj_name: the name of the FlatCAM object to be saved as Gerber + :param filename: Path to the Gerber file to save to. + :param local_use: if the Gerber code is to be saved to a file (None) or used within FlatCAM. + When not None, the value will be the actual Gerber object for which to create + the Gerber code + :param use_thread: if to be run in a separate thread + :return: + """ + if filename is None: + filename = self.app.options["global_last_save_folder"] if \ + self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] + + self.log.debug("export_gerber()") + + if local_use is None: + try: + obj = self.app.collection.get_by_name(str(obj_name)) + except Exception: + return 'fail' + else: + obj = local_use + + # updated units + gunits = self.options["gerber_exp_units"] + gwhole = self.options["gerber_exp_integer"] + gfract = self.options["gerber_exp_decimals"] + gzeros = self.options["gerber_exp_zeros"] + + fc_units = self.app_units.upper() + if fc_units == 'MM': + factor = 1 if gunits == 'MM' else 0.03937 + else: + factor = 25.4 if gunits == 'MM' else 1 + + def make_gerber(): + try: + time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + + header = 'G04*\n' + header += 'G04 RS-274X GERBER GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' % \ + (str(self.app.version), str(self.app.version_date)) + + header += 'G04 Filename: %s*' % str(obj_name) + '\n' + header += 'G04 Created on : %s*' % time_str + '\n' + header += '%%FS%sAX%s%sY%s%s*%%\n' % (gzeros, gwhole, gfract, gwhole, gfract) + header += "%MO{units}*%\n".format(units=gunits) + + for apid in obj.tools: + if obj.tools[apid]['type'] == 'C': + header += "%ADD{apid}{type},{size}*%\n".format( + apid=str(apid), + type='C', + size=(factor * obj.tools[apid]['size']) + ) + elif obj.tools[apid]['type'] == 'R': + header += "%ADD{apid}{type},{width}X{height}*%\n".format( + apid=str(apid), + type='R', + width=(factor * obj.tools[apid]['width']), + height=(factor * obj.tools[apid]['height']) + ) + elif obj.tools[apid]['type'] == 'O': + header += "%ADD{apid}{type},{width}X{height}*%\n".format( + apid=str(apid), + type='O', + width=(factor * obj.tools[apid]['width']), + height=(factor * obj.tools[apid]['height']) + ) + + header += '\n' + + # obsolete units but some software may need it + if gunits == 'IN': + header += 'G70*\n' + else: + header += 'G71*\n' + + # Absolute Mode + header += 'G90*\n' + + header += 'G01*\n' + # positive polarity + header += '%LPD*%\n' + + footer = 'M02*\n' + + gerber_code = obj.export_gerber(gwhole, gfract, g_zeros=gzeros, factor=factor) + + exported_gerber = header + exported_gerber += gerber_code + exported_gerber += footer + + if local_use is None: + try: + with open(filename, 'w') as fp: + fp.write(exported_gerber) + except PermissionError: + self.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return 'fail' + + if self.options["global_open_style"] is False: + self.app.file_opened.emit("Gerber", filename) + self.app.file_saved.emit("Gerber", filename) + self.inform.emit('[success] %s: %s' % (_("Gerber file exported to"), filename)) + else: + return exported_gerber + except Exception as e: + self.log.error("App.export_gerber.make_gerber() --> %s" % str(e)) + return 'fail' + + if use_thread is True: + with self.app.proc_container.new(_("Exporting ...")): + + def job_thread_grb(app_obj): + ret = make_gerber() + if ret == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) + return 'fail' + + self.worker_task.emit({'fcn': job_thread_grb, 'params': [self]}) + else: + gret = make_gerber() + if gret == 'fail': + self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) + return 'fail' + if local_use is not None: + return gret + + def export_dxf(self, obj_name, filename, local_use=None, use_thread=True): + """ + Exports a Geometry Object to an DXF file. + + :param obj_name: the name of the FlatCAM object to be saved as DXF + :param filename: Path to the DXF file to save to. + :param local_use: if the Gerber code is to be saved to a file (None) or used within FlatCAM. + When not None, the value will be the actual Geometry object for which to create + the Geometry/DXF code + :param use_thread: if to be run in a separate thread + :return: + """ + if filename is None: + filename = self.app.options["global_last_save_folder"] if \ + self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] + + self.log.debug("export_dxf()") + + if local_use is None: + try: + obj = self.app.collection.get_by_name(str(obj_name)) + except Exception: + return 'fail' + else: + obj = local_use + + def make_dxf(): + try: + dxf_code = obj.export_dxf() + if local_use is None: + try: + dxf_code.saveas(filename) + except PermissionError: + self.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return 'fail' + + if self.options["global_open_style"] is False: + self.app.file_opened.emit("DXF", filename) + self.app.file_saved.emit("DXF", filename) + self.inform.emit('[success] %s: %s' % (_("DXF file exported to"), filename)) + else: + return dxf_code + except Exception as e: + self.log.error("App.export_dxf.make_dxf() --> %s" % str(e)) + return 'fail' + + if use_thread is True: + + with self.app.proc_container.new(_("Exporting ...")): + + def job_thread_exc(app_obj): + ret_dxf_val = make_dxf() + if ret_dxf_val == 'fail': + app_obj.inform.emit('[WARNING_NOTCL] %s' % _('Could not export.')) + return + + self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]}) + else: + ret = make_dxf() + if ret == 'fail': + self.inform.emit('[WARNING_NOTCL] %s' % _('Could not export.')) + return + if local_use is not None: + return ret + + def import_svg(self, filename, geo_type='geometry', outname=None, plot=True): + """ + Adds a new Geometry Object to the projects and populates + it with shapes extracted from the SVG file. + + :param plot: If True then the resulting object will be plotted on canvas + :param filename: Path to the SVG file. + :param geo_type: Type of FlatCAM object that will be created from SVG + :param outname: The name given to the resulting FlatCAM object + :return: + """ + self.log.debug("App.import_svg()") + if not os.path.exists(filename): + self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) + return + + obj_type = "" + if geo_type is None or geo_type == "geometry": + obj_type = "geometry" + elif geo_type == "gerber": + obj_type = "gerber" + else: + self.inform.emit('[ERROR_NOTCL] %s' % + _("Not supported type is picked as parameter. Only Geometry and Gerber are supported")) + return + + units = self.app_units.upper() + + def obj_init(geo_obj, app_obj): + res = geo_obj.import_svg(filename, obj_type, units=units) + if res == 'fail': + return 'fail' + + geo_obj.multigeo = True + + with open(filename) as f: + file_content = f.read() + geo_obj.source_file = file_content + + # appGUI feedback + app_obj.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + with self.app.proc_container.new('%s ...' % _("Importing")): + + # Object name + name = outname or filename.split('/')[-1].split('\\')[-1] + + ret = self.app.app_obj.new_object(obj_type, name, obj_init, autoselected=False, plot=plot) + + if ret == 'fail': + self.inform.emit('[ERROR_NOTCL]%s' % _('Import failed.')) + return 'fail' + + # Register recent file + self.app.file_opened.emit("svg", filename) + + def import_dxf(self, filename, geo_type='geometry', outname=None, plot=True): + """ + Adds a new Geometry Object to the projects and populates + it with shapes extracted from the DXF file. + + :param filename: Path to the DXF file. + :param geo_type: Type of FlatCAM object that will be created from DXF + :param outname: Name for the imported Geometry + :param plot: If True then the resulting object will be plotted on canvas + :return: + """ + self.log.debug(" ********* Importing DXF as: %s ********* " % geo_type.capitalize()) + if not os.path.exists(filename): + self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) + return + + obj_type = "" + if geo_type is None or geo_type == "geometry": + obj_type = "geometry" + elif geo_type == "gerber": + obj_type = geo_type + else: + self.inform.emit('[ERROR_NOTCL] %s' % + _("Not supported type is picked as parameter. Only Geometry and Gerber are supported")) + return + + units = self.app_units.upper() + + def obj_init(geo_obj, app_obj): + if obj_type == "geometry": + geo_obj.import_dxf_as_geo(filename, units=units) + elif obj_type == "gerber": + geo_obj.import_dxf_as_gerber(filename, units=units) + else: + return "fail" + + with open(filename) as f: + file_content = f.read() + geo_obj.source_file = file_content + + # appGUI feedback + app_obj.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + with self.app.proc_container.new('%s ...' % _("Importing")): + + # Object name + name = outname or filename.split('/')[-1].split('\\')[-1] + + ret = self.app.app_obj.new_object(obj_type, name, obj_init, autoselected=False, plot=plot) + + if ret == 'fail': + self.inform.emit('[ERROR_NOTCL]%s' % _('Import failed.')) + return 'fail' + + # Register recent file + self.app.file_opened.emit("dxf", filename) + + def import_pdf(self, filename): + self.app.pdf_tool.periodic_check(1000) + self.worker_task.emit({'fcn': self.app.pdf_tool.open_pdf, 'params': [filename]}) + + def open_gerber(self, filename, outname=None, plot=True, from_tcl=False): + """ + Opens a Gerber file, parses it and creates a new object for + it in the program. Thread-safe. + + :param outname: Name of the resulting object. None causes the + name to be that of the file. Str. + :param filename: Gerber file filename + :type filename: str + :param plot: boolean, to plot or not the resulting object + :param from_tcl: True if run from Tcl Shell + :return: None + """ + + # How the object should be initialized + def obj_init(gerber_obj, app_obj): + + assert isinstance(gerber_obj, GerberObject), \ + "Expected to initialize a GerberObject but got %s" % type(gerber_obj) + + # Opening the file happens here + try: + parse_ret_val = gerber_obj.parse_file(filename) + except IOError: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename)) + return "fail" + except ParseError as parse_err: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(parse_err))) + app_obj.log.error(str(parse_err)) + return "fail" + except Exception as e: + app_obj.log.error("App.open_gerber() --> %s" % str(e)) + msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") + msg += traceback.format_exc() + app_obj.inform.emit(msg) + return "fail" + + if gerber_obj.is_empty(): + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Object is not Gerber file or empty. Aborting object creation.")) + return "fail" + + if parse_ret_val: + return parse_ret_val + + self.log.debug("open_gerber()") + if not os.path.exists(filename): + self.inform.emit('[ERROR_NOTCL] %s. %s' % (filename, _("File no longer available."))) + return + + with self.app.proc_container.new('%s...' % _("Opening")): + # Object name + name = outname or filename.split('/')[-1].split('\\')[-1] + + # # ## Object creation # ## + ret_val = self.app.app_obj.new_object("gerber", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + if from_tcl: + filename = self.options['global_tcl_path'] + '/' + name + ret_val = self.app.app_obj.new_object("gerber", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + self.inform.emit('[ERROR_NOTCL]%s' % _('Open Gerber failed. Probable not a Gerber file.')) + return 'fail' + + # Register recent file + self.app.file_opened.emit("gerber", filename) + + # appGUI feedback + self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + def open_excellon(self, filename, outname=None, plot=True, from_tcl=False): + """ + Opens an Excellon file, parses it and creates a new object for + it in the program. Thread-safe. + + :param outname: Name of the resulting object. None causes the name to be that of the file. + :param filename: Excellon file filename + :type filename: str + :param plot: boolean, to plot or not the resulting object + :param from_tcl: True if run from Tcl Shell + :return: None + """ + + self.log.debug("open_excellon()") + + if not os.path.exists(filename): + self.inform.emit('[ERROR_NOTCL] %s. %s' % (filename, _("File no longer available."))) + return + + # How the object should be initialized + def obj_init(excellon_obj, app_obj): + # populate excellon_obj.tools dict + try: + ret = excellon_obj.parse_file(filename=filename) + if ret == "fail": + app_obj.log.debug("Excellon parsing failed.") + self.inform.emit('[ERROR_NOTCL] %s' % _("This is not Excellon file.")) + return "fail" + except IOError: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot open file"), filename)) + app_obj.log.debug("Could not open Excellon object.") + return "fail" + except Exception: + msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n") + msg += traceback.format_exc() + app_obj.inform.emit(msg) + return "fail" + + # populate excellon_obj.solid_geometry list + ret = excellon_obj.create_geometry() + if ret == 'fail': + app_obj.log.debug("Could not create geometry for Excellon object.") + return "fail" + + for tool in excellon_obj.tools: + if excellon_obj.tools[tool]['solid_geometry']: + return + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("No geometry found in file"), filename)) + return "fail" + + with self.app.proc_container.new('%s...' % _("Opening")): + # Object name + name = outname or filename.split('/')[-1].split('\\')[-1] + ret_val = self.app.app_obj.new_object("excellon", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + if from_tcl: + filename = self.options['global_tcl_path'] + '/' + name + ret_val = self.app.app_obj.new_object("excellon", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + self.inform.emit('[ERROR_NOTCL] %s' % + _('Open Excellon file failed. Probable not an Excellon file.')) + return + + # Register recent file + self.app.file_opened.emit("excellon", filename) + + # appGUI feedback + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + def open_gcode(self, filename, outname=None, force_parsing=None, plot=True, from_tcl=False): + """ + Opens a G-gcode file, parses it and creates a new object for + it in the program. Thread-safe. + + :param filename: G-code file filename + :param outname: Name of the resulting object. None causes the name to be that of the file. + :param force_parsing: + :param plot: If True plot the object on canvas + :param from_tcl: True if run from Tcl Shell + :return: None + """ + self.log.debug("open_gcode()") + + if not os.path.exists(filename): + self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) + return + + # How the object should be initialized + def obj_init(job_obj, app_obj_: "appMain.App"): + """ + :param job_obj: the resulting object + :type app_obj_: App + """ + + app_obj_.inform.emit('%s...' % _("Reading GCode file")) # noqa + try: + f = open(filename) + gcode = f.read() + f.close() + except IOError: + app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open"), filename)) # noqa + return "fail" + + # try to find from what kind of object this GCode was created + gcode_origin = 'Geometry' + match = re.search(r'^.*Type:\s*.*(\bGeometry\b|\bExcellon\b)', gcode, re.MULTILINE) + if match: + gcode_origin = match.group(1) + job_obj.obj_options['type'] = gcode_origin + # add at least one default tool + if 'excellon' in gcode_origin.lower(): + job_obj.tools = {1: {'data': {'tools_drill_ppname_e': 'default'}}} + if 'geometry' in gcode_origin.lower(): + job_obj.tools = {1: {'data': {'tools_mill_ppname_g': 'default'}}} + + # try to find from what kind of object this GCode was created + match = re.search(r'^.*Preprocessor:\s*.*\bGeometry\b|\bExcellon\b:\s(\b.*\b)', gcode, re.MULTILINE) + detected_preprocessor = 'default' + if match: + detected_preprocessor = match.group(1) + # determine if there is any tool data + match = re.findall(r'^.*Tool:\s*(\d*)\s*->\s*Dia:\s*(\d*\.?\d*)', gcode, re.MULTILINE) + if match: + job_obj.tools = {} + for m in match: + if 'excellon' in gcode_origin.lower(): + job_obj.tools[int(m[0])] = { + 'tooldia': float(m[1]), + 'nr_drills': 0, + 'nr_slots': 0, + 'offset_z': 0, + 'data': {'tools_drill_ppname_e': detected_preprocessor} + } + # if 'geometry' in gcode_origin.lower(): + # job_obj.tools[int(m[0])] = { + # 'tooldia': float(m[1]), + # 'data': { + # 'tools_mill_ppname_g': detected_preprocessor, + # 'tools_mill_offset_value': 0.0, + # 'tools_mill_job_type': _('Roughing'), + # 'tools_mill_tool_shape': "C1" + # + # } + # } + job_obj.used_tools = list(job_obj.tools.keys()) + # determine if there is any Cut Z data + match = re.findall(r'^.*Tool:\s*(\d*)\s*->\s*Z_Cut:\s*([\-|+]?\d*\.?\d*)', gcode, re.MULTILINE) + if match: + for m in match: + if 'excellon' in gcode_origin.lower(): + if int(m[0]) in job_obj.tools: + job_obj.tools[int(m[0])]['offset_z'] = 0.0 + job_obj.tools[int(m[0])]['data']['tools_drill_cutz'] = float(m[1]) + # if 'geometry' in gcode_origin.lower(): + # if int(m[0]) in job_obj.tools: + # job_obj.tools[int(m[0])]['data']['tools_mill_cutz'] = float(m[1]) + + job_obj.gcode = gcode + + gcode_ret = job_obj.gcode_parse(force_parsing=force_parsing) + if gcode_ret == "fail": + self.inform.emit('[ERROR_NOTCL] %s' % _("This is not GCODE")) + return "fail" + + for k in job_obj.tools: + job_obj.tools[k]['gcode'] = gcode + job_obj.tools[k]['gcode_parsed'] = [] + + for k in job_obj.tools: + print(k, job_obj.tools[k]) + job_obj.create_geometry() + + with self.app.proc_container.new('%s...' % _("Opening")): + + # Object name + name = outname or filename.split('/')[-1].split('\\')[-1] + + # New object creation and file processing + ret_val = self.app.app_obj.new_object("cncjob", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + if from_tcl: + filename = self.options['global_tcl_path'] + '/' + name + ret_val = self.app.app_obj.new_object("cncjob", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + self.inform.emit('[ERROR_NOTCL] %s' % + _("Failed to create CNCJob Object. Probable not a GCode file. " + "Try to load it from File menu.\n " + "Attempting to create a FlatCAM CNCJob Object from " + "G-Code file failed during processing")) + return "fail" + + # Register recent file + self.app.file_opened.emit("cncjob", filename) + + # appGUI feedback + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + def open_hpgl2(self, filename, outname=None): + """ + Opens a HPGL2 file, parses it and creates a new object for + it in the program. Thread-safe. + + :param outname: Name of the resulting object. None causes the name to be that of the file. + :param filename: HPGL2 file filename + :return: None + """ + filename = filename + + # How the object should be initialized + def obj_init(geo_obj, app_obj): + + assert isinstance(geo_obj, GeometryObject), \ + "Expected to initialize a GeometryObject but got %s" % type(geo_obj) + + # Opening the file happens here + obj = HPGL2(self.app) + try: + HPGL2.parse_file(obj, filename) + except IOError: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename)) + return "fail" + except ParseError as parse_err: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(parse_err))) + app_obj.log.error(str(parse_err)) + return "fail" + except Exception as e: + app_obj.log.error("App.open_hpgl2() --> %s" % str(e)) + msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") + msg += traceback.format_exc() + app_obj.inform.emit(msg) + return "fail" + + geo_obj.multigeo = True + geo_obj.solid_geometry = deepcopy(obj.solid_geometry) + geo_obj.tools = deepcopy(obj.tools) + geo_obj.source_file = deepcopy(obj.source_file) + + del obj + + if not geo_obj.solid_geometry: + app_obj.inform.emit('[ERROR_NOTCL] %s' % + _("Object is not HPGL2 file or empty. Aborting object creation.")) + return "fail" + + self.log.debug("open_hpgl2()") + + with self.app.proc_container.new('%s...' % _("Opening")): + # Object name + name = outname or filename.split('/')[-1].split('\\')[-1] + + # # ## Object creation # ## + ret = self.app.app_obj.new_object("geometry", name, obj_init, autoselected=False) + if ret == 'fail': + self.inform.emit('[ERROR_NOTCL]%s' % _('Failed. Probable not a HPGL2 file.')) + return 'fail' + + # Register recent file + self.app.file_opened.emit("geometry", filename) + + # appGUI feedback + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + def open_script(self, filename, outname=None, silent=False): + """ + Opens a Script file, parses it and creates a new object for + it in the program. Thread-safe. + + :param outname: Name of the resulting object. None causes the name to be that of the file. + :param filename: Script file filename + :param silent: If True there will be no messages printed to StatusBar + :return: None + """ + + def obj_init(script_obj, app_obj): + + assert isinstance(script_obj, ScriptObject), \ + "Expected to initialize a ScriptObject but got %s" % type(script_obj) + + if silent is False: + app_obj.inform.emit('[success] %s' % _("TCL script file opened in Code Editor.")) + + try: + script_obj.parse_file(filename) + except IOError: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename)) + return "fail" + except ParseError as parse_err: + app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(parse_err))) + app_obj.log.error(str(parse_err)) + return "fail" + except Exception as e: + app_obj.log.error("App.open_script() -> %s" % str(e)) + msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") + msg += traceback.format_exc() + app_obj.inform.emit(msg) + return "fail" + + self.log.debug("open_script()") + if not os.path.exists(filename): + self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) + return + + with self.app.proc_container.new('%s...' % _("Opening")): + + # Object name + script_name = outname or filename.split('/')[-1].split('\\')[-1] + + # Object creation + ret_val = self.app.app_obj.new_object("script", script_name, obj_init, autoselected=False, plot=False) + if ret_val == 'fail': + filename = self.options['global_tcl_path'] + '/' + script_name + ret_val = self.app.app_obj.new_object("script", script_name, obj_init, autoselected=False, plot=False) + if ret_val == 'fail': + self.inform.emit('[ERROR_NOTCL]%s' % _('Failed to open TCL Script.')) + return 'fail' + + # Register recent file + self.app.file_opened.emit("script", filename) + + # appGUI feedback + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) + + def open_config_file(self, filename, run_from_arg=None): + """ + Loads a config file from the specified file. + + :param filename: Name of the file from which to load. + :param run_from_arg: if True the FlatConfig file will be open as an command line argument + :return: None + """ + self.log.debug("Opening config file: " + filename) + + if run_from_arg: + self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" + "Canvas initialization finished in"), + '%.2f' % self.app.used_time, + _("Opening FlatCAM Config file.")), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + # # add the tab if it was closed + # self.ui.plot_tab_area.addTab(self.ui.text_editor_tab, _("Code Editor")) + # # first clear previous text in text editor (if any) + # self.ui.text_editor_tab.code_editor.clear() + # + # # Switch plot_area to CNCJob tab + # self.ui.plot_tab_area.setCurrentWidget(self.ui.text_editor_tab) + + # close the Code editor if already open + if self.app.toggle_codeeditor: + self.app.on_toggle_code_editor() + + self.app.on_toggle_code_editor() + + try: + if filename: + f = QtCore.QFile(filename) + if f.open(QtCore.QIODevice.OpenModeFlag.ReadOnly): + stream = QtCore.QTextStream(f) + code_edited = stream.readAll() + self.app.text_editor_tab.load_text(code_edited, clear_text=True, move_to_start=True) + f.close() + except IOError: + self.log.error("Failed to open config file: %s" % filename) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed."), filename)) + return + + def open_project(self, filename, run_from_arg=False, plot=True, cli=False, from_tcl=False): + """ + Loads a project from the specified file. + + 1) Loads and parses file + 2) Registers the file as recently opened. + 3) Calls on_file_new_project() + 4) Updates options + 5) Calls app_obj.new_object() with the object's from_dict() as init method. + 6) Calls plot_all() if plot=True + + :param filename: Name of the file from which to load. + :param run_from_arg: True if run for arguments + :param plot: If True plot all objects in the project + :param cli: Run from command line + :param from_tcl: True if run from Tcl Sehll + :return: None + """ + + project_filename = filename + + self.log.debug("Opening project: " + project_filename) + if not os.path.exists(project_filename): + self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) + return + + # block autosaving while a project is loaded + self.app.block_autosave = True + + # for some reason, setting ui_title does not work when this method is called from Tcl Shell + # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled) + if cli is None: + self.app.ui.set_ui_title(name=_("Loading Project ... Please Wait ...")) + + if run_from_arg: + self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" + "Canvas initialization finished in"), + '%.2f' % self.app.used_time, + _("Opening FlatCAM Project file.")), + alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, + color=QtGui.QColor("lightgray")) + + def parse_worker(prj_filename): + with self.app.proc_container.new('%s' % _("Parsing...")): + # Open and parse an uncompressed Project file + try: + f = open(prj_filename, 'r') + except IOError: + if from_tcl: + name = prj_filename.split('/')[-1].split('\\')[-1] + prj_filename = os.path.join(self.options['global_tcl_path'], name) + try: + f = open(prj_filename, 'r') + except IOError: + self.log.error("Failed to open project file: %s" % prj_filename) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), prj_filename)) + return + else: + self.log.error("Failed to open project file: %s" % prj_filename) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), prj_filename)) + return + + try: + d = json.load(f, object_hook=dict2obj) + except Exception as e: + self.log.debug( + "Failed to parse project file, trying to see if it loads as an LZMA archive: %s because %s" % + (prj_filename, str(e))) + f.close() + + # Open and parse a compressed Project file + try: + with lzma.open(prj_filename) as f: + file_content = f.read().decode('utf-8') + d = json.loads(file_content, object_hook=dict2obj) + except Exception as e: + self.log.error("Failed to open project file: %s with error: %s" % (prj_filename, str(e))) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), prj_filename)) + return + + # Check for older projects + found_older_project = False + for obj in d['objs']: + if 'cnc_tools' in obj or 'exc_cnc_tools' in obj or 'apertures' in obj: + self.app.log.error( + 'AppIO.open_project() --> %s %s. %s' % + ("Failed to open the CNCJob file:", str(obj['options']['name']), + "Maybe it is an old project.")) + found_older_project = True + + if found_older_project: + if not run_from_arg or not cli or from_tcl is False: + msgbox = FCMessageBox(parent=self.app.ui) + title = _("Legacy Project") + txt = _("The project was made with an older app version.\n" + "It may not load correctly.\n\n" + "Do you want to continue?") + msgbox.setWindowTitle(title) # taskbar still shows it + msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) + msgbox.setText('%s' % title) + msgbox.setInformativeText(txt) + msgbox.setIcon(QtWidgets.QMessageBox.Icon.Question) + + bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) + bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) + + msgbox.setDefaultButton(bt_ok) + msgbox.exec() + response = msgbox.clickedButton() + + if response == bt_cancel: + return + else: + self.app.log.error("Legacy Project. Loading not supported.") + return + + self.app.restore_project.emit(d, prj_filename, run_from_arg, from_tcl, cli, plot) + + self.app.worker_task.emit({'fcn': parse_worker, 'params': [project_filename]}) + + def restore_project_handler(self, proj_dict, filename, run_from_arg, from_tcl, cli, plot): + # Clear the current project + # # NOT THREAD SAFE # ## + if run_from_arg is True: + pass + elif cli is True: + self.app.delete_selection_shape() + else: + self.on_file_new_project() + + if not run_from_arg or not cli or from_tcl is False: + msgbox = FCMessageBox(parent=self.app.ui) + title = _("Import Settings") + txt = _("Do you want to import the loaded project settings?") + msgbox.setWindowTitle(title) # taskbar still shows it + msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) + msgbox.setText('%s' % title) + msgbox.setInformativeText(txt) + msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/import.png')) + + bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.ButtonRole.YesRole) + bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.ButtonRole.NoRole) + # bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) + + msgbox.setDefaultButton(bt_yes) + msgbox.exec() + response = msgbox.clickedButton() + + if response == bt_yes: + # self.app.defaults.update(self.app.options) + # self.app.preferencesUiManager.save_defaults() + # Project options + self.app.options.update(proj_dict['options']) + if response == bt_no: + pass + else: + # Load by default new options when not using GUI + # Project options + self.app.options.update(proj_dict['options']) + + self.app.project_filename = filename + + # for some reason, setting ui_title does not work when this method is called from Tcl Shell + # it's because the TclCommand is run in another thread (it inherits TclCommandSignaled) + if cli is None: + self.app.set_screen_units(self.app.options["units"]) + + self.app.restore_project_objects_sig.emit(proj_dict, filename, cli, plot) + + def restore_project_objects(self, proj_dict, filename, cli, plot): + + def worker_task(): + with self.app.proc_container.new('%s' % _("Loading...")): + # Re create objects + self.log.debug(" **************** Started PROEJCT loading... **************** ") + for obj in proj_dict['objs']: + try: + msg = "Recreating from opened project an %s object: %s" % \ + (obj['kind'].capitalize(), obj['obj_options']['name']) + except KeyError: + # allowance for older projects + msg = "Recreating from opened project an %s object: %s" % \ + (obj['kind'].capitalize(), obj['options']['name']) + self.app.log.debug(msg) + + def obj_init(new_obj, app_inst): + try: + new_obj.from_dict(obj) + except Exception as erro: + app_inst.log.error('AppIO.open_project() --> ' + str(erro)) + return 'fail' + + # make the 'obj_options' dict a LoudDict + try: + new_obj_options = LoudDict() + new_obj_options.update(new_obj.obj_options) + new_obj.obj_options = new_obj_options + except AttributeError: + new_obj_options = LoudDict() + new_obj_options.update(new_obj.options) + new_obj.obj_options = new_obj_options + except Exception as erro: + app_inst.log.error('AppIO.open_project() make a LoudDict--> ' + str(erro)) + return 'fail' + + # ############################################################################################# + # for older projects loading try to convert the 'apertures' or 'cnc_tools' or 'exc_cnc_tools' + # attributes, if found, to 'tools' + # ############################################################################################# + # for older loaded projects + if 'apertures' in obj: + new_obj.tools = obj['apertures'] + if 'cnc_tools' in obj and obj['cnc_tools']: + new_obj.tools = obj['cnc_tools'] + # new_obj.used_tools = [int(k) for k in new_obj.tools.keys()] + # first_key = list(obj['cnc_tools'].keys())[0] + # used_preprocessor = obj['cnc_tools'][first_key]['data']['ppname_g'] + # new_obj.gc_start = new_obj.doformat(self.app.preprocessors[used_preprocessor].start_code) + if 'exc_cnc_tools' in obj and obj['exc_cnc_tools']: + new_obj.tools = obj['exc_cnc_tools'] + # add the used_tools (all of them will be used) + new_obj.used_tools = [float(k) for k in new_obj.tools.keys()] + # add a missing key, 'tooldia' used for plotting CNCJob objects + for td in new_obj.tools: + new_obj.tools[td]['tooldia'] = float(td) + # ############################################################################################# + # ############################################################################################# + + # try to make the keys in the tools dictionary to be integers + # JSON serialization makes them strings + # not all FlatCAM objects have the 'tools' dictionary attribute + try: + new_obj.tools = { + int(tool): tool_dict for tool, tool_dict in list(new_obj.tools.items()) + } + except ValueError: + # for older loaded projects + new_obj.tools = { + float(tool): tool_dict for tool, tool_dict in list(new_obj.tools.items()) + } + except Exception as erro: + app_inst.log.error('AppIO.open_project() keys to int--> ' + str(erro)) + return 'fail' + + # ############################################################################################# + # for older loaded projects + # ony older CNCJob objects hold those + if 'cnc_tools' in obj: + new_obj.obj_options['type'] = 'Geometry' + if 'exc_cnc_tools' in obj: + new_obj.obj_options['type'] = 'Excellon' + # ############################################################################################# + + if new_obj.kind == 'cncjob': + # some attributes are serialized so we need t otake this into consideration in + # CNCJob.set_ui() + new_obj.is_loaded_from_project = True + + # for some reason, setting ui_title does not work when this method is called from Tcl Shell + # it's because the TclCommand is run in another thread (it inherits TclCommandSignaled) + try: + if cli is None: + self.app.ui.set_ui_title(name="{} {}: {}".format( + _("Loading Project ... restoring"), obj['kind'].upper(), obj['obj_options']['name'])) + + ret = self.app.app_obj.new_object(obj['kind'], obj['obj_options']['name'], obj_init, plot=plot) + except KeyError: + # allowance for older projects + if cli is None: + self.app.ui.set_ui_title(name="{} {}: {}".format( + _("Loading Project ... restoring"), obj['kind'].upper(), obj['options']['name'])) + try: + ret = self.app.app_obj.new_object(obj['kind'], obj['options']['name'], obj_init, plot=plot) + except Exception: + continue + if ret == 'fail': + continue + + self.inform.emit('[success] %s: %s' % (_("Project loaded from"), filename)) + + self.app.should_we_save = False + self.app.file_opened.emit("project", filename) + + # restore autosaving after a project was loaded + self.app.block_autosave = False + + # for some reason, setting ui_title does not work when this method is called from Tcl Shell + # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled) + if cli is None: + self.app.ui.set_ui_title(name=self.app.project_filename) + + self.log.debug(" **************** Finished PROJECT loading... **************** ") + + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def save_project(self, filename, quit_action=False, silent=False, from_tcl=False): + """ + Saves the current project to the specified file. + + :param filename: Name of the file in which to save. + :type filename: str + :param quit_action: if the project saving will be followed by an app quit; boolean + :param silent: if True will not display status messages + :param from_tcl True is run from Tcl Shell + :return: None + """ + self.log.debug("save_project() -> Saving Project") + self.app.save_in_progress = True + + if from_tcl: + self.log.debug("AppIO.save_project() -> Project saved from TCL command.") + + with self.app.proc_container.new(_("Saving Project ...")): + # Capture the latest changes + # Current object + try: + current_object = self.app.collection.get_active() + if current_object: + current_object.read_form() + except Exception as e: + self.log.error("save_project() --> There was no active object. Skipping read_form. %s" % str(e)) + + app_options = {k: v for k, v in self.app.options.items()} + d = { + "objs": [obj.to_dict() for obj in self.app.collection.get_list()], + "options": app_options, + "version": self.app.version + } + + if self.options["global_save_compressed"] is True: + try: + project_as_json = json.dumps(d, default=to_dict, indent=2, sort_keys=True).encode('utf-8') + except Exception as e: + self.log.error( + "Failed to serialize file before compression: %s because: %s" % (str(filename), str(e))) + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return + + try: + # with lzma.open(filename, "w", preset=int(self.options['global_compression_level'])) as f: + # # # Write + # f.write(project_as_json) + + compressor_obj = lzma.LZMACompressor(preset=int(self.options['global_compression_level'])) + out1 = compressor_obj.compress(project_as_json) + out2 = compressor_obj.flush() + project_zipped = b"".join([out1, out2]) + except Exception as errrr: + self.log.error("Failed to save compressed file: %s because: %s" % (str(filename), str(errrr))) + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return + + if project_zipped != b'': + with open(filename, "wb") as f_to_write: + f_to_write.write(project_zipped) + + self.inform.emit('[success] %s: %s' % (_("Project saved to"), str(filename))) + else: + self.log.error("Failed to save file: %s. Empty binary file.", str(filename)) + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return + else: + # Open file + try: + f = open(filename, 'w') + except IOError: + self.log.error("Failed to open file for saving: %s", str(filename)) + self.inform.emit('[ERROR_NOTCL] %s' % _("The object is used by another application.")) + return + + # Write + try: + json.dump(d, f, default=to_dict, indent=2, sort_keys=True) + except Exception as e: + self.log.error( + "Failed to serialize file: %s because: %s" % (str(filename), str(e))) + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return + f.close() + + # verification of the saved project + # Open and parse + try: + saved_f = open(filename, 'r') + except IOError: + if silent is False: + self.inform.emit('[ERROR_NOTCL] %s: %s %s' % + (_("Failed to verify project file"), str(filename), _("Retry to save it."))) + return + + try: + saved_d = json.load(saved_f, object_hook=dict2obj) + if not saved_d: + self.inform.emit('[ERROR_NOTCL] %s: %s %s' % + (_("Failed to parse saved project file"), + str(filename), + _("Retry to save it."))) # noqa + f.close() + return + except Exception: + if silent is False: + self.inform.emit('[ERROR_NOTCL] %s: %s %s' % + (_("Failed to parse saved project file"), + str(filename), + _("Retry to save it."))) # noqa + f.close() + return + + saved_f.close() + + if silent is False: + if 'version' in saved_d: + self.inform.emit('[success] %s: %s' % (_("Project saved to"), str(filename))) + else: + self.inform.emit('[ERROR_NOTCL] %s: %s %s' % + (_("Failed to parse saved project file"), + str(filename), + _("Retry to save it."))) # noqa + + tb_settings = QSettings("Open Source", "FlatCAM") + lock_state = self.app.ui.lock_action.isChecked() + tb_settings.setValue('toolbar_lock', lock_state) + + # This will write the setting to the platform specific storage. + del tb_settings + + # if quit: + # t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename)) + # t.start() + self.app.start_delayed_quit(delay=500, filename=filename, should_quit=quit_action) + + def save_source_file(self, obj_name, filename): + """ + Exports a FlatCAM Object to an Gerber/Excellon file. + + :param obj_name: the name of the FlatCAM object for which to save it's embedded source file + :param filename: Path to the Gerber file to save to. + :return: + """ + + if filename is None: + filename = self.app.options["global_last_save_folder"] if \ + self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] + + self.log.debug("save_source_file()") + + obj = self.app.collection.get_by_name(obj_name) + + file_string = StringIO(obj.source_file) + time_string = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + + if file_string.getvalue() == '': + msg = _("Save cancelled because source file is empty. Try to export the file.") + self.inform.emit('[ERROR_NOTCL] %s' % msg) # noqa + return 'fail' + + try: + with open(filename, 'w') as file: + file.writelines('G04*\n') + file.writelines('G04 %s (RE)GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' % + (obj.kind.upper(), str(self.app.version), str(self.app.version_date))) + file.writelines('G04 Filename: %s*\n' % str(obj_name)) + file.writelines('G04 Created on : %s*\n' % time_string) + + for line in file_string: + file.writelines(line) + except PermissionError: + self.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) # noqa + return 'fail' + + def on_file_savedefaults(self): + """ + Callback for menu item File->Save Defaults. Saves application default options + ``self.options`` to current_defaults.FlatConfig. + + :return: None + """ + self.app.defaults.update(self.app.options) + self.app.preferencesUiManager.save_defaults() diff --git a/appHandlers/__init__.py b/appHandlers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/appMain.py b/appMain.py index f4aecb28..f76cc75d 100644 --- a/appMain.py +++ b/appMain.py @@ -15,24 +15,15 @@ import getopt import random import simplejson as json import shutil -import lzma from datetime import datetime -import ctypes import traceback from shapely.geometry import Point, MultiPolygon, MultiLineString from shapely.ops import unary_union from io import StringIO -from reportlab.graphics import renderPDF -from reportlab.pdfgen import canvas -from reportlab.lib.units import inch, mm -from reportlab.lib.pagesizes import landscape, portrait -from svglib.svglib import svg2rlg - import gc -from xml.dom.minidom import parseString as parse_xml_string from multiprocessing.connection import Listener, Client from multiprocessing import Pool @@ -51,18 +42,16 @@ import qdarktheme.themes.light.stylesheet as qlightsheet # Various from appGUI.themes import dark_style_sheet, light_style_sheet -from appCommon.Common import LoudDict from appCommon.Common import color_variant from appCommon.Common import ExclusionAreas from appCommon.Common import AppLogging from appCommon.RegisterFileKeywords import RegisterFK, Extensions, KeyWords +from appHandlers.AppIO import AppIO + from Bookmark import BookmarkManager from appDatabase import ToolsDB2 -from vispy.gloo.util import _screenshot -from vispy.io import write_png - # App defaults (preferences) from defaults import AppDefaults from defaults import AppOptions @@ -77,7 +66,7 @@ from appObjects.AppObject import AppObject # App Parsing files from appParsers.ParseExcellon import Excellon from appParsers.ParseGerber import Gerber -from camlib import to_dict, dict2obj, ET, ParseError, Geometry, CNCjob +from camlib import to_dict, Geometry, CNCjob # App appGUI from appGUI.PlotCanvas import * @@ -96,7 +85,6 @@ from appEditors.AppExcEditor import AppExcEditor from appEditors.AppGerberEditor import AppGerberEditor from appEditors.AppTextEditor import AppTextEditor from appEditors.appGCodeEditor import AppGCodeEditor -from appParsers.ParseHPGL2 import HPGL2 # App Workers from appProcess import * @@ -112,9 +100,6 @@ import builtins import darkdetect -if sys.platform == 'win32': - import winreg - fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -443,7 +428,7 @@ class App(QtCore.QObject): self.new_launch = ArgsThread() self.new_launch.open_signal[list].connect(self.on_startup_args) self.new_launch.moveToThread(self.listen_th) - self.new_launch.start.emit() + self.new_launch.start.emit() # noqa # ############################################################################################################ # ########################################## OS-specific ##################################################### @@ -842,8 +827,13 @@ class App(QtCore.QObject): # ########################################################################################################### # ##################################### UPDATE PREFERENCES GUI FORMS ######################################## # ########################################################################################################### - self.preferencesUiManager = PreferencesUIManager(data_path=self.data_path, ui=self.ui, inform=self.inform, - options=self.options, defaults=self.defaults) + self.preferencesUiManager = PreferencesUIManager( + data_path=self.data_path, + ui=self.ui, + inform=self.inform, + options=self.options, + defaults=self.defaults + ) self.preferencesUiManager.defaults_write_form() @@ -1107,7 +1097,7 @@ class App(QtCore.QObject): # ###################################### INSTANTIATE CLASSES THAT HOLD THE MENU HANDLERS #################### # ########################################################################################################### # ########################################################################################################### - self.f_handlers = MenuFileHandlers(app=self) + self.f_handlers = AppIO(app=self) # this is calculated in the class above (somehow?) self.options["root_folder_path"] = self.app_home @@ -1167,8 +1157,8 @@ class App(QtCore.QObject): # signals for displaying messages in the Tcl Shell are now connected in the ToolShell class # loading an project - self.restore_project.connect(self.f_handlers.restore_project_handler) - self.restore_project_objects_sig.connect(self.f_handlers.restore_project_objects) + self.restore_project.connect(self.f_handlers.restore_project_handler) # noqa + self.restore_project_objects_sig.connect(self.f_handlers.restore_project_objects) # noqa # signal to be called when the app is quiting self.app_quit.connect(self.quit_application, type=Qt.ConnectionType.QueuedConnection) self.message.connect( @@ -1269,10 +1259,10 @@ class App(QtCore.QObject): self.ui.notebook.tab_closed_signal.connect(self.on_notebook_closed) # signal to close the application - self.close_app_signal.connect(self.kill_app) + self.close_app_signal.connect(self.kill_app) # noqa # signal to process the body of a script - self.run_script.connect(self.script_processing) + self.run_script.connect(self.script_processing) # noqa # ################################# FINISHED CONNECTING SIGNALS ############################################# # ########################################################################################################### # ########################################################################################################### @@ -4817,7 +4807,7 @@ class App(QtCore.QObject): # first disconnect it as it may have been used by something else try: - self.replot_signal.disconnect() + self.replot_signal.disconnect() # noqa except TypeError: pass self.replot_signal[list].connect(origin_replot) @@ -5153,7 +5143,7 @@ class App(QtCore.QObject): else: location = custom_location - self.jump_signal.emit(location) + self.jump_signal.emit(location) # noqa if fit_center: self.plotcanvas.fit_center(loc=location) @@ -5269,7 +5259,7 @@ class App(QtCore.QObject): cy = loc_b[1] + abs((loc_b[3] - loc_b[1]) / 2) location = (cx, cy) - self.locate_signal.emit(location, location_point) + self.locate_signal.emit(location, location_point) # noqa if fit_center: self.plotcanvas.fit_center(loc=location) @@ -5946,7 +5936,7 @@ class App(QtCore.QObject): msg = "%s %s" % (_("Aborting."), _("The current task will be gracefully closed as soon as possible...")) self.inform.emit(msg) self.abort_flag = True - self.cleanup.emit() + self.cleanup.emit() # noqa def app_is_idle(self): if self.abort_flag: @@ -6339,7 +6329,7 @@ class App(QtCore.QObject): # this signal is used by the Plugins to change the selection on App objects combo boxes when the # selection happen in Project Tab (collection view) # when the plugin is closed then it's not needed - self.proj_selection_changed.disconnect() + self.proj_selection_changed.disconnect() # noqa except (TypeError, AttributeError): pass @@ -8903,8 +8893,8 @@ class ArgsThread(QtCore.QObject): self.listener = None self.thread_exit = False - self.start.connect(self.run) - self.stop.connect(self.close_listener) + self.start.connect(self.run) # noqa + self.stop.connect(self.close_listener) # noqa def my_loop(self, address): try: @@ -8942,7 +8932,7 @@ class ArgsThread(QtCore.QObject): msg = conn.recv() if msg == 'close': break - self.open_signal.emit(msg) + self.open_signal.emit(msg) # noqa conn.close() # the decorator is a must; without it this technique will not work unless the start signal is connected @@ -8967,2869 +8957,4 @@ class ArgsThread(QtCore.QObject): except Exception: pass - -class MenuFileHandlers(QtCore.QObject): - def __init__(self, app): - """ - A class that holds all the menu -> file handlers - """ - super().__init__() - - self.app = app - self.log = self.app.log - self.inform = self.app.inform - self.splash = self.app.splash - self.worker_task = self.app.worker_task - self.options = self.app.options - self.app_units = self.app.app_units - self.pagesize = {} - - self.app.new_project_signal.connect(self.on_new_project_house_keeping) - - def on_fileopengerber(self, name=None): - """ - File menu callback for opening a Gerber. - - :param name: - :return: None - """ - - self.log.debug("on_fileopengerber()") - - _filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 " \ - "*.gko *.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim *.mil *.grb " \ - "*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb *.pho *.gdo *.art *.gbd *.outline);;" \ - "Protel Files (*.gtl *.gbl *.gts *.gbs *.gto *.gbo *.gtp *.gbp *.gml *.gm1 *.gm3 *.gko " \ - "*.outline);;" \ - "Eagle Files (*.cmp *.sol *.stc *.sts *.plc *.pls *.crc *.crs *.tsm *.bsm *.ly2 *.ly15 *.dim " \ - "*.mil);;" \ - "OrCAD Files (*.top *.bot *.smt *.smb *.sst *.ssb *.spt *.spb);;" \ - "Allegro Files (*.art);;" \ - "Mentor Files (*.pho *.gdo);;" \ - "All Files (*.*)" - - if name is None: - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Gerber"), - directory=self.app.get_last_folder(), - filter=_filter_, - initialFilter=self.app.last_op_gerber_filter) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Gerber"), filter=_filter_) - - filenames = [str(filename) for filename in filenames] - self.app.last_op_gerber_filter = _f - else: - filenames = [name] - self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" - "Canvas initialization finished in"), - '%.2f' % self.app.used_time, - _("Opening Gerber file.")), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - - if len(filenames) == 0: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.open_gerber, 'params': [filename]}) - - def on_fileopenexcellon(self, name=None): - """ - File menu callback for opening an Excellon file. - - :param name: - :return: None - """ - - self.log.debug("on_fileopenexcellon()") - - _filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc *.ncd);;" \ - "All Files (*.*)" - if name is None: - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Excellon"), - directory=self.app.get_last_folder(), - filter=_filter_, - initialFilter=self.app.last_op_excellon_filter) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open Excellon"), filter=_filter_) - filenames = [str(filename) for filename in filenames] - self.app.last_op_excellon_filter = _f - else: - filenames = [str(name)] - self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" - "Canvas initialization finished in"), - '%.2f' % self.app.used_time, - _("Opening Excellon file.")), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - - if len(filenames) == 0: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.open_excellon, 'params': [filename]}) - - def on_fileopengcode(self, name=None): - """ - - File menu call back for opening gcode. - - :param name: - :return: - """ - - self.log.debug("on_fileopengcode()") - - # https://bobcadsupport.com/helpdesk/index.php?/Knowledgebase/Article/View/13/5/known-g-code-file-extensions - _filter_ = "G-Code Files (*.txt *.nc *.ncc *.tap *.gcode *.cnc *.ecs *.fnc *.dnc *.ncg *.gc *.fan *.fgc" \ - " *.din *.xpi *.hnc *.h *.i *.ncp *.min *.gcd *.rol *.knc *.mpr *.ply *.out *.eia *.sbp *.mpf);;" \ - "All Files (*.*)" - - if name is None: - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open G-Code"), - directory=self.app.get_last_folder(), - filter=_filter_, - initialFilter=self.app.last_op_gcode_filter) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open G-Code"), filter=_filter_) - - filenames = [str(filename) for filename in filenames] - self.app.last_op_gcode_filter = _f - else: - filenames = [name] - self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" - "Canvas initialization finished in"), - '%.2f' % self.app.used_time, - _("Opening G-Code file.")), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - - if len(filenames) == 0: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.open_gcode, 'params': [filename, None, True]}) - - def on_file_openproject(self): - """ - File menu callback for opening a project. - - :return: None - """ - - self.log.debug("on_file_openproject()") - - _filter_ = "FlatCAM Project (*.FlatPrj);;All Files (*.*)" - try: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Project"), - directory=self.app.get_last_folder(), filter=_filter_) - except TypeError: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Project"), filter=_filter_) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - # self.worker_task.emit({'fcn': self.open_project, - # 'params': [filename]}) - # The above was failing because open_project() is not - # thread safe. The new_project() - self.open_project(filename) - - def on_fileopenhpgl2(self, name=None): - """ - File menu callback for opening a HPGL2. - - :param name: - :return: None - """ - self.log.debug("on_fileopenhpgl2()") - - _filter_ = "HPGL2 Files (*.plt);;" \ - "All Files (*.*)" - - if name is None: - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"), - directory=self.app.get_last_folder(), - filter=_filter_) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open HPGL2"), filter=_filter_) - - filenames = [str(filename) for filename in filenames] - else: - filenames = [name] - self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" - "Canvas initialization finished in"), - '%.2f' % self.app.used_time, - _("Opening HPGL2 file.")), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - - if len(filenames) == 0: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.open_hpgl2, 'params': [filename]}) - - def on_file_openconfig(self): - """ - File menu callback for opening a config file. - - :return: None - """ - - self.log.debug("on_file_openconfig()") - - _filter_ = "FlatCAM Config (*.FlatConfig);;FlatCAM Config (*.json);;All Files (*.*)" - try: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Configuration File"), - directory=self.app.data_path, filter=_filter_) - except TypeError: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Open Configuration File"), - filter=_filter_) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - self.open_config_file(filename) - - def on_file_exportsvg(self): - """ - Callback for menu item File->Export SVG. - - :return: None - """ - self.log.debug("on_file_exportsvg()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if (not isinstance(obj, GeometryObject) - and not isinstance(obj, GerberObject) - and not isinstance(obj, CNCJobObject) - and not isinstance(obj, ExcellonObject)): - msg = _("Only Geometry, Gerber and CNCJob objects can be used.") - msgbox = FCMessageBox(parent=self.app.ui) - msgbox.setWindowTitle(msg) # taskbar still shows it - msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) - - msgbox.setInformativeText(msg) - msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/waning.png')) - - bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) - msgbox.setDefaultButton(bt_ok) - msgbox.exec() - return - - name = obj.obj_options["name"] - - _filter = "SVG File (*.svg);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export SVG"), - directory=self.app.get_last_save_folder() + '/' + str(name) + '_svg', - ext_filter=_filter) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export SVG"), - ext_filter=_filter) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - self.export_svg(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("SVG", filename) - self.app.file_saved.emit("SVG", filename) - - def on_file_exportpng(self): - - self.log.debug("on_file_exportpng()") - - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - - data = None - if self.app.use_3d_engine: - image = _screenshot(alpha=False) - data = np.asarray(image) - if not data.ndim == 3 and data.shape[-1] in (3, 4): - self.inform.emit('[[WARNING_NOTCL]] %s' % _('Data must be a 3D array with last dimension 3 or 4')) - return - - filter_ = "PNG File (*.png);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export PNG Image"), - directory=self.app.get_last_save_folder() + '/png_' + date, - ext_filter=filter_) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export PNG Image"), - ext_filter=filter_) - - filename = str(filename) - - if filename == "": - self.inform.emit(_("Cancelled.")) - return - else: - if self.app.use_3d_engine: - write_png(filename, data) - else: - self.app.plotcanvas.figure.savefig(filename) - - if self.options["global_open_style"] is False: - self.app.file_opened.emit("png", filename) - self.app.file_saved.emit("png", filename) - - def on_file_savegerber(self): - """ - Callback for menu item in Project context menu. - - :return: None - """ - self.log.debug("on_file_savegerber()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if not isinstance(obj, GerberObject): - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Gerber objects can be saved as Gerber files...")) - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter = "Gerber File (*.GBR);;Gerber File (*.GRB);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption="Save Gerber source file", - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Gerber source file"), - ext_filter=_filter) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - self.save_source_file(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Gerber", filename) - self.app.file_saved.emit("Gerber", filename) - - def on_file_savescript(self): - """ - Callback for menu item in Project context menu. - - :return: None - """ - self.log.debug("on_file_savescript()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if not isinstance(obj, ScriptObject): - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Script objects can be saved as TCL Script files...")) - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption="Save Script source file", - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Script source file"), - ext_filter=_filter) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - self.save_source_file(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Script", filename) - self.app.file_saved.emit("Script", filename) - - def on_file_savedocument(self): - """ - Callback for menu item in Project context menu. - - :return: None - """ - self.log.debug("on_file_savedocument()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if not isinstance(obj, ScriptObject): - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Document objects can be saved as Document files...")) - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter = "FlatCAM Documents (*.FlatDoc);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption="Save Document source file", - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Document source file"), - ext_filter=_filter) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - self.save_source_file(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Document", filename) - self.app.file_saved.emit("Document", filename) - - def on_file_saveexcellon(self): - """ - Callback for menu item in project context menu. - - :return: None - """ - self.log.debug("on_file_saveexcellon()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if not isinstance(obj, ExcellonObject): - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter = "Excellon File (*.DRL);;Excellon File (*.TXT);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Excellon source file"), - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Excellon source file"), ext_filter=_filter) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - self.save_source_file(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Excellon", filename) - self.app.file_saved.emit("Excellon", filename) - - def on_file_exportexcellon(self): - """ - Callback for menu item File->Export->Excellon. - - :return: None - """ - self.log.debug("on_file_exportexcellon()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if not isinstance(obj, ExcellonObject): - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter = self.options["excellon_save_filters"] - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Excellon"), - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Excellon"), - ext_filter=_filter) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - used_extension = filename.rpartition('.')[2] - obj.update_filters(last_ext=used_extension, filter_string='excellon_save_filters') - - self.export_excellon(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Excellon", filename) - self.app.file_saved.emit("Excellon", filename) - - def on_file_exportgerber(self): - """ - Callback for menu item File->Export->Gerber. - - :return: None - """ - self.log.debug("on_file_exportgerber()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if not isinstance(obj, GerberObject): - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Gerber objects can be saved as Gerber files...")) - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter_ = self.options['gerber_save_filters'] - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Gerber"), - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter_) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Gerber"), - ext_filter=_filter_) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - used_extension = filename.rpartition('.')[2] - obj.update_filters(last_ext=used_extension, filter_string='gerber_save_filters') - - self.export_gerber(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Gerber", filename) - self.app.file_saved.emit("Gerber", filename) - - def on_file_exportdxf(self): - """ - Callback for menu item File->Export DXF. - - :return: None - """ - self.log.debug("on_file_exportdxf()") - - obj = self.app.collection.get_active() - if obj is None: - self.inform.emit('[ERROR_NOTCL] %s' % _("No object is selected.")) - return - - # Check for more compatible types and add as required - if obj.kind != 'geometry': - msg = _("Only Geometry objects can be used.") - msgbox = FCMessageBox(parent=self.app.ui) - msgbox.setWindowTitle(msg) # taskbar still shows it - msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) - - msgbox.setInformativeText(msg) - msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/waning.png')) - - msgbox.setIcon(QtWidgets.QMessageBox.Icon.Warning) - - msgbox.setInformativeText(msg) - bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) - msgbox.setDefaultButton(bt_ok) - msgbox.exec() - return - - name = self.app.collection.get_active().obj_options["name"] - - _filter_ = "DXF File .dxf (*.DXF);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export DXF"), - directory=self.app.get_last_save_folder() + '/' + name, - ext_filter=_filter_) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export DXF"), - ext_filter=_filter_) - - filename = str(filename) - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - self.export_dxf(name, filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("DXF", filename) - self.app.file_saved.emit("DXF", filename) - - def on_file_importsvg(self, type_of_obj): - """ - Callback for menu item File->Import SVG. - :param type_of_obj: to import the SVG as Geometry or as Gerber - :type type_of_obj: str - :return: None - """ - self.log.debug("on_file_importsvg()") - - _filter_ = "SVG File .svg (*.svg);;All Files (*.*)" - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import SVG"), - directory=self.app.get_last_folder(), - filter=_filter_) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import SVG"), - filter=_filter_) - - if type_of_obj != "geometry" and type_of_obj != "gerber": - type_of_obj = "geometry" - - filenames = [str(filename) for filename in filenames] - - if len(filenames) == 0: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.import_svg, 'params': [filename, type_of_obj]}) - - def on_file_importdxf(self, type_of_obj): - """ - Callback for menu item File->Import DXF. - :param type_of_obj: to import the DXF as Geometry or as Gerber - :type type_of_obj: str - :return: None - """ - self.log.debug("on_file_importdxf()") - - _filter_ = "DXF File .dxf (*.DXF);;All Files (*.*)" - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import DXF"), - directory=self.app.get_last_folder(), - filter=_filter_) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Import DXF"), - filter=_filter_) - - if type_of_obj != "geometry" and type_of_obj != "gerber": - type_of_obj = "geometry" - - filenames = [str(filename) for filename in filenames] - - if len(filenames) == 0: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.import_dxf, 'params': [filename, type_of_obj]}) - - def on_file_new_click(self): - """ - Callback for menu item File -> New. - Executed on clicking the Menu -> File -> New Project - - :return: - """ - self.log.debug("on_file_new_click()") - - if self.app.collection.get_list() and self.app.should_we_save: - msgbox = FCMessageBox(parent=self.app.ui) - title = _("Save changes") - txt = _("There are files/objects opened.\n" - "Creating a New project will delete them.\n" - "Do you want to Save the project?") - msgbox.setWindowTitle(title) # taskbar still shows it - msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) - msgbox.setText('%s' % title) - msgbox.setInformativeText(txt) - msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/save_as.png')) - - bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.ButtonRole.YesRole) - bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.ButtonRole.NoRole) - bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) - - msgbox.setDefaultButton(bt_yes) - msgbox.exec() - response = msgbox.clickedButton() - - if response == bt_yes: - self.on_file_saveprojectas(use_thread=True) - elif response == bt_cancel: - return - elif response == bt_no: - self.on_file_new_project(use_thread=True, silenced=True) - else: - self.on_file_new_project(use_thread=True, silenced=True) - - def on_file_new_project(self, cli=None, reset_tcl=True, use_thread=None, silenced=None, keep_scripts=True): - """ - Returns the application to its startup state. This method is thread-safe. - - :param cli: Boolean. If True this method was run from command line - :param reset_tcl: Boolean. If False, on new project creation the Tcl instance is not recreated, therefore it - will remember all the previous variables. If True then the Tcl is re-instantiated. - :param use_thread: Bool. If True some part of the initialization are done threaded - :param silenced: Bool or None. If True then the app will not ask to save the current parameters. - :param keep_scripts: Bool. If True the Script objects are not deleted when creating a new project - :return: None - """ - - self.log.debug("on_file_new_project()") - - t_start_proj = time.time() - - # close any editor that might be open - if self.app.call_source != 'app': - self.app.editor2object(cleanup=True) - # ## EDITOR section - self.app.geo_editor = AppGeoEditor(self.app) - self.app.exc_editor = AppExcEditor(self.app) - self.app.grb_editor = AppGerberEditor(self.app) - - for obj in self.app.collection.get_list(): - # delete shapes left drawn from mark shape_collections, if any - if isinstance(obj, GerberObject): - try: - obj.mark_shapes_storage.clear() - obj.mark_shapes.clear(update=True) - obj.mark_shapes.enabled = False - except AttributeError: - pass - - # also delete annotation shapes, if any - elif isinstance(obj, CNCJobObject): - try: - obj.text_col.enabled = False - del obj.text_col - obj.annotation.clear(update=True) - del obj.annotation - except AttributeError: - pass - - # delete the exclusion areas - self.app.exc_areas.clear_shapes() - - # delete any selection shape on canvas - self.app.delete_selection_shape() - - # delete all App objects - if keep_scripts is True: - for prj_obj in self.app.collection.get_list(): - if prj_obj.kind != 'script': - self.app.collection.delete_by_name(prj_obj.obj_options['name'], select_project=False) - else: - self.app.collection.delete_all() - - self.log.debug('%s: %s %s.' % - ("Deleted all the application objects", str(time.time() - t_start_proj), _("seconds"))) - - # add in Selected tab an initial text that describe the flow of work in FlatCAm - self.app.setup_default_properties_tab() - - # Clear project filename - self.app.project_filename = None - - default_file = self.app.defaults_path() - # Load the application options - self.options.load(filename=default_file, inform=self.inform) - - # Re-fresh project options - self.app.on_defaults2options() - - if use_thread is True: - self.app.new_project_signal.emit() - else: - t0 = time.time() - # Clear pool - self.app.clear_pool() - - # Init FlatCAMTools - if reset_tcl is True: - self.app.init_tools(init_tcl=True) - else: - self.app.init_tools(init_tcl=False) - self.log.debug( - '%s: %s %s.' % ("Initiated the MP pool and plugins in: ", str(time.time() - t0), _("seconds"))) - - # tcl needs to be reinitialized, otherwise old shell variables etc remains - # self.app.shell.init_tcl() - - # Try to close all tabs in the PlotArea but only if the appGUI is active (CLI is None) - if cli is None: - # we need to go in reverse because once we remove a tab then the index changes - # meaning that removing the first tab (idx = 0) then the tab at former idx = 1 will assume idx = 0 - # and so on. Therefore the deletion should be done in reverse - wdg_count = self.app.ui.plot_tab_area.tabBar.count() - 1 - for index in range(wdg_count, -1, -1): - try: - self.app.ui.plot_tab_area.closeTab(index) - except Exception as e: - self.log.error("App.on_file_new_project() --> %s" % str(e)) - - # # And then add again the Plot Area - self.app.ui.plot_tab_area.insertTab(0, self.app.ui.plot_tab, _("Plot Area")) - self.app.ui.plot_tab_area.protectTab(0) - - # take the focus of the Notebook on Project Tab. - self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab) - - self.log.debug('%s: %s %s.' % (_("Project created in"), str(time.time() - t_start_proj), _("seconds"))) - self.app.ui.set_ui_title(name=_("New Project - Not saved")) - - self.inform.emit('[success] %s...' % _("New Project created")) - - def on_new_project_house_keeping(self): - """ - Do dome of the new project initialization in a threaded way - - :return: - :rtype: - """ - t0 = time.time() - - # Clear pool - self.log.debug("New Project: cleaning multiprocessing pool.") - self.app.clear_pool() - - # Init FlatCAMTools - self.log.debug("New Project: initializing the Tools and Tcl Shell.") - self.app.init_tools(init_tcl=True) - self.log.debug('%s: %s %s.' % ("Initiated the MP pool and plugins in: ", str(time.time() - t0), _("seconds"))) - - def on_filenewscript(self, silent=False): - """ - Will create a new script file and open it in the Code Editor - - :param silent: if True will not display status messages - :return: None - """ - self.log.debug("on_filenewscript()") - - if silent is False: - self.inform.emit('[success] %s' % _("New TCL script file created in Code Editor.")) - - # hide coordinates toolbars in the infobar while in DB - self.app.ui.coords_toolbar.hide() - self.app.ui.delta_coords_toolbar.hide() - - self.app.app_obj.new_script_object() - - def on_fileopenscript(self, name=None, silent=False): - """ - Will open a Tcl script file into the Code Editor - - :param silent: if True will not display status messages - :param name: name of a Tcl script file to open - :return: None - """ - - self.log.debug("on_fileopenscript()") - - _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ - "All Files (*.*)" - - if name: - filenames = [name] - else: - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames( - caption=_("Open TCL script"), directory=self.app.get_last_folder(), filter=_filter_) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_) - - if len(filenames) == 0: - if silent is False: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.open_script, 'params': [filename]}) - - def on_fileopenscript_example(self, name=None, silent=False): - """ - Will open a Tcl script file into the Code Editor - - :param silent: if True will not display status messages - :param name: name of a Tcl script file to open - :return: - """ - - self.log.debug("on_fileopenscript_example()") - - _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ - "All Files (*.*)" - - # test if the app was frozen and choose the path for the configuration file - if getattr(sys, "frozen", False) is True: - example_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\assets\\examples' - else: - example_path = os.path.dirname(os.path.realpath(__file__)) + '\\assets\\examples' - - if name: - filenames = [name] - else: - try: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames( - caption=_("Open TCL script"), directory=example_path, filter=_filter_) - except TypeError: - filenames, _f = QtWidgets.QFileDialog.getOpenFileNames(caption=_("Open TCL script"), filter=_filter_) - - if len(filenames) == 0: - if silent is False: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - for filename in filenames: - if filename != '': - self.worker_task.emit({'fcn': self.open_script, 'params': [filename]}) - - def on_filerunscript(self, name=None, silent=False): - """ - File menu callback for loading and running a TCL script. - - :param silent: if True will not display status messages - :param name: name of a Tcl script file to be run by FlatCAM - :return: None - """ - - self.log.debug("on_file_runscript()") - - if name: - filename = name - if self.app.cmd_line_headless != 1: - self.splash.showMessage('%s: %ssec\n%s' % - (_("Canvas initialization started.\n" - "Canvas initialization finished in"), '%.2f' % self.app.used_time, - _("Executing ScriptObject file.") - ), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - else: - _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ - "All Files (*.*)" - try: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Run TCL script"), - directory=self.app.get_last_folder(), - filter=_filter_) - except TypeError: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Run TCL script"), filter=_filter_) - - # The Qt methods above will return a QString which can cause problems later. - # So far json.dump() will fail to serialize it. - filename = str(filename) - - if filename == "": - if silent is False: - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - if self.app.cmd_line_headless != 1: - if self.app.ui.shell_dock.isHidden(): - self.app.ui.shell_dock.show() - - try: - with open(filename, "r") as tcl_script: - cmd_line_shellfile_content = tcl_script.read() - if self.app.cmd_line_headless != 1: - self.app.shell.exec_command(cmd_line_shellfile_content) - else: - self.app.shell.exec_command(cmd_line_shellfile_content, no_echo=True) - - if silent is False: - self.inform.emit('[success] %s' % _("TCL script file opened in Code Editor and executed.")) - except Exception as e: - self.app.error("App.on_filerunscript() -> %s" % str(e)) - sys.exit(2) - - def on_file_saveproject(self, silent=False): - """ - Callback for menu item File->Save Project. Saves the project to - ``self.project_filename`` or calls ``self.on_file_saveprojectas()`` - if set to None. The project is saved by calling ``self.save_project()``. - - :param silent: if True will not display status messages - :return: None - """ - self.log.debug("on_file_saveproject()") - - if self.app.project_filename is None: - self.on_file_saveprojectas() - else: - self.worker_task.emit({'fcn': self.save_project, 'params': [self.app.project_filename, silent]}) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("project", self.app.project_filename) - self.app.file_saved.emit("project", self.app.project_filename) - - self.app.ui.set_ui_title(name=self.app.project_filename) - - self.app.should_we_save = False - - def on_file_saveprojectas(self, make_copy=False, use_thread=True, quit_action=False): - """ - Callback for menu item File->Save Project As... Opens a file - chooser and saves the project to the given file via - ``self.save_project()``. - - :param make_copy if to be create a copy of the project; boolean - :param use_thread: if to be run in a separate thread; boolean - :param quit_action: if to be followed by quiting the application; boolean - :return: None - """ - self.log.debug("on_file_saveprojectas()") - - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - - filter_ = "FlatCAM Project .FlatPrj (*.FlatPrj);; All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Project As ..."), - directory='{l_save}/{proj}_{date}'.format(l_save=str(self.app.get_last_save_folder()), date=date, - proj=_("Project")), - ext_filter=filter_ - ) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Project As ..."), - ext_filter=filter_) - - filename = str(filename) - - if filename == '': - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - - if use_thread is True: - self.worker_task.emit({'fcn': self.save_project, 'params': [filename, quit_action]}) - else: - self.save_project(filename, quit_action) - - # self.save_project(filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("project", filename) - self.app.file_saved.emit("project", filename) - - if not make_copy: - self.app.project_filename = filename - - self.app.ui.set_ui_title(name=self.app.project_filename) - self.app.should_we_save = False - - def on_file_save_objects_pdf(self, use_thread=True): - self.log.debug("on_file_save_objects_pdf()") - - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - - try: - obj_selection = self.app.collection.get_selected() - if len(obj_selection) == 1: - obj_name = str(obj_selection[0].obj_options['name']) - else: - obj_name = _("General_print") - except AttributeError as att_err: - self.log.debug("App.on_file_save_object_pdf() --> %s" % str(att_err)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) - return - - if not obj_selection: - self.inform.emit( - '[WARNING_NOTCL] %s %s' % (_("No object is selected."), _("Print everything in the workspace."))) - obj_selection = self.app.collection.get_list() - - filter_ = "PDF File .pdf (*.PDF);; All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Object as PDF ..."), - directory='{l_save}/{obj_name}_{date}'.format(l_save=str(self.app.get_last_save_folder()), - obj_name=obj_name, - date=date), - ext_filter=filter_ - ) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Save Object as PDF ..."), - ext_filter=filter_) - - filename = str(filename) - - if filename == '': - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - - if use_thread is True: - self.app.proc_container.new(_("Printing PDF ...")) - self.worker_task.emit({'fcn': self.save_pdf, 'params': [filename, obj_selection]}) - else: - self.save_pdf(filename, obj_selection) - - # self.save_project(filename) - if self.options["global_open_style"] is False: - self.app.file_opened.emit("pdf", filename) - self.app.file_saved.emit("pdf", filename) - - def save_pdf(self, file_name, obj_selection): - self.log.debug("save_pdf()") - - p_size = self.options['global_workspaceT'] - orientation = self.options['global_workspace_orientation'] - color = 'black' - transparency_level = 1.0 - - self.pagesize.update( - { - 'Bounds': None, - 'A0': (841 * mm, 1189 * mm), - 'A1': (594 * mm, 841 * mm), - 'A2': (420 * mm, 594 * mm), - 'A3': (297 * mm, 420 * mm), - 'A4': (210 * mm, 297 * mm), - 'A5': (148 * mm, 210 * mm), - 'A6': (105 * mm, 148 * mm), - 'A7': (74 * mm, 105 * mm), - 'A8': (52 * mm, 74 * mm), - 'A9': (37 * mm, 52 * mm), - 'A10': (26 * mm, 37 * mm), - - 'B0': (1000 * mm, 1414 * mm), - 'B1': (707 * mm, 1000 * mm), - 'B2': (500 * mm, 707 * mm), - 'B3': (353 * mm, 500 * mm), - 'B4': (250 * mm, 353 * mm), - 'B5': (176 * mm, 250 * mm), - 'B6': (125 * mm, 176 * mm), - 'B7': (88 * mm, 125 * mm), - 'B8': (62 * mm, 88 * mm), - 'B9': (44 * mm, 62 * mm), - 'B10': (31 * mm, 44 * mm), - - 'C0': (917 * mm, 1297 * mm), - 'C1': (648 * mm, 917 * mm), - 'C2': (458 * mm, 648 * mm), - 'C3': (324 * mm, 458 * mm), - 'C4': (229 * mm, 324 * mm), - 'C5': (162 * mm, 229 * mm), - 'C6': (114 * mm, 162 * mm), - 'C7': (81 * mm, 114 * mm), - 'C8': (57 * mm, 81 * mm), - 'C9': (40 * mm, 57 * mm), - 'C10': (28 * mm, 40 * mm), - - # American paper sizes - 'LETTER': (8.5 * inch, 11 * inch), - 'LEGAL': (8.5 * inch, 14 * inch), - 'ELEVENSEVENTEEN': (11 * inch, 17 * inch), - - # From https://en.wikipedia.org/wiki/Paper_size - 'JUNIOR_LEGAL': (5 * inch, 8 * inch), - 'HALF_LETTER': (5.5 * inch, 8 * inch), - 'GOV_LETTER': (8 * inch, 10.5 * inch), - 'GOV_LEGAL': (8.5 * inch, 13 * inch), - 'LEDGER': (17 * inch, 11 * inch), - } - ) - - # make sure that the Excellon objeacts are drawn on top of everything - excellon_objs = [obj for obj in obj_selection if obj.kind == 'excellon'] - cncjob_objs = [obj for obj in obj_selection if obj.kind == 'cncjob'] - # reverse the object order such that the first selected is on top - rest_objs = [obj for obj in obj_selection if obj.kind != 'excellon' and obj.kind != 'cncjob'][::-1] - obj_selection = rest_objs + cncjob_objs + excellon_objs - - # generate the SVG files from the application objects - exported_svg = [] - for obj in obj_selection: - svg_obj = obj.export_svg(scale_stroke_factor=0.0) - - if obj.kind.lower() == 'gerber' or obj.kind.lower() == 'excellon': - color = obj.fill_color[:-2] - transparency_level = obj.fill_color[-2:] - elif obj.kind.lower() == 'geometry': - color = self.options["global_draw_color"] - - # Change the attributes of the exported SVG - # We don't need stroke-width - # We set opacity to maximum - # We set the colour to WHITE - - try: - root = ET.fromstring(svg_obj) - except Exception as e: - self.log.debug("MenuFileHandlers.save_pdf() -> Missing root node -> %s" % str(e)) - self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed.")) - return - - for child in root: - child.set('fill', str(color)) - child.set('opacity', str(transparency_level)) - child.set('stroke', str(color)) - - exported_svg.append(ET.tostring(root)) - - xmin = Inf - ymin = Inf - xmax = -Inf - ymax = -Inf - - for obj in obj_selection: - try: - gxmin, gymin, gxmax, gymax = obj.bounds() - xmin = min([xmin, gxmin]) - ymin = min([ymin, gymin]) - xmax = max([xmax, gxmax]) - ymax = max([ymax, gymax]) - except Exception as e: - self.log.error("Tried to get bounds of empty geometry in App.save_pdf(). %s" % str(e)) - - # Determine bounding area for svg export - bounds = [xmin, ymin, xmax, ymax] - size = bounds[2] - bounds[0], bounds[3] - bounds[1] - - # This contain the measure units - uom = obj_selection[0].units.lower() - - # Define a boundary around SVG of about 1.0mm (~39mils) - if uom in "mm": - boundary = 1.0 - else: - boundary = 0.0393701 - - # Convert everything to strings for use in the xml doc - svgwidth = str(size[0] + (2 * boundary)) - svgheight = str(size[1] + (2 * boundary)) - minx = str(bounds[0] - boundary) - miny = str(bounds[1] + boundary + size[1]) - - # Add a SVG Header and footer to the svg output from shapely - # The transform flips the Y Axis so that everything renders - # properly within svg apps such as inkscape - svg_header = ' PDF output --> %s" % str(e)) - return 'fail' - - self.inform.emit('[success] %s: %s' % (_("PDF file saved to"), file_name)) - - def export_svg(self, obj_name, filename, scale_stroke_factor=0.00): - """ - Exports a Geometry Object to an SVG file. - - :param obj_name: the name of the FlatCAM object to be saved as SVG - :param filename: Path to the SVG file to save to. - :param scale_stroke_factor: factor by which to change/scale the thickness of the features - :return: - """ - if filename is None: - filename = self.app.options["global_last_save_folder"] if \ - self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] - - self.log.debug("export_svg()") - - try: - obj = self.app.collection.get_by_name(str(obj_name)) - except Exception: - return 'fail' - - with self.app.proc_container.new(_("Exporting ...")): - exported_svg = obj.export_svg(scale_stroke_factor=scale_stroke_factor) - - # Determine bounding area for svg export - bounds = obj.bounds() - size = obj.size() - - # Convert everything to strings for use in the xml doc - svgwidth = str(size[0]) - svgheight = str(size[1]) - minx = str(bounds[0]) - miny = str(bounds[1] - size[1]) - uom = obj.units.lower() - - # Add a SVG Header and footer to the svg output from shapely - # The transform flips the Y Axis so that everything renders - # properly within svg apps such as inkscape - svg_header = '' - svg_header += '' - svg_footer = ' ' - svg_elem = svg_header + exported_svg + svg_footer - - # Parse the xml through a xml parser just to add line feeds - # and to make it look more pretty for the output - svgcode = parse_xml_string(svg_elem) - svgcode = svgcode.toprettyxml() - - try: - with open(filename, 'w') as fp: - fp.write(svgcode) - except PermissionError: - self.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return 'fail' - - if self.options["global_open_style"] is False: - self.app.file_opened.emit("SVG", filename) - self.app.file_saved.emit("SVG", filename) - self.inform.emit('[success] %s: %s' % (_("SVG file exported to"), filename)) - - def on_import_preferences(self): - """ - Loads the application default settings from a saved file into - ``self.options`` dictionary. - - :return: None - """ - - self.log.debug("App.on_import_preferences()") - - # Show file chooser - filter_ = "Config File (*.FlatConfig);;All Files (*.*)" - try: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Preferences"), - directory=self.app.data_path, - filter=filter_) - except TypeError: - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Preferences"), - filter=filter_) - filename = str(filename) - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - - # Load in the options from the chosen file - self.options.load(filename=filename, inform=self.inform) - - self.app.preferencesUiManager.on_preferences_edited() - self.inform.emit('[success] %s: %s' % (_("Imported Defaults from"), filename)) - - def on_export_preferences(self): - """ - Save the options dictionary to a file. - - :return: None - """ - self.log.debug("on_export_preferences()") - - # defaults_file_content = None - - # Show file chooser - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - filter__ = "Config File .FlatConfig (*.FlatConfig);;All Files (*.*)" - try: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export FlatCAM Preferences"), - directory=os.path.join(self.app.data_path, 'preferences_%s' % date), - ext_filter=filter__ - ) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export FlatCAM Preferences"), ext_filter=filter__) - filename = str(filename) - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return 'fail' - - # Update options - self.app.preferencesUiManager.defaults_read_form() - self.options.propagate_defaults() - - # Save update options - try: - self.options.write(filename=filename) - except Exception: - self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename))) - return - - if self.options["global_open_style"] is False: - self.app.file_opened.emit("preferences", filename) - self.app.file_saved.emit("preferences", filename) - self.inform.emit('[success] %s: %s' % (_("Exported preferences to"), filename)) - - def export_excellon(self, obj_name, filename, local_use=None, use_thread=True): - """ - Exports a Excellon Object to an Excellon file. - - :param obj_name: the name of the FlatCAM object to be saved as Excellon - :param filename: Path to the Excellon file to save to. - :param local_use: - :param use_thread: if to be run in a separate thread - :return: - """ - - if filename is None: - if self.app.options["global_last_save_folder"]: - filename = self.app.options["global_last_save_folder"] + '/' + 'exported_excellon' - else: - filename = self.app.options["global_last_folder"] + '/' + 'exported_excellon' - - self.log.debug("export_excellon()") - - format_exc = ';FILE_FORMAT=%d:%d\n' % (self.options["excellon_exp_integer"], - self.options["excellon_exp_decimals"] - ) - - if local_use is None: - try: - obj = self.app.collection.get_by_name(str(obj_name)) - except Exception: - return "Could not retrieve object: %s" % obj_name - else: - obj = local_use - - if not isinstance(obj, ExcellonObject): - self.inform.emit('[ERROR_NOTCL] %s' % - _("Failed. Only Excellon objects can be saved as Excellon files...")) - return - - # updated units - eunits = self.options["excellon_exp_units"] - ewhole = self.options["excellon_exp_integer"] - efract = self.options["excellon_exp_decimals"] - ezeros = self.options["excellon_exp_zeros"] - eformat = self.options["excellon_exp_format"] - slot_type = self.options["excellon_exp_slot_type"] - - fc_units = self.app_units.upper() - if fc_units == 'MM': - factor = 1 if eunits == 'METRIC' else 0.03937 - else: - factor = 25.4 if eunits == 'METRIC' else 1 - - def make_excellon(): - try: - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) - - header = 'M48\n' - header += ';EXCELLON GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ - (str(self.app.version), str(self.app.version_date)) - - header += ';Filename: %s' % str(obj_name) + '\n' - header += ';Created on : %s' % time_str + '\n' - - if eformat == 'dec': - has_slots, excellon_code = obj.export_excellon(ewhole, efract, factor=factor, slot_type=slot_type) - header += eunits + '\n' - - for tool in obj.tools: - if eunits == 'METRIC': - header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['tooldia']) * factor, - tool=str(tool), - dec=2) - else: - header += "T{tool}F00S00C{:.{dec}f}\n".format(float(obj.tools[tool]['tooldia']) * factor, - tool=str(tool), - dec=4) - else: - if ezeros == 'LZ': - has_slots, excellon_code = obj.export_excellon(ewhole, efract, - form='ndec', e_zeros='LZ', factor=factor, - slot_type=slot_type) - header += '%s,%s\n' % (eunits, 'LZ') - header += format_exc - - for tool in obj.tools: - if eunits == 'METRIC': - header += "T{tool}F00S00C{:.{dec}f}\n".format( - float(obj.tools[tool]['tooldia']) * factor, - tool=str(tool), - dec=2) - else: - header += "T{tool}F00S00C{:.{dec}f}\n".format( - float(obj.tools[tool]['tooldia']) * factor, - tool=str(tool), - dec=4) - else: - has_slots, excellon_code = obj.export_excellon(ewhole, efract, - form='ndec', e_zeros='TZ', factor=factor, - slot_type=slot_type) - header += '%s,%s\n' % (eunits, 'TZ') - header += format_exc - - for tool in obj.tools: - if eunits == 'METRIC': - header += "T{tool}F00S00C{:.{dec}f}\n".format( - float(obj.tools[tool]['tooldia']) * factor, - tool=str(tool), - dec=2) - else: - header += "T{tool}F00S00C{:.{dec}f}\n".format( - float(obj.tools[tool]['tooldia']) * factor, - tool=str(tool), - dec=4) - header += '%\n' - footer = 'M30\n' - - exported_excellon = header - exported_excellon += excellon_code - exported_excellon += footer - - if local_use is None: - try: - with open(filename, 'w') as fp: - fp.write(exported_excellon) - except PermissionError: - self.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return 'fail' - - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Excellon", filename) - self.app.file_saved.emit("Excellon", filename) - self.inform.emit('[success] %s: %s' % (_("Excellon file exported to"), filename)) - else: - return exported_excellon - except Exception as e: - self.log.error("App.export_excellon.make_excellon() --> %s" % str(e)) - return 'fail' - - if use_thread is True: - - with self.app.proc_container.new(_("Exporting ...")): - - def job_thread_exc(app_obj): - ret = make_excellon() - if ret == 'fail': - app_obj.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) - return - - self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]}) - else: - eret = make_excellon() - if eret == 'fail': - self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) - return 'fail' - if local_use is not None: - return eret - - def export_gerber(self, obj_name, filename, local_use=None, use_thread=True): - """ - Exports a Gerber Object to an Gerber file. - - :param obj_name: the name of the FlatCAM object to be saved as Gerber - :param filename: Path to the Gerber file to save to. - :param local_use: if the Gerber code is to be saved to a file (None) or used within FlatCAM. - When not None, the value will be the actual Gerber object for which to create - the Gerber code - :param use_thread: if to be run in a separate thread - :return: - """ - if filename is None: - filename = self.app.options["global_last_save_folder"] if \ - self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] - - self.log.debug("export_gerber()") - - if local_use is None: - try: - obj = self.app.collection.get_by_name(str(obj_name)) - except Exception: - return 'fail' - else: - obj = local_use - - # updated units - gunits = self.options["gerber_exp_units"] - gwhole = self.options["gerber_exp_integer"] - gfract = self.options["gerber_exp_decimals"] - gzeros = self.options["gerber_exp_zeros"] - - fc_units = self.app_units.upper() - if fc_units == 'MM': - factor = 1 if gunits == 'MM' else 0.03937 - else: - factor = 25.4 if gunits == 'MM' else 1 - - def make_gerber(): - try: - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) - - header = 'G04*\n' - header += 'G04 RS-274X GERBER GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' % \ - (str(self.app.version), str(self.app.version_date)) - - header += 'G04 Filename: %s*' % str(obj_name) + '\n' - header += 'G04 Created on : %s*' % time_str + '\n' - header += '%%FS%sAX%s%sY%s%s*%%\n' % (gzeros, gwhole, gfract, gwhole, gfract) - header += "%MO{units}*%\n".format(units=gunits) - - for apid in obj.tools: - if obj.tools[apid]['type'] == 'C': - header += "%ADD{apid}{type},{size}*%\n".format( - apid=str(apid), - type='C', - size=(factor * obj.tools[apid]['size']) - ) - elif obj.tools[apid]['type'] == 'R': - header += "%ADD{apid}{type},{width}X{height}*%\n".format( - apid=str(apid), - type='R', - width=(factor * obj.tools[apid]['width']), - height=(factor * obj.tools[apid]['height']) - ) - elif obj.tools[apid]['type'] == 'O': - header += "%ADD{apid}{type},{width}X{height}*%\n".format( - apid=str(apid), - type='O', - width=(factor * obj.tools[apid]['width']), - height=(factor * obj.tools[apid]['height']) - ) - - header += '\n' - - # obsolete units but some software may need it - if gunits == 'IN': - header += 'G70*\n' - else: - header += 'G71*\n' - - # Absolute Mode - header += 'G90*\n' - - header += 'G01*\n' - # positive polarity - header += '%LPD*%\n' - - footer = 'M02*\n' - - gerber_code = obj.export_gerber(gwhole, gfract, g_zeros=gzeros, factor=factor) - - exported_gerber = header - exported_gerber += gerber_code - exported_gerber += footer - - if local_use is None: - try: - with open(filename, 'w') as fp: - fp.write(exported_gerber) - except PermissionError: - self.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return 'fail' - - if self.options["global_open_style"] is False: - self.app.file_opened.emit("Gerber", filename) - self.app.file_saved.emit("Gerber", filename) - self.inform.emit('[success] %s: %s' % (_("Gerber file exported to"), filename)) - else: - return exported_gerber - except Exception as e: - self.log.error("App.export_gerber.make_gerber() --> %s" % str(e)) - return 'fail' - - if use_thread is True: - with self.app.proc_container.new(_("Exporting ...")): - - def job_thread_grb(app_obj): - ret = make_gerber() - if ret == 'fail': - app_obj.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) - return 'fail' - - self.worker_task.emit({'fcn': job_thread_grb, 'params': [self]}) - else: - gret = make_gerber() - if gret == 'fail': - self.inform.emit('[ERROR_NOTCL] %s' % _('Could not export.')) - return 'fail' - if local_use is not None: - return gret - - def export_dxf(self, obj_name, filename, local_use=None, use_thread=True): - """ - Exports a Geometry Object to an DXF file. - - :param obj_name: the name of the FlatCAM object to be saved as DXF - :param filename: Path to the DXF file to save to. - :param local_use: if the Gerber code is to be saved to a file (None) or used within FlatCAM. - When not None, the value will be the actual Geometry object for which to create - the Geometry/DXF code - :param use_thread: if to be run in a separate thread - :return: - """ - if filename is None: - filename = self.app.options["global_last_save_folder"] if \ - self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] - - self.log.debug("export_dxf()") - - if local_use is None: - try: - obj = self.app.collection.get_by_name(str(obj_name)) - except Exception: - return 'fail' - else: - obj = local_use - - def make_dxf(): - try: - dxf_code = obj.export_dxf() - if local_use is None: - try: - dxf_code.saveas(filename) - except PermissionError: - self.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return 'fail' - - if self.options["global_open_style"] is False: - self.app.file_opened.emit("DXF", filename) - self.app.file_saved.emit("DXF", filename) - self.inform.emit('[success] %s: %s' % (_("DXF file exported to"), filename)) - else: - return dxf_code - except Exception as e: - self.log.error("App.export_dxf.make_dxf() --> %s" % str(e)) - return 'fail' - - if use_thread is True: - - with self.app.proc_container.new(_("Exporting ...")): - - def job_thread_exc(app_obj): - ret_dxf_val = make_dxf() - if ret_dxf_val == 'fail': - app_obj.inform.emit('[WARNING_NOTCL] %s' % _('Could not export.')) - return - - self.worker_task.emit({'fcn': job_thread_exc, 'params': [self]}) - else: - ret = make_dxf() - if ret == 'fail': - self.inform.emit('[WARNING_NOTCL] %s' % _('Could not export.')) - return - if local_use is not None: - return ret - - def import_svg(self, filename, geo_type='geometry', outname=None, plot=True): - """ - Adds a new Geometry Object to the projects and populates - it with shapes extracted from the SVG file. - - :param plot: If True then the resulting object will be plotted on canvas - :param filename: Path to the SVG file. - :param geo_type: Type of FlatCAM object that will be created from SVG - :param outname: The name given to the resulting FlatCAM object - :return: - """ - self.log.debug("App.import_svg()") - if not os.path.exists(filename): - self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) - return - - obj_type = "" - if geo_type is None or geo_type == "geometry": - obj_type = "geometry" - elif geo_type == "gerber": - obj_type = "gerber" - else: - self.inform.emit('[ERROR_NOTCL] %s' % - _("Not supported type is picked as parameter. Only Geometry and Gerber are supported")) - return - - units = self.app_units.upper() - - def obj_init(geo_obj, app_obj): - res = geo_obj.import_svg(filename, obj_type, units=units) - if res == 'fail': - return 'fail' - - geo_obj.multigeo = True - - with open(filename) as f: - file_content = f.read() - geo_obj.source_file = file_content - - # appGUI feedback - app_obj.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - with self.app.proc_container.new('%s ...' % _("Importing")): - - # Object name - name = outname or filename.split('/')[-1].split('\\')[-1] - - ret = self.app.app_obj.new_object(obj_type, name, obj_init, autoselected=False, plot=plot) - - if ret == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _('Import failed.')) - return 'fail' - - # Register recent file - self.app.file_opened.emit("svg", filename) - - def import_dxf(self, filename, geo_type='geometry', outname=None, plot=True): - """ - Adds a new Geometry Object to the projects and populates - it with shapes extracted from the DXF file. - - :param filename: Path to the DXF file. - :param geo_type: Type of FlatCAM object that will be created from DXF - :param outname: Name for the imported Geometry - :param plot: If True then the resulting object will be plotted on canvas - :return: - """ - self.log.debug(" ********* Importing DXF as: %s ********* " % geo_type.capitalize()) - if not os.path.exists(filename): - self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) - return - - obj_type = "" - if geo_type is None or geo_type == "geometry": - obj_type = "geometry" - elif geo_type == "gerber": - obj_type = geo_type - else: - self.inform.emit('[ERROR_NOTCL] %s' % - _("Not supported type is picked as parameter. Only Geometry and Gerber are supported")) - return - - units = self.app_units.upper() - - def obj_init(geo_obj, app_obj): - if obj_type == "geometry": - geo_obj.import_dxf_as_geo(filename, units=units) - elif obj_type == "gerber": - geo_obj.import_dxf_as_gerber(filename, units=units) - else: - return "fail" - - with open(filename) as f: - file_content = f.read() - geo_obj.source_file = file_content - - # appGUI feedback - app_obj.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - with self.app.proc_container.new('%s ...' % _("Importing")): - - # Object name - name = outname or filename.split('/')[-1].split('\\')[-1] - - ret = self.app.app_obj.new_object(obj_type, name, obj_init, autoselected=False, plot=plot) - - if ret == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _('Import failed.')) - return 'fail' - - # Register recent file - self.app.file_opened.emit("dxf", filename) - - def import_pdf(self, filename): - self.app.pdf_tool.periodic_check(1000) - self.worker_task.emit({'fcn': self.app.pdf_tool.open_pdf, 'params': [filename]}) - - def open_gerber(self, filename, outname=None, plot=True, from_tcl=False): - """ - Opens a Gerber file, parses it and creates a new object for - it in the program. Thread-safe. - - :param outname: Name of the resulting object. None causes the - name to be that of the file. Str. - :param filename: Gerber file filename - :type filename: str - :param plot: boolean, to plot or not the resulting object - :param from_tcl: True if run from Tcl Shell - :return: None - """ - - # How the object should be initialized - def obj_init(gerber_obj, app_obj): - - assert isinstance(gerber_obj, GerberObject), \ - "Expected to initialize a GerberObject but got %s" % type(gerber_obj) - - # Opening the file happens here - try: - parse_ret_val = gerber_obj.parse_file(filename) - except IOError: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename)) - return "fail" - except ParseError as parse_err: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(parse_err))) - app_obj.log.error(str(parse_err)) - return "fail" - except Exception as e: - app_obj.log.error("App.open_gerber() --> %s" % str(e)) - msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") - msg += traceback.format_exc() - app_obj.inform.emit(msg) - return "fail" - - if gerber_obj.is_empty(): - app_obj.inform.emit('[ERROR_NOTCL] %s' % - _("Object is not Gerber file or empty. Aborting object creation.")) - return "fail" - - if parse_ret_val: - return parse_ret_val - - self.log.debug("open_gerber()") - if not os.path.exists(filename): - self.inform.emit('[ERROR_NOTCL] %s. %s' % (filename, _("File no longer available."))) - return - - with self.app.proc_container.new('%s...' % _("Opening")): - # Object name - name = outname or filename.split('/')[-1].split('\\')[-1] - - # # ## Object creation # ## - ret_val = self.app.app_obj.new_object("gerber", name, obj_init, autoselected=False, plot=plot) - if ret_val == 'fail': - if from_tcl: - filename = self.options['global_tcl_path'] + '/' + name - ret_val = self.app.app_obj.new_object("gerber", name, obj_init, autoselected=False, plot=plot) - if ret_val == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _('Open Gerber failed. Probable not a Gerber file.')) - return 'fail' - - # Register recent file - self.app.file_opened.emit("gerber", filename) - - # appGUI feedback - self.app.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - def open_excellon(self, filename, outname=None, plot=True, from_tcl=False): - """ - Opens an Excellon file, parses it and creates a new object for - it in the program. Thread-safe. - - :param outname: Name of the resulting object. None causes the name to be that of the file. - :param filename: Excellon file filename - :type filename: str - :param plot: boolean, to plot or not the resulting object - :param from_tcl: True if run from Tcl Shell - :return: None - """ - - self.log.debug("open_excellon()") - - if not os.path.exists(filename): - self.inform.emit('[ERROR_NOTCL] %s. %s' % (filename, _("File no longer available."))) - return - - # How the object should be initialized - def obj_init(excellon_obj, app_obj): - # populate excellon_obj.tools dict - try: - ret = excellon_obj.parse_file(filename=filename) - if ret == "fail": - app_obj.log.debug("Excellon parsing failed.") - self.inform.emit('[ERROR_NOTCL] %s' % _("This is not Excellon file.")) - return "fail" - except IOError: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Cannot open file"), filename)) - app_obj.log.debug("Could not open Excellon object.") - return "fail" - except Exception: - msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n") - msg += traceback.format_exc() - app_obj.inform.emit(msg) - return "fail" - - # populate excellon_obj.solid_geometry list - ret = excellon_obj.create_geometry() - if ret == 'fail': - app_obj.log.debug("Could not create geometry for Excellon object.") - return "fail" - - for tool in excellon_obj.tools: - if excellon_obj.tools[tool]['solid_geometry']: - return - app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("No geometry found in file"), filename)) - return "fail" - - with self.app.proc_container.new('%s...' % _("Opening")): - # Object name - name = outname or filename.split('/')[-1].split('\\')[-1] - ret_val = self.app.app_obj.new_object("excellon", name, obj_init, autoselected=False, plot=plot) - if ret_val == 'fail': - if from_tcl: - filename = self.options['global_tcl_path'] + '/' + name - ret_val = self.app.app_obj.new_object("excellon", name, obj_init, autoselected=False, plot=plot) - if ret_val == 'fail': - self.inform.emit('[ERROR_NOTCL] %s' % - _('Open Excellon file failed. Probable not an Excellon file.')) - return - - # Register recent file - self.app.file_opened.emit("excellon", filename) - - # appGUI feedback - self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - def open_gcode(self, filename, outname=None, force_parsing=None, plot=True, from_tcl=False): - """ - Opens a G-gcode file, parses it and creates a new object for - it in the program. Thread-safe. - - :param filename: G-code file filename - :param outname: Name of the resulting object. None causes the name to be that of the file. - :param force_parsing: - :param plot: If True plot the object on canvas - :param from_tcl: True if run from Tcl Shell - :return: None - """ - self.log.debug("open_gcode()") - - if not os.path.exists(filename): - self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) - return - - # How the object should be initialized - def obj_init(job_obj, app_obj_): - """ - :param job_obj: the resulting object - :type app_obj_: App - """ - assert isinstance(app_obj_, App), \ - "Initializer expected App, got %s" % type(app_obj_) - - app_obj_.inform.emit('%s...' % _("Reading GCode file")) - try: - f = open(filename) - gcode = f.read() - f.close() - except IOError: - app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open"), filename)) - return "fail" - - # try to find from what kind of object this GCode was created - gcode_origin = 'Geometry' - match = re.search(r'^.*Type:\s*.*(\bGeometry\b|\bExcellon\b)', gcode, re.MULTILINE) - if match: - gcode_origin = match.group(1) - job_obj.obj_options['type'] = gcode_origin - # add at least one default tool - if 'excellon' in gcode_origin.lower(): - job_obj.tools = {1: {'data': {'tools_drill_ppname_e': 'default'}}} - if 'geometry' in gcode_origin.lower(): - job_obj.tools = {1: {'data': {'tools_mill_ppname_g': 'default'}}} - - # try to find from what kind of object this GCode was created - match = re.search(r'^.*Preprocessor:\s*.*\bGeometry\b|\bExcellon\b:\s(\b.*\b)', gcode, re.MULTILINE) - detected_preprocessor = 'default' - if match: - detected_preprocessor = match.group(1) - # determine if there is any tool data - match = re.findall(r'^.*Tool:\s*([0-9]*)\s*->\s*Dia:\s*(\d*\.?\d*)', gcode, re.MULTILINE) - if match: - job_obj.tools = {} - for m in match: - if 'excellon' in gcode_origin.lower(): - job_obj.tools[int(m[0])] = { - 'tooldia': float(m[1]), - 'nr_drills': 0, - 'nr_slots': 0, - 'offset_z': 0, - 'data': {'tools_drill_ppname_e': detected_preprocessor} - } - # if 'geometry' in gcode_origin.lower(): - # job_obj.tools[int(m[0])] = { - # 'tooldia': float(m[1]), - # 'data': { - # 'tools_mill_ppname_g': detected_preprocessor, - # 'tools_mill_offset_value': 0.0, - # 'tools_mill_job_type': _('Roughing'), - # 'tools_mill_tool_shape': "C1" - # - # } - # } - job_obj.used_tools = list(job_obj.tools.keys()) - # determine if there is any Cut Z data - match = re.findall(r'^.*Tool:\s*([0-9]*)\s*->\s*Z_Cut:\s*([\-|+]?\d*\.?\d*)', gcode, re.MULTILINE) - if match: - for m in match: - if 'excellon' in gcode_origin.lower(): - if int(m[0]) in job_obj.tools: - job_obj.tools[int(m[0])]['offset_z'] = 0.0 - job_obj.tools[int(m[0])]['data']['tools_drill_cutz'] = float(m[1]) - # if 'geometry' in gcode_origin.lower(): - # if int(m[0]) in job_obj.tools: - # job_obj.tools[int(m[0])]['data']['tools_mill_cutz'] = float(m[1]) - - job_obj.gcode = gcode - - gcode_ret = job_obj.gcode_parse(force_parsing=force_parsing) - if gcode_ret == "fail": - self.inform.emit('[ERROR_NOTCL] %s' % _("This is not GCODE")) - return "fail" - - for k in job_obj.tools: - job_obj.tools[k]['gcode'] = gcode - job_obj.tools[k]['gcode_parsed'] = [] - - for k in job_obj.tools: - print(k, job_obj.tools[k]) - job_obj.create_geometry() - - with self.app.proc_container.new('%s...' % _("Opening")): - - # Object name - name = outname or filename.split('/')[-1].split('\\')[-1] - - # New object creation and file processing - ret_val = self.app.app_obj.new_object("cncjob", name, obj_init, autoselected=False, plot=plot) - if ret_val == 'fail': - if from_tcl: - filename = self.options['global_tcl_path'] + '/' + name - ret_val = self.app.app_obj.new_object("cncjob", name, obj_init, autoselected=False, plot=plot) - if ret_val == 'fail': - self.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to create CNCJob Object. Probable not a GCode file. " - "Try to load it from File menu.\n " - "Attempting to create a FlatCAM CNCJob Object from " - "G-Code file failed during processing")) - return "fail" - - # Register recent file - self.app.file_opened.emit("cncjob", filename) - - # appGUI feedback - self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - def open_hpgl2(self, filename, outname=None): - """ - Opens a HPGL2 file, parses it and creates a new object for - it in the program. Thread-safe. - - :param outname: Name of the resulting object. None causes the name to be that of the file. - :param filename: HPGL2 file filename - :return: None - """ - filename = filename - - # How the object should be initialized - def obj_init(geo_obj, app_obj): - - assert isinstance(geo_obj, GeometryObject), \ - "Expected to initialize a GeometryObject but got %s" % type(geo_obj) - - # Opening the file happens here - obj = HPGL2(self.app) - try: - HPGL2.parse_file(obj, filename) - except IOError: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename)) - return "fail" - except ParseError as parse_err: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(parse_err))) - app_obj.log.error(str(parse_err)) - return "fail" - except Exception as e: - app_obj.log.error("App.open_hpgl2() --> %s" % str(e)) - msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") - msg += traceback.format_exc() - app_obj.inform.emit(msg) - return "fail" - - geo_obj.multigeo = True - geo_obj.solid_geometry = deepcopy(obj.solid_geometry) - geo_obj.tools = deepcopy(obj.tools) - geo_obj.source_file = deepcopy(obj.source_file) - - del obj - - if not geo_obj.solid_geometry: - app_obj.inform.emit('[ERROR_NOTCL] %s' % - _("Object is not HPGL2 file or empty. Aborting object creation.")) - return "fail" - - self.log.debug("open_hpgl2()") - - with self.app.proc_container.new('%s...' % _("Opening")): - # Object name - name = outname or filename.split('/')[-1].split('\\')[-1] - - # # ## Object creation # ## - ret = self.app.app_obj.new_object("geometry", name, obj_init, autoselected=False) - if ret == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _('Failed. Probable not a HPGL2 file.')) - return 'fail' - - # Register recent file - self.app.file_opened.emit("geometry", filename) - - # appGUI feedback - self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - def open_script(self, filename, outname=None, silent=False): - """ - Opens a Script file, parses it and creates a new object for - it in the program. Thread-safe. - - :param outname: Name of the resulting object. None causes the name to be that of the file. - :param filename: Script file filename - :param silent: If True there will be no messages printed to StatusBar - :return: None - """ - - def obj_init(script_obj, app_obj): - - assert isinstance(script_obj, ScriptObject), \ - "Expected to initialize a ScriptObject but got %s" % type(script_obj) - - if silent is False: - app_obj.inform.emit('[success] %s' % _("TCL script file opened in Code Editor.")) - - try: - script_obj.parse_file(filename) - except IOError: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open file"), filename)) - return "fail" - except ParseError as parse_err: - app_obj.inform.emit('[ERROR_NOTCL] %s: %s. %s' % (_("Failed to parse file"), filename, str(parse_err))) - app_obj.log.error(str(parse_err)) - return "fail" - except Exception as e: - app_obj.log.error("App.open_script() -> %s" % str(e)) - msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") - msg += traceback.format_exc() - app_obj.inform.emit(msg) - return "fail" - - self.log.debug("open_script()") - if not os.path.exists(filename): - self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) - return - - with self.app.proc_container.new('%s...' % _("Opening")): - - # Object name - script_name = outname or filename.split('/')[-1].split('\\')[-1] - - # Object creation - ret_val = self.app.app_obj.new_object("script", script_name, obj_init, autoselected=False, plot=False) - if ret_val == 'fail': - filename = self.options['global_tcl_path'] + '/' + script_name - ret_val = self.app.app_obj.new_object("script", script_name, obj_init, autoselected=False, plot=False) - if ret_val == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _('Failed to open TCL Script.')) - return 'fail' - - # Register recent file - self.app.file_opened.emit("script", filename) - - # appGUI feedback - self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - - def open_config_file(self, filename, run_from_arg=None): - """ - Loads a config file from the specified file. - - :param filename: Name of the file from which to load. - :param run_from_arg: if True the FlatConfig file will be open as an command line argument - :return: None - """ - self.log.debug("Opening config file: " + filename) - - if run_from_arg: - self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" - "Canvas initialization finished in"), - '%.2f' % self.app.used_time, - _("Opening FlatCAM Config file.")), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - # # add the tab if it was closed - # self.ui.plot_tab_area.addTab(self.ui.text_editor_tab, _("Code Editor")) - # # first clear previous text in text editor (if any) - # self.ui.text_editor_tab.code_editor.clear() - # - # # Switch plot_area to CNCJob tab - # self.ui.plot_tab_area.setCurrentWidget(self.ui.text_editor_tab) - - # close the Code editor if already open - if self.app.toggle_codeeditor: - self.app.on_toggle_code_editor() - - self.app.on_toggle_code_editor() - - try: - if filename: - f = QtCore.QFile(filename) - if f.open(QtCore.QIODevice.OpenModeFlag.ReadOnly): - stream = QtCore.QTextStream(f) - code_edited = stream.readAll() - self.app.text_editor_tab.load_text(code_edited, clear_text=True, move_to_start=True) - f.close() - except IOError: - self.log.error("Failed to open config file: %s" % filename) - self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open config file"), filename)) - return - - def open_project(self, filename, run_from_arg=False, plot=True, cli=False, from_tcl=False): - """ - Loads a project from the specified file. - - 1) Loads and parses file - 2) Registers the file as recently opened. - 3) Calls on_file_new_project() - 4) Updates options - 5) Calls app_obj.new_object() with the object's from_dict() as init method. - 6) Calls plot_all() if plot=True - - :param filename: Name of the file from which to load. - :param run_from_arg: True if run for arguments - :param plot: If True plot all objects in the project - :param cli: Run from command line - :param from_tcl: True if run from Tcl Sehll - :return: None - """ - - project_filename = filename - - self.log.debug("Opening project: " + project_filename) - if not os.path.exists(project_filename): - self.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) - return - - # block autosaving while a project is loaded - self.app.block_autosave = True - - # for some reason, setting ui_title does not work when this method is called from Tcl Shell - # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled) - if cli is None: - self.app.ui.set_ui_title(name=_("Loading Project ... Please Wait ...")) - - if run_from_arg: - self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" - "Canvas initialization finished in"), - '%.2f' % self.app.used_time, - _("Opening FlatCAM Project file.")), - alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft, - color=QtGui.QColor("lightgray")) - - def parse_worker(prj_filename): - with self.app.proc_container.new('%s' % _("Parsing...")): - # Open and parse an uncompressed Project file - try: - f = open(prj_filename, 'r') - except IOError: - if from_tcl: - name = prj_filename.split('/')[-1].split('\\')[-1] - prj_filename = os.path.join(self.options['global_tcl_path'], name) - try: - f = open(prj_filename, 'r') - except IOError: - self.log.error("Failed to open project file: %s" % prj_filename) - self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), prj_filename)) - return - else: - self.log.error("Failed to open project file: %s" % prj_filename) - self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), prj_filename)) - return - - try: - d = json.load(f, object_hook=dict2obj) - except Exception as e: - self.log.debug( - "Failed to parse project file, trying to see if it loads as an LZMA archive: %s because %s" % - (prj_filename, str(e))) - f.close() - - # Open and parse a compressed Project file - try: - with lzma.open(prj_filename) as f: - file_content = f.read().decode('utf-8') - d = json.loads(file_content, object_hook=dict2obj) - except Exception as e: - self.log.error("Failed to open project file: %s with error: %s" % (prj_filename, str(e))) - self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), prj_filename)) - return - - # Check for older projects - found_older_project = False - for obj in d['objs']: - if 'cnc_tools' in obj or 'exc_cnc_tools' in obj or 'apertures' in obj: - self.app.log.error( - 'MenuFileHandlers.open_project() --> %s %s. %s' % - ("Failed to open the CNCJob file:", str(obj['options']['name']), - "Maybe it is an old project.")) - found_older_project = True - - if found_older_project: - if not run_from_arg or not cli or from_tcl is False: - msgbox = FCMessageBox(parent=self.app.ui) - title = _("Legacy Project") - txt = _("The project was made with an older app version.\n" - "It may not load correctly.\n\n" - "Do you want to continue?") - msgbox.setWindowTitle(title) # taskbar still shows it - msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) - msgbox.setText('%s' % title) - msgbox.setInformativeText(txt) - msgbox.setIcon(QtWidgets.QMessageBox.Icon.Question) - - bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) - bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) - - msgbox.setDefaultButton(bt_ok) - msgbox.exec() - response = msgbox.clickedButton() - - if response == bt_cancel: - return - else: - self.app.log.error("Legacy Project. Loading not supported.") - return - - self.app.restore_project.emit(d, prj_filename, run_from_arg, from_tcl, cli, plot) - - self.app.worker_task.emit({'fcn': parse_worker, 'params': [project_filename]}) - - def restore_project_handler(self, proj_dict, filename, run_from_arg, from_tcl, cli, plot): - # Clear the current project - # # NOT THREAD SAFE # ## - if run_from_arg is True: - pass - elif cli is True: - self.app.delete_selection_shape() - else: - self.on_file_new_project() - - if not run_from_arg or not cli or from_tcl is False: - msgbox = FCMessageBox(parent=self.app.ui) - title = _("Import Settings") - txt = _("Do you want to import the loaded project settings?") - msgbox.setWindowTitle(title) # taskbar still shows it - msgbox.setWindowIcon(QtGui.QIcon(self.app.resource_location + '/app128.png')) - msgbox.setText('%s' % title) - msgbox.setInformativeText(txt) - msgbox.setIconPixmap(QtGui.QPixmap(self.app.resource_location + '/import.png')) - - bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.ButtonRole.YesRole) - bt_no = msgbox.addButton(_('No'), QtWidgets.QMessageBox.ButtonRole.NoRole) - # bt_cancel = msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) - - msgbox.setDefaultButton(bt_yes) - msgbox.exec() - response = msgbox.clickedButton() - - if response == bt_yes: - # self.app.defaults.update(self.app.options) - # self.app.preferencesUiManager.save_defaults() - # Project options - self.app.options.update(proj_dict['options']) - if response == bt_no: - pass - else: - # Load by default new options when not using GUI - # Project options - self.app.options.update(proj_dict['options']) - - self.app.project_filename = filename - - # for some reason, setting ui_title does not work when this method is called from Tcl Shell - # it's because the TclCommand is run in another thread (it inherits TclCommandSignaled) - if cli is None: - self.app.set_screen_units(self.app.options["units"]) - - self.app.restore_project_objects_sig.emit(proj_dict, filename, cli, plot) - - def restore_project_objects(self, proj_dict, filename, cli, plot): - - def worker_task(): - with self.app.proc_container.new('%s' % _("Loading...")): - # Re create objects - self.log.debug(" **************** Started PROEJCT loading... **************** ") - for obj in proj_dict['objs']: - try: - msg = "Recreating from opened project an %s object: %s" % \ - (obj['kind'].capitalize(), obj['obj_options']['name']) - except KeyError: - # allowance for older projects - msg = "Recreating from opened project an %s object: %s" % \ - (obj['kind'].capitalize(), obj['options']['name']) - self.app.log.debug(msg) - - def obj_init(new_obj, app_inst): - try: - new_obj.from_dict(obj) - except Exception as erro: - app_inst.log.error('MenuFileHandlers.open_project() --> ' + str(erro)) - return 'fail' - - # make the 'obj_options' dict a LoudDict - try: - new_obj_options = LoudDict() - new_obj_options.update(new_obj.obj_options) - new_obj.obj_options = new_obj_options - except AttributeError: - new_obj_options = LoudDict() - new_obj_options.update(new_obj.options) - new_obj.obj_options = new_obj_options - except Exception as erro: - app_inst.log.error('MenuFileHandlers.open_project() make a LoudDict--> ' + str(erro)) - return 'fail' - - # ############################################################################################# - # for older projects loading try to convert the 'apertures' or 'cnc_tools' or 'exc_cnc_tools' - # attributes, if found, to 'tools' - # ############################################################################################# - # for older loaded projects - if 'apertures' in obj: - new_obj.tools = obj['apertures'] - if 'cnc_tools' in obj and obj['cnc_tools']: - new_obj.tools = obj['cnc_tools'] - # new_obj.used_tools = [int(k) for k in new_obj.tools.keys()] - # first_key = list(obj['cnc_tools'].keys())[0] - # used_preprocessor = obj['cnc_tools'][first_key]['data']['ppname_g'] - # new_obj.gc_start = new_obj.doformat(self.app.preprocessors[used_preprocessor].start_code) - if 'exc_cnc_tools' in obj and obj['exc_cnc_tools']: - new_obj.tools = obj['exc_cnc_tools'] - # add the used_tools (all of them will be used) - new_obj.used_tools = [float(k) for k in new_obj.tools.keys()] - # add a missing key, 'tooldia' used for plotting CNCJob objects - for td in new_obj.tools: - new_obj.tools[td]['tooldia'] = float(td) - # ############################################################################################# - # ############################################################################################# - - # try to make the keys in the tools dictionary to be integers - # JSON serialization makes them strings - # not all FlatCAM objects have the 'tools' dictionary attribute - try: - new_obj.tools = { - int(tool): tool_dict for tool, tool_dict in list(new_obj.tools.items()) - } - except ValueError: - # for older loaded projects - new_obj.tools = { - float(tool): tool_dict for tool, tool_dict in list(new_obj.tools.items()) - } - except Exception as erro: - app_inst.log.error('MenuFileHandlers.open_project() keys to int--> ' + str(erro)) - return 'fail' - - # ############################################################################################# - # for older loaded projects - # ony older CNCJob objects hold those - if 'cnc_tools' in obj: - new_obj.obj_options['type'] = 'Geometry' - if 'exc_cnc_tools' in obj: - new_obj.obj_options['type'] = 'Excellon' - # ############################################################################################# - - if new_obj.kind == 'cncjob': - # some attributes are serialized so we need t otake this into consideration in - # CNCJob.set_ui() - new_obj.is_loaded_from_project = True - - # for some reason, setting ui_title does not work when this method is called from Tcl Shell - # it's because the TclCommand is run in another thread (it inherits TclCommandSignaled) - try: - if cli is None: - self.app.ui.set_ui_title(name="{} {}: {}".format( - _("Loading Project ... restoring"), obj['kind'].upper(), obj['obj_options']['name'])) - - ret = self.app.app_obj.new_object(obj['kind'], obj['obj_options']['name'], obj_init, plot=plot) - except KeyError: - # allowance for older projects - if cli is None: - self.app.ui.set_ui_title(name="{} {}: {}".format( - _("Loading Project ... restoring"), obj['kind'].upper(), obj['options']['name'])) - try: - ret = self.app.app_obj.new_object(obj['kind'], obj['options']['name'], obj_init, plot=plot) - except Exception: - continue - if ret == 'fail': - continue - - self.inform.emit('[success] %s: %s' % (_("Project loaded from"), filename)) - - self.app.should_we_save = False - self.app.file_opened.emit("project", filename) - - # restore autosaving after a project was loaded - self.app.block_autosave = False - - # for some reason, setting ui_title does not work when this method is called from Tcl Shell - # it's because the TclCommand is run in another thread (it inherit TclCommandSignaled) - if cli is None: - self.app.ui.set_ui_title(name=self.app.project_filename) - - self.log.debug(" **************** Finished PROJECT loading... **************** ") - - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def save_project(self, filename, quit_action=False, silent=False, from_tcl=False): - """ - Saves the current project to the specified file. - - :param filename: Name of the file in which to save. - :type filename: str - :param quit_action: if the project saving will be followed by an app quit; boolean - :param silent: if True will not display status messages - :param from_tcl True is run from Tcl Shell - :return: None - """ - self.log.debug("save_project() -> Saving Project") - self.app.save_in_progress = True - - if from_tcl: - self.log.debug("MenuFileHandlers.save_project() -> Project saved from TCL command.") - - with self.app.proc_container.new(_("Saving Project ...")): - # Capture the latest changes - # Current object - try: - current_object = self.app.collection.get_active() - if current_object: - current_object.read_form() - except Exception as e: - self.log.error("save_project() --> There was no active object. Skipping read_form. %s" % str(e)) - - app_options = {k: v for k, v in self.app.options.items()} - d = { - "objs": [obj.to_dict() for obj in self.app.collection.get_list()], - "options": app_options, - "version": self.app.version - } - - if self.options["global_save_compressed"] is True: - try: - project_as_json = json.dumps(d, default=to_dict, indent=2, sort_keys=True).encode('utf-8') - except Exception as e: - self.log.error( - "Failed to serialize file before compression: %s because: %s" % (str(filename), str(e))) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) - return - - try: - # with lzma.open(filename, "w", preset=int(self.options['global_compression_level'])) as f: - # # # Write - # f.write(project_as_json) - - compressor_obj = lzma.LZMACompressor(preset=int(self.options['global_compression_level'])) - out1 = compressor_obj.compress(project_as_json) - out2 = compressor_obj.flush() - project_zipped = b"".join([out1, out2]) - except Exception as errrr: - self.log.error("Failed to save compressed file: %s because: %s" % (str(filename), str(errrr))) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) - return - - if project_zipped != b'': - with open(filename, "wb") as f_to_write: - f_to_write.write(project_zipped) - - self.inform.emit('[success] %s: %s' % (_("Project saved to"), str(filename))) - else: - self.log.error("Failed to save file: %s. Empty binary file.", str(filename)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) - return - else: - # Open file - try: - f = open(filename, 'w') - except IOError: - self.log.error("Failed to open file for saving: %s", str(filename)) - self.inform.emit('[ERROR_NOTCL] %s' % _("The object is used by another application.")) - return - - # Write - try: - json.dump(d, f, default=to_dict, indent=2, sort_keys=True) - except Exception as e: - self.log.error( - "Failed to serialize file: %s because: %s" % (str(filename), str(e))) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) - return - f.close() - - # verification of the saved project - # Open and parse - try: - saved_f = open(filename, 'r') - except IOError: - if silent is False: - self.inform.emit('[ERROR_NOTCL] %s: %s %s' % - (_("Failed to verify project file"), str(filename), _("Retry to save it."))) - return - - try: - saved_d = json.load(saved_f, object_hook=dict2obj) - if not saved_d: - self.inform.emit('[ERROR_NOTCL] %s: %s %s' % - (_("Failed to parse saved project file"), - str(filename), - _("Retry to save it."))) # noqa - f.close() - return - except Exception: - if silent is False: - self.inform.emit('[ERROR_NOTCL] %s: %s %s' % - (_("Failed to parse saved project file"), - str(filename), - _("Retry to save it."))) # noqa - f.close() - return - - saved_f.close() - - if silent is False: - if 'version' in saved_d: - self.inform.emit('[success] %s: %s' % (_("Project saved to"), str(filename))) - else: - self.inform.emit('[ERROR_NOTCL] %s: %s %s' % - (_("Failed to parse saved project file"), - str(filename), - _("Retry to save it."))) # noqa - - tb_settings = QSettings("Open Source", "FlatCAM") - lock_state = self.app.ui.lock_action.isChecked() - tb_settings.setValue('toolbar_lock', lock_state) - - # This will write the setting to the platform specific storage. - del tb_settings - - # if quit: - # t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename)) - # t.start() - self.app.start_delayed_quit(delay=500, filename=filename, should_quit=quit_action) - - def save_source_file(self, obj_name, filename): - """ - Exports a FlatCAM Object to an Gerber/Excellon file. - - :param obj_name: the name of the FlatCAM object for which to save it's embedded source file - :param filename: Path to the Gerber file to save to. - :return: - """ - - if filename is None: - filename = self.app.options["global_last_save_folder"] if \ - self.app.options["global_last_save_folder"] is not None else self.app.options["global_last_folder"] - - self.log.debug("save_source_file()") - - obj = self.app.collection.get_by_name(obj_name) - - file_string = StringIO(obj.source_file) - time_string = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) - - if file_string.getvalue() == '': - msg = _("Save cancelled because source file is empty. Try to export the file.") - self.inform.emit('[ERROR_NOTCL] %s' % msg) # noqa - return 'fail' - - try: - with open(filename, 'w') as file: - file.writelines('G04*\n') - file.writelines('G04 %s (RE)GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s*\n' % - (obj.kind.upper(), str(self.app.version), str(self.app.version_date))) - file.writelines('G04 Filename: %s*\n' % str(obj_name)) - file.writelines('G04 Created on : %s*\n' % time_string) - - for line in file_string: - file.writelines(line) - except PermissionError: - self.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) # noqa - return 'fail' - - def on_file_savedefaults(self): - """ - Callback for menu item File->Save Defaults. Saves application default options - ``self.options`` to current_defaults.FlatConfig. - - :return: None - """ - self.app.defaults.update(self.app.options) - self.app.preferencesUiManager.save_defaults() - # end of file From bfb3aa4118688977962fc6d54546d30487a20644 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 21 May 2022 02:13:47 +0300 Subject: [PATCH 04/55] - added properties for the mouse position and mouse clicked position (and setters) and cleaned up the code regarding this data --- CHANGELOG.md | 1 + appEditors/AppGeoEditor.py | 22 ++++---- appEditors/AppGerberEditor.py | 4 +- appGUI/MainGUI.py | 28 +++++----- appMain.py | 98 ++++++++++++++++++++--------------- appPlugins/ToolDblSided.py | 2 +- appPlugins/ToolIsolation.py | 2 +- 7 files changed, 85 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bb22aa7..4dcd0a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 21.05.2022 - more code refactored in the appMain.py +- added properties for the mouse position and mouse clicked position (and setters) and cleaned up the code regarding this data 20.05.2022 diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index d5fd6d60..99e75e16 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -646,8 +646,8 @@ class FCCircle(FCShapeTool): QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: - if self.draw_app.app.mouse[0] != self.points[-1][0] or ( - self.draw_app.app.mouse[1] != self.points[-1][1] and + if self.draw_app.app.mouse_pos[0] != self.points[-1][0] or ( + self.draw_app.app.mouse_pos[1] != self.points[-1][1] and self.circle_tool.ui.radius_link_btn.isChecked()): try: # VisPy keys @@ -662,8 +662,8 @@ class FCCircle(FCShapeTool): else: self.circle_tool.radius_x = str(self.circle_tool.radius_x) + chr(key) - if self.draw_app.app.mouse[1] != self.points[-1][1] or ( - self.draw_app.app.mouse[0] != self.points[-1][0] and + if self.draw_app.app.mouse_pos[1] != self.points[-1][1] or ( + self.draw_app.app.mouse_pos[0] != self.points[-1][0] and self.circle_tool.ui.radius_link_btn.isChecked()): try: # VisPy keys @@ -1180,7 +1180,7 @@ class FCRectangle(FCShapeTool): QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: - if self.draw_app.app.mouse[0] != self.points[-1][0]: + if self.draw_app.app.mouse_pos[0] != self.points[-1][0]: try: # VisPy keys if self.rect_tool.length == 0: @@ -1193,7 +1193,7 @@ class FCRectangle(FCShapeTool): self.rect_tool.length = chr(key) else: self.rect_tool.length = str(self.rect_tool.length) + chr(key) - if self.draw_app.app.mouse[1] != self.points[-1][1]: + if self.draw_app.app.mouse_pos[1] != self.points[-1][1]: try: # VisPy keys if self.rect_tool.width == 0: @@ -1423,7 +1423,7 @@ class FCPolygon(FCShapeTool): return _("Failed.") first_pt = self.points[-1] - last_pt = self.draw_app.app.mouse + last_pt = self.draw_app.app.mouse_pos seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) if seg_length == 0.0: @@ -1636,7 +1636,7 @@ class FCPath(FCShapeTool): return _("Failed.") first_pt = self.points[-1] - last_pt = self.draw_app.app.mouse + last_pt = self.draw_app.app.mouse_pos seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) if seg_length == 0.0: @@ -2172,7 +2172,7 @@ class FCMove(FCShapeTool): return _("Failed.") first_pt = self.points[-1] - last_pt = self.draw_app.app.mouse + last_pt = self.draw_app.app.mouse_pos seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) if seg_length == 0.0: @@ -2688,7 +2688,7 @@ class FCCopy(FCShapeTool): return _("Failed.") first_pt = self.points[-1] - last_pt = self.draw_app.app.mouse + last_pt = self.draw_app.app.mouse_pos seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) if seg_length == 0.0: @@ -4303,7 +4303,7 @@ class AppGeoEditor(QtCore.QObject): self.snap_x = x self.snap_y = y - self.app.mouse = [x, y] + self.app.mouse_pos = [x, y] if self.pos is None: self.pos = (0, 0) diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 3dcb8d2e..34968f87 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -2846,7 +2846,7 @@ class ImportEditorGrb(QtCore.QObject, DrawTool): self.snap_x = x self.snap_y = y - self.app.mouse = [x, y] + self.app.mouse_pos = [x, y] if self.pos is None: self.pos = (0, 0) @@ -5395,7 +5395,7 @@ class AppGerberEditor(QtCore.QObject): self.snap_x = x self.snap_y = y - self.app.mouse = [x, y] + self.app.mouse_pos = [x, y] if self.pos is None: self.pos = (0, 0) diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index b8d6d757..a89e9a1a 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -3456,11 +3456,11 @@ class MainGUI(QtWidgets.QMainWindow): # Zoom In if key == QtCore.Qt.Key.Key_Equal: - self.app.plotcanvas.zoom(1 / self.app.defaults['global_zoom_ratio'], self.app.mouse) + self.app.plotcanvas.zoom(1 / self.app.defaults['global_zoom_ratio'], self.app.mouse_pos) # Zoom Out if key == QtCore.Qt.Key.Key_Minus: - self.app.plotcanvas.zoom(self.app.defaults['global_zoom_ratio'], self.app.mouse) + self.app.plotcanvas.zoom(self.app.defaults['global_zoom_ratio'], self.app.mouse_pos) # toggle display of Notebook area if key == QtCore.Qt.Key.Key_QuoteLeft: @@ -3904,8 +3904,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.inform.emit("Click on target point.") self.app.ui.add_pad_ar_btn.setChecked(True) - self.app.grb_editor.x = self.app.mouse[0] - self.app.grb_editor.y = self.app.mouse[1] + self.app.grb_editor.x = self.app.mouse_pos[0] + self.app.grb_editor.y = self.app.mouse_pos[1] self.app.grb_editor.select_tool('array') return @@ -3989,8 +3989,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.inform.emit(_("Click on target point.")) self.app.ui.add_pad_ar_btn.setChecked(True) - self.app.grb_editor.x = self.app.mouse[0] - self.app.grb_editor.y = self.app.mouse[1] + self.app.grb_editor.x = self.app.mouse_pos[0] + self.app.grb_editor.y = self.app.mouse_pos[1] self.app.grb_editor.select_tool('pad') return @@ -4177,8 +4177,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.inform.emit(_("Click on target point.")) self.app.ui.add_slot_btn.setChecked(True) - self.app.exc_editor.x = self.app.mouse[0] - self.app.exc_editor.y = self.app.mouse[1] + self.app.exc_editor.x = self.app.mouse_pos[0] + self.app.exc_editor.y = self.app.mouse_pos[1] self.app.exc_editor.select_tool('slot_add') return @@ -4202,8 +4202,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.inform.emit("Click on target point.") self.app.ui.add_drill_array_btn.setChecked(True) - self.app.exc_editor.x = self.app.mouse[0] - self.app.exc_editor.y = self.app.mouse[1] + self.app.exc_editor.x = self.app.mouse_pos[0] + self.app.exc_editor.y = self.app.mouse_pos[1] self.app.exc_editor.select_tool('drill_array') return @@ -4228,8 +4228,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.inform.emit(_("Click on target point.")) self.app.ui.add_drill_btn.setChecked(True) - self.app.exc_editor.x = self.app.mouse[0] - self.app.exc_editor.y = self.app.mouse[1] + self.app.exc_editor.x = self.app.mouse_pos[0] + self.app.exc_editor.y = self.app.mouse_pos[1] self.app.exc_editor.select_tool('drill_add') return @@ -4258,8 +4258,8 @@ class MainGUI(QtWidgets.QMainWindow): self.app.inform.emit("Click on target point.") self.app.ui.add_slot_array_btn.setChecked(True) - self.app.exc_editor.x = self.app.mouse[0] - self.app.exc_editor.y = self.app.mouse[1] + self.app.exc_editor.x = self.app.mouse_pos[0] + self.app.exc_editor.y = self.app.mouse_pos[1] self.app.exc_editor.select_tool('slot_array') return diff --git a/appMain.py b/appMain.py index f76cc75d..db9296ef 100644 --- a/appMain.py +++ b/appMain.py @@ -316,14 +316,13 @@ class App(QtCore.QObject): self.rel_point2 = (0, 0) # variable to store coordinates - self.pos = (0, 0) - self.pos_canvas = (0, 0) self.pos_jump = (0, 0) # variable to store mouse coordinates - self.mouse = [0, 0] + self._mouse_click_pos = [0, 0] + self._mouse_pos = [0, 0] - # variable to store the delta positions on cavnas + # variable to store the delta positions on canvas self.dx = 0 self.dy = 0 @@ -5132,8 +5131,8 @@ class App(QtCore.QObject): return if dia_box.reference == 'rel': - rel_x = self.mouse[0] + location[0] - rel_y = self.mouse[1] + location[1] + rel_x = self.mouse_pos[0] + location[0] + rel_y = self.mouse_pos[1] + location[1] location = (rel_x, rel_y) self.options['global_jump_ref'] = dia_box.reference except Exception: @@ -6767,27 +6766,27 @@ class App(QtCore.QObject): was clicked, the pixel coordinates and the axes coordinates. :return: None """ - self.pos = [] - event_pos = event.pos if self.use_3d_engine else (event.xdata, event.ydata) - self.pos_canvas = self.plotcanvas.translate_coords(event_pos) + pos_canvas = self.plotcanvas.translate_coords(event_pos) # So it can receive key presses self.plotcanvas.native.setFocus() if self.grid_status(): - self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1]) + pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1]) else: - self.pos = (self.pos_canvas[0], self.pos_canvas[1]) + pos = (pos_canvas[0], pos_canvas[1]) + + self.mouse_click_pos = [pos[0], pos[1]] try: if event.button == 1: # Reset here the relative coordinates so there is a new reference on the click position if self.rel_point1 is None: - self.rel_point1 = self.pos + self.rel_point1 = self.mouse_click_pos else: self.rel_point2 = copy(self.rel_point1) - self.rel_point1 = self.pos + self.rel_point1 = self.mouse_click_pos self.on_mouse_move_over_plot(event, origin_click=True) except Exception as e: @@ -6861,7 +6860,7 @@ class App(QtCore.QObject): self.ui.update_location_labels(self.dx, self.dy, pos[0], pos[1]) self.plotcanvas.on_update_text_hud(self.dx, self.dy, pos[0], pos[1]) - self.mouse = [pos[0], pos[1]] + self.mouse_pos = [pos[0], pos[1]] if self.options['global_selection_shape'] is False: self.selection_type = None @@ -6876,11 +6875,12 @@ class App(QtCore.QObject): if self.event_is_dragging == 1 and event.button == 1: self.delete_selection_shape() if self.dx < 0: - self.draw_moving_selection_shape(self.pos, pos, color=self.options['global_alt_sel_line'], + self.draw_moving_selection_shape(self.mouse_click_pos, self.mouse_pos, + color=self.options['global_alt_sel_line'], face_color=self.options['global_alt_sel_fill']) self.selection_type = False elif self.dx >= 0: - self.draw_moving_selection_shape(self.pos, pos) + self.draw_moving_selection_shape(self.mouse_click_pos, self.mouse_pos) self.selection_type = True else: self.selection_type = None @@ -6923,7 +6923,7 @@ class App(QtCore.QObject): # self.ui.position_label.setText("") # self.ui.rel_position_label.setText("") self.ui.update_location_labels(0.0, 0.0, 0.0, 0.0) - self.mouse = None + self.mouse_pos = [None, None] def on_mouse_click_release_over_plot(self, event): """ @@ -6965,18 +6965,31 @@ class App(QtCore.QObject): ctrl_shift_modifier_key = ctrl_modifier_key | shift_modifier_key if key_modifier == shift_modifier_key or key_modifier == ctrl_shift_modifier_key: - self.on_mouse_and_key_modifiers(position=self.pos, modifiers=key_modifier) + self.on_mouse_and_key_modifiers(position=self.mouse_click_pos, modifiers=key_modifier) self.on_plugin_mouse_click_release(pos=pos) + self.mouse_click_pos = [pos[0], pos[1]] return else: self.on_plugin_mouse_click_release(pos=pos) # the object selection on canvas will not work for App Tools or for Editors if self.call_source != 'app': + self.mouse_click_pos = [pos[0], pos[1]] return if self.doubleclick is True: - self.on_mouse_double_click() + self.doubleclick = False + if self.collection.get_selected(): + self.ui.notebook.setCurrentWidget(self.ui.properties_tab) + if self.ui.splitter.sizes()[0] == 0: + self.ui.splitter.setSizes([1, 1]) + try: + # delete the selection shape(S) as it may be in the way + self.delete_selection_shape() + self.delete_hover_shape() + except Exception as e: + self.log.error("App.on_mouse_click_release_over_plot() double click --> Error: %s" % str(e)) + self.mouse_click_pos = [pos[0], pos[1]] return # WORKAROUND for LEGACY MODE @@ -6987,11 +7000,11 @@ class App(QtCore.QObject): if self.selection_type is not None: try: - self.selection_area_handler(self.pos, pos, self.selection_type) + self.selection_area_handler(self.mouse_click_pos, pos, self.selection_type) self.selection_type = None except Exception as e: - self.log.error( - "FlatCAMApp.on_mouse_click_release_over_plot() select area --> Error: %s" % str(e)) + self.log.error("App.on_mouse_click_release_over_plot() select area --> Error: %s" % str(e)) + self.mouse_click_pos = [pos[0], pos[1]] return if key_modifier == shift_modifier_key: @@ -7014,28 +7027,11 @@ class App(QtCore.QObject): self.delete_hover_shape() except Exception as e: - self.log.error( - "FlatCAMApp.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e)) + self.log.error("App.on_mouse_click_release_over_plot() select click --> Error: %s" % str(e)) + self.mouse_click_pos = [pos[0], pos[1]] return - def on_mouse_double_click(self): - """ - Called when mouse double clicking on canvas. - - :return: - """ - self.doubleclick = False - if self.collection.get_selected(): - self.ui.notebook.setCurrentWidget(self.ui.properties_tab) - if self.ui.splitter.sizes()[0] == 0: - self.ui.splitter.setSizes([1, 1]) - try: - # delete the selection shape(S) as it may be in the way - self.delete_selection_shape() - self.delete_hover_shape() - except Exception as e: - self.log.error( - "FlatCAMApp.on_mouse_click_release_over_plot() double click --> Error: %s" % str(e)) + self.mouse_click_pos = [pos[0], pos[1]] def on_mouse_and_key_modifiers(self, position, modifiers): """ @@ -7113,6 +7109,22 @@ class App(QtCore.QObject): self.ui.popmenu_move2origin.setDisabled(True) self.ui.popmenu_move.setDisabled(True) + @property + def mouse_click_pos(self) -> list[float]: + return [self._mouse_click_pos[0], self._mouse_click_pos[1]] + + @mouse_click_pos.setter + def mouse_click_pos(self, m_pos: list[float] | tuple[float]): + self._mouse_click_pos = m_pos + + @property + def mouse_pos(self) -> list[float]: + return [self._mouse_pos[0], self._mouse_pos[1]] + + @mouse_pos.setter + def mouse_pos(self, m_pos: list[float] | tuple[float]): + self._mouse_pos = m_pos + def selection_area_handler(self, start_pos, end_pos, sel_type): """ Called when the mouse selects by dragging left mouse button on canvas. @@ -7179,7 +7191,7 @@ class App(QtCore.QObject): self.objects_under_the_click_list = [] # Populate the list with the overlapped objects on the click position - curr_x, curr_y = self.pos + curr_x, curr_y = self.mouse_click_pos try: for obj in self.all_objects_list: diff --git a/appPlugins/ToolDblSided.py b/appPlugins/ToolDblSided.py index d40589e7..daddc2df 100644 --- a/appPlugins/ToolDblSided.py +++ b/appPlugins/ToolDblSided.py @@ -532,7 +532,7 @@ class DblSidedTool(AppTool): def on_point_add(self): val = self.app.options["global_point_clipboard_format"] % \ - (self.decimals, self.app.pos[0], self.decimals, self.app.pos[1]) + (self.decimals, self.app.mouse_click_pos[0], self.decimals, self.app.mouse_click_pos[1]) self.ui.point_entry.set_value(val) def on_drill_delete_last(self): diff --git a/appPlugins/ToolIsolation.py b/appPlugins/ToolIsolation.py index b3aa0e52..6e7f05b6 100644 --- a/appPlugins/ToolIsolation.py +++ b/appPlugins/ToolIsolation.py @@ -2518,7 +2518,7 @@ class ToolIsolation(AppTool, Gerber): clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1]), geoset=self.grb_obj.solid_geometry) if self.app.selection_type is not None: - self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type) + self.selection_area_handler(self.app.mouse_pos, curr_pos, self.app.selection_type) self.app.selection_type = None elif clicked_poly: if clicked_poly not in self.poly_dict.values(): From 9055cc1230f61f37d452031f07230b563f1cca12 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 22 May 2022 01:40:55 +0300 Subject: [PATCH 05/55] - in Gerber Editor upgraded the PadAdd GUI --- CHANGELOG.md | 4 + appEditors/AppExcEditor.py | 4 +- appEditors/AppGerberEditor.py | 199 +++++++++++++++++++++-- appEditors/exc_plugins/ExcDrillPlugin.py | 2 +- appEditors/grb_plugins/GrbPadPlugin.py | 196 ++++++++++++++++++++++ appGUI/MainGUI.py | 34 ++-- 6 files changed, 409 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dcd0a5a..c4e4dc07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +22.05.2022 + +- in Gerber Editor upgraded the PadAdd GUI + 21.05.2022 - more code refactored in the appMain.py diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 1e430d5f..2c47de85 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -3064,7 +3064,7 @@ class AppExcEditor(QtCore.QObject): self.ui.deltool_btn.clicked.connect(self.on_tool_delete) # self.ui.tools_table_exc.selectionModel().currentChanged.connect(self.on_row_selected) self.ui.tools_table_exc.cellPressed.connect(self.on_row_selected) - self.ui.tools_table_exc.selectionModel().selectionChanged.connect(self.on_table_selection) + self.ui.tools_table_exc.selectionModel().selectionChanged.connect(self.on_table_selection) # noqa self.app.ui.exc_add_array_drill_menuitem.triggered.connect(self.exc_add_drill_array) self.app.ui.exc_add_drill_menuitem.triggered.connect(self.exc_add_drill) @@ -4634,7 +4634,7 @@ class AppExcEditor(QtCore.QObject): self.app.plotcanvas.on_update_text_hud(self.app.dx, self.app.dy, x, y) # ## Utility geometry (animated) - self.update_utility_geometry(data=(x, y)) + # self.update_utility_geometry(data=(x, y)) self.update_utility_geometry(data=(x, y)) if self.active_tool.name in [ diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 34968f87..eb047b9f 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -11,6 +11,8 @@ from camlib import distance, arc, three_point_circle, flatten_shapely_geometry from appGUI.GUIElements import * from appTool import AppTool +from appEditors.grb_plugins.GrbPadPlugin import PadEditorTool + from appEditors.grb_plugins.GrbBufferPlugin import BufferEditorTool from appEditors.grb_plugins.GrbTransformationPlugin import TransformEditorTool from appEditors.grb_plugins.GrbSimplificationPlugin import SimplificationTool @@ -39,6 +41,7 @@ class PadEditorGrb(ShapeToolEditorGrb): DrawTool.__init__(self, draw_app) self.name = 'pad' self.draw_app = draw_app + self.app = self.draw_app.app self.dont_execute = False try: @@ -63,6 +66,15 @@ class PadEditorGrb(ShapeToolEditorGrb): self.draw_app.select_tool('select') return + if self.app.use_3d_engine: + self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + + self.pad_tool = PadEditorTool(self.app, self.draw_app, plugin_name=_("Pad")) + self.pad_tool.run() + self.pad_tool.length = self.draw_app.last_length + self.ui = self.pad_tool.ui + if self.radius == 0: self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % _("Aperture size is zero. It needs to be greater than zero.")) @@ -84,30 +96,57 @@ class PadEditorGrb(ShapeToolEditorGrb): except KeyError: pass - geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) + geo = self.utility_geometry(data=self.draw_app.app.mouse_pos) if isinstance(geo, DrawToolShape) and geo.geo is not None: self.draw_app.draw_utility_geometry(geo_shape=geo) - self.draw_app.app.inform.emit(_("Click to place ...")) - self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) - # Switch notebook to Properties page - self.draw_app.app.ui.notebook.setCurrentWidget(self.draw_app.app.ui.properties_tab) + self.app.ui.notebook.setTabText(2, _("Pad")) + if self.draw_app.app.ui.splitter.sizes()[0] == 0: + self.draw_app.app.ui.splitter.setSizes([1, 1]) + self.set_plugin_ui() + + # Signals + try: + self.ui.add_btn.clicked.disconnect() + except (AttributeError, TypeError): + pass + self.ui.add_btn.clicked.connect(self.on_add_pad) + + self.draw_app.app.inform.emit(_("Click to place ...")) self.start_msg = _("Click to place ...") + def set_plugin_ui(self): + dia = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) + self.ui.dia_entry.set_value(dia) + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) + def click(self, point): + self.points = point + self.draw_app.last_length = self.pad_tool.length + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) self.make() return "Done." + def on_add_pad(self): + self.draw_app.last_length = self.pad_tool.length + self.points = self.ui.x_entry.get_value(), self.ui.y_entry.get_value() + self.make() + self.draw_app.on_grb_shape_complete(self.draw_app.current_storage) + self.draw_app.build_ui() + self.draw_app.select_tool("pad") + def utility_geometry(self, data=None): if self.dont_execute is True: self.draw_app.select_tool('select') return - self.points = data - geo_data = self.util_shape(data) + pos = data if data else self.points + geo_data = self.util_shape(pos) if geo_data: return DrawToolUtilityShape(geo_data) else: @@ -128,7 +167,6 @@ class PadEditorGrb(ShapeToolEditorGrb): self.half_height = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['height']) / 2 except KeyError: pass - if point[0] is None and point[1] is None: point_x = self.draw_app.x point_y = self.draw_app.y @@ -230,13 +268,129 @@ class PadEditorGrb(ShapeToolEditorGrb): self.draw_app.in_action = False self.complete = True + + try: + self.draw_app.app.jump_signal.disconnect() + except (TypeError, AttributeError): + pass + self.draw_app.app.inform.emit('[success] %s' % _("Done.")) - self.draw_app.app.jump_signal.disconnect() + + def draw_cursor_data(self, pos=None, delete=False): + if pos is None: + pos = self.draw_app.snap_x, self.draw_app.snap_y + + if delete: + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + return + + # font size + qsettings = QtCore.QSettings("Open Source", "FlatCAM") + if qsettings.contains("hud_font_size"): + fsize = qsettings.value('hud_font_size', type=int) + else: + fsize = 8 + + old_x = self.ui.x_entry.get_value() + old_y = self.ui.y_entry.get_value() + + x = pos[0] + y = pos[1] + try: + length = abs(np.sqrt((x - old_x) ** 2 + (y - old_y) ** 2)) + except IndexError: + length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals) + units = self.draw_app.app.app_units.lower() + + x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0' + y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0' + length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0' + + l1_txt = 'X: %s [%s]' % (x_dec, units) + l2_txt = 'Y: %s [%s]' % (y_dec, units) + l3_txt = 'L: %s [%s]' % (length_dec, units) + cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt) + + if self.draw_app.app.use_3d_engine: + new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y)) + x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1])) + + # text + self.draw_app.app.plotcanvas.text_cursor.font_size = fsize + self.draw_app.app.plotcanvas.text_cursor.text = cursor_text + self.draw_app.app.plotcanvas.text_cursor.pos = x, y + self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top' + + if self.draw_app.app.plotcanvas.text_cursor.parent is None: + self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene + + def on_key(self, key): + # Jump to coords + if key == QtCore.Qt.Key.Key_J or key == 'J': + self.draw_app.app.on_jump_to() + + if key in [str(i) for i in range(10)] + ['.', ',', '+', '-', '/', '*'] or \ + key in [QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_1, QtCore.Qt.Key.Key_2, + QtCore.Qt.Key.Key_3, QtCore.Qt.Key.Key_4, QtCore.Qt.Key.Key_5, QtCore.Qt.Key.Key_6, + QtCore.Qt.Key.Key_7, QtCore.Qt.Key.Key_8, QtCore.Qt.Key.Key_9, QtCore.Qt.Key.Key_Minus, + QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, + QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: + try: + # VisPy keys + if self.pad_tool.length == self.draw_app.last_length: + self.pad_tool.length = str(key.name) + self.new_segment = False + else: + self.pad_tool.length = str(self.pad_tool.length) + str(key.name) + except AttributeError: + # Qt keys + if self.pad_tool.length == self.draw_app.last_length: + self.pad_tool.length = chr(key) + else: + self.pad_tool.length = str(self.pad_tool.length) + chr(key) + + if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter: + if self.pad_tool.length != 0: + target_length = self.pad_tool.length + if target_length is None: + self.pad_tool.length = 0.0 + return _("Failed.") + + first_pt = self.ui.x_entry.get_value(), self.ui.y_entry.get_value() + last_pt = self.draw_app.app.mouse_pos + + seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) + if seg_length == 0.0: + self.draw_app.app.log.debug("PadEditorGrb.on_key() --> 'ENTER'. Segment is zero.") + return + try: + new_x = first_pt[0] + (last_pt[0] - first_pt[0]) / seg_length * target_length + new_y = first_pt[1] + (last_pt[1] - first_pt[1]) / seg_length * target_length + except ZeroDivisionError as err: + self.clean_up() + return '[ERROR_NOTCL] %s %s' % (_("Failed."), str(err).capitalize()) + + if first_pt != (new_x, new_y): + self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) + if len(self.points) > 0: + msg = '%s: %s. %s' % ( + _("Projected"), str(self.pad_tool.length), + _("Click on next Point or click right mouse button to complete ...")) + self.draw_app.app.inform.emit(msg) + # self.interpolate_length = '' + # return "Click on next point or hit ENTER to complete ..." def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.apertures_table.clearSelection() self.draw_app.plot_all() + + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + try: self.draw_app.app.jump_signal.disconnect() except (TypeError, AttributeError): @@ -3275,6 +3429,20 @@ class AppGerberEditor(QtCore.QObject): self.snap_y = None self.pos = None + # ############################################################################################################# + # Plugin Attributes + # ############################################################################################################# + self.last_length = 0.0 + + self.last_parray_type = None + self.last_parray_size = None + self.last_parray_lin_dir = None + self.last_parray_circ_dir = None + self.last_parray_pitch = None + self.last_parray_lin_angle = None + self.last_parray_circ_angle = None + self.last_parray_radius = None + # used in RegionEditorGrb and TrackEditorGrb. Will store the bending mode self.bend_mode = 1 @@ -5418,13 +5586,21 @@ class AppGerberEditor(QtCore.QObject): self.app.plotcanvas.on_update_text_hud(self.app.dx, self.app.dy, x, y) self.update_utility_geometry(data=(x, y)) + if self.active_tool.name in [ + 'pad', + ]: + try: + self.active_tool.draw_cursor_data(pos=self.app.mouse_pos) + except AttributeError: + # this can happen if the method is not implemented yet for the active_tool + pass # # ## Selection area on canvas section # ## if event_is_dragging == 1 and event.button == 1: # I make an exception for RegionEditorGrb and TrackEditorGrb because clicking and dragging while making # regions can create strange issues like missing a point in a track/region if isinstance(self.active_tool, RegionEditorGrb) or isinstance(self.active_tool, TrackEditorGrb): - pass + self.app.selection_type = None else: dx = pos_canvas[0] - self.pos[0] self.app.delete_selection_shape() @@ -5557,6 +5733,9 @@ class AppGerberEditor(QtCore.QObject): self.shapes.add(shape=geometry, color=color, face_color=color, layer=0, tolerance=self.tolerance) + def on_shape_complete(self): + pass + @property def visible(self): return self.shapes.visible diff --git a/appEditors/exc_plugins/ExcDrillPlugin.py b/appEditors/exc_plugins/ExcDrillPlugin.py index b5c396cb..660e80b6 100644 --- a/appEditors/exc_plugins/ExcDrillPlugin.py +++ b/appEditors/exc_plugins/ExcDrillPlugin.py @@ -117,7 +117,7 @@ class ExcDrillEditorUI: self.drill_tool_frame.setLayout(self.editor_vbox) # Position - self.tool_lbl = FCLabel('%s' % _("Tool Diameter"), bold=True, color='blue') + self.tool_lbl = FCLabel('%s' % _("Diameter"), bold=True, color='blue') self.editor_vbox.addWidget(self.tool_lbl) # ############################################################################################################# # Diameter Frame diff --git a/appEditors/grb_plugins/GrbPadPlugin.py b/appEditors/grb_plugins/GrbPadPlugin.py index e69de29b..b11d1998 100644 --- a/appEditors/grb_plugins/GrbPadPlugin.py +++ b/appEditors/grb_plugins/GrbPadPlugin.py @@ -0,0 +1,196 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class PadEditorTool(AppToolEditor): + """ + Simple input for buffer distance. + """ + + def __init__(self, app, draw_app, plugin_name): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.plugin_name = plugin_name + + self.ui = PadEditorUI(layout=self.layout, pad_class=self, plugin_name=plugin_name) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.clear_btn.clicked.connect(self.on_clear) + + def disconnect_signals(self): + # Signals + try: + self.ui.clear_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + def run(self): + self.app.defaults.report_usage("Gerber Editor PadEditorTool()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.length = 0.0 + + def on_tab_close(self): + self.disconnect_signals() + self.hide_tool() + # self.app.ui.notebook.callback_on_close = lambda: None + + def on_clear(self): + self.set_tool_ui() + + @property + def length(self): + return self.ui.project_line_entry.get_value() + + @length.setter + def length(self, val): + self.ui.project_line_entry.set_value(val) + + def hide_tool(self): + self.ui.pad_tool_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + if self.draw_app.active_tool.name != 'select': + self.draw_app.select_tool("select") + + +class PadEditorUI: + + def __init__(self, layout, pad_class, plugin_name): + self.pluginName = plugin_name + self.ed_class = pad_class + self.decimals = self.ed_class.app.decimals + self.layout = layout + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.pad_tool_frame = QtWidgets.QFrame() + self.pad_tool_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.pad_tool_frame) + self.editor_vbox = QtWidgets.QVBoxLayout() + self.editor_vbox.setContentsMargins(0, 0, 0, 0) + self.pad_tool_frame.setLayout(self.editor_vbox) + + # Position + self.tool_lbl = FCLabel('%s' % _("Diameter"), bold=True, color='blue') + self.editor_vbox.addWidget(self.tool_lbl) + # ############################################################################################################# + # Diameter Frame + # ############################################################################################################# + dia_frame = FCFrame() + self.editor_vbox.addWidget(dia_frame) + + dia_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + dia_frame.setLayout(dia_grid) + + # Dia Value + self.dia_lbl = FCLabel('%s:' % _("Value")) + self.dia_entry = NumericalEvalEntry(border_color='#0069A9') + self.dia_entry.setDisabled(True) + self.dia_unit = FCLabel('%s' % 'mm') + + dia_grid.addWidget(self.dia_lbl, 0, 0) + dia_grid.addWidget(self.dia_entry, 0, 1) + dia_grid.addWidget(self.dia_unit, 0, 2) + + # Position + self.pos_lbl = FCLabel('%s' % _("Position"), bold=True, color='red') + self.editor_vbox.addWidget(self.pos_lbl) + # ############################################################################################################# + # Position Frame + # ############################################################################################################# + pos_frame = FCFrame() + self.editor_vbox.addWidget(pos_frame) + + pos_grid = GLay(v_spacing=5, h_spacing=3) + pos_frame.setLayout(pos_grid) + + # X Pos + self.x_lbl = FCLabel('%s:' % _("X")) + self.x_entry = FCDoubleSpinner() + self.x_entry.set_precision(self.decimals) + self.x_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.x_lbl, 2, 0) + pos_grid.addWidget(self.x_entry, 2, 1) + + # Y Pos + self.y_lbl = FCLabel('%s:' % _("Y")) + self.y_entry = FCDoubleSpinner() + self.y_entry.set_precision(self.decimals) + self.y_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.y_lbl, 4, 0) + pos_grid.addWidget(self.y_entry, 4, 1) + + # ############################################################################################################# + # Projection Frame + # ############################################################################################################# + pro_frame = FCFrame() + self.editor_vbox.addWidget(pro_frame) + + pro_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + pro_frame.setLayout(pro_grid) + + # Project distance + self.project_line_lbl = FCLabel('%s:' % _("Projection")) + self.project_line_lbl.setToolTip( + _("Length of the current segment/move.") + ) + self.project_line_entry = NumericalEvalEntry(border_color='#0069A9') + pro_grid.addWidget(self.project_line_lbl, 0, 0) + pro_grid.addWidget(self.project_line_entry, 0, 1) + + self.clear_btn = QtWidgets.QToolButton() + self.clear_btn.setIcon(QtGui.QIcon(self.ed_class.app.resource_location + '/trash32.png')) + pro_grid.addWidget(self.clear_btn, 0, 2) + + self.add_btn = FCButton(_("Add")) + self.add_btn.setIcon(QtGui.QIcon(self.ed_class.app.resource_location + '/plus32.png')) + self.editor_vbox.addWidget(self.add_btn) + + GLay.set_common_column_size([dia_grid, pos_grid, pro_grid], 0) + self.layout.addStretch(1) diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index a89e9a1a..3d43c108 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -3868,23 +3868,23 @@ class MainGUI(QtWidgets.QMainWindow): self.on_toggle_notebook() return - # Switch to Project Tab - if key == QtCore.Qt.Key.Key_1 or key == '1': - self.app.grb_editor.launched_from_shortcuts = True - self.on_select_tab('project') - return - - # Switch to Selected Tab - if key == QtCore.Qt.Key.Key_2 or key == '2': - self.app.grb_editor.launched_from_shortcuts = True - self.on_select_tab('selected') - return - - # Switch to Tool Tab - if key == QtCore.Qt.Key.Key_3 or key == '3': - self.app.grb_editor.launched_from_shortcuts = True - self.on_select_tab('tool') - return + # # Switch to Project Tab + # if key == QtCore.Qt.Key.Key_1 or key == '1': + # self.app.grb_editor.launched_from_shortcuts = True + # self.on_select_tab('project') + # return + # + # # Switch to Selected Tab + # if key == QtCore.Qt.Key.Key_2 or key == '2': + # self.app.grb_editor.launched_from_shortcuts = True + # self.on_select_tab('selected') + # return + # + # # Switch to Tool Tab + # if key == QtCore.Qt.Key.Key_3 or key == '3': + # self.app.grb_editor.launched_from_shortcuts = True + # self.on_select_tab('tool') + # return # we do this so we can reuse the following keys while inside a Tool # the above keys are general enough so were left outside From 830d500d041f46bff11226e7ca07b4e3f31b26f3 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 22 May 2022 03:18:51 +0300 Subject: [PATCH 06/55] - in Gerber Editor upgraded the PadArray GUI --- CHANGELOG.md | 1 + appEditors/AppExcEditor.py | 4 + appEditors/AppGerberEditor.py | 472 ++++++++---------- appEditors/exc_plugins/ExcDrillArrayPlugin.py | 2 +- appEditors/grb_plugins/GrbPadArrayPlugin.py | 360 +++++++++++++ appGUI/preferences/PreferencesUIManager.py | 2 +- defaults.py | 2 +- 7 files changed, 580 insertions(+), 263 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4e4dc07..383e2417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 22.05.2022 - in Gerber Editor upgraded the PadAdd GUI +- in Gerber Editor upgraded the PadArray GUI 21.05.2022 diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 2c47de85..576ed827 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -584,6 +584,10 @@ class DrillArray(FCShapeTool): pass self.ui.add_btn.clicked.connect(self.on_add_drill_array) + if self.ui.array_type_radio.get_value() == 'linear': + self.draw_app.app.inform.emit(_("Click on target location ...")) + else: + self.draw_app.app.inform.emit(_("Click on the circular array Center position")) # self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) def set_plugin_ui(self): diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index eb047b9f..c6423742 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -15,6 +15,7 @@ from appEditors.grb_plugins.GrbPadPlugin import PadEditorTool from appEditors.grb_plugins.GrbBufferPlugin import BufferEditorTool from appEditors.grb_plugins.GrbTransformationPlugin import TransformEditorTool +from appEditors.grb_plugins.GrbPadArrayPlugin import GrbPadArrayEditorTool from appEditors.grb_plugins.GrbSimplificationPlugin import SimplificationTool from appEditors.grb_plugins.GrbCopyPlugin import CopyEditorTool @@ -406,6 +407,7 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): DrawTool.__init__(self, draw_app) self.name = 'array' self.draw_app = draw_app + self.app = self.draw_app.app self.dont_execute = False try: @@ -448,18 +450,15 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): except KeyError: pass - self.draw_app.ui.array_frame.show() - self.selected_size = None - self.pad_axis = 'X' - self.pad_array = 'linear' # 'linear' + self.pad_array_dir = 'X' + self.array_type = 'linear' # 'linear' self.pad_array_size = None self.pad_pitch = None self.pad_linear_angle = None - self.pad_angle = None + self.pad_circular_angle = None self.pad_direction = None - self.pad_radius = None self.origin = None self.destination = None @@ -470,36 +469,82 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.pt = [] + # ############################################################################################################# + # Plugin UI + # ############################################################################################################# + self.parray_tool = GrbPadArrayEditorTool(self.app, self.draw_app, plugin_name=_("Pad Array")) + self.ui = self.parray_tool.ui + self.parray_tool.run() + geo = self.utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y), static=True) if isinstance(geo, DrawToolShape) and geo.geo is not None: self.draw_app.draw_utility_geometry(geo_shape=geo) - self.draw_app.app.inform.emit(_("Click on target location ...")) - + if self.app.use_3d_engine: + self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) - # Switch notebook to Properties page - self.draw_app.app.ui.notebook.setCurrentWidget(self.draw_app.app.ui.properties_tab) + if not self.draw_app.snap_x: + self.draw_app.snap_x = 0.0 + if not self.draw_app.snap_y: + self.draw_app.snap_y = 0.0 + + self.app.ui.notebook.setTabText(2, _("Pad Array")) + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + self.set_plugin_ui() + + # Signals + try: + self.ui.add_btn.clicked.disconnect() + except (AttributeError, TypeError): + pass + self.ui.add_btn.clicked.connect(self.on_add_pad_array) + + if self.ui.array_type_radio.get_value() == 'linear': + self.draw_app.app.inform.emit(_("Click on target location ...")) + else: + self.draw_app.app.inform.emit(_("Click on the circular array Center position")) + + def set_plugin_ui(self): + dia = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) + self.ui.dia_entry.set_value(dia) + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) + + self.ui.array_type_radio.set_value(self.draw_app.last_parray_type) + self.ui.on_array_type_radio(val=self.ui.array_type_radio.get_value()) + + self.ui.array_size_entry.set_value(self.draw_app.last_parray_size) + self.ui.axis_radio.set_value(self.draw_app.last_parray_lin_dir) + self.ui.pitch_entry.set_value(self.draw_app.last_parray_pitch) + self.ui.linear_angle_entry.set_value(self.draw_app.last_parray_lin_angle) + self.ui.array_dir_radio.set_value(self.draw_app.last_parray_circ_dir) + self.ui.circular_angle_entry.set_value(self.draw_app.last_parray_circ_angle) + self.ui.radius_entry.set_value(self.draw_app.last_parray_radius) def click(self, point): - - if self.draw_app.ui.array_type_radio.get_value() == 0: # 'Linear' + if self.ui.array_type_radio.get_value() == 'linear': # 'Linear' self.make() + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) return - else: - if self.flag_for_circ_array is None: - self.draw_app.in_action = True - self.pt.append(point) - self.flag_for_circ_array = True - self.set_origin(point) - self.draw_app.app.inform.emit(_("Click on the Pad Circular Array Start position")) - else: - self.destination = point - self.make() - self.flag_for_circ_array = None - return + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) + if self.flag_for_circ_array is None: + self.draw_app.in_action = True + self.pt.append(point) + + self.flag_for_circ_array = True + self.set_origin(point) + self.draw_app.app.inform.emit(_("Click on the circular array Center position")) + else: + self.destination = point + self.make() + self.flag_for_circ_array = None def set_origin(self, origin): self.origin = origin @@ -517,25 +562,26 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.draw_app.select_tool('select') return - self.pad_axis = self.draw_app.ui.pad_axis_radio.get_value() - self.pad_direction = self.draw_app.ui.pad_direction_radio.get_value() - self.pad_array = self.draw_app.ui.array_type_radio.get_value() + self.pad_array_dir = self.ui.axis_radio.get_value() + self.pad_direction = self.ui.array_dir_radio.get_value() + self.array_type = self.ui.array_type_radio.get_value() try: - self.pad_array_size = int(self.draw_app.ui.pad_array_size_entry.get_value()) - try: - self.pad_pitch = self.draw_app.ui.pad_pitch_entry.get_value() - self.pad_linear_angle = self.draw_app.ui.linear_angle_spinner.get_value() - self.pad_angle = self.draw_app.ui.pad_angle_entry.get_value() - except TypeError: - self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % - _("The value is not Float. Check for comma instead of dot separator.")) - return + self.pad_array_size = self.ui.array_size_entry.get_value() except Exception: self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % _("The value is mistyped. Check the value.")) return - if self.pad_array == 'linear': # 'Linear' + try: + self.pad_pitch = self.ui.pitch_entry.get_value() + self.pad_linear_angle = self.ui.linear_angle_entry.get_value() + self.pad_circular_angle = self.ui.circular_angle_entry.get_value() + except TypeError: + self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % + _("The value is not Float. Check for comma instead of dot separator.")) + return + + if self.array_type == 'linear': # 'Linear' if data[0] is None and data[1] is None: dx = self.draw_app.x dy = self.draw_app.y @@ -548,11 +594,11 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.points = [dx, dy] for item in range(self.pad_array_size): - if self.pad_axis == 'X': + if self.pad_array_dir == 'X': geo_el = self.util_shape(((dx + (self.pad_pitch * item)), dy)) - if self.pad_axis == 'Y': + if self.pad_array_dir == 'Y': geo_el = self.util_shape((dx, (dy + (self.pad_pitch * item)))) - if self.pad_axis == 'A': + if self.pad_array_dir == 'A': x_adj = self.pad_pitch * math.cos(math.radians(self.pad_linear_angle)) y_adj = self.pad_pitch * math.sin(math.radians(self.pad_linear_angle)) geo_el = self.util_shape( @@ -579,7 +625,7 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.last_dx = dx self.last_dy = dy return DrawToolUtilityShape(geo_el_list) - elif self.pad_array == 'circular': # 'Circular' + elif self.array_type == 'circular': # 'Circular' if data[0] is None and data[1] is None: cdx = self.draw_app.x cdy = self.draw_app.y @@ -593,9 +639,9 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): radius = distance((cdx, cdy), self.origin) except Exception: radius = 0 - if radius == 0: self.draw_app.delete_utility_geometry() + self.ui.radius_entry.set_value(radius) if len(self.pt) >= 1 and radius > 0: try: @@ -732,13 +778,13 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): return None def circular_util_shape(self, radius, angle): - self.pad_direction = self.draw_app.ui.pad_direction_radio.get_value() - self.pad_angle = self.draw_app.ui.pad_angle_entry.get_value() + self.pad_direction = self.ui.array_dir_radio.get_value() + self.pad_circular_angle = self.ui.circular_angle_entry.get_value() circular_geo = [] if self.pad_direction == 'CW': for i in range(self.pad_array_size): - angle_radians = math.radians(self.pad_angle * i) + angle_radians = math.radians(self.pad_circular_angle * i) x = self.origin[0] + radius * math.cos(-angle_radians + angle) y = self.origin[1] + radius * math.sin(-angle_radians + angle) @@ -752,7 +798,7 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): circular_geo.append(DrawToolShape(geo_el)) else: for i in range(self.pad_array_size): - angle_radians = math.radians(self.pad_angle * i) + angle_radians = math.radians(self.pad_circular_angle * i) x = self.origin[0] + radius * math.cos(angle_radians + angle) y = self.origin[1] + radius * math.sin(angle_radians + angle) @@ -773,13 +819,13 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.draw_app.current_storage = self.storage_obj - if self.pad_array == 'linear': # 'Linear' + if self.array_type == 'linear': # 'Linear' for item in range(self.pad_array_size): - if self.pad_axis == 'X': + if self.pad_array_dir == 'X': geo = self.util_shape(((self.points[0] + (self.pad_pitch * item)), self.points[1])) - if self.pad_axis == 'Y': + if self.pad_array_dir == 'Y': geo = self.util_shape((self.points[0], (self.points[1] + (self.pad_pitch * item)))) - if self.pad_axis == 'A': + if self.pad_array_dir == 'A': x_adj = self.pad_pitch * math.cos(math.radians(self.pad_linear_angle)) y_adj = self.pad_pitch * math.sin(math.radians(self.pad_linear_angle)) geo = self.util_shape( @@ -788,7 +834,7 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.geometry.append(DrawToolShape(geo)) else: # 'Circular' - if (self.pad_angle * self.pad_array_size) > 360: + if (self.pad_circular_angle * self.pad_array_size) > 360: self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % _("Too many items for the selected spacing angle.")) return @@ -810,8 +856,70 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.complete = True self.draw_app.app.inform.emit('[success] %s' % _("Done.")) self.draw_app.in_action = False - self.draw_app.ui.array_frame.hide() - self.draw_app.app.jump_signal.disconnect() + + self.draw_app.last_parray_type = self.ui.array_type_radio.get_value() + self.draw_app.last_parray_size = self.ui.array_size_entry.get_value() + self.draw_app.last_parray_lin_dir = self.ui.axis_radio.get_value() + self.draw_app.last_parray_circ_dir = self.ui.array_dir_radio.get_value() + self.draw_app.last_parray_pitch = self.ui.pitch_entry.get_value() + self.draw_app.last_parray_lin_angle = self.ui.linear_angle_entry.get_value() + self.draw_app.last_parray_circ_angle = self.ui.circular_angle_entry.get_value() + self.draw_app.last_parray_radius = self.ui.radius_entry.get_value() + + try: + self.draw_app.app.jump_signal.disconnect() + except (AttributeError, TypeError): + pass + + def draw_cursor_data(self, pos=None, delete=False): + if pos is None: + pos = self.draw_app.snap_x, self.draw_app.snap_y + + if delete: + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + return + + if not self.points: + self.points = self.draw_app.snap_x, self.draw_app.snap_y + + # font size + qsettings = QtCore.QSettings("Open Source", "FlatCAM") + if qsettings.contains("hud_font_size"): + fsize = qsettings.value('hud_font_size', type=int) + else: + fsize = 8 + + x = pos[0] + y = pos[1] + try: + length = abs(self.ui.radius_entry.get_value()) + except IndexError: + length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals) + + x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0' + y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0' + length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0' + + units = self.draw_app.app.app_units.lower() + l1_txt = 'X: %s [%s]' % (x_dec, units) + l2_txt = 'Y: %s [%s]' % (y_dec, units) + l3_txt = 'L: %s [%s]' % (length_dec, units) + cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt) + + if self.draw_app.app.use_3d_engine: + new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y)) + x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1])) + + # text + self.draw_app.app.plotcanvas.text_cursor.font_size = fsize + self.draw_app.app.plotcanvas.text_cursor.text = cursor_text + self.draw_app.app.plotcanvas.text_cursor.pos = x, y + self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top' + + if self.draw_app.app.plotcanvas.text_cursor.parent is None: + self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): key_modifier = QtWidgets.QApplication.keyboardModifiers() @@ -828,20 +936,55 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): elif mod_key is None: # Toggle Drill Array Direction if key == QtCore.Qt.Key.Key_Space: - if self.draw_app.ui.pad_axis_radio.get_value() == 'X': - self.draw_app.ui.pad_axis_radio.set_value('Y') - elif self.draw_app.ui.pad_axis_radio.get_value() == 'Y': - self.draw_app.ui.pad_axis_radio.set_value('A') - elif self.draw_app.ui.pad_axis_radio.get_value() == 'A': - self.draw_app.ui.pad_axis_radio.set_value('X') + if self.ui.pad_dir_radio.get_value() == 'X': + self.ui.pad_dir_radio.set_value('Y') + elif self.ui.pad_axis_radio.get_value() == 'Y': + self.ui.pad_axis_radio.set_value('A') + elif self.ui.pad_axis_radio.get_value() == 'A': + self.ui.pad_axis_radio.set_value('X') # ## Utility geometry (animated) self.draw_app.update_utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) + def add_pad_array(self, array_pos): + self.radius = self.ui.radius_entry.get_value() + self.array_type = self.ui.array_type_radio.get_value() + + curr_pos = self.draw_app.app.geo_editor.snap(array_pos[0], array_pos[1]) + self.draw_app.snap_x = curr_pos[0] + self.draw_app.snap_y = curr_pos[1] + + self.points = [self.draw_app.snap_x, self.draw_app.snap_y] + self.origin = [self.draw_app.snap_x, self.draw_app.snap_y] + self.destination = ((self.origin[0] + self.radius), self.origin[1]) + self.flag_for_circ_array = True + self.make() + + if self.draw_app.current_storage is not None: + self.draw_app.on_grb_shape_complete(self.draw_app.current_storage) + self.draw_app.build_ui() + + if self.draw_app.active_tool.complete: + self.draw_app.on_shape_complete() + + self.draw_app.select_tool("select") + + self.draw_app.clicked_pos = curr_pos + + def on_add_pad_array(self): + x = self.ui.x_entry.get_value() + y = self.ui.y_entry.get_value() + self.add_pad_array(array_pos=(x, y)) + def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.apertures_table.clearSelection() self.draw_app.plot_all() + + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + try: self.draw_app.app.jump_signal.disconnect() except (TypeError, AttributeError): @@ -3538,10 +3681,7 @@ class AppGerberEditor(QtCore.QObject): self.ui.delaperture_btn.clicked.connect(lambda: self.on_aperture_delete()) self.ui.apertures_table.cellPressed.connect(self.on_row_selected) - self.ui.apertures_table.selectionModel().selectionChanged.connect(self.on_table_selection) - - self.ui.array_type_radio.activated_custom.connect(self.on_array_type_radio) - self.ui.pad_axis_radio.activated_custom.connect(self.on_linear_angle_radio) + self.ui.apertures_table.selectionModel().selectionChanged.connect(self.on_table_selection) #noqa self.ui.simplification_btn.clicked.connect(self.on_simplification_click) self.ui.exit_editor_button.clicked.connect(lambda: self.app.editor2object()) @@ -3639,21 +3779,14 @@ class AppGerberEditor(QtCore.QObject): self.ui.apdim_entry.set_value(self.app.options["gerber_editor_newdim"]) # PAD Array - self.ui.array_type_radio.set_value('linear') # Linear - self.on_array_type_radio(val=self.ui.array_type_radio.get_value()) - self.ui.pad_array_size_entry.set_value(int(self.app.options["gerber_editor_array_size"])) - - # linear array - self.ui.pad_axis_radio.set_value('X') - self.on_linear_angle_radio(val=self.ui.pad_axis_radio.get_value()) - self.ui.pad_axis_radio.set_value(self.app.options["gerber_editor_lin_axis"]) - self.ui.pad_pitch_entry.set_value(float(self.app.options["gerber_editor_lin_pitch"])) - self.ui.linear_angle_spinner.set_value(self.app.options["gerber_editor_lin_angle"]) - - # circular array - self.ui.pad_direction_radio.set_value('CW') - self.ui.pad_direction_radio.set_value(self.app.options["gerber_editor_circ_dir"]) - self.ui.pad_angle_entry.set_value(float(self.app.options["gerber_editor_circ_angle"])) + self.last_parray_type = 'linear' + self.last_parray_size = int(self.app.options['gerber_editor_array_size']) + self.last_parray_lin_dir = self.app.options['gerber_editor_lin_dir'] + self.last_parray_circ_dir = self.app.options['gerber_editor_circ_dir'] + self.last_parray_pitch = float(self.app.options['gerber_editor_lin_pitch']) + self.last_parray_lin_angle = float(self.app.options['gerber_editor_lin_angle']) + self.last_parray_circ_angle = float(self.app.options['gerber_editor_circ_angle']) + self.last_parray_radius = 0.0 self.ui.geo_coords_entry.setText('') self.ui.geo_vertex_entry.set_value(0.0) @@ -5587,7 +5720,7 @@ class AppGerberEditor(QtCore.QObject): self.update_utility_geometry(data=(x, y)) if self.active_tool.name in [ - 'pad', + 'pad', 'array' ]: try: self.active_tool.draw_cursor_data(pos=self.app.mouse_pos) @@ -5920,48 +6053,6 @@ class AppGerberEditor(QtCore.QObject): if geo_el in self.selected: self.selected.remove(geo_el) - def on_array_type_radio(self, val): - if val == 'linear': - self.ui.pad_axis_label.show() - self.ui.pad_axis_radio.show() - self.ui.pad_pitch_label.show() - self.ui.pad_pitch_entry.show() - self.ui.linear_angle_label.show() - self.ui.linear_angle_spinner.show() - self.ui.lin_separator_line.show() - - self.ui.pad_direction_label.hide() - self.ui.pad_direction_radio.hide() - self.ui.pad_angle_label.hide() - self.ui.pad_angle_entry.hide() - self.ui.circ_separator_line.hide() - else: - self.delete_utility_geometry() - - self.ui.pad_axis_label.hide() - self.ui.pad_axis_radio.hide() - self.ui.pad_pitch_label.hide() - self.ui.pad_pitch_entry.hide() - self.ui.linear_angle_label.hide() - self.ui.linear_angle_spinner.hide() - self.ui.lin_separator_line.hide() - - self.ui.pad_direction_label.show() - self.ui.pad_direction_radio.show() - self.ui.pad_angle_label.show() - self.ui.pad_angle_entry.show() - self.ui.circ_separator_line.show() - - self.app.inform.emit(_("Click on the circular array Center position")) - - def on_linear_angle_radio(self, val): - if val == 'A': - self.ui.linear_angle_spinner.show() - self.ui.linear_angle_label.show() - else: - self.ui.linear_angle_spinner.hide() - self.ui.linear_angle_label.hide() - def on_copy_button(self): self.select_tool('copy') return @@ -6549,145 +6640,6 @@ class AppGerberEditorUI: ) hlay_ma.addWidget(self.ma_clear_button) - # ############################################################################################################# - # ######################################### Add Pad Array ##################################################### - # ############################################################################################################# - self.array_frame = QtWidgets.QFrame() - self.array_frame.setContentsMargins(0, 0, 0, 0) - self.custom_box.addWidget(self.array_frame) - self.array_box = QtWidgets.QVBoxLayout() - self.array_box.setContentsMargins(0, 0, 0, 0) - self.array_frame.setLayout(self.array_box) - - separator_line = QtWidgets.QFrame() - separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - self.array_box.addWidget(separator_line) - - array_grid = GLay(v_spacing=5, h_spacing=3) - self.array_box.addLayout(array_grid) - - # Title - self.padarray_label = FCLabel('%s' % _("Add Pad Array"), bold=True) - self.padarray_label.setToolTip( - _("Add an array of pads (linear or circular array)") - ) - array_grid.addWidget(self.padarray_label, 0, 0, 1, 2) - - # Array Type - array_type_lbl = FCLabel('%s:' % _("Type")) - array_type_lbl.setToolTip( - _("Select the type of pads array to create.\n" - "It can be Linear X(Y) or Circular") - ) - - self.array_type_radio = RadioSet([{'label': _('Linear'), 'value': 'linear'}, - {'label': _('Circular'), 'value': 'circular'}]) - - array_grid.addWidget(array_type_lbl, 2, 0) - array_grid.addWidget(self.array_type_radio, 2, 1) - - # Number of Pads in Array - pad_array_size_label = FCLabel('%s:' % _('Nr of pads')) - pad_array_size_label.setToolTip( - _("Specify how many pads to be in the array.") - ) - - self.pad_array_size_entry = FCSpinner() - self.pad_array_size_entry.set_range(1, 10000) - - array_grid.addWidget(pad_array_size_label, 4, 0) - array_grid.addWidget(self.pad_array_size_entry, 4, 1) - - # ############################################################################################################# - # ############################ Linear Pad Array ############################################################### - # ############################################################################################################# - self.lin_separator_line = QtWidgets.QFrame() - self.lin_separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - self.lin_separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - array_grid.addWidget(self.lin_separator_line, 6, 0, 1, 2) - - # Linear Direction - self.pad_axis_label = FCLabel('%s:' % _('Direction')) - self.pad_axis_label.setToolTip( - _("Direction on which the linear array is oriented:\n" - "- 'X' - horizontal axis \n" - "- 'Y' - vertical axis or \n" - "- 'Angle' - a custom angle for the array inclination") - ) - - self.pad_axis_radio = RadioSet([{'label': _('X'), 'value': 'X'}, - {'label': _('Y'), 'value': 'Y'}, - {'label': _('Angle'), 'value': 'A'}]) - - array_grid.addWidget(self.pad_axis_label, 8, 0) - array_grid.addWidget(self.pad_axis_radio, 8, 1) - - # Linear Pitch - self.pad_pitch_label = FCLabel('%s:' % _('Pitch')) - self.pad_pitch_label.setToolTip( - _("Pitch = Distance between elements of the array.") - ) - - self.pad_pitch_entry = FCDoubleSpinner() - self.pad_pitch_entry.set_precision(self.decimals) - self.pad_pitch_entry.set_range(0.0000, 10000.0000) - self.pad_pitch_entry.setSingleStep(0.1) - - array_grid.addWidget(self.pad_pitch_label, 10, 0) - array_grid.addWidget(self.pad_pitch_entry, 10, 1) - - # Linear Angle - self.linear_angle_label = FCLabel('%s:' % _('Angle')) - self.linear_angle_label.setToolTip( - _("Angle at which the linear array is placed.\n" - "The precision is of max 2 decimals.\n" - "Min value is: -360.00 degrees.\n" - "Max value is: 360.00 degrees.") - ) - - self.linear_angle_spinner = FCDoubleSpinner() - self.linear_angle_spinner.set_precision(self.decimals) - self.linear_angle_spinner.setRange(-360.00, 360.00) - - array_grid.addWidget(self.linear_angle_label, 12, 0) - array_grid.addWidget(self.linear_angle_spinner, 12, 1) - - # ############################################################################################################# - # ################################### Circular Pad Array ###################################################### - # ############################################################################################################# - self.circ_separator_line = QtWidgets.QFrame() - self.circ_separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) - self.circ_separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) - array_grid.addWidget(self.circ_separator_line, 14, 0, 1, 2) - - # Circular Direction - self.pad_direction_label = FCLabel('%s:' % _('Direction')) - self.pad_direction_label.setToolTip( - _("Direction for circular array.\n" - "Can be CW = clockwise or CCW = counter clockwise.") - ) - - self.pad_direction_radio = RadioSet([{'label': _('CW'), 'value': 'CW'}, - {'label': _('CCW'), 'value': 'CCW'}]) - - array_grid.addWidget(self.pad_direction_label, 16, 0) - array_grid.addWidget(self.pad_direction_radio, 16, 1) - - # Circular Angle - self.pad_angle_label = FCLabel('%s:' % _('Angle')) - self.pad_angle_label.setToolTip( - _("Angle at which each element in circular array is placed.") - ) - - self.pad_angle_entry = FCDoubleSpinner() - self.pad_angle_entry.set_precision(self.decimals) - self.pad_angle_entry.set_range(-360.00, 360.00) - self.pad_angle_entry.setSingleStep(0.1) - - array_grid.addWidget(self.pad_angle_label, 18, 0) - array_grid.addWidget(self.pad_angle_entry, 18, 1) - self.custom_box.addStretch() layout.addStretch() diff --git a/appEditors/exc_plugins/ExcDrillArrayPlugin.py b/appEditors/exc_plugins/ExcDrillArrayPlugin.py index 382135ea..fea30e04 100644 --- a/appEditors/exc_plugins/ExcDrillArrayPlugin.py +++ b/appEditors/exc_plugins/ExcDrillArrayPlugin.py @@ -110,7 +110,7 @@ class ExcDrillArrayEditorUI: self.darray_frame.setLayout(self.editor_vbox) # Position - self.tool_lbl = FCLabel('%s' % _("Tool Diameter"), bold=True, color='blue') + self.tool_lbl = FCLabel('%s' % _("Diameter"), bold=True, color='blue') self.editor_vbox.addWidget(self.tool_lbl) # ############################################################################################################# # Diameter Frame diff --git a/appEditors/grb_plugins/GrbPadArrayPlugin.py b/appEditors/grb_plugins/GrbPadArrayPlugin.py index e69de29b..f8516e6b 100644 --- a/appEditors/grb_plugins/GrbPadArrayPlugin.py +++ b/appEditors/grb_plugins/GrbPadArrayPlugin.py @@ -0,0 +1,360 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class GrbPadArrayEditorTool(AppToolEditor): + """ + Create an array of drill holes + """ + + def __init__(self, app, draw_app, plugin_name): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.plugin_name = plugin_name + + self.ui = GrbPadArrayEditorUI(layout=self.layout, parray_class=self, plugin_name=plugin_name) + self.connect_signals_at_init() + + def connect_signals_at_init(self): + # Signals + pass + + def disconnect_signals(self): + # Signals + pass + + def run(self): + self.app.defaults.report_usage("Exc Editor ArrayTool()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.ui.array_type_radio.set_value('linear') + self.ui.on_array_type_radio(self.ui.array_type_radio.get_value()) + self.ui.axis_radio.set_value('X') + self.ui.on_linear_angle_radio(self.ui.axis_radio.get_value()) + + self.ui.array_dir_radio.set_value('CW') + + def on_tab_close(self): + self.disconnect_signals() + self.hide_tool() + # self.app.ui.notebook.callback_on_close = lambda: None + + def on_clear(self): + self.set_tool_ui() + + def hide_tool(self): + self.ui.parray_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + if self.draw_app.active_tool.name != 'select': + self.draw_app.select_tool("select") + + +class GrbPadArrayEditorUI: + + def __init__(self, layout, parray_class, plugin_name): + self.pluginName = plugin_name + self.parray_class = parray_class + self.decimals = self.parray_class.app.decimals + self.layout = layout + self.app = self.parray_class.app + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.parray_frame = QtWidgets.QFrame() + self.parray_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.parray_frame) + self.editor_vbox = QtWidgets.QVBoxLayout() + self.editor_vbox.setContentsMargins(0, 0, 0, 0) + self.parray_frame.setLayout(self.editor_vbox) + + # Position + self.tool_lbl = FCLabel('%s' % _("Diameter"), bold=True, color='blue') + self.editor_vbox.addWidget(self.tool_lbl) + # ############################################################################################################# + # Diameter Frame + # ############################################################################################################# + dia_frame = FCFrame() + self.editor_vbox.addWidget(dia_frame) + + dia_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + dia_frame.setLayout(dia_grid) + + # Dia Value + self.dia_lbl = FCLabel('%s:' % _("Value")) + self.dia_entry = NumericalEvalEntry(border_color='#0069A9') + self.dia_entry.setDisabled(True) + self.dia_unit = FCLabel('%s' % 'mm') + + dia_grid.addWidget(self.dia_lbl, 0, 0) + dia_grid.addWidget(self.dia_entry, 0, 1) + dia_grid.addWidget(self.dia_unit, 0, 2) + + # Position + self.pos_lbl = FCLabel('%s' % _("Position"), bold=True, color='red') + self.editor_vbox.addWidget(self.pos_lbl) + # ############################################################################################################# + # Position Frame + # ############################################################################################################# + pos_frame = FCFrame() + self.editor_vbox.addWidget(pos_frame) + + pos_grid = GLay(v_spacing=5, h_spacing=3) + pos_frame.setLayout(pos_grid) + + # X Pos + self.x_lbl = FCLabel('%s:' % _("X")) + self.x_entry = FCDoubleSpinner() + self.x_entry.set_precision(self.decimals) + self.x_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.x_lbl, 2, 0) + pos_grid.addWidget(self.x_entry, 2, 1) + + # Y Pos + self.y_lbl = FCLabel('%s:' % _("Y")) + self.y_entry = FCDoubleSpinner() + self.y_entry.set_precision(self.decimals) + self.y_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.y_lbl, 4, 0) + pos_grid.addWidget(self.y_entry, 4, 1) + + # Position + self.par_lbl = FCLabel('%s' % _("Parameters"), bold=True, color='purple') + self.editor_vbox.addWidget(self.par_lbl) + # ############################################################################################################# + # ######################################## Add Array ########################################################## + # ############################################################################################################# + # add a frame and inside add a grid box layout. + self.array_frame = FCFrame() + self.editor_vbox.addWidget(self.array_frame) + + self.array_grid = GLay(v_spacing=5, h_spacing=3) + self.array_frame.setLayout(self.array_grid) + + # Array Type + array_type_lbl = FCLabel('%s:' % _("Type")) + array_type_lbl.setToolTip( + _("Select the type of array to create.\n" + "It can be Linear X(Y) or Circular") + ) + + self.array_type_radio = RadioSet([ + {'label': _('Linear'), 'value': 'linear'}, + {'label': _('Circular'), 'value': 'circular'} + ]) + + self.array_grid.addWidget(array_type_lbl, 2, 0) + self.array_grid.addWidget(self.array_type_radio, 2, 1) + + # Array Size + self.array_size_label = FCLabel('%s:' % _('Size')) + self.array_size_label.setToolTip(_("Specify how many items to be in the array.")) + + self.array_size_entry = FCSpinner(policy=False) + self.array_size_entry.set_range(1, 100000) + + self.array_grid.addWidget(self.array_size_label, 4, 0) + self.array_grid.addWidget(self.array_size_entry, 4, 1) + + separator_line = QtWidgets.QFrame() + separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) + separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) + self.array_grid.addWidget(separator_line, 6, 0, 1, 2) + + # ############################################################################################################# + # ############################ LINEAR Array ################################################################### + # ############################################################################################################# + self.array_linear_frame = QtWidgets.QFrame() + self.array_linear_frame.setContentsMargins(0, 0, 0, 0) + self.array_grid.addWidget(self.array_linear_frame, 8, 0, 1, 2) + + self.lin_grid = GLay(v_spacing=5, h_spacing=3) + self.lin_grid.setContentsMargins(0, 0, 0, 0) + self.array_linear_frame.setLayout(self.lin_grid) + + # Linear Drill Array direction + self.axis_label = FCLabel('%s:' % _('Direction')) + self.axis_label.setToolTip( + _("Direction on which the linear array is oriented:\n" + "- 'X' - horizontal axis \n" + "- 'Y' - vertical axis or \n" + "- 'Angle' - a custom angle for the array inclination") + ) + + self.axis_radio = RadioSet([ + {'label': _('X'), 'value': 'X'}, + {'label': _('Y'), 'value': 'Y'}, + {'label': _('Angle'), 'value': 'A'} + ]) + + self.lin_grid.addWidget(self.axis_label, 0, 0) + self.lin_grid.addWidget(self.axis_radio, 0, 1) + + # Linear Array pitch distance + self.pitch_label = FCLabel('%s:' % _('Pitch')) + self.pitch_label.setToolTip( + _("Pitch = Distance between elements of the array.") + ) + + self.pitch_entry = FCDoubleSpinner(policy=False) + self.pitch_entry.set_precision(self.decimals) + self.pitch_entry.set_range(0.0000, 10000.0000) + + self.lin_grid.addWidget(self.pitch_label, 2, 0) + self.lin_grid.addWidget(self.pitch_entry, 2, 1) + + # Linear Array angle + self.linear_angle_label = FCLabel('%s:' % _('Angle')) + self.linear_angle_label.setToolTip( + _("Angle at which the linear array is placed.\n" + "The precision is of max 2 decimals.\n" + "Min value is: -360.00 degrees.\n" + "Max value is: 360.00 degrees.") + ) + + self.linear_angle_entry = FCDoubleSpinner(policy=False) + self.linear_angle_entry.set_precision(self.decimals) + self.linear_angle_entry.setSingleStep(1.0) + self.linear_angle_entry.setRange(-360.00, 360.00) + + self.lin_grid.addWidget(self.linear_angle_label, 4, 0) + self.lin_grid.addWidget(self.linear_angle_entry, 4, 1) + + # ############################################################################################################# + # ############################ CIRCULAR Array ################################################################# + # ############################################################################################################# + self.array_circular_frame = QtWidgets.QFrame() + self.array_circular_frame.setContentsMargins(0, 0, 0, 0) + self.array_grid.addWidget(self.array_circular_frame, 12, 0, 1, 2) + + self.circ_grid = GLay(v_spacing=5, h_spacing=3) + self.circ_grid.setContentsMargins(0, 0, 0, 0) + self.array_circular_frame.setLayout(self.circ_grid) + + # Array Direction + self.array_dir_lbl = FCLabel('%s:' % _('Direction')) + self.array_dir_lbl.setToolTip( + _("Direction for circular array.\n" + "Can be CW = clockwise or CCW = counter clockwise.")) + + self.array_dir_radio = RadioSet([ + {'label': _('CW'), 'value': 'CW'}, + {'label': _('CCW'), 'value': 'CCW'}]) + + self.circ_grid.addWidget(self.array_dir_lbl, 0, 0) + self.circ_grid.addWidget(self.array_dir_radio, 0, 1) + + # Array Angle + self.circular_angle_lbl = FCLabel('%s:' % _('Angle')) + self.circular_angle_lbl.setToolTip(_("Angle at which each element in circular array is placed.")) + + self.circular_angle_entry = FCDoubleSpinner(policy=False) + self.circular_angle_entry.set_precision(self.decimals) + self.circular_angle_entry.setSingleStep(1.0) + self.circular_angle_entry.setRange(-360.00, 360.00) + + self.circ_grid.addWidget(self.circular_angle_lbl, 2, 0) + self.circ_grid.addWidget(self.circular_angle_entry, 2, 1) + + # Radius + self.radius_lbl = FCLabel('%s:' % _('Radius')) + self.radius_lbl.setToolTip(_("Array radius.")) + + self.radius_entry = FCDoubleSpinner(policy=False) + self.radius_entry.set_precision(self.decimals) + self.radius_entry.setSingleStep(1.0) + self.radius_entry.setRange(-10000.0000,10000.000) + + self.circ_grid.addWidget(self.radius_lbl, 4, 0) + self.circ_grid.addWidget(self.radius_entry, 4, 1) + + # ############################################################################################################# + # Buttons + # ############################################################################################################# + self.add_btn = FCButton(_("Add")) + self.add_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png')) + self.layout.addWidget(self.add_btn) + + GLay.set_common_column_size([dia_grid, pos_grid, self.array_grid, self.lin_grid, self.circ_grid], 0) + + self.layout.addStretch(1) + + # Signals + self.array_type_radio.activated_custom.connect(self.on_array_type_radio) + self.axis_radio.activated_custom.connect(self.on_linear_angle_radio) + + 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) + + 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) + + def on_array_type_radio(self, val): + if val == 'linear': + self.array_circular_frame.hide() + self.array_linear_frame.show() + + self.app.inform.emit(_("Click to place ...")) + else: # 'circular' + self.array_circular_frame.show() + self.array_linear_frame.hide() + + self.app.inform.emit(_("Click on the circular array Center position")) + + self.array_size_entry.setDisabled(False) + + def on_linear_angle_radio(self, val): + if val == 'A': + self.linear_angle_entry.setEnabled(True) + self.linear_angle_label.setEnabled(True) + else: + self.linear_angle_entry.setEnabled(False) + self.linear_angle_label.setEnabled(False) diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 612e653e..a66ec630 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -175,7 +175,7 @@ class PreferencesUIManager(QtCore.QObject): "gerber_editor_newtype": self.ui.gerber_pref_form.gerber_editor_group.addtype_combo, "gerber_editor_newdim": self.ui.gerber_pref_form.gerber_editor_group.adddim_entry, "gerber_editor_array_size": self.ui.gerber_pref_form.gerber_editor_group.grb_array_size_entry, - "gerber_editor_lin_axis": self.ui.gerber_pref_form.gerber_editor_group.grb_axis_radio, + "gerber_editor_lin_dir": self.ui.gerber_pref_form.gerber_editor_group.grb_axis_radio, "gerber_editor_lin_pitch": self.ui.gerber_pref_form.gerber_editor_group.grb_pitch_entry, "gerber_editor_lin_angle": self.ui.gerber_pref_form.gerber_editor_group.grb_angle_entry, "gerber_editor_circ_dir": self.ui.gerber_pref_form.gerber_editor_group.grb_circular_dir_radio, diff --git a/defaults.py b/defaults.py index ac9e1f30..0db909a0 100644 --- a/defaults.py +++ b/defaults.py @@ -236,7 +236,7 @@ class AppDefaults: "gerber_editor_newtype": 'C', "gerber_editor_newdim": "0.5, 0.5", "gerber_editor_array_size": 5, - "gerber_editor_lin_axis": 'X', + "gerber_editor_lin_dir": 'X', "gerber_editor_lin_pitch": 0.1, "gerber_editor_lin_angle": 0.0, "gerber_editor_circ_dir": 'CW', From 5298a59372305992b842e9bb0cc3e95b958b1bc2 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 22 May 2022 04:37:16 +0300 Subject: [PATCH 07/55] - in Gerber Editor upgraded the Track sub-tool GUI --- CHANGELOG.md | 1 + appEditors/AppGerberEditor.py | 206 ++++++++++++++++++++--- appEditors/grb_plugins/GrbTracePlugin.py | 149 ---------------- appEditors/grb_plugins/GrbTrackPlugin.py | 196 +++++++++++++++++++++ 4 files changed, 376 insertions(+), 176 deletions(-) delete mode 100644 appEditors/grb_plugins/GrbTracePlugin.py create mode 100644 appEditors/grb_plugins/GrbTrackPlugin.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 383e2417..cb3f2367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG for FlatCAM Evo beta - in Gerber Editor upgraded the PadAdd GUI - in Gerber Editor upgraded the PadArray GUI +- in Gerber Editor upgraded the Track sub-tool GUI 21.05.2022 diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index c6423742..8dc14270 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -16,6 +16,7 @@ from appEditors.grb_plugins.GrbPadPlugin import PadEditorTool from appEditors.grb_plugins.GrbBufferPlugin import BufferEditorTool from appEditors.grb_plugins.GrbTransformationPlugin import TransformEditorTool from appEditors.grb_plugins.GrbPadArrayPlugin import GrbPadArrayEditorTool +from appEditors.grb_plugins.GrbTrackPlugin import GrbTrackEditorTool from appEditors.grb_plugins.GrbSimplificationPlugin import SimplificationTool from appEditors.grb_plugins.GrbCopyPlugin import CopyEditorTool @@ -342,7 +343,6 @@ class PadEditorGrb(ShapeToolEditorGrb): # VisPy keys if self.pad_tool.length == self.draw_app.last_length: self.pad_tool.length = str(key.name) - self.new_segment = False else: self.pad_tool.length = str(self.pad_tool.length) + str(key.name) except AttributeError: @@ -1476,12 +1476,13 @@ class TrackEditorGrb(ShapeToolEditorGrb): DrawTool.__init__(self, draw_app) self.name = 'track' self.draw_app = draw_app + self.app = self.draw_app.app self.dont_execute = False self.steps_per_circle = self.draw_app.app.options["gerber_circle_steps"] try: - size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) + self.size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) except KeyError: self.draw_app.app.inform.emit('[ERROR_NOTCL] %s' % _("You need to preselect a aperture in the Aperture Table that has a size.")) @@ -1495,7 +1496,7 @@ class TrackEditorGrb(ShapeToolEditorGrb): self.draw_app.select_tool('select') return - self.buf_val = (size_ap / 2) if size_ap > 0 else 0.0000001 + self.buf_val = (self.size_ap / 2) if self.size_ap > 0 else 0.0000001 self.gridx_size = float(self.draw_app.app.ui.grid_gap_x_entry.get_value()) self.gridy_size = float(self.draw_app.app.ui.grid_gap_y_entry.get_value()) @@ -1514,17 +1515,54 @@ class TrackEditorGrb(ShapeToolEditorGrb): '/aero_path%s.png' % self.draw_app.bend_mode)) QtGui.QGuiApplication.setOverrideCursor(self.cursor) + # ############################################################################################################# + # Plugin UI + # ############################################################################################################# + self.track_tool = GrbTrackEditorTool(self.app, self.draw_app, plugin_name=_("Track")) + self.ui = self.track_tool.ui + self.track_tool.run() + + if self.app.use_3d_engine: + self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + self.track_tool.length = self.draw_app.last_length + if not self.draw_app.snap_x: + self.draw_app.snap_x = 0.0 + if not self.draw_app.snap_y: + self.draw_app.snap_y = 0.0 + + self.app.ui.notebook.setTabText(2, _("Track")) + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + self.set_plugin_ui() + + # Signals + try: + self.ui.add_btn.clicked.disconnect() + except (AttributeError, TypeError): + pass + self.ui.add_btn.clicked.connect(self.on_add_track) + + # self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + self.draw_app.app.inform.emit(_('Track Mode 1: 45 degrees ...')) + def set_plugin_ui(self): + self.ui.dia_entry.set_value(self.size_ap) + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) + def click(self, point): self.draw_app.in_action = True - self.current_point = point if not self.points or point != self.points[-1]: self.points.append(point) + self.draw_app.last_length = self.track_tool.length + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) else: return @@ -1650,9 +1688,68 @@ class TrackEditorGrb(ShapeToolEditorGrb): self.draw_app.in_action = False self.complete = True - self.draw_app.app.jump_signal.disconnect() + + try: + self.draw_app.app.jump_signal.disconnect() + except (TypeError, AttributeError): + pass + self.draw_app.app.inform.emit('[success] %s' % _("Done.")) + def draw_cursor_data(self, pos=None, delete=False): + if pos is None: + pos = self.draw_app.snap_x, self.draw_app.snap_y + + if delete: + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + return + + # font size + qsettings = QtCore.QSettings("Open Source", "FlatCAM") + if qsettings.contains("hud_font_size"): + fsize = qsettings.value('hud_font_size', type=int) + else: + fsize = 8 + + if not self.points: + old_x = self.draw_app.snap_x + old_y = self.draw_app.snap_y + else: + old_x = self.points[-1][0] + old_y = self.points[-1][1] + + x = pos[0] + y = pos[1] + try: + length = abs(np.sqrt((x - old_x) ** 2 + (y - old_y) ** 2)) + except IndexError: + length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals) + + x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0' + y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0' + length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0' + + units = self.draw_app.app.app_units.lower() + l1_txt = 'X: %s [%s]' % (x_dec, units) + l2_txt = 'Y: %s [%s]' % (y_dec, units) + l3_txt = 'L: %s [%s]' % (length_dec, units) + cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt) + + if self.draw_app.app.use_3d_engine: + new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y)) + x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1])) + + # text + self.draw_app.app.plotcanvas.text_cursor.font_size = fsize + self.draw_app.app.plotcanvas.text_cursor.text = cursor_text + self.draw_app.app.plotcanvas.text_cursor.pos = x, y + self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top' + + if self.draw_app.app.plotcanvas.text_cursor.parent is None: + self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene + def on_key(self, key): if key == 'Backspace' or key == QtCore.Qt.Key.Key_Backspace: if len(self.points) > 0: @@ -1749,6 +1846,61 @@ class TrackEditorGrb(ShapeToolEditorGrb): return msg + if key in [str(i) for i in range(10)] + ['.', ',', '+', '-', '/', '*'] or \ + key in [QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_1, QtCore.Qt.Key.Key_2, + QtCore.Qt.Key.Key_3, QtCore.Qt.Key.Key_4, QtCore.Qt.Key.Key_5, QtCore.Qt.Key.Key_6, + QtCore.Qt.Key.Key_7, QtCore.Qt.Key.Key_8, QtCore.Qt.Key.Key_9, QtCore.Qt.Key.Key_Minus, + QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, + QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: + try: + # VisPy keys + if self.track_tool.length == self.draw_app.last_length: + self.track_tool.length = str(key.name) + else: + self.track_tool.length = str(self.track_tool.length) + str(key.name) + except AttributeError: + # Qt keys + if self.track_tool.length == self.draw_app.last_length: + self.track_tool.length = chr(key) + else: + self.track_tool.length = str(self.track_tool.length) + chr(key) + + if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter: + if self.track_tool.length != 0: + target_length = self.track_tool.length + if target_length is None: + self.track_tool.length = 0.0 + return _("Failed.") + + first_pt = self.ui.x_entry.get_value(), self.ui.y_entry.get_value() + last_pt = self.draw_app.snap_x, self.draw_app.snap_y + + seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) + if seg_length == 0.0: + return + try: + new_x = first_pt[0] + (last_pt[0] - first_pt[0]) / seg_length * target_length + new_y = first_pt[1] + (last_pt[1] - first_pt[1]) / seg_length * target_length + except ZeroDivisionError as err: + self.clean_up() + return '[ERROR_NOTCL] %s %s' % (_("Failed."), str(err).capitalize()) + + if first_pt != (new_x, new_y): + self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) + self.add_track(track_pos=(new_x, new_y)) + + def add_track(self, track_pos): + self.draw_app.last_length = self.track_tool.length + self.click(track_pos) + self.ui.x_entry.set_value(track_pos[0]) + self.ui.y_entry.set_value(track_pos[1]) + self.draw_app.clicked_pos = track_pos + + def on_add_track(self): + x = self.ui.x_entry.get_value() + y = self.ui.y_entry.get_value() + self.add_track(track_pos=(x, y)) + def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.apertures_table.clearSelection() @@ -3681,7 +3833,7 @@ class AppGerberEditor(QtCore.QObject): self.ui.delaperture_btn.clicked.connect(lambda: self.on_aperture_delete()) self.ui.apertures_table.cellPressed.connect(self.on_row_selected) - self.ui.apertures_table.selectionModel().selectionChanged.connect(self.on_table_selection) #noqa + self.ui.apertures_table.selectionModel().selectionChanged.connect(self.on_table_selection) # noqa self.ui.simplification_btn.clicked.connect(self.on_simplification_click) self.ui.exit_editor_button.clicked.connect(lambda: self.app.editor2object()) @@ -5720,7 +5872,7 @@ class AppGerberEditor(QtCore.QObject): self.update_utility_geometry(data=(x, y)) if self.active_tool.name in [ - 'pad', 'array' + 'pad', 'array', 'track' ]: try: self.active_tool.draw_cursor_data(pos=self.app.mouse_pos) @@ -7030,22 +7182,22 @@ class TransformEditorTool(AppTool): self.ref_combo.currentIndexChanged.connect(self.on_reference_changed) self.point_button.clicked.connect(self.on_add_coords) - self.rotate_button.clicked.connect(self.on_rotate) + self.rotate_button.clicked.connect(lambda: self.on_rotate()) - self.skewx_button.clicked.connect(self.on_skewx) - self.skewy_button.clicked.connect(self.on_skewy) + self.skewx_button.clicked.connect(lambda: self.on_skewx()) + self.skewy_button.clicked.connect(lambda: self.on_skewy) - self.scalex_button.clicked.connect(self.on_scalex) - self.scaley_button.clicked.connect(self.on_scaley) + self.scalex_button.clicked.connect(lambda: self.on_scalex()) + self.scaley_button.clicked.connect(lambda: self.on_scaley()) - self.offx_button.clicked.connect(self.on_offx) - self.offy_button.clicked.connect(self.on_offy) + self.offx_button.clicked.connect(lambda: self.on_offx()) + self.offy_button.clicked.connect(lambda: self.on_offy()) - self.flipx_button.clicked.connect(self.on_flipx) - self.flipy_button.clicked.connect(self.on_flipy) + self.flipx_button.clicked.connect(lambda: self.on_flipx()) + self.flipy_button.clicked.connect(lambda: 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.buffer_button.clicked.connect(lambda: self.on_buffer_by_distance()) + self.buffer_factor_button.clicked.connect(lambda: self.on_buffer_by_factor()) # self.rotate_entry.editingFinished.connect(self.on_rotate) # self.skewx_entry.editingFinished.connect(self.on_skewx) @@ -7206,7 +7358,7 @@ class TransformEditorTool(AppTool): val = self.app.clipboard.text() self.point_entry.set_value(val) - def on_rotate(self, sig=None, val=None, ref=None): + def on_rotate(self, val=None, ref=None): value = float(self.rotate_entry.get_value()) if val is None else val if value == 0: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Rotate transformation can not be done for a value of 0.")) @@ -7216,21 +7368,21 @@ class TransformEditorTool(AppTool): return self.app.worker_task.emit({'fcn': self.on_rotate_action, 'params': [value, point]}) - def on_flipx(self, signal=None, ref=None): + def on_flipx(self, ref=None): axis = 'Y' point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) if point == 'fail': return self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]}) - def on_flipy(self, signal=None, ref=None): + def on_flipy(self, ref=None): axis = 'X' point = self.on_calculate_reference() if ref is None else self.on_calculate_reference(ref_index=ref) if point == 'fail': return self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis, point]}) - def on_skewx(self, signal=None, val=None, ref=None): + def on_skewx(self, val=None, ref=None): xvalue = float(self.skewx_entry.get_value()) if val is None else val if xvalue == 0: @@ -7248,7 +7400,7 @@ class TransformEditorTool(AppTool): self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]}) - def on_skewy(self, signal=None, val=None, ref=None): + def on_skewy(self, val=None, ref=None): xvalue = 0 yvalue = float(self.skewy_entry.get_value()) if val is None else val @@ -7262,7 +7414,7 @@ class TransformEditorTool(AppTool): self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, xvalue, yvalue, point]}) - def on_scalex(self, signal=None, val=None, ref=None): + def on_scalex(self, val=None, ref=None): xvalue = float(self.scalex_entry.get_value()) if val is None else val if xvalue == 0 or xvalue == 1: @@ -7282,7 +7434,7 @@ class TransformEditorTool(AppTool): self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]}) - def on_scaley(self, signal=None, val=None, ref=None): + def on_scaley(self, val=None, ref=None): xvalue = 1 yvalue = float(self.scaley_entry.get_value()) if val is None else val @@ -7298,7 +7450,7 @@ class TransformEditorTool(AppTool): self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]}) - def on_offx(self, signal=None, val=None): + def on_offx(self, val=None): value = float(self.offx_entry.get_value()) if val is None else val if value == 0: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0.")) @@ -7307,7 +7459,7 @@ class TransformEditorTool(AppTool): self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]}) - def on_offy(self, signal=None, val=None): + def on_offy(self, val=None): value = float(self.offy_entry.get_value()) if val is None else val if value == 0: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Offset transformation can not be done for a value of 0.")) diff --git a/appEditors/grb_plugins/GrbTracePlugin.py b/appEditors/grb_plugins/GrbTracePlugin.py deleted file mode 100644 index b9c92c3f..00000000 --- a/appEditors/grb_plugins/GrbTracePlugin.py +++ /dev/null @@ -1,149 +0,0 @@ - -from appTool import * - -fcTranslate.apply_language('strings') -if '_' not in builtins.__dict__: - _ = gettext.gettext - - -class TraceEditorTool(AppToolEditor): - """ - Simple input for buffer distance. - """ - - def __init__(self, app, draw_app, plugin_name): - AppToolEditor.__init__(self, app) - - self.draw_app = draw_app - self.decimals = app.decimals - self.plugin_name = plugin_name - - self.ui = PathEditorUI(layout=self.layout, path_class=self, plugin_name=plugin_name) - - self.connect_signals_at_init() - self.set_tool_ui() - - def connect_signals_at_init(self): - # Signals - self.ui.clear_btn.clicked.connect(self.on_clear) - - def disconnect_signals(self): - # Signals - try: - self.ui.clear_btn.clicked.disconnect() - except (TypeError, AttributeError): - pass - - def run(self): - self.app.defaults.report_usage("Geo Editor ToolPath()") - super().run() - - # if the splitter us hidden, display it - if self.app.ui.splitter.sizes()[0] == 0: - self.app.ui.splitter.setSizes([1, 1]) - - # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same - found_idx = None - for idx in range(self.app.ui.notebook.count()): - if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": - found_idx = idx - break - # show the Tab - if not found_idx: - try: - self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) - except RuntimeError: - self.app.ui.plugin_tab = QtWidgets.QWidget() - self.app.ui.plugin_tab.setObjectName("plugin_tab") - self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) - self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) - - self.app.ui.plugin_scroll_area = VerticalScrollArea() - self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) - self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) - - # focus on Tool Tab - self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) - - # self.app.ui.notebook.callback_on_close = self.on_tab_close - - self.app.ui.notebook.setTabText(2, self.plugin_name) - - def set_tool_ui(self): - # Init appGUI - self.length = 0.0 - - def on_tab_close(self): - self.disconnect_signals() - self.hide_tool() - # self.app.ui.notebook.callback_on_close = lambda: None - - def on_clear(self): - self.set_tool_ui() - - @property - def length(self): - return self.ui.project_line_entry.get_value() - - @length.setter - def length(self, val): - self.ui.project_line_entry.set_value(val) - - def hide_tool(self): - self.ui.path_tool_frame.hide() - self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) - if self.draw_app.active_tool.name != 'select': - self.draw_app.select_tool("select") - - -class PathEditorUI: - - def __init__(self, layout, path_class, plugin_name): - self.pluginName = plugin_name - self.path_class = path_class - self.decimals = self.path_class.app.decimals - self.layout = layout - - # Title - title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) - self.layout.addWidget(title_label) - - # this way I can hide/show the frame - self.path_tool_frame = QtWidgets.QFrame() - self.path_tool_frame.setContentsMargins(0, 0, 0, 0) - self.layout.addWidget(self.path_tool_frame) - self.path_tool_box = QtWidgets.QVBoxLayout() - self.path_tool_box.setContentsMargins(0, 0, 0, 0) - self.path_tool_frame.setLayout(self.path_tool_box) - - # Grid Layout - grid_path = GLay(v_spacing=5, h_spacing=3) - self.path_tool_box.addLayout(grid_path) - - # Project distance - self.project_line_lbl = FCLabel('%s:' % _("Projection")) - self.project_line_lbl.setToolTip( - _("Length of the current segment/move.") - ) - self.project_line_entry = NumericalEvalEntry(border_color='#0069A9') - grid_path.addWidget(self.project_line_lbl, 0, 0) - grid_path.addWidget(self.project_line_entry, 0, 1) - - # self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner")) - # self.buffer_corner_lbl.setToolTip( - # _("There are 3 types of corners:\n" - # " - 'Round': the corner is rounded for exterior buffer.\n" - # " - 'Square': the corner is met in a sharp angle for exterior buffer.\n" - # " - 'Beveled': the corner is a line that directly connects the features meeting in the corner") - # ) - # self.buffer_corner_cb = FCComboBox() - # self.buffer_corner_cb.addItem(_("Round")) - # self.buffer_corner_cb.addItem(_("Square")) - # self.buffer_corner_cb.addItem(_("Beveled")) - # grid_path.addWidget(self.buffer_corner_lbl, 2, 0) - # grid_path.addWidget(self.buffer_corner_cb, 2, 1) - - self.clear_btn = FCButton(_("Clear")) - grid_path.addWidget(self.clear_btn, 4, 0, 1, 2) - - self.layout.addStretch(1) diff --git a/appEditors/grb_plugins/GrbTrackPlugin.py b/appEditors/grb_plugins/GrbTrackPlugin.py new file mode 100644 index 00000000..253354e2 --- /dev/null +++ b/appEditors/grb_plugins/GrbTrackPlugin.py @@ -0,0 +1,196 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class GrbTrackEditorTool(AppToolEditor): + """ + Simple input for buffer distance. + """ + + def __init__(self, app, draw_app, plugin_name): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.plugin_name = plugin_name + + self.ui = GrbTrackEditorUI(layout=self.layout, track_class=self, plugin_name=plugin_name) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.clear_btn.clicked.connect(self.on_clear) + + def disconnect_signals(self): + # Signals + try: + self.ui.clear_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + def run(self): + self.app.defaults.report_usage("Geo Editor ToolPath()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.length = 0.0 + + def on_tab_close(self): + self.disconnect_signals() + self.hide_tool() + # self.app.ui.notebook.callback_on_close = lambda: None + + def on_clear(self): + self.set_tool_ui() + + @property + def length(self): + return self.ui.project_line_entry.get_value() + + @length.setter + def length(self, val): + self.ui.project_line_entry.set_value(val) + + def hide_tool(self): + self.ui.drill_tool_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + if self.draw_app.active_tool.name != 'select': + self.draw_app.select_tool("select") + + +class GrbTrackEditorUI: + + def __init__(self, layout, track_class, plugin_name): + self.pluginName = plugin_name + self.ed_class = track_class + self.decimals = self.ed_class.app.decimals + self.layout = layout + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.drill_tool_frame = QtWidgets.QFrame() + self.drill_tool_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.drill_tool_frame) + self.editor_vbox = QtWidgets.QVBoxLayout() + self.editor_vbox.setContentsMargins(0, 0, 0, 0) + self.drill_tool_frame.setLayout(self.editor_vbox) + + # Position + self.tool_lbl = FCLabel('%s' % _("Diameter"), bold=True, color='blue') + self.editor_vbox.addWidget(self.tool_lbl) + # ############################################################################################################# + # Diameter Frame + # ############################################################################################################# + dia_frame = FCFrame() + self.editor_vbox.addWidget(dia_frame) + + dia_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + dia_frame.setLayout(dia_grid) + + # Dia Value + self.dia_lbl = FCLabel('%s:' % _("Value")) + self.dia_entry = NumericalEvalEntry(border_color='#0069A9') + self.dia_entry.setDisabled(True) + self.dia_unit = FCLabel('%s' % 'mm') + + dia_grid.addWidget(self.dia_lbl, 0, 0) + dia_grid.addWidget(self.dia_entry, 0, 1) + dia_grid.addWidget(self.dia_unit, 0, 2) + + # Position + self.pos_lbl = FCLabel('%s' % _("Position"), bold=True, color='red') + self.editor_vbox.addWidget(self.pos_lbl) + # ############################################################################################################# + # Position Frame + # ############################################################################################################# + pos_frame = FCFrame() + self.editor_vbox.addWidget(pos_frame) + + pos_grid = GLay(v_spacing=5, h_spacing=3) + pos_frame.setLayout(pos_grid) + + # X Pos + self.x_lbl = FCLabel('%s:' % _("X")) + self.x_entry = FCDoubleSpinner() + self.x_entry.set_precision(self.decimals) + self.x_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.x_lbl, 2, 0) + pos_grid.addWidget(self.x_entry, 2, 1) + + # Y Pos + self.y_lbl = FCLabel('%s:' % _("Y")) + self.y_entry = FCDoubleSpinner() + self.y_entry.set_precision(self.decimals) + self.y_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.y_lbl, 4, 0) + pos_grid.addWidget(self.y_entry, 4, 1) + + # ############################################################################################################# + # Projection Frame + # ############################################################################################################# + pro_frame = FCFrame() + self.editor_vbox.addWidget(pro_frame) + + pro_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + pro_frame.setLayout(pro_grid) + + # Project distance + self.project_line_lbl = FCLabel('%s:' % _("Projection")) + self.project_line_lbl.setToolTip( + _("Length of the current segment/move.") + ) + self.project_line_entry = NumericalEvalEntry(border_color='#0069A9') + pro_grid.addWidget(self.project_line_lbl, 0, 0) + pro_grid.addWidget(self.project_line_entry, 0, 1) + + self.clear_btn = QtWidgets.QToolButton() + self.clear_btn.setIcon(QtGui.QIcon(self.ed_class.app.resource_location + '/trash32.png')) + pro_grid.addWidget(self.clear_btn, 0, 2) + + self.add_btn = FCButton(_("Add")) + self.add_btn.setIcon(QtGui.QIcon(self.ed_class.app.resource_location + '/plus32.png')) + self.editor_vbox.addWidget(self.add_btn) + + GLay.set_common_column_size([dia_grid, pos_grid, pro_grid], 0) + self.layout.addStretch(1) From 95066e352bac84d242832960e6f5ea62b75611fc Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 22 May 2022 14:01:21 +0300 Subject: [PATCH 08/55] - in Gerber Editor upgraded the Simplification sub-tool GUI --- CHANGELOG.md | 1 + appEditors/AppGerberEditor.py | 239 +++++++++--------- .../grb_plugins/GrbSimplificationPlugin.py | 97 ++----- 3 files changed, 146 insertions(+), 191 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3f2367..a3184b5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG for FlatCAM Evo beta - in Gerber Editor upgraded the PadAdd GUI - in Gerber Editor upgraded the PadArray GUI - in Gerber Editor upgraded the Track sub-tool GUI +- in Gerber Editor upgraded the Simplification sub-tool GUI 21.05.2022 diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 8dc14270..5910918f 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -2411,31 +2411,139 @@ class SimplifyEditorGrb(ShapeToolEditorGrb): self.draw_app = draw_app self.app = draw_app.app - self.draw_app.app.inform.emit(_("Buffer the selected apertures ...")) + self.draw_app.app.inform.emit(_("Simplify the selected apertures ...")) self.origin = (0, 0) + self.simp_tool = SimplificationTool(self.app, self.draw_app) + self.simp_tool.run() + self.ui = self.simp_tool.ui + + self.app.ui.notebook.setTabText(2, _("Simplification")) if self.draw_app.app.ui.splitter.sizes()[0] == 0: self.draw_app.app.ui.splitter.setSizes([1, 1]) - self.activate_buffer() - - def activate_buffer(self): - self.draw_app.hide_tool('all') + self.activate() + def activate(self): try: - self.draw_app.ui.buffer_button.clicked.disconnect() + self.draw_app.ui.simplification_btn.clicked.disconnect() except (TypeError, AttributeError): pass - self.draw_app.ui.buffer_button.clicked.connect(self.on_buffer_click) + self.ui.simplification_btn.clicked.connect(self.on_simplify_click) - def deactivate_buffer(self): - self.draw_app.ui.buffer_button.clicked.disconnect() + def deactivate(self): + self.draw_app.ui.simplification_btn.clicked.disconnect() self.complete = True self.draw_app.select_tool("select") self.draw_app.hide_tool(self.name) - def on_buffer_click(self): - self.draw_app.on_buffer() - self.deactivate_buffer() + def on_simplify_click(self): + self.on_simplification_click() + # self.deactivate() + + def set_origin(self, origin): + self.origin = origin + + def click(self, point): + key_modifier = QtWidgets.QApplication.keyboardModifiers() + + if key_modifier == QtCore.Qt.KeyboardModifier.ShiftModifier: + mod_key = 'Shift' + elif key_modifier == QtCore.Qt.KeyboardModifier.ControlModifier: + mod_key = 'Control' + else: + mod_key = None + + if mod_key == self.draw_app.app.options["global_mselect_key"]: + pass + else: + self.draw_app.selected = [] + + def click_release(self, point): + self.draw_app.ui.apertures_table.clearSelection() + key_modifier = QtWidgets.QApplication.keyboardModifiers() + + if key_modifier == QtCore.Qt.KeyboardModifier.ShiftModifier: + mod_key = 'Shift' + elif key_modifier == QtCore.Qt.KeyboardModifier.ControlModifier: + mod_key = 'Control' + else: + mod_key = None + + if mod_key != self.draw_app.app.options["global_mselect_key"]: + self.draw_app.selected.clear() + + for storage_val in self.draw_app.storage_dict.values(): + for shape_stored in storage_val['geometry']: + if 'solid' in shape_stored.geo: + geometric_data = shape_stored.geo['solid'] + if Point(point).intersects(geometric_data): + if shape_stored in self.draw_app.selected: + self.draw_app.selected.remove(shape_stored) + else: + self.draw_app.selected.append(shape_stored) + + self.draw_app.plot_all() + self.simp_tool.calculate_coords_vertex() + + def on_simplification_click(self): + self.app.log.debug("SimplifyEditorGrb.on_simplification_click()") + + selected_shapes = [] + selected_shapes_geos = [] + tol = self.ui.geo_tol_entry.get_value() + + # init the coordinates text field and vertex points field + self.ui.geo_coords_entry.set_value('') + self.ui.geo_vertex_entry.set_value(0) + + def task_job(): + with self.app.proc_container.new('%s...' % _("Simplify")): + for obj_shape in self.draw_app.selected: + try: + selected_shapes.append(obj_shape) + + new_geo = { + 'apid': '', + 'geo': {} + } + + # find the aperture where the shape is stored + current_apid = None + for apid in self.draw_app.storage_dict: + if obj_shape in self.draw_app.storage_dict[apid]['geometry']: + current_apid = apid + break + + if current_apid is None: + current_apid = self.draw_app.last_aperture_selected + + new_geo['apid'] = deepcopy(current_apid) + + if 'solid' in obj_shape.geo: + new_geo['geo']['solid'] = obj_shape.geo['solid'].simplify(tolerance=tol) + if 'follow' in obj_shape.geo: + new_geo['geo']['follow'] = obj_shape.geo['follow'].simplify(tolerance=tol) + if 'clear' in obj_shape.geo: + new_geo['geo']['clear'] = obj_shape.geo['clear'].simplify(tolerance=tol) + + selected_shapes_geos.append(deepcopy(new_geo)) + except ValueError: + pass + + for shape in selected_shapes: + self.draw_app.delete_shape(geo_el=shape) + + for geo in selected_shapes_geos: + stora = self.draw_app.storage_dict[geo['apid']]['geometry'] + geo_el = geo['geo'] + self.draw_app.add_gerber_shape(DrawToolShape(geo_el), storage=stora) + + is_sel_all = self.draw_app.on_table_selection() + if is_sel_all: + return + self.draw_app.plot_all() + + self.app.worker_task.emit({'fcn': task_job, 'params': []}) def clean_up(self): self.draw_app.selected = [] @@ -3835,7 +3943,6 @@ class AppGerberEditor(QtCore.QObject): self.ui.apertures_table.cellPressed.connect(self.on_row_selected) self.ui.apertures_table.selectionModel().selectionChanged.connect(self.on_table_selection) # noqa - self.ui.simplification_btn.clicked.connect(self.on_simplification_click) self.ui.exit_editor_button.clicked.connect(lambda: self.app.editor2object()) self.conversion_factor = 1 @@ -3850,15 +3957,9 @@ class AppGerberEditor(QtCore.QObject): # ############################### TOOLS TABLE context menu #################################################### # ############################################################################################################# self.ui.apertures_table.setupContextMenu() - # self.ui.apertures_table.addContextMenu( - # _("Add"), self.on_aperture_add, - # icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")) self.ui.apertures_table.addContextMenu( _("Delete"), lambda: self.on_aperture_delete(), icon=QtGui.QIcon(self.app.resource_location + "/trash16.png")) - # self.ui.apertures_table.addContextMenu( - # _("Simplify"), self.on_simplification_click, - # icon=QtGui.QIcon(self.app.resource_location + "/simplify32.png")) self.app.log.debug("Initialization of the Gerber Editor is finished ...") def make_callback(self, the_tool): @@ -3942,7 +4043,6 @@ class AppGerberEditor(QtCore.QObject): self.ui.geo_coords_entry.setText('') self.ui.geo_vertex_entry.set_value(0.0) - self.ui.geo_tol_entry.set_value(0.01) self.ui.geo_zoom.set_value(False) # Show/Hide Advanced Options @@ -4447,66 +4547,6 @@ class AppGerberEditor(QtCore.QObject): self.ui.apertures_table.itemChanged.connect(self.on_tool_edit) # self.ui.apertures_table.cellPressed.connect(self.on_row_selected) - def on_simplification_click(self): - self.app.log.debug("AppGrbEditor.on_simplification_click()") - - selected_shapes = [] - selected_shapes_geos = [] - tol = self.ui.geo_tol_entry.get_value() - - # init the coordinates text field and vertex points field - self.ui.geo_coords_entry.set_value('') - self.ui.geo_vertex_entry.set_value(0) - - def task_job(): - with self.app.proc_container.new('%s...' % _("Simplify")): - for obj_shape in self.selected: - try: - selected_shapes.append(obj_shape) - - new_geo = { - 'apid': '', - 'geo': {} - } - - # find the aperture where the shape is stored - current_apid = None - for apid in self.storage_dict: - if obj_shape in self.storage_dict[apid]['geometry']: - current_apid = apid - break - - if current_apid is None: - current_apid = self.last_aperture_selected - - new_geo['apid'] = deepcopy(current_apid) - - if 'solid' in obj_shape.geo: - new_geo['geo']['solid'] = obj_shape.geo['solid'].simplify(tolerance=tol) - if 'follow' in obj_shape.geo: - new_geo['geo']['follow'] = obj_shape.geo['follow'].simplify(tolerance=tol) - if 'clear' in obj_shape.geo: - new_geo['geo']['clear'] = obj_shape.geo['clear'].simplify(tolerance=tol) - - selected_shapes_geos.append(deepcopy(new_geo)) - except ValueError: - pass - - for shape in selected_shapes: - self.delete_shape(geo_el=shape) - - for geo in selected_shapes_geos: - stora = self.storage_dict[geo['apid']]['geometry'] - geo_el = geo['geo'] - self.add_gerber_shape(DrawToolShape(geo_el), storage=stora) - - is_sel_all = self.on_table_selection() - if is_sel_all: - return - self.plot_all() - - self.app.worker_task.emit({'fcn': task_job, 'params': []}) - def update_ui(self): is_zoom_selected = self.ui.geo_zoom.get_value() @@ -5605,7 +5645,7 @@ class AppGerberEditor(QtCore.QObject): modifier_to_use = Qt.KeyboardModifier.ShiftModifier # if modifier key is pressed then we add to the selected list the current shape but if it's already - # in the selected list, we removed it. Therefore first click selects, second deselects. + # in the selected list, we removed it. Therefore, first click selects, second deselects. if key_modifier == modifier_to_use: self.select_tool(self.active_tool.name) else: @@ -5698,8 +5738,10 @@ class AppGerberEditor(QtCore.QObject): if self.app.selection_type is not None: self.draw_selection_area_handler(self.pos, pos, self.app.selection_type) self.app.selection_type = None + if isinstance(self.active_tool, (SimplifyEditorGrb)): + self.active_tool.simp_tool.calculate_coords_vertex() - elif isinstance(self.active_tool, SelectEditorGrb): + elif isinstance(self.active_tool, (SelectEditorGrb, SimplifyEditorGrb)): self.active_tool.click_release((self.pos[0], self.pos[1])) # # if there are selected objects then plot them @@ -6235,7 +6277,7 @@ class AppGerberEditor(QtCore.QObject): self.select_tool('semidisc') def on_simplification(self): - pass + self.select_tool('simplify') def on_scale(self): scale_factor = 1.0 @@ -6639,37 +6681,6 @@ class AppGerberEditorUI: self.shape_grid.addWidget(vertex_lbl, 8, 0) self.shape_grid.addWidget(self.geo_vertex_entry, 8, 1, 1, 2) - # Simplification Title - simplif_lbl = FCLabel('%s' % _("Simplification"), bold=True) - simplif_lbl.setToolTip( - _("Simplify a geometry by reducing its vertex points number.") - ) - self.custom_box.addWidget(simplif_lbl) - - # Simplification Tolerance - simplification_tol_lbl = FCLabel('%s:' % _("Tolerance")) - simplification_tol_lbl.setToolTip( - _("All points in the simplified object will be\n" - "within the tolerance distance of the original geometry.") - ) - self.geo_tol_entry = FCDoubleSpinner() - self.geo_tol_entry.set_precision(self.decimals) - self.geo_tol_entry.setSingleStep(10 ** -self.decimals) - self.geo_tol_entry.set_range(0.0000, 10000.0000) - self.geo_tol_entry.set_value(10 ** -self.decimals) - - self.custom_box.addWidget(simplification_tol_lbl) - self.custom_box.addWidget(self.geo_tol_entry) - - # Simplification button - self.simplification_btn = FCButton(_("Simplify"), bold=True) - self.simplification_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/simplify32.png')) - self.simplification_btn.setToolTip( - _("Simplify a geometry element by reducing its vertex points number.") - ) - - self.custom_box.addWidget(self.simplification_btn) - # ############################################################################################################# # ########################################### SCALE TOOL ###################################################### # ############################################################################################################# diff --git a/appEditors/grb_plugins/GrbSimplificationPlugin.py b/appEditors/grb_plugins/GrbSimplificationPlugin.py index 38c064f6..4cbc152c 100644 --- a/appEditors/grb_plugins/GrbSimplificationPlugin.py +++ b/appEditors/grb_plugins/GrbSimplificationPlugin.py @@ -28,8 +28,7 @@ class SimplificationTool(AppToolEditor): def connect_signals_at_init(self): # Signals - self.ui.simplification_btn.clicked.connect(self.on_simplification_click) - self.update_ui.connect(self.on_update_ui) + self.update_ui.connect(self.on_update_ui) # noqa def run(self): self.app.defaults.report_usage("Geo Editor SimplificationTool()") @@ -69,90 +68,34 @@ class SimplificationTool(AppToolEditor): def set_tool_ui(self): # Init appGUI self.ui.geo_tol_entry.set_value(0.01 if self.draw_app.units == 'MM' else 0.0004) - - selected_shapes_geos = [] - selected_tree_items = self.draw_app.ui.tw.selectedItems() - for sel in selected_tree_items: - for obj_shape in self.draw_app.storage.get_objects(): - try: - if id(obj_shape) == int(sel.text(0)): - selected_shapes_geos.append(obj_shape.geo) - except ValueError: - pass - if selected_shapes_geos: + if self.draw_app.selected: # those are displayed by triggering the signal self.update_ui - self.calculate_coords_vertex(selected_shapes_geos[-1]) + self.calculate_coords_vertex() def on_tab_close(self): self.draw_app.select_tool("select") self.app.ui.notebook.callback_on_close = lambda: None - def on_simplification_click(self): - self.app.log.debug("FCSimplification.on_simplification_click()") + def calculate_coords_vertex(self): + vertex_nr = 0 + coords = [] + for sha in self.draw_app.selected: + sha_geo = sha.geo + if 'solid' in sha_geo: + sha_geo_solid = sha_geo['solid'] + if sha_geo_solid.geom_type == 'Polygon': + sha_geo_solid_coords = list(sha_geo_solid.exterior.coords) + elif sha_geo_solid.geom_type in ['LinearRing', 'LineString']: + sha_geo_solid_coords = list(sha_geo_solid.coords) + else: + sha_geo_solid_coords = [] + coords += sha_geo_solid_coords - selected_shapes_geos = [] - tol = self.ui.geo_tol_entry.get_value() + vertex_nr += len(sha_geo_solid_coords) - def task_job(self): - with self.app.proc_container.new('%s...' % _("Simplify")): - selected_shapes = self.draw_app.get_selected() - self.draw_app.interdict_selection = True - for obj_shape in selected_shapes: - selected_shapes_geos.append(obj_shape.geo.simplify(tolerance=tol)) + self.ui.geo_vertex_entry.set_value(vertex_nr) - if not selected_shapes: - self.app.inform.emit('%s' % _("Failed.")) - return - - for shape in selected_shapes: - self.draw_app.delete_shape(shape=shape) - - for geo in selected_shapes_geos: - self.draw_app.add_shape(geo, build_ui=False) - - self.draw_app.selected = [] - - last_sel_geo = selected_shapes_geos[-1] - self.calculate_coords_vertex(last_sel_geo) - - self.app.inform.emit('%s' % _("Done.")) - - self.draw_app.plot_all() - self.draw_app.interdict_selection = False - self.draw_app.build_ui_sig.emit() - - self.app.worker_task.emit({'fcn': task_job, 'params': [self]}) - - def calculate_coords_vertex(self, last_sel_geo): - if last_sel_geo: - if last_sel_geo.geom_type == 'MultiLineString': - coords = '' - vertex_nr = 0 - for idx, line in enumerate(last_sel_geo.geoms): - line_coords = list(line.coords) - vertex_nr += len(line_coords) - coords += 'Line %s\n' % str(idx) - coords += str(line_coords) + '\n' - elif last_sel_geo.geom_type == 'MultiPolygon': - coords = '' - vertex_nr = 0 - for idx, poly in enumerate(last_sel_geo.geoms): - poly_coords = list(poly.exterior.coords) + [list(i.coords) for i in poly.interiors] - vertex_nr += len(poly_coords) - - coords += 'Polygon %s\n' % str(idx) - coords += str(poly_coords) + '\n' - elif last_sel_geo.geom_type in ['LinearRing', 'LineString']: - coords = list(last_sel_geo.coords) - vertex_nr = len(coords) - elif last_sel_geo.geom_type == 'Polygon': - coords = list(last_sel_geo.exterior.coords) - vertex_nr = len(coords) - else: - coords = 'None' - vertex_nr = 0 - - self.update_ui.emit(coords, vertex_nr) + self.update_ui.emit(coords, vertex_nr) # noqa def on_update_ui(self, coords, vertex_nr): self.ui.geo_coords_entry.set_value(str(coords)) From 4c22e52b08070380683083bfa09efb8619e0bbd0 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 26 May 2022 00:20:43 +0300 Subject: [PATCH 09/55] - upgraded the UI of Region sub-tool in the Gerber Editor --- CHANGELOG.md | 4 + appEditors/AppGerberEditor.py | 140 ++++++++++++-- appEditors/grb_plugins/GrberRegionPlugin.py | 192 ++++++++++++++++++++ 3 files changed, 319 insertions(+), 17 deletions(-) create mode 100644 appEditors/grb_plugins/GrberRegionPlugin.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a3184b5f..e8641eba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +26.05.2022 + +- upgraded the UI of Region sub-tool in the Gerber Editor + 22.05.2022 - in Gerber Editor upgraded the PadAdd GUI diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 5910918f..08553e9a 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -19,6 +19,7 @@ from appEditors.grb_plugins.GrbPadArrayPlugin import GrbPadArrayEditorTool from appEditors.grb_plugins.GrbTrackPlugin import GrbTrackEditorTool from appEditors.grb_plugins.GrbSimplificationPlugin import SimplificationTool from appEditors.grb_plugins.GrbCopyPlugin import CopyEditorTool +from appEditors.grb_plugins.GrberRegionPlugin import GrbRegionEditorTool # import inspect @@ -1110,25 +1111,11 @@ class RegionEditorGrb(ShapeToolEditorGrb): DrawTool.__init__(self, draw_app) self.name = 'region' self.draw_app = draw_app + self.app = self.draw_app.app self.dont_execute = False self.steps_per_circle = self.draw_app.app.options["gerber_circle_steps"] - # try: - # size_ap = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) - # except KeyError: - # msg = '[ERROR_NOTCL] %s' % _("You need to preselect a aperture in the Aperture Table that has a size.") - # self.draw_app.app.inform.emit(msg) - # try: - # QtGui.QGuiApplication.restoreOverrideCursor() - # except Exception: - # pass - # self.dont_execute = True - # self.draw_app.in_action = False - # self.complete = True - # self.draw_app.select_tool('select') - # return - # regions are added always in the 0 aperture if 0 not in self.draw_app.storage_dict: self.draw_app.on_aperture_add(apcode=0) @@ -1152,18 +1139,37 @@ class RegionEditorGrb(ShapeToolEditorGrb): self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) + if self.app.use_3d_engine: + self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) + + self.reg_tool = GrbRegionEditorTool(self.app, self.draw_app, plugin_name=_("Region")) + self.reg_tool.run() + self.reg_tool.length = self.draw_app.last_length + self.ui = self.reg_tool.ui + + self.set_plugin_ui() + self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) self.draw_app.app.inform.emit(_('Corner Mode 1: 45 degrees ...')) - self.start_msg = _("Click on 1st point ...") + def set_plugin_ui(self): + dia = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) + self.ui.dia_entry.set_value(dia) + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) + def click(self, point): self.draw_app.in_action = True if self.inter_point is not None: self.points.append(self.inter_point) self.points.append(point) + self.draw_app.last_length = self.reg_tool.length + self.ui.x_entry.set_value(float(self.draw_app.snap_x)) + self.ui.y_entry.set_value(float(self.draw_app.snap_y)) if len(self.points) > 0: self.draw_app.app.inform.emit(_("Click on next Point or click right mouse button to complete ...")) @@ -1392,6 +1398,56 @@ class RegionEditorGrb(ShapeToolEditorGrb): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) + def draw_cursor_data(self, pos=None, delete=False): + if pos is None: + pos = self.draw_app.snap_x, self.draw_app.snap_y + + if delete: + if self.draw_app.app.use_3d_engine: + self.draw_app.app.plotcanvas.text_cursor.parent = None + self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None + return + + # font size + qsettings = QtCore.QSettings("Open Source", "FlatCAM") + if qsettings.contains("hud_font_size"): + fsize = qsettings.value('hud_font_size', type=int) + else: + fsize = 8 + + old_x = self.ui.x_entry.get_value() + old_y = self.ui.y_entry.get_value() + + x = pos[0] + y = pos[1] + try: + length = abs(np.sqrt((x - old_x) ** 2 + (y - old_y) ** 2)) + except IndexError: + length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals) + units = self.draw_app.app.app_units.lower() + + x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0' + y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0' + length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0' + + l1_txt = 'X: %s [%s]' % (x_dec, units) + l2_txt = 'Y: %s [%s]' % (y_dec, units) + l3_txt = 'L: %s [%s]' % (length_dec, units) + cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt) + + if self.draw_app.app.use_3d_engine: + new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y)) + x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1])) + + # text + self.draw_app.app.plotcanvas.text_cursor.font_size = fsize + self.draw_app.app.plotcanvas.text_cursor.text = cursor_text + self.draw_app.app.plotcanvas.text_cursor.pos = x, y + self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top' + + if self.draw_app.app.plotcanvas.text_cursor.parent is None: + self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene + def on_key(self, key): # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': @@ -1457,6 +1513,56 @@ class RegionEditorGrb(ShapeToolEditorGrb): return msg + if key in [str(i) for i in range(10)] + ['.', ',', '+', '-', '/', '*'] or \ + key in [QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_1, QtCore.Qt.Key.Key_2, + QtCore.Qt.Key.Key_3, QtCore.Qt.Key.Key_4, QtCore.Qt.Key.Key_5, QtCore.Qt.Key.Key_6, + QtCore.Qt.Key.Key_7, QtCore.Qt.Key.Key_8, QtCore.Qt.Key.Key_9, QtCore.Qt.Key.Key_Minus, + QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period, + QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: + try: + # VisPy keys + if self.reg_tool.length == self.draw_app.last_length: + self.reg_tool.length = str(key.name) + else: + self.reg_tool.length = str(self.reg_tool.length) + str(key.name) + except AttributeError: + # Qt keys + if self.reg_tool.length == self.draw_app.last_length: + self.reg_tool.length = chr(key) + else: + self.reg_tool.length = str(self.reg_tool.length) + chr(key) + + if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter: + if self.reg_tool.length != 0: + target_length = self.reg_tool.length + if target_length is None: + self.reg_tool.length = 0.0 + return _("Failed.") + + first_pt = self.ui.x_entry.get_value(), self.ui.y_entry.get_value() + last_pt = self.draw_app.app.mouse_pos + + seg_length = math.sqrt((last_pt[0] - first_pt[0])**2 + (last_pt[1] - first_pt[1])**2) + if seg_length == 0.0: + self.draw_app.app.log.debug("GrbRegionEditorGrb.on_key() --> 'ENTER'. Segment is zero.") + return + try: + new_x = first_pt[0] + (last_pt[0] - first_pt[0]) / seg_length * target_length + new_y = first_pt[1] + (last_pt[1] - first_pt[1]) / seg_length * target_length + except ZeroDivisionError as err: + self.clean_up() + return '[ERROR_NOTCL] %s %s' % (_("Failed."), str(err).capitalize()) + + if first_pt != (new_x, new_y): + self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) + if len(self.points) > 0: + msg = '%s: %s. %s' % ( + _("Projected"), str(self.reg_tool.length), + _("Click on next Point or click right mouse button to complete ...")) + self.draw_app.app.inform.emit(msg) + # self.interpolate_length = '' + # return "Click on next point or hit ENTER to complete ..." + def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.apertures_table.clearSelection() @@ -5914,7 +6020,7 @@ class AppGerberEditor(QtCore.QObject): self.update_utility_geometry(data=(x, y)) if self.active_tool.name in [ - 'pad', 'array', 'track' + 'pad', 'array', 'track', 'region' ]: try: self.active_tool.draw_cursor_data(pos=self.app.mouse_pos) diff --git a/appEditors/grb_plugins/GrberRegionPlugin.py b/appEditors/grb_plugins/GrberRegionPlugin.py new file mode 100644 index 00000000..ac31d690 --- /dev/null +++ b/appEditors/grb_plugins/GrberRegionPlugin.py @@ -0,0 +1,192 @@ + +from appTool import * + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class GrbRegionEditorTool(AppToolEditor): + """ + Simple input for buffer distance. + """ + + def __init__(self, app, draw_app, plugin_name): + AppToolEditor.__init__(self, app) + + self.draw_app = draw_app + self.decimals = app.decimals + self.plugin_name = plugin_name + + self.ui = GrbRegionEditorUI(layout=self.layout, reg_class=self, plugin_name=plugin_name) + + self.connect_signals_at_init() + self.set_tool_ui() + + def connect_signals_at_init(self): + # Signals + self.ui.clear_btn.clicked.connect(self.on_clear) + + def disconnect_signals(self): + # Signals + try: + self.ui.clear_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + def run(self): + self.app.defaults.report_usage("Geo Editor ToolPath()") + super().run() + + # if the splitter us hidden, display it + if self.app.ui.splitter.sizes()[0] == 0: + self.app.ui.splitter.setSizes([1, 1]) + + # if the Tool Tab is hidden display it, else hide it but only if the objectName is the same + found_idx = None + for idx in range(self.app.ui.notebook.count()): + if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab": + found_idx = idx + break + # show the Tab + if not found_idx: + try: + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + except RuntimeError: + self.app.ui.plugin_tab = QtWidgets.QWidget() + self.app.ui.plugin_tab.setObjectName("plugin_tab") + self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab) + self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2) + + self.app.ui.plugin_scroll_area = VerticalScrollArea() + self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area) + self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin")) + + # focus on Tool Tab + self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab) + + # self.app.ui.notebook.callback_on_close = self.on_tab_close + + self.app.ui.notebook.setTabText(2, self.plugin_name) + + def set_tool_ui(self): + # Init appGUI + self.length = 0.0 + + def on_tab_close(self): + self.disconnect_signals() + self.hide_tool() + # self.app.ui.notebook.callback_on_close = lambda: None + + def on_clear(self): + self.set_tool_ui() + + @property + def length(self): + return self.ui.project_line_entry.get_value() + + @length.setter + def length(self, val): + self.ui.project_line_entry.set_value(val) + + def hide_tool(self): + self.ui.reg_tool_frame.hide() + self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab) + if self.draw_app.active_tool.name != 'select': + self.draw_app.select_tool("select") + + +class GrbRegionEditorUI: + + def __init__(self, layout, reg_class, plugin_name): + self.pluginName = plugin_name + self.reg_class = reg_class + self.decimals = self.reg_class.app.decimals + self.layout = layout + + # Title + title_label = FCLabel("%s" % ('Editor ' + self.pluginName), size=16, bold=True) + self.layout.addWidget(title_label) + + # this way I can hide/show the frame + self.reg_tool_frame = QtWidgets.QFrame() + self.reg_tool_frame.setContentsMargins(0, 0, 0, 0) + self.layout.addWidget(self.reg_tool_frame) + self.editor_vbox = QtWidgets.QVBoxLayout() + self.editor_vbox.setContentsMargins(0, 0, 0, 0) + self.reg_tool_frame.setLayout(self.editor_vbox) + + # Position + self.tool_lbl = FCLabel('%s' % _("Diameter"), bold=True, color='blue') + self.editor_vbox.addWidget(self.tool_lbl) + # ############################################################################################################# + # Diameter Frame + # ############################################################################################################# + dia_frame = FCFrame() + self.editor_vbox.addWidget(dia_frame) + + dia_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + dia_frame.setLayout(dia_grid) + + # Dia Value + self.dia_lbl = FCLabel('%s:' % _("Value")) + self.dia_entry = NumericalEvalEntry(border_color='#0069A9') + self.dia_entry.setDisabled(True) + self.dia_unit = FCLabel('%s' % 'mm') + + dia_grid.addWidget(self.dia_lbl, 0, 0) + dia_grid.addWidget(self.dia_entry, 0, 1) + dia_grid.addWidget(self.dia_unit, 0, 2) + + # Position + self.pos_lbl = FCLabel('%s' % _("Position"), bold=True, color='red') + self.editor_vbox.addWidget(self.pos_lbl) + # ############################################################################################################# + # Position Frame + # ############################################################################################################# + pos_frame = FCFrame() + self.editor_vbox.addWidget(pos_frame) + + pos_grid = GLay(v_spacing=5, h_spacing=3) + pos_frame.setLayout(pos_grid) + + # X Pos + self.x_lbl = FCLabel('%s:' % _("X")) + self.x_entry = FCDoubleSpinner() + self.x_entry.set_precision(self.decimals) + self.x_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.x_lbl, 2, 0) + pos_grid.addWidget(self.x_entry, 2, 1) + + # Y Pos + self.y_lbl = FCLabel('%s:' % _("Y")) + self.y_entry = FCDoubleSpinner() + self.y_entry.set_precision(self.decimals) + self.y_entry.set_range(-10000.0000, 10000.0000) + pos_grid.addWidget(self.y_lbl, 4, 0) + pos_grid.addWidget(self.y_entry, 4, 1) + + # ############################################################################################################# + # Projection Frame + # ############################################################################################################# + pro_frame = FCFrame() + self.editor_vbox.addWidget(pro_frame) + + pro_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 1, 0]) + pro_frame.setLayout(pro_grid) + + # Project distance + self.project_line_lbl = FCLabel('%s:' % _("Projection")) + self.project_line_lbl.setToolTip( + _("Length of the current segment/move.") + ) + self.project_line_entry = NumericalEvalEntry(border_color='#0069A9') + pro_grid.addWidget(self.project_line_lbl, 0, 0) + pro_grid.addWidget(self.project_line_entry, 0, 1) + + self.clear_btn = QtWidgets.QToolButton() + self.clear_btn.setIcon(QtGui.QIcon(self.reg_class.app.resource_location + '/trash32.png')) + pro_grid.addWidget(self.clear_btn, 0, 2) + + GLay.set_common_column_size([dia_grid, pos_grid, pro_grid], 0) + self.layout.addStretch(1) From 419330ee9303151d15aab622916cb96f4ed4c65e Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 1 Aug 2022 12:27:14 +0300 Subject: [PATCH 10/55] - fixed some bugs in Geometry Editor in regards of Buffer Tool - fixed some issues in the Cutout Plugin by adding more checks - fixed issues when loading files by dragging in the UI (caused by recent code refactoring) --- CHANGELOG.md | 6 + appCommon/RegisterFileKeywords.py | 5 + appEditors/AppGeoEditor.py | 6 +- appEditors/geo_plugins/GeoBufferPlugin.py | 14 +- appGUI/MainGUI.py | 16 +-- appObjects/ObjectCollection.py | 14 +- appParsers/ParseDXF.py | 12 +- appParsers/ParseDXF_Spline.py | 166 +--------------------- appPlugins/ToolCutOut.py | 27 +++- 9 files changed, 72 insertions(+), 194 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8641eba..8b8a06fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM Evo beta ================================================= +1.08.2022 + +- fixed some bugs in Geometry Editor in regards of Buffer Tool +- fixed some issues in the Cutout Plugin by adding more checks +- fixed issues when loading files by dragging in the UI (caused by recent code refactoring) + 26.05.2022 - upgraded the UI of Region sub-tool in the Gerber Editor diff --git a/appCommon/RegisterFileKeywords.py b/appCommon/RegisterFileKeywords.py index 1e2396cc..5f6269d6 100644 --- a/appCommon/RegisterFileKeywords.py +++ b/appCommon/RegisterFileKeywords.py @@ -319,6 +319,11 @@ class RegisterFK(QtCore.QObject): self.exc_list = extensions.exc_list self.grb_list = extensions.grb_list self.gcode_list = extensions.gcode_list + self.svg_list = extensions.svg_list + self.dxf_list = extensions.dxf_list + self.pdf_list = extensions.pdf_list + self.prj_list = extensions.prj_list + self.conf_list = extensions.conf_list autocomplete_kw_list = self.options['util_autocomplete_keywords'].replace(' ', '').split(',') self.myKeywords = self.tcl_commands_list + autocomplete_kw_list + self.tcl_keywords diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index 99e75e16..11cfa5d9 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -2847,7 +2847,7 @@ class FCBuffer(FCShapeTool): # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) join_style = self.buff_tool.ui.buffer_corner_cb.currentIndex() + 1 - ret_val = self.draw_app.buffer(buffer_distance, join_style) + ret_val = self.buff_tool.buffer(buffer_distance, join_style) self.deactivate() if ret_val == 'fail': @@ -2873,7 +2873,7 @@ class FCBuffer(FCShapeTool): # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) join_style = self.buff_tool.ui.buffer_corner_cb.currentIndex() + 1 - ret_val = self.draw_app.buffer_int(buffer_distance, join_style) + ret_val = self.buff_tool.buffer_int(buffer_distance, join_style) self.deactivate() if ret_val == 'fail': @@ -2899,7 +2899,7 @@ class FCBuffer(FCShapeTool): # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) join_style = self.buff_tool.ui.buffer_corner_cb.currentIndex() + 1 - ret_val = self.draw_app.buffer_ext(buffer_distance, join_style) + ret_val = self.buff_tool.buffer_ext(buffer_distance, join_style) # self.app.ui.notebook.setTabText(2, _("Tools")) # self.draw_app.app.ui.splitter.setSizes([0, 1]) diff --git a/appEditors/geo_plugins/GeoBufferPlugin.py b/appEditors/geo_plugins/GeoBufferPlugin.py index 435c6871..72e1b847 100644 --- a/appEditors/geo_plugins/GeoBufferPlugin.py +++ b/appEditors/geo_plugins/GeoBufferPlugin.py @@ -200,7 +200,7 @@ class BufferSelectionTool(AppToolEditor): geo_editor.build_ui_sig.emit() geo_editor.app.inform.emit('[success] %s' % _("Done.")) - self.app.worker_task.emit({'fcn': work_task, 'params': [self]}) + self.app.worker_task.emit({'fcn': work_task, 'params': [self.draw_app]}) def buffer_int(self, buf_distance, join_style): def work_task(geo_editor): @@ -238,6 +238,8 @@ class BufferSelectionTool(AppToolEditor): for line in t.geo: if line.is_ring: b_geo = Polygon(line) + else: + b_geo = line results.append(b_geo.buffer( -buf_distance + 1e-10, resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4), @@ -246,6 +248,8 @@ class BufferSelectionTool(AppToolEditor): elif t.geo.geom_type in ['LineString', 'LinearRing']: if t.geo.is_ring: b_geo = Polygon(t.geo) + else: + b_geo = t.geo results.append(b_geo.buffer( -buf_distance + 1e-10, resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4), @@ -266,7 +270,7 @@ class BufferSelectionTool(AppToolEditor): geo_editor.build_ui_sig.emit() geo_editor.app.inform.emit('[success] %s' % _("Done.")) - self.app.worker_task.emit({'fcn': work_task, 'params': [self]}) + self.app.worker_task.emit({'fcn': work_task, 'params': [self.draw_app]}) def buffer_ext(self, buf_distance, join_style): def work_task(geo_editor): @@ -306,6 +310,8 @@ class BufferSelectionTool(AppToolEditor): for line in t.geo: if line.is_ring: b_geo = Polygon(line) + else: + b_geo = line results.append(b_geo.buffer( buf_distance - 1e-10, resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4), @@ -314,6 +320,8 @@ class BufferSelectionTool(AppToolEditor): elif t.geo.geom_type in ['LineString', 'LinearRing']: if t.geo.is_ring: b_geo = Polygon(t.geo) + else: + b_geo = t.geo results.append(b_geo.buffer( buf_distance - 1e-10, resolution=int(int(geo_editor.app.options["geometry_circle_steps"]) / 4), @@ -334,7 +342,7 @@ class BufferSelectionTool(AppToolEditor): geo_editor.build_ui_sig.emit() geo_editor.app.inform.emit('[success] %s' % _("Done.")) - self.app.worker_task.emit({'fcn': work_task, 'params': [self]}) + self.app.worker_task.emit({'fcn': work_task, 'params': [self.draw_app]}) def hide_tool(self): self.ui.buffer_tool_frame.hide() diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 3d43c108..262fc021 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -4522,44 +4522,44 @@ class MainGUI(QtWidgets.QMainWindow): else: extension = self.filename.lower().rpartition('.')[-1] - if extension in self.app.grb_list: + if extension in self.app.regFK.grb_list: self.app.worker_task.emit({'fcn': self.app.f_handlers.open_gerber, 'params': [self.filename]}) else: event.ignore() - if extension in self.app.exc_list: + if extension in self.app.regFK.exc_list: self.app.worker_task.emit({'fcn': self.app.f_handlers.open_excellon, 'params': [self.filename]}) else: event.ignore() - if extension in self.app.gcode_list: + if extension in self.app.regFK.gcode_list: self.app.worker_task.emit({'fcn': self.app.f_handlers.open_gcode, 'params': [self.filename]}) else: event.ignore() - if extension in self.app.svg_list: + if extension in self.app.regFK.svg_list: object_type = 'geometry' self.app.worker_task.emit({'fcn': self.app.f_handlers.import_svg, 'params': [self.filename, object_type, None]}) - if extension in self.app.dxf_list: + if extension in self.app.regFK.dxf_list: object_type = 'geometry' self.app.worker_task.emit({'fcn': self.app.f_handlers.import_dxf, 'params': [self.filename, object_type, None]}) - if extension in self.app.pdf_list: + if extension in self.app.regFK.pdf_list: self.app.pdf_tool.periodic_check(1000) self.app.worker_task.emit({'fcn': self.app.pdf_tool.open_pdf, 'params': [self.filename]}) - if extension in self.app.prj_list: + if extension in self.app.regFK.prj_list: # self.app.open_project() is not Thread Safe self.app.f_handlers.open_project(self.filename) - if extension in self.app.conf_list: + if extension in self.app.regFK.conf_list: self.app.f_handlers.open_config_file(self.filename) else: event.ignore() diff --git a/appObjects/ObjectCollection.py b/appObjects/ObjectCollection.py index 51b44282..88216cb4 100644 --- a/appObjects/ObjectCollection.py +++ b/appObjects/ObjectCollection.py @@ -154,35 +154,35 @@ class EventSensitiveListView(QtWidgets.QTreeView): if self.filename == "": self.app.inform.emit(_("Cancelled.")) else: - if self.filename.lower().rpartition('.')[-1] in self.app.grb_list: + if self.filename.lower().rpartition('.')[-1] in self.app.regFK.grb_list: self.app.worker_task.emit({'fcn': self.app.f_handlers.open_gerber, 'params': [self.filename]}) else: event.ignore() - if self.filename.lower().rpartition('.')[-1] in self.app.exc_list: + if self.filename.lower().rpartition('.')[-1] in self.app.regFK.exc_list: self.app.worker_task.emit({'fcn': self.app.f_handlers.open_excellon, 'params': [self.filename]}) else: event.ignore() - if self.filename.lower().rpartition('.')[-1] in self.app.gcode_list: + if self.filename.lower().rpartition('.')[-1] in self.app.regFK.gcode_list: self.app.worker_task.emit({'fcn': self.app.f_handlers.open_gcode, 'params': [self.filename]}) else: event.ignore() - if self.filename.lower().rpartition('.')[-1] in self.app.svg_list: + if self.filename.lower().rpartition('.')[-1] in self.app.regFK.svg_list: object_type = 'geometry' self.app.worker_task.emit({'fcn': self.app.f_handlers.import_svg, 'params': [self.filename, object_type, None]}) - if self.filename.lower().rpartition('.')[-1] in self.app.dxf_list: + if self.filename.lower().rpartition('.')[-1] in self.app.regFK.dxf_list: object_type = 'geometry' self.app.worker_task.emit({'fcn': self.app.f_handlers.import_dxf, 'params': [self.filename, object_type, None]}) - if self.filename.lower().rpartition('.')[-1] in self.app.prj_list: + if self.filename.lower().rpartition('.')[-1] in self.app.regFK.prj_list: # self.app.open_project() is not Thread Safe self.app.f_handlers.open_project(self.filename) else: @@ -558,7 +558,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): try: self.app.regFK.remove_keyword(old_name, update=False) self.app.regFK.prepend_keyword(new_name) - self.app.shell._edit.set_model_data(self.app.myKeywords) + self.app.shell._edit.set_model_data(self.app.regFK.myKeywords) except Exception as e: self.app.log.error( "setData() --> Could not remove the old object name from auto-completer model list. %s" % diff --git a/appParsers/ParseDXF.py b/appParsers/ParseDXF.py index 576e1d02..fb6e7639 100644 --- a/appParsers/ParseDXF.py +++ b/appParsers/ParseDXF.py @@ -11,7 +11,11 @@ from shapely.affinity import rotate from ezdxf.math import Vec3 as ezdxf_vector from appParsers.ParseFont import * -from appParsers.ParseDXF_Spline import * +from appParsers.ParseDXF_Spline import spline2Polyline, normalize_2 +from appParsers.ParseDXF_Spline import Vector as DxfVector + +import math + import logging log = logging.getLogger('base2') @@ -176,7 +180,7 @@ def dxfellipse2shapely(ellipse, ellipse_segments=100): ratio = ellipse.dxf.ratio points_list = [] - major_axis = Vector(list(major_axis)) + major_axis = DxfVector(list(major_axis)) major_x = major_axis[0] major_y = major_axis[1] @@ -191,7 +195,7 @@ def dxfellipse2shapely(ellipse, ellipse_segments=100): for step in range(line_seg + 1): if direction == 'CW': major_dim = normalize_2(major_axis) - minor_dim = normalize_2(Vector([ratio * k for k in major_axis])) + minor_dim = normalize_2(DxfVector([ratio * k for k in major_axis])) vx = (major_dim[0] + major_dim[1]) * math.cos(angle) vy = (minor_dim[0] - minor_dim[1]) * math.sin(angle) x = center[0] + major_x * vx - major_y * vy @@ -199,7 +203,7 @@ def dxfellipse2shapely(ellipse, ellipse_segments=100): angle += step_angle else: major_dim = normalize_2(major_axis) - minor_dim = (Vector([ratio * k for k in major_dim])) + minor_dim = (DxfVector([ratio * k for k in major_dim])) vx = (major_dim[0] + major_dim[1]) * math.cos(angle) vy = (minor_dim[0] + minor_dim[1]) * math.sin(angle) x = center[0] + major_x * vx + major_y * vy diff --git a/appParsers/ParseDXF_Spline.py b/appParsers/ParseDXF_Spline.py index 57d481ba..3d1a2b88 100644 --- a/appParsers/ParseDXF_Spline.py +++ b/appParsers/ParseDXF_Spline.py @@ -102,7 +102,7 @@ def spline2Polyline(xyz, degree, closed, segments, knots): # equal to the order at the ends. # c = order of the basis function # n = the number of defining polygon vertices -# n+2 = index of x[] for the first occurence of the maximum knot vector value +# n+2 = index of x[] for the first occurrence of the maximum knot vector value # n+order = maximum value of the knot vector -- $n + c$ # x[] = array containing the knot vector # ------------------------------------------------------------------------------ @@ -659,167 +659,3 @@ class Vector(list): """@return the transverse component (R in cylindrical coordinate system).""" return math.sqrt(self.perp2()) - - # ---------------------------------------------------------------------- - # Return a random 3D vector - # ---------------------------------------------------------------------- - # @staticmethod - # def random(): - # cosTheta = 2.0 * random.random() - 1.0 - # sinTheta = math.sqrt(1.0 - cosTheta ** 2) - # phi = 2.0 * math.pi * random.random() - # return Vector(math.cos(phi) * sinTheta, math.sin(phi) * sinTheta, cosTheta) - -# #=============================================================================== -# # Cardinal cubic spline class -# #=============================================================================== -# class CardinalSpline: -# def __init__(self, A=0.5): -# # The default matrix is the Catmull-Rom spline -# # which is equal to Cardinal matrix -# # for A = 0.5 -# # -# # Note: Vasilis -# # The A parameter should be the fraction in t where -# # the second derivative is zero -# self.setMatrix(A) -# -# #----------------------------------------------------------------------- -# # Set the matrix according to Cardinal -# #----------------------------------------------------------------------- -# def setMatrix(self, A=0.5): -# self.M = [] -# self.M.append([ -A, 2.-A, A-2., A ]) -# self.M.append([2.*A, A-3., 3.-2.*A, -A ]) -# self.M.append([ -A, 0., A, 0.]) -# self.M.append([ 0., 1., 0, 0.]) -# -# #----------------------------------------------------------------------- -# # Evaluate Cardinal spline at position t -# # @param P list or tuple with 4 points y positions -# # @param t [0..1] fraction of interval from points 1..2 -# # @param k index of starting 4 elements in P -# # @return spline evaluation -# #----------------------------------------------------------------------- -# def __call__(self, P, t, k=1): -# T = [t*t*t, t*t, t, 1.0] -# R = [0.0]*4 -# for i in range(4): -# for j in range(4): -# R[i] += T[j] * self.M[j][i] -# y = 0.0 -# for i in range(4): -# y += R[i]*P[k+i-1] -# -# return y -# -# #----------------------------------------------------------------------- -# # Return the coefficients of a 3rd degree polynomial -# # f(x) = a t^3 + b t^2 + c t + d -# # @return [a, b, c, d] -# #----------------------------------------------------------------------- -# def coefficients(self, P, k=1): -# C = [0.0]*4 -# for i in range(4): -# for j in range(4): -# C[i] += self.M[i][j] * P[k+j-1] -# return C -# -# #----------------------------------------------------------------------- -# # Evaluate the value of the spline using the coefficients -# #----------------------------------------------------------------------- -# def evaluate(self, C, t): -# return ((C[0]*t + C[1])*t + C[2])*t + C[3] -# -# #=============================================================================== -# # Cubic spline ensuring that the first and second derivative are continuous -# # adapted from Penelope Manual Appending B.1 -# # It requires all the points (xi,yi) and the assumption on how to deal -# # with the second derivative on the extremities -# # Option 1: assume zero as second derivative on both ends -# # Option 2: assume the same as the next or previous one -# #=============================================================================== -# class CubicSpline: -# def __init__(self, X, Y): -# self.X = X -# self.Y = Y -# self.n = len(X) -# -# # Option #1 -# s1 = 0.0 # zero based = s0 -# sN = 0.0 # zero based = sN-1 -# -# # Construct the tri-diagonal matrix -# A = [] -# B = [0.0] * (self.n-2) -# for i in range(self.n-2): -# A.append([0.0] * (self.n-2)) -# -# for i in range(1,self.n-1): -# hi = self.h(i) -# Hi = 2.0*(self.h(i-1) + hi) -# j = i-1 -# A[j][j] = Hi -# if i+1> s <<" -# # pprint(self.s) -# -# #----------------------------------------------------------------------- -# def h(self, i): -# return self.X[i+1] - self.X[i] -# -# #----------------------------------------------------------------------- -# def d(self, i): -# return (self.Y[i+1] - self.Y[i]) / (self.X[i+1] - self.X[i]) -# -# #----------------------------------------------------------------------- -# def coefficients(self, i): -# """return coefficients of cubic spline for interval i a*x**3+b*x**2+c*x+d""" -# hi = self.h(i) -# si = self.s[i] -# si1 = self.s[i+1] -# xi = self.X[i] -# xi1 = self.X[i+1] -# fi = self.Y[i] -# fi1 = self.Y[i+1] -# -# a = 1./(6.*hi)*(si*xi1**3 - si1*xi**3 + 6.*(fi*xi1 - fi1*xi)) + hi/6.*(si1*xi - si*xi1) -# b = 1./(2.*hi)*(si1*xi**2 - si*xi1**2 + 2*(fi1 - fi)) + hi/6.*(si - si1) -# c = 1./(2.*hi)*(si*xi1 - si1*xi) -# d = 1./(6.*hi)*(si1-si) -# -# return [d,c,b,a] -# -# #----------------------------------------------------------------------- -# def __call__(self, i, x): -# C = self.coefficients(i) -# return ((C[0]*x + C[1])*x + C[2])*x + C[3] -# -# #----------------------------------------------------------------------- -# # @return evaluation of cubic spline at x using coefficients C -# #----------------------------------------------------------------------- -# def evaluate(self, C, x): -# return ((C[0]*x + C[1])*x + C[2])*x + C[3] -# -# #----------------------------------------------------------------------- -# # Return evaluated derivative at x using coefficients C -# #----------------------------------------------------------------------- -# def derivative(self, C, x): -# a = 3.0*C[0] # derivative coefficients -# b = 2.0*C[1] # ... for sampling with rejection -# c = C[2] -# return (3.0*C[0]*x + 2.0*C[1])*x + C[2] -# diff --git a/appPlugins/ToolCutOut.py b/appPlugins/ToolCutOut.py index 02076c12..330052c8 100644 --- a/appPlugins/ToolCutOut.py +++ b/appPlugins/ToolCutOut.py @@ -756,8 +756,13 @@ class CutOut(AppTool): self.app.log.debug("Cutout.on_freeform_cutout() -> Empty geometry.") self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) return 'fail' + try: + solid_geo, rest_geo = self.any_cutout_handler(geo, abs(cut_dia), gaps, gapsize, margin) + except Exception as err: + self.app.log.error("Cutout.on_freeform_cutout() -> %s" % str(err)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + return - solid_geo, rest_geo = self.any_cutout_handler(geo, abs(cut_dia), gaps, gapsize, margin) if gap_type == 1 and thin_entry != 0: # "Thin gaps" gaps_solid_geo = rest_geo else: @@ -779,9 +784,21 @@ class CutOut(AppTool): geo_buf = geom_struct.buffer(0.0000001) geo_ext = geo_buf.exterior buff_geo_ext = geo_ext.buffer(-margin) - geom_struct = unary_union(buff_geo_ext.interiors) + if isinstance(buff_geo_ext, MultiPolygon): + self.app.log.debug( + "Cutout.on_freeform_cutout() -> The source geometry cannot be used.") + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + unary_union(buff_geo_ext) + else: + geom_struct = unary_union(buff_geo_ext.interiors) + + try: + c_geo, r_geo = self.any_cutout_handler(geom_struct, abs(cut_dia), gaps, gapsize, margin) + except Exception as err: + self.app.log.error("Cutout.on_freeform_cutout() -> %s" % str(err)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + continue - c_geo, r_geo = self.any_cutout_handler(geom_struct, abs(cut_dia), gaps, gapsize, margin) solid_geo += c_geo if gap_type == 1 and thin_entry != 0: # "Thin gaps" gaps_solid_geo += r_geo @@ -2178,6 +2195,8 @@ class CutOut(AppTool): work_geo = obj.geoms if isinstance(obj, (MultiPolygon, MultiLineString)) else obj for k in work_geo: + if k.is_empty or not k.is_valid: + continue minx_, miny_, maxx_, maxy_ = bounds_rec(k) minx = min(minx, minx_) miny = min(miny, miny_) @@ -2185,7 +2204,7 @@ class CutOut(AppTool): maxy = max(maxy, maxy_) return minx, miny, maxx, maxy except TypeError: - # it's a Shapely object, return it's bounds + # it's a Shapely object, return its bounds if obj: return obj.bounds From 3c1349a6c48e42abb7612d1805d53733f72c3e3e Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Thu, 1 Sep 2022 13:08:06 +0300 Subject: [PATCH 11/55] - added a new feature for Geometry export-as-SVG, the ability to export only the paths (outlines); the new feature is controlled from a new parameter in Preferences -> Geometry -> Export --- CHANGELOG.md | 4 ++++ appGUI/preferences/PreferencesUIManager.py | 1 + .../preferences/geometry/GeometryExpPrefGroupUI.py | 12 ++++++++++-- camlib.py | 7 ++++--- defaults.py | 1 + 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8a06fb..4d12c0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +1.09.2022 + +- added a new feature for Geometry export-as-SVG, the ability to export only the paths (outlines); the new feature is controlled from a new parameter in Preferences -> Geometry -> Export + 1.08.2022 - fixed some bugs in Geometry Editor in regards of Buffer Tool diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index a66ec630..a8b3c978 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -267,6 +267,7 @@ class PreferencesUIManager(QtCore.QObject): # Geometry Export "geometry_dxf_format": self.ui.geo_pref_form.geometry_exp_group.dxf_format_combo, + "geometry_paths_only": self.ui.geo_pref_form.geometry_exp_group.svg_paths_only_cb, # Geometry Editor "geometry_editor_sel_limit": self.ui.geo_pref_form.geometry_editor_group.sel_limit_entry, diff --git a/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py b/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py index f14d8ab1..92933bc3 100644 --- a/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py @@ -1,6 +1,7 @@ + from PyQt6 import QtWidgets, QtCore -from appGUI.GUIElements import FCLabel, FCComboBox, GLay, FCFrame +from appGUI.GUIElements import FCLabel, FCComboBox, GLay, FCFrame, FCCheckBox from appGUI.preferences.OptionsGroupUI import OptionsGroupUI import gettext import appTranslation as fcTranslate @@ -36,7 +37,7 @@ class GeometryExpPrefGroupUI(OptionsGroupUI): export_grid = GLay(v_spacing=5, h_spacing=3) export_frame.setLayout(export_grid) - # Excellon non-decimal format + # DXF format self.dxf_format_label = FCLabel("%s:" % _("Format")) self.dxf_format_label.setToolTip( _("Autodesk DXF Format used when exporting Geometry as DXF.") @@ -48,4 +49,11 @@ class GeometryExpPrefGroupUI(OptionsGroupUI): export_grid.addWidget(self.dxf_format_label, 0, 0) export_grid.addWidget(self.dxf_format_combo, 0, 1) + # SVG format + self.svg_paths_only_cb = FCCheckBox(_("SVG Paths-Only")) + self.svg_paths_only_cb.setToolTip( + _("SVG Format. When checked it will export only paths.") + ) + export_grid.addWidget(self.svg_paths_only_cb, 2, 0, 1, 2) + self.layout.addStretch() diff --git a/camlib.py b/camlib.py index 4416c259..af06ab01 100644 --- a/camlib.py +++ b/camlib.py @@ -2326,12 +2326,13 @@ class Geometry(object): flat_geo = [] if self.multigeo: for tool in self.tools: - flat_geo += self.flatten(self.tools[tool]['solid_geometry']) + flat_geo += self.flatten(self.tools[tool]['solid_geometry'], + pathonly=self.app.options["geometry_paths_only"]) geom_svg = unary_union(flat_geo) else: - geom_svg = unary_union(self.flatten()) + geom_svg = unary_union(self.flatten(pathonly=self.app.options["geometry_paths_only"])) else: - geom_svg = unary_union(self.flatten()) + geom_svg = unary_union(self.flatten(pathonly=self.app.options["geometry_paths_only"])) xmin, ymin, xmax, ymax = geom_svg.bounds diff --git a/defaults.py b/defaults.py index 0db909a0..67c8f401 100644 --- a/defaults.py +++ b/defaults.py @@ -322,6 +322,7 @@ class AppDefaults: # Geometry Export "geometry_dxf_format": 'R2010', + "geometry_paths_only": True, # Geometry Editor "geometry_editor_sel_limit": 30, From 71a3963c9662faeee2393555d86f006986a98dc5 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 10 Sep 2022 01:06:13 +0300 Subject: [PATCH 12/55] - hided the main UI on application quit to create a user experience of a shutdown without lag - added a way to terminate QThreads safely by waiting; should be much safer - made sure that the ArgsThread class receive the signal to stop - made sure that on application shutdown, all workers will quit before the actual exit --- CHANGELOG.md | 7 +++++++ appEditors/AppGerberEditor.py | 8 ++++++-- appMain.py | 27 +++++++++++++-------------- appPlugins/ToolPDF.py | 2 +- appPlugins/ToolSub.py | 2 +- appTranslation.py | 1 + appWorkerStack.py | 5 +++++ tclCommands/TclCommand.py | 4 ++-- 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d12c0c4..f6fba375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ CHANGELOG for FlatCAM Evo beta ================================================= +10.09.2022 + +- hided the main UI on application quit to create a user experience of a shutdown without lag +- added a way to terminate QThreads safely by waiting; should be much safer +- made sure that the ArgsThread class receive the signal to stop +- made sure that on application shutdown, all workers will quit before the actual exit + 1.09.2022 - added a new feature for Geometry export-as-SVG, the ability to export only the paths (outlines); the new feature is controlled from a new parameter in Preferences -> Geometry -> Export diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 08553e9a..60e0eeef 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -3994,7 +3994,7 @@ class AppGerberEditor(QtCore.QObject): self.plot_thread = None # a QThread for the edit process - self.thread = QtCore.QThread() + # self.thread = QtCore.QThread() # def entry2option(option, entry): # self.editor_options[option] = float(entry.text()) @@ -5044,7 +5044,11 @@ class AppGerberEditor(QtCore.QObject): pass def clear(self): - self.thread.quit() + # try: + # self.thread.quit() + # self.thread.wait() + # except Exception as erp: + # self.app.log.error("AppGerberEditor.clear() -> %s" % str(erp)) self.active_tool = None self.selected = [] diff --git a/appMain.py b/appMain.py index db9296ef..4c60206f 100644 --- a/appMain.py +++ b/appMain.py @@ -24,7 +24,6 @@ from io import StringIO import gc - from multiprocessing.connection import Listener, Client from multiprocessing import Pool import socket @@ -2701,7 +2700,7 @@ class App(QtCore.QObject): """ Informs the user. Normally on the status bar, optionally also on the shell. - :param msg: Text to write. Composed from a first part between brackets which is the level and the rest + :param msg: Text to write. Composed of a first part between brackets which is the level and the rest which is the message. The level part will control the text color and the used icon :type msg: str :param shell_echo: Control if to display the message msg in the Shell @@ -2710,7 +2709,7 @@ class App(QtCore.QObject): """ # Type of message in brackets at the beginning of the message. - match = re.search(r"^\[(.*?)\](.*)", msg) + match = re.search(r"^\[(.*?)](.*)", msg) if match: level = match.group(1) msg_ = match.group(2) @@ -3768,6 +3767,11 @@ class App(QtCore.QObject): :return: None """ + # hide the UI so the user experiments a faster shutdown + self.ui.hide() + + self.new_launch.stop.emit() # noqa + # close editors before quiting the app, if they are open if self.call_source == 'geo_editor': self.geo_editor.deactivate() @@ -3815,6 +3819,7 @@ class App(QtCore.QObject): # TODO in the future we need to make a difference between settings that need to be persistent all the time self.defaults.update(self.options) self.preferencesUiManager.save_defaults(silent=True) + if silent is False: self.log.debug("App.quit_application() --> App Defaults saved.") @@ -3852,21 +3857,12 @@ class App(QtCore.QObject): if silent is False: self.log.debug("App.quit_application() --> App UI state saved.") - # try to quit the Socket opened by ArgsThread class - # try: - # # self.new_launch.thread_exit = True - # # self.new_launch.listener.close() - # if sys.platform == 'win32' or sys.platform == 'linux': - # self.new_launch.close_listener() - # # self.new_launch.stop.emit() - # except Exception as err: - # self.log.error("App.quit_application() --> %s" % str(err)) - # try to quit the QThread that run ArgsThread class try: # del self.new_launch if sys.platform == 'win32': self.listen_th.quit() + self.listen_th.wait(1000) except Exception as e: if silent is False: self.log.error("App.quit_application() --> %s" % str(e)) @@ -3875,6 +3871,8 @@ class App(QtCore.QObject): # self.workers.__del__() self.clear_pool() + self.workers.quit() + # quit app by signalling for self.kill_app() method # self.close_app_signal.emit() # sys.exit(0) @@ -8906,7 +8904,7 @@ class ArgsThread(QtCore.QObject): self.thread_exit = False self.start.connect(self.run) # noqa - self.stop.connect(self.close_listener) # noqa + self.stop.connect(self.close_listener, type=Qt.ConnectionType.QueuedConnection) # noqa def my_loop(self, address): try: @@ -8941,6 +8939,7 @@ class ArgsThread(QtCore.QObject): def serve(self, conn): while self.thread_exit is False: + QtCore.QCoreApplication.processEvents() msg = conn.recv() if msg == 'close': break diff --git a/appPlugins/ToolPDF.py b/appPlugins/ToolPDF.py index 1d16c3b6..4049ed40 100644 --- a/appPlugins/ToolPDF.py +++ b/appPlugins/ToolPDF.py @@ -378,7 +378,7 @@ class ToolPDF(AppTool): pass self.check_thread.timeout.connect(self.periodic_check_handler) - self.check_thread.start(QtCore.QThread.Priority.HighPriority) + self.check_thread.start(QtCore.QThread.Priority.HighPriority) # noqa def periodic_check_handler(self): """ diff --git a/appPlugins/ToolSub.py b/appPlugins/ToolSub.py index 79c8ef09..b71e795a 100644 --- a/appPlugins/ToolSub.py +++ b/appPlugins/ToolSub.py @@ -704,7 +704,7 @@ class ToolSub(AppTool): pass self.check_thread.timeout.connect(self.periodic_check_handler) - self.check_thread.start(QtCore.QThread.Priority.HighPriority) + self.check_thread.start(QtCore.QThread.Priority.HighPriority) # noqa def periodic_check_handler(self): """ diff --git a/appTranslation.py b/appTranslation.py index b6ec9c09..7024752f 100644 --- a/appTranslation.py +++ b/appTranslation.py @@ -208,6 +208,7 @@ def restart_program(app, ask=None): # try to quit the QThread that run ArgsThread class try: app.listen_th.quit() + app.listen_th.wait(1000) except Exception as err: log.error("FlatCAMTranslation.restart_program() --> %s" % str(err)) diff --git a/appWorkerStack.py b/appWorkerStack.py index cc9d348f..44fc2023 100644 --- a/appWorkerStack.py +++ b/appWorkerStack.py @@ -41,3 +41,8 @@ class WorkerStack(QtCore.QObject): def on_task_completed(self, worker_name): self.load[str(worker_name)] -= 1 + + def quit(self): + for thread in self.threads: + thread.quit() + thread.wait() diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py index 4a2656b0..e016d783 100644 --- a/tclCommands/TclCommand.py +++ b/tclCommands/TclCommand.py @@ -53,8 +53,8 @@ class TclCommand(object): if self.app is None: raise TypeError('Expected app to be appMain instance.') - if not isinstance(self.app, appMain.App): - raise TypeError('Expected appMain, got %s.' % type(app)) + # if not isinstance(self.app, appMain.App): + # raise TypeError('Expected appMain, got %s.' % type(app)) self.log = self.app.log self.error_info = None From 565c839460c9d90e99500bf8c0eb45d797abda48 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 10 Sep 2022 01:50:13 +0300 Subject: [PATCH 13/55] - added insurance that the QThread where the ArgThread class is running, really receive the interruption request and it is finished --- CHANGELOG.md | 1 + appGUI/MainGUI.py | 2 +- appMain.py | 38 +++++++++++++++++++++++++------------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6fba375..9eeb6133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG for FlatCAM Evo beta - added a way to terminate QThreads safely by waiting; should be much safer - made sure that the ArgsThread class receive the signal to stop - made sure that on application shutdown, all workers will quit before the actual exit +- added insurance that the QThread where the ArgThread class is running, really receive the interruption request and it is finished 1.09.2022 diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 262fc021..5b748e07 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -2272,7 +2272,7 @@ class MainGUI(QtWidgets.QMainWindow): def restore_toolbar_view(self): """ Some toolbars may be hidden by user and here we restore the state of the toolbars visibility that - was saved in the options dictionary. + was saved in the 'options' dictionary. :return: None """ diff --git a/appMain.py b/appMain.py index 4c60206f..afbcbacb 100644 --- a/appMain.py +++ b/appMain.py @@ -3767,10 +3767,23 @@ class App(QtCore.QObject): :return: None """ + # make sure that any change we made while working in the app is saved to the defaults + # WARNING !!! Do not hide UI before saving the state of the UI in the defaults file !!! + # TODO in the future we need to make a difference between settings that need to be persistent all the time + self.defaults.update(self.options) + self.preferencesUiManager.save_defaults(silent=True) + + if silent is False: + self.log.debug("App.quit_application() --> App Defaults saved.") + # hide the UI so the user experiments a faster shutdown self.ui.hide() self.new_launch.stop.emit() # noqa + # https://forum.qt.io/topic/108777/stop-a-loop-in-object-that-has-been-moved-to-a-qthread/7 + if self.listen_th.isRunning(): + self.listen_th.requestInterruption() + self.log.debug("ArgThread QThread requested an interruption.") # close editors before quiting the app, if they are open if self.call_source == 'geo_editor': @@ -3815,14 +3828,6 @@ class App(QtCore.QObject): self.plotcanvas.graph_event_disconnect(self.mdc) self.plotcanvas.graph_event_disconnect(self.kp) - # make sure that any change we made while working in the app is saved to the defaults - # TODO in the future we need to make a difference between settings that need to be persistent all the time - self.defaults.update(self.options) - self.preferencesUiManager.save_defaults(silent=True) - - if silent is False: - self.log.debug("App.quit_application() --> App Defaults saved.") - if self.cmd_line_headless != 1: # save app state to file stgs = QSettings("Open Source", "FlatCAM") @@ -8901,6 +8906,7 @@ class ArgsThread(QtCore.QObject): def __init__(self): super().__init__() self.listener = None + self.conn = None self.thread_exit = False self.start.connect(self.run) # noqa @@ -8910,13 +8916,13 @@ class ArgsThread(QtCore.QObject): try: self.listener = Listener(*address) while self.thread_exit is False: - conn = self.listener.accept() - self.serve(conn) + self.conn = self.listener.accept() + self.serve(self.conn) except socket.error: try: - conn = Client(*address) - conn.send(sys.argv) - conn.send('close') + self.conn = Client(*address) + self.conn.send(sys.argv) + self.conn.send('close') # close the current instance only if there are args if len(sys.argv) > 1: try: @@ -8940,6 +8946,8 @@ class ArgsThread(QtCore.QObject): def serve(self, conn): while self.thread_exit is False: QtCore.QCoreApplication.processEvents() + if QtCore.QThread.currentThread().isInterruptionRequested(): + break msg = conn.recv() if msg == 'close': break @@ -8955,6 +8963,10 @@ class ArgsThread(QtCore.QObject): @pyqtSlot() def close_listener(self): self.thread_exit = True + try: + self.conn.close() + except Exception: + pass try: self.listener.close() except Exception: From 9b081916603cfc03d3054fb27c8cebceabd2ae63 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 25 Sep 2022 05:44:43 +0300 Subject: [PATCH 14/55] - added a new setting in Preferences ("3D compatibility") controlled by a checkbox. If the checkbox is checked then multithreading is disabled for the 3D mode (lower performance but perhaps more compatibility, especially for Linux) - this was done from the research done by Matti Eiden on bitbucket --- CHANGELOG.md | 4 ++++ appGUI/PlotCanvas.py | 2 +- appGUI/VisPyVisuals.py | 15 ++++++++++----- appGUI/preferences/PreferencesUIManager.py | 1 + .../preferences/general/GeneralAppPrefGroupUI.py | 6 ++++++ appObjects/AppObjectTemplate.py | 3 ++- defaults.py | 1 + 7 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eeb6133..51058606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +25.09.2022 + +- added a new setting in Preferences ("3D compatibility") controlled by a checkbox. If the checkbox is checked then multithreading is disabled for the 3D mode (lower performance but perhaps more compatibility, especially for Linux) - this was done from the research done by Matti Eiden on bitbucket + 10.09.2022 - hided the main UI on application quit to create a user experience of a shutdown without lag diff --git a/appGUI/PlotCanvas.py b/appGUI/PlotCanvas.py index 717b7d3c..b01884d8 100644 --- a/appGUI/PlotCanvas.py +++ b/appGUI/PlotCanvas.py @@ -511,7 +511,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): # sc = ShapeCollection(parent=self.view.scene, pool=self.app.pool, **kwargs) # self.shape_collections.append(sc) # return sc - return ShapeCollection(parent=self.view.scene, pool=self.fcapp.pool, **kwargs) + return ShapeCollection(parent=self.view.scene, pool=self.fcapp.pool, fcoptions=self.fcapp.options, **kwargs) def new_cursor(self, big=None): """ diff --git a/appGUI/VisPyVisuals.py b/appGUI/VisPyVisuals.py index 727b8877..08818141 100644 --- a/appGUI/VisPyVisuals.py +++ b/appGUI/VisPyVisuals.py @@ -230,7 +230,7 @@ class ShapeGroup(object): class ShapeCollectionVisual(CompoundVisual): - def __init__(self, linewidth=1, triangulation='vispy', layers=3, pool=None, **kwargs): + def __init__(self, linewidth=1, triangulation='vispy', layers=3, pool=None, fcoptions=None, **kwargs): """ Represents collection of shapes to draw on VisPy scene :param linewidth: float @@ -244,6 +244,8 @@ class ShapeCollectionVisual(CompoundVisual): Each layer adds 2 visuals on VisPy scene. Be careful: more layers cause less fps :param kwargs: """ + self.fc_options = fcoptions + self.data = {} self.last_key = -1 @@ -329,11 +331,14 @@ class ShapeCollectionVisual(CompoundVisual): if linewidth: self._line_width = linewidth - # Add data to process pool if pool exists - try: - self.results[key] = self.pool.map_async(_update_shape_buffers, [self.data[key]]) - except Exception: + if self.fc_options and self.fc_options["global_graphic_engine_3d_no_mp"] is True: self.data[key] = _update_shape_buffers(self.data[key]) + else: + # Add data to process pool if pool exists + try: + self.results[key] = self.pool.map_async(_update_shape_buffers, [self.data[key]]) + except Exception: + self.data[key] = _update_shape_buffers(self.data[key]) if update: self.redraw() # redraw() waits for pool process end diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index a8b3c978..f6b9e37e 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -57,6 +57,7 @@ class PreferencesUIManager(QtCore.QObject): "decimals_metric": self.ui.general_pref_form.general_app_group.precision_metric_entry, "units": self.ui.general_pref_form.general_app_group.units_radio, "global_graphic_engine": self.ui.general_pref_form.general_app_group.ge_radio, + "global_graphic_engine_3d_no_mp": self.ui.general_pref_form.general_app_group.ge_comp_cb, "global_app_level": self.ui.general_pref_form.general_app_group.app_level_radio, "global_log_verbose": self.ui.general_pref_form.general_app_group.verbose_combo, "global_portable": self.ui.general_pref_form.general_app_group.portability_cb, diff --git a/appGUI/preferences/general/GeneralAppPrefGroupUI.py b/appGUI/preferences/general/GeneralAppPrefGroupUI.py index a5346b49..16d67696 100644 --- a/appGUI/preferences/general/GeneralAppPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralAppPrefGroupUI.py @@ -102,6 +102,12 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): grid1.addWidget(self.ge_label, 0, 0) grid1.addWidget(self.ge_radio, 0, 1) + self.ge_comp_cb = FCCheckBox(_("3D Compatibility")) + self.ge_comp_cb.setToolTip(_("Check this if you have problems in 3D mode. Works only for 3D mode.\n" + "It will disable performance mods but perhaps add more compatibility.")) + + grid1.addWidget(self.ge_comp_cb, 1, 0, 1, 2) + # separator_line = QtWidgets.QFrame() # separator_line.setFrameShape(QtWidgets.QFrame.Shape.HLine) # separator_line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) diff --git a/appObjects/AppObjectTemplate.py b/appObjects/AppObjectTemplate.py index c198302a..efc98e1f 100644 --- a/appObjects/AppObjectTemplate.py +++ b/appObjects/AppObjectTemplate.py @@ -94,7 +94,8 @@ class FlatCAMObj(QtCore.QObject): if self.app.use_3d_engine: self.shapes = self.app.plotcanvas.new_shape_group() - self.mark_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1, pool=self.app.pool) + self.mark_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1, pool=self.app.pool, + fcoptions=self.app.options) else: self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name) self.mark_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name + "_mark_shapes") diff --git a/defaults.py b/defaults.py index 67c8f401..32135555 100644 --- a/defaults.py +++ b/defaults.py @@ -85,6 +85,7 @@ class AppDefaults: "decimals_inch": 4, "decimals_metric": 4, "global_graphic_engine": '3D', + "global_graphic_engine_3d_no_mp": False, "global_app_level": 'b', "global_log_verbose": 2, From 8f774df30b36f8095a5be2dc3f506915641d66e9 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 25 Sep 2022 05:57:35 +0300 Subject: [PATCH 15/55] - small fix in ToolCutout Plugin when trying to set a checkbox state with a float value which still works in Windows but creates issues in other OS's --- CHANGELOG.md | 1 + appPlugins/ToolCutOut.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51058606..b8c77c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 25.09.2022 - added a new setting in Preferences ("3D compatibility") controlled by a checkbox. If the checkbox is checked then multithreading is disabled for the 3D mode (lower performance but perhaps more compatibility, especially for Linux) - this was done from the research done by Matti Eiden on bitbucket +- small fix in ToolCutout Plugin when trying to set a checkbox state with a float value which still works in Windows but creates issues in other OS's 10.09.2022 diff --git a/appPlugins/ToolCutOut.py b/appPlugins/ToolCutOut.py index 330052c8..9956d843 100644 --- a/appPlugins/ToolCutOut.py +++ b/appPlugins/ToolCutOut.py @@ -379,7 +379,7 @@ class CutOut(AppTool): self.ui.gaps.set_value(tool_dict["tools_cutout_gaps_ff"]) self.ui.cutz_entry.set_value(float(tool_dict["tools_cutout_z"])) - self.ui.mpass_cb.set_value(float(tool_dict["tools_cutout_mdepth"])) + self.ui.mpass_cb.set_value(bool(tool_dict["tools_cutout_mdepth"])) self.ui.maxdepth_entry.set_value(float(tool_dict["tools_cutout_depthperpass"])) def on_cutout_type(self, val): From 56033b8bc4ea18a8b7a7e5051473b26715ec0492 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 27 Sep 2022 16:45:32 +0300 Subject: [PATCH 16/55] - in Milling Plugin, for Excellon target, re-enabled the control over the Feedrate X-Y parameter --- CHANGELOG.md | 4 ++++ appPlugins/ToolMilling.py | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c77c19..70806cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +27.09.2022 + +- in Milling Plugin, for Excellon target, re-enabled the control over the Feedrate X-Y parameter + 25.09.2022 - added a new setting in Preferences ("3D compatibility") controlled by a checkbox. If the checkbox is checked then multithreading is disabled for the 3D mode (lower performance but perhaps more compatibility, especially for Linux) - this was done from the research done by Matti Eiden on bitbucket diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index dc2ffe67..c681a5d3 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -1280,8 +1280,6 @@ class ToolMilling(AppTool, Excellon): self.ui.overlap_entry.show() self.ui.connect_cb.show() - self.ui.frxylabel.hide() - self.ui.xyfeedrate_entry.hide() self.ui.extracut_cb.hide() self.ui.e_cut_entry.hide() @@ -4621,9 +4619,6 @@ class MillingUI: param_grid.addWidget(self.frxylabel, 36, 0) param_grid.addWidget(self.xyfeedrate_entry, 36, 1) - self.frxylabel.hide() - self.xyfeedrate_entry.hide() - # Feedrate Z self.frzlabel = FCLabel('%s:' % _('Feedrate Z')) self.frzlabel.setToolTip( From 94901686b5d95478f33d34d6300c43da06669bc9 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 2 Oct 2022 06:56:13 +0300 Subject: [PATCH 17/55] - really small UI change in 2-Sided Plugin --- CHANGELOG.md | 4 ++++ appPlugins/ToolDblSided.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70806cad..39621195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +2.10.2022 + +- really small UI change in 2-Sided Plugin + 27.09.2022 - in Milling Plugin, for Excellon target, re-enabled the control over the Feedrate X-Y parameter diff --git a/appPlugins/ToolDblSided.py b/appPlugins/ToolDblSided.py index daddc2df..e7435166 100644 --- a/appPlugins/ToolDblSided.py +++ b/appPlugins/ToolDblSided.py @@ -871,7 +871,7 @@ class DsidedUI: grid_mirror.addWidget(separator_line, 3, 0, 1, 3) # ## Reference - self.axloc_label = FCLabel('%s' % _("Reference"), bold=True) + self.axloc_label = FCLabel('%s:' % _("Reference"), bold=True) self.axloc_label.setToolTip( _("The coordinates used as reference for the mirror operation.\n" "Can be:\n" From 2c229d8677e40f069e81060a026c9f1b8c418aae Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 19 Oct 2022 02:35:19 +0300 Subject: [PATCH 18/55] - added a hack so the Gerber files from Allegro 17.2 (which do not follow the Gerber specifications) can be loaded --- CHANGELOG.md | 4 ++++ camlib.py | 26 +++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39621195..e2898e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +19.10.2022 + +- added a hack so the Gerber files from Allegro 17.2 (which do not follow the Gerber specifications) can be loaded + 2.10.2022 - really small UI change in 2-Sided Plugin diff --git a/camlib.py b/camlib.py index af06ab01..e946a4c4 100644 --- a/camlib.py +++ b/camlib.py @@ -335,13 +335,29 @@ class ApertureMacro: """ pol = mods[0] - n = mods[1] - points = [(0, 0)] * (n + 1) - for i in range(n + 1): - points[i] = mods[2 * i + 2:2 * i + 4] + # n = mods[1] + # points = [(0, 0)] * (n + 1) + # + # for i in range(n + 1): + # points[i] = mods[2 * i + 2:2 * i + 4] + # + # angle = mods[2 * n + 4] - angle = mods[2 * n + 4] + # --------------------------- + # added to fix the issue on Allegro 17.2 Gerber's which have fewer points than declared + # discard first 2 values (exposure and vertex points number) and last one (rotation) + vertex_list = mods[2:-1] + # rotation is the last value + angle = mods[-1] + n = int(len(vertex_list) / 2) + points = [(0, 0)] * n + + for i in range(n): + start = 2 * i + stop = (2 * i) + 2 + points[i] = vertex_list[start:stop] + # --------------------------- poly = Polygon(points) poly_rotated = affinity.rotate(poly, angle, origin=(0, 0)) From 6509089dc4c9f3d176c231902c14188f2e00263b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 19 Oct 2022 16:59:05 +0300 Subject: [PATCH 19/55] - fixed an issue when exporting huge SVG files from the Film Tool by adding a fallback and using a custom XML parser with the 'huge_tree' option set --- CHANGELOG.md | 1 + appPlugins/ToolFilm.py | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2898e16..c052e976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 19.10.2022 - added a hack so the Gerber files from Allegro 17.2 (which do not follow the Gerber specifications) can be loaded +- fixed an issue when exporting huge SVG files from the Film Tool by adding a fallback and using a custom XML parser with the 'huge_tree' option set 2.10.2022 diff --git a/appPlugins/ToolFilm.py b/appPlugins/ToolFilm.py index 31bc970f..43e9e2c2 100644 --- a/appPlugins/ToolFilm.py +++ b/appPlugins/ToolFilm.py @@ -707,8 +707,13 @@ class Film(AppTool): # Change the attributes of the exported SVG # We don't need stroke-width - wrong, we do when we have lines with certain width # We set opacity to maximum - # We set the color to the inversed color - root = ET.fromstring(svg_geo) + # We set the color to the inverted color + try: + root = ET.fromstring(svg_geo) + except Exception: + self.app.log.debug("Film.create_negative_svg() getting XML root failed. Trying to use huge_tree option.") + root = ET.fromstring(svg_geo, parser=ET.XMLParser(huge_tree=True)) + for child in root: child.set('fill', self.get_complementary(color)) child.set('opacity', str(opacity)) @@ -925,13 +930,17 @@ class Film(AppTool): make_positive_film(color=color, transparency_level=transparency_level, scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y) - @staticmethod - def create_positive_svg(svg_geo, box_bounds, margin, color, opacity, svg_units): + def create_positive_svg(self, svg_geo, box_bounds, margin, color, opacity, svg_units): # Change the attributes of the exported SVG # We don't need stroke-width # We set opacity to maximum # We set the colour to WHITE - root = ET.fromstring(svg_geo) + try: + root = ET.fromstring(svg_geo) + except Exception: + self.app.log.debug("Film.create_positive_svg() getting XML root failed. Trying to use huge_tree option.") + root = ET.fromstring(svg_geo, parser=ET.XMLParser(huge_tree=True)) + for child in root: child.set('fill', str(color)) child.set('opacity', str(opacity)) @@ -939,7 +948,7 @@ class Film(AppTool): exported_svg = ET.tostring(root) - # This contain the measure units + # This contains the measure units uom = svg_units # Convert everything to strings for use in the xml doc @@ -1208,7 +1217,7 @@ class Film(AppTool): if scale_stroke_factor <= 0: scale_stroke_factor = 0.01 - # Convert to a SVG + # Convert to a SVG file svg_elem = geom.svg(scale_factor=scale_stroke_factor) return svg_elem From d1a4de676e2e6b701c8c5846cc3ad3b5886c2af1 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 6 Nov 2022 00:43:34 +0200 Subject: [PATCH 20/55] - if PikePDF module is not available then show an error but run the app (currently, for Python 3.11 is the only module not available) --- CHANGELOG.md | 4 ++++ appPlugins/ToolPDF.py | 13 ++++++++++++- requirements.txt | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c052e976..10c0810f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +6.11.2022 + +- if PikePDF module is not available then show an error but run the app (currently, for Python 3.11 is the only module not available) + 19.10.2022 - added a hack so the Gerber files from Allegro 17.2 (which do not follow the Gerber specifications) can be loaded diff --git a/appPlugins/ToolPDF.py b/appPlugins/ToolPDF.py index 4049ed40..b3f90c26 100644 --- a/appPlugins/ToolPDF.py +++ b/appPlugins/ToolPDF.py @@ -7,9 +7,15 @@ from appTool import * from appParsers.ParsePDF import PdfParser -from pikepdf import Pdf, parse_content_stream + from camlib import grace +HAS_PIKE_MODULE = True +try: + from pikepdf import Pdf, parse_content_stream +except ModuleNotFoundError: + HAS_PIKE_MODULE = False + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -96,6 +102,11 @@ class ToolPDF(AppTool): self.app.inform.emit('[ERROR_NOTCL] %s' % _("File no longer available.")) return + if HAS_PIKE_MODULE is False: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed.")) + self.app.log.error("PikePDF module is not available.") + return + short_name = filename.split('/')[-1].split('\\')[-1] self.parsing_promises.append(short_name) diff --git a/requirements.txt b/requirements.txt index c06ac446..09a17d32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ pyopengl pyqt6>=6.1.0 freetype-py vispy>=0.9.0 -pyqtdarktheme +pyqtdarktheme=1.1.1 gdal rasterio From 9ec2ee2920051ddc14b90364251d40eed3824ae4 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 8 Nov 2022 15:59:59 +0200 Subject: [PATCH 21/55] - in Drilling Plugin fixed a situation when having tools with the same diameter will get them multiplied by the number of those tools --- CHANGELOG.md | 4 +++ appPlugins/ToolDrilling.py | 56 +++++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c0810f..6c640478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +7.11.2022 + +- in Drilling Plugin fixed a situation when having tools with the same diameter will get them multiplied by the number of those tools + 6.11.2022 - if PikePDF module is not available then show an error but run the app (currently, for Python 3.11 is the only module not available) diff --git a/appPlugins/ToolDrilling.py b/appPlugins/ToolDrilling.py index e0e5504c..0a0af4bb 100644 --- a/appPlugins/ToolDrilling.py +++ b/appPlugins/ToolDrilling.py @@ -654,29 +654,47 @@ class ToolDrilling(AppTool, Excellon): self.app.log.debug("ToolDrilling.build_tool_ui()") self.ui_disconnect() - # order the tools by tool diameter if it's the case - sorted_tools = [] - for k, v in self.excellon_tools.items(): - sorted_tools.append(self.dec_format(float(v['tooldia']))) + # # order the tools by tool diameter if it's the case + # sorted_tools = [] + # for k, v in self.excellon_tools.items(): + # sorted_tools.append(self.dec_format(float(v['tooldia']))) + # + # order = self.ui.order_combo.get_value() + # if order == 1: # 'fwd' + # sorted_tools.sort(reverse=False) + # elif order == 2: # 'rev' + # sorted_tools.sort(reverse=True) + # else: + # pass + # + # # remake the excellon_tools dict in the order above + # new_id = 1 + # new_tools = {} + # for tooldia in sorted_tools: + # for old_tool in self.excellon_tools: + # if self.dec_format(float(self.excellon_tools[old_tool]['tooldia'])) == tooldia: + # new_tools[new_id] = deepcopy(self.excellon_tools[old_tool]) + # new_id += 1 order = self.ui.order_combo.get_value() if order == 1: # 'fwd' - sorted_tools.sort(reverse=False) - elif order == 2: # 'rev' - sorted_tools.sort(reverse=True) + new_tools = { + k: v for k, v in sorted(self.excellon_tools.items(), key=lambda it: it[1]['tooldia'], reverse=False) + } + for idx, v in enumerate(new_tools.values(), start=1): + self.excellon_tools[idx] = v + elif order == 2: # 'rev' + new_tools = { + k: v for k, v in sorted(self.excellon_tools.items(), key=lambda it: it[1]['tooldia'], reverse=True) + } + for idx, v in enumerate(new_tools.values(), start=1): + self.excellon_tools[idx] = v else: - pass - - # remake the excellon_tools dict in the order above - new_id = 1 - new_tools = {} - for tooldia in sorted_tools: - for old_tool in self.excellon_tools: - if self.dec_format(float(self.excellon_tools[old_tool]['tooldia'])) == tooldia: - new_tools[new_id] = deepcopy(self.excellon_tools[old_tool]) - new_id += 1 - - self.excellon_tools = new_tools + try: + self.excellon_tools = self.excellon_obj.tools + except AttributeError: + # when no object was loaded yet + pass if self.excellon_obj and self.excellon_tools: self.ui.exc_param_frame.setDisabled(False) From 8b4423dc0fd75707a4d67c95bb27cac32f6125c9 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 9 Nov 2022 19:55:59 +0200 Subject: [PATCH 22/55] - when changing the style for the decorations from Preferences, now change is applied immediately --- CHANGELOG.md | 4 ++++ FlatCAM.py | 2 ++ appGUI/preferences/general/GeneralGUIPrefGroupUI.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c640478..7524f35c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +9.11.2022 + +- when changing the style for the decorations from Preferences, now change is applied immediately + 7.11.2022 - in Drilling Plugin fixed a situation when having tools with the same diameter will get them multiplied by the number of those tools diff --git a/FlatCAM.py b/FlatCAM.py index b09db242..da2f78df 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -157,6 +157,8 @@ if __name__ == '__main__': idx = 0 style = QtWidgets.QStyleFactory.keys()[idx] app.setStyle(style) + else: + app.setStyle('windowsvista') fc = App(qapp=app) diff --git a/appGUI/preferences/general/GeneralGUIPrefGroupUI.py b/appGUI/preferences/general/GeneralGUIPrefGroupUI.py index 50e47c20..82c3ead3 100644 --- a/appGUI/preferences/general/GeneralGUIPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralGUIPrefGroupUI.py @@ -396,6 +396,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): qsettings = QSettings("Open Source", "FlatCAM") qsettings.setValue('style', str(style)) + new_style = QtWidgets.QStyleFactory.keys()[int(style)] + QtWidgets.QApplication.setStyle(new_style) + # This will write the setting to the platform specific storage. del qsettings From 22f4d92be798836e21754865e93f6a97758e5c22 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 9 Nov 2022 20:56:39 +0200 Subject: [PATCH 23/55] - minor change in the requirements.txt file --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09a17d32..f42375ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ pyopengl pyqt6>=6.1.0 freetype-py vispy>=0.9.0 -pyqtdarktheme=1.1.1 +pyqtdarktheme==1.1.1 gdal rasterio From c1377d509054f825c8943a31f71869173595e546 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 19 Nov 2022 02:29:40 +0200 Subject: [PATCH 24/55] - added a 'return' button when a fatal error is encountered allowing the user to continue the work - fixed a crash in Milling Plugin when trying to mill slots that do not have drills in the same file --- .gitignore | 1 + CHANGELOG.md | 5 +++++ FlatCAM.py | 10 ++++++++-- appPlugins/ToolMilling.py | 29 +++++++++++++++++++---------- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 30a8ae85..15c9c9f5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ tests/tmp/ build/ /venv/ +/venv/ # General for macOS .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 7524f35c..112da3ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +19.11.2022 + +- added a 'return' button when a fatal error is encountered allowing the user to continue the work +- fixed a crash in Milling Plugin when trying to mill slots that do not have drills in the same file + 9.11.2022 - when changing the style for the decorations from Preferences, now change is applied immediately diff --git a/FlatCAM.py b/FlatCAM.py index da2f78df..b4652c4b 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -134,13 +134,19 @@ if __name__ == '__main__': msgbox.setIcon(QtWidgets.QMessageBox.Icon.Critical) bt_yes = msgbox.addButton("Quit", QtWidgets.QMessageBox.ButtonRole.YesRole) + bt_ret = msgbox.addButton("Return", QtWidgets.QMessageBox.ButtonRole.NoRole) msgbox.setDefaultButton(bt_yes) # msgbox.setTextFormat(Qt.TextFormat.RichText) msgbox.exec() + + response = msgbox.clickedButton() + if response == bt_ret: + pass except Exception: - pass - QtWidgets.QApplication.quit() + QtWidgets.QApplication.quit() + else: + QtWidgets.QApplication.quit() # or QtWidgets.QApplication.exit(0) sys.excepthook = excepthook diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index c681a5d3..11fc626b 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -2819,18 +2819,27 @@ class ToolMilling(AppTool, Excellon): buff_dia = float(tools_dict[tool]['tooldia']) / 2.0 + offset if mill_type in ['drills', 'both']: - drills_tool_geo = [ - d_p.buffer(buff_dia) for d_p in tools_dict[tool]['drills'] - ] - total_paint_geo = drills_tool_geo - elif mill_type in ['slots', 'both']: - slots_tool_geo = [ - LineString(s_l).buffer(buff_dia) for s_l in tools_dict[tool]['slots'] - ] - total_paint_geo = slots_tool_geo - elif mill_type == 'both': + try: + drills_tool_geo = [ + d_p.buffer(buff_dia) for d_p in tools_dict[tool]['drills'] + ] + total_paint_geo = drills_tool_geo + except KeyError: + total_paint_geo = [] + if mill_type in ['slots', 'both']: + try: + slots_tool_geo = [ + LineString(s_l).buffer(buff_dia) for s_l in tools_dict[tool]['slots'] + ] + total_paint_geo = slots_tool_geo + except KeyError: + total_paint_geo = [] + if mill_type == 'both': total_paint_geo = drills_tool_geo + slots_tool_geo + if not total_paint_geo: + continue + pol_nr = 0 geo_len = len(total_paint_geo) cp = [] From 2d0ea65f1b28cf291c8e14a559cb25ab2c4c38cb Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 15 Jan 2023 15:25:20 +0200 Subject: [PATCH 25/55] - minor change --- appGUI/MainGUI.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 5b748e07..29888602 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -2945,7 +2945,7 @@ class MainGUI(QtWidgets.QMainWindow): def keyPressEvent(self, event): """ Key event handler for the entire app. - Some of the key events are also treated locally in the FlatCAM editors + Some key events are also treated locally in the FlatCAM editors :param event: QT event :return: @@ -5133,8 +5133,8 @@ class ShortcutsTab(QtWidgets.QWidget): _('Esc'), _("Deselects all objects") ) - self.app_sh_msg = self.app_sh_title + self.app_sh_no_mod + self.app_sh_ctrl_mod + self.app_sh_shift_mod + \ - self.app_sh_alt_mod + self.app_sh_combo_mod + self.app_sh_div + self.app_sh_msg = self.app_sh_title + self.app_sh_no_mod + self.app_sh_ctrl_mod \ + + self.app_sh_shift_mod + self.app_sh_alt_mod + self.app_sh_combo_mod + self.app_sh_div self.sh_app = QtWidgets.QTextEdit() self.sh_app.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.NoTextInteraction) From d6ebcef38d792f9a44b55b044c69ea281415dafd Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 15 Jan 2023 22:31:11 +0200 Subject: [PATCH 26/55] - PEP8 changes --- CHANGELOG.md | 4 ++++ appMain.py | 30 ++++++++++++++++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 112da3ee..14e1ab81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +15.01.2023 + +- PEP8 changes + 19.11.2022 - added a 'return' button when a fatal error is encountered allowing the user to continue the work diff --git a/appMain.py b/appMain.py index afbcbacb..547ae5e2 100644 --- a/appMain.py +++ b/appMain.py @@ -229,7 +229,7 @@ class App(QtCore.QObject): message = QtCore.pyqtSignal(str, str, str) - # Emmited when shell command is finished(one command only) + # Emitted when a shell command is finished(one command only) shell_command_finished = QtCore.pyqtSignal(object) # Emitted when multiprocess pool has been recreated pool_recreated = QtCore.pyqtSignal(object) @@ -239,7 +239,7 @@ class App(QtCore.QObject): # used to signal that there are arguments for the app args_at_startup = QtCore.pyqtSignal(list) # a reusable signal to replot a list of objects - # should be disconnected after use so it can be reused + # should be disconnected after use, so it can be reused replot_signal = pyqtSignal(list) # signal emitted when jumping jump_signal = pyqtSignal(tuple) @@ -334,7 +334,7 @@ class App(QtCore.QObject): # variable to store if a command is active (then the var is not None) and which one it is self.command_active = None # variable to store the status of moving selection action - # None value means that it's not an selection action + # None value means that it's not a selection action # True value = a selection from left to right # False value = a selection from right to left self.selection_type = None @@ -371,7 +371,7 @@ class App(QtCore.QObject): self.text_editor_tab = None - # here store the color of a Tab text before it is changed so it can be restored in the future + # here store the color of a Tab text before it is changed, so it can be restored in the future self.old_tab_text_color = None # reference for the self.ui.code_editor @@ -901,7 +901,7 @@ class App(QtCore.QObject): color=QtGui.QColor("lightgray")) start_plot_time = time.time() # debug - # setup the PlotCanvas + # set up the PlotCanvas self.plotcanvas = self.on_plotcanvas_setup() if self.plotcanvas == 'fail': self.splash.finish(self.ui) @@ -1059,7 +1059,7 @@ class App(QtCore.QObject): # ################################## ADDING FlatCAM EDITORS section ######################################### # ########################################################################################################### - # watch out for the position of the editors instantiation ... if it is done before a save of the default values + # watch out for the position of the editor instantiation ... if it is done before a save of the default values # at the first launch of the App , the editors will not be functional. try: self.geo_editor = AppGeoEditor(self) @@ -1154,7 +1154,7 @@ class App(QtCore.QObject): # signals for displaying messages in the Tcl Shell are now connected in the ToolShell class - # loading an project + # loading a project self.restore_project.connect(self.f_handlers.restore_project_handler) # noqa self.restore_project_objects_sig.connect(self.f_handlers.restore_project_objects) # noqa # signal to be called when the app is quiting @@ -2903,9 +2903,10 @@ class App(QtCore.QObject): QtWidgets.QDialog.__init__(self, parent=parent) self.app = app + self.app_icon = self.app.ui.app_icon # Icon and title - self.setWindowIcon(parent.app_icon) + self.setWindowIcon(self.app_icon) self.setWindowTitle(_("About")) self.resize(600, 200) # self.setStyleSheet("background-image: url(share/flatcam_icon256.png); background-attachment: fixed") @@ -3424,6 +3425,7 @@ class App(QtCore.QObject): QtWidgets.QDialog.__init__(self, parent=parent) self.app = app + self.app_icon = self.app.ui.app_icon open_source_link = "Open Source" new_features_link = "click" # Icon and title - self.setWindowIcon(parent.app_icon) + self.setWindowIcon(self.app_icon) self.setWindowTitle('%s ...' % _("How To")) self.resize(750, 375) @@ -5373,7 +5375,7 @@ class App(QtCore.QObject): if obj: return obj.bounds() - bounds = bounds_rec(obj_list) + bounds = bounds_rec(obj_list) # noqa if not val: dia_box_location = (0.0, 0.0) @@ -5698,9 +5700,9 @@ class App(QtCore.QObject): # create solid_geometry solid_geometry = [] - for apid in apertures: - for geo_el in apertures[apid]['geometry']: - solid_geometry.append(geo_el['solid']) + for apid_val in apertures.values(): + for geo_el in apid_val['geometry']: + solid_geometry.append(geo_el['solid']) # noqa solid_geometry = MultiPolygon(solid_geometry) solid_geometry = solid_geometry.buffer(0.0000001) @@ -6306,7 +6308,7 @@ class App(QtCore.QObject): self.ui.toggle_coords(checked=self.options["global_coordsbar_show"]) self.ui.toggle_delta_coords(checked=self.options["global_delta_coordsbar_show"]) - def on_notebook_closed(self, tab_obj_name): + def on_notebook_closed(self): # closed_plugin_name = self.ui.plugin_scroll_area.widget().objectName() # # print(closed_plugin_name) From 6c5a4684189dd953fd4b6c5afc513b7646ccdf1d Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 16 Jan 2023 13:02:47 +0200 Subject: [PATCH 27/55] - fixed a decoding error in the Excellon parser --- CHANGELOG.md | 4 ++++ appParsers/ParseExcellon.py | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14e1ab81..916efc7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +16.01.2023 + +- fixed a decoding error in the Excellon parser + 15.01.2023 - PEP8 changes diff --git a/appParsers/ParseExcellon.py b/appParsers/ParseExcellon.py index 06061556..5d1a60a6 100644 --- a/appParsers/ParseExcellon.py +++ b/appParsers/ParseExcellon.py @@ -230,9 +230,20 @@ class Excellon(Geometry): else: if filename is None: return "fail" - efile = open(filename, 'r') - estr = efile.readlines() - efile.close() + + estr = "" + decoded = False + encodings_types = ('cp1252', 'cp850', 'utf-8', 'utf8') + for enc in encodings_types: + try: + with open(filename, 'r', encoding=enc) as efile: + estr = efile.readlines() + decoded = True + break + except UnicodeDecodeError: + pass + if decoded is False: + return 'fail' try: self.parse_lines(estr) From a1b6f78d78b3e75e870c8f1a29e577f3a8ab3765 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 16 Jan 2023 13:28:50 +0200 Subject: [PATCH 28/55] - some PEP8 fixes --- CHANGELOG.md | 1 + appParsers/ParseExcellon.py | 58 ++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 916efc7e..2441cea1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 16.01.2023 - fixed a decoding error in the Excellon parser +- some PEP8 fixes 15.01.2023 diff --git a/appParsers/ParseExcellon.py b/appParsers/ParseExcellon.py index 5d1a60a6..3133b088 100644 --- a/appParsers/ParseExcellon.py +++ b/appParsers/ParseExcellon.py @@ -82,7 +82,7 @@ class Excellon(Geometry): self.source_file = '' - # it serve to flag if a start routing or a stop routing was encountered + # it serves to flag if a start routing or a stop routing was encountered # if a stop is encounter and this flag is still 0 (so there is no stop for a previous start) issue error self.routing_flag = 1 @@ -361,8 +361,8 @@ class Excellon(Geometry): self.excellon_format_upper_in = match.group(1) self.excellon_format_lower_in = match.group(2) - self.app.log.warning("Excellon format preset found in comments: %s:%s" % - (match.group(1), match.group(2))) + aef_msg = "Excellon format preset found in comments: %s:%s" % (match.group(1), match.group(2)) + self.app.log.warning(aef_msg) continue else: self.app.log.warning("Line ignored, it's a comment: %s" % eline) @@ -542,8 +542,8 @@ class Excellon(Geometry): # we have a slot self.app.log.debug('Parsed a slot with coordinates: ' + str([slot_start_x, - slot_start_y, slot_stop_x, - slot_stop_y])) + slot_start_y, slot_stop_x, + slot_stop_y])) # store current tool diameter as slot diameter slot_dia = 0.05 @@ -616,8 +616,8 @@ class Excellon(Geometry): # we have a slot self.app.log.debug('Parsed a slot with coordinates: ' + str([slot_start_x, - slot_start_y, slot_stop_x, - slot_stop_y])) + slot_start_y, slot_stop_x, + slot_stop_y])) # store current tool diameter as slot diameter slot_dia = 0.05 @@ -897,11 +897,13 @@ class Excellon(Geometry): self.app.log.warning("UNITS found inline - Value after conversion: %s" % self.units) if self.units == 'MM': - self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm))) + b_msg = "Excellon format preset is: %s:%s" % \ + (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm)) + self.app.log.warning(b_msg) else: - self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in))) + a_msg = "Excellon format preset is: %s:%s" % \ + (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in)) + self.app.log.warning(a_msg) self.app.log.warning("Type of ZEROS found inline, in header: %s" % self.zeros) continue @@ -909,21 +911,25 @@ class Excellon(Geometry): if "INCH" in eline: line_units = "IN" # Modified for issue #80 - self.app.log.warning("Type of UNITS found inline, in header, before conversion: %s" % line_units) + f_msg = "Type of UNITS found inline, in header, before conversion: %s" % line_units + self.app.log.warning(f_msg) self.convert_units(line_units) self.app.log.warning("Type of UNITS found inline, in header, after conversion: %s" % self.units) - self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in))) + ff_msg = "Excellon format preset is: %s:%s" % \ + (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in)) + self.app.log.warning(ff_msg) self.excellon_units_found = "IN" continue elif "METRIC" in eline: line_units = "MM" # Modified for issue #80 - self.app.log.warning("Type of UNITS found inline, in header, before conversion: %s" % line_units) + f_msg = "Type of UNITS found inline, in header, before conversion: %s" % line_units + self.app.log.warning(f_msg) self.convert_units(line_units) self.app.log.warning("Type of UNITS found inline, in header, after conversion: %s" % self.units) - self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm))) + ff_msg = "Excellon format preset is: %s:%s" % \ + (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm)) + self.app.log.warning(ff_msg) self.excellon_units_found = "MM" continue @@ -953,16 +959,19 @@ class Excellon(Geometry): self.excellon_format_lower_in = lower # Modified for issue #80 - self.app.log.warning("Type of UNITS found outside header, inline before conversion: %s" % self.units) + c_msg = "Type of UNITS found outside header, inline before conversion: %s" % self.units + self.app.log.warning(c_msg) self.convert_units(self.units) self.app.log.warning("Type of UNITS found outside header, inline after conversion: %s" % self.units) if self.units == 'MM': - self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm))) + cc_msg = "Excellon format preset is: %s:%s" % \ + (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm)) + self.app.log.warning(cc_msg) else: - self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in))) + cc_msg = "Excellon format preset is: %s:%s" % \ + (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in)) + self.app.log.warning(cc_msg) self.app.log.warning("Type of ZEROS found outside header, inline: %s" % self.zeros) continue @@ -1096,8 +1105,9 @@ class Excellon(Geometry): self.solid_geometry.append(poly) except Exception as e: - self.app.log.error("appParsers.ParseExcellon.Excellon.create_geometry() -> " - "Excellon geometry creation failed due of ERROR: %s" % str(e)) + err_msg = "appParsers.ParseExcellon.Excellon.create_geometry() -> " \ + "Excellon geometry creation failed due of ERROR: %s" % str(e) + self.app.log.error(err_msg) return "fail" def bounds(self, flatten=None): From 853b273e79a846284633f922d18f3a1fe7c06220 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 16 Jan 2023 23:52:30 +0200 Subject: [PATCH 29/55] - fixed the Voronoi generation in the Autolevelling Tool (removed the Foronoi package due of issues and now using the embedded functionality from Shapely) --- appPlugins/ToolLevelling.py | 123 +++++++++++++++++++----------------- requirements.txt | 2 +- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index e69f9e0e..949b472f 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -21,17 +21,23 @@ from io import StringIO from matplotlib.backend_bases import KeyEvent as mpl_key_event +# try: +# from foronoi import Voronoi +# from foronoi import Polygon as voronoi_poly +# VORONOI_ENABLED = True +# except Exception: +# try: +# from shapely.ops import voronoi_diagram +# VORONOI_ENABLED = True +# # from appCommon.Common import voronoi_diagram +# except Exception: +# VORONOI_ENABLED = False try: - from foronoi import Voronoi - from foronoi import Polygon as voronoi_poly + from shapely.ops import voronoi_diagram VORONOI_ENABLED = True + # from appCommon.Common import voronoi_diagram except Exception: - try: - from shapely.ops import voronoi_diagram - VORONOI_ENABLED = True - # from appCommon.Common import voronoi_diagram - except Exception: - VORONOI_ENABLED = False + VORONOI_ENABLED = False fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -548,7 +554,7 @@ class ToolLevelling(AppTool, CNCjob): al_method = self.ui.al_method_radio.get_value() if al_method == 'v': if VORONOI_ENABLED is True: - self.generate_voronoi_geometry_2(pts=vor_pts_list) + self.generate_voronoi_geometry(pts=vor_pts_list) # generate Probing GCode self.probing_gcode_text = self.probing_gcode(storage=self.al_voronoi_geo_storage) else: @@ -736,7 +742,7 @@ class ToolLevelling(AppTool, CNCjob): return new_voronoi = [] - for p in voronoi_union: + for p in voronoi_union.geoms: new_voronoi.append(p.intersection(env)) for pt_key in list(self.al_voronoi_geo_storage.keys()): @@ -744,52 +750,55 @@ class ToolLevelling(AppTool, CNCjob): if self.al_voronoi_geo_storage[pt_key]['point'].within(poly): self.al_voronoi_geo_storage[pt_key]['geo'] = poly - def generate_voronoi_geometry_2(self, pts): - env = self.solid_geo.envelope - fact = 1 if self.units == 'MM' else 0.039 - env = env.buffer(fact) - env_poly = voronoi_poly(list(env.exterior.coords)) - - new_pts = [[pt.x, pt.y] for pt in pts] - - # Initialize the algorithm - v = Voronoi(env_poly) - - # calculate the Voronoi diagram - try: - v.create_diagram(new_pts) - except AttributeError as e: - self.app.log.error("CNCJobObject.generate_voronoi_geometry_2() --> %s" % str(e)) - new_pts_2 = [] - for pt_index in range(len(new_pts)): - new_pts_2.append([ - new_pts[pt_index][0] + random.random() * 1e-03, - new_pts[pt_index][1] + random.random() * 1e-03 - ]) - - try: - v.create_diagram(new_pts_2) - except Exception: - print("Didn't work.") - return - - new_voronoi = [] - for p in v.points: - p_coords = [(coord.x, coord.y) for coord in p.get_coordinates()] - new_pol = Polygon(p_coords) - new_voronoi.append(new_pol) - - new_voronoi = MultiPolygon(new_voronoi) - - # new_voronoi = [] - # for p in voronoi_union: - # new_voronoi.append(p.intersection(env)) - # - for pt_key in list(self.al_voronoi_geo_storage.keys()): - for poly in new_voronoi: - if self.al_voronoi_geo_storage[pt_key]['point'].within(poly) or \ - self.al_voronoi_geo_storage[pt_key]['point'].intersects(poly): - self.al_voronoi_geo_storage[pt_key]['geo'] = poly + # def generate_voronoi_geometry_2(self, pts): + # env = self.solid_geo.envelope + # fact = 1 if self.units == 'MM' else 0.039 + # env = env.buffer(fact) + # env_poly = voronoi_poly(tuple(env.exterior.coords)) + # + # new_pts = [[pt.x, pt.y] for pt in pts] + # print(new_pts) + # print(env_poly) + # + # # Initialize the algorithm + # v = Voronoi(env_poly) + # + # # calculate the Voronoi diagram + # try: + # v.create_diagram(new_pts) + # except AttributeError as e: + # self.app.log.error("CNCJobObject.generate_voronoi_geometry_2() --> %s" % str(e)) + # new_pts_2 = [] + # for pt_index in range(len(new_pts)): + # new_pts_2.append([ + # new_pts[pt_index][0] + random.random() * 1e-03, + # new_pts[pt_index][1] + random.random() * 1e-03 + # ]) + # + # try: + # v.create_diagram(new_pts_2) + # except Exception: + # print("Didn't work.") + # return + # + # new_voronoi = [] + # for p in v.sites: + # # p_coords = [(coord.x, coord.y) for coord in p.get_coordinates()] + # p_coords = [(p.x, p.y)] + # new_pol = Polygon(p_coords) + # new_voronoi.append(new_pol) + # + # new_voronoi = MultiPolygon(new_voronoi) + # + # # new_voronoi = [] + # # for p in voronoi_union: + # # new_voronoi.append(p.intersection(env)) + # # + # for pt_key in list(self.al_voronoi_geo_storage.keys()): + # for poly in new_voronoi: + # if self.al_voronoi_geo_storage[pt_key]['point'].within(poly) or \ + # self.al_voronoi_geo_storage[pt_key]['point'].intersects(poly): + # self.al_voronoi_geo_storage[pt_key]['geo'] = poly def generate_bilinear_geometry(self, pts): self.al_bilinear_geo_storage = pts @@ -878,7 +887,7 @@ class ToolLevelling(AppTool, CNCjob): pts_list = [] for k in self.al_voronoi_geo_storage: pts_list.append(self.al_voronoi_geo_storage[k]['point']) - self.generate_voronoi_geometry_2(pts=pts_list) + self.generate_voronoi_geometry(pts=pts_list) self.probing_gcode_text = self.probing_gcode(self.al_voronoi_geo_storage) else: diff --git a/requirements.txt b/requirements.txt index f42375ed..4277ddd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ simplejson qrcode>=6.1 rtree -foronoi>=1.0.3 +# foronoi>=1.0.3 shapely>=1.8.0 # ############################### From 220157641d22d1e930e46af0196a3f9854e77368 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 17 Jan 2023 01:02:52 +0200 Subject: [PATCH 30/55] - fixed the Voronoi generation in the Autolevelling Tool (removed the Foronoi package due of issues and now using the embedded functionality from Shapely) - fixed the enable of Levelling Tool only for CNCJob objects that were made from geometries that were segmented --- CHANGELOG.md | 2 ++ appPlugins/ToolLevelling.py | 48 ++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2441cea1..a0cacad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ CHANGELOG for FlatCAM Evo beta - fixed a decoding error in the Excellon parser - some PEP8 fixes +- fixed the Voronoi generation in the Autolevelling Tool (removed the Foronoi package due of issues and now using the embedded functionality from Shapely) +- fixed the enable of Levelling Tool only for CNCJob objects that were made from geometries that were segmented 15.01.2023 diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index 949b472f..b169ae9e 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -118,7 +118,7 @@ class ToolLevelling(AppTool, CNCjob): AppTool.install(self, icon, separator, shortcut='', **kwargs) def run(self, toggle=True): - self.app.defaults.report_usage("ToolFollow()") + self.app.defaults.report_usage("ToolLevelling()") if toggle: # if the splitter is hidden, display it @@ -294,18 +294,8 @@ class ToolLevelling(AppTool, CNCjob): self.ui.al_columns_entry.setDisabled(True) self.ui.al_columns_label.setDisabled(True) self.ui.al_method_lbl.setDisabled(True) - self.ui.al_method_radio.setDisabled(True) self.ui.al_method_radio.set_value('v') - - if loaded_obj and loaded_obj.is_segmented_gcode is True and loaded_obj.obj_options["type"] == 'Geometry': - self.ui.al_frame.setDisabled(False) - self.ui.al_mode_radio.set_value(loaded_obj.obj_options['tools_al_mode']) - self.on_controller_change() - - self.on_mode_radio(val=loaded_obj.obj_options['tools_al_mode']) - self.on_method_radio(val=loaded_obj.obj_options['tools_al_method']) - else: - self.ui.al_frame.setDisabled(True) + self.ui.al_method_radio.setDisabled(True) # Show/Hide Advanced Options app_mode = self.app.options["global_app_level"] @@ -319,6 +309,16 @@ class ToolLevelling(AppTool, CNCjob): self.build_tool_ui() + if loaded_obj and loaded_obj.is_segmented_gcode is True and loaded_obj.obj_options["type"] == 'Geometry': + self.ui.al_frame.setDisabled(False) + self.ui.al_mode_radio.set_value(loaded_obj.obj_options['tools_al_mode']) + self.on_controller_change() + + self.on_mode_radio(val=loaded_obj.obj_options['tools_al_mode']) + self.on_method_radio(val=loaded_obj.obj_options['tools_al_method']) + else: + self.ui.al_frame.setDisabled(True) + def on_object_changed(self): # load the object @@ -342,9 +342,8 @@ class ToolLevelling(AppTool, CNCjob): pool=self.app.pool) else: self.probing_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=obj_name + "_probing_shapes") - return - - self.ui.al_frame.setDisabled(True) + else: + self.ui.al_frame.setDisabled(True) def on_object_selection_changed(self, current, previous): found_idx = None @@ -997,13 +996,13 @@ class ToolLevelling(AppTool, CNCjob): self.build_al_table() if val == "manual": + self.ui.al_method_radio.set_value('v') self.ui.al_rows_entry.setDisabled(True) self.ui.al_rows_label.setDisabled(True) self.ui.al_columns_entry.setDisabled(True) self.ui.al_columns_label.setDisabled(True) self.ui.al_method_lbl.setDisabled(True) self.ui.al_method_radio.setDisabled(True) - self.ui.al_method_radio.set_value('v') else: self.ui.al_rows_entry.setDisabled(False) self.ui.al_rows_label.setDisabled(False) @@ -1792,6 +1791,7 @@ class LevelUI: self.al_box = QtWidgets.QVBoxLayout() self.al_box.setContentsMargins(0, 0, 0, 0) self.al_frame.setLayout(self.al_box) + self.al_frame.setDisabled(True) grid0 = GLay(v_spacing=5, h_spacing=3) self.al_box.addLayout(grid0) @@ -1814,7 +1814,7 @@ class LevelUI: # Tool Table Frame # ############################################################################################################# tt_frame = FCFrame() - self.tools_box.addWidget(tt_frame) + self.al_box.addWidget(tt_frame) # Grid Layout tool_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 0]) @@ -1844,10 +1844,10 @@ class LevelUI: "either through a file or directly, with the intent to get the height map\n" "that is to modify the original GCode to level the cutting height.") ) - self.tools_box.addWidget(self.probe_gc_label) + self.al_box.addWidget(self.probe_gc_label) tp_frame = FCFrame() - self.tools_box.addWidget(tp_frame) + self.al_box.addWidget(tp_frame) # Grid Layout param_grid = GLay(v_spacing=5, h_spacing=3) @@ -1950,7 +1950,7 @@ class LevelUI: param_grid.addWidget(self.al_rows_entry, 14, 1) self.al_add_button = FCButton(_("Add Probe Points")) - self.tools_box.addWidget(self.al_add_button) + self.al_box.addWidget(self.al_add_button) # ############################################################################################################# # Controller Frame @@ -1960,10 +1960,10 @@ class LevelUI: _("The kind of controller for which to generate\n" "height map gcode.") ) - self.tools_box.addWidget(self.al_controller_label) + self.al_box.addWidget(self.al_controller_label) self.c_frame = FCFrame() - self.tools_box.addWidget(self.c_frame) + self.al_box.addWidget(self.c_frame) ctrl_grid = GLay(v_spacing=5, h_spacing=3) self.c_frame.setLayout(ctrl_grid) @@ -2306,7 +2306,7 @@ class LevelUI: # height_lay.addStretch() height_lay.addWidget(self.view_h_gcode_button) - self.tools_box.addLayout(height_lay) + self.al_box.addLayout(height_lay) self.import_heights_button = FCButton(_("Import Height Map")) self.import_heights_button.setToolTip( @@ -2315,7 +2315,7 @@ class LevelUI: "over the original GCode therefore\n" "doing autolevelling.") ) - self.tools_box.addWidget(self.import_heights_button) + self.al_box.addWidget(self.import_heights_button) # self.h_gcode_button.hide() # self.import_heights_button.hide() From f42f05d93e0fa30675610ddefafccbcbe8b7e3e0 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Jan 2023 01:07:31 +0200 Subject: [PATCH 31/55] - in Autolevelling Tool made sure that when adding manual probe points mouse dragging with the right button is not counted as end of adding operation --- CHANGELOG.md | 5 + appPlugins/ToolLevelling.py | 201 ++++++++++++++++++++---------------- 2 files changed, 119 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0cacad3..b0d8b928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +18.01.2023 + +- in Autolevelling Tool made sure that when adding manual probe points mouse dragging with the right button is not counted as end of adding operation + + 16.01.2023 - fixed a decoding error in the Excellon parser diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index b169ae9e..9957c288 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -68,7 +68,6 @@ class ToolLevelling(AppTool, CNCjob): self.first_click = False self.cursor_pos = None - self.mouse_is_dragging = False # if mouse is dragging set the object True self.mouse_is_dragging = False @@ -509,107 +508,114 @@ class ToolLevelling(AppTool, CNCjob): # reset the al dict self.al_voronoi_geo_storage.clear() + if self.ui.al_mode_radio.get_value() == 'grid': + self.on_add_grid_points() + else: + self.on_add_manual_points() + + def on_add_grid_points(self): xmin, ymin, xmax, ymax = self.solid_geo.bounds - if self.ui.al_mode_radio.get_value() == 'grid': - width = abs(xmax - xmin) - height = abs(ymax - ymin) - cols = self.ui.al_columns_entry.get_value() - rows = self.ui.al_rows_entry.get_value() + width = abs(xmax - xmin) + height = abs(ymax - ymin) + cols = self.ui.al_columns_entry.get_value() + rows = self.ui.al_rows_entry.get_value() - dx = 0 if cols == 1 else width / (cols - 1) - dy = 0 if rows == 1 else height / (rows - 1) + dx = 0 if cols == 1 else width / (cols - 1) + dy = 0 if rows == 1 else height / (rows - 1) - points = [] - new_y = ymin - for x in range(rows): - new_x = xmin - for y in range(cols): - formatted_point = ( - self.app.dec_format(new_x, self.app.decimals), - self.app.dec_format(new_y, self.app.decimals) - ) - # do not add the point if is already added - if formatted_point not in points: - points.append(formatted_point) - new_x += dx - new_y += dy + points = [] + new_y = ymin + for x in range(rows): + new_x = xmin + for y in range(cols): + formatted_point = ( + self.app.dec_format(new_x, self.app.decimals), + self.app.dec_format(new_y, self.app.decimals) + ) + # do not add the point if is already added + if formatted_point not in points: + points.append(formatted_point) + new_x += dx + new_y += dy - pt_id = 0 - vor_pts_list = [] - bl_pts_list = [] - for point in points: - pt_id += 1 - pt = Point(point) - vor_pts_list.append(pt) - bl_pts_list.append((point[0], point[1], 0.0)) - new_dict = { - 'point': pt, - 'geo': None, - 'height': 0.0 - } - self.al_voronoi_geo_storage[pt_id] = deepcopy(new_dict) - - al_method = self.ui.al_method_radio.get_value() - if al_method == 'v': - if VORONOI_ENABLED is True: - self.generate_voronoi_geometry(pts=vor_pts_list) - # generate Probing GCode - self.probing_gcode_text = self.probing_gcode(storage=self.al_voronoi_geo_storage) - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Voronoi function can not be loaded.\n" - "Shapely >= 1.8 is required")) - else: - self.generate_bilinear_geometry(pts=bl_pts_list) - # generate Probing GCode - self.probing_gcode_text = self.probing_gcode(storage=self.al_bilinear_geo_storage) - - self.build_al_table_sig.emit() - if self.ui.plot_probing_pts_cb.get_value(): - self.show_probing_geo(state=True, reset=True) - else: - # clear probe shapes - self.plot_probing_geo(None, False) - - else: - f_probe_pt = Point([xmin, xmin]) - int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] - new_id = max(int_keys) + 1 if int_keys else 1 + pt_id = 0 + vor_pts_list = [] + bl_pts_list = [] + for point in points: + pt_id += 1 + pt = Point(point) + vor_pts_list.append(pt) + bl_pts_list.append((point[0], point[1], 0.0)) new_dict = { - 'point': f_probe_pt, + 'point': pt, 'geo': None, 'height': 0.0 } - self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) + self.al_voronoi_geo_storage[pt_id] = deepcopy(new_dict) - radius = 0.3 if self.units == 'MM' else 0.012 - fprobe_pt_buff = f_probe_pt.buffer(radius) - - self.app.inform.emit(_("Click on canvas to add a Probe Point...")) - self.app.options['global_selection_shape'] = False - - if self.app.use_3d_engine: - self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent) - self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) + al_method = self.ui.al_method_radio.get_value() + if al_method == 'v': + if VORONOI_ENABLED is True: + self.generate_voronoi_geometry(pts=vor_pts_list) + # generate Probing GCode + self.probing_gcode_text = self.probing_gcode(storage=self.al_voronoi_geo_storage) else: - self.app.plotcanvas.graph_event_disconnect(self.app.kp) - self.app.plotcanvas.graph_event_disconnect(self.app.mp) - self.app.plotcanvas.graph_event_disconnect(self.app.mr) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Voronoi function can not be loaded.\n" + "Shapely >= 1.8 is required")) + else: + self.generate_bilinear_geometry(pts=bl_pts_list) + # generate Probing GCode + self.probing_gcode_text = self.probing_gcode(storage=self.al_bilinear_geo_storage) - self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) - self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) + self.build_al_table_sig.emit() + if self.ui.plot_probing_pts_cb.get_value(): + self.show_probing_geo(state=True, reset=True) + else: + # clear probe shapes + self.plot_probing_geo(None, False) - self.mouse_events_connected = True + def on_add_manual_points(self): + xmin, ymin, xmax, ymax = self.solid_geo.bounds + f_probe_pt = Point([xmin, xmin]) + int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] + new_id = max(int_keys) + 1 if int_keys else 1 + new_dict = { + 'point': f_probe_pt, + 'geo': None, + 'height': 0.0 + } + self.al_voronoi_geo_storage[new_id] = deepcopy(new_dict) - self.build_al_table_sig.emit() - if self.ui.plot_probing_pts_cb.get_value(): - self.show_probing_geo(state=True, reset=True) - else: - # clear probe shapes - self.plot_probing_geo(None, False) + radius = 0.3 if self.units == 'MM' else 0.012 + fprobe_pt_buff = f_probe_pt.buffer(radius) - self.plot_probing_geo(geometry=fprobe_pt_buff, visibility=True, custom_color="#0000FFFA") + self.app.inform.emit(_("Click on canvas to add a Probe Point...")) + self.app.options['global_selection_shape'] = False + + if self.app.use_3d_engine: + self.app.plotcanvas.graph_event_disconnect('key_press', self.app.ui.keyPressEvent) + self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot) + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot) + else: + self.app.plotcanvas.graph_event_disconnect(self.app.kp) + self.app.plotcanvas.graph_event_disconnect(self.app.mp) + self.app.plotcanvas.graph_event_disconnect(self.app.mr) + + self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press) + self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) + self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move) + + self.mouse_events_connected = True + + self.build_al_table_sig.emit() + if self.ui.plot_probing_pts_cb.get_value(): + self.show_probing_geo(state=True, reset=True) + else: + # clear probe shapes + self.plot_probing_geo(None, False) + + self.plot_probing_geo(geometry=fprobe_pt_buff, visibility=True, custom_color="#0000FFFA") def show_probing_geo(self, state, reset=False): self.app.log.debug("ToolLevelling.show_probing_geo() -> %s" % ('cleared' if state is False else 'displayed')) @@ -802,6 +808,24 @@ class ToolLevelling(AppTool, CNCjob): def generate_bilinear_geometry(self, pts): self.al_bilinear_geo_storage = pts + def on_mouse_move(self, event): + """ + Callback for the mouse motion event over the plot. + + :param event: Contains information about the event. + :return: None + """ + + if self.app.use_3d_engine: + self.mouse_is_dragging = event.is_dragging + else: + self.mouse_is_dragging = self.app.plotcanvas.is_dragging + + # So it can receive key presses but not when the Tcl Shell is active + if not self.app.ui.shell_dock.isVisible(): + if not self.app.plotcanvas.native.hasFocus(): + self.app.plotcanvas.native.setFocus() + # To be called after clicking on the plot. def on_mouse_click_release(self, event): @@ -863,9 +887,11 @@ class ToolLevelling(AppTool, CNCjob): if self.app.use_3d_engine: self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) else: self.app.plotcanvas.graph_event_disconnect(self.kp) self.app.plotcanvas.graph_event_disconnect(self.mr) + self.app.plotcanvas.graph_event_disconnect(self.mm) self.app.kp = self.app.plotcanvas.graph_event_connect('key_press', self.app.ui.keyPressEvent) self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot) @@ -941,6 +967,7 @@ class ToolLevelling(AppTool, CNCjob): if self.app.use_3d_engine: self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press) self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move) else: self.app.plotcanvas.graph_event_disconnect(self.kp) self.app.plotcanvas.graph_event_disconnect(self.mr) From c85d04bc0a5658710254dce1baf299e025eebaea Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Jan 2023 01:32:28 +0200 Subject: [PATCH 32/55] - in Autolevelling Tool, when adding manual probe points, added an option that prevent adding a probe point within a drill hole of an Excellon object that is plotted on canvas --- CHANGELOG.md | 2 +- appPlugins/ToolLevelling.py | 33 +++++++++++++++++++++++++++++++-- defaults.py | 2 ++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d8b928..a7310c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ CHANGELOG for FlatCAM Evo beta 18.01.2023 - in Autolevelling Tool made sure that when adding manual probe points mouse dragging with the right button is not counted as end of adding operation - +- in Autolevelling Tool, when adding manual probe points, added an option that prevent adding a probe point within a drill hole of an Excellon object that is plotted on canvas 16.01.2023 diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index 9957c288..eeaeea11 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -828,7 +828,6 @@ class ToolLevelling(AppTool, CNCjob): # To be called after clicking on the plot. def on_mouse_click_release(self, event): - if self.app.use_3d_engine: event_pos = event.pos right_button = 2 @@ -845,8 +844,9 @@ class ToolLevelling(AppTool, CNCjob): # do paint single only for left mouse clicks if event.button == 1: - pos = self.app.plotcanvas.translate_coords(event_pos) + check_for_exc_hole = self.ui.avoid_exc_holes_cb.get_value() + pos = self.app.plotcanvas.translate_coords(event_pos) # use the snapped position as reference snapped_pos = self.app.geo_editor.snap(pos[0], pos[1]) @@ -855,6 +855,7 @@ class ToolLevelling(AppTool, CNCjob): if (snapped_pos[0], snapped_pos[1]) in old_points_coords: return + # Clicked Point probe_pt = Point(snapped_pos) xxmin, yymin, xxmax, yymax = self.solid_geo.bounds @@ -863,6 +864,16 @@ class ToolLevelling(AppTool, CNCjob): self.app.inform.emit(_("Point is not within the object area. Choose another point.")) return + # check if chosen point is within an Excellon drill hole geometry + if check_for_exc_hole is True: + for obj_in_collection in self.app.collection.get_list(): + if obj_in_collection.kind == 'excellon' and obj_in_collection.obj_options['plot'] is True: + exc_solid_geometry = MultiPolygon(obj_in_collection.solid_geometry) + for exc_geo in exc_solid_geometry.geoms: + if probe_pt.within(exc_geo): + self.app.inform.emit(_("Point on an Excellon drill hole. Choose another point.")) + return + int_keys = [int(k) for k in self.al_voronoi_geo_storage.keys()] new_id = max(int_keys) + 1 if int_keys else 1 new_dict = { @@ -1847,6 +1858,7 @@ class LevelUI: tool_grid = GLay(v_spacing=5, h_spacing=3, c_stretch=[0, 0]) tt_frame.setLayout(tool_grid) + # Probe Points table self.al_probe_points_table = FCTable() self.al_probe_points_table.setColumnCount(3) self.al_probe_points_table.setColumnWidth(0, 20) @@ -1854,6 +1866,7 @@ class LevelUI: tool_grid.addWidget(self.al_probe_points_table, 0, 0, 1, 2) + # Plot Probe Points self.plot_probing_pts_cb = FCCheckBox(_("Plot probing points")) self.plot_probing_pts_cb.setToolTip( _("Plot the probing points in the table.\n" @@ -1862,6 +1875,13 @@ class LevelUI: ) tool_grid.addWidget(self.plot_probing_pts_cb, 2, 0, 1, 2) + # Avoid Excellon holes + self.avoid_exc_holes_cb = FCCheckBox(_("Avoid Excellon holes")) + self.avoid_exc_holes_cb.setToolTip( + _("When active, the user cannot add probe points over a drill hole.") + ) + tool_grid.addWidget(self.avoid_exc_holes_cb, 4, 0, 1, 2) + # ############################################################################################################# # ############### Probe GCode Generation ###################################################################### # ############################################################################################################# @@ -2364,6 +2384,9 @@ class LevelUI: # ############################ FINISHED GUI ################################### # ############################################################################# + self.plot_probing_pts_cb.stateChanged.connect(self.on_plot_points_changed) + self.avoid_exc_holes_cb.stateChanged.connect(self.on_avoid_exc_holes_changed) + 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"), @@ -2380,3 +2403,9 @@ class LevelUI: (_("Edited value is out of range"), minval, maxval), False) else: self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) + + def on_plot_points_changed(self, state): + self.app.defaults["tools_al_plot_points"] = False if not state else True + + def on_avoid_exc_holes_changed(self, state): + self.app.defaults["tools_al_avoid_exc_holes"] = False if not state else True diff --git a/defaults.py b/defaults.py index 32135555..4c79a9d0 100644 --- a/defaults.py +++ b/defaults.py @@ -503,6 +503,8 @@ class AppDefaults: "tools_mill_search_time": 3, # Autolevelling Plugin + "tools_al_plot_points": False, + "tools_al_avoid_exc_holes": False, "tools_al_status": False, "tools_al_mode": 'grid', "tools_al_method": 'v', From 9ce9c3cb54f39e46aa571f08c3fe5806a2007f11 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 18 Jan 2023 18:22:33 +0200 Subject: [PATCH 33/55] - in Autolevelling Plugin, preventing the add of a probe point on Excellon hole checkbox is not disabled except for the Manual mode - in Autolevelling Plugin, the status of soem checkoxes (avoid Excellon holes and plot points) is stored between app restarts - in Isolation Plugin, added an convenience shortcut button to launch the Milling Plugin. --- CHANGELOG.md | 7 +++++-- appPlugins/ToolIsolation.py | 15 ++++++++++++++- appPlugins/ToolLevelling.py | 9 +++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7310c70..84a12224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,11 @@ CHANGELOG for FlatCAM Evo beta 18.01.2023 -- in Autolevelling Tool made sure that when adding manual probe points mouse dragging with the right button is not counted as end of adding operation -- in Autolevelling Tool, when adding manual probe points, added an option that prevent adding a probe point within a drill hole of an Excellon object that is plotted on canvas +- in Autolevelling Plugin made sure that when adding manual probe points mouse dragging with the right button is not counted as end of adding operation +- in Autolevelling Plugin, when adding manual probe points, added an option that prevent adding a probe point within a drill hole of an Excellon object that is plotted on canvas +- in Autolevelling Plugin, preventing the add of a probe point on Excellon hole checkbox is not disabled except for the Manual mode +- in Autolevelling Plugin, the status of soem checkoxes (avoid Excellon holes and plot points) is stored between app restarts +- in Isolation Plugin, added an convenience shortcut button to launch the Milling Plugin. 16.01.2023 diff --git a/appPlugins/ToolIsolation.py b/appPlugins/ToolIsolation.py index 6e7f05b6..0318daba 100644 --- a/appPlugins/ToolIsolation.py +++ b/appPlugins/ToolIsolation.py @@ -3964,6 +3964,9 @@ class IsoUI: # ############################################################################################################# # Generate Geometry object # ############################################################################################################# + gen_hlay = QtWidgets.QHBoxLayout() + self.tools_box.addLayout(gen_hlay) + self.generate_iso_button = FCButton("%s" % _("Generate Geometry"), bold=True) self.generate_iso_button.setIcon(QtGui.QIcon(self.app.resource_location + '/geometry32.png')) self.generate_iso_button.setToolTip( @@ -3977,7 +3980,15 @@ class IsoUI: "inside the actual Gerber feature, use a negative tool\n" "diameter above.") ) - self.tools_box.addWidget(self.generate_iso_button) + gen_hlay.addWidget(self.generate_iso_button, stretch=1) + + # Milling Plugin shortcut + self.milling_button = QtWidgets.QToolButton() + self.milling_button.setIcon(QtGui.QIcon(self.app.resource_location + '/milling_tool32.png')) + self.milling_button.setToolTip( + _("Generate a CNCJob by milling a Geometry.") + ) + gen_hlay.addWidget(self.milling_button) self.create_buffer_button = FCButton(_('Buffer Solid Geometry')) self.create_buffer_button.setToolTip( @@ -4000,6 +4011,8 @@ class IsoUI: # ############################ FINSIHED GUI ################################### # ############################################################################# + self.milling_button.clicked.connect(lambda: self.app.milling_tool.run()) + 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"), diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index eeaeea11..1fcae386 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -269,6 +269,9 @@ class ToolLevelling(AppTool, CNCjob): self.to_form() self.on_controller_change_alter_ui() + self.ui.plot_probing_pts_cb.set_value(self.app.options["tools_al_plot_points"]) + self.ui.avoid_exc_holes_cb.set_value(self.app.options["tools_al_avoid_exc_holes"]) + self.ui.al_probe_points_table.setRowCount(0) self.ui.al_probe_points_table.resizeColumnsToContents() self.ui.al_probe_points_table.resizeRowsToContents() @@ -1041,6 +1044,7 @@ class ToolLevelling(AppTool, CNCjob): self.ui.al_columns_label.setDisabled(True) self.ui.al_method_lbl.setDisabled(True) self.ui.al_method_radio.setDisabled(True) + self.ui.avoid_exc_holes_cb.setDisabled(False) else: self.ui.al_rows_entry.setDisabled(False) self.ui.al_rows_label.setDisabled(False) @@ -1049,6 +1053,7 @@ class ToolLevelling(AppTool, CNCjob): self.ui.al_method_lbl.setDisabled(False) self.ui.al_method_radio.setDisabled(False) self.ui.al_method_radio.set_value(self.app.options['tools_al_method']) + self.ui.avoid_exc_holes_cb.setDisabled(True) def on_method_radio(self, val): if val == 'b': @@ -2405,7 +2410,7 @@ class LevelUI: self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False) def on_plot_points_changed(self, state): - self.app.defaults["tools_al_plot_points"] = False if not state else True + self.app.options["tools_al_plot_points"] = False if not state else True def on_avoid_exc_holes_changed(self, state): - self.app.defaults["tools_al_avoid_exc_holes"] = False if not state else True + self.app.options["tools_al_avoid_exc_holes"] = False if not state else True From 9d8852e611b32df734d21c3de62bc1db6b01850e Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 20 Jan 2023 19:23:34 +0200 Subject: [PATCH 34/55] - added a new method for bilinear interpolation --- CHANGELOG.md | 6 ++- appCommon/bilinear.py | 77 ++++++++++++++++++++----------- appCommon/bilinearInterpolator.py | 1 + 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84a12224..9c212d4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,16 @@ CHANGELOG for FlatCAM Evo beta ================================================= +19.01.2023 + +- added a new method for bilinear interpolation + 18.01.2023 - in Autolevelling Plugin made sure that when adding manual probe points mouse dragging with the right button is not counted as end of adding operation - in Autolevelling Plugin, when adding manual probe points, added an option that prevent adding a probe point within a drill hole of an Excellon object that is plotted on canvas - in Autolevelling Plugin, preventing the add of a probe point on Excellon hole checkbox is not disabled except for the Manual mode -- in Autolevelling Plugin, the status of soem checkoxes (avoid Excellon holes and plot points) is stored between app restarts +- in Autolevelling Plugin, the status of some checkboxes (avoid Excellon holes and plot points) is stored between app restarts - in Isolation Plugin, added an convenience shortcut button to launch the Milling Plugin. 16.01.2023 diff --git a/appCommon/bilinear.py b/appCommon/bilinear.py index 336aa407..3eb08809 100644 --- a/appCommon/bilinear.py +++ b/appCommon/bilinear.py @@ -69,12 +69,6 @@ class BilinearInterpolation(object): self.values = values self.x_length = x_length self.y_length = y_length - self.extrapolate = True - - # slopes = self.slopes = [] - # for j in range(y_length): - # intervals = zip(x_index, x_index[1:], values[j], values[j][1:]) - # slopes.append([(y2 - y1) / (x2 - x1) for x1, x2, y1, y2 in intervals]) def __call__(self, x, y): # local lookups @@ -83,29 +77,23 @@ class BilinearInterpolation(object): i = bisect_left(x_index, x) - 1 j = bisect_left(y_index, y) - 1 - if self.extrapolate: - # fix x index - if i == -1: - x_slice = slice(None, 2) - elif i == self.x_length - 1: - x_slice = slice(-2, None) - else: - x_slice = slice(i, i + 2) - - # fix y index - if j == -1: - j = 0 - y_slice = slice(None, 2) - elif j == self.y_length - 1: - j = -2 - y_slice = slice(-2, None) - else: - y_slice = slice(j, j + 2) + # fix x index + if i == -1: + x_slice = slice(None, 2) + elif i == self.x_length - 1: + x_slice = slice(-2, None) else: - if i == -1 or i == self.x_length - 1: - raise ValueError("Extrapolation not allowed!") - if j == -1 or j == self.y_length - 1: - raise ValueError("Extrapolation not allowed!") + x_slice = slice(i, i + 2) + + # fix y index + if j == -1: + j = 0 + y_slice = slice(None, 2) + elif j == self.y_length - 1: + j = -2 + y_slice = slice(-2, None) + else: + y_slice = slice(j, j + 2) # if the extrapolations is False this will fail x1, x2 = x_index[x_slice] @@ -117,3 +105,36 @@ class BilinearInterpolation(object): z21 * (x - x1) * (y2 - y) + z12 * (x2 - x) * (y - y1) + z22 * (x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1)) + + +def bilinear_interpolation(x, y, points): + """ + https://stackoverflow.com/questions/8661537/how-to-perform-bilinear-interpolation-in-python + + Interpolate (x,y) from values associated with four points. + + The four points are a list of four triplets: (x, y, value). + The four points can be in any order. They should form a rectangle. + + >>> bilinear_interpolation(12, 5.5, + ... [(10, 4, 100), + ... (20, 4, 200), + ... (10, 6, 150), + ... (20, 6, 300)]) + 165.0 + """ + # See formula at: http://en.wikipedia.org/wiki/Bilinear_interpolation + + points = sorted(points) # order points by x, then by y + (x1, y1, q11), (_x1, y2, q12), (x2, _y1, q21), (_x2, _y2, q22) = points + + if x1 != _x1 or x2 != _x2 or y1 != _y1 or y2 != _y2: + raise ValueError('points do not form a rectangle') + if not x1 <= x <= x2 or not y1 <= y <= y2: + raise ValueError('(x, y) not within the rectangle') + + return (q11 * (x2 - x) * (y2 - y) + + q21 * (x - x1) * (y2 - y) + + q12 * (x2 - x) * (y - y1) + + q22 * (x - x1) * (y - y1) + ) / ((x2 - x1) * (y2 - y1) + 0.0) diff --git a/appCommon/bilinearInterpolator.py b/appCommon/bilinearInterpolator.py index 7cb46572..03c8fb61 100644 --- a/appCommon/bilinearInterpolator.py +++ b/appCommon/bilinearInterpolator.py @@ -1,3 +1,4 @@ + # import csv import math import numpy as np From 33de5314b15a2773e15abf7fab2e907e0cf1b8c8 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 1 Mar 2023 18:15:10 +0200 Subject: [PATCH 35/55] - in Tool Milling made sure that deleting the only tool will not crash the application - added a new parameter in Preferences to control the number of processes created by the Pool() - more processes better performance but also a lot of memory consumed - made sure that the display of messages in the Status Bar is done asap --- CHANGELOG.md | 6 +++++ appGUI/preferences/PreferencesUIManager.py | 1 + .../general/GeneralAppPrefGroupUI.py | 27 ++++++++++++++----- appMain.py | 5 ++-- appPlugins/ToolMilling.py | 2 +- appPool.py | 2 +- defaults.py | 1 + 7 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c212d4e..dc622ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM Evo beta ================================================= +01.03.2023 + +- in Tool Milling made sure that deleting the only tool will not crash the application +- added a new parameter in Preferences to control the number of processes created by the Pool() - more processes better performance but also a lot of memory consumed +- made sure that the display of messages in the Status Bar is done asap + 19.01.2023 - added a new method for bilinear interpolation diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index f6b9e37e..4f545226 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -71,6 +71,7 @@ class PreferencesUIManager(QtCore.QObject): "global_send_stats": self.ui.general_pref_form.general_app_group.send_stats_cb, "global_worker_number": self.ui.general_pref_form.general_app_group.worker_number_sb, + "global_process_number": self.ui.general_pref_form.general_app_group.process_number_sb, "global_tolerance": self.ui.general_pref_form.general_app_group.tol_entry, "global_compression_level": self.ui.general_pref_form.general_app_group.compress_spinner, diff --git a/appGUI/preferences/general/GeneralAppPrefGroupUI.py b/appGUI/preferences/general/GeneralAppPrefGroupUI.py index 16d67696..16453bf7 100644 --- a/appGUI/preferences/general/GeneralAppPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralAppPrefGroupUI.py @@ -119,16 +119,29 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("The number of Qthreads made available to the App.\n" "A bigger number may finish the jobs more quickly but\n" "depending on your computer speed, may make the App\n" - "unresponsive. Can have a value between 2 and 16.\n" + "unresponsive. Can have a value between 2 and 32.\n" "Default value is 2.\n" "After change, it will be applied at next App start.") ) self.worker_number_sb = FCSpinner() - self.worker_number_sb.set_range(2, 16) + self.worker_number_sb.set_range(2, 32) grid1.addWidget(self.worker_number_label, 2, 0) grid1.addWidget(self.worker_number_sb, 2, 1) + # Process Numbers + self.process_number_label = FCLabel('%s:' % _('Process number')) + self.process_number_label.setToolTip( + _("The number of processes.\n" + "A larger number may improve performance but it will require more memory.\n" + "After change, it will be applied at next App start.") + ) + self.process_number_sb = FCSpinner() + self.process_number_sb.set_range(2, 32) + + grid1.addWidget(self.process_number_label, 4, 0) + grid1.addWidget(self.process_number_sb, 4, 1) + # Geometric tolerance tol_label = FCLabel('%s:' % _("Geo Tolerance")) tol_label.setToolTip(_( @@ -143,8 +156,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): self.tol_entry.setSingleStep(0.001) self.tol_entry.set_precision(6) - grid1.addWidget(tol_label, 4, 0) - grid1.addWidget(self.tol_entry, 4, 1) + grid1.addWidget(tol_label, 6, 0) + grid1.addWidget(self.tol_entry, 6, 1) # Portability self.portability_cb = FCCheckBox('%s' % _('Portable app')) @@ -153,7 +166,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): "which means that the preferences files will be saved\n" "in the application folder, in the lib\\config subfolder.")) - grid1.addWidget(self.portability_cb, 6, 0, 1, 2) + grid1.addWidget(self.portability_cb, 8, 0, 1, 2) # Verbose Log self.verbose_label = FCLabel('%s:' % _('Verbose log')) @@ -166,8 +179,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): self.verbose_combo = FCComboBox2() self.verbose_combo.addItems(['0','1', '2']) - grid1.addWidget(self.verbose_label, 8, 0) - grid1.addWidget(self.verbose_combo, 8, 1) + grid1.addWidget(self.verbose_label, 10, 0) + grid1.addWidget(self.verbose_combo, 10, 1) # ############################################################################################################# # Grid0 Frame diff --git a/appMain.py b/appMain.py index 547ae5e2..93482057 100644 --- a/appMain.py +++ b/appMain.py @@ -634,7 +634,7 @@ class App(QtCore.QObject): # ########################################################################################################### # ###################################### CREATE MULTIPROCESSING POOL ####################################### # ########################################################################################################### - self.pool = Pool() + self.pool = Pool(processes=self.options["global_process_number"]) # ########################################################################################################### # ###################################### Clear GUI Settings - once at first start ########################### @@ -1564,7 +1564,7 @@ class App(QtCore.QObject): """ self.pool.close() - self.pool = Pool() + self.pool = Pool(processes=self.options["global_process_number"]) self.pool_recreated.emit(self.pool) gc.collect() @@ -2743,6 +2743,7 @@ class App(QtCore.QObject): # is not printed over and over on the shell if msg != '' and shell_echo is True: self.shell_message(msg) + QtWidgets.QApplication.processEvents() def info_shell(self, msg, new_line=True): """ diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index 11fc626b..fb4d6cee 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -402,7 +402,7 @@ class ToolMilling(AppTool, Excellon): _("Copy"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png")) self.ui.geo_tools_table.addContextMenu( - _("Delete"), lambda: self.on_tool_delete(clicked_signal=None, all_tools=None), + _("Delete"), lambda: self.on_tool_delete(all_tools=None), icon=QtGui.QIcon(self.app.resource_location + "/trash16.png")) # ############################################################################################################# diff --git a/appPool.py b/appPool.py index 464fcbac..6333159c 100644 --- a/appPool.py +++ b/appPool.py @@ -23,7 +23,7 @@ class WorkerPool(QtCore.QObject): def __init__(self): super(WorkerPool, self).__init__() - self.pool = Pool(cpu_count()) + self.pool = Pool(int(cpu_count() / 4)) def add_task(self, task): print("adding task", task) diff --git a/defaults.py b/defaults.py index 4c79a9d0..3cd24993 100644 --- a/defaults.py +++ b/defaults.py @@ -100,6 +100,7 @@ class AppDefaults: "global_version_check": True, "global_send_stats": True, "global_worker_number": int((os.cpu_count()) / 2) if os.cpu_count() > 4 else 1, + "global_process_number": int((os.cpu_count()) / 4) if os.cpu_count() > 4 else 1, "global_tolerance": 0.005, "global_save_compressed": True, From 3b3c87e953dda07005ea0eb5c710ad86656353a8 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 6 Mar 2023 16:40:49 +0200 Subject: [PATCH 36/55] - fixed some possible issues due of changes in version 2.0 of Shapely - removed the import * statement from most of the app --- Bookmark.py | 6 +- CHANGELOG.md | 5 + FlatCAM.py | 1 + appDatabase.py | 3 +- appEditors/AppExcEditor.py | 6 +- appEditors/AppGeoEditor.py | 6 +- appEditors/AppGerberEditor.py | 119 +- appEditors/AppTextEditor.py | 2 +- appEditors/appGCodeEditor.py | 5 +- appEditors/exc_plugins/ExcCopyPlugin.py | 10 +- appEditors/exc_plugins/ExcDrillArrayPlugin.py | 11 +- appEditors/exc_plugins/ExcDrillPlugin.py | 8 +- appEditors/exc_plugins/ExcResizePlugin.py | 8 +- appEditors/exc_plugins/ExcSlotArrayPlugin.py | 8 +- appEditors/exc_plugins/ExcSlotPlugin.py | 8 +- appEditors/geo_plugins/GeoBufferPlugin.py | 10 +- appEditors/geo_plugins/GeoCirclePlugin.py | 11 +- appEditors/geo_plugins/GeoCopyPlugin.py | 8 +- appEditors/geo_plugins/GeoPaintPlugin.py | 14 +- appEditors/geo_plugins/GeoPathPlugin.py | 8 +- appEditors/geo_plugins/GeoRectanglePlugin.py | 11 +- .../geo_plugins/GeoSimplificationPlugin.py | 37 +- appEditors/geo_plugins/GeoTextPlugin.py | 12 +- .../geo_plugins/GeoTransformationPlugin.py | 11 +- appEditors/grb_plugins/GrbBufferPlugin.py | 11 +- appEditors/grb_plugins/GrbCommon.py | 9 +- appEditors/grb_plugins/GrbCopyPlugin.py | 9 +- appEditors/grb_plugins/GrbPadArrayPlugin.py | 10 +- appEditors/grb_plugins/GrbPadPlugin.py | 9 +- .../grb_plugins/GrbSimplificationPlugin.py | 10 +- appEditors/grb_plugins/GrbTrackPlugin.py | 9 +- .../grb_plugins/GrbTransformationPlugin.py | 11 +- appEditors/grb_plugins/GrberRegionPlugin.py | 8 +- appGUI/preferences/PreferencesSectionUI.py | 1 + appGUI/preferences/PreferencesUIManager.py | 6 +- .../cncjob/CNCJobAdvOptPrefGroupUI.py | 1 + .../cncjob/CNCJobEditorPrefGroupUI.py | 1 + .../cncjob/CNCJobGenPrefGroupUI.py | 1 + .../cncjob/CNCJobOptPrefGroupUI.py | 1 + appGUI/preferences/cncjob/CNCJobPPGroupUI.py | 2 + .../preferences/cncjob/CNCJobPreferencesUI.py | 1 + .../excellon/ExcellonAdvOptPrefGroupUI.py | 2 + .../excellon/ExcellonExpPrefGroupUI.py | 2 + .../excellon/ExcellonGenPrefGroupUI.py | 2 + .../excellon/ExcellonOptPrefGroupUI.py | 2 + .../excellon/ExcellonPreferencesUI.py | 1 + .../general/GeneralAPPSetGroupUI.py | 1 + .../general/GeneralAppPrefGroupUI.py | 1 + .../general/GeneralAppSettingsGroupUI.py | 7 +- .../general/GeneralGUIPrefGroupUI.py | 1 + .../general/GeneralPreferencesUI.py | 1 + .../geometry/GeometryAdvOptPrefGroupUI.py | 1 + .../geometry/GeometryEditorPrefGroupUI.py | 1 + .../geometry/GeometryExpPrefGroupUI.py | 1 + .../geometry/GeometryGenPrefGroupUI.py | 1 + .../geometry/GeometryOptPrefGroupUI.py | 1 + .../geometry/GeometryPreferencesUI.py | 1 + .../gerber/GerberAdvOptPrefGroupUI.py | 1 + .../gerber/GerberEditorPrefGroupUI.py | 1 + .../gerber/GerberExpPrefGroupUI.py | 1 + .../gerber/GerberGenPrefGroupUI.py | 1 + .../gerber/GerberOptPrefGroupUI.py | 1 + .../preferences/gerber/GerberPreferencesUI.py | 1 + .../tools/Plugins2PreferencesUI.py | 1 + .../tools/PluginsEngravingPreferencesUI.py | 1 + .../preferences/tools/PluginsPreferencesUI.py | 1 + .../tools/Tools2CThievingPrefGroupUI.py | 1 + .../tools/Tools2ExtractPrefGroupUI.py | 1 + .../tools/Tools2FiducialsPrefGroupUI.py | 1 + .../tools/Tools2InvertPrefGroupUI.py | 1 + .../tools/Tools2OptimalPrefGroupUI.py | 1 + .../tools/Tools2PunchGerberPrefGroupUI.py | 1 + .../tools/Tools2QRCodePrefGroupUI.py | 1 + .../tools/Tools2RulesCheckPrefGroupUI.py | 1 + .../tools/Tools2sidedPrefGroupUI.py | 1 + .../tools/ToolsCalculatorsPrefGroupUI.py | 1 + .../tools/ToolsCutoutPrefGroupUI.py | 1 + .../tools/ToolsDrillPrefGroupUI.py | 1 + .../preferences/tools/ToolsFilmPrefGroupUI.py | 1 + .../preferences/tools/ToolsISOPrefGroupUI.py | 1 + .../tools/ToolsLevelPrefGroupUI.py | 1 + .../tools/ToolsMarkersPrefGroupUI.py | 1 + .../preferences/tools/ToolsMillPrefGroupUI.py | 1 + .../preferences/tools/ToolsNCCPrefGroupUI.py | 1 + .../tools/ToolsPaintPrefGroupUI.py | 1 + .../tools/ToolsPanelizePrefGroupUI.py | 1 + .../tools/ToolsSolderpastePrefGroupUI.py | 1 + .../preferences/tools/ToolsSubPrefGroupUI.py | 1 + .../tools/ToolsTransformPrefGroupUI.py | 1 + .../utilities/AutoCompletePrefGroupUI.py | 1 + .../preferences/utilities/FAExcPrefGroupUI.py | 1 + .../preferences/utilities/FAGcoPrefGroupUI.py | 1 + .../preferences/utilities/FAGrbPrefGroupUI.py | 1 + .../utilities/UtilPreferencesUI.py | 1 + appHandlers/AppIO.py | 5 +- appMain.py | 159 +- appObjects/AppObject.py | 4 +- appObjects/AppObjectTemplate.py | 15 +- appObjects/CNCJobObject.py | 15 +- appObjects/DocumentObject.py | 4 + appObjects/ExcellonObject.py | 10 +- appObjects/GeometryObject.py | 19 +- appObjects/GerberObject.py | 10 +- appObjects/ScriptObject.py | 6 +- appParsers/ParseDXF.py | 11 +- appParsers/ParseExcellon.py | 4 +- appParsers/ParseFont.py | 1 + appParsers/ParseGerber.py | 23 +- appParsers/ParseHPGL2.py | 1 - appPlugins/ToolAlignObjects.py | 14 +- appPlugins/ToolCalculators.py | 14 +- appPlugins/ToolCopperThieving.py | 21 +- appPlugins/ToolCutOut.py | 24 +- appPlugins/ToolDblSided.py | 16 +- appPlugins/ToolDistance.py | 18 +- appPlugins/ToolDrilling.py | 21 +- appPlugins/ToolEtchCompensation.py | 18 +- appPlugins/ToolExtract.py | 14 +- appPlugins/ToolFiducials.py | 21 +- appPlugins/ToolFilm.py | 17 +- appPlugins/ToolFollow.py | 20 +- appPlugins/ToolImage.py | 16 +- appPlugins/ToolInvertGerber.py | 15 +- appPlugins/ToolIsolation.py | 35 +- appPlugins/ToolLevelling.py | 25 +- appPlugins/ToolMarkers.py | 16 +- appPlugins/ToolMilling.py | 21 +- appPlugins/ToolMove.py | 16 +- appPlugins/ToolNCC.py | 55 +- appPlugins/ToolObjectDistance.py | 15 +- appPlugins/ToolOptimal.py | 19 +- appPlugins/ToolPDF.py | 23 +- appPlugins/ToolPaint.py | 47 +- appPlugins/ToolPanelize.py | 19 +- appPlugins/ToolPcbWizard.py | 17 +- appPlugins/ToolPunchGerber.py | 52 +- appPlugins/ToolQRCode.py | 32 +- appPlugins/ToolReport.py | 21 +- appPlugins/ToolRulesCheck.py | 29 +- appPlugins/ToolShell.py | 6 +- appPlugins/ToolSolderPaste.py | 36 +- appPlugins/ToolSub.py | 28 +- appPlugins/ToolTransform.py | 16 +- appPool.py | 1 + appPreProcessor.py | 1 - appTool.py | 26 +- appWorkerStack.py | 1 + camlib.py | 40 +- preprocessors/Berta_CNC.py | 3 +- preprocessors/Check_points.py | 3 +- preprocessors/Default_no_M6.py | 3 +- preprocessors/GRBL_11.py | 3 +- preprocessors/GRBL_11_no_M6.py | 3 +- preprocessors/GRBL_laser.py | 3 +- preprocessors/GRBL_laser_z.py | 2 +- preprocessors/ISEL_CNC.py | 3 +- preprocessors/ISEL_ICP_CNC.py | 3 +- preprocessors/Line_xyz.py | 3 +- preprocessors/Marlin.py | 3 +- preprocessors/Marlin_laser_FAN_pin.py | 2 +- preprocessors/Marlin_laser_Spindle_pin.py | 2 +- preprocessors/Marlin_laser_z.py | 2 +- preprocessors/NCCAD9.py | 3 +- preprocessors/Paste_1.py | 4 +- preprocessors/Paste_GRBL.py | 6 +- preprocessors/Paste_Marlin.py | 2 +- preprocessors/Repetier.py | 3 +- preprocessors/Roland_MDX_20.py | 2 +- preprocessors/Roland_MDX_540.py | 2 +- preprocessors/Toolchange_Manual.py | 3 +- preprocessors/Toolchange_Probe_MACH3.py | 3 +- preprocessors/default.py | 3 +- preprocessors/default_laser.py | 3 +- preprocessors/grbl_laser_eleks_drd.py | 3 +- preprocessors/hpgl.py | 2 +- requirements.txt | 1 + tests/__init__.py | 0 tests/canvas/performance.py | 95 - tests/canvas/prof.sh | 6 - tests/excellon_files/case1.drl | 125 - tests/frameless_window.py | 148 - tests/gerber_files/STM32F4-spindle.cmp | 6358 ----------------- tests/gerber_files/detector_contour.gbr | 26 - tests/gerber_files/detector_copper_bottom.gbr | 2146 ------ tests/gerber_files/detector_copper_top.gbr | 71 - tests/gerber_files/detector_drill.txt | 46 - tests/gerber_files/simple1.gbr | 54 - tests/gerber_parsing_profiling/gerber1.gbr | 3045 -------- .../gerber_parsing_line_profile_1.py | 13 - .../gerber_parsing_profile_1.py | 17 - tests/new_window_test.py | 70 - tests/other/destructor_test.py | 34 - tests/other/profile_gerber_parser.py | 8 - tests/other/test_excellon_1.py | 49 - tests/other/test_fcrts.py | 37 - tests/other/test_plotg.py | 48 - tests/other/test_rt.py | 24 - tests/svg/7segment_9,9.svg | 34 - tests/svg/Arduino Nano3_pcb.svg | 468 -- tests/svg/drawing.svg | 126 - tests/svg/usb_connector.svg | 77 - tests/svg/use.svg | 101 - tests/test_excellon.py | 331 - tests/test_excellon_flow.py | 163 - tests/test_gerber_buffer.py | 35 - tests/test_gerber_flow.py | 190 - tests/test_paint.py | 213 - tests/test_pathconnect.py | 89 - tests/test_polygon_paint.py | 220 - tests/test_svg_flow.py | 127 - tests/test_tclCommands/__init__.py | 18 - .../test_TclCommandAddPolygon.py | 18 - .../test_TclCommandAddPolyline.py | 18 - .../test_tclCommands/test_TclCommandCncjob.py | 17 - .../test_TclCommandDrillcncjob.py | 18 - .../test_TclCommandExportGcode.py | 33 - .../test_TclCommandExteriors.py | 24 - .../test_TclCommandImportSvg.py | 60 - .../test_TclCommandInteriors.py | 24 - .../test_TclCommandIsolate.py | 21 - tests/test_tclCommands/test_TclCommandNew.py | 48 - .../test_TclCommandNewGeometry.py | 14 - .../test_TclCommandOpenExcellon.py | 15 - .../test_TclCommandOpenGerber.py | 25 - .../test_TclCommandPaintPolygon.py | 25 - tests/test_tcl_shell.py | 272 - tests/test_voronoi.py | 26 - tests/titlebar_custom.py | 197 - .../toollift_minimization_line_profile1.py | 8 - .../toollift_minimization_profile1.py | 11 - 230 files changed, 1253 insertions(+), 15935 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/canvas/performance.py delete mode 100755 tests/canvas/prof.sh delete mode 100644 tests/excellon_files/case1.drl delete mode 100644 tests/frameless_window.py delete mode 100644 tests/gerber_files/STM32F4-spindle.cmp delete mode 100644 tests/gerber_files/detector_contour.gbr delete mode 100644 tests/gerber_files/detector_copper_bottom.gbr delete mode 100644 tests/gerber_files/detector_copper_top.gbr delete mode 100644 tests/gerber_files/detector_drill.txt delete mode 100644 tests/gerber_files/simple1.gbr delete mode 100755 tests/gerber_parsing_profiling/gerber1.gbr delete mode 100644 tests/gerber_parsing_profiling/gerber_parsing_line_profile_1.py delete mode 100644 tests/gerber_parsing_profiling/gerber_parsing_profile_1.py delete mode 100644 tests/new_window_test.py delete mode 100644 tests/other/destructor_test.py delete mode 100644 tests/other/profile_gerber_parser.py delete mode 100644 tests/other/test_excellon_1.py delete mode 100644 tests/other/test_fcrts.py delete mode 100644 tests/other/test_plotg.py delete mode 100644 tests/other/test_rt.py delete mode 100644 tests/svg/7segment_9,9.svg delete mode 100644 tests/svg/Arduino Nano3_pcb.svg delete mode 100644 tests/svg/drawing.svg delete mode 100644 tests/svg/usb_connector.svg delete mode 100644 tests/svg/use.svg delete mode 100644 tests/test_excellon.py delete mode 100644 tests/test_excellon_flow.py delete mode 100644 tests/test_gerber_buffer.py delete mode 100644 tests/test_gerber_flow.py delete mode 100644 tests/test_paint.py delete mode 100644 tests/test_pathconnect.py delete mode 100644 tests/test_polygon_paint.py delete mode 100644 tests/test_svg_flow.py delete mode 100644 tests/test_tclCommands/__init__.py delete mode 100644 tests/test_tclCommands/test_TclCommandAddPolygon.py delete mode 100644 tests/test_tclCommands/test_TclCommandAddPolyline.py delete mode 100644 tests/test_tclCommands/test_TclCommandCncjob.py delete mode 100644 tests/test_tclCommands/test_TclCommandDrillcncjob.py delete mode 100644 tests/test_tclCommands/test_TclCommandExportGcode.py delete mode 100644 tests/test_tclCommands/test_TclCommandExteriors.py delete mode 100644 tests/test_tclCommands/test_TclCommandImportSvg.py delete mode 100644 tests/test_tclCommands/test_TclCommandInteriors.py delete mode 100644 tests/test_tclCommands/test_TclCommandIsolate.py delete mode 100644 tests/test_tclCommands/test_TclCommandNew.py delete mode 100644 tests/test_tclCommands/test_TclCommandNewGeometry.py delete mode 100644 tests/test_tclCommands/test_TclCommandOpenExcellon.py delete mode 100644 tests/test_tclCommands/test_TclCommandOpenGerber.py delete mode 100644 tests/test_tclCommands/test_TclCommandPaintPolygon.py delete mode 100644 tests/test_tcl_shell.py delete mode 100644 tests/test_voronoi.py delete mode 100644 tests/titlebar_custom.py delete mode 100644 tests/toolpath_optimization_profiling/toollift_minimization_line_profile1.py delete mode 100644 tests/toolpath_optimization_profiling/toollift_minimization_profile1.py diff --git a/Bookmark.py b/Bookmark.py index 7826c844..8c3bbf49 100644 --- a/Bookmark.py +++ b/Bookmark.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui, QtCore, QtWidgets from appGUI.GUIElements import FCTable, FCEntry, FCButton, FCFileSaveDialog, GLay, FCLabel @@ -5,7 +6,8 @@ import sys import webbrowser from copy import deepcopy -from datetime import datetime +from datetime import datetime as dt + import gettext import appTranslation as fcTranslate import builtins @@ -284,7 +286,7 @@ class BookmarkManager(QtWidgets.QWidget): self.app.defaults.report_usage("on_export_bookmarks") self.app.log.debug("on_export_bookmarks()") - date = str(datetime.today()).rpartition('.')[0] + date = str(dt.today()).rpartition('.')[0] date = ''.join(c for c in date if c not in ':-') date = date.replace(' ', '_') diff --git a/CHANGELOG.md b/CHANGELOG.md index dc622ade..7050396f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +6.03.2023 + +- fixed some possible issues due of changes in version 2.0 of Shapely +- removed the import * statement from most of the app + 01.03.2023 - in Tool Milling made sure that deleting the only tool will not crash the application diff --git a/FlatCAM.py b/FlatCAM.py index b4652c4b..61f7498a 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -1,3 +1,4 @@ + import sys import os import traceback diff --git a/appDatabase.py b/appDatabase.py index 57bebbcd..7d5d47f1 100644 --- a/appDatabase.py +++ b/appDatabase.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui, QtCore, QtWidgets from appGUI.GUIElements import FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \ FCTree, RadioSet, FCFileSaveDialog, FCLabel, FCComboBox2, GLay @@ -3091,7 +3092,7 @@ class ToolsDB2(QtWidgets.QWidget): # multidepth_item.set_value(data['multidepth']) # widget.setCellWidget(row, 8, multidepth_item) # -# # to make the checkbox centered but it can no longer have it's value accessed - needs a fix using findchild() +# # to make the checkbox centered, but it can no longer have its value accessed - needs a fix using findchild() # # multidepth_item = QtWidgets.QWidget() # # cb = FCCheckBox() # # cb.set_value(data['multidepth']) diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 576ed827..c6fe2191 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -7,7 +7,9 @@ from camlib import distance, arc, AppRTreeStorage -from appEditors.exc_plugins.ExcDrillPlugin import * +from PyQt6 import QtCore, QtWidgets, QtGui +from PyQt6.QtCore import Qt +from appEditors.exc_plugins.ExcDrillPlugin import ExcDrillEditorTool from appEditors.exc_plugins.ExcSlotPlugin import ExcSlotEditorTool from appEditors.exc_plugins.ExcDrillArrayPlugin import ExcDrillArrayEditorTool from appEditors.exc_plugins.ExcSlotArrayPlugin import ExcSlotArrayEditorTool @@ -3102,7 +3104,7 @@ class AppExcEditor(QtCore.QObject): return f - def connect_exc_toolbar_signals(self): + def connect_exc_toolbar_signals(self) -> None: self.tools_exc.update({ "drill_select": {"button": self.app.ui.select_drill_btn, "constructor": SelectEditorExc}, "drill_add": {"button": self.app.ui.add_drill_btn, "constructor": DrillAdd}, diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index 11cfa5d9..19db79e4 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -11,11 +11,14 @@ # Date: 3/10/2019 # # ########################################################## +from PyQt6 import QtGui, QtCore, QtWidgets +from PyQt6.QtCore import Qt # import inspect import math from camlib import distance, arc, three_point_circle, Geometry, AppRTreeStorage, flatten_shapely_geometry -from appGUI.GUIElements import * +from appGUI.GUIElements import FCLabel, GLay, FCDoubleSpinner, FCTree, FCButton, FCFrame, FCCheckBox, FCEntry, \ + FCTextEdit from appGUI.VisPyVisuals import ShapeCollection from appEditors.geo_plugins.GeoBufferPlugin import BufferSelectionTool @@ -44,6 +47,7 @@ from rtree import index as rtindex from copy import deepcopy # from vispy.io import read_png + import gettext import appTranslation as fcTranslate import builtins diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 60e0eeef..1612cd3f 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -5,7 +5,7 @@ # MIT Licence # # ########################################################## -from appEditors.grb_plugins.GrbCommon import * +from appEditors.grb_plugins.GrbCommon import DrawToolUtilityShape, DrawToolShape, DrawTool, ShapeToolEditorGrb from camlib import distance, arc, three_point_circle, flatten_shapely_geometry from appGUI.GUIElements import * @@ -24,8 +24,18 @@ from appEditors.grb_plugins.GrberRegionPlugin import GrbRegionEditorTool # import inspect # from vispy.io import read_png +from vispy.geometry.rect import Rect # import pngcanvas import traceback +import numpy as np +from numpy.linalg import norm as numpy_norm +import math +from copy import deepcopy + +from shapely.geometry import Point, Polygon, MultiPolygon, LineString, LinearRing, box +from shapely.ops import unary_union +from shapely.affinity import translate, scale, skew, rotate + import gettext import appTranslation as fcTranslate import builtins @@ -610,11 +620,11 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): new_geo_el = {} if 'solid' in geo_el: - new_geo_el['solid'] = affinity.translate( + new_geo_el['solid'] = translate( geo_el['solid'], xoff=(dx - self.last_dx), yoff=(dy - self.last_dy) ) if 'follow' in geo_el: - new_geo_el['follow'] = affinity.translate( + new_geo_el['follow'] = translate( geo_el['follow'], xoff=(dx - self.last_dx), yoff=(dy - self.last_dy) ) geo_el_list.append(new_geo_el) @@ -790,8 +800,8 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): y = self.origin[1] + radius * math.sin(-angle_radians + angle) geo = self.util_shape((x, y)) - geo_sol = affinity.rotate(geo['solid'], angle=(math.pi - angle_radians + angle), use_radians=True) - geo_fol = affinity.rotate(geo['follow'], angle=(math.pi - angle_radians + angle), use_radians=True) + geo_sol = rotate(geo['solid'], angle=(math.pi - angle_radians + angle), use_radians=True) + geo_fol = rotate(geo['follow'], angle=(math.pi - angle_radians + angle), use_radians=True) geo_el = { 'solid': geo_sol, 'follow': geo_fol @@ -804,8 +814,8 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): y = self.origin[1] + radius * math.sin(angle_radians + angle) geo = self.util_shape((x, y)) - geo_sol = affinity.rotate(geo['solid'], angle=(angle_radians + angle - math.pi), use_radians=True) - geo_fol = affinity.rotate(geo['follow'], angle=(angle_radians + angle - math.pi), use_radians=True) + geo_sol = rotate(geo['solid'], angle=(angle_radians + angle - math.pi), use_radians=True) + geo_fol = rotate(geo['follow'], angle=(angle_radians + angle - math.pi), use_radians=True) geo_el = { 'solid': geo_sol, 'follow': geo_fol @@ -1089,7 +1099,7 @@ class PoligonizeEditorGrb(ShapeToolEditorGrb): else: modifier_to_use = Qt.KeyboardModifier.ShiftModifier # if modifier key is pressed then we add to the selected list the current shape but if it's already - # in the selected list, we removed it. Therefore first click selects, second deselects. + # in the selected list, we removed it. Therefore, first click selects, second deselects. if key_modifier == modifier_to_use: self.draw_app.select_tool(self.draw_app.active_tool.name) else: @@ -2163,7 +2173,7 @@ class DiscSemiEditorGrb(ShapeToolEditorGrb): except KeyError: size_ap = 0.0 # self.draw_app.app.inform.emit( - # '[ERROR_NOTCL] %s' % _("You need to preselect a aperture in the Aperture Table that has a size.")) + # '[ERROR_NOTCL] %s' % _("You need to preselect an aperture in the Aperture Table that has a size.")) # try: # QtGui.QGuiApplication.restoreOverrideCursor() # except Exception: @@ -2839,11 +2849,11 @@ class MoveEditorGrb(ShapeToolEditorGrb): geometric_data = select_shape.geo new_geo_el = {} if 'solid' in geometric_data: - new_geo_el['solid'] = affinity.translate(geometric_data['solid'], xoff=dx, yoff=dy) + new_geo_el['solid'] = translate(geometric_data['solid'], xoff=dx, yoff=dy) if 'follow' in geometric_data: - new_geo_el['follow'] = affinity.translate(geometric_data['follow'], xoff=dx, yoff=dy) + new_geo_el['follow'] = translate(geometric_data['follow'], xoff=dx, yoff=dy) if 'clear' in geometric_data: - new_geo_el['clear'] = affinity.translate(geometric_data['clear'], xoff=dx, yoff=dy) + new_geo_el['clear'] = translate(geometric_data['clear'], xoff=dx, yoff=dy) self.geometry.append(DrawToolShape(new_geo_el)) self.current_storage.remove(select_shape) @@ -2892,15 +2902,15 @@ class MoveEditorGrb(ShapeToolEditorGrb): for geom in self.draw_app.get_selected(): new_geo_el = {} if 'solid' in geom.geo: - new_geo_el['solid'] = affinity.translate(geom.geo['solid'], xoff=dx, yoff=dy) + new_geo_el['solid'] = translate(geom.geo['solid'], xoff=dx, yoff=dy) if 'follow' in geom.geo: - new_geo_el['follow'] = affinity.translate(geom.geo['follow'], xoff=dx, yoff=dy) + new_geo_el['follow'] = translate(geom.geo['follow'], xoff=dx, yoff=dy) if 'clear' in geom.geo: - new_geo_el['clear'] = affinity.translate(geom.geo['clear'], xoff=dx, yoff=dy) + new_geo_el['clear'] = translate(geom.geo['clear'], xoff=dx, yoff=dy) geo_list.append(deepcopy(new_geo_el)) return DrawToolUtilityShape(geo_list) else: - ss_el = {'solid': affinity.translate(self.selection_shape, xoff=dx, yoff=dy)} + ss_el = {'solid': translate(self.selection_shape, xoff=dx, yoff=dy)} return DrawToolUtilityShape(ss_el) @@ -2922,11 +2932,11 @@ class CopyEditorGrb(MoveEditorGrb): geometric_data = select_shape.geo new_geo_el = {} if 'solid' in geometric_data: - new_geo_el['solid'] = affinity.translate(geometric_data['solid'], xoff=dx, yoff=dy) + new_geo_el['solid'] = translate(geometric_data['solid'], xoff=dx, yoff=dy) if 'follow' in geometric_data: - new_geo_el['follow'] = affinity.translate(geometric_data['follow'], xoff=dx, yoff=dy) + new_geo_el['follow'] = translate(geometric_data['follow'], xoff=dx, yoff=dy) if 'clear' in geometric_data: - new_geo_el['clear'] = affinity.translate(geometric_data['clear'], xoff=dx, yoff=dy) + new_geo_el['clear'] = translate(geometric_data['clear'], xoff=dx, yoff=dy) self.geometry.append(DrawToolShape(new_geo_el)) sel_shapes_to_be_deleted.append(select_shape) @@ -3121,11 +3131,11 @@ class EraserEditorGrb(ShapeToolEditorGrb): for geom in self.draw_app.get_selected(): new_geo_el = {} if 'solid' in geom.geo: - new_geo_el['solid'] = affinity.translate(geom.geo['solid'], xoff=dx, yoff=dy) + new_geo_el['solid'] = translate(geom.geo['solid'], xoff=dx, yoff=dy) if 'follow' in geom.geo: - new_geo_el['follow'] = affinity.translate(geom.geo['follow'], xoff=dx, yoff=dy) + new_geo_el['follow'] = translate(geom.geo['follow'], xoff=dx, yoff=dy) if 'clear' in geom.geo: - new_geo_el['clear'] = affinity.translate(geom.geo['clear'], xoff=dx, yoff=dy) + new_geo_el['clear'] = translate(geom.geo['clear'], xoff=dx, yoff=dy) geo_list.append(deepcopy(new_geo_el)) return DrawToolUtilityShape(geo_list) @@ -4558,14 +4568,11 @@ class AppGerberEditor(QtCore.QObject): geometric_data = geo_el.geo new_geo_el = {} if 'solid' in geometric_data: - new_geo_el['solid'] = deepcopy(affinity.scale(geometric_data['solid'], - xfact=factor, yfact=factor)) + new_geo_el['solid'] = deepcopy(scale(geometric_data['solid'], xfact=factor, yfact=factor)) if 'follow' in geometric_data: - new_geo_el['follow'] = deepcopy(affinity.scale(geometric_data['follow'], - xfact=factor, yfact=factor)) + new_geo_el['follow'] = deepcopy(scale(geometric_data['follow'], xfact=factor, yfact=factor)) if 'clear' in geometric_data: - new_geo_el['clear'] = deepcopy(affinity.scale(geometric_data['clear'], - xfact=factor, yfact=factor)) + new_geo_el['clear'] = deepcopy(scale(geometric_data['clear'], xfact=factor, yfact=factor)) geometry.append(new_geo_el) self.add_gerber_shape(geometry, self.storage_dict[val_edited]) @@ -5848,7 +5855,7 @@ class AppGerberEditor(QtCore.QObject): if self.app.selection_type is not None: self.draw_selection_area_handler(self.pos, pos, self.app.selection_type) self.app.selection_type = None - if isinstance(self.active_tool, (SimplifyEditorGrb)): + if isinstance(self.active_tool, SimplifyEditorGrb): self.active_tool.simp_tool.calculate_coords_vertex() elif isinstance(self.active_tool, (SelectEditorGrb, SimplifyEditorGrb)): @@ -6416,15 +6423,15 @@ class AppGerberEditor(QtCore.QObject): geometric_data = geom_el.geo scaled_geom_el = {} if 'solid' in geometric_data: - scaled_geom_el['solid'] = affinity.scale( + scaled_geom_el['solid'] = scale( geometric_data['solid'], scale_factor, scale_factor, origin='center' ) if 'follow' in geometric_data: - scaled_geom_el['follow'] = affinity.scale( + scaled_geom_el['follow'] = scale( geometric_data['follow'], scale_factor, scale_factor, origin='center' ) if 'clear' in geometric_data: - scaled_geom_el['clear'] = affinity.scale( + scaled_geom_el['clear'] = scale( geometric_data['clear'], scale_factor, scale_factor, origin='center' ) @@ -7625,11 +7632,11 @@ class TransformEditorTool(AppTool): for sel_el_shape in elem_list: sel_el = sel_el_shape.geo if 'solid' in sel_el: - sel_el['solid'] = affinity.rotate(sel_el['solid'], angle=-val, origin=(px, py)) + sel_el['solid'] = rotate(sel_el['solid'], angle=-val, origin=(px, py)) if 'follow' in sel_el: - sel_el['follow'] = affinity.rotate(sel_el['follow'], angle=-val, origin=(px, py)) + sel_el['follow'] = rotate(sel_el['follow'], angle=-val, origin=(px, py)) if 'clear' in sel_el: - sel_el['clear'] = affinity.rotate(sel_el['clear'], angle=-val, origin=(px, py)) + sel_el['clear'] = rotate(sel_el['clear'], angle=-val, origin=(px, py)) self.draw_app.plot_all() self.app.inform.emit('[success] %s' % _("Done.")) @@ -7660,19 +7667,19 @@ class TransformEditorTool(AppTool): sel_el = sel_el_shape.geo if axis == 'X': if 'solid' in sel_el: - sel_el['solid'] = affinity.scale(sel_el['solid'], xfact=1, yfact=-1, origin=(px, py)) + sel_el['solid'] = scale(sel_el['solid'], xfact=1, yfact=-1, origin=(px, py)) if 'follow' in sel_el: - sel_el['follow'] = affinity.scale(sel_el['follow'], xfact=1, yfact=-1, origin=(px, py)) + sel_el['follow'] = scale(sel_el['follow'], xfact=1, yfact=-1, origin=(px, py)) if 'clear' in sel_el: - sel_el['clear'] = affinity.scale(sel_el['clear'], xfact=1, yfact=-1, origin=(px, py)) + sel_el['clear'] = scale(sel_el['clear'], xfact=1, yfact=-1, origin=(px, py)) self.app.inform.emit('[success] %s...' % _('Flip on Y axis done')) elif axis == 'Y': if 'solid' in sel_el: - sel_el['solid'] = affinity.scale(sel_el['solid'], xfact=-1, yfact=1, origin=(px, py)) + sel_el['solid'] = scale(sel_el['solid'], xfact=-1, yfact=1, origin=(px, py)) if 'follow' in sel_el: - sel_el['follow'] = affinity.scale(sel_el['follow'], xfact=-1, yfact=1, origin=(px, py)) + sel_el['follow'] = scale(sel_el['follow'], xfact=-1, yfact=1, origin=(px, py)) if 'clear' in sel_el: - sel_el['clear'] = affinity.scale(sel_el['clear'], xfact=-1, yfact=1, origin=(px, py)) + sel_el['clear'] = scale(sel_el['clear'], xfact=-1, yfact=1, origin=(px, py)) self.app.inform.emit('[success] %s...' % _('Flip on X axis done')) self.draw_app.plot_all() except Exception as e: @@ -7703,11 +7710,11 @@ class TransformEditorTool(AppTool): sel_el = sel_el_shape.geo if 'solid' in sel_el: - sel_el['solid'] = affinity.skew(sel_el['solid'], xval, yval, origin=(px, py)) + sel_el['solid'] = skew(sel_el['solid'], xval, yval, origin=(px, py)) if 'follow' in sel_el: - sel_el['follow'] = affinity.skew(sel_el['follow'], xval, yval, origin=(px, py)) + sel_el['follow'] = skew(sel_el['follow'], xval, yval, origin=(px, py)) if 'clear' in sel_el: - sel_el['clear'] = affinity.skew(sel_el['clear'], xval, yval, origin=(px, py)) + sel_el['clear'] = skew(sel_el['clear'], xval, yval, origin=(px, py)) self.draw_app.plot_all() @@ -7742,11 +7749,11 @@ class TransformEditorTool(AppTool): for sel_el_shape in elem_list: sel_el = sel_el_shape.geo if 'solid' in sel_el: - sel_el['solid'] = affinity.scale(sel_el['solid'], xfactor, yfactor, origin=(px, py)) + sel_el['solid'] = scale(sel_el['solid'], xfactor, yfactor, origin=(px, py)) if 'follow' in sel_el: - sel_el['follow'] = affinity.scale(sel_el['follow'], xfactor, yfactor, origin=(px, py)) + sel_el['follow'] = scale(sel_el['follow'], xfactor, yfactor, origin=(px, py)) if 'clear' in sel_el: - sel_el['clear'] = affinity.scale(sel_el['clear'], xfactor, yfactor, origin=(px, py)) + sel_el['clear'] = scale(sel_el['clear'], xfactor, yfactor, origin=(px, py)) self.draw_app.plot_all() if str(axis) == 'X': @@ -7779,18 +7786,18 @@ class TransformEditorTool(AppTool): sel_el = sel_el_shape.geo if axis == 'X': if 'solid' in sel_el: - sel_el['solid'] = affinity.translate(sel_el['solid'], num, 0) + sel_el['solid'] = translate(sel_el['solid'], num, 0) if 'follow' in sel_el: - sel_el['follow'] = affinity.translate(sel_el['follow'], num, 0) + sel_el['follow'] = translate(sel_el['follow'], num, 0) if 'clear' in sel_el: - sel_el['clear'] = affinity.translate(sel_el['clear'], num, 0) + sel_el['clear'] = translate(sel_el['clear'], num, 0) elif axis == 'Y': if 'solid' in sel_el: - sel_el['solid'] = affinity.translate(sel_el['solid'], 0, num) + sel_el['solid'] = translate(sel_el['solid'], 0, num) if 'follow' in sel_el: - sel_el['follow'] = affinity.translate(sel_el['follow'], 0, num) + sel_el['follow'] = translate(sel_el['follow'], 0, num) if 'clear' in sel_el: - sel_el['clear'] = affinity.translate(sel_el['clear'], 0, num) + sel_el['clear'] = translate(sel_el['clear'], 0, num) self.draw_app.plot_all() if str(axis) == 'X': @@ -7816,11 +7823,11 @@ class TransformEditorTool(AppTool): if factor: if 'solid' in sel_el: - sel_el['solid'] = affinity.scale(sel_el['solid'], value, value, origin='center') + sel_el['solid'] = scale(sel_el['solid'], value, value, origin='center') if 'follow' in sel_el: - sel_el['follow'] = affinity.scale(sel_el['solid'], value, value, origin='center') + sel_el['follow'] = scale(sel_el['solid'], value, value, origin='center') if 'clear' in sel_el: - sel_el['clear'] = affinity.scale(sel_el['solid'], value, value, origin='center') + sel_el['clear'] = scale(sel_el['solid'], value, value, origin='center') else: if 'solid' in sel_el: sel_el['solid'] = sel_el['solid'].buffer( diff --git a/appEditors/AppTextEditor.py b/appEditors/AppTextEditor.py index ec3d1084..fad03f61 100644 --- a/appEditors/AppTextEditor.py +++ b/appEditors/AppTextEditor.py @@ -5,9 +5,9 @@ # MIT Licence # # ########################################################## +from PyQt6 import QtPrintSupport, QtWidgets, QtCore, QtGui from appGUI.GUIElements import FCFileSaveDialog, FCEntry, FCTextAreaExtended, FCTextAreaLineNumber, FCButton, \ FCCheckBox, FCMessageBox -from PyQt6 import QtPrintSupport, QtWidgets, QtCore, QtGui from reportlab.platypus import SimpleDocTemplate, Paragraph from reportlab.lib.styles import getSampleStyleSheet diff --git a/appEditors/appGCodeEditor.py b/appEditors/appGCodeEditor.py index f55a08ba..2923d298 100644 --- a/appEditors/appGCodeEditor.py +++ b/appEditors/appGCodeEditor.py @@ -5,11 +5,12 @@ # MIT Licence # # ########################################################## +from PyQt6 import QtWidgets, QtCore, QtGui +from PyQt6.QtCore import Qt + from appEditors.AppTextEditor import AppTextEditor from appObjects.CNCJobObject import CNCJobObject from appGUI.GUIElements import FCTextArea, FCEntry, FCButton, FCTable, GLay, FCLabel -from PyQt6 import QtWidgets, QtCore, QtGui -from PyQt6.QtCore import Qt # from io import StringIO diff --git a/appEditors/exc_plugins/ExcCopyPlugin.py b/appEditors/exc_plugins/ExcCopyPlugin.py index f2610c71..9a5cacde 100644 --- a/appEditors/exc_plugins/ExcCopyPlugin.py +++ b/appEditors/exc_plugins/ExcCopyPlugin.py @@ -1,5 +1,13 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui + +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, NumericalEvalEntry, GLay, FCFrame, FCSpinner, \ + RadioSet, FCDoubleSpinner + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/exc_plugins/ExcDrillArrayPlugin.py b/appEditors/exc_plugins/ExcDrillArrayPlugin.py index fea30e04..cc1466ed 100644 --- a/appEditors/exc_plugins/ExcDrillArrayPlugin.py +++ b/appEditors/exc_plugins/ExcDrillArrayPlugin.py @@ -1,5 +1,12 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, FCFrame, NumericalEvalEntry, FCSpinner, \ + FCDoubleSpinner, RadioSet + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -304,7 +311,7 @@ class ExcDrillArrayEditorUI: self.radius_entry = FCDoubleSpinner(policy=False) self.radius_entry.set_precision(self.decimals) self.radius_entry.setSingleStep(1.0) - self.radius_entry.setRange(-10000.0000,10000.000) + self.radius_entry.setRange(-10000.0000, 10000.000) self.circ_grid.addWidget(self.radius_lbl, 4, 0) self.circ_grid.addWidget(self.radius_entry, 4, 1) diff --git a/appEditors/exc_plugins/ExcDrillPlugin.py b/appEditors/exc_plugins/ExcDrillPlugin.py index 660e80b6..f933111f 100644 --- a/appEditors/exc_plugins/ExcDrillPlugin.py +++ b/appEditors/exc_plugins/ExcDrillPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import FCLabel, FCButton, GLay, FCFrame, VerticalScrollArea, NumericalEvalEntry, \ + FCDoubleSpinner +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/exc_plugins/ExcResizePlugin.py b/appEditors/exc_plugins/ExcResizePlugin.py index e4d117d7..1322d54a 100644 --- a/appEditors/exc_plugins/ExcResizePlugin.py +++ b/appEditors/exc_plugins/ExcResizePlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, FCFrame, NumericalEvalEntry, \ + FCDoubleSpinner +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/exc_plugins/ExcSlotArrayPlugin.py b/appEditors/exc_plugins/ExcSlotArrayPlugin.py index 19fb582d..d1c57bbc 100644 --- a/appEditors/exc_plugins/ExcSlotArrayPlugin.py +++ b/appEditors/exc_plugins/ExcSlotArrayPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, FCFrame, NumericalEvalEntry, \ + FCDoubleSpinner, RadioSet, FCSpinner +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/exc_plugins/ExcSlotPlugin.py b/appEditors/exc_plugins/ExcSlotPlugin.py index 65d8ba03..4c0546d5 100644 --- a/appEditors/exc_plugins/ExcSlotPlugin.py +++ b/appEditors/exc_plugins/ExcSlotPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, FCFrame, FCDoubleSpinner,\ + NumericalEvalEntry, RadioSet +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoBufferPlugin.py b/appEditors/geo_plugins/GeoBufferPlugin.py index 72e1b847..caa2bc8b 100644 --- a/appEditors/geo_plugins/GeoBufferPlugin.py +++ b/appEditors/geo_plugins/GeoBufferPlugin.py @@ -1,5 +1,13 @@ -from appTool import * +from PyQt6 import QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner, FCComboBox + +from shapely.geometry import Polygon + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoCirclePlugin.py b/appEditors/geo_plugins/GeoCirclePlugin.py index f3be68e8..f162147c 100644 --- a/appEditors/geo_plugins/GeoCirclePlugin.py +++ b/appEditors/geo_plugins/GeoCirclePlugin.py @@ -1,5 +1,14 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner + +from shapely.geometry import Point +from shapely.affinity import scale, rotate + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoCopyPlugin.py b/appEditors/geo_plugins/GeoCopyPlugin.py index 9d1e2f0d..e9595e26 100644 --- a/appEditors/geo_plugins/GeoCopyPlugin.py +++ b/appEditors/geo_plugins/GeoCopyPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, GLay, FCLabel, FCButton, FCFrame, NumericalEvalEntry, RadioSet, \ + FCSpinner, FCDoubleSpinner +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoPaintPlugin.py b/appEditors/geo_plugins/GeoPaintPlugin.py index 3993d2ec..9c1fd6d7 100644 --- a/appEditors/geo_plugins/GeoPaintPlugin.py +++ b/appEditors/geo_plugins/GeoPaintPlugin.py @@ -1,7 +1,17 @@ -from appTool import * +from PyQt6 import QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCFrame, FCButton, GLay, FCDoubleSpinner, FCComboBox, \ + FCCheckBox from camlib import Geometry +from shapely.geometry import Polygon +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -145,7 +155,7 @@ class PaintOptionsTool(AppToolEditor): Creates a list of non-iterable linear geometry objects. Results are placed in self.flat_geometry - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type, list or list of lists of such. :param reset: Clears the contents of self.flat_geometry. """ diff --git a/appEditors/geo_plugins/GeoPathPlugin.py b/appEditors/geo_plugins/GeoPathPlugin.py index 622e969c..ac6391a4 100644 --- a/appEditors/geo_plugins/GeoPathPlugin.py +++ b/appEditors/geo_plugins/GeoPathPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, GLay, FCButton, NumericalEvalEntry + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoRectanglePlugin.py b/appEditors/geo_plugins/GeoRectanglePlugin.py index 7e92876e..87bbba08 100644 --- a/appEditors/geo_plugins/GeoRectanglePlugin.py +++ b/appEditors/geo_plugins/GeoRectanglePlugin.py @@ -1,5 +1,14 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, FCFrame, FCDoubleSpinner, RadioSetCross, \ + RadioSet, NumericalEvalEntry + +from shapely.geometry import box, base + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoSimplificationPlugin.py b/appEditors/geo_plugins/GeoSimplificationPlugin.py index 38c064f6..40757d29 100644 --- a/appEditors/geo_plugins/GeoSimplificationPlugin.py +++ b/appEditors/geo_plugins/GeoSimplificationPlugin.py @@ -1,5 +1,12 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets, QtCore +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, GLay, FCLabel, FCButton, FCFrame, FCTextEdit, FCEntry, \ + FCDoubleSpinner + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -11,7 +18,7 @@ class SimplificationTool(AppToolEditor): Do a shape simplification for the selected geometry. """ - update_ui = pyqtSignal(object, int) + update_ui = QtCore.pyqtSignal(object, int) def __init__(self, app, draw_app): AppToolEditor.__init__(self, app) @@ -93,33 +100,33 @@ class SimplificationTool(AppToolEditor): selected_shapes_geos = [] tol = self.ui.geo_tol_entry.get_value() - def task_job(self): - with self.app.proc_container.new('%s...' % _("Simplify")): - selected_shapes = self.draw_app.get_selected() - self.draw_app.interdict_selection = True + def task_job(self_class): + with self_class.app.proc_container.new('%s...' % _("Simplify")): + selected_shapes = self_class.draw_app.get_selected() + self_class.draw_app.interdict_selection = True for obj_shape in selected_shapes: selected_shapes_geos.append(obj_shape.geo.simplify(tolerance=tol)) if not selected_shapes: - self.app.inform.emit('%s' % _("Failed.")) + self_class.app.inform.emit('%s' % _("Failed.")) return for shape in selected_shapes: - self.draw_app.delete_shape(shape=shape) + self_class.draw_app.delete_shape(shape=shape) for geo in selected_shapes_geos: - self.draw_app.add_shape(geo, build_ui=False) + self_class.draw_app.add_shape(geo, build_ui=False) - self.draw_app.selected = [] + self_class.draw_app.selected = [] last_sel_geo = selected_shapes_geos[-1] - self.calculate_coords_vertex(last_sel_geo) + self_class.calculate_coords_vertex(last_sel_geo) - self.app.inform.emit('%s' % _("Done.")) + self_class.app.inform.emit('%s' % _("Done.")) - self.draw_app.plot_all() - self.draw_app.interdict_selection = False - self.draw_app.build_ui_sig.emit() + self_class.draw_app.plot_all() + self_class.draw_app.interdict_selection = False + self_class.draw_app.build_ui_sig.emit() self.app.worker_task.emit({'fcn': task_job, 'params': [self]}) diff --git a/appEditors/geo_plugins/GeoTextPlugin.py b/appEditors/geo_plugins/GeoTextPlugin.py index ee1648b9..b2391666 100644 --- a/appEditors/geo_plugins/GeoTextPlugin.py +++ b/appEditors/geo_plugins/GeoTextPlugin.py @@ -1,6 +1,14 @@ -from appTool import * -from appParsers.ParseFont import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCTextAreaRich, FCComboBox +from appParsers.ParseFont import ParseFont + +import sys + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/geo_plugins/GeoTransformationPlugin.py b/appEditors/geo_plugins/GeoTransformationPlugin.py index 63d3723c..012c1ecc 100644 --- a/appEditors/geo_plugins/GeoTransformationPlugin.py +++ b/appEditors/geo_plugins/GeoTransformationPlugin.py @@ -1,5 +1,14 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor, AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner, \ + FCInputDoubleSpinner, FCComboBox, NumericalEvalTupleEntry, FCCheckBox, OptionalInputSection + +import numpy as np + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/grb_plugins/GrbBufferPlugin.py b/appEditors/grb_plugins/GrbBufferPlugin.py index 64026d24..862d26d5 100644 --- a/appEditors/grb_plugins/GrbBufferPlugin.py +++ b/appEditors/grb_plugins/GrbBufferPlugin.py @@ -1,6 +1,15 @@ -from appTool import * +from PyQt6 import QtWidgets +from appTool import AppToolEditor from appEditors.grb_plugins.GrbCommon import DrawToolShape +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner, FCComboBox + +from copy import deepcopy +import traceback + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/grb_plugins/GrbCommon.py b/appEditors/grb_plugins/GrbCommon.py index a90b867e..9b433ba3 100644 --- a/appEditors/grb_plugins/GrbCommon.py +++ b/appEditors/grb_plugins/GrbCommon.py @@ -4,16 +4,9 @@ # ########################################################################################### from PyQt6.QtCore import Qt -from shapely.geometry import LineString, LinearRing, MultiLineString, Point, Polygon, MultiPolygon, box -from shapely.ops import unary_union -import shapely.affinity as affinity +from shapely.geometry import MultiLineString, Polygon -import math import numpy as np -from numpy.linalg import norm as numpy_norm - -from vispy.geometry import Rect -from copy import deepcopy import logging diff --git a/appEditors/grb_plugins/GrbCopyPlugin.py b/appEditors/grb_plugins/GrbCopyPlugin.py index 9d1e2f0d..79a2cc78 100644 --- a/appEditors/grb_plugins/GrbCopyPlugin.py +++ b/appEditors/grb_plugins/GrbCopyPlugin.py @@ -1,5 +1,12 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, NumericalEvalEntry, RadioSet, \ + FCSpinner, FCDoubleSpinner + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/grb_plugins/GrbPadArrayPlugin.py b/appEditors/grb_plugins/GrbPadArrayPlugin.py index f8516e6b..5d050f9d 100644 --- a/appEditors/grb_plugins/GrbPadArrayPlugin.py +++ b/appEditors/grb_plugins/GrbPadArrayPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, NumericalEvalEntry, \ + FCDoubleSpinner, FCSpinner, RadioSet +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -304,7 +310,7 @@ class GrbPadArrayEditorUI: self.radius_entry = FCDoubleSpinner(policy=False) self.radius_entry.set_precision(self.decimals) self.radius_entry.setSingleStep(1.0) - self.radius_entry.setRange(-10000.0000,10000.000) + self.radius_entry.setRange(-10000.0000, 10000.000) self.circ_grid.addWidget(self.radius_lbl, 4, 0) self.circ_grid.addWidget(self.radius_entry, 4, 1) diff --git a/appEditors/grb_plugins/GrbPadPlugin.py b/appEditors/grb_plugins/GrbPadPlugin.py index b11d1998..72e3e0c5 100644 --- a/appEditors/grb_plugins/GrbPadPlugin.py +++ b/appEditors/grb_plugins/GrbPadPlugin.py @@ -1,5 +1,12 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner, \ + NumericalEvalEntry + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/grb_plugins/GrbSimplificationPlugin.py b/appEditors/grb_plugins/GrbSimplificationPlugin.py index 4cbc152c..941e8734 100644 --- a/appEditors/grb_plugins/GrbSimplificationPlugin.py +++ b/appEditors/grb_plugins/GrbSimplificationPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui, QtCore +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCTextEdit, FCEntry, \ + FCDoubleSpinner +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -11,7 +17,7 @@ class SimplificationTool(AppToolEditor): Do a shape simplification for the selected geometry. """ - update_ui = pyqtSignal(object, int) + update_ui = QtCore.pyqtSignal(object, int) def __init__(self, app, draw_app): AppToolEditor.__init__(self, app) diff --git a/appEditors/grb_plugins/GrbTrackPlugin.py b/appEditors/grb_plugins/GrbTrackPlugin.py index 253354e2..0b507c2d 100644 --- a/appEditors/grb_plugins/GrbTrackPlugin.py +++ b/appEditors/grb_plugins/GrbTrackPlugin.py @@ -1,5 +1,12 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, NumericalEvalEntry, \ + FCDoubleSpinner + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/grb_plugins/GrbTransformationPlugin.py b/appEditors/grb_plugins/GrbTransformationPlugin.py index 63d3723c..012c1ecc 100644 --- a/appEditors/grb_plugins/GrbTransformationPlugin.py +++ b/appEditors/grb_plugins/GrbTransformationPlugin.py @@ -1,5 +1,14 @@ -from appTool import * +from PyQt6 import QtGui, QtWidgets +from appTool import AppToolEditor, AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner, \ + FCInputDoubleSpinner, FCComboBox, NumericalEvalTupleEntry, FCCheckBox, OptionalInputSection + +import numpy as np + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appEditors/grb_plugins/GrberRegionPlugin.py b/appEditors/grb_plugins/GrberRegionPlugin.py index ac31d690..5f71fdc6 100644 --- a/appEditors/grb_plugins/GrberRegionPlugin.py +++ b/appEditors/grb_plugins/GrberRegionPlugin.py @@ -1,5 +1,11 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppToolEditor +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCDoubleSpinner, FCFrame, GLay, NumericalEvalEntry + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appGUI/preferences/PreferencesSectionUI.py b/appGUI/preferences/PreferencesSectionUI.py index 8c341b02..b6d8eb79 100644 --- a/appGUI/preferences/PreferencesSectionUI.py +++ b/appGUI/preferences/PreferencesSectionUI.py @@ -1,3 +1,4 @@ + from typing import Dict from PyQt6 import QtWidgets diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 4f545226..c2d67100 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -1,8 +1,10 @@ -import os + from PyQt6 import QtGui, QtCore, QtWidgets from PyQt6.QtCore import QSettings -from defaults import AppDefaults +import os + +from defaults import AppDefaults from appGUI.GUIElements import FCMessageBox import gettext diff --git a/appGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py b/appGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py index 5b7c675d..29982de2 100644 --- a/appGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobAdvOptPrefGroupUI.py @@ -2,6 +2,7 @@ from PyQt6 import QtGui from appGUI.GUIElements import FCSpinner, FCColorEntry, FCLabel, GLay, FCFrame from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py b/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py index 648d8c02..e439b7e2 100644 --- a/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py b/appGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py index 0ce6c68b..075018c1 100644 --- a/appGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobGenPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui from appGUI.GUIElements import FCCheckBox, RadioSet, FCSpinner, FCDoubleSpinner, FCSliderWithSpinner, FCColorEntry, \ diff --git a/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py b/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py index ae52d885..b38f160e 100644 --- a/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/cncjob/CNCJobPPGroupUI.py b/appGUI/preferences/cncjob/CNCJobPPGroupUI.py index dfa1858d..cd6852ab 100644 --- a/appGUI/preferences/cncjob/CNCJobPPGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobPPGroupUI.py @@ -1,7 +1,9 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCLabel, GLay, FCFrame from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/cncjob/CNCJobPreferencesUI.py b/appGUI/preferences/cncjob/CNCJobPreferencesUI.py index 162e04b1..bfb82e3d 100644 --- a/appGUI/preferences/cncjob/CNCJobPreferencesUI.py +++ b/appGUI/preferences/cncjob/CNCJobPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.cncjob.CNCJobAdvOptPrefGroupUI import CNCJobAdvOptPrefGroupUI diff --git a/appGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py b/appGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py index 81710146..55fa1ee3 100644 --- a/appGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py +++ b/appGUI/preferences/excellon/ExcellonAdvOptPrefGroupUI.py @@ -1,7 +1,9 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCCheckBox, FCLabel, GLay, FCFrame from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/excellon/ExcellonExpPrefGroupUI.py b/appGUI/preferences/excellon/ExcellonExpPrefGroupUI.py index 7a1b7423..d9d2df1c 100644 --- a/appGUI/preferences/excellon/ExcellonExpPrefGroupUI.py +++ b/appGUI/preferences/excellon/ExcellonExpPrefGroupUI.py @@ -1,7 +1,9 @@ + from PyQt6 import QtWidgets, QtCore from appGUI.GUIElements import RadioSet, FCSpinner, FCLabel, GLay, FCFrame from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/excellon/ExcellonGenPrefGroupUI.py b/appGUI/preferences/excellon/ExcellonGenPrefGroupUI.py index 4b46681c..07ead8d1 100644 --- a/appGUI/preferences/excellon/ExcellonGenPrefGroupUI.py +++ b/appGUI/preferences/excellon/ExcellonGenPrefGroupUI.py @@ -1,3 +1,4 @@ + import platform from PyQt6 import QtWidgets, QtCore, QtGui @@ -5,6 +6,7 @@ from PyQt6 import QtWidgets, QtCore, QtGui from appGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCSliderWithSpinner, FCColorEntry, FCLabel, \ GLay, FCFrame, FCButton from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/excellon/ExcellonOptPrefGroupUI.py b/appGUI/preferences/excellon/ExcellonOptPrefGroupUI.py index c62b2296..563f5c7a 100644 --- a/appGUI/preferences/excellon/ExcellonOptPrefGroupUI.py +++ b/appGUI/preferences/excellon/ExcellonOptPrefGroupUI.py @@ -1,7 +1,9 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCLabel, GLay, FCFrame from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/excellon/ExcellonPreferencesUI.py b/appGUI/preferences/excellon/ExcellonPreferencesUI.py index 6d1018d4..790a38d9 100644 --- a/appGUI/preferences/excellon/ExcellonPreferencesUI.py +++ b/appGUI/preferences/excellon/ExcellonPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.excellon.ExcellonEditorPrefGroupUI import ExcellonEditorPrefGroupUI diff --git a/appGUI/preferences/general/GeneralAPPSetGroupUI.py b/appGUI/preferences/general/GeneralAPPSetGroupUI.py index a6b0c158..4752e50c 100644 --- a/appGUI/preferences/general/GeneralAPPSetGroupUI.py +++ b/appGUI/preferences/general/GeneralAPPSetGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtCore, QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/general/GeneralAppPrefGroupUI.py b/appGUI/preferences/general/GeneralAppPrefGroupUI.py index 16453bf7..b76400ff 100644 --- a/appGUI/preferences/general/GeneralAppPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralAppPrefGroupUI.py @@ -1,3 +1,4 @@ + import sys from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/general/GeneralAppSettingsGroupUI.py b/appGUI/preferences/general/GeneralAppSettingsGroupUI.py index 60bbc5fa..785204c2 100644 --- a/appGUI/preferences/general/GeneralAppSettingsGroupUI.py +++ b/appGUI/preferences/general/GeneralAppSettingsGroupUI.py @@ -1,11 +1,16 @@ + from PyQt6 import QtCore +from PyQt6.QtCore import QSettings + from appGUI.GUIElements import OptionalInputSection -from appGUI.preferences.OptionUI import * +from appGUI.preferences.OptionUI import OptionUI, HeadingOptionUI, SeparatorOptionUI, DoubleSpinnerOptionUI, \ + SpinnerOptionUI, CheckboxOptionUI, ComboboxOptionUI, RadioSetOptionUI, ColorOptionUI from appGUI.preferences.OptionsGroupUI import OptionsGroupUI2 import gettext import appTranslation as fcTranslate import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext diff --git a/appGUI/preferences/general/GeneralGUIPrefGroupUI.py b/appGUI/preferences/general/GeneralGUIPrefGroupUI.py index 82c3ead3..af532e2b 100644 --- a/appGUI/preferences/general/GeneralGUIPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralGUIPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtCore, QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/general/GeneralPreferencesUI.py b/appGUI/preferences/general/GeneralPreferencesUI.py index 67ebbaf2..47c18fd5 100644 --- a/appGUI/preferences/general/GeneralPreferencesUI.py +++ b/appGUI/preferences/general/GeneralPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.general.GeneralAppPrefGroupUI import GeneralAppPrefGroupUI diff --git a/appGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py b/appGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py index 509d5e43..14c8d90f 100644 --- a/appGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryAdvOptPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/geometry/GeometryEditorPrefGroupUI.py b/appGUI/preferences/geometry/GeometryEditorPrefGroupUI.py index 4a6e8faa..515ecaad 100644 --- a/appGUI/preferences/geometry/GeometryEditorPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryEditorPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCSpinner, RadioSet, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py b/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py index 92933bc3..417c24eb 100644 --- a/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryExpPrefGroupUI.py @@ -3,6 +3,7 @@ from PyQt6 import QtWidgets, QtCore from appGUI.GUIElements import FCLabel, FCComboBox, GLay, FCFrame, FCCheckBox from appGUI.preferences.OptionsGroupUI import OptionsGroupUI + import gettext import appTranslation as fcTranslate import builtins diff --git a/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py b/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py index b9f6a4ac..f5cd6a54 100644 --- a/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryGenPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui from appGUI.GUIElements import FCCheckBox, FCSpinner, FCColorEntry, RadioSet, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/geometry/GeometryOptPrefGroupUI.py b/appGUI/preferences/geometry/GeometryOptPrefGroupUI.py index 151038b9..00ea2bea 100644 --- a/appGUI/preferences/geometry/GeometryOptPrefGroupUI.py +++ b/appGUI/preferences/geometry/GeometryOptPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from PyQt6.QtCore import Qt diff --git a/appGUI/preferences/geometry/GeometryPreferencesUI.py b/appGUI/preferences/geometry/GeometryPreferencesUI.py index a3347584..7ce75efd 100644 --- a/appGUI/preferences/geometry/GeometryPreferencesUI.py +++ b/appGUI/preferences/geometry/GeometryPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.geometry.GeometryEditorPrefGroupUI import GeometryEditorPrefGroupUI diff --git a/appGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py b/appGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py index 09c0d1b4..01172152 100644 --- a/appGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py +++ b/appGUI/preferences/gerber/GerberAdvOptPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCLabel, OptionalInputSection, GLay, \ diff --git a/appGUI/preferences/gerber/GerberEditorPrefGroupUI.py b/appGUI/preferences/gerber/GerberEditorPrefGroupUI.py index 2c1cd70b..af5f8ec6 100644 --- a/appGUI/preferences/gerber/GerberEditorPrefGroupUI.py +++ b/appGUI/preferences/gerber/GerberEditorPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, FCComboBox, FCLabel, RadioSet, NumericalEvalTupleEntry, \ diff --git a/appGUI/preferences/gerber/GerberExpPrefGroupUI.py b/appGUI/preferences/gerber/GerberExpPrefGroupUI.py index a9a4408f..3c9b8f2b 100644 --- a/appGUI/preferences/gerber/GerberExpPrefGroupUI.py +++ b/appGUI/preferences/gerber/GerberExpPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtCore from appGUI.GUIElements import RadioSet, FCSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/gerber/GerberGenPrefGroupUI.py b/appGUI/preferences/gerber/GerberGenPrefGroupUI.py index f2eed361..7148c439 100644 --- a/appGUI/preferences/gerber/GerberGenPrefGroupUI.py +++ b/appGUI/preferences/gerber/GerberGenPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtCore, QtGui, QtWidgets from appGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCButton, FCSliderWithSpinner, FCLabel, \ diff --git a/appGUI/preferences/gerber/GerberOptPrefGroupUI.py b/appGUI/preferences/gerber/GerberOptPrefGroupUI.py index 748b43bc..48967fae 100644 --- a/appGUI/preferences/gerber/GerberOptPrefGroupUI.py +++ b/appGUI/preferences/gerber/GerberOptPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/gerber/GerberPreferencesUI.py b/appGUI/preferences/gerber/GerberPreferencesUI.py index eaf47e0c..92d6d5ab 100644 --- a/appGUI/preferences/gerber/GerberPreferencesUI.py +++ b/appGUI/preferences/gerber/GerberPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.gerber.GerberEditorPrefGroupUI import GerberEditorPrefGroupUI diff --git a/appGUI/preferences/tools/Plugins2PreferencesUI.py b/appGUI/preferences/tools/Plugins2PreferencesUI.py index dad3c937..f7fd6e40 100644 --- a/appGUI/preferences/tools/Plugins2PreferencesUI.py +++ b/appGUI/preferences/tools/Plugins2PreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.tools.Tools2InvertPrefGroupUI import Tools2InvertPrefGroupUI diff --git a/appGUI/preferences/tools/PluginsEngravingPreferencesUI.py b/appGUI/preferences/tools/PluginsEngravingPreferencesUI.py index 42e5224a..da6d6158 100644 --- a/appGUI/preferences/tools/PluginsEngravingPreferencesUI.py +++ b/appGUI/preferences/tools/PluginsEngravingPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.tools.Tools2sidedPrefGroupUI import Tools2sidedPrefGroupUI diff --git a/appGUI/preferences/tools/PluginsPreferencesUI.py b/appGUI/preferences/tools/PluginsPreferencesUI.py index 8143cfe6..56d0d88c 100644 --- a/appGUI/preferences/tools/PluginsPreferencesUI.py +++ b/appGUI/preferences/tools/PluginsPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.preferences.tools.ToolsSubPrefGroupUI import ToolsSubPrefGroupUI diff --git a/appGUI/preferences/tools/Tools2CThievingPrefGroupUI.py b/appGUI/preferences/tools/Tools2CThievingPrefGroupUI.py index c274284d..58a275f0 100644 --- a/appGUI/preferences/tools/Tools2CThievingPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2CThievingPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCLabel, FCCheckBox, GLay, FCFrame, \ diff --git a/appGUI/preferences/tools/Tools2ExtractPrefGroupUI.py b/appGUI/preferences/tools/Tools2ExtractPrefGroupUI.py index 35964f27..35e6eb25 100644 --- a/appGUI/preferences/tools/Tools2ExtractPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2ExtractPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py b/appGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py index c211cbd4..9830d074 100644 --- a/appGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2FiducialsPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, RadioSet, FCLabel, GLay, FCComboBox2, FCFrame diff --git a/appGUI/preferences/tools/Tools2InvertPrefGroupUI.py b/appGUI/preferences/tools/Tools2InvertPrefGroupUI.py index 19d2ce24..21f0ab5f 100644 --- a/appGUI/preferences/tools/Tools2InvertPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2InvertPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, RadioSet, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/Tools2OptimalPrefGroupUI.py b/appGUI/preferences/tools/Tools2OptimalPrefGroupUI.py index f242de2b..a1f8b168 100644 --- a/appGUI/preferences/tools/Tools2OptimalPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2OptimalPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py b/appGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py index 147495bb..0e383846 100644 --- a/appGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2PunchGerberPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCCheckBox, RadioSet, FCDoubleSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/Tools2QRCodePrefGroupUI.py b/appGUI/preferences/tools/Tools2QRCodePrefGroupUI.py index ca65c84d..dc39003e 100644 --- a/appGUI/preferences/tools/Tools2QRCodePrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2QRCodePrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui from appGUI.GUIElements import FCSpinner, RadioSet, FCTextArea, FCLabel, FCColorEntry, GLay, FCFrame diff --git a/appGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py b/appGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py index 0316ac20..0dd1fe8f 100644 --- a/appGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2RulesCheckPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCCheckBox, FCDoubleSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/Tools2sidedPrefGroupUI.py b/appGUI/preferences/tools/Tools2sidedPrefGroupUI.py index 19970bc8..04c8e6e3 100644 --- a/appGUI/preferences/tools/Tools2sidedPrefGroupUI.py +++ b/appGUI/preferences/tools/Tools2sidedPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, RadioSet, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py b/appGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py index 6ee6cde1..532963aa 100644 --- a/appGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsCalculatorsPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/ToolsCutoutPrefGroupUI.py b/appGUI/preferences/tools/ToolsCutoutPrefGroupUI.py index 7fd06432..994185af 100644 --- a/appGUI/preferences/tools/ToolsCutoutPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsCutoutPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, FCLabel, OptionalInputSection, \ diff --git a/appGUI/preferences/tools/ToolsDrillPrefGroupUI.py b/appGUI/preferences/tools/ToolsDrillPrefGroupUI.py index 8d5e40d5..b48e6d5c 100644 --- a/appGUI/preferences/tools/ToolsDrillPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsDrillPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtCore from PyQt6.QtCore import Qt diff --git a/appGUI/preferences/tools/ToolsFilmPrefGroupUI.py b/appGUI/preferences/tools/ToolsFilmPrefGroupUI.py index ef75ae59..971de233 100644 --- a/appGUI/preferences/tools/ToolsFilmPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsFilmPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtGui from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, FCComboBox, FCColorEntry, FCLabel, FCSpinner, \ diff --git a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py index 0a041e38..56539e47 100644 --- a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox2, FCCheckBox, FCSpinner, NumericalEvalTupleEntry, \ diff --git a/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py b/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py index e6d4f2ce..5002e6c7 100644 --- a/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsLevelPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCLabel, FCComboBox, GLay, FCFrame diff --git a/appGUI/preferences/tools/ToolsMarkersPrefGroupUI.py b/appGUI/preferences/tools/ToolsMarkersPrefGroupUI.py index e8e7f181..c8616da7 100644 --- a/appGUI/preferences/tools/ToolsMarkersPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsMarkersPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCLabel, RadioSet, GLay, FCFrame diff --git a/appGUI/preferences/tools/ToolsMillPrefGroupUI.py b/appGUI/preferences/tools/ToolsMillPrefGroupUI.py index 00793fb0..4e3609e5 100644 --- a/appGUI/preferences/tools/ToolsMillPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsMillPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtCore from PyQt6.QtCore import Qt diff --git a/appGUI/preferences/tools/ToolsNCCPrefGroupUI.py b/appGUI/preferences/tools/ToolsNCCPrefGroupUI.py index c547ebb5..7017c4c8 100644 --- a/appGUI/preferences/tools/ToolsNCCPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsNCCPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry, FCComboBox2, FCLabel, \ diff --git a/appGUI/preferences/tools/ToolsPaintPrefGroupUI.py b/appGUI/preferences/tools/ToolsPaintPrefGroupUI.py index 789de600..977debd8 100644 --- a/appGUI/preferences/tools/ToolsPaintPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsPaintPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import RadioSet, FCDoubleSpinner, FCComboBox2, FCCheckBox, NumericalEvalTupleEntry, FCLabel, \ diff --git a/appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py b/appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py index 82532ca4..d4d9f8f8 100644 --- a/appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsPanelizePrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets from appGUI.GUIElements import FCDoubleSpinner, FCSpinner, RadioSet, FCCheckBox, FCLabel, GLay, FCFrame diff --git a/appGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py b/appGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py index f17a1618..d440b84d 100644 --- a/appGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsSolderpastePrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtCore from appGUI.GUIElements import FCDoubleSpinner, FCSpinner, FCComboBox, NumericalEvalTupleEntry, FCLabel, GLay, \ diff --git a/appGUI/preferences/tools/ToolsSubPrefGroupUI.py b/appGUI/preferences/tools/ToolsSubPrefGroupUI.py index 6ae2249e..a14ccdb3 100644 --- a/appGUI/preferences/tools/ToolsSubPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsSubPrefGroupUI.py @@ -1,3 +1,4 @@ + from appGUI.GUIElements import FCCheckBox, FCLabel, FCFrame, GLay from appGUI.preferences.OptionsGroupUI import OptionsGroupUI diff --git a/appGUI/preferences/tools/ToolsTransformPrefGroupUI.py b/appGUI/preferences/tools/ToolsTransformPrefGroupUI.py index ff0a8204..69d12267 100644 --- a/appGUI/preferences/tools/ToolsTransformPrefGroupUI.py +++ b/appGUI/preferences/tools/ToolsTransformPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtGui from appGUI.GUIElements import FCDoubleSpinner, FCCheckBox, NumericalEvalTupleEntry, FCComboBox, FCLabel, \ diff --git a/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py b/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py index 93139239..70fbd118 100644 --- a/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py +++ b/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/utilities/FAExcPrefGroupUI.py b/appGUI/preferences/utilities/FAExcPrefGroupUI.py index 68a6a5f7..674ca6a3 100644 --- a/appGUI/preferences/utilities/FAExcPrefGroupUI.py +++ b/appGUI/preferences/utilities/FAExcPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/utilities/FAGcoPrefGroupUI.py b/appGUI/preferences/utilities/FAGcoPrefGroupUI.py index 9df84f31..eaa99a60 100644 --- a/appGUI/preferences/utilities/FAGcoPrefGroupUI.py +++ b/appGUI/preferences/utilities/FAGcoPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/utilities/FAGrbPrefGroupUI.py b/appGUI/preferences/utilities/FAGrbPrefGroupUI.py index dcec8eca..459bbdae 100644 --- a/appGUI/preferences/utilities/FAGrbPrefGroupUI.py +++ b/appGUI/preferences/utilities/FAGrbPrefGroupUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets, QtGui from PyQt6.QtCore import QSettings diff --git a/appGUI/preferences/utilities/UtilPreferencesUI.py b/appGUI/preferences/utilities/UtilPreferencesUI.py index e47d3034..2b52c1b6 100644 --- a/appGUI/preferences/utilities/UtilPreferencesUI.py +++ b/appGUI/preferences/utilities/UtilPreferencesUI.py @@ -1,3 +1,4 @@ + from PyQt6 import QtWidgets import sys diff --git a/appHandlers/AppIO.py b/appHandlers/AppIO.py index 2f05a00a..c786b227 100644 --- a/appHandlers/AppIO.py +++ b/appHandlers/AppIO.py @@ -1,4 +1,7 @@ +from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6.QtCore import Qt, QSettings + from appEditors.AppExcEditor import AppExcEditor from appEditors.AppGeoEditor import AppGeoEditor from appEditors.AppGerberEditor import AppGerberEditor @@ -7,7 +10,7 @@ from appGUI.GUIElements import FCFileSaveDialog, FCMessageBox from camlib import to_dict, dict2obj, ET, ParseError from appParsers.ParseHPGL2 import HPGL2 -from appObjects.ObjectCollection import * +from appObjects.ObjectCollection import GerberObject, ExcellonObject, GeometryObject, ScriptObject, CNCJobObject from reportlab.graphics import renderPDF from reportlab.pdfgen import canvas diff --git a/appMain.py b/appMain.py index 93482057..08ab60bd 100644 --- a/appMain.py +++ b/appMain.py @@ -6,19 +6,36 @@ # MIT Licence # # Modified by Marius Stanciu (2019) # # ########################################################### + +from PyQt6 import QtGui, QtWidgets +from PyQt6.QtCore import QSettings, pyqtSlot +from PyQt6.QtCore import Qt, pyqtSignal +from PyQt6.QtGui import QAction, QTextCursor + import os.path +import sys + import urllib.request import urllib.parse import urllib.error +from datetime import datetime as dt +from copy import deepcopy, copy +import numpy as np + import getopt import random import simplejson as json import shutil -from datetime import datetime import traceback +import logging +import time +import webbrowser +import platform +import re +import subprocess -from shapely.geometry import Point, MultiPolygon, MultiLineString +from shapely.geometry import Point, MultiPolygon, MultiLineString, Polygon, LinearRing, LineString from shapely.ops import unary_union from io import StringIO @@ -38,9 +55,18 @@ import qdarktheme.themes.light.stylesheet as qlightsheet # ################################### Imports part of FlatCAM ############################################# # #################################################################################################################### -# Various +# App appGUI +from appGUI.PlotCanvas import PlotCanvas +from appGUI.PlotCanvasLegacy import PlotCanvasLegacy +from appGUI.PlotCanvas3d import PlotCanvas3d +from appGUI.MainGUI import MainGUI +from appGUI.VisPyVisuals import ShapeCollection +from appGUI.GUIElements import FCMessageBox, FCInputSpinner, FCButton, DialogBoxRadio, Dialog_box, FCTree, \ + FCInputDoubleSpinner, FCFileSaveDialog, message_dialog, AppSystemTray, FCInputDialogSlider, \ + GLay, FCLabel, DialogBoxChoice, VerticalScrollArea from appGUI.themes import dark_style_sheet, light_style_sheet +# Various from appCommon.Common import color_variant from appCommon.Common import ExclusionAreas from appCommon.Common import AppLogging @@ -58,7 +84,8 @@ from defaults import AppOptions # App Objects from appGUI.preferences.OptionsGroupUI import OptionsGroupUI from appGUI.preferences.PreferencesUIManager import PreferencesUIManager -from appObjects.ObjectCollection import * +from appObjects.ObjectCollection import ObjectCollection, GerberObject, ExcellonObject, GeometryObject, \ + CNCJobObject, ScriptObject, DocumentObject from appObjects.AppObjectTemplate import FlatCAMObj from appObjects.AppObject import AppObject @@ -67,14 +94,6 @@ from appParsers.ParseExcellon import Excellon from appParsers.ParseGerber import Gerber from camlib import to_dict, Geometry, CNCjob -# App appGUI -from appGUI.PlotCanvas import * -from appGUI.PlotCanvasLegacy import * -from appGUI.PlotCanvas3d import * -from appGUI.MainGUI import * -from appGUI.GUIElements import FCFileSaveDialog, message_dialog, AppSystemTray, FCInputDialogSlider, \ - GLay, FCLabel, DialogBoxChoice - # App Pre-processors from appPreProcessor import load_preprocessors @@ -92,6 +111,8 @@ from appWorkerStack import WorkerStack # App Plugins from appPlugins import * +from numpy import Inf + # App Translation import gettext import appTranslation as fcTranslate @@ -158,7 +179,7 @@ class App(QtCore.QObject): engine = '3D' # current date now - date = str(datetime.today()).rpartition('.')[0] + date = str(dt.today()).rpartition('.')[0] date = ''.join(c for c in date if c not in ':-') date = date.replace(' ', '_') @@ -258,8 +279,8 @@ class App(QtCore.QObject): """ Starts the application. - :return: app - :rtype: App + :return: the application + :rtype: QtCore.QObject """ super().__init__() @@ -1357,7 +1378,7 @@ class App(QtCore.QObject): sys.exit(2) # accept some type file as command line parameter: FlatCAM project, FlatCAM preferences or scripts - # the path/file_name must be enclosed in quotes if it contain spaces + # the path/file_name must be enclosed in quotes, if it contains spaces if App.args: self.args_at_startup.emit(App.args) @@ -1573,13 +1594,13 @@ class App(QtCore.QObject): """ This installs the FlatCAM tools (plugin-like) which reside in their own classes. Instantiation of the Tools classes. - The order that the tools are installed is important as they can depend on each other install position. + The order that the tools are installed is important as they can depend on each other installing position. :return: None """ if init_tcl: - # shell tool has to be initialized always first because other tools print messages in the Shell Dock + # Tcl Shell tool has to be initialized always first because other tools print messages in the Shell Dock self.shell = FCShell(app=self, version=self.version) self.log.debug("TCL was re-instantiated. TCL variables are reset.") @@ -2259,7 +2280,7 @@ class App(QtCore.QObject): def object2editor(self): """ - Send the current Geometry, Gerber, Excellon object or CNCJob (if any) into the it's editor. + Send the current Geometry, Gerber, Excellon object or CNCJob (if any) its editor. :return: None """ @@ -2286,7 +2307,7 @@ class App(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s' % _("The Editor could not start.")) return - # store the Geometry Editor Toolbar visibility before entering in the Editor + # store the Geometry Editor Toolbar visibility before entering the Editor self.geo_editor.toolbar_old_state = True if self.ui.geo_edit_toolbar.isVisible() else False # we set the notebook to hidden @@ -2329,7 +2350,7 @@ class App(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s' % _("The Editor could not start.")) return - # store the Excellon Editor Toolbar visibility before entering in the Editor + # store the Excellon Editor Toolbar visibility before entering the Editor self.exc_editor.toolbar_old_state = True if self.ui.exc_edit_toolbar.isVisible() else False if self.ui.splitter.sizes()[0] == 0: @@ -2345,7 +2366,7 @@ class App(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s' % _("The Editor could not start.")) return - # store the Gerber Editor Toolbar visibility before entering in the Editor + # store the Gerber Editor Toolbar visibility before entering the Editor self.grb_editor.toolbar_old_state = True if self.ui.grb_edit_toolbar.isVisible() else False if self.ui.splitter.sizes()[0] == 0: @@ -2410,9 +2431,9 @@ class App(QtCore.QObject): def editor2object(self, cleanup=None, force_cancel=None): """ - Transfers the Geometry or Excellon from it's editor to the current object. + Transfers the Geometry or Excellon from its editor to the current object. - :param cleanup: if True then we closed the app when the editor was open so we close first the editor + :param cleanup: if True then we closed the app when the editor was open, so we close first the editor :param force_cancel: if True always add Cancel button :return: None """ @@ -2430,7 +2451,7 @@ class App(QtCore.QObject): except Exception: pass - # This is the object that exit from the Editor. It may be the edited object but it can be a new object + # This is the object that exit from the Editor. It may be the edited object, but it can be a new object # created by the Editor edited_obj = self.collection.get_active() @@ -2480,7 +2501,7 @@ class App(QtCore.QObject): # Remove anything else in the appGUI self.ui.plugin_scroll_area.takeWidget() - # update the geo object options so it is including the bounding box values + # update the geo object options, so it is including the bounding box values try: xmin, ymin, xmax, ymax = edited_obj.bounds(flatten=True) edited_obj.obj_options['xmin'] = xmin @@ -2753,8 +2774,8 @@ class App(QtCore.QObject): :type msg: str :param new_line: if True then after printing the message add a new line char :type new_line: bool - :return: None - :rtype: None + :return: + :rtype: """ self.shell_message(msg=msg, new_line=new_line) @@ -2766,13 +2787,13 @@ class App(QtCore.QObject): :type content_to_save: str :param txt_content: text that is not HTML :type txt_content: str - :return: None - :rtype: None + :return: + :rtype: """ self.defaults.report_usage("save_to_file") self.log.debug("save_to_file()") - date = str(datetime.today()).rpartition('.')[0] + date = str(dt.today()).rpartition('.')[0] date = ''.join(c for c in date if c not in ':-') date = date.replace(' ', '_') @@ -2834,7 +2855,7 @@ class App(QtCore.QObject): def register_recent(self, kind, filename): """ - Will register the files opened into record dictionaries. The FlatCAM projects has it's own + Will register the files opened into record dictionaries. The FlatCAM projects has its own dictionary. :param kind: type of file that was opened @@ -3687,8 +3708,8 @@ class App(QtCore.QObject): """ Called when the user click on the menu entry Help -> Bookmarks -> Backup Site - :return: None - :rtype: None + :return: + :rtype: """ msgbox = FCMessageBox(parent=self.ui) title = _("Alternative website") @@ -3913,7 +3934,7 @@ class App(QtCore.QObject): data = None if sys.platform != 'win32': - # this won't work in Linux or MacOS + # this won't work in Linux or macOS return # test if the app was frozen and choose the path for the configuration file @@ -4117,7 +4138,7 @@ class App(QtCore.QObject): Called for converting a Geometry object from single-geo to multi-geo. Single-geo Geometry objects store their geometry data into self.solid_geometry. Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually) - having as a value another dictionary. This value dictionary has one of it's keys 'solid_geometry' which holds + having as a value another dictionary. This value dictionary has one of its keys 'solid_geometry' which holds the solid-geometry of that tool. :return: None @@ -4153,7 +4174,7 @@ class App(QtCore.QObject): Called for converting a Geometry object from multi-geo to single-geo. Single-geo Geometry objects store their geometry data into self.solid_geometry. Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually) - having as a value another dictionary. This value dictionary has one of it's keys 'solid_geometry' which holds + having as a value another dictionary. This value dictionary has one of its keys 'solid_geometry' which holds the solid-geometry of that tool. :return: None @@ -4191,8 +4212,8 @@ class App(QtCore.QObject): Called whenever a key changed in the self.options dictionary. It will set the required GUI element in the Edit -> Preferences tab window. - :param field: the key of the self.options dictionary that was changed. - :return: None + :param field: the key of the self.options dictionary that was changed. + :return: None """ self.preferencesUiManager.defaults_write_form_field(field=field) @@ -4213,7 +4234,7 @@ class App(QtCore.QObject): new_units, factor = self.on_toggle_units() except TypeError: # hen the self.on_toggle_units() return only one value (maybe None) it means something went wrong, - # it will end up in this exception and and we should return + # it will end up in this exception, and we should return return if self.ui.plot_tab_area.currentWidget().objectName() == "preferences_tab": @@ -4230,7 +4251,7 @@ class App(QtCore.QObject): def scale_preferences(self, sfactor, new_units): self.preferencesUiManager.defaults_read_form() - # update the defaults from form, some may assume that the conversion is enough and it's not + # update the defaults from form, some may assume that the conversion is enough, and it's not self.on_defaults2options() # Keys in self.options for which to scale their values @@ -4404,7 +4425,7 @@ class App(QtCore.QObject): """ Callback for the Units radio-button change in the Preferences tab. Changes the application's default units adn for the project too. - If changing the project's units, the change propagates to all of + If changing the project's units, the change propagates to all the objects in the project. :return: The new application units. String: "IN" or "MM" with caps lock @@ -4477,14 +4498,14 @@ class App(QtCore.QObject): self.plot_all() self.set_screen_units(new_units) - # flag for the app that we changed the object properties and it should save the project + # flag for the app that we changed the object properties, and it should save the project self.should_we_save = True self.inform.emit('[success] %s: %s' % (_("Converted units to"), new_units)) else: factor = 1 - # store the grid values so they are not changed in the next step + # store the grid values, so they are not changed in the next step val_x = float(self.options['global_gridx']) val_y = float(self.options['global_gridy']) @@ -4667,7 +4688,7 @@ class App(QtCore.QObject): else: self.on_delete() - # It's meant to delete selected objects. It work also activated by a shortcut key 'Delete' same as above so in + # It's meant to delete selected objects. It may work also activated by a shortcut key 'Delete' same as above so in # some screens you have to be careful where you hover with your mouse. # Hovering over Selected tab, if the selected tab is a Geometry it will delete tools in tool table. But even if # there is a Selected tab in focus with a Geometry inside, if you hover over canvas it will delete an object. @@ -5156,8 +5177,8 @@ class App(QtCore.QObject): cursor = QtGui.QCursor() if self.use_3d_engine: - # I don't know where those differences come from but they are constant for the current - # execution of the application and they are multiples of a value around 0.0263mm. + # I don't know where those differences come from, but they are constant for the current + # execution of the application, and they are multiples of a value around 0.0263mm. # In a random way sometimes they are more sometimes they are less # if units == 'MM': # cal_factor = 0.0263 @@ -5272,8 +5293,8 @@ class App(QtCore.QObject): cursor = QtGui.QCursor() if self.use_3d_engine: - # I don't know where those differences come from but they are constant for the current - # execution of the application and they are multiples of a value around 0.0263mm. + # I don't know where those differences come from, but they are constant for the current + # execution of the application, and they are multiples of a value around 0.0263mm. # In a random way sometimes they are more sometimes they are less # if units == 'MM': # cal_factor = 0.0263 @@ -5339,7 +5360,7 @@ class App(QtCore.QObject): def on_numeric_move(self, val=None): """ - Move to a specific location (absolute or relative against current position)' + Move to a specific location (absolute or relative against current position) :param val: custom offset value, (x, y) :type val: tuple @@ -5372,7 +5393,7 @@ class App(QtCore.QObject): maxy = max(maxy, maxy_) return minx, miny, maxx, maxy except TypeError: - # it's a App object, return its bounds + # it's an App object, return its bounds if obj: return obj.bounds() @@ -6806,9 +6827,9 @@ class App(QtCore.QObject): """ Callback for the mouse motion event over the plot. - :param event: Contains information about the event. - :param origin_click - :return: None + :param event: Contains information about the event. + :param origin_click: + :return: None """ if self.use_3d_engine: @@ -6918,7 +6939,7 @@ class App(QtCore.QObject): obj.isHovering = False self.delete_hover_shape() except Exception: - # the Exception here will happen if we try to select on screen and we have an + # the Exception here will happen if we try to select on screen, and we have a # newly (and empty) just created Geometry or Excellon object that do not have the # xmin, xmax, ymin, ymax options. # In this case poly_obj creation (see above) will fail @@ -7028,7 +7049,7 @@ class App(QtCore.QObject): self.select_objects(key='multisel') else: # If there is no active command (self.command_active is None) then we check if - # we clicked on a object by checking the bounding limits against mouse click position + # we clicked on an object by checking the bounding limits against mouse click position self.select_objects() self.delete_hover_shape() @@ -7179,7 +7200,7 @@ class App(QtCore.QObject): self.collection.set_active(obj.obj_options['name']) obj.selection_shape_drawn = True except Exception as e: - # the Exception here will happen if we try to select on screen and we have an newly (and empty) + # the Exception here will happen if we try to select on screen, and we have a newly (and empty) # just created Geometry or Excellon object that do not have the xmin, xmax, ymin, ymax options. # In this case poly_obj creation (see above) will fail self.log.error("App.selection_area_handler() --> %s" % str(e)) @@ -7221,7 +7242,7 @@ class App(QtCore.QObject): if self.objects_under_the_click_list: curr_sel_obj = self.collection.get_active() - # case when there is only an object under the click and we toggle it + # case when there is only an object under the click, and we toggle it if len(self.objects_under_the_click_list) == 1: try: if curr_sel_obj is None: @@ -7265,7 +7286,7 @@ class App(QtCore.QObject): self.collection.get_by_name(self.objects_under_the_click_list[0]).selection_shape_drawn = True name_sel_obj = self.collection.get_active().obj_options['name'] - # In case that there is a selected object but it is not in the overlapped object list + # In case that there is a selected object, but it is not in the overlapped object list # make that object inactive and activate the first element in the overlapped object list if name_sel_obj not in self.objects_under_the_click_list: self.collection.set_inactive(name_sel_obj) @@ -7324,8 +7345,8 @@ class App(QtCore.QObject): :param curr_sel_obj: Application object that have geometry: Geometry, Gerber, Excellon, CNCJob :type curr_sel_obj: - :return: None - :rtype: None + :return: + :rtype: """ if curr_sel_obj: if curr_sel_obj.kind == 'gerber': @@ -7803,7 +7824,7 @@ class App(QtCore.QObject): line = 0 if dia_box.ok: - # make sure to move first the cursor at the end so after finding the line the line will be positioned + # make sure to move first the cursor at the end so after finding the line, the line will be positioned # at the top of the window self.ui.plot_tab_area.currentWidget().code_editor.moveCursor(QTextCursor.MoveOperation.End) # get the document() of the AppTextEditor @@ -7883,7 +7904,7 @@ class App(QtCore.QObject): def setup_recent_items(self): """ - Setup a dictionary with the recent files accessed, organized by type + Set up a dictionary with the recent files accessed, organized by type :return: """ @@ -7956,7 +7977,7 @@ class App(QtCore.QObject): fp.close() # Closure needed to create callbacks in a loop. - # Otherwise late binding occurs. + # Otherwise, late binding occurs. def make_callback(func, fname): def opener(): func(fname) @@ -8286,7 +8307,7 @@ class App(QtCore.QObject): def on_plotcanvas_add(plotcanvas_obj, container): """ - :param plotcanvas_obj: the class that setup the canvas + :param plotcanvas_obj: the class that set up the canvas :type plotcanvas_obj: class :param container: a layout where to add the native widget of the plotcanvas_obj class :type container: @@ -8686,8 +8707,8 @@ class App(QtCore.QObject): :type fill_color: str :param outline_color: the outline color that will be set for the selected objects :type outline_color: str - :return: None - :rtype: None + :return: + :rtype: """ # make sure to set the color in the Gerber colors storage self.options["gerber_color_list"] @@ -8794,8 +8815,8 @@ class App(QtCore.QObject): :param msg: Message to display. :param show: Opens the shell. :param error: Shows the message as an error. - :param warning: Shows the message as an warning. - :param success: Shows the message as an success. + :param warning: Shows the message as a warning. + :param success: Shows the message as a success. :param selected: Indicate that something was selected on canvas :return: None """ diff --git a/appObjects/AppObject.py b/appObjects/AppObject.py index 10d43655..ae542164 100644 --- a/appObjects/AppObject.py +++ b/appObjects/AppObject.py @@ -7,7 +7,8 @@ # Modified by Marius Stanciu (2020) # # ########################################################### -from appObjects.ObjectCollection import * +from PyQt6 import QtCore + from appObjects.CNCJobObject import CNCJobObject from appObjects.DocumentObject import DocumentObject from appObjects.ExcellonObject import ExcellonObject @@ -17,6 +18,7 @@ from appObjects.ScriptObject import ScriptObject import time import traceback +from copy import deepcopy # FlatCAM Translation import gettext diff --git a/appObjects/AppObjectTemplate.py b/appObjects/AppObjectTemplate.py index efc98e1f..73db2086 100644 --- a/appObjects/AppObjectTemplate.py +++ b/appObjects/AppObjectTemplate.py @@ -10,10 +10,9 @@ # File modified by: Marius Stanciu # # ########################################################## -# import inspect - -from appGUI.ObjectUI import * +from PyQt6 import QtCore, QtGui +from appGUI.ObjectUI import ObjectUI from appCommon.Common import LoudDict from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy from appGUI.VisPyVisuals import ShapeCollection @@ -21,9 +20,10 @@ from appGUI.VisPyVisuals import ShapeCollection from shapely.ops import unary_union from shapely.geometry import Polygon, MultiPolygon, Point, LineString -from copy import deepcopy +from copy import deepcopy, copy import sys import math +import inspect import gettext import appTranslation as fcTranslate @@ -211,12 +211,12 @@ class FlatCAMObj(QtCore.QObject): @property def visible(self): - ''' + """ This property is used by Editors to turn off plotting for the original object that is edited, such that when deleting certain elements there is no background plot in place to confuse things. :return: :rtype: - ''' + """ return self.shapes.visible @visible.setter @@ -227,6 +227,7 @@ class FlatCAMObj(QtCore.QObject): current_visibility = self.shapes.visible self.shapes.visible = current_visibility # maybe this is slower in VisPy? use enabled property? + def task(visibility): if visibility is True: if value is False: @@ -666,7 +667,7 @@ class FlatCAMObj(QtCore.QObject): if isinstance(geo, list) and geo[0] is not None: if isinstance(geo, MultiPolygon): env_obj = geo.convex_hull - elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \ + elif (isinstance(geo, MultiPolygon) and len(geo.geoms) == 1) or \ (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon): env_obj = unary_union(geo) env_obj = env_obj.convex_hull diff --git a/appObjects/CNCJobObject.py b/appObjects/CNCJobObject.py index e3af96cc..83250a15 100644 --- a/appObjects/CNCJobObject.py +++ b/appObjects/CNCJobObject.py @@ -10,17 +10,22 @@ # File modified by: Marius Stanciu # # ########################################################## -from io import StringIO -from datetime import datetime +from PyQt6 import QtCore, QtWidgets from appEditors.AppTextEditor import AppTextEditor -from appObjects.AppObjectTemplate import * - +from appObjects.AppObjectTemplate import FlatCAMObj, ObjectDeleted +from appGUI.GUIElements import FCFileSaveDialog, FCCheckBox +from appGUI.ObjectUI import CNCObjectUI from camlib import CNCjob import os import sys import math +import re + +from io import StringIO +from datetime import datetime as dt +from copy import deepcopy import gettext import appTranslation as fcTranslate @@ -855,7 +860,7 @@ class CNCJobObject(FlatCAMObj, CNCjob): """ self.app.log.debug("FlatCAMCNCJob.gcode_header()") - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + time_str = "{:%A, %d %B %Y at %H:%M}".format(dt.now()) marlin = False hpgl = False probe_pp = False diff --git a/appObjects/DocumentObject.py b/appObjects/DocumentObject.py index 28a88afb..3092ab0f 100644 --- a/appObjects/DocumentObject.py +++ b/appObjects/DocumentObject.py @@ -9,8 +9,12 @@ # ########################################################## # File modified by: Marius Stanciu # # ########################################################## + +from PyQt6.QtCore import Qt + from appEditors.AppTextEditor import AppTextEditor from appObjects.AppObjectTemplate import * +from appGUI.ObjectUI import DocumentObjectUI import gettext import appTranslation as fcTranslate diff --git a/appObjects/ExcellonObject.py b/appObjects/ExcellonObject.py index 39b52230..bcba1428 100644 --- a/appObjects/ExcellonObject.py +++ b/appObjects/ExcellonObject.py @@ -10,14 +10,18 @@ # File modified by: Marius Stanciu # # ########################################################## - -from shapely.geometry import LineString +from PyQt6 import QtWidgets, QtCore, QtGui from appParsers.ParseExcellon import Excellon -from appObjects.AppObjectTemplate import * +from appObjects.AppObjectTemplate import FlatCAMObj, ObjectDeleted +from appGUI.GUIElements import FCCheckBox +from appGUI.ObjectUI import ExcellonObjectUI import itertools import numpy as np +from copy import deepcopy + +from shapely.geometry import LineString import gettext import appTranslation as fcTranslate diff --git a/appObjects/GeometryObject.py b/appObjects/GeometryObject.py index 4915fa4d..04a72390 100644 --- a/appObjects/GeometryObject.py +++ b/appObjects/GeometryObject.py @@ -10,17 +10,22 @@ # File modified by: Marius Stanciu # # ########################################################## -from shapely.geometry import MultiLineString, LinearRing -import shapely.affinity as affinity +from PyQt6 import QtWidgets, QtCore +from appObjects.AppObjectTemplate import FlatCAMObj, ObjectDeleted +from appGUI.GUIElements import FCCheckBox +from appGUI.ObjectUI import GeometryObjectUI + +from shapely.geometry import MultiLineString, LinearRing, Polygon, MultiPolygon, LineString +from shapely.affinity import scale, translate +from shapely.ops import unary_union from camlib import Geometry, flatten_shapely_geometry -from appObjects.AppObjectTemplate import * - +import re import ezdxf import numpy as np -from copy import deepcopy import traceback +from copy import deepcopy from collections import defaultdict from functools import reduce @@ -1193,7 +1198,7 @@ class GeometryObject(FlatCAMObj, Geometry): self.app.proc_container.update_view_text(' %d%%' % disp_number) self.old_disp_number = disp_number - return affinity.scale(geom, xfactor, yfactor, origin=(px, py)) + return scale(geom, xfactor, yfactor, origin=(px, py)) except AttributeError: return geom @@ -1269,7 +1274,7 @@ class GeometryObject(FlatCAMObj, Geometry): self.app.proc_container.update_view_text(' %d%%' % disp_number) self.old_disp_number = disp_number - return affinity.translate(geom, xoff=dx, yoff=dy) + return translate(geom, xoff=dx, yoff=dy) except AttributeError: return geom diff --git a/appObjects/GerberObject.py b/appObjects/GerberObject.py index ed02bf1a..4e5c038c 100644 --- a/appObjects/GerberObject.py +++ b/appObjects/GerberObject.py @@ -10,12 +10,16 @@ # File modified by: Marius Stanciu # # ########################################################## +from PyQt6 import QtWidgets, QtCore +from appGUI.GUIElements import FCCheckBox +from appGUI.ObjectUI import GerberObjectUI +from appParsers.ParseGerber import Gerber +from appObjects.AppObjectTemplate import FlatCAMObj, ObjectDeleted, ValidationError -from shapely.geometry import MultiLineString, LinearRing from camlib import flatten_shapely_geometry -from appParsers.ParseGerber import Gerber -from appObjects.AppObjectTemplate import * +from shapely.geometry import MultiLineString, LinearRing, MultiPolygon, Polygon, LineString, Point +from shapely.ops import unary_union import numpy as np from copy import deepcopy diff --git a/appObjects/ScriptObject.py b/appObjects/ScriptObject.py index a7eb5d75..f1446abd 100644 --- a/appObjects/ScriptObject.py +++ b/appObjects/ScriptObject.py @@ -10,9 +10,11 @@ # File modified by: Marius Stanciu # # ########################################################## +from PyQt6 import QtCore + from appEditors.AppTextEditor import AppTextEditor -from appObjects.AppObjectTemplate import * -from appGUI.ObjectUI import * +from appObjects.AppObjectTemplate import FlatCAMObj +from appGUI.ObjectUI import ScriptObjectUI import gettext import appTranslation as fcTranslate diff --git a/appParsers/ParseDXF.py b/appParsers/ParseDXF.py index fb6e7639..9fe62ca8 100644 --- a/appParsers/ParseDXF.py +++ b/appParsers/ParseDXF.py @@ -5,15 +5,14 @@ # MIT Licence # # ########################################################## -from shapely.geometry import LineString, Point -from shapely.affinity import rotate -# from ezdxf.math import Vector as ezdxf_vector -from ezdxf.math import Vec3 as ezdxf_vector - -from appParsers.ParseFont import * from appParsers.ParseDXF_Spline import spline2Polyline, normalize_2 from appParsers.ParseDXF_Spline import Vector as DxfVector +from shapely.geometry import LineString, Point, Polygon +from shapely.affinity import rotate, translate, scale +# from ezdxf.math import Vector as ezdxf_vector +from ezdxf.math import Vec3 as ezdxf_vector + import math import logging diff --git a/appParsers/ParseExcellon.py b/appParsers/ParseExcellon.py index 3133b088..5e660e19 100644 --- a/appParsers/ParseExcellon.py +++ b/appParsers/ParseExcellon.py @@ -419,10 +419,10 @@ class Excellon(Geometry): self.app.log.debug("ALternative M71/M72 units found, after conversion: %s" % self.units) if self.units == 'MM': self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm))) + (str(self.excellon_format_upper_mm), str(self.excellon_format_lower_mm))) else: self.app.log.warning("Excellon format preset is: %s:%s" % - (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in))) + (str(self.excellon_format_upper_in), str(self.excellon_format_lower_in))) continue # ### Body #### diff --git a/appParsers/ParseFont.py b/appParsers/ParseFont.py index 719bd96e..c0dcb23b 100644 --- a/appParsers/ParseFont.py +++ b/appParsers/ParseFont.py @@ -22,6 +22,7 @@ import freetype as ft from fontTools import ttLib import logging + import gettext import appTranslation as fcTranslate import builtins diff --git a/appParsers/ParseGerber.py b/appParsers/ParseGerber.py index 74b31dad..490c4107 100644 --- a/appParsers/ParseGerber.py +++ b/appParsers/ParseGerber.py @@ -1,6 +1,10 @@ + from PyQt6 import QtWidgets from camlib import Geometry, arc, arc_angle, ApertureMacro, grace, flatten_shapely_geometry +from appParsers.ParseDXF import getdxfgeo +from appParsers.ParseSVG import svgparselength, getsvggeo, svgparse_viewbox + import numpy as np import traceback from copy import deepcopy @@ -8,13 +12,13 @@ from copy import deepcopy from shapely.ops import unary_union, linemerge import shapely.affinity as affinity from shapely.geometry import box as shply_box -from shapely.geometry import LinearRing, MultiLineString +from shapely.geometry import LinearRing, MultiLineString, LineString, Polygon, MultiPolygon, Point from lxml import etree as ET import ezdxf - -from appParsers.ParseDXF import * -from appParsers.ParseSVG import svgparselength, getsvggeo, svgparse_viewbox +import logging +import re +import sys import gettext import builtins @@ -1422,7 +1426,8 @@ class Gerber(Geometry): j = 0 if quadrant_mode is None: - self.app.log.error("Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num) + self.app.log.error( + "Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num) self.app.log.error(gline) continue @@ -1959,7 +1964,7 @@ class Gerber(Geometry): self.app.log.debug("appParsers.ParseGerber.Gerber.import_svg(). Finished parsing the SVG geometry.") if flip: - geos = [translate(scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos] + geos = [affinity.translate(affinity.scale(g, 1.0, -1.0, origin=(0, 0)), yoff=h) for g in geos] self.app.log.debug("appParsers.ParseGerber.Gerber.import_svg(). SVG geometry was flipped.") # Add to object @@ -2529,7 +2534,7 @@ class Gerber(Geometry): try: if isinstance(self.solid_geometry, (MultiPolygon, MultiLineString)): self.geo_len = len(self.solid_geometry.geoms) - else: + if isinstance(self.solid_geometry, list): self.geo_len = len(self.solid_geometry) except (TypeError, ValueError, RuntimeError): self.geo_len = 1 @@ -2634,8 +2639,8 @@ class Gerber(Geometry): geo_p = shply_box(minx, miny, maxx, maxy) new_geo_el['solid'] = geo_p else: - self.app.log.debug("appParsers.ParseGerber.Gerber.buffer() --> " - "ap type not supported") + self.app.log.debug( + "appParsers.ParseGerber.Gerber.buffer() --> ap type not supported") else: new_geo_el['solid'] = geo_el['follow'].buffer( size/1.9999, diff --git a/appParsers/ParseHPGL2.py b/appParsers/ParseHPGL2.py index d91d9493..81d1d554 100644 --- a/appParsers/ParseHPGL2.py +++ b/appParsers/ParseHPGL2.py @@ -18,7 +18,6 @@ import sys from shapely.ops import unary_union from shapely.geometry import LineString, Point -# import AppTranslation as fcTranslate import gettext import builtins diff --git a/appPlugins/ToolAlignObjects.py b/appPlugins/ToolAlignObjects.py index 8ab7abe5..cfa82c13 100644 --- a/appPlugins/ToolAlignObjects.py +++ b/appPlugins/ToolAlignObjects.py @@ -5,7 +5,19 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtGui, QtCore +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet + +from shapely.geometry import Point +from shapely.affinity import translate + +import logging +import math + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appPlugins/ToolCalculators.py b/appPlugins/ToolCalculators.py index 0dd99735..5f416404 100644 --- a/appPlugins/ToolCalculators.py +++ b/appPlugins/ToolCalculators.py @@ -5,12 +5,24 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, NumericalEvalEntry, RadioSet, \ + FCDoubleSpinner, FCSpinner + +import logging +import math + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext +log = logging.getLogger('base') + class ToolCalculator(AppTool): diff --git a/appPlugins/ToolCopperThieving.py b/appPlugins/ToolCopperThieving.py index e44d9edd..efa3a4a2 100644 --- a/appPlugins/ToolCopperThieving.py +++ b/appPlugins/ToolCopperThieving.py @@ -5,11 +5,28 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtGui, QtCore +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet, \ + FCDoubleSpinner, FCComboBox2, FCEntry, FCCheckBox + from appCommon.Common import LoudDict from appCommon.Common import GracefulException as grace -from camlib import flatten_shapely_geometry +from camlib import flatten_shapely_geometry + +import logging +from copy import deepcopy +import numpy as np +from typing import Iterable + import shapely.geometry.base as base +from shapely.geometry import Polygon, MultiPolygon, box, Point, LineString +from shapely.ops import unary_union +from shapely.affinity import translate + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appPlugins/ToolCutOut.py b/appPlugins/ToolCutOut.py index 9956d843..4ebc328c 100644 --- a/appPlugins/ToolCutOut.py +++ b/appPlugins/ToolCutOut.py @@ -5,11 +5,29 @@ # MIT Licence # # ########################################################## -from appTool import * -from camlib import grace, flatten_shapely_geometry -from matplotlib.backend_bases import KeyEvent as mpl_key_event +from PyQt6 import QtWidgets, QtGui, QtCore +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet, \ + FCDoubleSpinner, FCComboBox2, OptionalInputSection, FCCheckBox +from camlib import flatten_shapely_geometry + +import math +import logging +from copy import deepcopy +import simplejson as json +import sys from numpy import Inf +from shapely.geometry import Polygon, MultiPolygon, box, Point, LineString, MultiLineString, LinearRing +from shapely.ops import unary_union, linemerge +from shapely.affinity import rotate + +from matplotlib.backend_bases import KeyEvent as mpl_key_event + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext diff --git a/appPlugins/ToolDblSided.py b/appPlugins/ToolDblSided.py index e7435166..5d0e8719 100644 --- a/appPlugins/ToolDblSided.py +++ b/appPlugins/ToolDblSided.py @@ -1,5 +1,19 @@ -from appTool import * +from PyQt6 import QtWidgets, QtGui, QtCore +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet, \ + FCDoubleSpinner, FCComboBox2, NumericalEvalTupleEntry + +import logging +from copy import deepcopy +from numpy import Inf + +from shapely.geometry import Point +from shapely.affinity import scale + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appPlugins/ToolDistance.py b/appPlugins/ToolDistance.py index 35e21632..56c4f898 100644 --- a/appPlugins/ToolDistance.py +++ b/appPlugins/ToolDistance.py @@ -5,11 +5,25 @@ # MIT Licence # # ########################################################## -from appTool import * -from appGUI.VisPyVisuals import * +from PyQt6 import QtWidgets, QtCore +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCEntry, FCCheckBox +from appGUI.VisPyVisuals import ShapeCollection from camlib import AppRTreeStorage from appEditors.AppGeoEditor import DrawToolShape +import math +import logging +from copy import copy +import numpy as np + +from shapely.geometry import Polygon, Point, LineString, MultiLineString +from shapely.strtree import STRtree + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext diff --git a/appPlugins/ToolDrilling.py b/appPlugins/ToolDrilling.py index 0a0af4bb..d7ef7ccd 100644 --- a/appPlugins/ToolDrilling.py +++ b/appPlugins/ToolDrilling.py @@ -5,10 +5,29 @@ # License: MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCSpinner, NumericalEvalTupleEntry, NumericalEvalEntry, FCTable, \ + OptionalInputSection, OptionalHideInputSection from appParsers.ParseExcellon import Excellon + from matplotlib.backend_bases import KeyEvent as mpl_key_event +import logging +from copy import deepcopy +import numpy as np +import simplejson as json +import sys +import platform +import re + +from shapely.geometry import LineString + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext diff --git a/appPlugins/ToolEtchCompensation.py b/appPlugins/ToolEtchCompensation.py index 5732c862..5def644e 100644 --- a/appPlugins/ToolEtchCompensation.py +++ b/appPlugins/ToolEtchCompensation.py @@ -5,9 +5,22 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCEntry, \ + RadioSet, FCDoubleSpinner, NumericalEvalEntry from camlib import flatten_shapely_geometry +import logging +from copy import deepcopy +import math + +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -209,7 +222,8 @@ class ToolEtchCompensation(AppTool): offset = offset_value / 1000 # in microns if offset == 0: - # no need to do anything for zero value offset isn't it? compensating with zero is the same as the original + # no need to do anything for zero value, offset, isn't it? + # compensating with zero is the same as the original return grb_obj.solid_geometry = flatten_shapely_geometry(grb_obj.solid_geometry) diff --git a/appPlugins/ToolExtract.py b/appPlugins/ToolExtract.py index d5d70dfb..fe4c46d7 100644 --- a/appPlugins/ToolExtract.py +++ b/appPlugins/ToolExtract.py @@ -5,7 +5,19 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + RadioSet, FCDoubleSpinner, FCTable + +import logging +from copy import deepcopy + +from shapely.geometry import Polygon, MultiPolygon, Point, box + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appPlugins/ToolFiducials.py b/appPlugins/ToolFiducials.py index 8f1c2de4..957da7ff 100644 --- a/appPlugins/ToolFiducials.py +++ b/appPlugins/ToolFiducials.py @@ -5,10 +5,23 @@ # MIT Licence # # ########################################################## -from appTool import * -import shapely.geometry +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, EvalEntry, FCTable from appCommon.Common import LoudDict +import logging +from copy import deepcopy +import math + +from shapely.geometry import LineString, Polygon, MultiPolygon, box, Point, base +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -541,7 +554,7 @@ class ToolFiducials(AppTool): geo_buff_list = [] if aperture_found: for geo in geo_list: - assert isinstance(geo, shapely.geometry.base.BaseGeometry) + assert isinstance(geo, base.BaseGeometry) geo_buff_list.append(geo) dict_el = {'follow': geo.centroid, 'solid': geo} @@ -562,7 +575,7 @@ class ToolFiducials(AppTool): } for geo in geo_list: - assert isinstance(geo, shapely.geometry.base.BaseGeometry) + assert isinstance(geo, base.BaseGeometry) geo_buff_list.append(geo) dict_el = {'follow': geo.centroid, 'solid': geo} diff --git a/appPlugins/ToolFilm.py b/appPlugins/ToolFilm.py index 43e9e2c2..f467bb7b 100644 --- a/appPlugins/ToolFilm.py +++ b/appPlugins/ToolFilm.py @@ -5,7 +5,22 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCSpinner, FCFileSaveDialog, OptionalHideInputSection + +import logging +from copy import deepcopy +import math + +from shapely.geometry import LineString, MultiPolygon, Point, Polygon, LinearRing +from shapely.affinity import scale, skew +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins from reportlab.graphics import renderPDF from reportlab.pdfgen import canvas diff --git a/appPlugins/ToolFollow.py b/appPlugins/ToolFollow.py index d33bf6ee..a67f9e8e 100644 --- a/appPlugins/ToolFollow.py +++ b/appPlugins/ToolFollow.py @@ -5,7 +5,21 @@ # License: MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet + +import logging +from copy import deepcopy +import numpy as np + +from shapely.geometry import Polygon +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + from appParsers.ParseGerber import Gerber from matplotlib.backend_bases import KeyEvent as mpl_key_event from camlib import flatten_shapely_geometry @@ -294,8 +308,8 @@ class ToolFollow(AppTool, Gerber): followed_obj.follow_geometry = flatten_shapely_geometry(followed_obj.follow_geometry) follow_geo = [ - g for g in followed_obj.follow_geometry if g and not g.is_empty and g.is_valid and - g.geom_type != 'Point' + g for g in followed_obj.follow_geometry + if g and not g.is_empty and g.is_valid and g.geom_type != 'Point' ] if not follow_geo: diff --git a/appPlugins/ToolImage.py b/appPlugins/ToolImage.py index e889bc27..96756e7e 100644 --- a/appPlugins/ToolImage.py +++ b/appPlugins/ToolImage.py @@ -5,7 +5,21 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCSpinner, FCMessageBox + +from copy import deepcopy +import numpy as np +import os + +from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, shape +from shapely.affinity import scale, translate +import gettext +import appTranslation as fcTranslate +import builtins + from rasterio import open as rasterio_open from rasterio.features import shapes diff --git a/appPlugins/ToolInvertGerber.py b/appPlugins/ToolInvertGerber.py index 63a6c22b..52e4e4d6 100644 --- a/appPlugins/ToolInvertGerber.py +++ b/appPlugins/ToolInvertGerber.py @@ -5,7 +5,20 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet, \ + FCDoubleSpinner + +import logging +from copy import deepcopy + +from shapely.geometry import box + +import gettext +import appTranslation as fcTranslate +import builtins + from camlib import flatten_shapely_geometry fcTranslate.apply_language('strings') diff --git a/appPlugins/ToolIsolation.py b/appPlugins/ToolIsolation.py index 0318daba..3290117c 100644 --- a/appPlugins/ToolIsolation.py +++ b/appPlugins/ToolIsolation.py @@ -5,7 +5,28 @@ # License: MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCSpinner, FCInputDialogSpinnerButton, FCTable, \ + OptionalInputSection + +import logging +from copy import deepcopy +from typing import Iterable + +import numpy as np +import simplejson as json +import sys +import math + +from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, Point, LinearRing +from shapely.ops import unary_union, nearest_points + +import gettext +import appTranslation as fcTranslate +import builtins + from appParsers.ParseGerber import Gerber from matplotlib.backend_bases import KeyEvent as mpl_key_event from camlib import grace @@ -2397,7 +2418,7 @@ class ToolIsolation(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiPolygon): - for poly in geo_elem: + for poly in geo_elem.geoms: for ring in self.poly2rings(poly): new_geo = ring.difference(sub_union) if new_geo and not new_geo.is_empty: @@ -2408,7 +2429,7 @@ class ToolIsolation(AppTool, Gerber): if not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: + for line_elem in geo_elem.geoms: new_geo = line_elem.difference(sub_union) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -2424,7 +2445,7 @@ class ToolIsolation(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(target_geo, MultiLineString): - for line_elem in target_geo: + for line_elem in target_geo.geoms: new_geo = line_elem.difference(sub_union) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -2451,7 +2472,7 @@ class ToolIsolation(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiPolygon): - for poly in geo_elem: + for poly in geo_elem.geoms: for ring in self.poly2rings(poly): new_geo = ring.intersection(intersect_union) if new_geo and not new_geo.is_empty: @@ -2462,7 +2483,7 @@ class ToolIsolation(AppTool, Gerber): if not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: + for line_elem in geo_elem.geoms: new_geo = line_elem.intersection(intersect_union) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -2478,7 +2499,7 @@ class ToolIsolation(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(target_geo, MultiLineString): - for line_elem in target_geo: + for line_elem in target_geo.geoms: new_geo = line_elem.intersection(intersect_union) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index 1fcae386..8950cff9 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -5,7 +5,26 @@ # License: MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from PyQt6.QtCore import Qt +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCJog, RadioSet, FCDoubleSpinner, FCSpinner, FCFileSaveDialog, FCDetachableTab, FCTable, \ + FCZeroAxes, FCSliderWithDoubleSpinner, FCEntry, RotatedToolButton + +import logging +from copy import deepcopy +import sys + +from shapely.geometry import Point, MultiPoint, MultiPolygon, box +from shapely.ops import unary_union +from shapely.affinity import translate +from datetime import datetime as dt + +import gettext +import appTranslation as fcTranslate +import builtins + from appObjects.AppObjectTemplate import ObjectDeleted from appGUI.VisPyVisuals import * from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy @@ -1391,7 +1410,7 @@ class ToolLevelling(AppTool, CNCjob): p_gcode = '' header = '' - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + time_str = "{:%A, %d %B %Y at %H:%M}".format(dt.now()) coords = [] al_method = self.ui.al_method_radio.get_value() @@ -1492,7 +1511,7 @@ class ToolLevelling(AppTool, CNCjob): str(self.app.dec_format(probe_fr, target_obj.fr_decimals)), ) # store in a global numeric variable the value of the detected probe Z - # I offset the global numeric variable by 500 so it does not conflict with something else + # I offset the global numeric variable by 500 so, it does not conflict with something else # temp_var = int(idx + 500) # p_gcode += "#%d = %s\n" % (temp_var, probing_var) diff --git a/appPlugins/ToolMarkers.py b/appPlugins/ToolMarkers.py index da970529..1e858668 100644 --- a/appPlugins/ToolMarkers.py +++ b/appPlugins/ToolMarkers.py @@ -5,7 +5,21 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, NumericalEvalTupleEntry + +import logging +from copy import deepcopy + +from shapely.geometry import LineString, Point, MultiPolygon +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + from appCommon.Common import LoudDict from camlib import flatten_shapely_geometry diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index fb4d6cee..d2c0d90d 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -5,7 +5,26 @@ # License: MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCSpinner, NumericalEvalTupleEntry, FCTable, \ + OptionalInputSection, OptionalHideInputSection + +import logging +from copy import deepcopy +import numpy as np +import simplejson as json +import sys +import math +import traceback + +from shapely.geometry import LineString, box + +import gettext +import appTranslation as fcTranslate +import builtins + from appParsers.ParseExcellon import Excellon from matplotlib.backend_bases import KeyEvent as mpl_key_event from camlib import grace diff --git a/appPlugins/ToolMove.py b/appPlugins/ToolMove.py index 434534d0..f513c59a 100644 --- a/appPlugins/ToolMove.py +++ b/appPlugins/ToolMove.py @@ -5,8 +5,18 @@ # MIT Licence # # ########################################################## -from appTool import * -from appGUI.VisPyVisuals import * +from PyQt6 import QtWidgets, QtCore +from appTool import AppTool +from appGUI.VisPyVisuals import ShapeCollection + +import logging +from copy import copy + +from shapely.geometry import Polygon + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -87,7 +97,7 @@ class ToolMove(AppTool): return else: self.setVisible(True) - # signal that there is a command active and it is 'Move' + # signal that there is a command active, and it is 'Move' self.app.command_active = "Move" sel_obj_list = self.app.collection.get_selected() diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 62dac7eb..5941fb1b 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -5,7 +5,26 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCInputDialogSpinnerButton, FCTable, \ + OptionalInputSection + +import logging +from copy import deepcopy +import numpy as np +import simplejson as json +import sys +import traceback + +from shapely.geometry import LineString, Polygon, MultiPolygon, MultiLineString, base, LinearRing +from shapely.ops import unary_union, nearest_points + +import gettext +import appTranslation as fcTranslate +import builtins + from appParsers.ParseGerber import Gerber from camlib import grace, flatten_shapely_geometry from matplotlib.backend_bases import KeyEvent as mpl_key_event @@ -523,7 +542,7 @@ class NonCopperClear(AppTool, Gerber): sel_model = self.ui.tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns too, but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -543,7 +562,7 @@ class NonCopperClear(AppTool, Gerber): sel_model = self.ui.tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns too, but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -619,7 +638,7 @@ class NonCopperClear(AppTool, Gerber): def form_to_storage(self): if self.ui.tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage + # there is no tool in tool table, so we can't save the GUI elements values to storage return self.blockSignals(True) @@ -647,7 +666,7 @@ class NonCopperClear(AppTool, Gerber): def on_apply_param_to_all_clicked(self): if self.ui.tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage + # there is no tool in tool table, so we can't save the GUI elements values to storage self.app.log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.") return @@ -1033,7 +1052,7 @@ class NonCopperClear(AppTool, Gerber): self.app.inform.emit('[ERROR_NOTCL] %s' % msg) return 'fail' - # check if the tools diameters are less then the safe tool diameter + # check if the tools diameters are less than the safe tool diameter suitable_tools = [] for tool in sorted_tools: tool_dia = float(self.ncc_tools[tool]['tooldia']) @@ -1873,7 +1892,7 @@ class NonCopperClear(AppTool, Gerber): try: if isinstance(geo_n, MultiPolygon): env_obj = geo_n.convex_hull - elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \ + elif (isinstance(geo_n, MultiPolygon) and len(geo_n.geoms) == 1) or \ (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon): env_obj = unary_union(geo_n) else: @@ -2070,7 +2089,7 @@ class NonCopperClear(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiPolygon): - for poly in geo_elem: + for poly in geo_elem.geoms: for ring in self.poly2rings(poly): new_geo = ring.intersection(bounding_box) if new_geo and not new_geo.is_empty: @@ -2081,7 +2100,7 @@ class NonCopperClear(AppTool, Gerber): if not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: + for line_elem in geo_elem.geoms: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -2097,7 +2116,7 @@ class NonCopperClear(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(isolated_geo, MultiLineString): - for line_elem in isolated_geo: + for line_elem in isolated_geo.geoms: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -2921,7 +2940,7 @@ class NonCopperClear(AppTool, Gerber): try: if isinstance(geo_n, MultiPolygon): env_obj = geo_n.convex_hull - elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \ + elif (isinstance(geo_n, MultiPolygon) and len(geo_n.geoms) == 1) or \ (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon): env_obj = unary_union(geo_n) else: @@ -3089,7 +3108,7 @@ class NonCopperClear(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiPolygon): - for a_poly in geo_elem: + for a_poly in geo_elem.geoms: for ring in self.poly2rings(a_poly): new_geo = ring.intersection(bounding_box) if new_geo and not new_geo.is_empty: @@ -3100,7 +3119,7 @@ class NonCopperClear(AppTool, Gerber): if not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: + for line_elem in geo_elem.geoms: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -3116,7 +3135,7 @@ class NonCopperClear(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(isolated_geo, MultiLineString): - for line_elem in isolated_geo: + for line_elem in isolated_geo.geoms: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -3470,7 +3489,7 @@ class NonCopperClear(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiPolygon): - for poly_g in geo_elem: + for poly_g in geo_elem.geoms: for ring in self.poly2rings(poly_g): new_geo = ring.intersection(bounding_box) if new_geo and not new_geo.is_empty: @@ -3481,7 +3500,7 @@ class NonCopperClear(AppTool, Gerber): if not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: + for line_elem in geo_elem.geoms: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -3498,7 +3517,7 @@ class NonCopperClear(AppTool, Gerber): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(isolated_geo, MultiLineString): - for line_elem in isolated_geo: + for line_elem in isolated_geo.geoms: new_geo = line_elem.intersection(bounding_box) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -3653,7 +3672,7 @@ class NonCopperClear(AppTool, Gerber): # a smaller tool rest_geo.append(p) elif isinstance(p, MultiPolygon): - for poly_p in p: + for poly_p in p.geoms: if poly_p is not None: # provide the app with a way to process the GUI events when # in a blocking loop diff --git a/appPlugins/ToolObjectDistance.py b/appPlugins/ToolObjectDistance.py index db3229bb..bd0950af 100644 --- a/appPlugins/ToolObjectDistance.py +++ b/appPlugins/ToolObjectDistance.py @@ -5,7 +5,20 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCEntry, FCComboBox2 + +import logging +from copy import deepcopy +import math + +from shapely.geometry import Point, MultiPolygon +from shapely.ops import nearest_points, unary_union + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appPlugins/ToolOptimal.py b/appPlugins/ToolOptimal.py index 37e60ac7..998114cd 100644 --- a/appPlugins/ToolOptimal.py +++ b/appPlugins/ToolOptimal.py @@ -5,9 +5,22 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCEntry, FCTextArea, FCSpinner, OptionalHideInputSection from camlib import grace +import logging +import numpy as np + +from shapely.geometry import MultiPolygon +from shapely.ops import nearest_points + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -575,7 +588,7 @@ class OptimalUI: self.locations_textb.setStyleSheet(stylesheet) res_grid.addWidget(self.locations_textb, 6, 0, 1, 3) - # Jump button + # "Jump" button self.locate_button = FCButton(_("Jump to selected position")) self.locate_button.setToolTip( _("Select a position in the Locations text box and then\n" @@ -660,7 +673,7 @@ class OptimalUI: self.locations_sec_textb.setStyleSheet(stylesheet) self.distances_box.addWidget(self.locations_sec_textb) - # Jump button + # "Jump" button self.locate_sec_button = FCButton(_("Jump to selected position")) self.locate_sec_button.setToolTip( _("Select a position in the Locations text box and then\n" diff --git a/appPlugins/ToolPDF.py b/appPlugins/ToolPDF.py index b3f90c26..765469f5 100644 --- a/appPlugins/ToolPDF.py +++ b/appPlugins/ToolPDF.py @@ -5,9 +5,24 @@ # MIT Licence # # ########################################################## -from appTool import * -from appParsers.ParsePDF import PdfParser +from PyQt6 import QtWidgets, QtCore +from appTool import AppTool +import logging +from copy import deepcopy +import os +import time +import re +import traceback + +from shapely.geometry import Point, MultiPolygon +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + +from appParsers.ParsePDF import PdfParser from camlib import grace HAS_PIKE_MODULE = True @@ -367,7 +382,7 @@ class ToolPDF(AppTool): def periodic_check(self, check_period): """ - This function starts an QTimer and it will periodically check if parsing was done + This function starts an QTimer, and it will periodically check if parsing was done :param check_period: time at which to check periodically if all plots finished to be plotted :return: @@ -428,7 +443,7 @@ class ToolPDF(AppTool): else: self.app.worker_task.emit({'fcn': self.layer_rendering_as_gerber, 'params': [filename, ap_dict, layer_nr]}) - # delete the object already processed so it will not be processed again for other objects + # delete the object already processed, so it will not be processed again for other objects # that were opened at the same time; like in drag & drop on appGUI for obj_name in obj_to_delete: if obj_name in self.pdf_parsed: diff --git a/appPlugins/ToolPaint.py b/appPlugins/ToolPaint.py index c3f762eb..e2c6a213 100644 --- a/appPlugins/ToolPaint.py +++ b/appPlugins/ToolPaint.py @@ -5,12 +5,33 @@ # MIT Licence # # ########################################################## -from appTool import * -from appParsers.ParseGerber import Gerber -from camlib import Geometry, AppRTreeStorage, grace +from PyQt6 import QtWidgets, QtCore, QtGui +from PyQt6.QtCore import Qt + +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCComboBox2, RadioSet, FCDoubleSpinner, FCInputDoubleSpinner, FCTable from matplotlib.backend_bases import KeyEvent as mpl_key_event +import logging +from copy import deepcopy +import numpy as np +import simplejson as json +import sys +import traceback +from numpy import Inf + +from shapely.geometry import LineString, Polygon, MultiLineString, MultiPolygon, Point, LinearRing, base +from shapely.ops import unary_union, linemerge + +import gettext +import appTranslation as fcTranslate +import builtins + +from appParsers.ParseGerber import Gerber +from camlib import Geometry, AppRTreeStorage, grace + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -482,7 +503,7 @@ class ToolPaint(AppTool, Gerber): sel_model = self.ui.tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns, too but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -502,7 +523,7 @@ class ToolPaint(AppTool, Gerber): sel_model = self.ui.tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns too, but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -592,7 +613,7 @@ class ToolPaint(AppTool, Gerber): def form_to_storage(self): if self.ui.tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage + # there is no tool in tool table, so we can't save the GUI elements values to storage return self.blockSignals(True) @@ -620,7 +641,7 @@ class ToolPaint(AppTool, Gerber): def on_apply_param_to_all_clicked(self): if self.ui.tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage + # there is no tool in tool table, so we can't save the GUI elements values to storage self.app.log.debug("NonCopperClear.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.") return @@ -1900,7 +1921,7 @@ class ToolPaint(AppTool, Gerber): self.app.inform.emit(msg) self.app.proc_container.update_view_text(' %d%%' % 0) - # find the tooluid associated with the current tool_dia so we know what tool to use + # find the tooluid associated with the current tool_dia, so we know what tool to use for k, v in tools_storage.items(): if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)): current_uid = int(k) @@ -2088,7 +2109,7 @@ class ToolPaint(AppTool, Gerber): self.app.inform.emit(msg) self.app.proc_container.update_view_text(' %d%%' % 0) - # find the tooluid associated with the current tool_dia so we know what tool to use + # find the tooluid associated with the current tool_dia, so we know what tool to use for k, v in tools_storage.items(): if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)): current_uid = int(k) @@ -2421,7 +2442,7 @@ class ToolPaint(AppTool, Gerber): Creates a list of non-iterable linear geometry objects. Results are placed in self.flat_geometry - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type, list or list of lists of such. :param reset: Clears the contents of self.flat_geometry. """ if self.app.abort_flag: @@ -2500,7 +2521,7 @@ class ToolPaint(AppTool, Gerber): Creates a list of non-iterable linear geometry objects. Results are placed in self.flat_geometry - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type, list or list of lists of such. :param reset: Clears the contents of self.flat_geometry. """ if self.app.abort_flag: @@ -2611,7 +2632,7 @@ class ToolPaint(AppTool, Gerber): try: if isinstance(geo, MultiPolygon): env_obj = geo.convex_hull - elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \ + elif (isinstance(geo, MultiPolygon) and len(geo.geoms) == 1) or \ (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon): env_obj = unary_union(self.bound_obj.solid_geometry) else: @@ -2811,7 +2832,7 @@ class ToolPaint(AppTool, Gerber): def on_paint_tool_from_db_inserted(self, tool): """ - Called from the Tools DB object through a App method when adding a tool from Tools Database + Called from the Tools DB object through an App method when adding a tool from Tools Database :param tool: a dict with the tool data :return: None """ diff --git a/appPlugins/ToolPanelize.py b/appPlugins/ToolPanelize.py index cb365f81..fff9a7c6 100644 --- a/appPlugins/ToolPanelize.py +++ b/appPlugins/ToolPanelize.py @@ -5,9 +5,24 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + RadioSet, FCDoubleSpinner, FCSpinner, OptionalInputSection from camlib import grace +import logging +from copy import deepcopy +import numpy as np + +from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon +from shapely.ops import unary_union, linemerge, snap +from shapely.affinity import translate + +import gettext +import appTranslation as fcTranslate +import builtins + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -734,7 +749,7 @@ class Panelize(AppTool): if isinstance(geo, LineString): lines.append(geo) elif isinstance(geo, MultiLineString): - for line in geo: + for line in geo.geoms: lines.append(line) else: other_geo.append(geo) diff --git a/appPlugins/ToolPcbWizard.py b/appPlugins/ToolPcbWizard.py index 524856fc..878b3f52 100644 --- a/appPlugins/ToolPcbWizard.py +++ b/appPlugins/ToolPcbWizard.py @@ -5,13 +5,26 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, RadioSet, FCSpinner, FCTable +import logging from io import StringIO +import os +import re + +from datetime import datetime as dt + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext +log = logging.getLogger('base') + class PcbWizard(AppTool): @@ -290,7 +303,7 @@ class PcbWizard(AppTool): def on_file_loaded(self, signal, filename): self.build_ui() - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + time_str = "{:%A, %d %B %Y at %H:%M}".format(dt.now()) if signal == 'inf': self.inf_loaded = True diff --git a/appPlugins/ToolPunchGerber.py b/appPlugins/ToolPunchGerber.py index cef8987b..e4566241 100644 --- a/appPlugins/ToolPunchGerber.py +++ b/appPlugins/ToolPunchGerber.py @@ -5,12 +5,26 @@ # MIT Licence # # ########################################################## -from appTool import * -from appParsers.ParseGerber import Gerber -from camlib import Geometry +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + RadioSet, FCDoubleSpinner, FCTable from matplotlib.backend_bases import KeyEvent as mpl_key_event +import logging +from copy import deepcopy + +from shapely.geometry import Point, MultiPolygon +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins + +from appParsers.ParseGerber import Gerber +from camlib import Geometry + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -642,7 +656,7 @@ class ToolPunchGerber(AppTool, Gerber): punched_solid_geometry.append(geo) punched_solid_geometry = unary_union(punched_solid_geometry) - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully new_apertures = deepcopy(grb_obj.tools) new_apertures_items = new_apertures.items() @@ -756,7 +770,7 @@ class ToolPunchGerber(AppTool, Gerber): punched_solid_geometry.append(geo) punched_solid_geometry = unary_union(punched_solid_geometry) - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully new_apertures = deepcopy(self.grb_obj.tools) new_apertures_items = new_apertures.items() @@ -900,7 +914,7 @@ class ToolPunchGerber(AppTool, Gerber): self.app.inform.emit(msg) return 'fail' - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully new_apertures = deepcopy(grb_obj.tools) new_apertures_items = new_apertures.items() @@ -995,7 +1009,7 @@ class ToolPunchGerber(AppTool, Gerber): self.app.inform.emit(msg) return 'fail' - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully new_apertures = deepcopy(self.grb_obj.tools) new_apertures_items = new_apertures.items() @@ -1143,7 +1157,7 @@ class ToolPunchGerber(AppTool, Gerber): if isinstance(elem['follow'], Point): punching_geo.append(elem['follow'].buffer(dia / 2)) - # if dia is None then none of the above applied so we skip the following + # if dia is None then none of the above applied, so we skip the following if dia is None: continue @@ -1154,12 +1168,12 @@ class ToolPunchGerber(AppTool, Gerber): punched_solid_geometry = punched_solid_geometry.difference(punching_geo) - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully for elem in apid_value['geometry']: # make it work only for Gerber Flashes who are Points in 'follow' if 'solid' in elem and isinstance(elem['follow'], Point): clear_apid_size = dia - for geo in punching_geo: + for geo in punching_geo.geoms: # since there may be drills that do not drill into a pad we test only for geos in a pad if geo.within(elem['solid']): @@ -1283,7 +1297,7 @@ class ToolPunchGerber(AppTool, Gerber): pad_point = self.grb_obj.tools[apid]['geometry'][pad_idx]['follow'] punching_geo.append(pad_point.buffer(dia / 2)) - # if dia is None then none of the above applied so we skip the following + # if dia is None then none of the above applied, so we skip the following if dia is None: continue @@ -1294,12 +1308,12 @@ class ToolPunchGerber(AppTool, Gerber): punched_solid_geometry = punched_solid_geometry.difference(punching_geo) - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully for elem in apid_value['geometry']: # make it work only for Gerber Flashes who are Points in 'follow' if 'solid' in elem and isinstance(elem['follow'], Point): clear_apid_size = dia - for geo in punching_geo: + for geo in punching_geo.geoms: # since there may be drills that do not drill into a pad we test only for geos in a pad if geo.within(elem['solid']): @@ -1427,7 +1441,7 @@ class ToolPunchGerber(AppTool, Gerber): if isinstance(elem['follow'], Point): punching_geo.append(elem['follow'].buffer(dia / 2)) - # if dia is None then none of the above applied so we skip the following + # if dia is None then none of the above applied, so we skip the following if dia is None: continue @@ -1438,12 +1452,12 @@ class ToolPunchGerber(AppTool, Gerber): punched_solid_geometry = punched_solid_geometry.difference(punching_geo) - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully for elem in apid_value['geometry']: # make it work only for Gerber Flashes who are Points in 'follow' if 'solid' in elem and isinstance(elem['follow'], Point): clear_apid_size = dia - for geo in punching_geo: + for geo in punching_geo.geoms: # since there may be drills that do not drill into a pad we test only for geos in a pad if geo.within(elem['solid']): @@ -1563,7 +1577,7 @@ class ToolPunchGerber(AppTool, Gerber): pad_point = self.grb_obj.tools[apid]['geometry'][pad_idx]['follow'] punching_geo.append(pad_point.buffer(dia / 2)) - # if dia is None then none of the above applied so we skip the following + # if dia is None then none of the above applied, so we skip the following if dia is None: continue @@ -1574,12 +1588,12 @@ class ToolPunchGerber(AppTool, Gerber): punched_solid_geometry = punched_solid_geometry.difference(punching_geo) - # update the gerber apertures to include the clear geometry so it can be exported successfully + # update the gerber apertures to include the clear geometry, so it can be exported successfully for elem in apid_value['geometry']: # make it work only for Gerber Flashes who are Points in 'follow' if 'solid' in elem and isinstance(elem['follow'], Point): clear_apid_size = dia - for geo in punching_geo: + for geo in punching_geo.geoms: # since there may be drills that do not drill into a pad we test only for geos in a pad if geo.within(elem['solid']): diff --git a/appPlugins/ToolQRCode.py b/appPlugins/ToolQRCode.py index 607c5f6f..6f3b39a2 100644 --- a/appPlugins/ToolQRCode.py +++ b/appPlugins/ToolQRCode.py @@ -5,9 +5,27 @@ # MIT Licence # # ########################################################## -from appTool import * -from appParsers.ParseSVG import * +from PyQt6 import QtWidgets, QtCore, QtGui +from PyQt6.QtCore import Qt + +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCFileSaveDialog, RadioSet, FCTextArea, FCSpinner, FCEntry + +import logging +from copy import deepcopy from io import StringIO, BytesIO +from typing import Iterable +import math + +from shapely.geometry import MultiPolygon, box, Polygon +from shapely.ops import unary_union +from shapely.affinity import translate, scale +import gettext +import appTranslation as fcTranslate +import builtins + +from appParsers.ParseSVG import getsvggeo, getsvgtext, svgparselength, svgparse_viewbox import qrcode import qrcode.image.svg @@ -399,7 +417,7 @@ class QRCode(AppTool): offset_geo = [] # I use the len of self.qrcode_geometry instead of the utility one because the complexity of the polygons is - # better seen in this (bit what if the sel.qrcode_geometry is just one geo element? len will fail ... + # better seen in this (bit what if the sel.qrcode_geometry is just one geo element? len will fail ...) qrcode_geometry_len = len(self.qrcode_geometry.geoms) if isinstance(self.qrcode_geometry, MultiPolygon) else \ len(self.qrcode_geometry) @@ -480,11 +498,11 @@ class QRCode(AppTool): """ Convert shapes from an SVG file into a geometry list. - :param filename: A String Stream file. + :param filename: A String Stream file. :param object_type: parameter passed further along. What kind the object will receive the SVG geometry - :param flip: Flip the vertically. - :type flip: bool - :param units: FlatCAM units + :param flip: Flip this vertically. + :type flip: bool + :param units: FlatCAM units :return: None """ diff --git a/appPlugins/ToolReport.py b/appPlugins/ToolReport.py index debed045..53d16fb2 100644 --- a/appPlugins/ToolReport.py +++ b/appPlugins/ToolReport.py @@ -5,7 +5,19 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCTree +import logging +from copy import deepcopy +import math + +from shapely.geometry import MultiPolygon, Polygon, MultiLineString +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -303,11 +315,12 @@ class ObjectReport(AppTool): for geo_el in obj_prop.tools[tool_k]['solid_geometry']: geo_tools.append(geo_el) + geo_tools_mp = MultiPolygon(geo_tools) try: - for geo_el in geo_tools: + for geo_el in geo_tools_mp.geoms: copper_area += geo_el.area except TypeError: - copper_area += geo_tools.area + copper_area += geo_tools_mp.area copper_area /= 100 except Exception as err: self.app.log.error("Properties.addItems() --> %s" % str(err)) @@ -319,7 +332,7 @@ class ObjectReport(AppTool): if isinstance(geo, list) and geo[0] is not None: if isinstance(geo, MultiPolygon): env_obj = geo.convex_hull - elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \ + elif (isinstance(geo, MultiPolygon) and len(geo.geoms) == 1) or \ (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon): env_obj = unary_union(geo) env_obj = env_obj.convex_hull diff --git a/appPlugins/ToolRulesCheck.py b/appPlugins/ToolRulesCheck.py index 7ea02991..74200339 100644 --- a/appPlugins/ToolRulesCheck.py +++ b/appPlugins/ToolRulesCheck.py @@ -5,8 +5,21 @@ # MIT Licence # # ########################################################## -from appTool import * -from appPool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCDoubleSpinner, OptionalInputSection +from appObjects import GerberObject + +import logging +from copy import deepcopy + +from shapely.geometry import Polygon, MultiPolygon +from shapely.ops import nearest_points + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -37,7 +50,7 @@ class RulesCheck(AppTool): # final name for the panel object self.outname = "" - # flag to signal the constrain was activated + # flag to signal that the constraint was activated self.constrain_flag = False # Multiprocessing Process Pool @@ -249,7 +262,7 @@ class RulesCheck(AppTool): return rule_title, violations @staticmethod - def check_gerber_clearance(gerber_list, size, rule): + def check_gerber_clearance(gerber_list: list[GerberObject], size, rule): # log.debug("RulesCheck.check_gerber_clearance()") rule_title = rule @@ -262,7 +275,7 @@ class RulesCheck(AppTool): if len(gerber_list) == 2: gerber_1 = gerber_list[0] - # added it so I won't have errors of using before declaring + # added it, so I won't have errors of using before declaring gerber_2 = {} gerber_3 = gerber_list[1] @@ -488,7 +501,7 @@ class RulesCheck(AppTool): 'points': list() }) - # added it so I won't have errors of using before declaring + # added it so, I won't have errors of using before declaring gerber_obj = {} gerber_extra_obj = {} exc_obj = {} @@ -1572,7 +1585,7 @@ class RulesUI: self.silk_grid = GLay(v_spacing=5, h_spacing=3) silk_frame.setLayout(self.silk_grid) - # Silkscreen2silkscreen clearance + # "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" @@ -1622,7 +1635,7 @@ class RulesUI: self.s2sm = OptionalInputSection( self.clearance_silk2sm_cb, [self.clearance_silk2sm_lbl, self.clearance_silk2sm_entry]) - # Silk2outline clearance + # "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" diff --git a/appPlugins/ToolShell.py b/appPlugins/ToolShell.py index 2a9df2a8..710c3607 100644 --- a/appPlugins/ToolShell.py +++ b/appPlugins/ToolShell.py @@ -9,8 +9,10 @@ from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import QSettings from PyQt6.QtGui import QTextCursor, QPixmap -from PyQt6.QtWidgets import QVBoxLayout, QWidget, QHBoxLayout, QLabel +from PyQt6.QtWidgets import QVBoxLayout, QWidget, QHBoxLayout + from appGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit, FCLabel + import html import sys import traceback @@ -73,7 +75,7 @@ class TermWidget(QWidget): hlay = QHBoxLayout() hlay.addWidget(self._delete_line) - hlay.addWidget(QLabel(" ")) + hlay.addWidget(FCLabel(" ")) hlay.addWidget(self._edit) layout.addLayout(hlay) diff --git a/appPlugins/ToolSolderPaste.py b/appPlugins/ToolSolderPaste.py index b827d5a0..cacbca3b 100644 --- a/appPlugins/ToolSolderPaste.py +++ b/appPlugins/ToolSolderPaste.py @@ -5,7 +5,24 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCFileSaveDialog, \ + FCComboBox2, FCEntry, FCDoubleSpinner, FCSpinner, FCInputSpinner, FCTable + +import traceback +from copy import deepcopy +import re + +from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, Point +from shapely.ops import unary_union + +from datetime import datetime as dt + +import gettext +import appTranslation as fcTranslate +import builtins + from appCommon.Common import LoudDict from camlib import distance @@ -155,7 +172,7 @@ class SolderPaste(AppTool): sel_model = self.ui.tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns too, but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -326,6 +343,7 @@ class SolderPaste(AppTool): def update_ui(self, row=None): """ Will update the UI form with the data from obj.tools + :param row: the row (tool) from which to extract information's used to populate the form :return: """ @@ -380,7 +398,7 @@ class SolderPaste(AppTool): sel_model = self.ui.tools_table.selectionModel() sel_indexes = sel_model.selectedIndexes() - # it will iterate over all indexes which means all items in all columns too but I'm interested only on rows + # it will iterate over all indexes which means all items in all columns too, but I'm interested only on rows sel_rows = set() for idx in sel_indexes: sel_rows.add(idx.row()) @@ -451,7 +469,7 @@ class SolderPaste(AppTool): try: obj_name = obj.obj_options['name'] except AttributeError: - # this happen when the 'delete all' is emitted since in that case the obj is set to None and None has no + # this happens when the 'delete all' is emitted since in that case the obj is set to None and None has no # attribute named 'options' return @@ -480,6 +498,7 @@ class SolderPaste(AppTool): def form_to_storage(self, tooluid=None): """ Will read all the items in the UI form and set the self.tools data accordingly + :param tooluid: the uid of the tool to be updated in the obj.tools :return: """ @@ -505,6 +524,7 @@ class SolderPaste(AppTool): def set_form_from_defaults(self): """ Will read all the parameters of Solder Paste Tool from the app self.defaults and update the UI + :return: """ for key in self.form_fields: @@ -646,7 +666,7 @@ class SolderPaste(AppTool): """ Will delete tool(s) in the Tool Table - :param rows_to_delete: tell which row (tool) to delete + :param rows_to_delete: tell which row (tool) to be deleted :param all_tools: to delete all tools at once :return: """ @@ -784,7 +804,7 @@ class SolderPaste(AppTool): Results are placed in flat_geometry - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type, list or list of lists of such. :param reset: Clears the contents of self.flat_geometry. :param pathonly: Expands polygons into linear elements from the exterior attribute. """ @@ -1090,7 +1110,7 @@ class SolderPaste(AppTool): :return: """ - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + time_str = "{:%A, %d %B %Y at %H:%M}".format(dt.now()) name = self.ui.cnc_obj_combo.currentText() obj = self.app.collection.get_by_name(name) @@ -1157,7 +1177,7 @@ class SolderPaste(AppTool): :return: """ - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + time_str = "{:%A, %d %B %Y at %H:%M}".format(dt.now()) name = self.ui.cnc_obj_combo.currentText() obj = self.app.collection.get_by_name(name) diff --git a/appPlugins/ToolSub.py b/appPlugins/ToolSub.py index b71e795a..fdda6e53 100644 --- a/appPlugins/ToolSub.py +++ b/appPlugins/ToolSub.py @@ -5,7 +5,21 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox + +import logging +from copy import deepcopy +import time +import traceback + +from shapely.geometry import LineString, Polygon, MultiPolygon, MultiLineString +from shapely.ops import unary_union + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -545,7 +559,7 @@ class ToolSub(AppTool): self.sub_union = unary_union(self.sub_geo_obj.solid_geometry) - # start the QTimer to check for promises with 0.5 second period check + # start the QTimer to check for promises with 0.5 seconds period check self.periodic_check(500, reset=True) if self.target_geo_obj.multigeo: @@ -582,7 +596,7 @@ class ToolSub(AppTool): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiPolygon): - for poly in geo_elem: + for poly in geo_elem.geoms: for ring in self.poly2rings(poly): new_geo = ring.difference(self.sub_union) if new_geo and not new_geo.is_empty: @@ -593,7 +607,7 @@ class ToolSub(AppTool): if not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: + for line_elem in geo_elem.geoms: new_geo = line_elem.difference(self.sub_union) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -609,7 +623,7 @@ class ToolSub(AppTool): if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) elif isinstance(geo, MultiLineString): - for line_elem in geo: + for line_elem in geo.geoms: new_geo = line_elem.difference(self.sub_union) if new_geo and not new_geo.is_empty: new_geometry.append(new_geo) @@ -625,7 +639,7 @@ class ToolSub(AppTool): time.sleep(0.5) while True: - # removal from list is done in a multithreaded way therefore not always the removal can be done + # removal from list is done in a multithreaded way therefore not always the removal can be done, # so we keep trying until it's done if tool not in self.promises: break @@ -682,7 +696,7 @@ class ToolSub(AppTool): def periodic_check(self, check_period, reset=False): """ - This function starts an QTimer and it will periodically check if intersections are done + This function starts an QTimer, and it will periodically check if intersections are done :param check_period: time at which to check periodically :param reset: will reset the timer diff --git a/appPlugins/ToolTransform.py b/appPlugins/ToolTransform.py index e6bdbe9a..aa63eadc 100644 --- a/appPlugins/ToolTransform.py +++ b/appPlugins/ToolTransform.py @@ -5,12 +5,24 @@ # MIT Licence # # ########################################################## -from appTool import * +from PyQt6 import QtWidgets, QtCore, QtGui +from appTool import AppTool +from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ + FCDoubleSpinner, NumericalEvalTupleEntry, OptionalInputSection + +import logging +import numpy as np + +import gettext +import appTranslation as fcTranslate +import builtins fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext +log = logging.getLogger('base') + class ToolTransform(AppTool): @@ -552,7 +564,7 @@ class ToolTransform(AppTool): maxy = max(maxy, maxy_) return minx, miny, maxx, maxy except TypeError: - # it's an object, return it's bounds + # it's an object, return its bounds return lst.bounds() return bounds_rec(obj_list) diff --git a/appPool.py b/appPool.py index 6333159c..2b6b8069 100644 --- a/appPool.py +++ b/appPool.py @@ -1,3 +1,4 @@ + from PyQt6 import QtCore from multiprocessing import Pool, cpu_count import dill diff --git a/appPreProcessor.py b/appPreProcessor.py index 955ee910..96ea1921 100644 --- a/appPreProcessor.py +++ b/appPreProcessor.py @@ -9,7 +9,6 @@ from importlib.machinery import SourceFileLoader import os from abc import ABCMeta, abstractmethod -import math # keep this. it is used in preprocessors # module-root dictionary of preprocessors diff --git a/appTool.py b/appTool.py index 290b8a7b..aadd8b59 100644 --- a/appTool.py +++ b/appTool.py @@ -6,34 +6,12 @@ # MIT Licence # # ########################################################## ## -from shapely.geometry import Polygon, LineString, box, MultiPolygon, MultiPoint, MultiLineString, LinearRing, Point, \ - shape, base -from shapely.strtree import STRtree -from shapely.ops import unary_union, nearest_points, linemerge, snap -from shapely.affinity import translate, scale, skew, rotate - -from appGUI.GUIElements import * - -from copy import copy, deepcopy -import math - -import numpy as np -from numpy import Inf - -import simplejson as json -import os -import sys -import re -import time -import platform -from collections.abc import Iterable -import traceback -from datetime import datetime +from PyQt6 import QtGui, QtWidgets, QtCore +from shapely.geometry import Polygon, LineString import gettext import appTranslation as fcTranslate import builtins -import logging fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: diff --git a/appWorkerStack.py b/appWorkerStack.py index 44fc2023..6641b05c 100644 --- a/appWorkerStack.py +++ b/appWorkerStack.py @@ -1,3 +1,4 @@ + from PyQt6 import QtCore from appWorker import Worker diff --git a/camlib.py b/camlib.py index e946a4c4..892b824d 100644 --- a/camlib.py +++ b/camlib.py @@ -6,27 +6,37 @@ # MIT Licence # # ########################################################## ## - from PyQt6 import QtWidgets -from io import StringIO -from numpy.linalg import solve, norm +from appCommon.Common import GracefulException as grace + +# from scipy.spatial import KDTree, Delaunay +# from scipy.spatial import Delaunay + +from appParsers.ParseSVG import svgparselength, svgparse_viewbox, getsvggeo, getsvgtext +from appParsers.ParseDXF import getdxfgeo + +from numpy.linalg import solve import platform -from copy import deepcopy - import traceback from decimal import Decimal +from copy import deepcopy +from collections.abc import Iterable +from copy import copy from rtree import index as rtindex from lxml import etree as ET +from io import StringIO +import ezdxf # See: http://toblerity.org/shapely/manual.html -from shapely.geometry import Polygon, Point, LinearRing, MultiPoint +from shapely.geometry import Polygon, Point, LinearRing, MultiPoint, MultiLineString, MultiPolygon, LineString from shapely.geometry import box as shply_box from shapely.ops import unary_union, substring, linemerge import shapely.affinity as affinity +from shapely.affinity import scale, translate from shapely.wkt import loads as sloads from shapely.wkt import dumps as sdumps from shapely.geometry.base import BaseGeometry @@ -37,26 +47,14 @@ from shapely.geometry.base import BaseGeometry from descartes.patch import PolygonPatch # --------------------------------------- -from collections.abc import Iterable - -import ezdxf - -from appCommon.Common import GracefulException as grace - -# from scipy.spatial import KDTree, Delaunay -# from scipy.spatial import Delaunay - -from appParsers.ParseSVG import * -from appParsers.ParseDXF import * - import logging +import re +import numpy as np import gettext import appTranslation as fcTranslate import builtins -import copy - HAS_ORTOOLS = True if platform.architecture()[0] == '64bit': @@ -2299,7 +2297,7 @@ class Geometry(object): # d = {} # for attr in self.ser_attrs: # d[attr] = getattr(self, attr) - return {attr: copy.copy(getattr(self, attr)) for attr in self.ser_attrs} + return {attr: copy(getattr(self, attr)) for attr in self.ser_attrs} def from_dict(self, d): """ diff --git a/preprocessors/Berta_CNC.py b/preprocessors/Berta_CNC.py index 11e02177..d18f65f2 100644 --- a/preprocessors/Berta_CNC.py +++ b/preprocessors/Berta_CNC.py @@ -10,7 +10,8 @@ # MIT Licence # ############################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Berta_CNC(PreProc): diff --git a/preprocessors/Check_points.py b/preprocessors/Check_points.py index ea5d9dec..041eb7ea 100644 --- a/preprocessors/Check_points.py +++ b/preprocessors/Check_points.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Check_points(PreProc): diff --git a/preprocessors/Default_no_M6.py b/preprocessors/Default_no_M6.py index 61b8c2d5..a7ebd039 100644 --- a/preprocessors/Default_no_M6.py +++ b/preprocessors/Default_no_M6.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Default_no_M6(PreProc): diff --git a/preprocessors/GRBL_11.py b/preprocessors/GRBL_11.py index d7e1f496..fe453c56 100644 --- a/preprocessors/GRBL_11.py +++ b/preprocessors/GRBL_11.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class GRBL_11(PreProc): diff --git a/preprocessors/GRBL_11_no_M6.py b/preprocessors/GRBL_11_no_M6.py index b474c746..9dc192cd 100644 --- a/preprocessors/GRBL_11_no_M6.py +++ b/preprocessors/GRBL_11_no_M6.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class GRBL_11_no_M6(PreProc): diff --git a/preprocessors/GRBL_laser.py b/preprocessors/GRBL_laser.py index b559f510..0b9801da 100644 --- a/preprocessors/GRBL_laser.py +++ b/preprocessors/GRBL_laser.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math # This post processor is configured to output code that # is compatible with almost any version of Grbl. diff --git a/preprocessors/GRBL_laser_z.py b/preprocessors/GRBL_laser_z.py index 608bf43b..adb1b046 100644 --- a/preprocessors/GRBL_laser_z.py +++ b/preprocessors/GRBL_laser_z.py @@ -8,7 +8,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc # This post processor is configured to output code that # is compatible with almost any version of Grbl. diff --git a/preprocessors/ISEL_CNC.py b/preprocessors/ISEL_CNC.py index 5668a222..f54fd6c2 100644 --- a/preprocessors/ISEL_CNC.py +++ b/preprocessors/ISEL_CNC.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class ISEL_CNC(PreProc): diff --git a/preprocessors/ISEL_ICP_CNC.py b/preprocessors/ISEL_ICP_CNC.py index 604b9690..42e2f34e 100644 --- a/preprocessors/ISEL_ICP_CNC.py +++ b/preprocessors/ISEL_ICP_CNC.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class ISEL_ICP_CNC(PreProc): diff --git a/preprocessors/Line_xyz.py b/preprocessors/Line_xyz.py index 28345d2f..1c7d3c5f 100644 --- a/preprocessors/Line_xyz.py +++ b/preprocessors/Line_xyz.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Line_xyz(PreProc): diff --git a/preprocessors/Marlin.py b/preprocessors/Marlin.py index 974aa296..427d9903 100644 --- a/preprocessors/Marlin.py +++ b/preprocessors/Marlin.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Marlin(PreProc): diff --git a/preprocessors/Marlin_laser_FAN_pin.py b/preprocessors/Marlin_laser_FAN_pin.py index 2eb23120..85929da6 100644 --- a/preprocessors/Marlin_laser_FAN_pin.py +++ b/preprocessors/Marlin_laser_FAN_pin.py @@ -6,7 +6,7 @@ # License: MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc class Marlin_laser_FAN_pin(PreProc): diff --git a/preprocessors/Marlin_laser_Spindle_pin.py b/preprocessors/Marlin_laser_Spindle_pin.py index c028266f..180e8200 100644 --- a/preprocessors/Marlin_laser_Spindle_pin.py +++ b/preprocessors/Marlin_laser_Spindle_pin.py @@ -6,7 +6,7 @@ # License: MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc class Marlin_laser_Spindle_pin(PreProc): diff --git a/preprocessors/Marlin_laser_z.py b/preprocessors/Marlin_laser_z.py index 121f6d5f..1a4e9cb3 100644 --- a/preprocessors/Marlin_laser_z.py +++ b/preprocessors/Marlin_laser_z.py @@ -6,7 +6,7 @@ # License: MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc class Marlin_laser_z(PreProc): diff --git a/preprocessors/NCCAD9.py b/preprocessors/NCCAD9.py index 6164cbb7..276e6195 100644 --- a/preprocessors/NCCAD9.py +++ b/preprocessors/NCCAD9.py @@ -10,7 +10,8 @@ # http://www.max-computer.de/x5e/adaption-of-post-processor.html # https://github.com/FreeCAD/FreeCAD/pull/2876/files -from appPreProcessor import * +from appPreProcessor import PreProc +import math class NCCAD9(PreProc): diff --git a/preprocessors/Paste_1.py b/preprocessors/Paste_1.py index c30698f0..47be9563 100644 --- a/preprocessors/Paste_1.py +++ b/preprocessors/Paste_1.py @@ -6,7 +6,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import AppPreProcTools class Paste_1(AppPreProcTools): @@ -133,7 +133,7 @@ class Paste_1(AppPreProcTools): ' F%s' % str(p['frz_dispense']) def down_z_stop_code(self, p): - return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_stop'])) + ' F%s' % str(p['frz']) + return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_stop'])) + ' F%s' % str(p['frz']) def toolchange_code(self, p): fr_rapids = float(p['fr_rapids']) diff --git a/preprocessors/Paste_GRBL.py b/preprocessors/Paste_GRBL.py index 38e0b8a3..0396df70 100644 --- a/preprocessors/Paste_GRBL.py +++ b/preprocessors/Paste_GRBL.py @@ -6,7 +6,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import AppPreProcTools class Paste_GRBL(AppPreProcTools): @@ -134,7 +134,7 @@ class Paste_GRBL(AppPreProcTools): ' F%s' % str(p['frz_dispense']) def down_z_stop_code(self, p): - return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_stop'])) + ' F%s' % str(p['frz']) + return 'G01 Z' + self.coordinate_format % (p.coords_decimals, float(p['z_stop'])) + ' F%s' % str(p['frz']) def toolchange_code(self, p): fr_rapids = float(p['fr_rapids']) @@ -250,4 +250,4 @@ G01 Z{z_toolchange} F{fr_rapids} def dwell_rev_code(self, p): if p.dwelltime: - return 'G4 P' + str(float(p['dwellrev'])) \ No newline at end of file + return 'G4 P' + str(float(p['dwellrev'])) diff --git a/preprocessors/Paste_Marlin.py b/preprocessors/Paste_Marlin.py index ebc56def..3b9464e5 100644 --- a/preprocessors/Paste_Marlin.py +++ b/preprocessors/Paste_Marlin.py @@ -6,7 +6,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import AppPreProcTools class Paste_Marlin(AppPreProcTools): diff --git a/preprocessors/Repetier.py b/preprocessors/Repetier.py index 48fef768..bc7e55bc 100644 --- a/preprocessors/Repetier.py +++ b/preprocessors/Repetier.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Repetier(PreProc): diff --git a/preprocessors/Roland_MDX_20.py b/preprocessors/Roland_MDX_20.py index fbdec386..cc00f025 100644 --- a/preprocessors/Roland_MDX_20.py +++ b/preprocessors/Roland_MDX_20.py @@ -6,7 +6,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc # for Roland Preprocessors it is mandatory for the preprocessor name (python file and class name, both of them must be diff --git a/preprocessors/Roland_MDX_540.py b/preprocessors/Roland_MDX_540.py index beafa6fb..0652fabb 100644 --- a/preprocessors/Roland_MDX_540.py +++ b/preprocessors/Roland_MDX_540.py @@ -6,7 +6,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc # for Roland Preprocessors it is mandatory for the preprocessor name (python file and class name, both of them must be diff --git a/preprocessors/Toolchange_Manual.py b/preprocessors/Toolchange_Manual.py index 936e0b07..23962d3d 100644 --- a/preprocessors/Toolchange_Manual.py +++ b/preprocessors/Toolchange_Manual.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Toolchange_Manual(PreProc): diff --git a/preprocessors/Toolchange_Probe_MACH3.py b/preprocessors/Toolchange_Probe_MACH3.py index 039a2085..92184db9 100644 --- a/preprocessors/Toolchange_Probe_MACH3.py +++ b/preprocessors/Toolchange_Probe_MACH3.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class Toolchange_Probe_MACH3(PreProc): diff --git a/preprocessors/default.py b/preprocessors/default.py index 0c4e7f42..416d3703 100644 --- a/preprocessors/default.py +++ b/preprocessors/default.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class default(PreProc): diff --git a/preprocessors/default_laser.py b/preprocessors/default_laser.py index d0f48104..a89676e7 100644 --- a/preprocessors/default_laser.py +++ b/preprocessors/default_laser.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc +import math class default_laser(PreProc): diff --git a/preprocessors/grbl_laser_eleks_drd.py b/preprocessors/grbl_laser_eleks_drd.py index f065fccc..6e355571 100644 --- a/preprocessors/grbl_laser_eleks_drd.py +++ b/preprocessors/grbl_laser_eleks_drd.py @@ -6,7 +6,8 @@ # MIT Licence # # ########################################################## ## -from appPreProcessor import * +from appPreProcessor import PreProc +import math # This post processor is configured to output code for # lasers without Z Axis # and to convert excellon drillcodes into arcs diff --git a/preprocessors/hpgl.py b/preprocessors/hpgl.py index ad812f00..82d6781c 100644 --- a/preprocessors/hpgl.py +++ b/preprocessors/hpgl.py @@ -6,7 +6,7 @@ # MIT Licence # # ########################################################## -from appPreProcessor import * +from appPreProcessor import PreProc # for Roland Preprocessors it is mandatory for the preprocessor name (python file and class name, both of them must be diff --git a/requirements.txt b/requirements.txt index 4277ddd4..a27d4040 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,6 +34,7 @@ ezdxf reportlab>=3.5 pyserial>=3.4 pikepdf>=2.0 +pyppeteer matplotlib>=3.5.0 pyopengl diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/canvas/performance.py b/tests/canvas/performance.py deleted file mode 100644 index 40047291..00000000 --- a/tests/canvas/performance.py +++ /dev/null @@ -1,95 +0,0 @@ - -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import numpy as np -import io -from matplotlib.backends.backend_agg import FigureCanvasAgg -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg -from matplotlib.figure import Figure -import cProfile -import sys - - -def gen_data(): - N = 100000 - x = np.random.rand(N) * 10 - y = np.random.rand(N) * 10 - colors = np.random.rand(N) - area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radiuses - data = x, y, area, colors - return data - - -# @profile -def large_plot(data): - x, y, area, colors = data - - fig = Figure(figsize=(10, 10), dpi=80) - axes = fig.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0) - axes.set_frame_on(False) - axes.set_xticks([]) - axes.set_yticks([]) - # axes.set_xlim(0, 10) - # axes.set_ylim(0, 10) - - axes.scatter(x, y, s=area, c=colors, alpha=0.5) - - axes.set_xlim(0, 10) - axes.set_ylim(0, 10) - - canvas = FigureCanvasAgg(fig) - canvas.draw() - # canvas = FigureCanvasQTAgg(fig) - # buf = canvas.tostring_rgb() - buf = fig.canvas.tostring_rgb() - - ncols, nrows = fig.canvas.get_width_height() - img = np.fromstring(buf, dtype=np.uint8).reshape(nrows, ncols, 3) - - return img - - -def small_plot(data): - x, y, area, colors = data - - fig = Figure(figsize=(3, 3), dpi=80) - axes = fig.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0) - axes.set_frame_on(False) - axes.set_xticks([]) - axes.set_yticks([]) - # axes.set_xlim(5, 6) - # axes.set_ylim(5, 6) - - axes.scatter(x, y, s=area, c=colors, alpha=0.5) - - axes.set_xlim(4, 7) - axes.set_ylim(4, 7) - - canvas = FigureCanvasAgg(fig) - canvas.draw() - # canvas = FigureCanvasQTAgg(fig) - # buf = canvas.tostring_rgb() - buf = fig.canvas.tostring_rgb() - - ncols, nrows = fig.canvas.get_width_height() - img = np.fromstring(buf, dtype=np.uint8).reshape(nrows, ncols, 3) - - return img - -def doit(): - d = gen_data() - img = large_plot(d) - return img - - -if __name__ == "__main__": - - d = gen_data() - - if sys.argv[1] == 'large': - cProfile.runctx('large_plot(d)', None, locals()) - else: - cProfile.runctx('small_plot(d)', None, locals()) - - diff --git a/tests/canvas/prof.sh b/tests/canvas/prof.sh deleted file mode 100755 index b9075848..00000000 --- a/tests/canvas/prof.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -echo "*** LARGE ***" -python performance.py large | egrep "(\(scatter\))|(\(draw\))|(tostring_rgb)|(fromstring)" -echo "*** SMALL ***" -python performance.py small | egrep "(\(scatter\))|(\(draw\))|(tostring_rgb)|(fromstring)" \ No newline at end of file diff --git a/tests/excellon_files/case1.drl b/tests/excellon_files/case1.drl deleted file mode 100644 index 95b89ca3..00000000 --- a/tests/excellon_files/case1.drl +++ /dev/null @@ -1,125 +0,0 @@ -M48 -INCH -T01C0.0200 -T02C0.0800 -T03C0.0600 -T04C0.0300 -T05C0.0650 -T06C0.0450 -T07C0.0400 -T08C0.1181 -T09C0.0500 -% -T01 -X-018204Y+015551 -X-025842Y+015551 -T02 -X-000118Y+020629 -X-000118Y+016889 -X+012401Y+020629 -X+012401Y+016889 -X-010170Y+002440 -X-010110Y+011470 -X+018503Y+026574 -T03 -X+013060Y+010438 -X+013110Y+000000 -X-049015Y+002165 -X+018378Y+010433 -X+018317Y+000000 -X-049015Y+010039 -X-041141Y-000629 -X-041181Y+012992 -X-056496Y+012992 -X-056496Y-000590 -T04 -X-037560Y+030490 -X-036560Y+030490 -X-035560Y+030490 -X-034560Y+030490 -X-033560Y+030490 -X-032560Y+030490 -X-031560Y+030490 -X-030560Y+030490 -X-029560Y+030490 -X-028560Y+030490 -X-027560Y+030490 -X-026560Y+030490 -X-025560Y+030490 -X-024560Y+030490 -X-024560Y+036490 -X-025560Y+036490 -X-026560Y+036490 -X-027560Y+036490 -X-028560Y+036490 -X-029560Y+036490 -X-030560Y+036490 -X-031560Y+036490 -X-032560Y+036490 -X-033560Y+036490 -X-034560Y+036490 -X-035560Y+036490 -X-036560Y+036490 -X-037560Y+036490 -X-014590Y+030810 -X-013590Y+030810 -X-012590Y+030810 -X-011590Y+030810 -X-011590Y+033810 -X-012590Y+033810 -X-013590Y+033810 -X-014590Y+033810 -X-021260Y+034680 -X-020010Y+034680 -X-008390Y+035840 -X-008390Y+034590 -X-008440Y+031870 -X-008440Y+030620 -T05 -X-022504Y+019291 -X-020354Y+019291 -X-018204Y+019291 -X-030142Y+019291 -X-027992Y+019291 -X-025842Y+019291 -X-012779Y+019291 -X-010629Y+019291 -X-008479Y+019291 -T06 -X-028080Y+028230 -X-030080Y+028230 -X-034616Y+024409 -X-039616Y+024409 -X-045364Y+023346 -X-045364Y+018346 -X-045364Y+030157 -X-045364Y+025157 -X-008604Y+026983 -X-013604Y+026983 -X-016844Y+034107 -X-016844Y+029107 -T07 -X-041655Y+026456 -X-040655Y+026456 -X-039655Y+026456 -X-041640Y+022047 -X-040640Y+022047 -X-039640Y+022047 -X-049760Y+029430 -X-048760Y+029430 -X-047760Y+029430 -X-019220Y+037380 -X-020220Y+037380 -X-021220Y+037380 -T08 -X-024212Y+007751 -X-024212Y+004011 -X-035629Y+007874 -X-035629Y+004133 -T09 -X+007086Y+030708 -X+007086Y+032874 -X-000787Y+031889 -X-000787Y+035826 -X-000787Y+027952 -M30 diff --git a/tests/frameless_window.py b/tests/frameless_window.py deleted file mode 100644 index 47e28b31..00000000 --- a/tests/frameless_window.py +++ /dev/null @@ -1,148 +0,0 @@ -import sys -from PyQt5 import QtGui, QtWidgets -from PyQt5 import QtCore -from PyQt5.QtCore import Qt - - -class TitleBar(QtWidgets.QDialog): - def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - self.setWindowFlags(Qt.FramelessWindowHint) - css = """ - QWidget{ - Background: #AA00AA; - color:white; - font:12px bold; - font-weight:bold; - border-radius: 1px; - height: 11px; - } - QDialog{ - Background-image:url('img/titlebar bg.png'); - font-size:12px; - color: black; - - } - QToolButton{ - Background:#AA00AA; - font-size:11px; - } - QToolButton:hover{ - Background: #FF00FF; - font-size:11px; - } - """ - self.setAutoFillBackground(True) - self.setBackgroundRole(QtGui.QPalette.Highlight) - self.setStyleSheet(css) - self.minimize=QtWidgets.QToolButton(self) - self.minimize.setIcon(QtGui.QIcon('img/min.png')) - self.maximize=QtWidgets.QToolButton(self); - self.maximize.setIcon(QtGui.QIcon('img/max.png')) - close=QtWidgets.QToolButton(self) - close.setIcon(QtGui.QIcon('img/close.png')) - self.minimize.setMinimumHeight(10) - close.setMinimumHeight(10) - self.maximize.setMinimumHeight(10) - label=QtWidgets.QLabel(self) - label.setText("Abracadabra") - self.setWindowTitle("Alhambra") - hbox=QtWidgets.QHBoxLayout(self) - hbox.addWidget(label) - hbox.addWidget(self.minimize) - hbox.addWidget(self.maximize) - hbox.addWidget(close) - hbox.insertStretch(1,500) - hbox.setSpacing(0) - self.setSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Fixed) - self.maxNormal=False - close.clicked.connect(self.close) - self.minimize.clicked.connect(self.showSmall) - self.maximize.clicked.connect(self.showMaxRestore) - - def showSmall(self): - box.showMinimized() - - def showMaxRestore(self): - if(self.maxNormal): - box.showNormal() - self.maxNormal= False - self.maximize.setIcon(QtGui.QIcon('img/max.png')) - print(1) - else: - box.showMaximized() - self.maxNormal= True - print(2) - self.maximize.setIcon(QtGui.QIcon('img/max2.png')) - - def close(self): - box.close() - - def mousePressEvent(self,event): - if event.button() == Qt.LeftButton: - box.moving = True; box.offset = event.pos() - - def mouseMoveEvent(self,event): - if box.moving: box.move(event.globalPos()-box.offset) - - -class Frame(QtWidgets.QFrame): - def __init__(self, parent=None): - QtWidgets.QFrame.__init__(self, parent) - self.m_mouse_down= False - self.setFrameShape(QtWidgets.QFrame.StyledPanel) - css = """ - QFrame{ - Background: #D700D7; - color:white; - font:13px ; - font-weight:bold; - } - """ - self.setStyleSheet(css) - self.setWindowFlags(Qt.FramelessWindowHint) - self.setMouseTracking(True) - self.m_titleBar= TitleBar(self) - self.m_content= QtWidgets.QWidget(self) - vbox=QtWidgets.QVBoxLayout(self) - vbox.addWidget(self.m_titleBar) - vbox.setContentsMargins(0, 0, 0, 0) - vbox.setSpacing(0) - layout=QtWidgets.QVBoxLayout(self) - layout.addWidget(self.m_content) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(0) - vbox.addLayout(layout) - # Allows you to access the content area of the frame - # where widgets and layouts can be added - - def contentWidget(self): - return self.m_content - - def titleBar(self): - return self.m_titleBar - - def mousePressEvent(self,event): - self.m_old_pos = event.pos() - self.m_mouse_down = event.button()== Qt.LeftButton - - def mouseMoveEvent(self,event): - x=event.x() - y=event.y() - - def mouseReleaseEvent(self,event): - m_mouse_down=False - - -if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) - box = Frame() - box.move(60,60) - l = QtWidgets.QVBoxLayout(box.contentWidget()) - l.setContentsMargins(0, 0,0 ,0) - edit = QtWidgets.QLabel("""I would've did anything for you to show you how much I adored you -But it's over now, it's too late to save our loveJust promise me you'll think of me -Every time you look up in the sky and see a star 'cuz I'm your star.""") - l.addWidget(edit) - box.show() - app.exec_() \ No newline at end of file diff --git a/tests/gerber_files/STM32F4-spindle.cmp b/tests/gerber_files/STM32F4-spindle.cmp deleted file mode 100644 index bca2de6e..00000000 --- a/tests/gerber_files/STM32F4-spindle.cmp +++ /dev/null @@ -1,6358 +0,0 @@ -G75* -%MOIN*% -%OFA0B0*% -%FSLAX25Y25*% -%IPPOS*% -%LPD*% -%AMOC8* -5,1,8,0,0,1.08239X$1,22.5* -% -%ADD10R,0.30000X0.30000*% -%ADD11R,0.44685X0.17500*% -%ADD12R,0.04724X0.01181*% -%ADD13R,0.01181X0.04724*% -%ADD14R,0.05512X0.04724*% -%ADD15R,0.05118X0.06299*% -%ADD16R,0.06299X0.05118*% -%ADD17R,0.09843X0.01969*% -%ADD18R,0.09843X0.07874*% -%ADD19R,0.05118X0.05906*% -%ADD20R,0.03150X0.05512*% -%ADD21R,0.03150X0.07480*% -%ADD22C,0.06000*% -%ADD23R,0.05906X0.07874*% -%ADD24OC8,0.06400*% -%ADD25R,0.05512X0.05512*% -%ADD26R,0.12598X0.04331*% -%ADD27C,0.07400*% -%ADD28R,0.06299X0.07098*% -%ADD29R,0.03937X0.04331*% -%ADD30R,0.05906X0.05118*% -%ADD31C,0.01200*% -%ADD32C,0.02400*% -%ADD33C,0.01600*% -%ADD34C,0.03181*% -%ADD35C,0.04000*% -D10* -X0102500Y0095000D03* -D11* -X0212657Y0076250D03* -D12* -X0124941Y0080236D03* -X0124941Y0082205D03* -X0124941Y0084173D03* -X0124941Y0086142D03* -X0124941Y0088110D03* -X0124941Y0090079D03* -X0124941Y0092047D03* -X0124941Y0094016D03* -X0124941Y0095984D03* -X0124941Y0097953D03* -X0124941Y0099921D03* -X0124941Y0101890D03* -X0124941Y0103858D03* -X0124941Y0105827D03* -X0124941Y0107795D03* -X0124941Y0109764D03* -X0080059Y0109764D03* -X0080059Y0107795D03* -X0080059Y0105827D03* -X0080059Y0103858D03* -X0080059Y0101890D03* -X0080059Y0099921D03* -X0080059Y0097953D03* -X0080059Y0095984D03* -X0080059Y0094016D03* -X0080059Y0092047D03* -X0080059Y0090079D03* -X0080059Y0088110D03* -X0080059Y0086142D03* -X0080059Y0084173D03* -X0080059Y0082205D03* -X0080059Y0080236D03* -D13* -X0087736Y0072559D03* -X0089705Y0072559D03* -X0091673Y0072559D03* -X0093642Y0072559D03* -X0095610Y0072559D03* -X0097579Y0072559D03* -X0099547Y0072559D03* -X0101516Y0072559D03* -X0103484Y0072559D03* -X0105453Y0072559D03* -X0107421Y0072559D03* -X0109390Y0072559D03* -X0111358Y0072559D03* -X0113327Y0072559D03* -X0115295Y0072559D03* -X0117264Y0072559D03* -X0117264Y0117441D03* -X0115295Y0117441D03* -X0113327Y0117441D03* -X0111358Y0117441D03* -X0109390Y0117441D03* -X0107421Y0117441D03* -X0105453Y0117441D03* -X0103484Y0117441D03* -X0101516Y0117441D03* -X0099547Y0117441D03* -X0097579Y0117441D03* -X0095610Y0117441D03* -X0093642Y0117441D03* -X0091673Y0117441D03* -X0089705Y0117441D03* -X0087736Y0117441D03* -D14* -X0059331Y0113346D03* -X0059331Y0106654D03* -X0050669Y0106654D03* -X0050669Y0113346D03* -D15* -X0051063Y0125000D03* -X0058937Y0125000D03* -X0056063Y0072500D03* -X0063937Y0072500D03* -X0078563Y0060000D03* -X0086437Y0060000D03* -X0121063Y0060000D03* -X0128937Y0060000D03* -X0136063Y0067500D03* -X0143937Y0067500D03* -X0141437Y0115000D03* -X0133563Y0115000D03* -X0133563Y0130000D03* -X0141437Y0130000D03* -D16* -X0082500Y0136063D03* -X0082500Y0143937D03* -X0072500Y0123937D03* -X0072500Y0116063D03* -X0040000Y0113937D03* -X0040000Y0106063D03* -X0027500Y0141063D03* -X0027500Y0148937D03* -X0275000Y0208563D03* -X0275000Y0216437D03* -D17* -X0255157Y0111299D03* -X0255157Y0108150D03* -X0255157Y0105000D03* -X0255157Y0101850D03* -X0255157Y0098701D03* -D18* -X0255157Y0087283D03* -X0276811Y0087283D03* -X0276811Y0122717D03* -X0255157Y0122717D03* -D19* -X0223740Y0107500D03* -X0216260Y0107500D03* -X0216260Y0097500D03* -X0223740Y0097500D03* -X0267510Y0072500D03* -X0274990Y0072500D03* -X0161240Y0122500D03* -X0153760Y0122500D03* -X0082490Y0176250D03* -X0075010Y0176250D03* -X0103760Y0217500D03* -X0111240Y0217500D03* -X0128760Y0217500D03* -X0136240Y0217500D03* -X0153760Y0217500D03* -X0161240Y0217500D03* -X0126240Y0047500D03* -X0118760Y0047500D03* -D20* -X0216598Y0062201D03* -X0228402Y0062201D03* -D21* -X0222500Y0063201D03* -D22* -X0223500Y0069516D02* -X0221500Y0069516D01* -X0221500Y0077886D01* -X0223500Y0077886D01* -X0223500Y0069516D01* -X0223500Y0075515D02* -X0221500Y0075515D01* -D23* -X0244094Y0072500D03* -X0255906Y0072500D03* -X0255906Y0060000D03* -X0244094Y0060000D03* -D24* -X0225000Y0147500D03* -X0215000Y0147500D03* -X0215000Y0157500D03* -X0225000Y0157500D03* -X0225000Y0167500D03* -X0215000Y0167500D03* -X0215000Y0177500D03* -X0225000Y0177500D03* -X0195000Y0207500D03* -X0185000Y0207500D03* -X0185000Y0217500D03* -X0195000Y0217500D03* -X0062500Y0217500D03* -X0052500Y0217500D03* -X0052500Y0207500D03* -X0062500Y0207500D03* -X0042500Y0207500D03* -X0032500Y0207500D03* -X0022500Y0207500D03* -X0022500Y0217500D03* -X0032500Y0217500D03* -X0042500Y0217500D03* -X0082500Y0025000D03* -X0092500Y0025000D03* -X0092500Y0015000D03* -X0082500Y0015000D03* -X0102500Y0015000D03* -X0112500Y0015000D03* -X0112500Y0025000D03* -X0102500Y0025000D03* -X0122500Y0025000D03* -X0122500Y0015000D03* -D25* -X0240000Y0081270D03* -X0240000Y0096230D03* -X0246250Y0140020D03* -X0257500Y0140020D03* -X0257500Y0154980D03* -X0246250Y0154980D03* -D26* -X0234114Y0202126D03* -X0234114Y0217874D03* -X0260886Y0217874D03* -X0260886Y0202126D03* -D27* -X0273800Y0162500D02* -X0281200Y0162500D01* -X0281200Y0152500D02* -X0273800Y0152500D01* -X0273800Y0047500D02* -X0281200Y0047500D01* -X0271200Y0047500D02* -X0263800Y0047500D01* -X0263800Y0037500D02* -X0271200Y0037500D01* -X0273800Y0037500D02* -X0281200Y0037500D01* -X0281200Y0027500D02* -X0273800Y0027500D01* -X0271200Y0027500D02* -X0263800Y0027500D01* -X0263800Y0017500D02* -X0271200Y0017500D01* -X0273800Y0017500D02* -X0281200Y0017500D01* -X0096250Y0177550D02* -X0096250Y0184950D01* -D28* -X0101902Y0205000D03* -X0113098Y0205000D03* -X0126902Y0205000D03* -X0138098Y0205000D03* -X0151902Y0205000D03* -X0163098Y0205000D03* -X0265652Y0060000D03* -X0276848Y0060000D03* -D29* -X0063750Y0058346D03* -X0063750Y0051654D03* -D30* -X0027500Y0071260D03* -X0027500Y0078740D03* -D31* -X0002568Y0002568D02* -X0002568Y0227392D01* -X0014348Y0227392D01* -X0010705Y0223749D01* -X0009814Y0222858D01* -X0009331Y0221693D01* -X0009331Y0091870D01* -X0009814Y0090705D01* -X0014814Y0085705D01* -X0015705Y0084814D01* -X0016870Y0084331D01* -X0024331Y0084331D01* -X0024331Y0083868D01* -X0024036Y0083868D01* -X0023092Y0083477D01* -X0022370Y0082754D01* -X0021979Y0081810D01* -X0021979Y0075670D01* -X0022256Y0075000D01* -X0021979Y0074330D01* -X0021979Y0068190D01* -X0022370Y0067246D01* -X0023092Y0066523D01* -X0024036Y0066132D01* -X0024331Y0066132D01* -X0024331Y0061653D01* -X0024814Y0060489D01* -X0028751Y0056552D01* -X0029642Y0055660D01* -X0030807Y0055178D01* -X0059417Y0055178D01* -X0059491Y0055000D01* -X0059213Y0054330D01* -X0059213Y0048977D01* -X0059604Y0048033D01* -X0060327Y0047311D01* -X0061271Y0046920D01* -X0066229Y0046920D01* -X0067173Y0047311D01* -X0067896Y0048033D01* -X0068083Y0048485D01* -X0083130Y0048485D01* -X0084295Y0048967D01* -X0088232Y0052904D01* -X0089123Y0053796D01* -X0089324Y0054282D01* -X0089507Y0054282D01* -X0090451Y0054673D01* -X0091174Y0055395D01* -X0091565Y0056339D01* -X0091565Y0059347D01* -X0092442Y0060225D01* -X0092442Y0052749D01* -X0090045Y0050353D01* -X0089642Y0050186D01* -X0088751Y0049295D01* -X0084814Y0045358D01* -X0084331Y0044193D01* -X0084331Y0030100D01* -X0082900Y0030100D01* -X0082900Y0025400D01* -X0082100Y0025400D01* -X0082100Y0030100D01* -X0080388Y0030100D01* -X0077400Y0027112D01* -X0077400Y0025400D01* -X0082100Y0025400D01* -X0082100Y0024600D01* -X0077400Y0024600D01* -X0077400Y0022888D01* -X0079815Y0020473D01* -X0076731Y0017389D01* -X0076731Y0012611D01* -X0080111Y0009231D01* -X0084889Y0009231D01* -X0087500Y0011842D01* -X0090111Y0009231D01* -X0094889Y0009231D01* -X0097500Y0011842D01* -X0100111Y0009231D01* -X0104889Y0009231D01* -X0107500Y0011842D01* -X0110111Y0009231D01* -X0114889Y0009231D01* -X0117500Y0011842D01* -X0120111Y0009231D01* -X0124889Y0009231D01* -X0128268Y0012611D01* -X0128268Y0016288D01* -X0131795Y0019814D01* -X0132686Y0020705D01* -X0133168Y0021870D01* -X0133168Y0034193D01* -X0132686Y0035358D01* -X0128749Y0039295D01* -X0127858Y0040186D01* -X0126693Y0040668D01* -X0112671Y0040668D01* -X0110590Y0042749D01* -X0110590Y0045756D01* -X0111532Y0044814D01* -X0112697Y0044331D01* -X0113632Y0044331D01* -X0113632Y0044036D01* -X0114023Y0043092D01* -X0114746Y0042370D01* -X0115690Y0041979D01* -X0121830Y0041979D01* -X0122774Y0042370D01* -X0123132Y0042727D01* -X0123431Y0042647D01* -X0125640Y0042647D01* -X0125640Y0046900D01* -X0126840Y0046900D01* -X0126840Y0042647D01* -X0129049Y0042647D01* -X0129533Y0042777D01* -X0129966Y0043027D01* -X0130320Y0043381D01* -X0130570Y0043814D01* -X0130699Y0044297D01* -X0130699Y0046900D01* -X0126840Y0046900D01* -X0126840Y0048100D01* -X0125640Y0048100D01* -X0125640Y0052353D01* -X0123431Y0052353D01* -X0123132Y0052273D01* -X0122774Y0052630D01* -X0121830Y0053021D01* -X0115690Y0053021D01* -X0114746Y0052630D01* -X0114023Y0051908D01* -X0113841Y0051467D01* -X0112558Y0052749D01* -X0112558Y0058328D01* -X0112609Y0058205D01* -X0113500Y0057314D01* -X0114665Y0056831D01* -X0115935Y0056831D01* -X0115935Y0056339D01* -X0116326Y0055395D01* -X0117049Y0054673D01* -X0117993Y0054282D01* -X0124133Y0054282D01* -X0125077Y0054673D01* -X0125543Y0055139D01* -X0125645Y0055080D01* -X0126128Y0054950D01* -X0128337Y0054950D01* -X0128337Y0059400D01* -X0129537Y0059400D01* -X0129537Y0060600D01* -X0133396Y0060600D01* -X0133396Y0061782D01* -X0139133Y0061782D01* -X0140077Y0062173D01* -X0140543Y0062639D01* -X0140645Y0062580D01* -X0141128Y0062450D01* -X0143337Y0062450D01* -X0143337Y0066900D01* -X0144537Y0066900D01* -X0144537Y0068100D01* -X0148396Y0068100D01* -X0148396Y0070900D01* -X0148307Y0071231D01* -X0163731Y0071231D01* -X0163731Y0049250D01* -X0164305Y0047865D01* -X0165365Y0046805D01* -X0166750Y0046231D01* -X0257540Y0046231D01* -X0258486Y0043949D01* -X0259935Y0042500D01* -X0258486Y0041051D01* -X0257531Y0038747D01* -X0257531Y0036253D01* -X0258486Y0033949D01* -X0260249Y0032186D01* -X0260550Y0032061D01* -X0260152Y0031771D01* -X0259529Y0031148D01* -X0259010Y0030435D01* -X0258610Y0029650D01* -X0258338Y0028811D01* -X0258209Y0028000D01* -X0267000Y0028000D01* -X0267000Y0027000D01* -X0268000Y0027000D01* -X0268000Y0028000D01* -X0277000Y0028000D01* -X0277000Y0027000D01* -X0276791Y0027000D01* -X0268000Y0027000D01* -X0268000Y0021900D01* -X0268000Y0018000D01* -X0277000Y0018000D01* -X0277000Y0027000D01* -X0278000Y0027000D01* -X0278000Y0021900D01* -X0278000Y0018000D01* -X0277000Y0018000D01* -X0277000Y0017000D01* -X0276791Y0017000D01* -X0268000Y0017000D01* -X0268000Y0018000D01* -X0267000Y0018000D01* -X0267000Y0027000D01* -X0258209Y0027000D01* -X0258338Y0026189D01* -X0258610Y0025350D01* -X0259010Y0024565D01* -X0259529Y0023852D01* -X0260152Y0023229D01* -X0260865Y0022710D01* -X0261278Y0022500D01* -X0260865Y0022290D01* -X0260152Y0021771D01* -X0259529Y0021148D01* -X0259010Y0020435D01* -X0258610Y0019650D01* -X0258338Y0018811D01* -X0258209Y0018000D01* -X0267000Y0018000D01* -X0267000Y0017000D01* -X0268000Y0017000D01* -X0268000Y0011900D01* -X0271641Y0011900D01* -X0272500Y0012036D01* -X0273359Y0011900D01* -X0277000Y0011900D01* -X0277000Y0017000D01* -X0278000Y0017000D01* -X0278000Y0011900D01* -X0281641Y0011900D01* -X0282511Y0012038D01* -X0283350Y0012310D01* -X0284135Y0012710D01* -X0284848Y0013229D01* -X0285471Y0013852D01* -X0285990Y0014565D01* -X0286132Y0014845D01* -X0286132Y0002568D01* -X0002568Y0002568D01* -X0002568Y0003596D02* -X0286132Y0003596D01* -X0286132Y0004794D02* -X0002568Y0004794D01* -X0002568Y0005993D02* -X0286132Y0005993D01* -X0286132Y0007191D02* -X0002568Y0007191D01* -X0002568Y0008390D02* -X0286132Y0008390D01* -X0286132Y0009588D02* -X0125246Y0009588D01* -X0126444Y0010787D02* -X0286132Y0010787D01* -X0286132Y0011985D02* -X0282178Y0011985D01* -X0284786Y0013184D02* -X0286132Y0013184D01* -X0286132Y0014382D02* -X0285857Y0014382D01* -X0278000Y0014382D02* -X0277000Y0014382D01* -X0277000Y0013184D02* -X0278000Y0013184D01* -X0278000Y0011985D02* -X0277000Y0011985D01* -X0272822Y0011985D02* -X0272178Y0011985D01* -X0268000Y0011985D02* -X0267000Y0011985D01* -X0267000Y0011900D02* -X0267000Y0017000D01* -X0258209Y0017000D01* -X0258338Y0016189D01* -X0258610Y0015350D01* -X0259010Y0014565D01* -X0259529Y0013852D01* -X0260152Y0013229D01* -X0260865Y0012710D01* -X0261650Y0012310D01* -X0262489Y0012038D01* -X0263359Y0011900D01* -X0267000Y0011900D01* -X0267000Y0013184D02* -X0268000Y0013184D01* -X0268000Y0014382D02* -X0267000Y0014382D01* -X0267000Y0015581D02* -X0268000Y0015581D01* -X0268000Y0016779D02* -X0267000Y0016779D01* -X0267000Y0017978D02* -X0129959Y0017978D01* -X0131157Y0019176D02* -X0258456Y0019176D01* -X0258980Y0020375D02* -X0132356Y0020375D01* -X0133046Y0021573D02* -X0259954Y0021573D01* -X0260781Y0022772D02* -X0133168Y0022772D01* -X0133168Y0023970D02* -X0259443Y0023970D01* -X0258703Y0025169D02* -X0133168Y0025169D01* -X0133168Y0026367D02* -X0258310Y0026367D01* -X0258330Y0028764D02* -X0133168Y0028764D01* -X0133168Y0027566D02* -X0267000Y0027566D01* -X0268000Y0027566D02* -X0277000Y0027566D01* -X0277000Y0026367D02* -X0278000Y0026367D01* -X0278000Y0025169D02* -X0277000Y0025169D01* -X0277000Y0023970D02* -X0278000Y0023970D01* -X0278000Y0022772D02* -X0277000Y0022772D01* -X0277000Y0021573D02* -X0278000Y0021573D01* -X0278000Y0020375D02* -X0277000Y0020375D01* -X0277000Y0019176D02* -X0278000Y0019176D01* -X0277000Y0017978D02* -X0268000Y0017978D01* -X0268000Y0019176D02* -X0267000Y0019176D01* -X0267000Y0020375D02* -X0268000Y0020375D01* -X0268000Y0021573D02* -X0267000Y0021573D01* -X0267000Y0022772D02* -X0268000Y0022772D01* -X0268000Y0023970D02* -X0267000Y0023970D01* -X0267000Y0025169D02* -X0268000Y0025169D01* -X0268000Y0026367D02* -X0267000Y0026367D01* -X0259542Y0031161D02* -X0258224Y0031161D01* -X0258093Y0030847D02* -X0258356Y0031482D01* -X0258491Y0032156D01* -X0258491Y0032500D01* -X0258491Y0032844D01* -X0258356Y0033518D01* -X0258093Y0034153D01* -X0257711Y0034725D01* -X0257225Y0035211D01* -X0256653Y0035593D01* -X0256018Y0035856D01* -X0255344Y0035991D01* -X0255000Y0035991D01* -X0254656Y0035991D01* -X0253982Y0035856D01* -X0253347Y0035593D01* -X0252775Y0035211D01* -X0252289Y0034725D01* -X0251907Y0034153D01* -X0251644Y0033518D01* -X0251509Y0032844D01* -X0251509Y0032500D01* -X0251509Y0032156D01* -X0251644Y0031482D01* -X0251907Y0030847D01* -X0252289Y0030275D01* -X0252775Y0029789D01* -X0253347Y0029407D01* -X0253982Y0029144D01* -X0254656Y0029009D01* -X0255000Y0029009D01* -X0255344Y0029009D01* -X0256018Y0029144D01* -X0256653Y0029407D01* -X0257225Y0029789D01* -X0257711Y0030275D01* -X0258093Y0030847D01* -X0258770Y0029963D02* -X0257399Y0029963D01* -X0255000Y0029963D02* -X0255000Y0029963D01* -X0255000Y0029009D02* -X0255000Y0032500D01* -X0258491Y0032500D01* -X0255000Y0032500D01* -X0255000Y0032500D01* -X0255000Y0032500D01* -X0255000Y0035991D01* -X0255000Y0032500D01* -X0255000Y0032500D01* -X0255000Y0032500D01* -X0251509Y0032500D01* -X0255000Y0032500D01* -X0255000Y0029009D01* -X0255000Y0031161D02* -X0255000Y0031161D01* -X0255000Y0032360D02* -X0255000Y0032360D01* -X0255000Y0033558D02* -X0255000Y0033558D01* -X0255000Y0034757D02* -X0255000Y0034757D01* -X0255000Y0035955D02* -X0255000Y0035955D01* -X0255521Y0035955D02* -X0257655Y0035955D01* -X0257680Y0034757D02* -X0258151Y0034757D01* -X0258340Y0033558D02* -X0258877Y0033558D01* -X0258491Y0032360D02* -X0260075Y0032360D01* -X0257531Y0037154D02* -X0130890Y0037154D01* -X0129692Y0038352D02* -X0257531Y0038352D01* -X0257865Y0039551D02* -X0128493Y0039551D01* -X0126063Y0037500D02* -X0111358Y0037500D01* -X0107421Y0041437D01* -X0107421Y0072559D01* -X0105453Y0072559D02* -X0105453Y0038937D01* -X0109390Y0035000D01* -X0113563Y0035000D01* -X0117500Y0031063D01* -X0117500Y0020000D01* -X0112500Y0015000D01* -X0116444Y0010787D02* -X0118555Y0010787D01* -X0119754Y0009588D02* -X0115246Y0009588D01* -X0109754Y0009588D02* -X0105246Y0009588D01* -X0106444Y0010787D02* -X0108555Y0010787D01* -X0102500Y0015000D02* -X0107500Y0020000D01* -X0107500Y0028563D01* -X0103563Y0032500D01* -X0103484Y0032500D01* -X0099547Y0036437D01* -X0099547Y0072559D01* -X0097579Y0072559D02* -X0097579Y0020079D01* -X0092500Y0015000D01* -X0096444Y0010787D02* -X0098555Y0010787D01* -X0099754Y0009588D02* -X0095246Y0009588D01* -X0089754Y0009588D02* -X0085246Y0009588D01* -X0086444Y0010787D02* -X0088555Y0010787D01* -X0082500Y0015000D02* -X0087500Y0020000D01* -X0087500Y0043563D01* -X0091437Y0047500D01* -X0091673Y0047500D01* -X0095610Y0051437D01* -X0095610Y0072559D01* -X0091673Y0072559D02* -X0091673Y0086142D01* -X0100000Y0086142D01* -X0100000Y0109764D01* -X0080059Y0109764D01* -X0124941Y0109764D01* -X0128750Y0109764D01* -X0128750Y0122500D01* -X0153760Y0122500D01* -X0155000Y0122500D01* -X0155000Y0130000D01* -X0190000Y0130000D01* -X0186708Y0133168D02* -X0187412Y0133873D01* -X0189091Y0134568D01* -X0190909Y0134568D01* -X0192588Y0133873D01* -X0193873Y0132588D01* -X0194331Y0131481D01* -X0194331Y0144208D01* -X0193627Y0144912D01* -X0193168Y0146019D01* -X0193168Y0140807D01* -X0192686Y0139642D01* -X0189999Y0136955D01* -X0189999Y0136955D01* -X0189108Y0136064D01* -X0187943Y0135581D01* -X0136963Y0135581D01* -X0137577Y0135327D01* -X0138043Y0134861D01* -X0138145Y0134920D01* -X0138628Y0135050D01* -X0140837Y0135050D01* -X0140837Y0130600D01* -X0142037Y0130600D01* -X0142037Y0135050D01* -X0144246Y0135050D01* -X0144729Y0134920D01* -X0145163Y0134670D01* -X0145516Y0134316D01* -X0145767Y0133883D01* -X0145896Y0133400D01* -X0145896Y0130600D01* -X0142037Y0130600D01* -X0142037Y0129400D01* -X0145896Y0129400D01* -X0145896Y0126600D01* -X0145767Y0126117D01* -X0145516Y0125684D01* -X0145501Y0125668D01* -X0148632Y0125668D01* -X0148632Y0125964D01* -X0149023Y0126908D01* -X0149746Y0127630D01* -X0150058Y0127759D01* -X0150000Y0127759D01* -X0150000Y0131250D01* -X0150000Y0131250D01* -X0150000Y0134741D01* -X0149656Y0134741D01* -X0148982Y0134606D01* -X0148347Y0134343D01* -X0147775Y0133961D01* -X0147289Y0133475D01* -X0146907Y0132903D01* -X0146644Y0132268D01* -X0146509Y0131594D01* -X0146509Y0131250D01* -X0146509Y0130906D01* -X0146644Y0130232D01* -X0146907Y0129597D01* -X0147289Y0129025D01* -X0147775Y0128539D01* -X0148347Y0128157D01* -X0148982Y0127894D01* -X0149656Y0127759D01* -X0150000Y0127759D01* -X0150000Y0131250D01* -X0150000Y0131250D01* -X0150000Y0131250D01* -X0146509Y0131250D01* -X0150000Y0131250D01* -X0150000Y0134741D01* -X0150344Y0134741D01* -X0151018Y0134606D01* -X0151653Y0134343D01* -X0152225Y0133961D01* -X0152711Y0133475D01* -X0153093Y0132903D01* -X0153190Y0132671D01* -X0153205Y0132686D01* -X0154370Y0133168D01* -X0186708Y0133168D01* -X0188282Y0134233D02* -X0151818Y0134233D01* -X0153005Y0133035D02* -X0154047Y0133035D01* -X0150000Y0133035D02* -X0150000Y0133035D01* -X0150000Y0134233D02* -X0150000Y0134233D01* -X0148182Y0134233D02* -X0145564Y0134233D01* -X0145896Y0133035D02* -X0146995Y0133035D01* -X0146558Y0131836D02* -X0145896Y0131836D01* -X0145896Y0130638D02* -X0146563Y0130638D01* -X0147012Y0129439D02* -X0142037Y0129439D01* -X0142037Y0130638D02* -X0140837Y0130638D01* -X0140837Y0131836D02* -X0142037Y0131836D01* -X0142037Y0133035D02* -X0140837Y0133035D01* -X0140837Y0134233D02* -X0142037Y0134233D01* -X0137324Y0135432D02* -X0194331Y0135432D01* -X0194331Y0136630D02* -X0189674Y0136630D01* -X0190873Y0137829D02* -X0194331Y0137829D01* -X0194331Y0139027D02* -X0192071Y0139027D01* -X0192928Y0140226D02* -X0194331Y0140226D01* -X0194331Y0141424D02* -X0193168Y0141424D01* -X0193168Y0142623D02* -X0194331Y0142623D01* -X0194331Y0143821D02* -X0193168Y0143821D01* -X0193168Y0145020D02* -X0193582Y0145020D01* -X0197500Y0147500D02* -X0197500Y0123937D01* -X0196063Y0122500D01* -X0161240Y0122500D01* -X0160856Y0122500D01* -X0160856Y0109764D01* -X0156919Y0105827D01* -X0124941Y0105827D01* -X0124941Y0107795D02* -X0133563Y0107795D01* -X0133563Y0115000D01* -X0136731Y0109323D02* -X0137577Y0109673D01* -X0138043Y0110139D01* -X0138145Y0110080D01* -X0138628Y0109950D01* -X0140837Y0109950D01* -X0140837Y0114400D01* -X0142037Y0114400D01* -X0142037Y0115600D01* -X0145896Y0115600D01* -X0145896Y0118400D01* -X0145767Y0118883D01* -X0145516Y0119316D01* -X0145501Y0119331D01* -X0148632Y0119331D01* -X0148632Y0119036D01* -X0149023Y0118092D01* -X0149746Y0117370D01* -X0150690Y0116979D01* -X0156830Y0116979D01* -X0157500Y0117256D01* -X0157688Y0117179D01* -X0157688Y0111076D01* -X0155607Y0108995D01* -X0136731Y0108995D01* -X0136731Y0109323D01* -X0136731Y0109065D02* -X0150629Y0109065D01* -X0150906Y0109009D02* -X0151250Y0109009D01* -X0151594Y0109009D01* -X0152268Y0109144D01* -X0152903Y0109407D01* -X0153475Y0109789D01* -X0153961Y0110275D01* -X0154343Y0110847D01* -X0154606Y0111482D01* -X0154741Y0112156D01* -X0154741Y0112500D01* -X0154741Y0112844D01* -X0154606Y0113518D01* -X0154343Y0114153D01* -X0153961Y0114725D01* -X0153475Y0115211D01* -X0152903Y0115593D01* -X0152268Y0115856D01* -X0151594Y0115991D01* -X0151250Y0115991D01* -X0150906Y0115991D01* -X0150232Y0115856D01* -X0149597Y0115593D01* -X0149025Y0115211D01* -X0148539Y0114725D01* -X0148157Y0114153D01* -X0147894Y0113518D01* -X0147759Y0112844D01* -X0147759Y0112500D01* -X0147759Y0112156D01* -X0147894Y0111482D01* -X0148157Y0110847D01* -X0148539Y0110275D01* -X0149025Y0109789D01* -X0149597Y0109407D01* -X0150232Y0109144D01* -X0150906Y0109009D01* -X0151250Y0109009D02* -X0151250Y0112500D01* -X0154741Y0112500D01* -X0151250Y0112500D01* -X0151250Y0112500D01* -X0151250Y0112500D01* -X0151250Y0115991D01* -X0151250Y0112500D01* -X0151250Y0109009D01* -X0151250Y0109065D02* -X0151250Y0109065D01* -X0151871Y0109065D02* -X0155676Y0109065D01* -X0156875Y0110263D02* -X0153949Y0110263D01* -X0154598Y0111462D02* -X0157688Y0111462D01* -X0157688Y0112660D02* -X0154741Y0112660D01* -X0154465Y0113859D02* -X0157688Y0113859D01* -X0157688Y0115057D02* -X0153629Y0115057D01* -X0151250Y0115057D02* -X0151250Y0115057D01* -X0151250Y0113859D02* -X0151250Y0113859D01* -X0151250Y0112660D02* -X0151250Y0112660D01* -X0151250Y0112500D02* -X0151250Y0112500D01* -X0151250Y0112500D01* -X0147759Y0112500D01* -X0151250Y0112500D01* -X0151250Y0111462D02* -X0151250Y0111462D01* -X0151250Y0110263D02* -X0151250Y0110263D01* -X0148551Y0110263D02* -X0145047Y0110263D01* -X0145163Y0110330D02* -X0145516Y0110684D01* -X0145767Y0111117D01* -X0145896Y0111600D01* -X0145896Y0114400D01* -X0142037Y0114400D01* -X0142037Y0109950D01* -X0144246Y0109950D01* -X0144729Y0110080D01* -X0145163Y0110330D01* -X0145859Y0111462D02* -X0147902Y0111462D01* -X0147759Y0112660D02* -X0145896Y0112660D01* -X0145896Y0113859D02* -X0148035Y0113859D01* -X0148871Y0115057D02* -X0142037Y0115057D01* -X0142037Y0113859D02* -X0140837Y0113859D01* -X0140837Y0112660D02* -X0142037Y0112660D01* -X0142037Y0111462D02* -X0140837Y0111462D01* -X0140837Y0110263D02* -X0142037Y0110263D01* -X0145896Y0116256D02* -X0157688Y0116256D01* -X0164025Y0116256D02* -X0249618Y0116256D01* -X0249725Y0116211D02* -X0251181Y0116211D01* -X0251181Y0114183D01* -X0249986Y0114183D01* -X0249503Y0114054D01* -X0249070Y0113804D01* -X0248716Y0113450D01* -X0248466Y0113017D01* -X0248336Y0112534D01* -X0248336Y0111299D01* -X0248336Y0110866D01* -X0248059Y0110589D01* -X0247668Y0109645D01* -X0247668Y0108168D01* -X0236312Y0108168D01* -X0235186Y0109295D01* -X0234295Y0110186D01* -X0233130Y0110668D01* -X0228868Y0110668D01* -X0228868Y0110964D01* -X0228477Y0111908D01* -X0227754Y0112630D01* -X0226810Y0113021D01* -X0220670Y0113021D01* -X0220000Y0112744D01* -X0219330Y0113021D01* -X0213190Y0113021D01* -X0212246Y0112630D01* -X0211523Y0111908D01* -X0211132Y0110964D01* -X0211132Y0110668D01* -X0206870Y0110668D01* -X0205705Y0110186D01* -X0204814Y0109295D01* -X0202546Y0107027D01* -X0162600Y0107027D01* -X0162651Y0107078D01* -X0163542Y0107969D01* -X0164025Y0109134D01* -X0164025Y0116979D01* -X0164310Y0116979D01* -X0165254Y0117370D01* -X0165977Y0118092D01* -X0166368Y0119036D01* -X0166368Y0119331D01* -X0196693Y0119331D01* -X0197858Y0119814D01* -X0198749Y0120705D01* -X0200186Y0122142D01* -X0200668Y0123307D01* -X0200668Y0144208D01* -X0201373Y0144912D01* -X0202068Y0146591D01* -X0202068Y0148409D01* -X0201373Y0150088D01* -X0200088Y0151373D01* -X0198409Y0152068D01* -X0196591Y0152068D01* -X0194912Y0151373D01* -X0193627Y0150088D01* -X0193168Y0148981D01* -X0193168Y0152251D01* -X0194729Y0153811D01* -X0194912Y0153627D01* -X0196591Y0152931D01* -X0198409Y0152931D01* -X0200088Y0153627D01* -X0201373Y0154912D01* -X0202068Y0156591D01* -X0202068Y0158409D01* -X0201373Y0160088D01* -X0200088Y0161373D01* -X0198409Y0162068D01* -X0196591Y0162068D01* -X0194912Y0161373D01* -X0194208Y0160668D01* -X0193307Y0160668D01* -X0193168Y0160611D01* -X0193168Y0202400D01* -X0194600Y0202400D01* -X0194600Y0207100D01* -X0195400Y0207100D01* -X0195400Y0207900D01* -X0200100Y0207900D01* -X0200100Y0209612D01* -X0197685Y0212027D01* -X0200768Y0215111D01* -X0200768Y0219331D01* -X0225246Y0219331D01* -X0225246Y0215198D01* -X0225637Y0214254D01* -X0226360Y0213531D01* -X0227304Y0213140D01* -X0240924Y0213140D01* -X0241868Y0213531D01* -X0242591Y0214254D01* -X0242778Y0214706D01* -X0252222Y0214706D01* -X0252409Y0214254D01* -X0253132Y0213531D01* -X0254076Y0213140D01* -X0267696Y0213140D01* -X0268640Y0213531D01* -X0269282Y0214173D01* -X0269282Y0213367D01* -X0269673Y0212423D01* -X0270139Y0211957D01* -X0270080Y0211855D01* -X0269950Y0211372D01* -X0269950Y0209163D01* -X0274400Y0209163D01* -X0274400Y0207963D01* -X0269950Y0207963D01* -X0269950Y0205754D01* -X0270080Y0205271D01* -X0270330Y0204837D01* -X0270684Y0204484D01* -X0271117Y0204233D01* -X0271600Y0204104D01* -X0274400Y0204104D01* -X0274400Y0207963D01* -X0275600Y0207963D01* -X0275600Y0209163D01* -X0280050Y0209163D01* -X0280050Y0211372D01* -X0279920Y0211855D01* -X0279861Y0211957D01* -X0280327Y0212423D01* -X0280718Y0213367D01* -X0280718Y0219507D01* -X0280327Y0220451D01* -X0279605Y0221174D01* -X0278661Y0221565D01* -X0271339Y0221565D01* -X0270395Y0221174D01* -X0270264Y0221043D01* -X0269550Y0221043D01* -X0269363Y0221494D01* -X0268640Y0222217D01* -X0267696Y0222608D01* -X0254076Y0222608D01* -X0253132Y0222217D01* -X0252409Y0221494D01* -X0252222Y0221043D01* -X0242778Y0221043D01* -X0242591Y0221494D01* -X0241868Y0222217D01* -X0240924Y0222608D01* -X0237283Y0222608D01* -X0237283Y0223130D01* -X0236800Y0224295D01* -X0235909Y0225186D01* -X0234744Y0225668D01* -X0189370Y0225668D01* -X0188205Y0225186D01* -X0187314Y0224295D01* -X0186288Y0223268D01* -X0182611Y0223268D01* -X0179231Y0219889D01* -X0179231Y0216212D01* -X0178205Y0215186D01* -X0177314Y0214295D01* -X0176831Y0213130D01* -X0176831Y0193812D01* -X0173688Y0190668D01* -X0149551Y0190668D01* -X0153696Y0194814D01* -X0154588Y0195705D01* -X0155070Y0196870D01* -X0155070Y0198882D01* -X0155562Y0198882D01* -X0156506Y0199273D01* -X0157229Y0199996D01* -X0157500Y0200651D01* -X0157771Y0199996D01* -X0158494Y0199273D01* -X0159438Y0198882D01* -X0166759Y0198882D01* -X0167703Y0199273D01* -X0168425Y0199996D01* -X0168817Y0200940D01* -X0168817Y0209060D01* -X0168425Y0210004D01* -X0167703Y0210727D01* -X0166759Y0211118D01* -X0166267Y0211118D01* -X0166267Y0213793D01* -X0166368Y0214036D01* -X0166368Y0220964D01* -X0165977Y0221908D01* -X0165254Y0222630D01* -X0164310Y0223021D01* -X0158170Y0223021D01* -X0157226Y0222630D01* -X0156868Y0222273D01* -X0156569Y0222353D01* -X0154360Y0222353D01* -X0154360Y0218100D01* -X0153160Y0218100D01* -X0153160Y0222353D01* -X0150951Y0222353D01* -X0150467Y0222223D01* -X0150034Y0221973D01* -X0149680Y0221619D01* -X0149430Y0221186D01* -X0149301Y0220703D01* -X0149301Y0218100D01* -X0153160Y0218100D01* -X0153160Y0216900D01* -X0154360Y0216900D01* -X0154360Y0212647D01* -X0156569Y0212647D01* -X0156868Y0212727D01* -X0157226Y0212370D01* -X0158170Y0211979D01* -X0159930Y0211979D01* -X0159930Y0211118D01* -X0159438Y0211118D01* -X0158494Y0210727D01* -X0157771Y0210004D01* -X0157500Y0209349D01* -X0157229Y0210004D01* -X0156506Y0210727D01* -X0155562Y0211118D01* -X0148241Y0211118D01* -X0147297Y0210727D01* -X0146574Y0210004D01* -X0146183Y0209060D01* -X0146183Y0200940D01* -X0146574Y0199996D01* -X0147297Y0199273D01* -X0148241Y0198882D01* -X0148733Y0198882D01* -X0148733Y0198812D01* -X0145589Y0195668D01* -X0129551Y0195668D01* -X0129588Y0195705D01* -X0130070Y0196870D01* -X0130070Y0198882D01* -X0130562Y0198882D01* -X0131506Y0199273D01* -X0132229Y0199996D01* -X0132500Y0200651D01* -X0132771Y0199996D01* -X0133494Y0199273D01* -X0134438Y0198882D01* -X0141759Y0198882D01* -X0142703Y0199273D01* -X0143425Y0199996D01* -X0143817Y0200940D01* -X0143817Y0209060D01* -X0143425Y0210004D01* -X0142703Y0210727D01* -X0141759Y0211118D01* -X0141267Y0211118D01* -X0141267Y0213793D01* -X0141368Y0214036D01* -X0141368Y0220964D01* -X0140977Y0221908D01* -X0140254Y0222630D01* -X0139310Y0223021D01* -X0133170Y0223021D01* -X0132226Y0222630D01* -X0131868Y0222273D01* -X0131569Y0222353D01* -X0129360Y0222353D01* -X0129360Y0218100D01* -X0128160Y0218100D01* -X0128160Y0222353D01* -X0125951Y0222353D01* -X0125467Y0222223D01* -X0125034Y0221973D01* -X0124680Y0221619D01* -X0124430Y0221186D01* -X0124301Y0220703D01* -X0124301Y0218100D01* -X0128160Y0218100D01* -X0128160Y0216900D01* -X0129360Y0216900D01* -X0129360Y0212647D01* -X0131569Y0212647D01* -X0131868Y0212727D01* -X0132226Y0212370D01* -X0133170Y0211979D01* -X0134930Y0211979D01* -X0134930Y0211118D01* -X0134438Y0211118D01* -X0133494Y0210727D01* -X0132771Y0210004D01* -X0132500Y0209349D01* -X0132229Y0210004D01* -X0131506Y0210727D01* -X0130562Y0211118D01* -X0123241Y0211118D01* -X0122297Y0210727D01* -X0121574Y0210004D01* -X0121183Y0209060D01* -X0121183Y0200940D01* -X0121296Y0200668D01* -X0118771Y0200668D01* -X0118690Y0200635D01* -X0118817Y0200940D01* -X0118817Y0209060D01* -X0118425Y0210004D01* -X0117703Y0210727D01* -X0116759Y0211118D01* -X0116267Y0211118D01* -X0116267Y0213793D01* -X0116368Y0214036D01* -X0116368Y0220964D01* -X0115977Y0221908D01* -X0115254Y0222630D01* -X0114310Y0223021D01* -X0108170Y0223021D01* -X0107226Y0222630D01* -X0106868Y0222273D01* -X0106569Y0222353D01* -X0104360Y0222353D01* -X0104360Y0218100D01* -X0103160Y0218100D01* -X0103160Y0222353D01* -X0100951Y0222353D01* -X0100467Y0222223D01* -X0100034Y0221973D01* -X0099680Y0221619D01* -X0099430Y0221186D01* -X0099301Y0220703D01* -X0099301Y0218100D01* -X0103160Y0218100D01* -X0103160Y0216900D01* -X0104360Y0216900D01* -X0104360Y0212647D01* -X0106569Y0212647D01* -X0106868Y0212727D01* -X0107226Y0212370D01* -X0108170Y0211979D01* -X0109930Y0211979D01* -X0109930Y0211118D01* -X0109438Y0211118D01* -X0108494Y0210727D01* -X0107771Y0210004D01* -X0107500Y0209349D01* -X0107229Y0210004D01* -X0106506Y0210727D01* -X0105562Y0211118D01* -X0098241Y0211118D01* -X0097297Y0210727D01* -X0096574Y0210004D01* -X0096183Y0209060D01* -X0096183Y0208168D01* -X0085653Y0208168D01* -X0085186Y0209295D01* -X0084295Y0210186D01* -X0079295Y0215186D01* -X0078130Y0215668D01* -X0068268Y0215668D01* -X0068268Y0219889D01* -X0064889Y0223268D01* -X0061212Y0223268D01* -X0057686Y0226795D01* -X0057089Y0227392D01* -X0286132Y0227392D01* -X0286132Y0166433D01* -X0284751Y0167814D01* -X0282447Y0168768D01* -X0272553Y0168768D01* -X0270249Y0167814D01* -X0268704Y0166268D01* -X0261750Y0166268D01* -X0260365Y0165695D01* -X0255365Y0160695D01* -X0254975Y0160305D01* -X0254233Y0160305D01* -X0253289Y0159914D01* -X0252567Y0159191D01* -X0252176Y0158247D01* -X0252176Y0151713D01* -X0252567Y0150769D01* -X0253289Y0150047D01* -X0254233Y0149656D01* -X0260767Y0149656D01* -X0261711Y0150047D01* -X0262433Y0150769D01* -X0262824Y0151713D01* -X0262824Y0157495D01* -X0264061Y0158731D01* -X0268704Y0158731D01* -X0270249Y0157186D01* -X0270550Y0157061D01* -X0270152Y0156771D01* -X0269529Y0156148D01* -X0269010Y0155435D01* -X0268610Y0154650D01* -X0268338Y0153811D01* -X0268209Y0153000D01* -X0277000Y0153000D01* -X0277000Y0152000D01* -X0278000Y0152000D01* -X0278000Y0146900D01* -X0281641Y0146900D01* -X0282511Y0147038D01* -X0283350Y0147310D01* -X0284135Y0147710D01* -X0284848Y0148229D01* -X0285471Y0148852D01* -X0285990Y0149565D01* -X0286132Y0149845D01* -X0286132Y0051433D01* -X0284751Y0052814D01* -X0282447Y0053768D01* -X0272553Y0053768D01* -X0272500Y0053746D01* -X0272447Y0053768D01* -X0269418Y0053768D01* -X0269418Y0053926D01* -X0270256Y0054273D01* -X0270979Y0054996D01* -X0271250Y0055651D01* -X0271521Y0054996D01* -X0272244Y0054273D01* -X0273188Y0053882D01* -X0280509Y0053882D01* -X0281453Y0054273D01* -X0282175Y0054996D01* -X0282567Y0055940D01* -X0282567Y0064060D01* -X0282175Y0065004D01* -X0281453Y0065727D01* -X0280509Y0066118D01* -X0280017Y0066118D01* -X0280017Y0068793D01* -X0280118Y0069036D01* -X0280118Y0075964D01* -X0279727Y0076908D01* -X0279004Y0077630D01* -X0278060Y0078021D01* -X0277876Y0078021D01* -X0278093Y0078347D01* -X0278356Y0078982D01* -X0278491Y0079656D01* -X0278491Y0080000D01* -X0278491Y0080344D01* -X0278404Y0080778D01* -X0282243Y0080778D01* -X0283187Y0081169D01* -X0283910Y0081892D01* -X0284301Y0082836D01* -X0284301Y0091731D01* -X0283910Y0092675D01* -X0283187Y0093398D01* -X0282243Y0093789D01* -X0281318Y0093789D01* -X0281318Y0116211D01* -X0282243Y0116211D01* -X0283187Y0116602D01* -X0283910Y0117325D01* -X0284301Y0118269D01* -X0284301Y0127164D01* -X0283910Y0128108D01* -X0283187Y0128831D01* -X0282243Y0129222D01* -X0271379Y0129222D01* -X0271268Y0129176D01* -X0271268Y0135750D01* -X0270695Y0137135D01* -X0269635Y0138195D01* -X0264635Y0143195D01* -X0263250Y0143768D01* -X0262625Y0143768D01* -X0262433Y0144231D01* -X0261711Y0144953D01* -X0260767Y0145344D01* -X0254233Y0145344D01* -X0253289Y0144953D01* -X0252567Y0144231D01* -X0252375Y0143768D01* -X0251375Y0143768D01* -X0251183Y0144231D01* -X0250461Y0144953D01* -X0249517Y0145344D01* -X0242983Y0145344D01* -X0242039Y0144953D01* -X0241317Y0144231D01* -X0240926Y0143286D01* -X0240926Y0136753D01* -X0241317Y0135809D01* -X0242039Y0135086D01* -X0242983Y0134695D01* -X0249517Y0134695D01* -X0250461Y0135086D01* -X0251183Y0135809D01* -X0251358Y0136231D01* -X0252392Y0136231D01* -X0252567Y0135809D01* -X0253289Y0135086D01* -X0254233Y0134695D01* -X0260767Y0134695D01* -X0261711Y0135086D01* -X0261898Y0135273D01* -X0263731Y0133439D01* -X0263731Y0126318D01* -X0262647Y0126318D01* -X0262647Y0127164D01* -X0262256Y0128108D01* -X0261534Y0128831D01* -X0260590Y0129222D01* -X0249725Y0129222D01* -X0248781Y0128831D01* -X0248059Y0128108D01* -X0247668Y0127164D01* -X0247668Y0118269D01* -X0248059Y0117325D01* -X0248781Y0116602D01* -X0249725Y0116211D01* -X0251181Y0115057D02* -X0164025Y0115057D01* -X0164025Y0113859D02* -X0249164Y0113859D01* -X0248370Y0112660D02* -X0227682Y0112660D01* -X0228661Y0111462D02* -X0248336Y0111462D01* -X0248336Y0111299D02* -X0248769Y0111299D01* -X0248336Y0111299D01* -X0248769Y0111299D02* -X0248769Y0111299D01* -X0247924Y0110263D02* -X0234109Y0110263D01* -X0235416Y0109065D02* -X0247668Y0109065D01* -X0240000Y0111299D02* -X0240000Y0122500D01* -X0243491Y0122500D01* -X0243491Y0122844D01* -X0243356Y0123518D01* -X0243093Y0124153D01* -X0242711Y0124725D01* -X0242225Y0125211D01* -X0241653Y0125593D01* -X0241018Y0125856D01* -X0240344Y0125991D01* -X0240000Y0125991D01* -X0239656Y0125991D01* -X0238982Y0125856D01* -X0238347Y0125593D01* -X0237775Y0125211D01* -X0237289Y0124725D01* -X0236907Y0124153D01* -X0236644Y0123518D01* -X0236509Y0122844D01* -X0236509Y0122500D01* -X0236509Y0122156D01* -X0236644Y0121482D01* -X0236907Y0120847D01* -X0237289Y0120275D01* -X0237775Y0119789D01* -X0238347Y0119407D01* -X0238982Y0119144D01* -X0239656Y0119009D01* -X0240000Y0119009D01* -X0240344Y0119009D01* -X0241018Y0119144D01* -X0241653Y0119407D01* -X0242225Y0119789D01* -X0242711Y0120275D01* -X0243093Y0120847D01* -X0243356Y0121482D01* -X0243491Y0122156D01* -X0243491Y0122500D01* -X0240000Y0122500D01* -X0240000Y0122500D01* -X0240000Y0122500D01* -X0240000Y0125991D01* -X0240000Y0122500D01* -X0240000Y0119009D01* -X0240000Y0122500D01* -X0240000Y0122500D01* -X0240000Y0122500D01* -X0236509Y0122500D01* -X0240000Y0122500D01* -X0240000Y0122248D02* -X0240000Y0122248D01* -X0240000Y0121050D02* -X0240000Y0121050D01* -X0240000Y0119851D02* -X0240000Y0119851D01* -X0242288Y0119851D02* -X0247668Y0119851D01* -X0247668Y0118653D02* -X0166209Y0118653D01* -X0165338Y0117454D02* -X0248005Y0117454D01* -X0247668Y0121050D02* -X0243177Y0121050D01* -X0243491Y0122248D02* -X0247668Y0122248D01* -X0247668Y0123447D02* -X0243371Y0123447D01* -X0242765Y0124645D02* -X0247668Y0124645D01* -X0247668Y0125844D02* -X0241049Y0125844D01* -X0240000Y0125844D02* -X0240000Y0125844D01* -X0238951Y0125844D02* -X0200668Y0125844D01* -X0200668Y0127042D02* -X0247668Y0127042D01* -X0248191Y0128241D02* -X0200668Y0128241D01* -X0200668Y0129439D02* -X0263731Y0129439D01* -X0263731Y0128241D02* -X0262124Y0128241D01* -X0262647Y0127042D02* -X0263731Y0127042D01* -X0263731Y0130638D02* -X0200668Y0130638D01* -X0200668Y0131836D02* -X0223517Y0131836D01* -X0223347Y0131907D02* -X0223982Y0131644D01* -X0224656Y0131509D01* -X0225000Y0131509D01* -X0225344Y0131509D01* -X0226018Y0131644D01* -X0226653Y0131907D01* -X0227225Y0132289D01* -X0227711Y0132775D01* -X0228093Y0133347D01* -X0228356Y0133982D01* -X0228491Y0134656D01* -X0228491Y0135000D01* -X0228491Y0135344D01* -X0228356Y0136018D01* -X0228093Y0136653D01* -X0227711Y0137225D01* -X0227225Y0137711D01* -X0226653Y0138093D01* -X0226018Y0138356D01* -X0225344Y0138491D01* -X0225000Y0138491D01* -X0224656Y0138491D01* -X0223982Y0138356D01* -X0223347Y0138093D01* -X0222775Y0137711D01* -X0222289Y0137225D01* -X0221907Y0136653D01* -X0221644Y0136018D01* -X0221509Y0135344D01* -X0221509Y0135000D01* -X0221509Y0134656D01* -X0221644Y0133982D01* -X0221907Y0133347D01* -X0222289Y0132775D01* -X0222775Y0132289D01* -X0223347Y0131907D01* -X0225000Y0131836D02* -X0225000Y0131836D01* -X0225000Y0131509D02* -X0225000Y0135000D01* -X0228491Y0135000D01* -X0225000Y0135000D01* -X0225000Y0135000D01* -X0225000Y0135000D01* -X0225000Y0138491D01* -X0225000Y0135000D01* -X0225000Y0135000D01* -X0225000Y0135000D01* -X0221509Y0135000D01* -X0225000Y0135000D01* -X0225000Y0131509D01* -X0226483Y0131836D02* -X0263731Y0131836D01* -X0263731Y0133035D02* -X0227885Y0133035D01* -X0228406Y0134233D02* -X0262937Y0134233D01* -X0268802Y0139027D02* -X0274355Y0139027D01* -X0274407Y0139153D02* -X0274144Y0138518D01* -X0274009Y0137844D01* -X0274009Y0137500D01* -X0274009Y0137156D01* -X0274144Y0136482D01* -X0274407Y0135847D01* -X0274789Y0135275D01* -X0275275Y0134789D01* -X0275847Y0134407D01* -X0276482Y0134144D01* -X0277156Y0134009D01* -X0277500Y0134009D01* -X0277844Y0134009D01* -X0278518Y0134144D01* -X0279153Y0134407D01* -X0279725Y0134789D01* -X0280211Y0135275D01* -X0280593Y0135847D01* -X0280856Y0136482D01* -X0280991Y0137156D01* -X0280991Y0137500D01* -X0280991Y0137844D01* -X0280856Y0138518D01* -X0280593Y0139153D01* -X0280211Y0139725D01* -X0279725Y0140211D01* -X0279153Y0140593D01* -X0278518Y0140856D01* -X0277844Y0140991D01* -X0277500Y0140991D01* -X0277156Y0140991D01* -X0276482Y0140856D01* -X0275847Y0140593D01* -X0275275Y0140211D01* -X0274789Y0139725D01* -X0274407Y0139153D01* -X0274009Y0137829D02* -X0270001Y0137829D01* -X0270904Y0136630D02* -X0274114Y0136630D01* -X0274009Y0137500D02* -X0277500Y0137500D01* -X0280991Y0137500D01* -X0277500Y0137500D01* -X0277500Y0137500D01* -X0277500Y0137500D01* -X0277500Y0140991D01* -X0277500Y0137500D01* -X0277500Y0134009D01* -X0277500Y0137500D01* -X0277500Y0137500D01* -X0277500Y0137500D01* -X0274009Y0137500D01* -X0274684Y0135432D02* -X0271268Y0135432D01* -X0271268Y0134233D02* -X0276265Y0134233D01* -X0277500Y0134233D02* -X0277500Y0134233D01* -X0277500Y0135432D02* -X0277500Y0135432D01* -X0277500Y0136630D02* -X0277500Y0136630D01* -X0277500Y0137829D02* -X0277500Y0137829D01* -X0277500Y0139027D02* -X0277500Y0139027D01* -X0277500Y0140226D02* -X0277500Y0140226D01* -X0275297Y0140226D02* -X0267604Y0140226D01* -X0266405Y0141424D02* -X0286132Y0141424D01* -X0286132Y0140226D02* -X0279703Y0140226D01* -X0280645Y0139027D02* -X0286132Y0139027D01* -X0286132Y0137829D02* -X0280991Y0137829D01* -X0280886Y0136630D02* -X0286132Y0136630D01* -X0286132Y0135432D02* -X0280316Y0135432D01* -X0278735Y0134233D02* -X0286132Y0134233D01* -X0286132Y0133035D02* -X0271268Y0133035D01* -X0271268Y0131836D02* -X0286132Y0131836D01* -X0286132Y0130638D02* -X0271268Y0130638D01* -X0271268Y0129439D02* -X0286132Y0129439D01* -X0286132Y0128241D02* -X0283778Y0128241D01* -X0284301Y0127042D02* -X0286132Y0127042D01* -X0286132Y0125844D02* -X0284301Y0125844D01* -X0284301Y0124645D02* -X0286132Y0124645D01* -X0286132Y0123447D02* -X0284301Y0123447D01* -X0284301Y0122248D02* -X0286132Y0122248D01* -X0286132Y0121050D02* -X0284301Y0121050D01* -X0284301Y0119851D02* -X0286132Y0119851D01* -X0286132Y0118653D02* -X0284301Y0118653D01* -X0283963Y0117454D02* -X0286132Y0117454D01* -X0286132Y0116256D02* -X0282351Y0116256D01* -X0281318Y0115057D02* -X0286132Y0115057D01* -X0286132Y0113859D02* -X0281318Y0113859D01* -X0281318Y0112660D02* -X0286132Y0112660D01* -X0286132Y0111462D02* -X0281318Y0111462D01* -X0281318Y0110263D02* -X0286132Y0110263D01* -X0286132Y0109065D02* -X0281318Y0109065D01* -X0281318Y0107866D02* -X0286132Y0107866D01* -X0286132Y0106668D02* -X0281318Y0106668D01* -X0281318Y0105469D02* -X0286132Y0105469D01* -X0286132Y0104270D02* -X0281318Y0104270D01* -X0281318Y0103072D02* -X0286132Y0103072D01* -X0286132Y0101873D02* -X0281318Y0101873D01* -X0281318Y0100675D02* -X0286132Y0100675D01* -X0286132Y0099476D02* -X0281318Y0099476D01* -X0281318Y0098278D02* -X0286132Y0098278D01* -X0286132Y0097079D02* -X0281318Y0097079D01* -X0281318Y0095881D02* -X0286132Y0095881D01* -X0286132Y0094682D02* -X0281318Y0094682D01* -X0282980Y0093484D02* -X0286132Y0093484D01* -X0286132Y0092285D02* -X0284071Y0092285D01* -X0284301Y0091087D02* -X0286132Y0091087D01* -X0286132Y0089888D02* -X0284301Y0089888D01* -X0284301Y0088690D02* -X0286132Y0088690D01* -X0286132Y0087491D02* -X0284301Y0087491D01* -X0284301Y0086293D02* -X0286132Y0086293D01* -X0286132Y0085094D02* -X0284301Y0085094D01* -X0284301Y0083896D02* -X0286132Y0083896D01* -X0286132Y0082697D02* -X0284244Y0082697D01* -X0283517Y0081499D02* -X0286132Y0081499D01* -X0286132Y0080300D02* -X0278491Y0080300D01* -X0278491Y0080000D02* -X0275000Y0080000D01* -X0275000Y0080000D01* -X0275000Y0080000D01* -X0271509Y0080000D01* -X0271509Y0079656D01* -X0271644Y0078982D01* -X0271907Y0078347D01* -X0272124Y0078021D01* -X0271920Y0078021D01* -X0270976Y0077630D01* -X0270618Y0077273D01* -X0270319Y0077353D01* -X0268110Y0077353D01* -X0268110Y0073100D01* -X0266910Y0073100D01* -X0266910Y0077353D01* -X0265182Y0077353D01* -X0265695Y0077865D01* -X0269717Y0081887D01* -X0270435Y0081169D01* -X0271379Y0080778D01* -X0271596Y0080778D01* -X0271509Y0080344D01* -X0271509Y0080000D01* -X0275000Y0080000D01* -X0278491Y0080000D01* -X0278380Y0079102D02* -X0286132Y0079102D01* -X0286132Y0077903D02* -X0278345Y0077903D01* -X0279811Y0076705D02* -X0286132Y0076705D01* -X0286132Y0075506D02* -X0280118Y0075506D01* -X0280118Y0074308D02* -X0286132Y0074308D01* -X0286132Y0073109D02* -X0280118Y0073109D01* -X0280118Y0071911D02* -X0286132Y0071911D01* -X0286132Y0070712D02* -X0280118Y0070712D01* -X0280118Y0069514D02* -X0286132Y0069514D01* -X0286132Y0068315D02* -X0280017Y0068315D01* -X0280017Y0067117D02* -X0286132Y0067117D01* -X0286132Y0065918D02* -X0280991Y0065918D01* -X0282293Y0064720D02* -X0286132Y0064720D01* -X0286132Y0063521D02* -X0282567Y0063521D01* -X0282567Y0062323D02* -X0286132Y0062323D01* -X0286132Y0061124D02* -X0282567Y0061124D01* -X0282567Y0059926D02* -X0286132Y0059926D01* -X0286132Y0058727D02* -X0282567Y0058727D01* -X0282567Y0057529D02* -X0286132Y0057529D01* -X0286132Y0056330D02* -X0282567Y0056330D01* -X0282232Y0055132D02* -X0286132Y0055132D01* -X0286132Y0053933D02* -X0280631Y0053933D01* -X0284830Y0052734D02* -X0286132Y0052734D01* -X0286132Y0051536D02* -X0286029Y0051536D01* -X0276848Y0060000D02* -X0276848Y0072500D01* -X0274990Y0072500D01* -X0273680Y0066979D02* -X0271920Y0066979D01* -X0270976Y0067370D01* -X0270618Y0067727D01* -X0270319Y0067647D01* -X0268110Y0067647D01* -X0268110Y0071900D01* -X0266910Y0071900D01* -X0266910Y0067647D01* -X0264701Y0067647D01* -X0264217Y0067777D01* -X0263784Y0068027D01* -X0263430Y0068381D01* -X0263180Y0068814D01* -X0263051Y0069297D01* -X0263051Y0071900D01* -X0266910Y0071900D01* -X0266910Y0073100D01* -X0263051Y0073100D01* -X0263051Y0075703D01* -X0263180Y0076186D01* -X0263206Y0076231D01* -X0260758Y0076231D01* -X0260758Y0073100D01* -X0256506Y0073100D01* -X0256506Y0071900D01* -X0260758Y0071900D01* -X0260758Y0068313D01* -X0260629Y0067830D01* -X0260379Y0067396D01* -X0260025Y0067043D01* -X0259592Y0066792D01* -X0259108Y0066663D01* -X0256505Y0066663D01* -X0256505Y0071900D01* -X0255306Y0071900D01* -X0255306Y0066663D01* -X0255068Y0066663D01* -X0255068Y0065837D01* -X0255306Y0065837D01* -X0255306Y0060600D01* -X0256505Y0060600D01* -X0256505Y0065837D01* -X0259108Y0065837D01* -X0259592Y0065708D01* -X0260025Y0065457D01* -X0260379Y0065104D01* -X0260395Y0065075D01* -X0261047Y0065727D01* -X0261991Y0066118D01* -X0269312Y0066118D01* -X0270256Y0065727D01* -X0270979Y0065004D01* -X0271250Y0064349D01* -X0271521Y0065004D01* -X0272244Y0065727D01* -X0273188Y0066118D01* -X0273680Y0066118D01* -X0273680Y0066979D01* -X0272706Y0065918D02* -X0269794Y0065918D01* -X0271097Y0064720D02* -X0271403Y0064720D01* -X0271587Y0067117D02* -X0260099Y0067117D01* -X0260758Y0068315D02* -X0263496Y0068315D01* -X0263051Y0069514D02* -X0260758Y0069514D01* -X0260758Y0070712D02* -X0263051Y0070712D01* -X0263051Y0073109D02* -X0260758Y0073109D01* -X0260758Y0074308D02* -X0263051Y0074308D01* -X0263051Y0075506D02* -X0260758Y0075506D01* -X0265733Y0077903D02* -X0271635Y0077903D01* -X0271620Y0079102D02* -X0266931Y0079102D01* -X0268130Y0080300D02* -X0271509Y0080300D01* -X0270105Y0081499D02* -X0269328Y0081499D01* -X0268110Y0076705D02* -X0266910Y0076705D01* -X0266910Y0075506D02* -X0268110Y0075506D01* -X0268110Y0074308D02* -X0266910Y0074308D01* -X0266910Y0073109D02* -X0268110Y0073109D01* -X0266910Y0071911D02* -X0256506Y0071911D01* -X0256505Y0070712D02* -X0255306Y0070712D01* -X0255306Y0069514D02* -X0256505Y0069514D01* -X0256505Y0068315D02* -X0255306Y0068315D01* -X0255306Y0067117D02* -X0256505Y0067117D01* -X0255068Y0065918D02* -X0261509Y0065918D01* -X0256505Y0064720D02* -X0255306Y0064720D01* -X0255306Y0063521D02* -X0256505Y0063521D01* -X0256505Y0062323D02* -X0255306Y0062323D01* -X0255306Y0061124D02* -X0256505Y0061124D01* -X0256505Y0059400D02* -X0256505Y0054163D01* -X0259108Y0054163D01* -X0259592Y0054292D01* -X0260025Y0054543D01* -X0260379Y0054896D01* -X0260395Y0054925D01* -X0261047Y0054273D01* -X0261991Y0053882D01* -X0263081Y0053882D01* -X0263081Y0053768D01* -X0262553Y0053768D01* -X0254770Y0053768D01* -X0254933Y0054163D01* -X0255306Y0054163D01* -X0255306Y0059400D01* -X0256505Y0059400D01* -X0256505Y0058727D02* -X0255306Y0058727D01* -X0255306Y0057529D02* -X0256505Y0057529D01* -X0256505Y0056330D02* -X0255306Y0056330D01* -X0255306Y0055132D02* -X0256505Y0055132D01* -X0254838Y0053933D02* -X0261869Y0053933D01* -X0266250Y0050000D02* -X0266250Y0060000D01* -X0265652Y0060000D01* -X0271035Y0055132D02* -X0271465Y0055132D01* -X0273065Y0053933D02* -X0269435Y0053933D01* -X0257825Y0045543D02* -X0137891Y0045543D01* -X0137711Y0045275D02* -X0138093Y0045847D01* -X0138356Y0046482D01* -X0138491Y0047156D01* -X0138491Y0047500D01* -X0138491Y0047844D01* -X0138356Y0048518D01* -X0138093Y0049153D01* -X0137711Y0049725D01* -X0137225Y0050211D01* -X0136653Y0050593D01* -X0136018Y0050856D01* -X0135344Y0050991D01* -X0135000Y0050991D01* -X0134656Y0050991D01* -X0133982Y0050856D01* -X0133347Y0050593D01* -X0132775Y0050211D01* -X0132289Y0049725D01* -X0131907Y0049153D01* -X0131644Y0048518D01* -X0131509Y0047844D01* -X0131509Y0047500D01* -X0131509Y0047156D01* -X0131644Y0046482D01* -X0131907Y0045847D01* -X0132289Y0045275D01* -X0132775Y0044789D01* -X0133347Y0044407D01* -X0133982Y0044144D01* -X0134656Y0044009D01* -X0135000Y0044009D01* -X0135344Y0044009D01* -X0136018Y0044144D01* -X0136653Y0044407D01* -X0137225Y0044789D01* -X0137711Y0045275D01* -X0136504Y0044345D02* -X0258322Y0044345D01* -X0259289Y0043146D02* -X0130085Y0043146D01* -X0130699Y0044345D02* -X0133496Y0044345D01* -X0135000Y0044345D02* -X0135000Y0044345D01* -X0135000Y0044009D02* -X0135000Y0047500D01* -X0138491Y0047500D01* -X0135000Y0047500D01* -X0135000Y0047500D01* -X0135000Y0047500D01* -X0135000Y0050991D01* -X0135000Y0047500D01* -X0135000Y0047500D01* -X0135000Y0047500D01* -X0131509Y0047500D01* -X0135000Y0047500D01* -X0135000Y0044009D01* -X0135000Y0045543D02* -X0135000Y0045543D01* -X0135000Y0046742D02* -X0135000Y0046742D01* -X0135000Y0047940D02* -X0135000Y0047940D01* -X0135000Y0049139D02* -X0135000Y0049139D01* -X0135000Y0050337D02* -X0135000Y0050337D01* -X0132964Y0050337D02* -X0130699Y0050337D01* -X0130699Y0050703D02* -X0130570Y0051186D01* -X0130320Y0051619D01* -X0129966Y0051973D01* -X0129533Y0052223D01* -X0129049Y0052353D01* -X0126840Y0052353D01* -X0126840Y0048100D01* -X0130699Y0048100D01* -X0130699Y0050703D01* -X0130368Y0051536D02* -X0163731Y0051536D01* -X0163731Y0052734D02* -X0122522Y0052734D01* -X0125640Y0051536D02* -X0126840Y0051536D01* -X0126840Y0050337D02* -X0125640Y0050337D01* -X0125640Y0049139D02* -X0126840Y0049139D01* -X0126840Y0047940D02* -X0131529Y0047940D01* -X0131592Y0046742D02* -X0130699Y0046742D01* -X0130699Y0045543D02* -X0132109Y0045543D01* -X0131901Y0049139D02* -X0130699Y0049139D01* -X0126840Y0046742D02* -X0125640Y0046742D01* -X0125640Y0045543D02* -X0126840Y0045543D01* -X0126840Y0044345D02* -X0125640Y0044345D01* -X0125640Y0043146D02* -X0126840Y0043146D01* -X0126063Y0037500D02* -X0130000Y0033563D01* -X0130000Y0022500D01* -X0122500Y0015000D01* -X0128268Y0015581D02* -X0258535Y0015581D01* -X0258244Y0016779D02* -X0128760Y0016779D01* -X0128268Y0014382D02* -X0259143Y0014382D01* -X0260214Y0013184D02* -X0128268Y0013184D01* -X0127643Y0011985D02* -X0262822Y0011985D01* -X0277000Y0015581D02* -X0278000Y0015581D01* -X0278000Y0016779D02* -X0277000Y0016779D01* -X0252601Y0029963D02* -X0133168Y0029963D01* -X0133168Y0031161D02* -X0251776Y0031161D01* -X0251509Y0032360D02* -X0133168Y0032360D01* -X0133168Y0033558D02* -X0251660Y0033558D01* -X0252320Y0034757D02* -X0132935Y0034757D01* -X0132089Y0035955D02* -X0254479Y0035955D01* -X0258361Y0040749D02* -X0112590Y0040749D01* -X0111391Y0041948D02* -X0259383Y0041948D01* -X0239969Y0053768D02* -X0171268Y0053768D01* -X0171268Y0075750D01* -X0170695Y0077135D01* -X0169635Y0078195D01* -X0168250Y0078768D01* -X0129720Y0078768D01* -X0129872Y0079135D01* -X0129872Y0094784D01* -X0188688Y0094784D01* -X0195267Y0088205D01* -X0195903Y0087568D01* -X0189804Y0087568D01* -X0188860Y0087177D01* -X0188137Y0086455D01* -X0187746Y0085511D01* -X0187746Y0066989D01* -X0188137Y0066045D01* -X0188860Y0065323D01* -X0189804Y0064931D01* -X0213124Y0064931D01* -X0213124Y0062388D01* -X0216411Y0062388D01* -X0216411Y0062013D01* -X0216786Y0062013D01* -X0216786Y0057545D01* -X0218423Y0057545D01* -X0218907Y0057674D01* -X0219016Y0057737D01* -X0219470Y0057283D01* -X0220414Y0056892D01* -X0224586Y0056892D01* -X0225432Y0057243D01* -X0226111Y0056961D01* -X0226267Y0056805D01* -X0227652Y0056231D01* -X0238573Y0056231D01* -X0238573Y0055552D01* -X0238964Y0054608D01* -X0239687Y0053886D01* -X0239969Y0053768D01* -X0239639Y0053933D02* -X0171268Y0053933D01* -X0171268Y0055132D02* -X0204932Y0055132D01* -X0204789Y0055275D02* -X0205275Y0054789D01* -X0205847Y0054407D01* -X0206482Y0054144D01* -X0207156Y0054009D01* -X0207500Y0054009D01* -X0207844Y0054009D01* -X0208518Y0054144D01* -X0209153Y0054407D01* -X0209725Y0054789D01* -X0210211Y0055275D01* -X0210593Y0055847D01* -X0210856Y0056482D01* -X0210991Y0057156D01* -X0210991Y0057500D01* -X0210991Y0057844D01* -X0210856Y0058518D01* -X0210593Y0059153D01* -X0210211Y0059725D01* -X0209725Y0060211D01* -X0209153Y0060593D01* -X0208518Y0060856D01* -X0207844Y0060991D01* -X0207500Y0060991D01* -X0207156Y0060991D01* -X0206482Y0060856D01* -X0205847Y0060593D01* -X0205275Y0060211D01* -X0204789Y0059725D01* -X0204407Y0059153D01* -X0204144Y0058518D01* -X0204009Y0057844D01* -X0204009Y0057500D01* -X0204009Y0057156D01* -X0204144Y0056482D01* -X0204407Y0055847D01* -X0204789Y0055275D01* -X0204206Y0056330D02* -X0171268Y0056330D01* -X0171268Y0057529D02* -X0204009Y0057529D01* -X0204009Y0057500D02* -X0207500Y0057500D01* -X0210991Y0057500D01* -X0207500Y0057500D01* -X0207500Y0057500D01* -X0207500Y0057500D01* -X0207500Y0060991D01* -X0207500Y0057500D01* -X0207500Y0054009D01* -X0207500Y0057500D01* -X0207500Y0057500D01* -X0207500Y0057500D01* -X0204009Y0057500D01* -X0204230Y0058727D02* -X0171268Y0058727D01* -X0171268Y0059926D02* -X0204989Y0059926D01* -X0207500Y0059926D02* -X0207500Y0059926D01* -X0207500Y0058727D02* -X0207500Y0058727D01* -X0207500Y0057529D02* -X0207500Y0057529D01* -X0207500Y0056330D02* -X0207500Y0056330D01* -X0207500Y0055132D02* -X0207500Y0055132D01* -X0210068Y0055132D02* -X0238747Y0055132D01* -X0227414Y0056330D02* -X0210794Y0056330D01* -X0210991Y0057529D02* -X0219225Y0057529D01* -X0216786Y0058727D02* -X0216411Y0058727D01* -X0216411Y0057545D02* -X0216411Y0062013D01* -X0213124Y0062013D01* -X0213124Y0059195D01* -X0213253Y0058712D01* -X0213503Y0058278D01* -X0213857Y0057924D01* -X0214290Y0057674D01* -X0214773Y0057545D01* -X0216411Y0057545D01* -X0216411Y0059926D02* -X0216786Y0059926D01* -X0216786Y0061124D02* -X0216411Y0061124D01* -X0216411Y0062323D02* -X0171268Y0062323D01* -X0171268Y0063521D02* -X0213124Y0063521D01* -X0213124Y0064720D02* -X0171268Y0064720D01* -X0171268Y0065918D02* -X0188264Y0065918D01* -X0187746Y0067117D02* -X0171268Y0067117D01* -X0171268Y0068315D02* -X0187746Y0068315D01* -X0187746Y0069514D02* -X0171268Y0069514D01* -X0171268Y0070712D02* -X0187746Y0070712D01* -X0187746Y0071911D02* -X0171268Y0071911D01* -X0171268Y0073109D02* -X0187746Y0073109D01* -X0187746Y0074308D02* -X0171268Y0074308D01* -X0171268Y0075506D02* -X0187746Y0075506D01* -X0187746Y0076705D02* -X0170873Y0076705D01* -X0169926Y0077903D02* -X0187746Y0077903D01* -X0187746Y0079102D02* -X0129858Y0079102D01* -X0129872Y0080300D02* -X0187746Y0080300D01* -X0187746Y0081499D02* -X0129872Y0081499D01* -X0129872Y0082697D02* -X0187746Y0082697D01* -X0187746Y0083896D02* -X0129872Y0083896D01* -X0129872Y0085094D02* -X0187746Y0085094D01* -X0188070Y0086293D02* -X0129872Y0086293D01* -X0129872Y0087491D02* -X0189618Y0087491D01* -X0192385Y0091087D02* -X0129872Y0091087D01* -X0129872Y0092285D02* -X0191186Y0092285D01* -X0189988Y0093484D02* -X0129872Y0093484D01* -X0129872Y0094682D02* -X0188789Y0094682D01* -X0190000Y0097953D02* -X0197953Y0090000D01* -X0232500Y0090000D01* -X0237500Y0095000D01* -X0237500Y0098701D01* -X0236437Y0101850D02* -X0232500Y0097913D01* -X0232500Y0097500D01* -X0223740Y0097500D01* -X0216260Y0097500D02* -X0208937Y0097500D01* -X0207500Y0099136D01* -X0204746Y0101890D01* -X0124941Y0101890D01* -X0124941Y0103858D02* -X0203858Y0103858D01* -X0207500Y0107500D01* -X0216260Y0107500D01* -X0211339Y0111462D02* -X0164025Y0111462D01* -X0164025Y0112660D02* -X0212318Y0112660D01* -X0205891Y0110263D02* -X0164025Y0110263D01* -X0163996Y0109065D02* -X0204584Y0109065D01* -X0203385Y0107866D02* -X0163439Y0107866D01* -X0149662Y0117454D02* -X0145896Y0117454D01* -X0145828Y0118653D02* -X0148791Y0118653D01* -X0148632Y0125844D02* -X0145609Y0125844D01* -X0145896Y0127042D02* -X0149158Y0127042D01* -X0150000Y0128241D02* -X0150000Y0128241D01* -X0150000Y0129439D02* -X0150000Y0129439D01* -X0150000Y0130638D02* -X0150000Y0130638D01* -X0150000Y0131836D02* -X0150000Y0131836D01* -X0148221Y0128241D02* -X0145896Y0128241D01* -X0133563Y0130000D02* -X0128750Y0130000D01* -X0128750Y0122500D01* -X0117264Y0117441D02* -X0117264Y0133563D01* -X0122451Y0138750D01* -X0187313Y0138750D01* -X0190000Y0141437D01* -X0190000Y0153563D01* -X0193937Y0157500D01* -X0197500Y0157500D01* -X0201657Y0159402D02* -X0209231Y0159402D01* -X0209231Y0159889D02* -X0209231Y0155111D01* -X0211842Y0152500D01* -X0209231Y0149889D01* -X0209231Y0145111D01* -X0212611Y0141731D01* -X0217389Y0141731D01* -X0220000Y0144342D01* -X0222611Y0141731D01* -X0227389Y0141731D01* -X0230768Y0145111D01* -X0230768Y0149889D01* -X0227685Y0152973D01* -X0230100Y0155388D01* -X0230100Y0157100D01* -X0225400Y0157100D01* -X0225400Y0157900D01* -X0230100Y0157900D01* -X0230100Y0159612D01* -X0227212Y0162500D01* -X0230100Y0165388D01* -X0230100Y0167100D01* -X0225400Y0167100D01* -X0225400Y0167900D01* -X0230100Y0167900D01* -X0230100Y0169612D01* -X0227685Y0172027D01* -X0230768Y0175111D01* -X0230768Y0179889D01* -X0227389Y0183268D01* -X0222611Y0183268D01* -X0220000Y0180658D01* -X0217389Y0183268D01* -X0212611Y0183268D01* -X0209231Y0179889D01* -X0209231Y0175111D01* -X0211842Y0172500D01* -X0209231Y0169889D01* -X0209231Y0165111D01* -X0211842Y0162500D01* -X0209231Y0159889D01* -X0209943Y0160601D02* -X0200860Y0160601D01* -X0199059Y0161799D02* -X0211141Y0161799D01* -X0211345Y0162998D02* -X0193168Y0162998D01* -X0193168Y0164196D02* -X0210146Y0164196D01* -X0209231Y0165395D02* -X0193168Y0165395D01* -X0193168Y0166593D02* -X0209231Y0166593D01* -X0209231Y0167792D02* -X0193168Y0167792D01* -X0193168Y0168990D02* -X0209231Y0168990D01* -X0209531Y0170189D02* -X0193168Y0170189D01* -X0193168Y0171387D02* -X0210729Y0171387D01* -X0211756Y0172586D02* -X0193168Y0172586D01* -X0193168Y0173784D02* -X0210558Y0173784D01* -X0209359Y0174983D02* -X0193168Y0174983D01* -X0193168Y0176181D02* -X0209231Y0176181D01* -X0209231Y0177380D02* -X0193168Y0177380D01* -X0193168Y0178578D02* -X0209231Y0178578D01* -X0209231Y0179777D02* -X0193168Y0179777D01* -X0193168Y0180975D02* -X0210317Y0180975D01* -X0211516Y0182174D02* -X0193168Y0182174D01* -X0193168Y0183372D02* -X0286132Y0183372D01* -X0286132Y0182174D02* -X0228484Y0182174D01* -X0229683Y0180975D02* -X0243264Y0180975D01* -X0243662Y0181373D02* -X0242377Y0180088D01* -X0241681Y0178409D01* -X0241681Y0176591D01* -X0242377Y0174912D01* -X0242481Y0174808D01* -X0242481Y0160097D01* -X0242039Y0159914D01* -X0241317Y0159191D01* -X0240926Y0158247D01* -X0240926Y0151713D01* -X0241317Y0150769D01* -X0242039Y0150047D01* -X0242983Y0149656D01* -X0249517Y0149656D01* -X0250461Y0150047D01* -X0251183Y0150769D01* -X0251574Y0151713D01* -X0251574Y0158247D01* -X0251183Y0159191D01* -X0250461Y0159914D01* -X0250018Y0160097D01* -X0250018Y0174808D01* -X0250123Y0174912D01* -X0250818Y0176591D01* -X0250818Y0178409D01* -X0250123Y0180088D01* -X0248838Y0181373D01* -X0247159Y0182068D01* -X0245341Y0182068D01* -X0243662Y0181373D01* -X0242248Y0179777D02* -X0230768Y0179777D01* -X0230768Y0178578D02* -X0241752Y0178578D01* -X0241681Y0177380D02* -X0230768Y0177380D01* -X0230768Y0176181D02* -X0241851Y0176181D01* -X0242348Y0174983D02* -X0230641Y0174983D01* -X0229442Y0173784D02* -X0242481Y0173784D01* -X0242481Y0172586D02* -X0228244Y0172586D01* -X0228325Y0171387D02* -X0242481Y0171387D01* -X0242481Y0170189D02* -X0229524Y0170189D01* -X0230100Y0168990D02* -X0242481Y0168990D01* -X0242481Y0167792D02* -X0225400Y0167792D01* -X0225400Y0167100D02* -X0225400Y0162400D01* -X0225400Y0157900D01* -X0224600Y0157900D01* -X0224600Y0167100D01* -X0225400Y0167100D01* -X0225400Y0166593D02* -X0224600Y0166593D01* -X0224600Y0165395D02* -X0225400Y0165395D01* -X0225400Y0164196D02* -X0224600Y0164196D01* -X0224600Y0162998D02* -X0225400Y0162998D01* -X0225400Y0161799D02* -X0224600Y0161799D01* -X0224600Y0160601D02* -X0225400Y0160601D01* -X0225400Y0159402D02* -X0224600Y0159402D01* -X0224600Y0158203D02* -X0225400Y0158203D01* -X0227913Y0161799D02* -X0234080Y0161799D01* -X0234144Y0161482D02* -X0234407Y0160847D01* -X0234789Y0160275D01* -X0235275Y0159789D01* -X0235847Y0159407D01* -X0236482Y0159144D01* -X0237156Y0159009D01* -X0237500Y0159009D01* -X0237844Y0159009D01* -X0238518Y0159144D01* -X0239153Y0159407D01* -X0239725Y0159789D01* -X0240211Y0160275D01* -X0240593Y0160847D01* -X0240856Y0161482D01* -X0240991Y0162156D01* -X0240991Y0162500D01* -X0240991Y0162844D01* -X0240856Y0163518D01* -X0240593Y0164153D01* -X0240211Y0164725D01* -X0239725Y0165211D01* -X0239153Y0165593D01* -X0238518Y0165856D01* -X0237844Y0165991D01* -X0237500Y0165991D01* -X0237156Y0165991D01* -X0236482Y0165856D01* -X0235847Y0165593D01* -X0235275Y0165211D01* -X0234789Y0164725D01* -X0234407Y0164153D01* -X0234144Y0163518D01* -X0234009Y0162844D01* -X0234009Y0162500D01* -X0234009Y0162156D01* -X0234144Y0161482D01* -X0234571Y0160601D02* -X0229112Y0160601D01* -X0230100Y0159402D02* -X0235858Y0159402D01* -X0237500Y0159402D02* -X0237500Y0159402D01* -X0237500Y0159009D02* -X0237500Y0162500D01* -X0240991Y0162500D01* -X0237500Y0162500D01* -X0237500Y0162500D01* -X0237500Y0162500D01* -X0237500Y0165991D01* -X0237500Y0162500D01* -X0237500Y0162500D01* -X0237500Y0162500D01* -X0234009Y0162500D01* -X0237500Y0162500D01* -X0237500Y0159009D01* -X0237500Y0160601D02* -X0237500Y0160601D01* -X0237500Y0161799D02* -X0237500Y0161799D01* -X0237500Y0162998D02* -X0237500Y0162998D01* -X0237500Y0164196D02* -X0237500Y0164196D01* -X0237500Y0165395D02* -X0237500Y0165395D01* -X0239451Y0165395D02* -X0242481Y0165395D01* -X0242481Y0166593D02* -X0230100Y0166593D01* -X0230100Y0165395D02* -X0235549Y0165395D01* -X0234435Y0164196D02* -X0228909Y0164196D01* -X0227710Y0162998D02* -X0234040Y0162998D01* -X0230100Y0158203D02* -X0240926Y0158203D01* -X0240926Y0157005D02* -X0230100Y0157005D01* -X0230100Y0155806D02* -X0240926Y0155806D01* -X0240926Y0154608D02* -X0229320Y0154608D01* -X0228122Y0153409D02* -X0240926Y0153409D01* -X0240926Y0152211D02* -X0228447Y0152211D01* -X0229645Y0151012D02* -X0241216Y0151012D01* -X0242602Y0149814D02* -X0230768Y0149814D01* -X0230768Y0148615D02* -X0269765Y0148615D01* -X0269529Y0148852D02* -X0270152Y0148229D01* -X0270865Y0147710D01* -X0271650Y0147310D01* -X0272489Y0147038D01* -X0273359Y0146900D01* -X0277000Y0146900D01* -X0277000Y0152000D01* -X0268209Y0152000D01* -X0268338Y0151189D01* -X0268610Y0150350D01* -X0269010Y0149565D01* -X0269529Y0148852D01* -X0268884Y0149814D02* -X0261148Y0149814D01* -X0262534Y0151012D02* -X0268395Y0151012D01* -X0268274Y0153409D02* -X0262824Y0153409D01* -X0262824Y0152211D02* -X0277000Y0152211D01* -X0277000Y0151012D02* -X0278000Y0151012D01* -X0278000Y0149814D02* -X0277000Y0149814D01* -X0277000Y0148615D02* -X0278000Y0148615D01* -X0278000Y0147417D02* -X0277000Y0147417D01* -X0271441Y0147417D02* -X0230768Y0147417D01* -X0230768Y0146218D02* -X0286132Y0146218D01* -X0286132Y0145020D02* -X0261550Y0145020D01* -X0262603Y0143821D02* -X0286132Y0143821D01* -X0286132Y0142623D02* -X0265207Y0142623D01* -X0257500Y0140020D02* -X0257500Y0140000D01* -X0252397Y0143821D02* -X0251353Y0143821D01* -X0250300Y0145020D02* -X0253450Y0145020D01* -X0253852Y0149814D02* -X0249898Y0149814D01* -X0251284Y0151012D02* -X0252466Y0151012D01* -X0252176Y0152211D02* -X0251574Y0152211D01* -X0251574Y0153409D02* -X0252176Y0153409D01* -X0252176Y0154608D02* -X0251574Y0154608D01* -X0251574Y0155806D02* -X0252176Y0155806D01* -X0252176Y0157005D02* -X0251574Y0157005D01* -X0251574Y0158203D02* -X0252176Y0158203D01* -X0252777Y0159402D02* -X0250973Y0159402D01* -X0250018Y0160601D02* -X0255271Y0160601D01* -X0256470Y0161799D02* -X0250018Y0161799D01* -X0250018Y0162998D02* -X0257668Y0162998D01* -X0258867Y0164196D02* -X0250018Y0164196D01* -X0250018Y0165395D02* -X0260065Y0165395D01* -X0263533Y0158203D02* -X0269231Y0158203D01* -X0270473Y0157005D02* -X0262824Y0157005D01* -X0262824Y0155806D02* -X0269280Y0155806D01* -X0268597Y0154608D02* -X0262824Y0154608D01* -X0269028Y0166593D02* -X0250018Y0166593D01* -X0250018Y0167792D02* -X0270227Y0167792D01* -X0284773Y0167792D02* -X0286132Y0167792D01* -X0286132Y0168990D02* -X0250018Y0168990D01* -X0250018Y0170189D02* -X0286132Y0170189D01* -X0286132Y0171387D02* -X0250018Y0171387D01* -X0250018Y0172586D02* -X0286132Y0172586D01* -X0286132Y0173784D02* -X0250018Y0173784D01* -X0250152Y0174983D02* -X0286132Y0174983D01* -X0286132Y0176181D02* -X0250649Y0176181D01* -X0250818Y0177380D02* -X0286132Y0177380D01* -X0286132Y0178578D02* -X0250748Y0178578D01* -X0250252Y0179777D02* -X0286132Y0179777D01* -X0286132Y0180975D02* -X0249236Y0180975D01* -X0253853Y0198190D02* -X0254336Y0198061D01* -X0260403Y0198061D01* -X0260403Y0201643D01* -X0261369Y0201643D01* -X0261369Y0202609D01* -X0269085Y0202609D01* -X0269085Y0204541D01* -X0268956Y0205025D01* -X0268705Y0205458D01* -X0268352Y0205812D01* -X0267918Y0206062D01* -X0267435Y0206191D01* -X0261369Y0206191D01* -X0261369Y0202609D01* -X0260403Y0202609D01* -X0260403Y0206191D01* -X0254336Y0206191D01* -X0253853Y0206062D01* -X0253420Y0205812D01* -X0253066Y0205458D01* -X0252816Y0205025D01* -X0252687Y0204541D01* -X0252687Y0202609D01* -X0260403Y0202609D01* -X0260403Y0201643D01* -X0252687Y0201643D01* -X0252687Y0199710D01* -X0252816Y0199227D01* -X0253066Y0198794D01* -X0253420Y0198440D01* -X0253853Y0198190D01* -X0252974Y0198953D02* -X0242025Y0198953D01* -X0241934Y0198794D02* -X0242184Y0199227D01* -X0242313Y0199710D01* -X0242313Y0201643D01* -X0234597Y0201643D01* -X0234597Y0198061D01* -X0240664Y0198061D01* -X0241147Y0198190D01* -X0241580Y0198440D01* -X0241934Y0198794D01* -X0242313Y0200151D02* -X0252687Y0200151D01* -X0252687Y0201350D02* -X0242313Y0201350D01* -X0242313Y0202609D02* -X0242313Y0204541D01* -X0242184Y0205025D01* -X0241934Y0205458D01* -X0241580Y0205812D01* -X0241147Y0206062D01* -X0240664Y0206191D01* -X0234597Y0206191D01* -X0234597Y0202609D01* -X0233631Y0202609D01* -X0233631Y0201643D01* -X0225915Y0201643D01* -X0225915Y0199710D01* -X0226044Y0199227D01* -X0226295Y0198794D01* -X0226648Y0198440D01* -X0227082Y0198190D01* -X0227565Y0198061D01* -X0233631Y0198061D01* -X0233631Y0201643D01* -X0234597Y0201643D01* -X0234597Y0202609D01* -X0242313Y0202609D01* -X0242313Y0203747D02* -X0252687Y0203747D01* -X0252795Y0204945D02* -X0242205Y0204945D01* -X0240840Y0206144D02* -X0254160Y0206144D01* -X0260403Y0206144D02* -X0261369Y0206144D01* -X0261369Y0204945D02* -X0260403Y0204945D01* -X0260403Y0203747D02* -X0261369Y0203747D01* -X0261369Y0202548D02* -X0286132Y0202548D01* -X0286132Y0201350D02* -X0269085Y0201350D01* -X0269085Y0201643D02* -X0269085Y0199710D01* -X0268956Y0199227D01* -X0268705Y0198794D01* -X0268352Y0198440D01* -X0267918Y0198190D01* -X0267435Y0198061D01* -X0261369Y0198061D01* -X0261369Y0201643D01* -X0269085Y0201643D01* -X0270000Y0202126D02* -X0275000Y0207126D01* -X0275000Y0207500D01* -X0274400Y0207342D02* -X0275600Y0207342D01* -X0275600Y0207963D02* -X0275600Y0204104D01* -X0278400Y0204104D01* -X0278883Y0204233D01* -X0279316Y0204484D01* -X0279670Y0204837D01* -X0279920Y0205271D01* -X0280050Y0205754D01* -X0280050Y0207963D01* -X0275600Y0207963D01* -X0275600Y0208541D02* -X0286132Y0208541D01* -X0286132Y0209739D02* -X0280050Y0209739D01* -X0280050Y0210938D02* -X0286132Y0210938D01* -X0286132Y0212137D02* -X0280041Y0212137D01* -X0280705Y0213335D02* -X0286132Y0213335D01* -X0286132Y0214534D02* -X0280718Y0214534D01* -X0280718Y0215732D02* -X0286132Y0215732D01* -X0286132Y0216931D02* -X0280718Y0216931D01* -X0280718Y0218129D02* -X0286132Y0218129D01* -X0286132Y0219328D02* -X0280718Y0219328D01* -X0280252Y0220526D02* -X0286132Y0220526D01* -X0286132Y0221725D02* -X0269132Y0221725D01* -X0275000Y0217874D02* -X0260886Y0217874D01* -X0234114Y0217874D01* -X0234114Y0222500D01* -X0190000Y0222500D01* -X0185000Y0217500D01* -X0180000Y0212500D01* -X0180000Y0192500D01* -X0175000Y0187500D01* -X0127500Y0187500D01* -X0122500Y0182500D01* -X0122500Y0157500D01* -X0117500Y0152500D01* -X0107421Y0152500D01* -X0103484Y0148563D01* -X0103484Y0117441D01* -X0101516Y0117441D02* -X0101516Y0151063D01* -X0105453Y0155000D01* -X0113750Y0155000D01* -X0118750Y0160000D01* -X0118750Y0187500D01* -X0123750Y0192500D01* -X0146902Y0192500D01* -X0151902Y0197500D01* -X0151902Y0205000D01* -X0146183Y0204945D02* -X0143817Y0204945D01* -X0143817Y0203747D02* -X0146183Y0203747D01* -X0146183Y0202548D02* -X0143817Y0202548D01* -X0143817Y0201350D02* -X0146183Y0201350D01* -X0146510Y0200151D02* -X0143490Y0200151D01* -X0141929Y0198953D02* -X0148071Y0198953D01* -X0147675Y0197754D02* -X0130070Y0197754D01* -X0129940Y0196556D02* -X0146476Y0196556D01* -X0150644Y0191762D02* -X0168696Y0191762D01* -X0168982Y0191644D02* -X0169656Y0191509D01* -X0170000Y0191509D01* -X0170344Y0191509D01* -X0171018Y0191644D01* -X0171653Y0191907D01* -X0172225Y0192289D01* -X0172711Y0192775D01* -X0173093Y0193347D01* -X0173356Y0193982D01* -X0173491Y0194656D01* -X0173491Y0195000D01* -X0173491Y0195344D01* -X0173356Y0196018D01* -X0173093Y0196653D01* -X0172711Y0197225D01* -X0172225Y0197711D01* -X0171653Y0198093D01* -X0171018Y0198356D01* -X0170344Y0198491D01* -X0170000Y0198491D01* -X0169656Y0198491D01* -X0168982Y0198356D01* -X0168347Y0198093D01* -X0167775Y0197711D01* -X0167289Y0197225D01* -X0166907Y0196653D01* -X0166644Y0196018D01* -X0166509Y0195344D01* -X0166509Y0195000D01* -X0166509Y0194656D01* -X0166644Y0193982D01* -X0166907Y0193347D01* -X0167289Y0192775D01* -X0167775Y0192289D01* -X0168347Y0191907D01* -X0168982Y0191644D01* -X0170000Y0191762D02* -X0170000Y0191762D01* -X0170000Y0191509D02* -X0170000Y0195000D01* -X0173491Y0195000D01* -X0170000Y0195000D01* -X0170000Y0195000D01* -X0170000Y0195000D01* -X0170000Y0198491D01* -X0170000Y0195000D01* -X0170000Y0195000D01* -X0170000Y0195000D01* -X0166509Y0195000D01* -X0170000Y0195000D01* -X0170000Y0191509D01* -X0171304Y0191762D02* -X0174781Y0191762D01* -X0175979Y0192960D02* -X0172835Y0192960D01* -X0173392Y0194159D02* -X0176831Y0194159D01* -X0176831Y0195357D02* -X0173488Y0195357D01* -X0173134Y0196556D02* -X0176831Y0196556D01* -X0176831Y0197754D02* -X0172161Y0197754D01* -X0170000Y0197754D02* -X0170000Y0197754D01* -X0170000Y0196556D02* -X0170000Y0196556D01* -X0170000Y0195357D02* -X0170000Y0195357D01* -X0170000Y0194159D02* -X0170000Y0194159D01* -X0170000Y0192960D02* -X0170000Y0192960D01* -X0167165Y0192960D02* -X0151843Y0192960D01* -X0153041Y0194159D02* -X0166608Y0194159D01* -X0166512Y0195357D02* -X0154240Y0195357D01* -X0154940Y0196556D02* -X0166866Y0196556D01* -X0167839Y0197754D02* -X0155070Y0197754D01* -X0155733Y0198953D02* -X0159267Y0198953D01* -X0157707Y0200151D02* -X0157293Y0200151D01* -X0163098Y0205000D02* -X0163098Y0217500D01* -X0161240Y0217500D01* -X0166267Y0213335D02* -X0176916Y0213335D01* -X0176831Y0212137D02* -X0166267Y0212137D01* -X0167193Y0210938D02* -X0176831Y0210938D01* -X0176831Y0209739D02* -X0168535Y0209739D01* -X0168817Y0208541D02* -X0176831Y0208541D01* -X0176831Y0207342D02* -X0168817Y0207342D01* -X0168817Y0206144D02* -X0176831Y0206144D01* -X0176831Y0204945D02* -X0168817Y0204945D01* -X0168817Y0203747D02* -X0176831Y0203747D01* -X0176831Y0202548D02* -X0168817Y0202548D01* -X0168817Y0201350D02* -X0176831Y0201350D01* -X0176831Y0200151D02* -X0168490Y0200151D01* -X0166929Y0198953D02* -X0176831Y0198953D01* -X0183168Y0198953D02* -X0186831Y0198953D01* -X0186831Y0200151D02* -X0183168Y0200151D01* -X0183168Y0201350D02* -X0186831Y0201350D01* -X0186831Y0202400D02* -X0186831Y0161312D01* -X0184455Y0158936D01* -X0184455Y0158936D01* -X0183564Y0158045D01* -X0183081Y0156880D01* -X0183081Y0147562D01* -X0182039Y0146520D01* -X0182068Y0146591D01* -X0182068Y0148409D01* -X0181373Y0150088D01* -X0180088Y0151373D01* -X0178409Y0152068D01* -X0176591Y0152068D01* -X0174912Y0151373D01* -X0174208Y0150668D01* -X0120149Y0150668D01* -X0120186Y0150705D01* -X0125186Y0155705D01* -X0125668Y0156870D01* -X0125668Y0181188D01* -X0128812Y0184331D01* -X0175630Y0184331D01* -X0176795Y0184814D01* -X0177686Y0185705D01* -X0182686Y0190705D01* -X0183168Y0191870D01* -X0183168Y0202400D01* -X0184600Y0202400D01* -X0184600Y0207100D01* -X0185400Y0207100D01* -X0185400Y0202400D01* -X0186831Y0202400D01* -X0185400Y0202548D02* -X0184600Y0202548D01* -X0184600Y0203747D02* -X0185400Y0203747D01* -X0185400Y0204945D02* -X0184600Y0204945D01* -X0184600Y0206144D02* -X0185400Y0206144D01* -X0190000Y0212500D02* -X0190000Y0160000D01* -X0186250Y0156250D01* -X0186250Y0146250D01* -X0182500Y0142500D01* -X0115295Y0142500D01* -X0111358Y0138563D01* -X0111358Y0117441D01* -X0105453Y0117441D02* -X0105453Y0143563D01* -X0109390Y0147500D01* -X0177500Y0147500D01* -X0182068Y0147417D02* -X0182936Y0147417D01* -X0183081Y0148615D02* -X0181983Y0148615D01* -X0181486Y0149814D02* -X0183081Y0149814D01* -X0183081Y0151012D02* -X0180448Y0151012D01* -X0183081Y0152211D02* -X0121692Y0152211D01* -X0122890Y0153409D02* -X0183081Y0153409D01* -X0183081Y0154608D02* -X0124089Y0154608D01* -X0125228Y0155806D02* -X0183081Y0155806D01* -X0183133Y0157005D02* -X0125668Y0157005D01* -X0125668Y0158203D02* -X0183723Y0158203D01* -X0184921Y0159402D02* -X0125668Y0159402D01* -X0125668Y0160601D02* -X0186120Y0160601D01* -X0186831Y0161799D02* -X0125668Y0161799D01* -X0125668Y0162998D02* -X0186831Y0162998D01* -X0186831Y0164196D02* -X0125668Y0164196D01* -X0125668Y0165395D02* -X0186831Y0165395D01* -X0186831Y0166593D02* -X0125668Y0166593D01* -X0125668Y0167792D02* -X0186831Y0167792D01* -X0186831Y0168990D02* -X0125668Y0168990D01* -X0125668Y0170189D02* -X0186831Y0170189D01* -X0186831Y0171387D02* -X0125668Y0171387D01* -X0125668Y0172586D02* -X0149978Y0172586D01* -X0149789Y0172775D02* -X0150275Y0172289D01* -X0150847Y0171907D01* -X0151482Y0171644D01* -X0152156Y0171509D01* -X0152500Y0171509D01* -X0152844Y0171509D01* -X0153518Y0171644D01* -X0154153Y0171907D01* -X0154725Y0172289D01* -X0155211Y0172775D01* -X0155593Y0173347D01* -X0155856Y0173982D01* -X0155991Y0174656D01* -X0155991Y0175000D01* -X0155991Y0175344D01* -X0155856Y0176018D01* -X0155593Y0176653D01* -X0155211Y0177225D01* -X0154725Y0177711D01* -X0154153Y0178093D01* -X0153518Y0178356D01* -X0152844Y0178491D01* -X0152500Y0178491D01* -X0152156Y0178491D01* -X0151482Y0178356D01* -X0150847Y0178093D01* -X0150275Y0177711D01* -X0149789Y0177225D01* -X0149407Y0176653D01* -X0149144Y0176018D01* -X0149009Y0175344D01* -X0149009Y0175000D01* -X0149009Y0174656D01* -X0149144Y0173982D01* -X0149407Y0173347D01* -X0149789Y0172775D01* -X0149225Y0173784D02* -X0125668Y0173784D01* -X0125668Y0174983D02* -X0149009Y0174983D01* -X0149009Y0175000D02* -X0152500Y0175000D01* -X0155991Y0175000D01* -X0152500Y0175000D01* -X0152500Y0175000D01* -X0152500Y0175000D01* -X0152500Y0178491D01* -X0152500Y0175000D01* -X0152500Y0171509D01* -X0152500Y0175000D01* -X0152500Y0175000D01* -X0152500Y0175000D01* -X0149009Y0175000D01* -X0149211Y0176181D02* -X0125668Y0176181D01* -X0125668Y0177380D02* -X0149943Y0177380D01* -X0152500Y0177380D02* -X0152500Y0177380D01* -X0152500Y0176181D02* -X0152500Y0176181D01* -X0152500Y0174983D02* -X0152500Y0174983D01* -X0152500Y0173784D02* -X0152500Y0173784D01* -X0152500Y0172586D02* -X0152500Y0172586D01* -X0155022Y0172586D02* -X0186831Y0172586D01* -X0186831Y0173784D02* -X0155775Y0173784D01* -X0155991Y0174983D02* -X0186831Y0174983D01* -X0186831Y0176181D02* -X0155789Y0176181D01* -X0155057Y0177380D02* -X0186831Y0177380D01* -X0186831Y0178578D02* -X0125668Y0178578D01* -X0125668Y0179777D02* -X0186831Y0179777D01* -X0186831Y0180975D02* -X0125668Y0180975D01* -X0126655Y0182174D02* -X0186831Y0182174D01* -X0186831Y0183372D02* -X0127853Y0183372D01* -X0114402Y0192500D02* -X0119402Y0197500D01* -X0126902Y0197500D01* -X0126902Y0205000D01* -X0121183Y0204945D02* -X0118817Y0204945D01* -X0118817Y0203747D02* -X0121183Y0203747D01* -X0121183Y0202548D02* -X0118817Y0202548D01* -X0118817Y0201350D02* -X0121183Y0201350D01* -X0121183Y0206144D02* -X0118817Y0206144D01* -X0118817Y0207342D02* -X0121183Y0207342D01* -X0121183Y0208541D02* -X0118817Y0208541D01* -X0118535Y0209739D02* -X0121465Y0209739D01* -X0122807Y0210938D02* -X0117193Y0210938D01* -X0116267Y0212137D02* -X0132789Y0212137D01* -X0134004Y0210938D02* -X0130996Y0210938D01* -X0132338Y0209739D02* -X0132662Y0209739D01* -X0128160Y0212647D02* -X0128160Y0216900D01* -X0124301Y0216900D01* -X0124301Y0214297D01* -X0124430Y0213814D01* -X0124680Y0213381D01* -X0125034Y0213027D01* -X0125467Y0212777D01* -X0125951Y0212647D01* -X0128160Y0212647D01* -X0128160Y0213335D02* -X0129360Y0213335D01* -X0129360Y0214534D02* -X0128160Y0214534D01* -X0128160Y0215732D02* -X0129360Y0215732D01* -X0128160Y0216931D02* -X0116368Y0216931D01* -X0116368Y0218129D02* -X0124301Y0218129D01* -X0124301Y0219328D02* -X0116368Y0219328D01* -X0116368Y0220526D02* -X0124301Y0220526D01* -X0124786Y0221725D02* -X0116053Y0221725D01* -X0114547Y0222923D02* -X0132933Y0222923D01* -X0129360Y0221725D02* -X0128160Y0221725D01* -X0128160Y0220526D02* -X0129360Y0220526D01* -X0129360Y0219328D02* -X0128160Y0219328D01* -X0128160Y0218129D02* -X0129360Y0218129D01* -X0124301Y0215732D02* -X0116368Y0215732D01* -X0116368Y0214534D02* -X0124301Y0214534D01* -X0124726Y0213335D02* -X0116267Y0213335D01* -X0113098Y0217500D02* -X0111240Y0217500D01* -X0113098Y0217500D02* -X0113098Y0205000D01* -X0107662Y0209739D02* -X0107338Y0209739D01* -X0105996Y0210938D02* -X0109004Y0210938D01* -X0107789Y0212137D02* -X0082344Y0212137D01* -X0081146Y0213335D02* -X0099726Y0213335D01* -X0099680Y0213381D02* -X0100034Y0213027D01* -X0100467Y0212777D01* -X0100951Y0212647D01* -X0103160Y0212647D01* -X0103160Y0216900D01* -X0099301Y0216900D01* -X0099301Y0214297D01* -X0099430Y0213814D01* -X0099680Y0213381D01* -X0099301Y0214534D02* -X0079947Y0214534D01* -X0077500Y0212500D02* -X0082500Y0207500D01* -X0082500Y0205000D01* -X0101902Y0205000D01* -X0096183Y0201831D02* -X0096183Y0200940D01* -X0096296Y0200668D01* -X0053812Y0200668D01* -X0052081Y0202400D01* -X0052100Y0202400D01* -X0052100Y0207100D01* -X0052900Y0207100D01* -X0052900Y0207900D01* -X0057600Y0207900D01* -X0062100Y0207900D01* -X0062100Y0207100D01* -X0052900Y0207100D01* -X0052900Y0202400D01* -X0054612Y0202400D01* -X0057500Y0205288D01* -X0060388Y0202400D01* -X0062100Y0202400D01* -X0062100Y0207100D01* -X0062900Y0207100D01* -X0062900Y0207900D01* -X0067600Y0207900D01* -X0067600Y0209331D01* -X0076188Y0209331D01* -X0079331Y0206188D01* -X0079331Y0204370D01* -X0079814Y0203205D01* -X0080705Y0202314D01* -X0081870Y0201831D01* -X0096183Y0201831D01* -X0096183Y0201350D02* -X0053131Y0201350D01* -X0052900Y0202548D02* -X0052100Y0202548D01* -X0052100Y0203747D02* -X0052900Y0203747D01* -X0052900Y0204945D02* -X0052100Y0204945D01* -X0052100Y0206144D02* -X0052900Y0206144D01* -X0052900Y0207342D02* -X0062100Y0207342D01* -X0062900Y0207342D02* -X0078177Y0207342D01* -X0079331Y0206144D02* -X0067600Y0206144D01* -X0067600Y0205388D02* -X0067600Y0207100D01* -X0062900Y0207100D01* -X0062900Y0202400D01* -X0064612Y0202400D01* -X0067600Y0205388D01* -X0067158Y0204945D02* -X0079331Y0204945D01* -X0079589Y0203747D02* -X0065959Y0203747D01* -X0064761Y0202548D02* -X0080471Y0202548D01* -X0076978Y0208541D02* -X0067600Y0208541D01* -X0062900Y0206144D02* -X0062100Y0206144D01* -X0062100Y0204945D02* -X0062900Y0204945D01* -X0062900Y0203747D02* -X0062100Y0203747D01* -X0062100Y0202548D02* -X0062900Y0202548D01* -X0060239Y0202548D02* -X0054761Y0202548D01* -X0055959Y0203747D02* -X0059041Y0203747D01* -X0057842Y0204945D02* -X0057158Y0204945D01* -X0057500Y0212500D02* -X0077500Y0212500D01* -X0083543Y0210938D02* -X0097807Y0210938D01* -X0096465Y0209739D02* -X0084741Y0209739D01* -X0085498Y0208541D02* -X0096183Y0208541D01* -X0103160Y0213335D02* -X0104360Y0213335D01* -X0104360Y0214534D02* -X0103160Y0214534D01* -X0103160Y0215732D02* -X0104360Y0215732D01* -X0103160Y0216931D02* -X0068268Y0216931D01* -X0068268Y0218129D02* -X0099301Y0218129D01* -X0099301Y0219328D02* -X0068268Y0219328D01* -X0067632Y0220526D02* -X0099301Y0220526D01* -X0099786Y0221725D02* -X0066433Y0221725D01* -X0065235Y0222923D02* -X0107933Y0222923D01* -X0104360Y0221725D02* -X0103160Y0221725D01* -X0103160Y0220526D02* -X0104360Y0220526D01* -X0104360Y0219328D02* -X0103160Y0219328D01* -X0103160Y0218129D02* -X0104360Y0218129D01* -X0099301Y0215732D02* -X0068268Y0215732D01* -X0062500Y0217500D02* -X0055000Y0225000D01* -X0016437Y0225000D01* -X0012500Y0221063D01* -X0012500Y0092500D01* -X0017500Y0087500D01* -X0027500Y0087500D01* -X0027500Y0078740D01* -X0021979Y0079102D02* -X0002568Y0079102D01* -X0002568Y0080300D02* -X0021979Y0080300D01* -X0021979Y0081499D02* -X0002568Y0081499D01* -X0002568Y0082697D02* -X0022346Y0082697D01* -X0024331Y0083896D02* -X0002568Y0083896D01* -X0002568Y0085094D02* -X0015425Y0085094D01* -X0014226Y0086293D02* -X0002568Y0086293D01* -X0002568Y0087491D02* -X0013028Y0087491D01* -X0011829Y0088690D02* -X0002568Y0088690D01* -X0002568Y0089888D02* -X0010631Y0089888D01* -X0009656Y0091087D02* -X0002568Y0091087D01* -X0002568Y0092285D02* -X0009331Y0092285D01* -X0009331Y0093484D02* -X0002568Y0093484D01* -X0002568Y0094682D02* -X0009331Y0094682D01* -X0009331Y0095881D02* -X0002568Y0095881D01* -X0002568Y0097079D02* -X0009331Y0097079D01* -X0009331Y0098278D02* -X0002568Y0098278D01* -X0002568Y0099476D02* -X0009331Y0099476D01* -X0009331Y0100675D02* -X0002568Y0100675D01* -X0002568Y0101873D02* -X0009331Y0101873D01* -X0009331Y0103072D02* -X0002568Y0103072D01* -X0002568Y0104270D02* -X0009331Y0104270D01* -X0009331Y0105469D02* -X0002568Y0105469D01* -X0002568Y0106668D02* -X0009331Y0106668D01* -X0009331Y0107866D02* -X0002568Y0107866D01* -X0002568Y0109065D02* -X0009331Y0109065D01* -X0009331Y0110263D02* -X0002568Y0110263D01* -X0002568Y0111462D02* -X0009331Y0111462D01* -X0009331Y0112660D02* -X0002568Y0112660D01* -X0002568Y0113859D02* -X0009331Y0113859D01* -X0009331Y0115057D02* -X0002568Y0115057D01* -X0002568Y0116256D02* -X0009331Y0116256D01* -X0009331Y0117454D02* -X0002568Y0117454D01* -X0002568Y0118653D02* -X0009331Y0118653D01* -X0009331Y0119851D02* -X0002568Y0119851D01* -X0002568Y0121050D02* -X0009331Y0121050D01* -X0009331Y0122248D02* -X0002568Y0122248D01* -X0002568Y0123447D02* -X0009331Y0123447D01* -X0009331Y0124645D02* -X0002568Y0124645D01* -X0002568Y0125844D02* -X0009331Y0125844D01* -X0009331Y0127042D02* -X0002568Y0127042D01* -X0002568Y0128241D02* -X0009331Y0128241D01* -X0009331Y0129439D02* -X0002568Y0129439D01* -X0002568Y0130638D02* -X0009331Y0130638D01* -X0009331Y0131836D02* -X0002568Y0131836D01* -X0002568Y0133035D02* -X0009331Y0133035D01* -X0009331Y0134233D02* -X0002568Y0134233D01* -X0002568Y0135432D02* -X0009331Y0135432D01* -X0009331Y0136630D02* -X0002568Y0136630D01* -X0002568Y0137829D02* -X0009331Y0137829D01* -X0009331Y0139027D02* -X0002568Y0139027D01* -X0002568Y0140226D02* -X0009331Y0140226D01* -X0009331Y0141424D02* -X0002568Y0141424D01* -X0002568Y0142623D02* -X0009331Y0142623D01* -X0009331Y0143821D02* -X0002568Y0143821D01* -X0002568Y0145020D02* -X0009331Y0145020D01* -X0009331Y0146218D02* -X0002568Y0146218D01* -X0002568Y0147417D02* -X0009331Y0147417D01* -X0009331Y0148615D02* -X0002568Y0148615D01* -X0002568Y0149814D02* -X0009331Y0149814D01* -X0009331Y0151012D02* -X0002568Y0151012D01* -X0002568Y0152211D02* -X0009331Y0152211D01* -X0009331Y0153409D02* -X0002568Y0153409D01* -X0002568Y0154608D02* -X0009331Y0154608D01* -X0009331Y0155806D02* -X0002568Y0155806D01* -X0002568Y0157005D02* -X0009331Y0157005D01* -X0009331Y0158203D02* -X0002568Y0158203D01* -X0002568Y0159402D02* -X0009331Y0159402D01* -X0009331Y0160601D02* -X0002568Y0160601D01* -X0002568Y0161799D02* -X0009331Y0161799D01* -X0009331Y0162998D02* -X0002568Y0162998D01* -X0002568Y0164196D02* -X0009331Y0164196D01* -X0009331Y0165395D02* -X0002568Y0165395D01* -X0002568Y0166593D02* -X0009331Y0166593D01* -X0009331Y0167792D02* -X0002568Y0167792D01* -X0002568Y0168990D02* -X0009331Y0168990D01* -X0009331Y0170189D02* -X0002568Y0170189D01* -X0002568Y0171387D02* -X0009331Y0171387D01* -X0009331Y0172586D02* -X0002568Y0172586D01* -X0002568Y0173784D02* -X0009331Y0173784D01* -X0009331Y0174983D02* -X0002568Y0174983D01* -X0002568Y0176181D02* -X0009331Y0176181D01* -X0009331Y0177380D02* -X0002568Y0177380D01* -X0002568Y0178578D02* -X0009331Y0178578D01* -X0009331Y0179777D02* -X0002568Y0179777D01* -X0002568Y0180975D02* -X0009331Y0180975D01* -X0009331Y0182174D02* -X0002568Y0182174D01* -X0002568Y0183372D02* -X0009331Y0183372D01* -X0009331Y0184571D02* -X0002568Y0184571D01* -X0002568Y0185769D02* -X0009331Y0185769D01* -X0009331Y0186968D02* -X0002568Y0186968D01* -X0002568Y0188166D02* -X0009331Y0188166D01* -X0009331Y0189365D02* -X0002568Y0189365D01* -X0002568Y0190563D02* -X0009331Y0190563D01* -X0009331Y0191762D02* -X0002568Y0191762D01* -X0002568Y0192960D02* -X0009331Y0192960D01* -X0009331Y0194159D02* -X0002568Y0194159D01* -X0002568Y0195357D02* -X0009331Y0195357D01* -X0009331Y0196556D02* -X0002568Y0196556D01* -X0002568Y0197754D02* -X0009331Y0197754D01* -X0009331Y0198953D02* -X0002568Y0198953D01* -X0002568Y0200151D02* -X0009331Y0200151D01* -X0009331Y0201350D02* -X0002568Y0201350D01* -X0002568Y0202548D02* -X0009331Y0202548D01* -X0009331Y0203747D02* -X0002568Y0203747D01* -X0002568Y0204945D02* -X0009331Y0204945D01* -X0009331Y0206144D02* -X0002568Y0206144D01* -X0002568Y0207342D02* -X0009331Y0207342D01* -X0009331Y0208541D02* -X0002568Y0208541D01* -X0002568Y0209739D02* -X0009331Y0209739D01* -X0009331Y0210938D02* -X0002568Y0210938D01* -X0002568Y0212137D02* -X0009331Y0212137D01* -X0009331Y0213335D02* -X0002568Y0213335D01* -X0002568Y0214534D02* -X0009331Y0214534D01* -X0009331Y0215732D02* -X0002568Y0215732D01* -X0002568Y0216931D02* -X0009331Y0216931D01* -X0009331Y0218129D02* -X0002568Y0218129D01* -X0002568Y0219328D02* -X0009331Y0219328D01* -X0009331Y0220526D02* -X0002568Y0220526D01* -X0002568Y0221725D02* -X0009344Y0221725D01* -X0009879Y0222923D02* -X0002568Y0222923D01* -X0002568Y0224122D02* -X0011078Y0224122D01* -X0012276Y0225320D02* -X0002568Y0225320D01* -X0002568Y0226519D02* -X0013475Y0226519D01* -X0018937Y0222500D02* -X0015000Y0218563D01* -X0015000Y0095000D01* -X0020000Y0090000D01* -X0068563Y0090000D01* -X0070610Y0092047D01* -X0080059Y0092047D01* -X0080059Y0090079D02* -X0072579Y0090079D01* -X0070000Y0087500D01* -X0027500Y0087500D01* -X0030668Y0084331D02* -X0057079Y0084331D01* -X0056525Y0083961D01* -X0056039Y0083475D01* -X0055657Y0082903D01* -X0055394Y0082268D01* -X0055259Y0081594D01* -X0055259Y0081250D01* -X0055259Y0080906D01* -X0055394Y0080232D01* -X0055657Y0079597D01* -X0056039Y0079025D01* -X0056525Y0078539D01* -X0057097Y0078157D01* -X0057732Y0077894D01* -X0058406Y0077759D01* -X0058750Y0077759D01* -X0059094Y0077759D01* -X0059768Y0077894D01* -X0060403Y0078157D01* -X0060975Y0078539D01* -X0061461Y0079025D01* -X0061843Y0079597D01* -X0062106Y0080232D01* -X0062241Y0080906D01* -X0062241Y0081250D01* -X0062241Y0081594D01* -X0062106Y0082268D01* -X0061843Y0082903D01* -X0061461Y0083475D01* -X0060975Y0083961D01* -X0060421Y0084331D01* -X0069919Y0084331D01* -X0063747Y0078218D01* -X0060867Y0078218D01* -X0059923Y0077827D01* -X0059457Y0077361D01* -X0059355Y0077420D01* -X0058872Y0077550D01* -X0056663Y0077550D01* -X0056663Y0073100D01* -X0055463Y0073100D01* -X0055463Y0077550D01* -X0053254Y0077550D01* -X0052771Y0077420D01* -X0052337Y0077170D01* -X0051984Y0076816D01* -X0051733Y0076383D01* -X0051604Y0075900D01* -X0051604Y0073100D01* -X0055463Y0073100D01* -X0055463Y0071900D01* -X0056663Y0071900D01* -X0056663Y0067450D01* -X0058872Y0067450D01* -X0059355Y0067580D01* -X0059457Y0067639D01* -X0059923Y0067173D01* -X0060769Y0066823D01* -X0060769Y0062872D01* -X0060327Y0062689D01* -X0059604Y0061967D01* -X0059417Y0061515D01* -X0032749Y0061515D01* -X0030668Y0063596D01* -X0030668Y0066132D01* -X0030964Y0066132D01* -X0031908Y0066523D01* -X0032630Y0067246D01* -X0033021Y0068190D01* -X0033021Y0074330D01* -X0032744Y0075000D01* -X0033021Y0075670D01* -X0033021Y0081810D01* -X0032630Y0082754D01* -X0031908Y0083477D01* -X0030964Y0083868D01* -X0030668Y0083868D01* -X0030668Y0084331D01* -X0030668Y0083896D02* -X0056459Y0083896D01* -X0055571Y0082697D02* -X0032654Y0082697D01* -X0033021Y0081499D02* -X0055259Y0081499D01* -X0055259Y0081250D02* -X0058750Y0081250D01* -X0062241Y0081250D01* -X0058750Y0081250D01* -X0058750Y0081250D01* -X0058750Y0077759D01* -X0058750Y0081250D01* -X0058750Y0081250D01* -X0058750Y0081250D01* -X0055259Y0081250D01* -X0055380Y0080300D02* -X0033021Y0080300D01* -X0033021Y0079102D02* -X0055987Y0079102D01* -X0057709Y0077903D02* -X0033021Y0077903D01* -X0033021Y0076705D02* -X0051919Y0076705D01* -X0051604Y0075506D02* -X0032953Y0075506D01* -X0033021Y0074308D02* -X0051604Y0074308D01* -X0051604Y0073109D02* -X0033021Y0073109D01* -X0033021Y0071911D02* -X0055463Y0071911D01* -X0055463Y0071900D02* -X0051604Y0071900D01* -X0051604Y0069100D01* -X0051733Y0068617D01* -X0051984Y0068184D01* -X0052337Y0067830D01* -X0052771Y0067580D01* -X0053254Y0067450D01* -X0055463Y0067450D01* -X0055463Y0071900D01* -X0055463Y0070712D02* -X0056663Y0070712D01* -X0056663Y0069514D02* -X0055463Y0069514D01* -X0055463Y0068315D02* -X0056663Y0068315D01* -X0060059Y0067117D02* -X0032501Y0067117D01* -X0033021Y0068315D02* -X0051908Y0068315D01* -X0051604Y0069514D02* -X0033021Y0069514D01* -X0033021Y0070712D02* -X0051604Y0070712D01* -X0055463Y0073109D02* -X0056663Y0073109D01* -X0056663Y0074308D02* -X0055463Y0074308D01* -X0055463Y0075506D02* -X0056663Y0075506D01* -X0056663Y0076705D02* -X0055463Y0076705D01* -X0058750Y0077903D02* -X0058750Y0077903D01* -X0059791Y0077903D02* -X0060107Y0077903D01* -X0058750Y0079102D02* -X0058750Y0079102D01* -X0058750Y0080300D02* -X0058750Y0080300D01* -X0061513Y0079102D02* -X0064639Y0079102D01* -X0065849Y0080300D02* -X0062120Y0080300D01* -X0062241Y0081499D02* -X0067059Y0081499D01* -X0068269Y0082697D02* -X0061929Y0082697D01* -X0062500Y0083750D02* -X0070000Y0083750D01* -X0074360Y0088110D01* -X0080059Y0088110D01* -X0080059Y0086142D02* -X0076250Y0086142D01* -X0065000Y0075000D01* -X0063937Y0072500D01* -X0063937Y0060846D01* -X0063750Y0058346D01* -X0031437Y0058346D01* -X0027500Y0062283D01* -X0027500Y0071260D01* -X0021979Y0070712D02* -X0002568Y0070712D01* -X0002568Y0069514D02* -X0021979Y0069514D01* -X0021979Y0068315D02* -X0002568Y0068315D01* -X0002568Y0067117D02* -X0022499Y0067117D01* -X0024331Y0065918D02* -X0002568Y0065918D01* -X0002568Y0064720D02* -X0024331Y0064720D01* -X0024331Y0063521D02* -X0002568Y0063521D01* -X0002568Y0062323D02* -X0024331Y0062323D01* -X0024551Y0061124D02* -X0002568Y0061124D01* -X0002568Y0059926D02* -X0025377Y0059926D01* -X0026575Y0058727D02* -X0002568Y0058727D01* -X0002568Y0057529D02* -X0027774Y0057529D01* -X0028972Y0056330D02* -X0002568Y0056330D01* -X0002568Y0055132D02* -X0059436Y0055132D01* -X0059213Y0053933D02* -X0002568Y0053933D01* -X0002568Y0052734D02* -X0059213Y0052734D01* -X0059213Y0051536D02* -X0002568Y0051536D01* -X0002568Y0050337D02* -X0059213Y0050337D01* -X0059213Y0049139D02* -X0002568Y0049139D01* -X0002568Y0047940D02* -X0059697Y0047940D01* -X0063750Y0051654D02* -X0082500Y0051654D01* -X0086437Y0055591D01* -X0086437Y0060000D01* -X0087736Y0060000D01* -X0091673Y0063937D01* -X0091673Y0072559D01* -X0089705Y0072559D02* -X0089705Y0065000D01* -X0077500Y0065000D01* -X0077500Y0060000D01* -X0077963Y0059926D02* -X0068287Y0059926D01* -X0068287Y0061023D02* -X0067896Y0061967D01* -X0067173Y0062689D01* -X0067105Y0062717D01* -X0067105Y0066823D01* -X0067951Y0067173D01* -X0068674Y0067895D01* -X0069065Y0068839D01* -X0069065Y0074566D01* -X0073681Y0079139D01* -X0073681Y0076989D01* -X0074073Y0076045D01* -X0074795Y0075323D01* -X0075739Y0074931D01* -X0076181Y0074931D01* -X0076181Y0070739D01* -X0076573Y0069795D01* -X0077295Y0069073D01* -X0078239Y0068681D01* -X0085029Y0068681D01* -X0085691Y0068019D01* -X0086635Y0067628D01* -X0088505Y0067628D01* -X0088505Y0065718D01* -X0083367Y0065718D01* -X0082423Y0065327D01* -X0081957Y0064861D01* -X0081855Y0064920D01* -X0081372Y0065050D01* -X0079163Y0065050D01* -X0079163Y0060600D01* -X0077963Y0060600D01* -X0077963Y0065050D01* -X0075754Y0065050D01* -X0075271Y0064920D01* -X0074837Y0064670D01* -X0074484Y0064316D01* -X0074233Y0063883D01* -X0074104Y0063400D01* -X0074104Y0060600D01* -X0077963Y0060600D01* -X0077963Y0059400D01* -X0079163Y0059400D01* -X0079163Y0054950D01* -X0081316Y0054950D01* -X0081188Y0054822D01* -X0068083Y0054822D01* -X0068009Y0055000D01* -X0068287Y0055670D01* -X0068287Y0061023D01* -X0068245Y0061124D02* -X0074104Y0061124D01* -X0074104Y0062323D02* -X0067540Y0062323D01* -X0067105Y0063521D02* -X0074136Y0063521D01* -X0073518Y0064144D02* -X0074153Y0064407D01* -X0074725Y0064789D01* -X0075211Y0065275D01* -X0075593Y0065847D01* -X0075856Y0066482D01* -X0075991Y0067156D01* -X0075991Y0067500D01* -X0075991Y0067844D01* -X0075856Y0068518D01* -X0075593Y0069153D01* -X0075211Y0069725D01* -X0074725Y0070211D01* -X0074153Y0070593D01* -X0073518Y0070856D01* -X0072844Y0070991D01* -X0072500Y0070991D01* -X0072156Y0070991D01* -X0071482Y0070856D01* -X0070847Y0070593D01* -X0070275Y0070211D01* -X0069789Y0069725D01* -X0069407Y0069153D01* -X0069144Y0068518D01* -X0069009Y0067844D01* -X0069009Y0067500D01* -X0069009Y0067156D01* -X0069144Y0066482D01* -X0069407Y0065847D01* -X0069789Y0065275D01* -X0070275Y0064789D01* -X0070847Y0064407D01* -X0071482Y0064144D01* -X0072156Y0064009D01* -X0072500Y0064009D01* -X0072844Y0064009D01* -X0073518Y0064144D01* -X0072500Y0064009D02* -X0072500Y0067500D01* -X0075991Y0067500D01* -X0072500Y0067500D01* -X0072500Y0067500D01* -X0072500Y0067500D01* -X0072500Y0070991D01* -X0072500Y0067500D01* -X0072500Y0067500D01* -X0072500Y0067500D01* -X0069009Y0067500D01* -X0072500Y0067500D01* -X0072500Y0064009D01* -X0072500Y0064720D02* -X0072500Y0064720D01* -X0072500Y0065918D02* -X0072500Y0065918D01* -X0072500Y0067117D02* -X0072500Y0067117D01* -X0072500Y0068315D02* -X0072500Y0068315D01* -X0072500Y0069514D02* -X0072500Y0069514D01* -X0072500Y0070712D02* -X0072500Y0070712D01* -X0073866Y0070712D02* -X0076193Y0070712D01* -X0076181Y0071911D02* -X0069065Y0071911D01* -X0069065Y0073109D02* -X0076181Y0073109D01* -X0076181Y0074308D02* -X0069065Y0074308D01* -X0070014Y0075506D02* -X0074611Y0075506D01* -X0073799Y0076705D02* -X0071224Y0076705D01* -X0072434Y0077903D02* -X0073681Y0077903D01* -X0073644Y0079102D02* -X0073681Y0079102D01* -X0069479Y0083896D02* -X0061041Y0083896D01* -X0066063Y0092500D02* -X0021437Y0092500D01* -X0017500Y0096437D01* -X0017500Y0151063D01* -X0021437Y0155000D01* -X0027500Y0155000D01* -X0027500Y0148937D01* -X0033218Y0148615D02* -X0088505Y0148615D01* -X0088505Y0147417D02* -X0087312Y0147417D01* -X0087420Y0147229D02* -X0087170Y0147663D01* -X0086816Y0148016D01* -X0086383Y0148267D01* -X0085900Y0148396D01* -X0083100Y0148396D01* -X0083100Y0144537D01* -X0087550Y0144537D01* -X0087550Y0146746D01* -X0087420Y0147229D01* -X0087550Y0146218D02* -X0088505Y0146218D01* -X0088505Y0145020D02* -X0087550Y0145020D01* -X0088505Y0143821D02* -X0083100Y0143821D01* -X0083100Y0143337D02* -X0083100Y0144537D01* -X0081900Y0144537D01* -X0081900Y0143337D01* -X0077450Y0143337D01* -X0077450Y0141128D01* -X0077580Y0140645D01* -X0077639Y0140543D01* -X0077173Y0140077D01* -X0076782Y0139133D01* -X0076782Y0132993D01* -X0077173Y0132049D01* -X0077895Y0131326D01* -X0078839Y0130935D01* -X0079331Y0130935D01* -X0079331Y0126870D01* -X0079814Y0125705D01* -X0080705Y0124814D01* -X0081870Y0124331D01* -X0084568Y0124331D01* -X0084568Y0121318D01* -X0078239Y0121318D01* -X0077521Y0121021D01* -X0077550Y0121128D01* -X0077550Y0123337D01* -X0073100Y0123337D01* -X0073100Y0124537D01* -X0077550Y0124537D01* -X0077550Y0126746D01* -X0077420Y0127229D01* -X0077170Y0127663D01* -X0076816Y0128016D01* -X0076383Y0128267D01* -X0075900Y0128396D01* -X0073100Y0128396D01* -X0073100Y0124537D01* -X0071900Y0124537D01* -X0071900Y0123337D01* -X0067450Y0123337D01* -X0067450Y0121128D01* -X0067580Y0120645D01* -X0067639Y0120543D01* -X0067173Y0120077D01* -X0066782Y0119133D01* -X0066782Y0115012D01* -X0066249Y0115545D01* -X0065358Y0116436D01* -X0064401Y0116832D01* -X0064264Y0117164D01* -X0063542Y0117886D01* -X0063168Y0118041D01* -X0063168Y0119890D01* -X0063674Y0120395D01* -X0064065Y0121339D01* -X0064065Y0128661D01* -X0063674Y0129605D01* -X0062951Y0130327D01* -X0062007Y0130718D01* -X0055867Y0130718D01* -X0054923Y0130327D01* -X0054457Y0129861D01* -X0054355Y0129920D01* -X0053872Y0130050D01* -X0051663Y0130050D01* -X0051663Y0127741D01* -X0050630Y0128168D01* -X0050463Y0128168D01* -X0050463Y0130050D01* -X0048254Y0130050D01* -X0047771Y0129920D01* -X0047337Y0129670D01* -X0046984Y0129316D01* -X0046733Y0128883D01* -X0046604Y0128400D01* -X0046604Y0125600D01* -X0046831Y0125600D01* -X0046831Y0124400D01* -X0046604Y0124400D01* -X0046604Y0121600D01* -X0046733Y0121117D01* -X0046831Y0120947D01* -X0046831Y0117278D01* -X0046747Y0117229D01* -X0046393Y0116875D01* -X0046143Y0116442D01* -X0046013Y0115959D01* -X0046013Y0113928D01* -X0046831Y0113928D01* -X0046831Y0112765D01* -X0046013Y0112765D01* -X0046013Y0110748D01* -X0045736Y0110471D01* -X0045467Y0109822D01* -X0045433Y0109822D01* -X0045327Y0110077D01* -X0044861Y0110543D01* -X0044920Y0110645D01* -X0045050Y0111128D01* -X0045050Y0113337D01* -X0040600Y0113337D01* -X0040600Y0114537D01* -X0045050Y0114537D01* -X0045050Y0116746D01* -X0044920Y0117229D01* -X0044670Y0117663D01* -X0044316Y0118016D01* -X0043883Y0118267D01* -X0043400Y0118396D01* -X0040600Y0118396D01* -X0040600Y0114537D01* -X0039400Y0114537D01* -X0039400Y0113337D01* -X0034950Y0113337D01* -X0034950Y0111128D01* -X0035080Y0110645D01* -X0035139Y0110543D01* -X0034673Y0110077D01* -X0034282Y0109133D01* -X0034282Y0102993D01* -X0034673Y0102049D01* -X0035395Y0101326D01* -X0036339Y0100935D01* -X0043661Y0100935D01* -X0044605Y0101326D01* -X0045327Y0102049D01* -X0045695Y0102936D01* -X0045736Y0102836D01* -X0046458Y0102114D01* -X0047402Y0101723D01* -X0047501Y0101723D01* -X0047501Y0099291D01* -X0047983Y0098126D01* -X0048874Y0097235D01* -X0050039Y0096753D01* -X0067365Y0096753D01* -X0065286Y0095668D01* -X0022749Y0095668D01* -X0020668Y0097749D01* -X0020668Y0149751D01* -X0021782Y0150864D01* -X0021782Y0145867D01* -X0022173Y0144923D01* -X0022639Y0144457D01* -X0022580Y0144355D01* -X0022450Y0143872D01* -X0022450Y0141663D01* -X0026900Y0141663D01* -X0026900Y0140463D01* -X0022450Y0140463D01* -X0022450Y0138254D01* -X0022580Y0137771D01* -X0022830Y0137337D01* -X0023184Y0136984D01* -X0023617Y0136733D01* -X0024100Y0136604D01* -X0026900Y0136604D01* -X0026900Y0140463D01* -X0028100Y0140463D01* -X0028100Y0141663D01* -X0032550Y0141663D01* -X0032550Y0143872D01* -X0032420Y0144355D01* -X0032361Y0144457D01* -X0032827Y0144923D01* -X0033218Y0145867D01* -X0033218Y0151831D01* -X0079208Y0151831D01* -X0079912Y0151127D01* -X0081591Y0150431D01* -X0083409Y0150431D01* -X0085088Y0151127D01* -X0086373Y0152412D01* -X0087068Y0154091D01* -X0087068Y0155909D01* -X0086872Y0156383D01* -X0088505Y0154751D01* -X0088505Y0130611D01* -X0088366Y0130668D01* -X0085668Y0130668D01* -X0085668Y0130935D01* -X0086161Y0130935D01* -X0087105Y0131326D01* -X0087827Y0132049D01* -X0088218Y0132993D01* -X0088218Y0139133D01* -X0087827Y0140077D01* -X0087361Y0140543D01* -X0087420Y0140645D01* -X0087550Y0141128D01* -X0087550Y0143337D01* -X0083100Y0143337D01* -X0081900Y0143821D02* -X0032550Y0143821D01* -X0032550Y0142623D02* -X0077450Y0142623D01* -X0077450Y0141424D02* -X0028100Y0141424D01* -X0028100Y0140463D02* -X0032550Y0140463D01* -X0032550Y0138254D01* -X0032420Y0137771D01* -X0032170Y0137337D01* -X0031816Y0136984D01* -X0031383Y0136733D01* -X0030900Y0136604D01* -X0028100Y0136604D01* -X0028100Y0140463D01* -X0028100Y0140226D02* -X0026900Y0140226D01* -X0026900Y0141424D02* -X0020668Y0141424D01* -X0020668Y0140226D02* -X0022450Y0140226D01* -X0022450Y0139027D02* -X0020668Y0139027D01* -X0020668Y0137829D02* -X0022564Y0137829D01* -X0024002Y0136630D02* -X0020668Y0136630D01* -X0020668Y0135432D02* -X0069684Y0135432D01* -X0069789Y0135275D02* -X0070275Y0134789D01* -X0070847Y0134407D01* -X0071482Y0134144D01* -X0072156Y0134009D01* -X0072500Y0134009D01* -X0072844Y0134009D01* -X0073518Y0134144D01* -X0074153Y0134407D01* -X0074725Y0134789D01* -X0075211Y0135275D01* -X0075593Y0135847D01* -X0075856Y0136482D01* -X0075991Y0137156D01* -X0075991Y0137500D01* -X0075991Y0137844D01* -X0075856Y0138518D01* -X0075593Y0139153D01* -X0075211Y0139725D01* -X0074725Y0140211D01* -X0074153Y0140593D01* -X0073518Y0140856D01* -X0072844Y0140991D01* -X0072500Y0140991D01* -X0072156Y0140991D01* -X0071482Y0140856D01* -X0070847Y0140593D01* -X0070275Y0140211D01* -X0069789Y0139725D01* -X0069407Y0139153D01* -X0069144Y0138518D01* -X0069009Y0137844D01* -X0069009Y0137500D01* -X0069009Y0137156D01* -X0069144Y0136482D01* -X0069407Y0135847D01* -X0069789Y0135275D01* -X0069114Y0136630D02* -X0030998Y0136630D01* -X0032436Y0137829D02* -X0069009Y0137829D01* -X0069009Y0137500D02* -X0072500Y0137500D01* -X0075991Y0137500D01* -X0072500Y0137500D01* -X0072500Y0137500D01* -X0072500Y0137500D01* -X0072500Y0140991D01* -X0072500Y0137500D01* -X0072500Y0134009D01* -X0072500Y0137500D01* -X0072500Y0137500D01* -X0072500Y0137500D01* -X0069009Y0137500D01* -X0069355Y0139027D02* -X0032550Y0139027D01* -X0032550Y0140226D02* -X0070297Y0140226D01* -X0072500Y0140226D02* -X0072500Y0140226D01* -X0072500Y0139027D02* -X0072500Y0139027D01* -X0072500Y0137829D02* -X0072500Y0137829D01* -X0072500Y0136630D02* -X0072500Y0136630D01* -X0072500Y0135432D02* -X0072500Y0135432D01* -X0072500Y0134233D02* -X0072500Y0134233D01* -X0073735Y0134233D02* -X0076782Y0134233D01* -X0076782Y0133035D02* -X0020668Y0133035D01* -X0020668Y0134233D02* -X0071265Y0134233D01* -X0075316Y0135432D02* -X0076782Y0135432D01* -X0076782Y0136630D02* -X0075886Y0136630D01* -X0075991Y0137829D02* -X0076782Y0137829D01* -X0076782Y0139027D02* -X0075645Y0139027D01* -X0074703Y0140226D02* -X0077322Y0140226D01* -X0077450Y0144537D02* -X0081900Y0144537D01* -X0081900Y0148396D01* -X0079100Y0148396D01* -X0078617Y0148267D01* -X0078184Y0148016D01* -X0077830Y0147663D01* -X0077580Y0147229D01* -X0077450Y0146746D01* -X0077450Y0144537D01* -X0077450Y0145020D02* -X0032867Y0145020D01* -X0033218Y0146218D02* -X0077450Y0146218D01* -X0077688Y0147417D02* -X0033218Y0147417D01* -X0033218Y0149814D02* -X0088505Y0149814D01* -X0088505Y0151012D02* -X0084811Y0151012D01* -X0086172Y0152211D02* -X0088505Y0152211D01* -X0088505Y0153409D02* -X0086786Y0153409D01* -X0087068Y0154608D02* -X0088505Y0154608D01* -X0087449Y0155806D02* -X0087068Y0155806D01* -X0091673Y0156063D02* -X0091673Y0117441D01* -X0089705Y0117441D02* -X0089705Y0145000D01* -X0082500Y0145000D01* -X0081900Y0145020D02* -X0083100Y0145020D01* -X0083100Y0146218D02* -X0081900Y0146218D01* -X0081900Y0147417D02* -X0083100Y0147417D01* -X0080189Y0151012D02* -X0033218Y0151012D01* -X0027500Y0155000D02* -X0082500Y0155000D01* -X0086673Y0161063D02* -X0091673Y0156063D01* -X0093642Y0160000D02* -X0093642Y0117441D01* -X0095610Y0117441D02* -X0095610Y0180000D01* -X0096250Y0181250D01* -X0082500Y0181250D01* -X0082500Y0177500D01* -X0082490Y0176250D01* -X0087618Y0176181D02* -X0090032Y0176181D01* -X0089981Y0176303D02* -X0090936Y0173999D01* -X0092442Y0172493D01* -X0092442Y0165681D01* -X0090437Y0167686D01* -X0089272Y0168168D01* -X0043812Y0168168D01* -X0040668Y0171312D01* -X0040668Y0202400D01* -X0042100Y0202400D01* -X0042100Y0207100D01* -X0042900Y0207100D01* -X0042900Y0202400D01* -X0044331Y0202400D01* -X0044331Y0201870D01* -X0044814Y0200705D01* -X0049814Y0195705D01* -X0050705Y0194814D01* -X0051870Y0194331D01* -X0103688Y0194331D01* -X0106831Y0191188D01* -X0106831Y0166312D01* -X0103688Y0163168D01* -X0100885Y0163168D01* -X0099721Y0162686D01* -X0098830Y0161795D01* -X0098779Y0161744D01* -X0098779Y0171812D01* -X0099801Y0172236D01* -X0101564Y0173999D01* -X0102518Y0176303D01* -X0102518Y0186197D01* -X0101564Y0188501D01* -X0099801Y0190264D01* -X0097497Y0191218D01* -X0095003Y0191218D01* -X0092699Y0190264D01* -X0090936Y0188501D01* -X0089981Y0186197D01* -X0089981Y0184418D01* -X0081870Y0184418D01* -X0080705Y0183936D01* -X0079814Y0183045D01* -X0079331Y0181880D01* -X0079331Y0181735D01* -X0078476Y0181380D01* -X0078118Y0181023D01* -X0077819Y0181103D01* -X0075610Y0181103D01* -X0075610Y0176850D01* -X0074410Y0176850D01* -X0074410Y0181103D01* -X0072201Y0181103D01* -X0071717Y0180973D01* -X0071284Y0180723D01* -X0070930Y0180369D01* -X0070680Y0179936D01* -X0070551Y0179453D01* -X0070551Y0176850D01* -X0074410Y0176850D01* -X0074410Y0175650D01* -X0075610Y0175650D01* -X0075610Y0171397D01* -X0077819Y0171397D01* -X0078118Y0171477D01* -X0078476Y0171120D01* -X0079420Y0170729D01* -X0085560Y0170729D01* -X0086504Y0171120D01* -X0087227Y0171842D01* -X0087618Y0172786D01* -X0087618Y0178081D01* -X0089981Y0178081D01* -X0089981Y0176303D01* -X0089981Y0177380D02* -X0087618Y0177380D01* -X0087618Y0174983D02* -X0090528Y0174983D01* -X0091151Y0173784D02* -X0087618Y0173784D01* -X0087535Y0172586D02* -X0092349Y0172586D01* -X0092442Y0171387D02* -X0086771Y0171387D01* -X0090182Y0167792D02* -X0092442Y0167792D01* -X0092442Y0168990D02* -X0042991Y0168990D01* -X0041792Y0170189D02* -X0092442Y0170189D01* -X0092442Y0166593D02* -X0091530Y0166593D01* -X0088642Y0165000D02* -X0093642Y0160000D01* -X0098779Y0161799D02* -X0098834Y0161799D01* -X0098779Y0162998D02* -X0100473Y0162998D01* -X0098779Y0164196D02* -X0104715Y0164196D01* -X0105914Y0165395D02* -X0098779Y0165395D01* -X0098779Y0166593D02* -X0106831Y0166593D01* -X0106831Y0167792D02* -X0098779Y0167792D01* -X0098779Y0168990D02* -X0106831Y0168990D01* -X0106831Y0170189D02* -X0098779Y0170189D01* -X0098779Y0171387D02* -X0106831Y0171387D01* -X0106831Y0172586D02* -X0100151Y0172586D01* -X0101349Y0173784D02* -X0106831Y0173784D01* -X0106831Y0174983D02* -X0101972Y0174983D01* -X0102468Y0176181D02* -X0106831Y0176181D01* -X0106831Y0177380D02* -X0102518Y0177380D01* -X0102518Y0178578D02* -X0106831Y0178578D01* -X0106831Y0179777D02* -X0102518Y0179777D01* -X0102518Y0180975D02* -X0106831Y0180975D01* -X0106831Y0182174D02* -X0102518Y0182174D01* -X0102518Y0183372D02* -X0106831Y0183372D01* -X0106831Y0184571D02* -X0102518Y0184571D01* -X0102518Y0185769D02* -X0106831Y0185769D01* -X0106831Y0186968D02* -X0102199Y0186968D01* -X0101703Y0188166D02* -X0106831Y0188166D01* -X0106831Y0189365D02* -X0100700Y0189365D01* -X0099079Y0190563D02* -X0106831Y0190563D01* -X0106257Y0191762D02* -X0081775Y0191762D01* -X0081653Y0191843D02* -X0081018Y0192106D01* -X0080344Y0192241D01* -X0080000Y0192241D01* -X0079656Y0192241D01* -X0078982Y0192106D01* -X0078347Y0191843D01* -X0077775Y0191461D01* -X0077289Y0190975D01* -X0076907Y0190403D01* -X0076644Y0189768D01* -X0076509Y0189094D01* -X0076509Y0188750D01* -X0076509Y0188406D01* -X0076644Y0187732D01* -X0076907Y0187097D01* -X0077289Y0186525D01* -X0077775Y0186039D01* -X0078347Y0185657D01* -X0078982Y0185394D01* -X0079656Y0185259D01* -X0080000Y0185259D01* -X0080344Y0185259D01* -X0081018Y0185394D01* -X0081653Y0185657D01* -X0082225Y0186039D01* -X0082711Y0186525D01* -X0083093Y0187097D01* -X0083356Y0187732D01* -X0083491Y0188406D01* -X0083491Y0188750D01* -X0083491Y0189094D01* -X0083356Y0189768D01* -X0083093Y0190403D01* -X0082711Y0190975D01* -X0082225Y0191461D01* -X0081653Y0191843D01* -X0080000Y0191762D02* -X0080000Y0191762D01* -X0080000Y0192241D02* -X0080000Y0188750D01* -X0083491Y0188750D01* -X0080000Y0188750D01* -X0080000Y0188750D01* -X0080000Y0188750D01* -X0080000Y0192241D01* -X0080000Y0190563D02* -X0080000Y0190563D01* -X0080000Y0189365D02* -X0080000Y0189365D01* -X0080000Y0188750D02* -X0080000Y0188750D01* -X0080000Y0185259D01* -X0080000Y0188750D01* -X0080000Y0188750D01* -X0076509Y0188750D01* -X0080000Y0188750D01* -X0080000Y0188166D02* -X0080000Y0188166D01* -X0080000Y0186968D02* -X0080000Y0186968D01* -X0080000Y0185769D02* -X0080000Y0185769D01* -X0081822Y0185769D02* -X0089981Y0185769D01* -X0089981Y0184571D02* -X0040668Y0184571D01* -X0040668Y0185769D02* -X0078178Y0185769D01* -X0076993Y0186968D02* -X0040668Y0186968D01* -X0040668Y0188166D02* -X0076557Y0188166D01* -X0076563Y0189365D02* -X0040668Y0189365D01* -X0040668Y0190563D02* -X0077014Y0190563D01* -X0078225Y0191762D02* -X0040668Y0191762D01* -X0040668Y0192960D02* -X0105059Y0192960D01* -X0103860Y0194159D02* -X0040668Y0194159D01* -X0040668Y0195357D02* -X0050162Y0195357D01* -X0048963Y0196556D02* -X0040668Y0196556D01* -X0040668Y0197754D02* -X0047765Y0197754D01* -X0046566Y0198953D02* -X0040668Y0198953D01* -X0040668Y0200151D02* -X0045368Y0200151D01* -X0044547Y0201350D02* -X0040668Y0201350D01* -X0042100Y0202548D02* -X0042900Y0202548D01* -X0042900Y0203747D02* -X0042100Y0203747D01* -X0042100Y0204945D02* -X0042900Y0204945D01* -X0042900Y0206144D02* -X0042100Y0206144D01* -X0047500Y0202500D02* -X0052500Y0197500D01* -X0105000Y0197500D01* -X0110000Y0192500D01* -X0110000Y0165000D01* -X0105000Y0160000D01* -X0101516Y0160000D01* -X0097579Y0156063D01* -X0097579Y0117441D01* -X0099547Y0117441D02* -X0099547Y0153563D01* -X0103484Y0157500D01* -X0109402Y0157500D01* -X0114402Y0162500D01* -X0114402Y0192500D01* -X0130733Y0198953D02* -X0134267Y0198953D01* -X0132707Y0200151D02* -X0132293Y0200151D01* -X0138098Y0205000D02* -X0138098Y0217500D01* -X0136240Y0217500D01* -X0141368Y0216931D02* -X0153160Y0216931D01* -X0153160Y0216900D02* -X0149301Y0216900D01* -X0149301Y0214297D01* -X0149430Y0213814D01* -X0149680Y0213381D01* -X0150034Y0213027D01* -X0150467Y0212777D01* -X0150951Y0212647D01* -X0153160Y0212647D01* -X0153160Y0216900D01* -X0153160Y0215732D02* -X0154360Y0215732D01* -X0154360Y0214534D02* -X0153160Y0214534D01* -X0153160Y0213335D02* -X0154360Y0213335D01* -X0149726Y0213335D02* -X0141267Y0213335D01* -X0141267Y0212137D02* -X0157789Y0212137D01* -X0159004Y0210938D02* -X0155996Y0210938D01* -X0157338Y0209739D02* -X0157662Y0209739D01* -X0149301Y0214534D02* -X0141368Y0214534D01* -X0141368Y0215732D02* -X0149301Y0215732D01* -X0149301Y0218129D02* -X0141368Y0218129D01* -X0141368Y0219328D02* -X0149301Y0219328D01* -X0149301Y0220526D02* -X0141368Y0220526D01* -X0141053Y0221725D02* -X0149786Y0221725D01* -X0153160Y0221725D02* -X0154360Y0221725D01* -X0154360Y0220526D02* -X0153160Y0220526D01* -X0153160Y0219328D02* -X0154360Y0219328D01* -X0154360Y0218129D02* -X0153160Y0218129D01* -X0157933Y0222923D02* -X0139547Y0222923D01* -X0142193Y0210938D02* -X0147807Y0210938D01* -X0146465Y0209739D02* -X0143535Y0209739D01* -X0143817Y0208541D02* -X0146183Y0208541D01* -X0146183Y0207342D02* -X0143817Y0207342D01* -X0143817Y0206144D02* -X0146183Y0206144D01* -X0166368Y0214534D02* -X0177553Y0214534D01* -X0178751Y0215732D02* -X0166368Y0215732D01* -X0166368Y0216931D02* -X0179231Y0216931D01* -X0179231Y0218129D02* -X0166368Y0218129D01* -X0166368Y0219328D02* -X0179231Y0219328D01* -X0179868Y0220526D02* -X0166368Y0220526D01* -X0166053Y0221725D02* -X0181067Y0221725D01* -X0182265Y0222923D02* -X0164547Y0222923D01* -X0187141Y0224122D02* -X0060359Y0224122D01* -X0059161Y0225320D02* -X0188529Y0225320D01* -X0195000Y0217500D02* -X0190000Y0212500D01* -X0195400Y0207342D02* -X0209009Y0207342D01* -X0209009Y0207500D02* -X0209009Y0207156D01* -X0209144Y0206482D01* -X0209407Y0205847D01* -X0209789Y0205275D01* -X0210275Y0204789D01* -X0210847Y0204407D01* -X0211482Y0204144D01* -X0212156Y0204009D01* -X0212500Y0204009D01* -X0212844Y0204009D01* -X0213518Y0204144D01* -X0214153Y0204407D01* -X0214725Y0204789D01* -X0215211Y0205275D01* -X0215593Y0205847D01* -X0215856Y0206482D01* -X0215991Y0207156D01* -X0215991Y0207500D01* -X0215991Y0207844D01* -X0215856Y0208518D01* -X0215593Y0209153D01* -X0215211Y0209725D01* -X0214725Y0210211D01* -X0214153Y0210593D01* -X0213518Y0210856D01* -X0212844Y0210991D01* -X0212500Y0210991D01* -X0212156Y0210991D01* -X0211482Y0210856D01* -X0210847Y0210593D01* -X0210275Y0210211D01* -X0209789Y0209725D01* -X0209407Y0209153D01* -X0209144Y0208518D01* -X0209009Y0207844D01* -X0209009Y0207500D01* -X0212500Y0207500D01* -X0215991Y0207500D01* -X0212500Y0207500D01* -X0212500Y0207500D01* -X0212500Y0207500D01* -X0212500Y0210991D01* -X0212500Y0207500D01* -X0212500Y0204009D01* -X0212500Y0207500D01* -X0212500Y0207500D01* -X0212500Y0207500D01* -X0209009Y0207500D01* -X0209153Y0208541D02* -X0200100Y0208541D01* -X0199973Y0209739D02* -X0209803Y0209739D01* -X0211892Y0210938D02* -X0198774Y0210938D01* -X0197794Y0212137D02* -X0269959Y0212137D01* -X0269950Y0210938D02* -X0213108Y0210938D01* -X0212500Y0210938D02* -X0212500Y0210938D01* -X0212500Y0209739D02* -X0212500Y0209739D01* -X0212500Y0208541D02* -X0212500Y0208541D01* -X0212500Y0207342D02* -X0212500Y0207342D01* -X0212500Y0206144D02* -X0212500Y0206144D01* -X0212500Y0204945D02* -X0212500Y0204945D01* -X0214882Y0204945D02* -X0226023Y0204945D01* -X0226044Y0205025D02* -X0225915Y0204541D01* -X0225915Y0202609D01* -X0233631Y0202609D01* -X0233631Y0206191D01* -X0227565Y0206191D01* -X0227082Y0206062D01* -X0226648Y0205812D01* -X0226295Y0205458D01* -X0226044Y0205025D01* -X0225915Y0203747D02* -X0198459Y0203747D01* -X0197261Y0202548D02* -X0233631Y0202548D01* -X0234597Y0202548D02* -X0260403Y0202548D01* -X0260886Y0202126D02* -X0270000Y0202126D01* -X0269085Y0203747D02* -X0286132Y0203747D01* -X0286132Y0204945D02* -X0279732Y0204945D01* -X0280050Y0206144D02* -X0286132Y0206144D01* -X0286132Y0207342D02* -X0280050Y0207342D01* -X0275600Y0206144D02* -X0274400Y0206144D01* -X0274400Y0204945D02* -X0275600Y0204945D01* -X0274400Y0208541D02* -X0215847Y0208541D01* -X0215991Y0207342D02* -X0269950Y0207342D01* -X0269950Y0206144D02* -X0267612Y0206144D01* -X0268977Y0204945D02* -X0270268Y0204945D01* -X0269950Y0209739D02* -X0215197Y0209739D01* -X0215716Y0206144D02* -X0227388Y0206144D01* -X0225915Y0201350D02* -X0193168Y0201350D01* -X0193168Y0200151D02* -X0225915Y0200151D01* -X0226203Y0198953D02* -X0193168Y0198953D01* -X0193168Y0197754D02* -X0286132Y0197754D01* -X0286132Y0196556D02* -X0193168Y0196556D01* -X0193168Y0195357D02* -X0286132Y0195357D01* -X0286132Y0194159D02* -X0193168Y0194159D01* -X0193168Y0192960D02* -X0286132Y0192960D01* -X0286132Y0191762D02* -X0193168Y0191762D01* -X0193168Y0190563D02* -X0286132Y0190563D01* -X0286132Y0189365D02* -X0193168Y0189365D01* -X0193168Y0188166D02* -X0286132Y0188166D01* -X0286132Y0186968D02* -X0193168Y0186968D01* -X0193168Y0185769D02* -X0286132Y0185769D01* -X0286132Y0184571D02* -X0193168Y0184571D01* -X0186831Y0184571D02* -X0176208Y0184571D01* -X0177750Y0185769D02* -X0186831Y0185769D01* -X0186831Y0186968D02* -X0178949Y0186968D01* -X0180147Y0188166D02* -X0186831Y0188166D01* -X0186831Y0189365D02* -X0181346Y0189365D01* -X0182544Y0190563D02* -X0186831Y0190563D01* -X0186831Y0191762D02* -X0183124Y0191762D01* -X0183168Y0192960D02* -X0186831Y0192960D01* -X0186831Y0194159D02* -X0183168Y0194159D01* -X0183168Y0195357D02* -X0186831Y0195357D01* -X0186831Y0196556D02* -X0183168Y0196556D01* -X0183168Y0197754D02* -X0186831Y0197754D01* -X0194600Y0202548D02* -X0195400Y0202548D01* -X0195400Y0202400D02* -X0197112Y0202400D01* -X0200100Y0205388D01* -X0200100Y0207100D01* -X0195400Y0207100D01* -X0195400Y0202400D01* -X0195400Y0203747D02* -X0194600Y0203747D01* -X0194600Y0204945D02* -X0195400Y0204945D01* -X0195400Y0206144D02* -X0194600Y0206144D01* -X0199658Y0204945D02* -X0210118Y0204945D01* -X0209284Y0206144D02* -X0200100Y0206144D01* -X0198993Y0213335D02* -X0226834Y0213335D01* -X0225522Y0214534D02* -X0200191Y0214534D01* -X0200768Y0215732D02* -X0225246Y0215732D01* -X0225246Y0216931D02* -X0200768Y0216931D01* -X0200768Y0218129D02* -X0225246Y0218129D01* -X0225246Y0219328D02* -X0200768Y0219328D01* -X0233631Y0206144D02* -X0234597Y0206144D01* -X0234597Y0204945D02* -X0233631Y0204945D01* -X0233631Y0203747D02* -X0234597Y0203747D01* -X0234597Y0201350D02* -X0233631Y0201350D01* -X0233631Y0200151D02* -X0234597Y0200151D01* -X0234597Y0198953D02* -X0233631Y0198953D01* -X0241395Y0213335D02* -X0253605Y0213335D01* -X0252293Y0214534D02* -X0242707Y0214534D01* -X0242361Y0221725D02* -X0252639Y0221725D01* -X0237283Y0222923D02* -X0286132Y0222923D01* -X0286132Y0224122D02* -X0236872Y0224122D01* -X0235585Y0225320D02* -X0286132Y0225320D01* -X0286132Y0226519D02* -X0057962Y0226519D01* -X0057686Y0226795D02* -X0057686Y0226795D01* -X0052500Y0217500D02* -X0057500Y0212500D01* -X0052500Y0217500D02* -X0047500Y0222500D01* -X0018937Y0222500D01* -X0022500Y0217500D02* -X0027500Y0212500D01* -X0027500Y0166063D01* -X0032500Y0161063D01* -X0086673Y0161063D01* -X0088642Y0165000D02* -X0042500Y0165000D01* -X0037500Y0170000D01* -X0037500Y0212500D01* -X0032500Y0217500D01* -X0042500Y0217500D02* -X0047500Y0212500D01* -X0047500Y0202500D01* -X0034331Y0202400D02* -X0034331Y0169370D01* -X0034814Y0168205D01* -X0038788Y0164231D01* -X0033812Y0164231D01* -X0030668Y0167375D01* -X0030668Y0202400D01* -X0032100Y0202400D01* -X0032100Y0207100D01* -X0032900Y0207100D01* -X0032900Y0202400D01* -X0034331Y0202400D01* -X0034331Y0201350D02* -X0030668Y0201350D01* -X0030668Y0200151D02* -X0034331Y0200151D01* -X0034331Y0198953D02* -X0030668Y0198953D01* -X0030668Y0197754D02* -X0034331Y0197754D01* -X0034331Y0196556D02* -X0030668Y0196556D01* -X0030668Y0195357D02* -X0034331Y0195357D01* -X0034331Y0194159D02* -X0030668Y0194159D01* -X0030668Y0192960D02* -X0034331Y0192960D01* -X0034331Y0191762D02* -X0030668Y0191762D01* -X0030668Y0190563D02* -X0034331Y0190563D01* -X0034331Y0189365D02* -X0030668Y0189365D01* -X0030668Y0188166D02* -X0034331Y0188166D01* -X0034331Y0186968D02* -X0030668Y0186968D01* -X0030668Y0185769D02* -X0034331Y0185769D01* -X0034331Y0184571D02* -X0030668Y0184571D01* -X0030668Y0183372D02* -X0034331Y0183372D01* -X0034331Y0182174D02* -X0030668Y0182174D01* -X0030668Y0180975D02* -X0034331Y0180975D01* -X0034331Y0179777D02* -X0030668Y0179777D01* -X0030668Y0178578D02* -X0034331Y0178578D01* -X0034331Y0177380D02* -X0030668Y0177380D01* -X0030668Y0176181D02* -X0034331Y0176181D01* -X0034331Y0174983D02* -X0030668Y0174983D01* -X0030668Y0173784D02* -X0034331Y0173784D01* -X0034331Y0172586D02* -X0030668Y0172586D01* -X0030668Y0171387D02* -X0034331Y0171387D01* -X0034331Y0170189D02* -X0030668Y0170189D01* -X0030668Y0168990D02* -X0034489Y0168990D01* -X0035227Y0167792D02* -X0030668Y0167792D01* -X0031451Y0166593D02* -X0036426Y0166593D01* -X0037624Y0165395D02* -X0032649Y0165395D01* -X0028482Y0160601D02* -X0018168Y0160601D01* -X0018168Y0161799D02* -X0027283Y0161799D01* -X0026084Y0162998D02* -X0018168Y0162998D01* -X0018168Y0164196D02* -X0024886Y0164196D01* -X0024814Y0164268D02* -X0029814Y0159268D01* -X0030705Y0158377D01* -X0031208Y0158168D01* -X0028130Y0158168D01* -X0020807Y0158168D01* -X0019642Y0157686D01* -X0018751Y0156795D01* -X0018168Y0156212D01* -X0018168Y0204619D01* -X0020388Y0202400D01* -X0022100Y0202400D01* -X0022100Y0207100D01* -X0022900Y0207100D01* -X0022900Y0202400D01* -X0024331Y0202400D01* -X0024331Y0165433D01* -X0024814Y0164268D01* -X0024347Y0165395D02* -X0018168Y0165395D01* -X0018168Y0166593D02* -X0024331Y0166593D01* -X0024331Y0167792D02* -X0018168Y0167792D01* -X0018168Y0168990D02* -X0024331Y0168990D01* -X0024331Y0170189D02* -X0018168Y0170189D01* -X0018168Y0171387D02* -X0024331Y0171387D01* -X0024331Y0172586D02* -X0018168Y0172586D01* -X0018168Y0173784D02* -X0024331Y0173784D01* -X0024331Y0174983D02* -X0018168Y0174983D01* -X0018168Y0176181D02* -X0024331Y0176181D01* -X0024331Y0177380D02* -X0018168Y0177380D01* -X0018168Y0178578D02* -X0024331Y0178578D01* -X0024331Y0179777D02* -X0018168Y0179777D01* -X0018168Y0180975D02* -X0024331Y0180975D01* -X0024331Y0182174D02* -X0018168Y0182174D01* -X0018168Y0183372D02* -X0024331Y0183372D01* -X0024331Y0184571D02* -X0018168Y0184571D01* -X0018168Y0185769D02* -X0024331Y0185769D01* -X0024331Y0186968D02* -X0018168Y0186968D01* -X0018168Y0188166D02* -X0024331Y0188166D01* -X0024331Y0189365D02* -X0018168Y0189365D01* -X0018168Y0190563D02* -X0024331Y0190563D01* -X0024331Y0191762D02* -X0018168Y0191762D01* -X0018168Y0192960D02* -X0024331Y0192960D01* -X0024331Y0194159D02* -X0018168Y0194159D01* -X0018168Y0195357D02* -X0024331Y0195357D01* -X0024331Y0196556D02* -X0018168Y0196556D01* -X0018168Y0197754D02* -X0024331Y0197754D01* -X0024331Y0198953D02* -X0018168Y0198953D01* -X0018168Y0200151D02* -X0024331Y0200151D01* -X0024331Y0201350D02* -X0018168Y0201350D01* -X0018168Y0202548D02* -X0020239Y0202548D01* -X0019041Y0203747D02* -X0018168Y0203747D01* -X0022100Y0203747D02* -X0022900Y0203747D01* -X0022900Y0204945D02* -X0022100Y0204945D01* -X0022100Y0206144D02* -X0022900Y0206144D01* -X0022900Y0202548D02* -X0022100Y0202548D01* -X0032100Y0202548D02* -X0032900Y0202548D01* -X0032900Y0203747D02* -X0032100Y0203747D01* -X0032100Y0204945D02* -X0032900Y0204945D01* -X0032900Y0206144D02* -X0032100Y0206144D01* -X0040668Y0183372D02* -X0080141Y0183372D01* -X0079453Y0182174D02* -X0040668Y0182174D01* -X0040668Y0180975D02* -X0071725Y0180975D01* -X0070638Y0179777D02* -X0040668Y0179777D01* -X0040668Y0178578D02* -X0070551Y0178578D01* -X0070551Y0177380D02* -X0040668Y0177380D01* -X0040668Y0176181D02* -X0074410Y0176181D01* -X0074410Y0175650D02* -X0070551Y0175650D01* -X0070551Y0173047D01* -X0070680Y0172564D01* -X0070930Y0172131D01* -X0071284Y0171777D01* -X0071717Y0171527D01* -X0072201Y0171397D01* -X0074410Y0171397D01* -X0074410Y0175650D01* -X0074410Y0174983D02* -X0075610Y0174983D01* -X0075610Y0173784D02* -X0074410Y0173784D01* -X0074410Y0172586D02* -X0075610Y0172586D01* -X0078209Y0171387D02* -X0040668Y0171387D01* -X0040668Y0172586D02* -X0070674Y0172586D01* -X0070551Y0173784D02* -X0040668Y0173784D01* -X0040668Y0174983D02* -X0070551Y0174983D01* -X0074410Y0177380D02* -X0075610Y0177380D01* -X0075610Y0178578D02* -X0074410Y0178578D01* -X0074410Y0179777D02* -X0075610Y0179777D01* -X0075610Y0180975D02* -X0074410Y0180975D01* -X0083007Y0186968D02* -X0090301Y0186968D01* -X0090797Y0188166D02* -X0083443Y0188166D01* -X0083437Y0189365D02* -X0091800Y0189365D01* -X0093421Y0190563D02* -X0082986Y0190563D01* -X0120493Y0151012D02* -X0174552Y0151012D01* -X0193168Y0151012D02* -X0194552Y0151012D01* -X0193514Y0149814D02* -X0193168Y0149814D01* -X0193168Y0152211D02* -X0211553Y0152211D01* -X0210933Y0153409D02* -X0199563Y0153409D01* -X0201069Y0154608D02* -X0209734Y0154608D01* -X0209231Y0155806D02* -X0201743Y0155806D01* -X0202068Y0157005D02* -X0209231Y0157005D01* -X0209231Y0158203D02* -X0202068Y0158203D01* -X0195941Y0161799D02* -X0193168Y0161799D01* -X0194327Y0153409D02* -X0195437Y0153409D01* -X0200448Y0151012D02* -X0210355Y0151012D01* -X0209231Y0149814D02* -X0201486Y0149814D01* -X0201983Y0148615D02* -X0209231Y0148615D01* -X0209231Y0147417D02* -X0202068Y0147417D01* -X0201914Y0146218D02* -X0209231Y0146218D01* -X0209322Y0145020D02* -X0201418Y0145020D01* -X0200668Y0143821D02* -X0210521Y0143821D01* -X0211719Y0142623D02* -X0200668Y0142623D01* -X0200668Y0141424D02* -X0240926Y0141424D01* -X0240926Y0140226D02* -X0200668Y0140226D01* -X0200668Y0139027D02* -X0240926Y0139027D01* -X0240926Y0137829D02* -X0227049Y0137829D01* -X0228103Y0136630D02* -X0240976Y0136630D01* -X0241694Y0135432D02* -X0228473Y0135432D01* -X0225000Y0135432D02* -X0225000Y0135432D01* -X0225000Y0136630D02* -X0225000Y0136630D01* -X0225000Y0137829D02* -X0225000Y0137829D01* -X0222951Y0137829D02* -X0200668Y0137829D01* -X0200668Y0136630D02* -X0221897Y0136630D01* -X0221527Y0135432D02* -X0200668Y0135432D01* -X0200668Y0134233D02* -X0221594Y0134233D01* -X0222115Y0133035D02* -X0200668Y0133035D01* -X0194331Y0133035D02* -X0193426Y0133035D01* -X0194184Y0131836D02* -X0194331Y0131836D01* -X0194331Y0134233D02* -X0191718Y0134233D01* -X0200668Y0124645D02* -X0237235Y0124645D01* -X0236629Y0123447D02* -X0200668Y0123447D01* -X0200230Y0122248D02* -X0236509Y0122248D01* -X0236823Y0121050D02* -X0199094Y0121050D01* -X0197895Y0119851D02* -X0237712Y0119851D01* -X0240000Y0123447D02* -X0240000Y0123447D01* -X0240000Y0124645D02* -X0240000Y0124645D01* -X0250806Y0135432D02* -X0252944Y0135432D01* -X0247500Y0140000D02* -X0246250Y0140020D01* -X0240926Y0142623D02* -X0228281Y0142623D01* -X0229479Y0143821D02* -X0241147Y0143821D01* -X0242200Y0145020D02* -X0230678Y0145020D01* -X0221719Y0142623D02* -X0218281Y0142623D01* -X0219479Y0143821D02* -X0220521Y0143821D01* -X0225000Y0134233D02* -X0225000Y0134233D01* -X0225000Y0133035D02* -X0225000Y0133035D01* -X0223740Y0107500D02* -X0232500Y0107500D01* -X0235000Y0105000D01* -X0255157Y0105000D01* -X0255157Y0101850D02* -X0236437Y0101850D01* -X0266910Y0070712D02* -X0268110Y0070712D01* -X0268110Y0069514D02* -X0266910Y0069514D01* -X0266910Y0068315D02* -X0268110Y0068315D01* -X0213124Y0061124D02* -X0171268Y0061124D01* -X0163731Y0061124D02* -X0133396Y0061124D01* -X0133396Y0059400D02* -X0129537Y0059400D01* -X0129537Y0054950D01* -X0131746Y0054950D01* -X0132229Y0055080D01* -X0132663Y0055330D01* -X0133016Y0055684D01* -X0133267Y0056117D01* -X0133396Y0056600D01* -X0133396Y0059400D01* -X0133396Y0058727D02* -X0163731Y0058727D01* -X0163731Y0057529D02* -X0133396Y0057529D01* -X0133324Y0056330D02* -X0163731Y0056330D01* -X0163731Y0055132D02* -X0132319Y0055132D01* -X0129537Y0055132D02* -X0128337Y0055132D01* -X0128337Y0056330D02* -X0129537Y0056330D01* -X0129537Y0057529D02* -X0128337Y0057529D01* -X0128337Y0058727D02* -X0129537Y0058727D01* -X0129537Y0059926D02* -X0163731Y0059926D01* -X0163731Y0062323D02* -X0140227Y0062323D01* -X0143337Y0063521D02* -X0144537Y0063521D01* -X0144537Y0062450D02* -X0146746Y0062450D01* -X0147229Y0062580D01* -X0147663Y0062830D01* -X0148016Y0063184D01* -X0148267Y0063617D01* -X0148396Y0064100D01* -X0148396Y0066900D01* -X0144537Y0066900D01* -X0144537Y0062450D01* -X0144537Y0064720D02* -X0143337Y0064720D01* -X0143337Y0065918D02* -X0144537Y0065918D01* -X0144537Y0067117D02* -X0163731Y0067117D01* -X0163731Y0068315D02* -X0148396Y0068315D01* -X0148396Y0069514D02* -X0163731Y0069514D01* -X0163731Y0070712D02* -X0148396Y0070712D01* -X0148396Y0065918D02* -X0163731Y0065918D01* -X0163731Y0064720D02* -X0148396Y0064720D01* -X0148211Y0063521D02* -X0163731Y0063521D01* -X0163731Y0053933D02* -X0112558Y0053933D01* -X0112558Y0055132D02* -X0116590Y0055132D01* -X0115939Y0056330D02* -X0112558Y0056330D01* -X0112558Y0057529D02* -X0113286Y0057529D01* -X0115295Y0060000D02* -X0121063Y0060000D01* -X0122500Y0060000D01* -X0125536Y0055132D02* -X0125555Y0055132D01* -X0118760Y0047500D02* -X0113327Y0047500D01* -X0109390Y0051437D01* -X0109390Y0072559D01* -X0115295Y0072559D02* -X0115295Y0060000D01* -X0114998Y0052734D02* -X0112573Y0052734D01* -X0113772Y0051536D02* -X0113869Y0051536D01* -X0110802Y0045543D02* -X0110590Y0045543D01* -X0110590Y0044345D02* -X0112664Y0044345D01* -X0114001Y0043146D02* -X0110590Y0043146D01* -X0118712Y0034331D02* -X0124751Y0034331D01* -X0126831Y0032251D01* -X0126831Y0027881D01* -X0124612Y0030100D01* -X0122900Y0030100D01* -X0122900Y0025400D01* -X0122100Y0025400D01* -X0122100Y0030100D01* -X0120668Y0030100D01* -X0120668Y0031693D01* -X0120186Y0032858D01* -X0118712Y0034331D01* -X0119486Y0033558D02* -X0125524Y0033558D01* -X0126722Y0032360D02* -X0120392Y0032360D01* -X0120668Y0031161D02* -X0126831Y0031161D01* -X0126831Y0029963D02* -X0124750Y0029963D01* -X0125948Y0028764D02* -X0126831Y0028764D01* -X0122900Y0028764D02* -X0122100Y0028764D01* -X0122100Y0027566D02* -X0122900Y0027566D01* -X0122900Y0026367D02* -X0122100Y0026367D01* -X0122100Y0029963D02* -X0122900Y0029963D01* -X0113982Y0030100D02* -X0112251Y0031831D01* -X0108759Y0031831D01* -X0108679Y0031865D01* -X0110186Y0030358D01* -X0110321Y0030033D01* -X0110388Y0030100D01* -X0112100Y0030100D01* -X0112100Y0025400D01* -X0112900Y0025400D01* -X0112900Y0030100D01* -X0113982Y0030100D01* -X0112900Y0029963D02* -X0112100Y0029963D01* -X0112100Y0028764D02* -X0112900Y0028764D01* -X0112900Y0027566D02* -X0112100Y0027566D01* -X0112100Y0026367D02* -X0112900Y0026367D01* -X0112921Y0031161D02* -X0109383Y0031161D01* -X0094410Y0031161D02* -X0090668Y0031161D01* -X0090668Y0030100D02* -X0090668Y0042251D01* -X0093065Y0044647D01* -X0093468Y0044814D01* -X0094410Y0045756D01* -X0094410Y0030100D01* -X0092900Y0030100D01* -X0092900Y0025400D01* -X0092100Y0025400D01* -X0092100Y0030100D01* -X0090668Y0030100D01* -X0092100Y0029963D02* -X0092900Y0029963D01* -X0092900Y0028764D02* -X0092100Y0028764D01* -X0092100Y0027566D02* -X0092900Y0027566D01* -X0092900Y0026367D02* -X0092100Y0026367D01* -X0090668Y0032360D02* -X0094410Y0032360D01* -X0094410Y0033558D02* -X0090668Y0033558D01* -X0090668Y0034757D02* -X0094410Y0034757D01* -X0094410Y0035955D02* -X0090668Y0035955D01* -X0090668Y0037154D02* -X0094410Y0037154D01* -X0094410Y0038352D02* -X0090668Y0038352D01* -X0090668Y0039551D02* -X0094410Y0039551D01* -X0094410Y0040749D02* -X0090668Y0040749D01* -X0090668Y0041948D02* -X0094410Y0041948D01* -X0094410Y0043146D02* -X0091564Y0043146D01* -X0092763Y0044345D02* -X0094410Y0044345D01* -X0094410Y0045543D02* -X0094198Y0045543D01* -X0090008Y0050337D02* -X0085665Y0050337D01* -X0084466Y0049139D02* -X0088595Y0049139D01* -X0087397Y0047940D02* -X0067803Y0047940D01* -X0068064Y0055132D02* -X0075181Y0055132D01* -X0075271Y0055080D02* -X0075754Y0054950D01* -X0077963Y0054950D01* -X0077963Y0059400D01* -X0074104Y0059400D01* -X0074104Y0056600D01* -X0074233Y0056117D01* -X0074484Y0055684D01* -X0074837Y0055330D01* -X0075271Y0055080D01* -X0074176Y0056330D02* -X0068287Y0056330D01* -X0068287Y0057529D02* -X0074104Y0057529D01* -X0074104Y0058727D02* -X0068287Y0058727D01* -X0060769Y0063521D02* -X0030743Y0063521D01* -X0030668Y0064720D02* -X0060769Y0064720D01* -X0060769Y0065918D02* -X0030668Y0065918D01* -X0031942Y0062323D02* -X0059960Y0062323D01* -X0067105Y0064720D02* -X0070378Y0064720D01* -X0069377Y0065918D02* -X0067105Y0065918D01* -X0067815Y0067117D02* -X0069017Y0067117D01* -X0069103Y0068315D02* -X0068847Y0068315D01* -X0069065Y0069514D02* -X0069647Y0069514D01* -X0069065Y0070712D02* -X0071134Y0070712D01* -X0075353Y0069514D02* -X0076854Y0069514D01* -X0075897Y0068315D02* -X0085395Y0068315D01* -X0088505Y0067117D02* -X0075983Y0067117D01* -X0075623Y0065918D02* -X0088505Y0065918D01* -X0092143Y0059926D02* -X0092442Y0059926D01* -X0092442Y0058727D02* -X0091565Y0058727D01* -X0091565Y0057529D02* -X0092442Y0057529D01* -X0092442Y0056330D02* -X0091561Y0056330D01* -X0090910Y0055132D02* -X0092442Y0055132D01* -X0092442Y0053933D02* -X0089180Y0053933D01* -X0088062Y0052734D02* -X0092427Y0052734D01* -X0091228Y0051536D02* -X0086863Y0051536D01* -X0086198Y0046742D02* -X0002568Y0046742D01* -X0002568Y0045543D02* -X0084999Y0045543D01* -X0084394Y0044345D02* -X0002568Y0044345D01* -X0002568Y0043146D02* -X0084331Y0043146D01* -X0084331Y0041948D02* -X0002568Y0041948D01* -X0002568Y0040749D02* -X0084331Y0040749D01* -X0084331Y0039551D02* -X0002568Y0039551D01* -X0002568Y0038352D02* -X0084331Y0038352D01* -X0084331Y0037154D02* -X0002568Y0037154D01* -X0002568Y0035955D02* -X0084331Y0035955D01* -X0084331Y0034757D02* -X0002568Y0034757D01* -X0002568Y0033558D02* -X0084331Y0033558D01* -X0084331Y0032360D02* -X0002568Y0032360D01* -X0002568Y0031161D02* -X0084331Y0031161D01* -X0082900Y0029963D02* -X0082100Y0029963D01* -X0082100Y0028764D02* -X0082900Y0028764D01* -X0082900Y0027566D02* -X0082100Y0027566D01* -X0082100Y0026367D02* -X0082900Y0026367D01* -X0082100Y0025169D02* -X0002568Y0025169D01* -X0002568Y0026367D02* -X0077400Y0026367D01* -X0077853Y0027566D02* -X0002568Y0027566D01* -X0002568Y0028764D02* -X0079052Y0028764D01* -X0080250Y0029963D02* -X0002568Y0029963D01* -X0002568Y0023970D02* -X0077400Y0023970D01* -X0077516Y0022772D02* -X0002568Y0022772D01* -X0002568Y0021573D02* -X0078714Y0021573D01* -X0079717Y0020375D02* -X0002568Y0020375D01* -X0002568Y0019176D02* -X0078518Y0019176D01* -X0077320Y0017978D02* -X0002568Y0017978D01* -X0002568Y0016779D02* -X0076731Y0016779D01* -X0076731Y0015581D02* -X0002568Y0015581D01* -X0002568Y0014382D02* -X0076731Y0014382D01* -X0076731Y0013184D02* -X0002568Y0013184D01* -X0002568Y0011985D02* -X0077357Y0011985D01* -X0078555Y0010787D02* -X0002568Y0010787D01* -X0002568Y0009588D02* -X0079754Y0009588D01* -X0079163Y0055132D02* -X0077963Y0055132D01* -X0077963Y0056330D02* -X0079163Y0056330D01* -X0079163Y0057529D02* -X0077963Y0057529D01* -X0077963Y0058727D02* -X0079163Y0058727D01* -X0079163Y0061124D02* -X0077963Y0061124D01* -X0077963Y0062323D02* -X0079163Y0062323D01* -X0079163Y0063521D02* -X0077963Y0063521D01* -X0077963Y0064720D02* -X0079163Y0064720D01* -X0074923Y0064720D02* -X0074622Y0064720D01* -X0100000Y0086142D02* -X0117264Y0086142D01* -X0117264Y0072559D01* -X0117264Y0067500D01* -X0127500Y0067500D01* -X0136063Y0067500D01* -X0137036Y0050337D02* -X0163731Y0050337D01* -X0163778Y0049139D02* -X0138099Y0049139D01* -X0138471Y0047940D02* -X0164274Y0047940D01* -X0165518Y0046742D02* -X0138408Y0046742D01* -X0129872Y0088690D02* -X0194782Y0088690D01* -X0193583Y0089888D02* -X0129872Y0089888D01* -X0124941Y0097953D02* -X0190000Y0097953D01* -X0162500Y0122500D02* -X0161240Y0122500D01* -X0088505Y0130638D02* -X0088441Y0130638D01* -X0088505Y0131836D02* -X0087614Y0131836D01* -X0088218Y0133035D02* -X0088505Y0133035D01* -X0088505Y0134233D02* -X0088218Y0134233D01* -X0088218Y0135432D02* -X0088505Y0135432D01* -X0088505Y0136630D02* -X0088218Y0136630D01* -X0088218Y0137829D02* -X0088505Y0137829D01* -X0088505Y0139027D02* -X0088218Y0139027D01* -X0088505Y0140226D02* -X0087678Y0140226D01* -X0087550Y0141424D02* -X0088505Y0141424D01* -X0088505Y0142623D02* -X0087550Y0142623D01* -X0082500Y0136063D02* -X0082500Y0127500D01* -X0087736Y0127500D01* -X0087736Y0117441D01* -X0087500Y0117500D02* -X0087500Y0110000D01* -X0080059Y0109764D02* -X0075000Y0109764D01* -X0072500Y0112264D01* -X0072500Y0116063D01* -X0066782Y0116256D02* -X0065538Y0116256D01* -X0066737Y0115057D02* -X0066782Y0115057D01* -X0066782Y0117454D02* -X0063974Y0117454D01* -X0063168Y0118653D02* -X0066782Y0118653D01* -X0067079Y0119851D02* -X0063168Y0119851D01* -X0063944Y0121050D02* -X0067471Y0121050D01* -X0067450Y0122248D02* -X0064065Y0122248D01* -X0064065Y0123447D02* -X0071900Y0123447D01* -X0071900Y0124537D02* -X0067450Y0124537D01* -X0067450Y0126746D01* -X0067580Y0127229D01* -X0067830Y0127663D01* -X0068184Y0128016D01* -X0068617Y0128267D01* -X0069100Y0128396D01* -X0071900Y0128396D01* -X0071900Y0124537D01* -X0071900Y0124645D02* -X0073100Y0124645D01* -X0073100Y0123447D02* -X0084568Y0123447D01* -X0084568Y0122248D02* -X0077550Y0122248D01* -X0077529Y0121050D02* -X0077590Y0121050D01* -X0077550Y0124645D02* -X0081112Y0124645D01* -X0079756Y0125844D02* -X0077550Y0125844D01* -X0077470Y0127042D02* -X0079331Y0127042D01* -X0079331Y0128241D02* -X0076428Y0128241D01* -X0079331Y0129439D02* -X0063742Y0129439D01* -X0064065Y0128241D02* -X0068572Y0128241D01* -X0067530Y0127042D02* -X0064065Y0127042D01* -X0064065Y0125844D02* -X0067450Y0125844D01* -X0067450Y0124645D02* -X0064065Y0124645D01* -X0060000Y0125000D02* -X0060000Y0113750D01* -X0060000Y0112500D01* -X0060000Y0113750D02* -X0063563Y0113750D01* -X0075423Y0101890D01* -X0080059Y0101890D01* -X0080059Y0099921D02* -X0050669Y0099921D01* -X0050669Y0106654D01* -X0040000Y0106654D01* -X0040000Y0106063D01* -X0045141Y0110263D02* -X0045650Y0110263D01* -X0046013Y0111462D02* -X0045050Y0111462D01* -X0045050Y0112660D02* -X0046013Y0112660D01* -X0046831Y0113859D02* -X0040600Y0113859D01* -X0040000Y0113937D02* -X0040000Y0113346D01* -X0050669Y0113346D01* -X0050000Y0112500D02* -X0050000Y0125000D01* -X0046831Y0124645D02* -X0043488Y0124645D01* -X0043491Y0124656D02* -X0043491Y0125000D01* -X0043491Y0125344D01* -X0043356Y0126018D01* -X0043093Y0126653D01* -X0042711Y0127225D01* -X0042225Y0127711D01* -X0041653Y0128093D01* -X0041018Y0128356D01* -X0040344Y0128491D01* -X0040000Y0128491D01* -X0039656Y0128491D01* -X0038982Y0128356D01* -X0038347Y0128093D01* -X0037775Y0127711D01* -X0037289Y0127225D01* -X0036907Y0126653D01* -X0036644Y0126018D01* -X0036509Y0125344D01* -X0036509Y0125000D01* -X0036509Y0124656D01* -X0036644Y0123982D01* -X0036907Y0123347D01* -X0037289Y0122775D01* -X0037775Y0122289D01* -X0038347Y0121907D01* -X0038982Y0121644D01* -X0039656Y0121509D01* -X0040000Y0121509D01* -X0040344Y0121509D01* -X0041018Y0121644D01* -X0041653Y0121907D01* -X0042225Y0122289D01* -X0042711Y0122775D01* -X0043093Y0123347D01* -X0043356Y0123982D01* -X0043491Y0124656D01* -X0043491Y0125000D02* -X0040000Y0125000D01* -X0043491Y0125000D01* -X0043391Y0125844D02* -X0046604Y0125844D01* -X0046604Y0127042D02* -X0042833Y0127042D01* -X0041297Y0128241D02* -X0046604Y0128241D01* -X0047107Y0129439D02* -X0020668Y0129439D01* -X0020668Y0128241D02* -X0038703Y0128241D01* -X0040000Y0128241D02* -X0040000Y0128241D01* -X0040000Y0128491D02* -X0040000Y0125000D01* -X0040000Y0125000D01* -X0040000Y0125000D01* -X0040000Y0128491D01* -X0040000Y0127042D02* -X0040000Y0127042D01* -X0040000Y0125844D02* -X0040000Y0125844D01* -X0040000Y0125000D02* -X0040000Y0125000D01* -X0040000Y0121509D01* -X0040000Y0125000D01* -X0040000Y0125000D01* -X0036509Y0125000D01* -X0040000Y0125000D01* -X0040000Y0124645D02* -X0040000Y0124645D01* -X0040000Y0123447D02* -X0040000Y0123447D01* -X0040000Y0122248D02* -X0040000Y0122248D01* -X0042164Y0122248D02* -X0046604Y0122248D01* -X0046604Y0123447D02* -X0043135Y0123447D01* -X0046772Y0121050D02* -X0020668Y0121050D01* -X0020668Y0122248D02* -X0037836Y0122248D01* -X0036865Y0123447D02* -X0020668Y0123447D01* -X0020668Y0124645D02* -X0036512Y0124645D01* -X0036609Y0125844D02* -X0020668Y0125844D01* -X0020668Y0127042D02* -X0037167Y0127042D01* -X0036600Y0118396D02* -X0036117Y0118267D01* -X0035684Y0118016D01* -X0035330Y0117663D01* -X0035080Y0117229D01* -X0034950Y0116746D01* -X0034950Y0114537D01* -X0039400Y0114537D01* -X0039400Y0118396D01* -X0036600Y0118396D01* -X0035210Y0117454D02* -X0020668Y0117454D01* -X0020668Y0116256D02* -X0034950Y0116256D01* -X0034950Y0115057D02* -X0020668Y0115057D01* -X0020668Y0113859D02* -X0039400Y0113859D01* -X0039400Y0115057D02* -X0040600Y0115057D01* -X0040600Y0116256D02* -X0039400Y0116256D01* -X0039400Y0117454D02* -X0040600Y0117454D01* -X0044790Y0117454D02* -X0046831Y0117454D01* -X0046831Y0118653D02* -X0020668Y0118653D01* -X0020668Y0119851D02* -X0046831Y0119851D01* -X0046093Y0116256D02* -X0045050Y0116256D01* -X0045050Y0115057D02* -X0046013Y0115057D01* -X0034950Y0112660D02* -X0020668Y0112660D01* -X0020668Y0111462D02* -X0034950Y0111462D01* -X0034859Y0110263D02* -X0020668Y0110263D01* -X0020668Y0109065D02* -X0034282Y0109065D01* -X0034282Y0107866D02* -X0020668Y0107866D01* -X0020668Y0106668D02* -X0034282Y0106668D01* -X0034282Y0105469D02* -X0020668Y0105469D01* -X0020668Y0104270D02* -X0034282Y0104270D01* -X0034282Y0103072D02* -X0020668Y0103072D01* -X0020668Y0101873D02* -X0034848Y0101873D01* -X0045152Y0101873D02* -X0047039Y0101873D01* -X0047501Y0100675D02* -X0020668Y0100675D01* -X0020668Y0099476D02* -X0047501Y0099476D01* -X0047920Y0098278D02* -X0020668Y0098278D01* -X0021339Y0097079D02* -X0049250Y0097079D01* -X0060000Y0105000D02* -X0066250Y0105000D01* -X0060000Y0105000D02* -X0060000Y0107500D01* -X0065693Y0095881D02* -X0022537Y0095881D01* -X0021979Y0077903D02* -X0002568Y0077903D01* -X0002568Y0076705D02* -X0021979Y0076705D01* -X0022047Y0075506D02* -X0002568Y0075506D01* -X0002568Y0074308D02* -X0021979Y0074308D01* -X0021979Y0073109D02* -X0002568Y0073109D01* -X0002568Y0071911D02* -X0021979Y0071911D01* -X0066063Y0092500D02* -X0076516Y0097953D01* -X0080059Y0097953D01* -X0073100Y0125844D02* -X0071900Y0125844D01* -X0071900Y0127042D02* -X0073100Y0127042D01* -X0073100Y0128241D02* -X0071900Y0128241D01* -X0077386Y0131836D02* -X0020668Y0131836D01* -X0020668Y0130638D02* -X0055673Y0130638D01* -X0051663Y0129439D02* -X0050463Y0129439D01* -X0050463Y0128241D02* -X0051663Y0128241D01* -X0062201Y0130638D02* -X0079331Y0130638D01* -X0031124Y0158203D02* -X0018168Y0158203D01* -X0018168Y0157005D02* -X0018961Y0157005D01* -X0018168Y0159402D02* -X0029680Y0159402D01* -X0021782Y0149814D02* -X0020732Y0149814D01* -X0020668Y0148615D02* -X0021782Y0148615D01* -X0021782Y0147417D02* -X0020668Y0147417D01* -X0020668Y0146218D02* -X0021782Y0146218D01* -X0022133Y0145020D02* -X0020668Y0145020D01* -X0020668Y0143821D02* -X0022450Y0143821D01* -X0022450Y0142623D02* -X0020668Y0142623D01* -X0026900Y0139027D02* -X0028100Y0139027D01* -X0028100Y0137829D02* -X0026900Y0137829D01* -X0026900Y0136630D02* -X0028100Y0136630D01* -X0210011Y0059926D02* -X0213124Y0059926D01* -X0213249Y0058727D02* -X0210770Y0058727D01* -X0283559Y0147417D02* -X0286132Y0147417D01* -X0286132Y0148615D02* -X0285235Y0148615D01* -X0286116Y0149814D02* -X0286132Y0149814D01* -X0285972Y0166593D02* -X0286132Y0166593D01* -X0286132Y0198953D02* -X0268797Y0198953D01* -X0269085Y0200151D02* -X0286132Y0200151D01* -X0269295Y0213335D02* -X0268166Y0213335D01* -X0275000Y0216437D02* -X0275000Y0217874D01* -X0261369Y0201350D02* -X0260403Y0201350D01* -X0260403Y0200151D02* -X0261369Y0200151D01* -X0261369Y0198953D02* -X0260403Y0198953D01* -X0221516Y0182174D02* -X0218484Y0182174D01* -X0219683Y0180975D02* -X0220317Y0180975D01* -X0240565Y0164196D02* -X0242481Y0164196D01* -X0242481Y0162998D02* -X0240960Y0162998D01* -X0240919Y0161799D02* -X0242481Y0161799D01* -X0242481Y0160601D02* -X0240429Y0160601D01* -X0241527Y0159402D02* -X0239142Y0159402D01* -D32* -X0246250Y0154980D02* -X0246250Y0177500D01* -X0262500Y0162500D02* -X0257500Y0157500D01* -X0257500Y0154980D01* -X0262500Y0162500D02* -X0277500Y0162500D01* -X0262500Y0140000D02* -X0257500Y0140000D01* -X0247500Y0140000D01* -X0262500Y0140000D02* -X0267500Y0135000D01* -X0267500Y0085000D01* -X0262500Y0080000D01* -X0240000Y0080000D01* -X0237500Y0080000D01* -X0237500Y0077500D01* -X0242500Y0077500D01* -X0242500Y0072500D01* -X0237500Y0077500D02* -X0222500Y0077500D01* -X0222500Y0073701D01* -X0228402Y0062201D02* -X0228402Y0060000D01* -X0244094Y0060000D01* -X0245000Y0060000D01* -X0245000Y0050000D01* -X0167500Y0050000D01* -X0167500Y0075000D01* -X0127500Y0075000D01* -X0245000Y0050000D02* -X0266250Y0050000D01* -X0267500Y0047500D01* -X0255906Y0060000D02* -X0255906Y0072500D01* -X0255000Y0072500D02* -X0267500Y0072500D01* -D33* -X0267500Y0075000D01* -X0272500Y0080000D01* -X0275000Y0080000D01* -X0267510Y0072500D02* -X0267500Y0072500D01* -X0244094Y0072500D02* -X0242500Y0072500D01* -X0240000Y0080000D02* -X0240000Y0081270D01* -X0237500Y0094980D02* -X0237500Y0095000D01* -X0237500Y0094980D02* -X0240000Y0096230D01* -X0237500Y0098701D02* -X0255157Y0098701D01* -X0255157Y0111299D02* -X0240000Y0111299D01* -X0222500Y0073701D02* -X0222500Y0063201D01* -X0267500Y0047500D02* -X0267500Y0037500D01* -X0277500Y0037500D01* -X0277500Y0027500D02* -X0267500Y0027500D01* -X0267500Y0017500D01* -X0277500Y0017500D01* -X0277500Y0047500D02* -X0267500Y0047500D01* -X0127500Y0067500D02* -X0127500Y0075000D01* -D34* -X0135000Y0047500D03* -X0072500Y0067500D03* -X0058750Y0081250D03* -X0040000Y0125000D03* -X0072500Y0137500D03* -X0080000Y0188750D03* -X0152500Y0175000D03* -X0170000Y0195000D03* -X0212500Y0207500D03* -X0237500Y0162500D03* -X0225000Y0135000D03* -X0240000Y0122500D03* -X0277500Y0137500D03* -X0275000Y0080000D03* -X0255000Y0032500D03* -X0207500Y0057500D03* -X0151250Y0112500D03* -X0150000Y0131250D03* -D35* -X0177500Y0147500D03* -X0197500Y0147500D03* -X0197500Y0157500D03* -X0190000Y0130000D03* -X0246250Y0177500D03* -X0082500Y0155000D03* -X0066250Y0105000D03* -M02* diff --git a/tests/gerber_files/detector_contour.gbr b/tests/gerber_files/detector_contour.gbr deleted file mode 100644 index 93adef01..00000000 --- a/tests/gerber_files/detector_contour.gbr +++ /dev/null @@ -1,26 +0,0 @@ -G04 MADE WITH FRITZING* -G04 WWW.FRITZING.ORG* -G04 DOUBLE SIDED* -G04 HOLES PLATED* -G04 CONTOUR ON CENTER OF CONTOUR VECTOR* -%ASAXBY*% -%FSLAX23Y23*% -%MOIN*% -%OFA0B0*% -%SFA1.0B1.0*% -%ADD10R,1.771650X1.181100*% -%ADD11C,0.008000*% -%ADD10C,0.008*% -%LNCONTOUR*% -G90* -G70* -G54D10* -G54D11* -X4Y1177D02* -X1768Y1177D01* -X1768Y4D01* -X4Y4D01* -X4Y1177D01* -D02* -G04 End of contour* -M02* \ No newline at end of file diff --git a/tests/gerber_files/detector_copper_bottom.gbr b/tests/gerber_files/detector_copper_bottom.gbr deleted file mode 100644 index d3bca481..00000000 --- a/tests/gerber_files/detector_copper_bottom.gbr +++ /dev/null @@ -1,2146 +0,0 @@ -G04 MADE WITH FRITZING* -G04 WWW.FRITZING.ORG* -G04 DOUBLE SIDED* -G04 HOLES PLATED* -G04 CONTOUR ON CENTER OF CONTOUR VECTOR* -%ASAXBY*% -%FSLAX23Y23*% -%MOIN*% -%OFA0B0*% -%SFA1.0B1.0*% -%ADD10C,0.075000*% -%ADD11C,0.099055*% -%ADD12C,0.078740*% -%ADD13R,0.075000X0.075000*% -%ADD14C,0.048000*% -%ADD15C,0.020000*% -%ADD16R,0.001000X0.001000*% -%LNCOPPER0*% -G90* -G70* -G54D10* -X1149Y872D03* -X1349Y872D03* -X749Y722D03* -X749Y522D03* -X1149Y522D03* -X1449Y522D03* -X1149Y422D03* -X1449Y422D03* -X1149Y322D03* -X1449Y322D03* -X1149Y222D03* -X1449Y222D03* -X949Y472D03* -X949Y72D03* -G54D11* -X749Y972D03* -X599Y972D03* -X349Y322D03* -X349Y472D03* -X349Y672D03* -X349Y822D03* -G54D10* -X699Y122D03* -X699Y322D03* -G54D12* -X699Y222D03* -X949Y972D03* -X749Y622D03* -X1049Y222D03* -X1249Y872D03* -G54D13* -X1149Y872D03* -X1149Y522D03* -G54D14* -X949Y373D02* -X949Y433D01* -D02* -X999Y323D02* -X949Y373D01* -D02* -X1109Y322D02* -X999Y323D01* -D02* -X499Y873D02* -X1109Y872D01* -D02* -X1299Y73D02* -X989Y72D01* -D02* -X1399Y322D02* -X1349Y272D01* -D02* -X1349Y272D02* -X1349Y122D01* -D02* -X1349Y122D02* -X1299Y73D01* -D02* -X1409Y322D02* -X1399Y322D01* -D02* -X909Y72D02* -X749Y73D01* -D02* -X749Y73D02* -X727Y94D01* -D02* -X649Y522D02* -X709Y522D01* -D02* -X599Y473D02* -X649Y522D01* -D02* -X401Y472D02* -X599Y473D01* -D02* -X789Y522D02* -X899Y522D01* -D02* -X709Y722D02* -X599Y722D01* -D02* -X599Y722D02* -X549Y673D01* -D02* -X549Y673D02* -X401Y672D01* -D02* -X1149Y562D02* -X1149Y833D01* -D02* -X499Y972D02* -X499Y873D01* -D02* -X547Y972D02* -X499Y972D01* -D02* -X699Y283D02* -X699Y260D01* -D02* -X749Y562D02* -X749Y584D01* -D02* -X499Y873D02* -X499Y972D01* -D02* -X499Y972D02* -X547Y972D01* -D02* -X401Y823D02* -X449Y823D01* -D02* -X899Y522D02* -X921Y500D01* -D02* -X1309Y872D02* -X1287Y872D01* -D02* -X449Y823D02* -X499Y873D01* -D02* -X1349Y422D02* -X1349Y833D01* -D02* -X1189Y422D02* -X1349Y422D01* -D02* -X1399Y322D02* -X1409Y322D01* -D02* -X1349Y372D02* -X1399Y322D01* -D02* -X1349Y422D02* -X1349Y372D01* -D02* -X1189Y422D02* -X1349Y422D01* -D02* -X801Y972D02* -X911Y972D01* -D02* -X1109Y222D02* -X1087Y222D01* -D02* -X401Y322D02* -X659Y322D01* -D02* -X1399Y972D02* -X987Y972D01* -D02* -X1449Y923D02* -X1399Y972D01* -D02* -X1449Y562D02* -X1449Y923D01* -G54D15* -X776Y695D02* -X721Y695D01* -X721Y750D01* -X776Y750D01* -X776Y695D01* -D02* -X671Y150D02* -X726Y150D01* -X726Y95D01* -X671Y95D01* -X671Y150D01* -D02* -G54D16* -X766Y1112D02* -X769Y1112D01* -X764Y1111D02* -X771Y1111D01* -X763Y1110D02* -X772Y1110D01* -X762Y1109D02* -X772Y1109D01* -X762Y1108D02* -X773Y1108D01* -X762Y1107D02* -X773Y1107D01* -X762Y1106D02* -X773Y1106D01* -X762Y1105D02* -X773Y1105D01* -X762Y1104D02* -X773Y1104D01* -X762Y1103D02* -X773Y1103D01* -X762Y1102D02* -X773Y1102D01* -X762Y1101D02* -X773Y1101D01* -X762Y1100D02* -X773Y1100D01* -X762Y1099D02* -X773Y1099D01* -X762Y1098D02* -X773Y1098D01* -X762Y1097D02* -X773Y1097D01* -X762Y1096D02* -X773Y1096D01* -X762Y1095D02* -X773Y1095D01* -X762Y1094D02* -X773Y1094D01* -X762Y1093D02* -X773Y1093D01* -X762Y1092D02* -X773Y1092D01* -X762Y1091D02* -X773Y1091D01* -X762Y1090D02* -X773Y1090D01* -X762Y1089D02* -X773Y1089D01* -X566Y1088D02* -X618Y1088D01* -X741Y1088D02* -X793Y1088D01* -X565Y1087D02* -X620Y1087D01* -X740Y1087D02* -X795Y1087D01* -X564Y1086D02* -X621Y1086D01* -X739Y1086D02* -X796Y1086D01* -X563Y1085D02* -X621Y1085D01* -X738Y1085D02* -X796Y1085D01* -X563Y1084D02* -X622Y1084D01* -X738Y1084D02* -X796Y1084D01* -X563Y1083D02* -X622Y1083D01* -X738Y1083D02* -X796Y1083D01* -X563Y1082D02* -X622Y1082D01* -X738Y1082D02* -X796Y1082D01* -X563Y1081D02* -X622Y1081D01* -X738Y1081D02* -X796Y1081D01* -X563Y1080D02* -X622Y1080D01* -X738Y1080D02* -X796Y1080D01* -X563Y1079D02* -X622Y1079D01* -X739Y1079D02* -X795Y1079D01* -X563Y1078D02* -X622Y1078D01* -X739Y1078D02* -X795Y1078D01* -X563Y1077D02* -X622Y1077D01* -X741Y1077D02* -X794Y1077D01* -X563Y1076D02* -X622Y1076D01* -X762Y1076D02* -X773Y1076D01* -X563Y1075D02* -X621Y1075D01* -X762Y1075D02* -X773Y1075D01* -X563Y1074D02* -X621Y1074D01* -X762Y1074D02* -X773Y1074D01* -X564Y1073D02* -X620Y1073D01* -X762Y1073D02* -X773Y1073D01* -X565Y1072D02* -X619Y1072D01* -X762Y1072D02* -X773Y1072D01* -X569Y1071D02* -X615Y1071D01* -X762Y1071D02* -X773Y1071D01* -X762Y1070D02* -X773Y1070D01* -X762Y1069D02* -X773Y1069D01* -X762Y1068D02* -X773Y1068D01* -X762Y1067D02* -X773Y1067D01* -X762Y1066D02* -X773Y1066D01* -X762Y1065D02* -X773Y1065D01* -X762Y1064D02* -X773Y1064D01* -X762Y1063D02* -X773Y1063D01* -X762Y1062D02* -X773Y1062D01* -X762Y1061D02* -X773Y1061D01* -X762Y1060D02* -X773Y1060D01* -X762Y1059D02* -X773Y1059D01* -X762Y1058D02* -X773Y1058D01* -X762Y1057D02* -X773Y1057D01* -X762Y1056D02* -X773Y1056D01* -X763Y1055D02* -X772Y1055D01* -X763Y1054D02* -X771Y1054D01* -X765Y1053D02* -X770Y1053D01* -X1661Y878D02* -X1697Y878D01* -X1658Y877D02* -X1698Y877D01* -X1656Y876D02* -X1700Y876D01* -X1653Y875D02* -X1701Y875D01* -X1651Y874D02* -X1701Y874D01* -X1648Y873D02* -X1702Y873D01* -X1645Y872D02* -X1702Y872D01* -X1643Y871D02* -X1702Y871D01* -X1640Y870D02* -X1702Y870D01* -X1638Y869D02* -X1703Y869D01* -X1635Y868D02* -X1702Y868D01* -X1633Y867D02* -X1702Y867D01* -X1630Y866D02* -X1702Y866D01* -X1627Y865D02* -X1701Y865D01* -X1625Y864D02* -X1701Y864D01* -X1622Y863D02* -X1700Y863D01* -X1620Y862D02* -X1699Y862D01* -X1617Y861D02* -X1697Y861D01* -X1615Y860D02* -X1664Y860D01* -X1612Y859D02* -X1661Y859D01* -X1609Y858D02* -X1659Y858D01* -X1607Y857D02* -X1656Y857D01* -X1604Y856D02* -X1653Y856D01* -X1602Y855D02* -X1651Y855D01* -X1599Y854D02* -X1648Y854D01* -X1597Y853D02* -X1646Y853D01* -X1594Y852D02* -X1643Y852D01* -X1592Y851D02* -X1641Y851D01* -X1589Y850D02* -X1638Y850D01* -X1586Y849D02* -X1635Y849D01* -X1584Y848D02* -X1633Y848D01* -X1581Y847D02* -X1630Y847D01* -X1579Y846D02* -X1628Y846D01* -X1576Y845D02* -X1625Y845D01* -X1574Y844D02* -X1623Y844D01* -X1571Y843D02* -X1620Y843D01* -X1569Y842D02* -X1618Y842D01* -X1567Y841D02* -X1615Y841D01* -X1566Y840D02* -X1612Y840D01* -X1565Y839D02* -X1610Y839D01* -X1564Y838D02* -X1607Y838D01* -X1564Y837D02* -X1605Y837D01* -X1563Y836D02* -X1602Y836D01* -X1563Y835D02* -X1600Y835D01* -X1563Y834D02* -X1597Y834D01* -X1563Y833D02* -X1599Y833D01* -X1563Y832D02* -X1601Y832D01* -X1564Y831D02* -X1604Y831D01* -X1564Y830D02* -X1606Y830D01* -X1564Y829D02* -X1609Y829D01* -X1565Y828D02* -X1611Y828D01* -X1566Y827D02* -X1614Y827D01* -X1567Y826D02* -X1616Y826D01* -X1569Y825D02* -X1619Y825D01* -X1572Y824D02* -X1622Y824D01* -X1574Y823D02* -X1624Y823D01* -X1577Y822D02* -X1627Y822D01* -X1580Y821D02* -X1629Y821D01* -X1582Y820D02* -X1632Y820D01* -X1585Y819D02* -X1634Y819D01* -X1587Y818D02* -X1637Y818D01* -X1590Y817D02* -X1639Y817D01* -X1592Y816D02* -X1642Y816D01* -X1595Y815D02* -X1645Y815D01* -X1598Y814D02* -X1647Y814D01* -X1600Y813D02* -X1650Y813D01* -X1603Y812D02* -X1652Y812D01* -X1605Y811D02* -X1655Y811D01* -X1608Y810D02* -X1657Y810D01* -X1610Y809D02* -X1660Y809D01* -X1613Y808D02* -X1662Y808D01* -X1616Y807D02* -X1695Y807D01* -X1618Y806D02* -X1698Y806D01* -X1621Y805D02* -X1699Y805D01* -X1623Y804D02* -X1700Y804D01* -X1626Y803D02* -X1701Y803D01* -X1628Y802D02* -X1702Y802D01* -X1631Y801D02* -X1702Y801D01* -X1634Y800D02* -X1702Y800D01* -X1636Y799D02* -X1702Y799D01* -X1639Y798D02* -X1703Y798D01* -X1641Y797D02* -X1702Y797D01* -X1644Y796D02* -X1702Y796D01* -X1646Y795D02* -X1702Y795D01* -X1649Y794D02* -X1702Y794D01* -X1652Y793D02* -X1701Y793D01* -X1654Y792D02* -X1700Y792D01* -X1657Y791D02* -X1699Y791D01* -X1659Y790D02* -X1698Y790D01* -X1662Y789D02* -X1694Y789D01* -X191Y786D02* -X194Y786D01* -X106Y785D02* -X117Y785D01* -X189Y785D02* -X198Y785D01* -X104Y784D02* -X119Y784D01* -X187Y784D02* -X200Y784D01* -X102Y783D02* -X121Y783D01* -X186Y783D02* -X202Y783D01* -X101Y782D02* -X122Y782D01* -X186Y782D02* -X204Y782D01* -X100Y781D02* -X123Y781D01* -X185Y781D02* -X205Y781D01* -X99Y780D02* -X125Y780D01* -X185Y780D02* -X206Y780D01* -X98Y779D02* -X126Y779D01* -X185Y779D02* -X207Y779D01* -X97Y778D02* -X127Y778D01* -X185Y778D02* -X208Y778D01* -X97Y777D02* -X128Y777D01* -X185Y777D02* -X208Y777D01* -X96Y776D02* -X130Y776D01* -X185Y776D02* -X209Y776D01* -X96Y775D02* -X131Y775D01* -X186Y775D02* -X210Y775D01* -X96Y774D02* -X132Y774D01* -X186Y774D02* -X210Y774D01* -X95Y773D02* -X134Y773D01* -X187Y773D02* -X211Y773D01* -X95Y772D02* -X135Y772D01* -X188Y772D02* -X211Y772D01* -X95Y771D02* -X136Y771D01* -X191Y771D02* -X211Y771D01* -X95Y770D02* -X109Y770D01* -X113Y770D02* -X137Y770D01* -X195Y770D02* -X211Y770D01* -X95Y769D02* -X109Y769D01* -X114Y769D02* -X139Y769D01* -X196Y769D02* -X212Y769D01* -X95Y768D02* -X109Y768D01* -X116Y768D02* -X140Y768D01* -X197Y768D02* -X212Y768D01* -X95Y767D02* -X109Y767D01* -X117Y767D02* -X141Y767D01* -X197Y767D02* -X212Y767D01* -X95Y766D02* -X109Y766D01* -X118Y766D02* -X143Y766D01* -X198Y766D02* -X212Y766D01* -X95Y765D02* -X109Y765D01* -X120Y765D02* -X144Y765D01* -X198Y765D02* -X212Y765D01* -X95Y764D02* -X109Y764D01* -X121Y764D02* -X145Y764D01* -X198Y764D02* -X212Y764D01* -X95Y763D02* -X109Y763D01* -X122Y763D02* -X146Y763D01* -X198Y763D02* -X212Y763D01* -X95Y762D02* -X109Y762D01* -X123Y762D02* -X148Y762D01* -X198Y762D02* -X212Y762D01* -X95Y761D02* -X109Y761D01* -X125Y761D02* -X149Y761D01* -X198Y761D02* -X212Y761D01* -X95Y760D02* -X109Y760D01* -X126Y760D02* -X150Y760D01* -X198Y760D02* -X212Y760D01* -X95Y759D02* -X109Y759D01* -X127Y759D02* -X152Y759D01* -X198Y759D02* -X212Y759D01* -X95Y758D02* -X109Y758D01* -X129Y758D02* -X153Y758D01* -X198Y758D02* -X212Y758D01* -X95Y757D02* -X109Y757D01* -X130Y757D02* -X154Y757D01* -X198Y757D02* -X212Y757D01* -X95Y756D02* -X109Y756D01* -X131Y756D02* -X155Y756D01* -X198Y756D02* -X212Y756D01* -X95Y755D02* -X109Y755D01* -X132Y755D02* -X157Y755D01* -X198Y755D02* -X212Y755D01* -X95Y754D02* -X109Y754D01* -X134Y754D02* -X158Y754D01* -X198Y754D02* -X212Y754D01* -X95Y753D02* -X109Y753D01* -X135Y753D02* -X159Y753D01* -X198Y753D02* -X212Y753D01* -X95Y752D02* -X109Y752D01* -X136Y752D02* -X161Y752D01* -X198Y752D02* -X212Y752D01* -X95Y751D02* -X109Y751D01* -X138Y751D02* -X162Y751D01* -X198Y751D02* -X212Y751D01* -X95Y750D02* -X109Y750D01* -X139Y750D02* -X163Y750D01* -X198Y750D02* -X212Y750D01* -X95Y749D02* -X109Y749D01* -X140Y749D02* -X164Y749D01* -X198Y749D02* -X212Y749D01* -X95Y748D02* -X109Y748D01* -X141Y748D02* -X166Y748D01* -X198Y748D02* -X212Y748D01* -X1569Y748D02* -X1620Y748D01* -X95Y747D02* -X109Y747D01* -X143Y747D02* -X167Y747D01* -X198Y747D02* -X212Y747D01* -X1567Y747D02* -X1622Y747D01* -X95Y746D02* -X109Y746D01* -X144Y746D02* -X168Y746D01* -X198Y746D02* -X212Y746D01* -X1566Y746D02* -X1623Y746D01* -X95Y745D02* -X109Y745D01* -X145Y745D02* -X170Y745D01* -X198Y745D02* -X212Y745D01* -X1565Y745D02* -X1624Y745D01* -X95Y744D02* -X109Y744D01* -X147Y744D02* -X171Y744D01* -X198Y744D02* -X212Y744D01* -X1565Y744D02* -X1625Y744D01* -X95Y743D02* -X109Y743D01* -X148Y743D02* -X172Y743D01* -X198Y743D02* -X212Y743D01* -X1564Y743D02* -X1626Y743D01* -X95Y742D02* -X109Y742D01* -X149Y742D02* -X173Y742D01* -X198Y742D02* -X212Y742D01* -X1564Y742D02* -X1626Y742D01* -X95Y741D02* -X109Y741D01* -X151Y741D02* -X175Y741D01* -X198Y741D02* -X212Y741D01* -X1563Y741D02* -X1626Y741D01* -X95Y740D02* -X109Y740D01* -X152Y740D02* -X176Y740D01* -X198Y740D02* -X212Y740D01* -X1563Y740D02* -X1626Y740D01* -X95Y739D02* -X109Y739D01* -X153Y739D02* -X177Y739D01* -X198Y739D02* -X212Y739D01* -X1563Y739D02* -X1626Y739D01* -X95Y738D02* -X109Y738D01* -X154Y738D02* -X179Y738D01* -X198Y738D02* -X212Y738D01* -X1563Y738D02* -X1626Y738D01* -X95Y737D02* -X109Y737D01* -X156Y737D02* -X180Y737D01* -X198Y737D02* -X212Y737D01* -X1563Y737D02* -X1626Y737D01* -X95Y736D02* -X109Y736D01* -X157Y736D02* -X181Y736D01* -X198Y736D02* -X212Y736D01* -X1563Y736D02* -X1626Y736D01* -X95Y735D02* -X109Y735D01* -X158Y735D02* -X182Y735D01* -X198Y735D02* -X212Y735D01* -X1563Y735D02* -X1626Y735D01* -X95Y734D02* -X109Y734D01* -X160Y734D02* -X184Y734D01* -X198Y734D02* -X212Y734D01* -X1563Y734D02* -X1626Y734D01* -X95Y733D02* -X109Y733D01* -X161Y733D02* -X185Y733D01* -X198Y733D02* -X212Y733D01* -X1563Y733D02* -X1626Y733D01* -X95Y732D02* -X109Y732D01* -X162Y732D02* -X186Y732D01* -X198Y732D02* -X212Y732D01* -X1563Y732D02* -X1626Y732D01* -X95Y731D02* -X109Y731D01* -X163Y731D02* -X188Y731D01* -X198Y731D02* -X212Y731D01* -X1563Y731D02* -X1626Y731D01* -X95Y730D02* -X109Y730D01* -X165Y730D02* -X189Y730D01* -X198Y730D02* -X212Y730D01* -X1563Y730D02* -X1581Y730D01* -X1609Y730D02* -X1626Y730D01* -X95Y729D02* -X110Y729D01* -X166Y729D02* -X190Y729D01* -X198Y729D02* -X212Y729D01* -X1563Y729D02* -X1580Y729D01* -X1609Y729D02* -X1626Y729D01* -X95Y728D02* -X110Y728D01* -X167Y728D02* -X191Y728D01* -X198Y728D02* -X212Y728D01* -X1563Y728D02* -X1580Y728D01* -X1609Y728D02* -X1626Y728D01* -X95Y727D02* -X111Y727D01* -X169Y727D02* -X193Y727D01* -X198Y727D02* -X212Y727D01* -X1563Y727D02* -X1580Y727D01* -X1609Y727D02* -X1626Y727D01* -X96Y726D02* -X114Y726D01* -X170Y726D02* -X194Y726D01* -X196Y726D02* -X212Y726D01* -X1563Y726D02* -X1580Y726D01* -X1609Y726D02* -X1626Y726D01* -X96Y725D02* -X118Y725D01* -X171Y725D02* -X212Y725D01* -X1563Y725D02* -X1580Y725D01* -X1609Y725D02* -X1626Y725D01* -X96Y724D02* -X119Y724D01* -X172Y724D02* -X212Y724D01* -X1563Y724D02* -X1580Y724D01* -X1609Y724D02* -X1626Y724D01* -X97Y723D02* -X120Y723D01* -X174Y723D02* -X211Y723D01* -X1563Y723D02* -X1580Y723D01* -X1609Y723D02* -X1626Y723D01* -X97Y722D02* -X121Y722D01* -X175Y722D02* -X211Y722D01* -X1563Y722D02* -X1580Y722D01* -X1609Y722D02* -X1626Y722D01* -X98Y721D02* -X122Y721D01* -X176Y721D02* -X211Y721D01* -X1563Y721D02* -X1580Y721D01* -X1609Y721D02* -X1626Y721D01* -X98Y720D02* -X122Y720D01* -X178Y720D02* -X210Y720D01* -X1563Y720D02* -X1580Y720D01* -X1609Y720D02* -X1626Y720D01* -X99Y719D02* -X122Y719D01* -X179Y719D02* -X210Y719D01* -X1563Y719D02* -X1580Y719D01* -X1609Y719D02* -X1626Y719D01* -X100Y718D02* -X122Y718D01* -X180Y718D02* -X209Y718D01* -X1563Y718D02* -X1580Y718D01* -X1609Y718D02* -X1626Y718D01* -X101Y717D02* -X122Y717D01* -X181Y717D02* -X208Y717D01* -X1563Y717D02* -X1580Y717D01* -X1609Y717D02* -X1626Y717D01* -X102Y716D02* -X122Y716D01* -X183Y716D02* -X207Y716D01* -X1563Y716D02* -X1580Y716D01* -X1609Y716D02* -X1626Y716D01* -X103Y715D02* -X121Y715D01* -X184Y715D02* -X206Y715D01* -X1563Y715D02* -X1580Y715D01* -X1609Y715D02* -X1626Y715D01* -X104Y714D02* -X121Y714D01* -X185Y714D02* -X205Y714D01* -X1563Y714D02* -X1580Y714D01* -X1609Y714D02* -X1626Y714D01* -X106Y713D02* -X120Y713D01* -X187Y713D02* -X204Y713D01* -X1563Y713D02* -X1580Y713D01* -X1609Y713D02* -X1626Y713D01* -X108Y712D02* -X119Y712D01* -X189Y712D02* -X202Y712D01* -X1563Y712D02* -X1580Y712D01* -X1609Y712D02* -X1626Y712D01* -X112Y711D02* -X117Y711D01* -X192Y711D02* -X198Y711D01* -X1563Y711D02* -X1580Y711D01* -X1609Y711D02* -X1626Y711D01* -X1563Y710D02* -X1580Y710D01* -X1609Y710D02* -X1626Y710D01* -X1563Y709D02* -X1580Y709D01* -X1609Y709D02* -X1626Y709D01* -X1563Y708D02* -X1580Y708D01* -X1609Y708D02* -X1626Y708D01* -X1563Y707D02* -X1580Y707D01* -X1609Y707D02* -X1626Y707D01* -X1563Y706D02* -X1580Y706D01* -X1609Y706D02* -X1626Y706D01* -X1563Y705D02* -X1580Y705D01* -X1609Y705D02* -X1626Y705D01* -X1563Y704D02* -X1580Y704D01* -X1609Y704D02* -X1626Y704D01* -X1563Y703D02* -X1580Y703D01* -X1609Y703D02* -X1626Y703D01* -X1563Y702D02* -X1580Y702D01* -X1609Y702D02* -X1626Y702D01* -X1563Y701D02* -X1580Y701D01* -X1609Y701D02* -X1626Y701D01* -X1563Y700D02* -X1580Y700D01* -X1609Y700D02* -X1626Y700D01* -X1563Y699D02* -X1580Y699D01* -X1609Y699D02* -X1626Y699D01* -X1563Y698D02* -X1580Y698D01* -X1609Y698D02* -X1626Y698D01* -X1563Y697D02* -X1580Y697D01* -X1609Y697D02* -X1626Y697D01* -X1563Y696D02* -X1580Y696D01* -X1609Y696D02* -X1626Y696D01* -X1563Y695D02* -X1580Y695D01* -X1609Y695D02* -X1626Y695D01* -X1563Y694D02* -X1580Y694D01* -X1609Y694D02* -X1626Y694D01* -X1563Y693D02* -X1580Y693D01* -X1609Y693D02* -X1626Y693D01* -X1563Y692D02* -X1580Y692D01* -X1609Y692D02* -X1626Y692D01* -X1563Y691D02* -X1580Y691D01* -X1609Y691D02* -X1626Y691D01* -X1563Y690D02* -X1580Y690D01* -X1609Y690D02* -X1626Y690D01* -X1563Y689D02* -X1580Y689D01* -X1609Y689D02* -X1626Y689D01* -X1563Y688D02* -X1580Y688D01* -X1609Y688D02* -X1626Y688D01* -X1563Y687D02* -X1580Y687D01* -X1609Y687D02* -X1626Y687D01* -X1563Y686D02* -X1580Y686D01* -X1609Y686D02* -X1626Y686D01* -X1563Y685D02* -X1580Y685D01* -X1609Y685D02* -X1626Y685D01* -X1690Y685D02* -X1698Y685D01* -X1563Y684D02* -X1580Y684D01* -X1609Y684D02* -X1626Y684D01* -X1689Y684D02* -X1699Y684D01* -X1563Y683D02* -X1580Y683D01* -X1609Y683D02* -X1626Y683D01* -X1688Y683D02* -X1700Y683D01* -X1563Y682D02* -X1580Y682D01* -X1609Y682D02* -X1626Y682D01* -X1687Y682D02* -X1701Y682D01* -X1563Y681D02* -X1580Y681D01* -X1609Y681D02* -X1626Y681D01* -X1686Y681D02* -X1702Y681D01* -X1563Y680D02* -X1580Y680D01* -X1609Y680D02* -X1626Y680D01* -X1686Y680D02* -X1702Y680D01* -X1563Y679D02* -X1580Y679D01* -X1609Y679D02* -X1626Y679D01* -X1686Y679D02* -X1702Y679D01* -X1563Y678D02* -X1580Y678D01* -X1609Y678D02* -X1626Y678D01* -X1685Y678D02* -X1702Y678D01* -X1563Y677D02* -X1581Y677D01* -X1609Y677D02* -X1627Y677D01* -X1685Y677D02* -X1703Y677D01* -X1563Y676D02* -X1703Y676D01* -X1563Y675D02* -X1703Y675D01* -X1563Y674D02* -X1703Y674D01* -X1563Y673D02* -X1703Y673D01* -X1563Y672D02* -X1703Y672D01* -X1563Y671D02* -X1703Y671D01* -X1563Y670D02* -X1703Y670D01* -X1563Y669D02* -X1703Y669D01* -X1563Y668D02* -X1703Y668D01* -X1563Y667D02* -X1702Y667D01* -X1563Y666D02* -X1702Y666D01* -X1564Y665D02* -X1702Y665D01* -X1564Y664D02* -X1702Y664D01* -X1565Y663D02* -X1701Y663D01* -X1566Y662D02* -X1700Y662D01* -X1567Y661D02* -X1699Y661D01* -X1568Y660D02* -X1698Y660D01* -X1572Y659D02* -X1694Y659D01* -X1623Y618D02* -X1635Y618D01* -X1621Y617D02* -X1637Y617D01* -X1620Y616D02* -X1639Y616D01* -X1619Y615D02* -X1640Y615D01* -X1618Y614D02* -X1640Y614D01* -X1617Y613D02* -X1641Y613D01* -X1617Y612D02* -X1641Y612D01* -X1617Y611D02* -X1641Y611D01* -X1617Y610D02* -X1642Y610D01* -X1617Y609D02* -X1642Y609D01* -X1617Y608D02* -X1642Y608D01* -X1617Y607D02* -X1642Y607D01* -X1617Y606D02* -X1642Y606D01* -X1617Y605D02* -X1642Y605D01* -X1617Y604D02* -X1642Y604D01* -X1617Y603D02* -X1642Y603D01* -X1617Y602D02* -X1642Y602D01* -X1617Y601D02* -X1642Y601D01* -X1617Y600D02* -X1642Y600D01* -X1617Y599D02* -X1642Y599D01* -X1617Y598D02* -X1642Y598D01* -X1617Y597D02* -X1642Y597D01* -X1617Y596D02* -X1642Y596D01* -X1617Y595D02* -X1642Y595D01* -X1617Y594D02* -X1642Y594D01* -X1617Y593D02* -X1642Y593D01* -X1617Y592D02* -X1642Y592D01* -X1617Y591D02* -X1642Y591D01* -X1617Y590D02* -X1642Y590D01* -X1617Y589D02* -X1642Y589D01* -X1617Y588D02* -X1642Y588D01* -X1617Y587D02* -X1642Y587D01* -X1617Y586D02* -X1642Y586D01* -X1617Y585D02* -X1642Y585D01* -X1617Y584D02* -X1642Y584D01* -X1617Y583D02* -X1642Y583D01* -X1617Y582D02* -X1642Y582D01* -X1617Y581D02* -X1642Y581D01* -X1617Y580D02* -X1642Y580D01* -X1617Y579D02* -X1642Y579D01* -X1617Y578D02* -X1642Y578D01* -X1617Y577D02* -X1642Y577D01* -X1617Y576D02* -X1642Y576D01* -X1617Y575D02* -X1642Y575D01* -X1617Y574D02* -X1642Y574D01* -X1617Y573D02* -X1642Y573D01* -X1617Y572D02* -X1642Y572D01* -X1617Y571D02* -X1642Y571D01* -X1617Y570D02* -X1642Y570D01* -X1617Y569D02* -X1642Y569D01* -X1617Y568D02* -X1642Y568D01* -X1617Y567D02* -X1642Y567D01* -X1617Y566D02* -X1642Y566D01* -X1617Y565D02* -X1642Y565D01* -X1617Y564D02* -X1642Y564D01* -X1617Y563D02* -X1642Y563D01* -X1617Y562D02* -X1642Y562D01* -X1617Y561D02* -X1642Y561D01* -X1617Y560D02* -X1642Y560D01* -X1617Y559D02* -X1642Y559D01* -X1617Y558D02* -X1642Y558D01* -X1617Y557D02* -X1642Y557D01* -X1617Y556D02* -X1642Y556D01* -X1617Y555D02* -X1642Y555D01* -X1617Y554D02* -X1642Y554D01* -X1617Y553D02* -X1642Y553D01* -X1617Y552D02* -X1642Y552D01* -X1617Y551D02* -X1642Y551D01* -X1617Y550D02* -X1642Y550D01* -X1617Y549D02* -X1642Y549D01* -X1617Y548D02* -X1642Y548D01* -X1617Y547D02* -X1642Y547D01* -X1617Y546D02* -X1642Y546D01* -X1617Y545D02* -X1642Y545D01* -X1617Y544D02* -X1642Y544D01* -X1617Y543D02* -X1642Y543D01* -X1617Y542D02* -X1642Y542D01* -X1617Y541D02* -X1642Y541D01* -X1617Y540D02* -X1642Y540D01* -X1617Y539D02* -X1642Y539D01* -X1617Y538D02* -X1642Y538D01* -X1617Y537D02* -X1642Y537D01* -X1617Y536D02* -X1641Y536D01* -X1617Y535D02* -X1641Y535D01* -X1618Y534D02* -X1641Y534D01* -X1618Y533D02* -X1640Y533D01* -X1619Y532D02* -X1639Y532D01* -X1620Y531D02* -X1638Y531D01* -X1621Y530D02* -X1637Y530D01* -X1625Y529D02* -X1633Y529D01* -X1627Y488D02* -X1638Y488D01* -X1623Y487D02* -X1643Y487D01* -X1620Y486D02* -X1646Y486D01* -X1617Y485D02* -X1649Y485D01* -X1615Y484D02* -X1651Y484D01* -X1613Y483D02* -X1653Y483D01* -X1611Y482D02* -X1655Y482D01* -X1609Y481D02* -X1657Y481D01* -X1607Y480D02* -X1659Y480D01* -X1605Y479D02* -X1661Y479D01* -X1603Y478D02* -X1663Y478D01* -X1601Y477D02* -X1665Y477D01* -X1599Y476D02* -X1667Y476D01* -X1597Y475D02* -X1669Y475D01* -X1595Y474D02* -X1671Y474D01* -X1593Y473D02* -X1673Y473D01* -X1591Y472D02* -X1675Y472D01* -X1589Y471D02* -X1677Y471D01* -X1587Y470D02* -X1629Y470D01* -X1637Y470D02* -X1679Y470D01* -X1585Y469D02* -X1625Y469D01* -X1641Y469D02* -X1681Y469D01* -X1583Y468D02* -X1622Y468D01* -X1643Y468D02* -X1683Y468D01* -X1581Y467D02* -X1620Y467D01* -X1645Y467D02* -X1685Y467D01* -X1579Y466D02* -X1618Y466D01* -X1647Y466D02* -X1687Y466D01* -X1577Y465D02* -X1616Y465D01* -X1649Y465D02* -X1689Y465D01* -X1575Y464D02* -X1614Y464D01* -X1651Y464D02* -X1690Y464D01* -X1573Y463D02* -X1612Y463D01* -X1653Y463D02* -X1692Y463D01* -X1572Y462D02* -X1611Y462D01* -X1655Y462D02* -X1693Y462D01* -X1571Y461D02* -X1609Y461D01* -X1657Y461D02* -X1694Y461D01* -X1570Y460D02* -X1607Y460D01* -X1659Y460D02* -X1695Y460D01* -X1569Y459D02* -X1605Y459D01* -X1661Y459D02* -X1696Y459D01* -X1569Y458D02* -X1603Y458D01* -X1663Y458D02* -X1697Y458D01* -X1568Y457D02* -X1601Y457D01* -X1665Y457D02* -X1697Y457D01* -X1567Y456D02* -X1599Y456D01* -X1667Y456D02* -X1698Y456D01* -X1567Y455D02* -X1597Y455D01* -X1669Y455D02* -X1699Y455D01* -X1566Y454D02* -X1595Y454D01* -X1671Y454D02* -X1699Y454D01* -X1566Y453D02* -X1593Y453D01* -X1673Y453D02* -X1700Y453D01* -X1565Y452D02* -X1591Y452D01* -X1675Y452D02* -X1700Y452D01* -X1565Y451D02* -X1589Y451D01* -X1677Y451D02* -X1701Y451D01* -X1565Y450D02* -X1587Y450D01* -X1679Y450D02* -X1701Y450D01* -X1564Y449D02* -X1585Y449D01* -X1681Y449D02* -X1701Y449D01* -X1564Y448D02* -X1583Y448D01* -X1682Y448D02* -X1702Y448D01* -X1564Y447D02* -X1582Y447D01* -X1683Y447D02* -X1702Y447D01* -X1564Y446D02* -X1582Y446D01* -X1684Y446D02* -X1702Y446D01* -X1563Y445D02* -X1581Y445D01* -X1685Y445D02* -X1702Y445D01* -X1563Y444D02* -X1581Y444D01* -X1685Y444D02* -X1702Y444D01* -X1563Y443D02* -X1581Y443D01* -X1685Y443D02* -X1702Y443D01* -X1563Y442D02* -X1580Y442D01* -X1685Y442D02* -X1703Y442D01* -X1563Y441D02* -X1580Y441D01* -X1685Y441D02* -X1703Y441D01* -X1563Y440D02* -X1580Y440D01* -X1685Y440D02* -X1703Y440D01* -X1563Y439D02* -X1580Y439D01* -X1685Y439D02* -X1703Y439D01* -X1563Y438D02* -X1580Y438D01* -X1685Y438D02* -X1703Y438D01* -X1563Y437D02* -X1580Y437D01* -X1685Y437D02* -X1703Y437D01* -X1563Y436D02* -X1580Y436D01* -X1685Y436D02* -X1703Y436D01* -X1563Y435D02* -X1581Y435D01* -X1685Y435D02* -X1703Y435D01* -X1563Y434D02* -X1703Y434D01* -X99Y433D02* -X105Y433D01* -X202Y433D02* -X208Y433D01* -X1563Y433D02* -X1703Y433D01* -X98Y432D02* -X106Y432D01* -X200Y432D02* -X209Y432D01* -X1563Y432D02* -X1703Y432D01* -X97Y431D02* -X107Y431D01* -X199Y431D02* -X210Y431D01* -X1563Y431D02* -X1703Y431D01* -X96Y430D02* -X108Y430D01* -X199Y430D02* -X211Y430D01* -X1563Y430D02* -X1703Y430D01* -X95Y429D02* -X109Y429D01* -X198Y429D02* -X211Y429D01* -X1563Y429D02* -X1703Y429D01* -X95Y428D02* -X109Y428D01* -X198Y428D02* -X212Y428D01* -X1563Y428D02* -X1703Y428D01* -X95Y427D02* -X109Y427D01* -X198Y427D02* -X212Y427D01* -X1563Y427D02* -X1703Y427D01* -X95Y426D02* -X109Y426D01* -X198Y426D02* -X212Y426D01* -X1563Y426D02* -X1703Y426D01* -X95Y425D02* -X109Y425D01* -X198Y425D02* -X212Y425D01* -X1563Y425D02* -X1703Y425D01* -X95Y424D02* -X109Y424D01* -X198Y424D02* -X212Y424D01* -X1563Y424D02* -X1703Y424D01* -X95Y423D02* -X109Y423D01* -X198Y423D02* -X212Y423D01* -X1563Y423D02* -X1703Y423D01* -X95Y422D02* -X109Y422D01* -X198Y422D02* -X212Y422D01* -X1563Y422D02* -X1703Y422D01* -X95Y421D02* -X109Y421D01* -X198Y421D02* -X212Y421D01* -X1563Y421D02* -X1703Y421D01* -X95Y420D02* -X109Y420D01* -X198Y420D02* -X212Y420D01* -X1563Y420D02* -X1703Y420D01* -X95Y419D02* -X109Y419D01* -X198Y419D02* -X212Y419D01* -X1563Y419D02* -X1703Y419D01* -X95Y418D02* -X109Y418D01* -X198Y418D02* -X212Y418D01* -X1563Y418D02* -X1703Y418D01* -X95Y417D02* -X109Y417D01* -X198Y417D02* -X212Y417D01* -X1563Y417D02* -X1703Y417D01* -X95Y416D02* -X109Y416D01* -X198Y416D02* -X212Y416D01* -X1563Y416D02* -X1580Y416D01* -X1685Y416D02* -X1703Y416D01* -X95Y415D02* -X109Y415D01* -X198Y415D02* -X212Y415D01* -X1563Y415D02* -X1580Y415D01* -X1685Y415D02* -X1703Y415D01* -X95Y414D02* -X109Y414D01* -X198Y414D02* -X212Y414D01* -X1563Y414D02* -X1580Y414D01* -X1685Y414D02* -X1703Y414D01* -X95Y413D02* -X109Y413D01* -X198Y413D02* -X212Y413D01* -X1563Y413D02* -X1580Y413D01* -X1685Y413D02* -X1703Y413D01* -X95Y412D02* -X109Y412D01* -X198Y412D02* -X212Y412D01* -X1563Y412D02* -X1580Y412D01* -X1685Y412D02* -X1703Y412D01* -X95Y411D02* -X109Y411D01* -X198Y411D02* -X212Y411D01* -X1563Y411D02* -X1580Y411D01* -X1685Y411D02* -X1703Y411D01* -X95Y410D02* -X109Y410D01* -X198Y410D02* -X212Y410D01* -X1563Y410D02* -X1580Y410D01* -X1685Y410D02* -X1703Y410D01* -X95Y409D02* -X109Y409D01* -X198Y409D02* -X212Y409D01* -X1563Y409D02* -X1580Y409D01* -X1685Y409D02* -X1703Y409D01* -X95Y408D02* -X109Y408D01* -X198Y408D02* -X212Y408D01* -X1563Y408D02* -X1580Y408D01* -X1685Y408D02* -X1703Y408D01* -X95Y407D02* -X109Y407D01* -X198Y407D02* -X212Y407D01* -X1563Y407D02* -X1580Y407D01* -X1685Y407D02* -X1702Y407D01* -X95Y406D02* -X109Y406D01* -X198Y406D02* -X212Y406D01* -X1563Y406D02* -X1580Y406D01* -X1686Y406D02* -X1702Y406D01* -X95Y405D02* -X109Y405D01* -X198Y405D02* -X212Y405D01* -X1564Y405D02* -X1580Y405D01* -X1686Y405D02* -X1702Y405D01* -X95Y404D02* -X109Y404D01* -X198Y404D02* -X212Y404D01* -X1564Y404D02* -X1580Y404D01* -X1686Y404D02* -X1702Y404D01* -X95Y403D02* -X109Y403D01* -X198Y403D02* -X212Y403D01* -X1565Y403D02* -X1579Y403D01* -X1687Y403D02* -X1701Y403D01* -X95Y402D02* -X109Y402D01* -X198Y402D02* -X212Y402D01* -X1565Y402D02* -X1578Y402D01* -X1688Y402D02* -X1700Y402D01* -X95Y401D02* -X109Y401D01* -X198Y401D02* -X212Y401D01* -X1567Y401D02* -X1577Y401D01* -X1689Y401D02* -X1699Y401D01* -X95Y400D02* -X109Y400D01* -X198Y400D02* -X212Y400D01* -X1568Y400D02* -X1576Y400D01* -X1690Y400D02* -X1698Y400D01* -X95Y399D02* -X109Y399D01* -X198Y399D02* -X212Y399D01* -X1571Y399D02* -X1573Y399D01* -X1693Y399D02* -X1695Y399D01* -X95Y398D02* -X109Y398D01* -X198Y398D02* -X212Y398D01* -X95Y397D02* -X109Y397D01* -X198Y397D02* -X212Y397D01* -X95Y396D02* -X109Y396D01* -X197Y396D02* -X212Y396D01* -X95Y395D02* -X110Y395D01* -X197Y395D02* -X212Y395D01* -X95Y394D02* -X110Y394D01* -X197Y394D02* -X212Y394D01* -X95Y393D02* -X111Y393D01* -X196Y393D02* -X211Y393D01* -X96Y392D02* -X112Y392D01* -X195Y392D02* -X211Y392D01* -X96Y391D02* -X114Y391D01* -X193Y391D02* -X211Y391D01* -X96Y390D02* -X116Y390D01* -X191Y390D02* -X211Y390D01* -X97Y389D02* -X118Y389D01* -X189Y389D02* -X210Y389D01* -X97Y388D02* -X120Y388D01* -X187Y388D02* -X210Y388D01* -X98Y387D02* -X122Y387D01* -X185Y387D02* -X209Y387D01* -X98Y386D02* -X124Y386D01* -X183Y386D02* -X209Y386D01* -X99Y385D02* -X126Y385D01* -X181Y385D02* -X208Y385D01* -X100Y384D02* -X128Y384D01* -X179Y384D02* -X207Y384D01* -X101Y383D02* -X130Y383D01* -X177Y383D02* -X207Y383D01* -X101Y382D02* -X132Y382D01* -X175Y382D02* -X206Y382D01* -X102Y381D02* -X134Y381D01* -X173Y381D02* -X205Y381D01* -X104Y380D02* -X136Y380D01* -X171Y380D02* -X204Y380D01* -X105Y379D02* -X138Y379D01* -X169Y379D02* -X202Y379D01* -X107Y378D02* -X140Y378D01* -X167Y378D02* -X201Y378D01* -X108Y377D02* -X141Y377D01* -X165Y377D02* -X199Y377D01* -X110Y376D02* -X143Y376D01* -X163Y376D02* -X197Y376D01* -X112Y375D02* -X146Y375D01* -X161Y375D02* -X195Y375D01* -X114Y374D02* -X149Y374D01* -X157Y374D02* -X193Y374D01* -X116Y373D02* -X191Y373D01* -X118Y372D02* -X189Y372D01* -X120Y371D02* -X187Y371D01* -X122Y370D02* -X185Y370D01* -X124Y369D02* -X183Y369D01* -X126Y368D02* -X181Y368D01* -X128Y367D02* -X179Y367D01* -X130Y366D02* -X177Y366D01* -X132Y365D02* -X174Y365D01* -X134Y364D02* -X172Y364D01* -X136Y363D02* -X170Y363D01* -X138Y362D02* -X168Y362D01* -X141Y361D02* -X166Y361D01* -X144Y360D02* -X163Y360D01* -X148Y359D02* -X159Y359D01* -X1569Y358D02* -X1702Y358D01* -X1567Y357D02* -X1703Y357D01* -X1566Y356D02* -X1703Y356D01* -X1565Y355D02* -X1703Y355D01* -X1565Y354D02* -X1703Y354D01* -X1564Y353D02* -X1703Y353D01* -X1564Y352D02* -X1703Y352D01* -X1563Y351D02* -X1703Y351D01* -X1563Y350D02* -X1703Y350D01* -X1563Y349D02* -X1703Y349D01* -X1563Y348D02* -X1703Y348D01* -X1564Y347D02* -X1703Y347D01* -X1564Y346D02* -X1703Y346D01* -X1564Y345D02* -X1703Y345D01* -X1565Y344D02* -X1703Y344D01* -X1566Y343D02* -X1703Y343D01* -X1567Y342D02* -X1703Y342D01* -X1569Y341D02* -X1703Y341D01* -X1678Y340D02* -X1703Y340D01* -X1677Y339D02* -X1703Y339D01* -X1675Y338D02* -X1703Y338D01* -X1674Y337D02* -X1703Y337D01* -X1672Y336D02* -X1703Y336D01* -X1671Y335D02* -X1702Y335D01* -X1670Y334D02* -X1700Y334D01* -X1668Y333D02* -X1699Y333D01* -X1667Y332D02* -X1697Y332D01* -X1665Y331D02* -X1696Y331D01* -X1664Y330D02* -X1694Y330D01* -X1662Y329D02* -X1693Y329D01* -X1661Y328D02* -X1692Y328D01* -X1660Y327D02* -X1690Y327D01* -X1658Y326D02* -X1689Y326D01* -X1657Y325D02* -X1687Y325D01* -X1655Y324D02* -X1686Y324D01* -X1654Y323D02* -X1684Y323D01* -X1645Y322D02* -X1683Y322D01* -X1643Y321D02* -X1682Y321D01* -X1642Y320D02* -X1680Y320D01* -X1641Y319D02* -X1679Y319D01* -X1641Y318D02* -X1677Y318D01* -X1640Y317D02* -X1676Y317D01* -X1640Y316D02* -X1674Y316D01* -X1640Y315D02* -X1673Y315D01* -X1640Y314D02* -X1672Y314D01* -X1640Y313D02* -X1672Y313D01* -X1640Y312D02* -X1673Y312D01* -X1640Y311D02* -X1675Y311D01* -X1640Y310D02* -X1676Y310D01* -X1641Y309D02* -X1678Y309D01* -X1641Y308D02* -X1679Y308D01* -X1642Y307D02* -X1681Y307D01* -X1644Y306D02* -X1682Y306D01* -X1646Y305D02* -X1683Y305D01* -X1654Y304D02* -X1685Y304D01* -X1655Y303D02* -X1686Y303D01* -X1657Y302D02* -X1688Y302D01* -X1658Y301D02* -X1689Y301D01* -X1660Y300D02* -X1691Y300D01* -X1661Y299D02* -X1692Y299D01* -X1663Y298D02* -X1693Y298D01* -X1664Y297D02* -X1695Y297D01* -X1665Y296D02* -X1696Y296D01* -X1667Y295D02* -X1698Y295D01* -X1668Y294D02* -X1699Y294D01* -X1670Y293D02* -X1700Y293D01* -X1671Y292D02* -X1702Y292D01* -X1673Y291D02* -X1703Y291D01* -X1674Y290D02* -X1703Y290D01* -X1675Y289D02* -X1703Y289D01* -X1677Y288D02* -X1703Y288D01* -X1571Y287D02* -X1703Y287D01* -X1568Y286D02* -X1703Y286D01* -X1567Y285D02* -X1703Y285D01* -X1566Y284D02* -X1703Y284D01* -X1565Y283D02* -X1703Y283D01* -X1564Y282D02* -X1703Y282D01* -X1564Y281D02* -X1703Y281D01* -X1563Y280D02* -X1703Y280D01* -X1563Y279D02* -X1703Y279D01* -X1563Y278D02* -X1703Y278D01* -X1563Y277D02* -X1703Y277D01* -X1563Y276D02* -X1703Y276D01* -X1564Y275D02* -X1703Y275D01* -X1564Y274D02* -X1703Y274D01* -X1565Y273D02* -X1703Y273D01* -X1565Y272D02* -X1703Y272D01* -X1567Y271D02* -X1703Y271D01* -X1568Y270D02* -X1703Y270D01* -X1571Y269D02* -X1702Y269D01* -D02* -G04 End of Copper0* -M02* \ No newline at end of file diff --git a/tests/gerber_files/detector_copper_top.gbr b/tests/gerber_files/detector_copper_top.gbr deleted file mode 100644 index 52b2e2ae..00000000 --- a/tests/gerber_files/detector_copper_top.gbr +++ /dev/null @@ -1,71 +0,0 @@ -G04 MADE WITH FRITZING* -G04 WWW.FRITZING.ORG* -G04 DOUBLE SIDED* -G04 HOLES PLATED* -G04 CONTOUR ON CENTER OF CONTOUR VECTOR* -%ASAXBY*% -%FSLAX23Y23*% -%MOIN*% -%OFA0B0*% -%SFA1.0B1.0*% -%ADD10C,0.075000*% -%ADD11C,0.099055*% -%ADD12C,0.078740*% -%ADD13R,0.075000X0.075000*% -%ADD14C,0.024000*% -%ADD15C,0.020000*% -%LNCOPPER1*% -G90* -G70* -G54D10* -X1149Y872D03* -X1349Y872D03* -X749Y722D03* -X749Y522D03* -X1149Y522D03* -X1449Y522D03* -X1149Y422D03* -X1449Y422D03* -X1149Y322D03* -X1449Y322D03* -X1149Y222D03* -X1449Y222D03* -X949Y472D03* -X949Y72D03* -G54D11* -X749Y972D03* -X599Y972D03* -X349Y322D03* -X349Y472D03* -X349Y672D03* -X349Y822D03* -G54D10* -X699Y122D03* -X699Y322D03* -G54D12* -X699Y222D03* -X949Y972D03* -X749Y622D03* -X1049Y222D03* -X1249Y872D03* -G54D13* -X1149Y872D03* -X1149Y522D03* -G54D14* -X952Y946D02* -X1045Y249D01* -G54D15* -X776Y695D02* -X721Y695D01* -X721Y750D01* -X776Y750D01* -X776Y695D01* -D02* -X671Y150D02* -X726Y150D01* -X726Y95D01* -X671Y95D01* -X671Y150D01* -D02* -G04 End of Copper1* -M02* \ No newline at end of file diff --git a/tests/gerber_files/detector_drill.txt b/tests/gerber_files/detector_drill.txt deleted file mode 100644 index c4945b84..00000000 --- a/tests/gerber_files/detector_drill.txt +++ /dev/null @@ -1,46 +0,0 @@ -; NON-PLATED HOLES START AT T1 -; THROUGH (PLATED) HOLES START AT T100 -M48 -INCH -T1C0.125984 -T100C0.031496 -T101C0.035000 -T102C0.059055 -% -T1 -X001488Y010223 -X001488Y001223 -X016488Y001223 -X016488Y010223 -T100 -X009488Y009723 -X007488Y006223 -X012488Y008723 -X010488Y002223 -X006988Y002223 -T101 -X014488Y004223 -X006988Y003223 -X013488Y008723 -X011488Y008723 -X007488Y005223 -X014488Y003223 -X014488Y002223 -X011488Y005223 -X009488Y000723 -X011488Y004223 -X006988Y001223 -X009488Y004723 -X007488Y007223 -X011488Y003223 -X014488Y005223 -X011488Y002223 -T102 -X003488Y008223 -X003488Y004723 -X007488Y009723 -X003488Y006723 -X005988Y009723 -X003488Y003223 -T00 -M30 diff --git a/tests/gerber_files/simple1.gbr b/tests/gerber_files/simple1.gbr deleted file mode 100644 index 2ca21d77..00000000 --- a/tests/gerber_files/simple1.gbr +++ /dev/null @@ -1,54 +0,0 @@ -G04 MADE WITH FRITZING* -G04 WWW.FRITZING.ORG* -G04 DOUBLE SIDED* -G04 HOLES PLATED* -G04 CONTOUR ON CENTER OF CONTOUR VECTOR* -%ASAXBY*% -%FSLAX23Y23*% -%MOIN*% -%OFA0B0*% -%SFA1.0B1.0*% -%ADD10R,0.047244X0.078740*% -%ADD11C,0.024000*% -%LNCOPPER1*% -G90* -G70* -G54D10* -X2940Y1051D03* -X2940Y941D03* -G54D11* -X2438Y839D02* -X2440Y1023D01* -D02* -X2940Y907D02* -X2941Y839D01* -D02* -X2941Y839D02* -X2438Y839D01* -D02* -X2941Y1239D02* -X2940Y1085D01* -D02* -X2438Y1239D02* -X2941Y1239D01* -D02* -X2440Y1126D02* -X2438Y1239D01* -G36* -X2418Y1064D02* -X2461Y1064D01* -X2461Y1017D01* -X2418Y1017D01* -X2418Y1064D01* -G37* -D02* -G36* -X2418Y1131D02* -X2461Y1131D01* -X2461Y1084D01* -X2418Y1084D01* -X2418Y1131D01* -G37* -D02* -G04 End of Copper1* -M02* \ No newline at end of file diff --git a/tests/gerber_parsing_profiling/gerber1.gbr b/tests/gerber_parsing_profiling/gerber1.gbr deleted file mode 100755 index 245ae335..00000000 --- a/tests/gerber_parsing_profiling/gerber1.gbr +++ /dev/null @@ -1,3045 +0,0 @@ -G04 Generated by Ultiboard 12.0 * -%FSLAX25Y25*% -%MOMM*% - -%ADD10C,0.00001*% -%ADD11C,0.25400*% -%ADD12C,0.50800*% -%ADD13C,1.90500*% -%ADD14C,0.12700*% -%ADD15C,2.00000*% -%ADD16R,1.80000X1.15000*% -%ADD17C,1.60884*% -%ADD18R,0.67742X0.67742*% -%ADD19C,0.84658*% -%ADD20R,0.76200X0.76200*% -%ADD21C,0.50800*% -%ADD22R,1.27000X1.27000*% -%ADD23R,2.70000X1.15000*% -%ADD24R,1.15000X1.45000*% -%ADD25R,1.15000X2.70000*% -%ADD26R,0.52908X0.52908*% -%ADD27C,0.99492*% -%ADD28C,1.80000*% -%ADD29R,1.80000X1.80000*% -%ADD30R,2.57000X2.80000*% -%ADD31C,1.98984*% - - -G04 ColorRGB 002000 for the following layer * -%LNCopper Top*% -%LPD*% -G54D10* -G36* -X1590549Y63807D02* -X1590549Y63807D01* -X3936193Y63807D01* -X3936193Y6436193D01* -X63807Y6436193D01* -X63807Y63807D01* -X1254251Y63807D01* -G75* -D01* -G02X1230093Y113800I39649J49993* -G01* -X1230093Y113800D01* -X1230093Y185606D01* -X1354206Y185606D01* -X1354206Y63807D01* -X1490594Y63807D01* -X1490594Y185606D01* -X1614707Y185606D01* -X1614707Y113800D01* -G75* -D01* -G02X1590549Y63807I-63807J0* -G01* -D02* -G37* -%LPC*% -G36* -X2821307Y4787900D02* -X2821307Y4787900D01* -X2821307Y4717860D01* -G75* -D01* -G02X2855900Y4724830I34593J-82359* -G01* -X2855900Y4724830D01* -X2932100Y4724830D01* -G74* -D01* -G02X3021430Y4635500I0J89330* -G01* -X3021430Y4635500D01* -X3021430Y4559300D01* -G75* -D01* -G02X2940855Y4470400I-89330J0* -G01* -G74* -D01* -G02X3021430Y4381500I8755J88900* -G01* -X3021430Y4381500D01* -X3021430Y4379662D01* -X3024710Y4382942D01* -X3024710Y5027758D01* -X2858598Y5193870D01* -X1490507Y5193870D01* -X1490507Y5148200D01* -G75* -D01* -G02X1426700Y5084393I-63807J0* -G01* -X1426700Y5084393D01* -X1311700Y5084393D01* -G75* -D01* -G02X1247893Y5148200I0J63807* -G01* -X1247893Y5148200D01* -X1247893Y5418200D01* -G75* -D01* -G02X1311700Y5482007I63807J0* -G01* -X1311700Y5482007D01* -X1426700Y5482007D01* -G74* -D01* -G02X1490507Y5418200I0J63807* -G01* -X1490507Y5418200D01* -X1490507Y5372530D01* -X2022842Y5372530D01* -G75* -D01* -G02X2244358Y5372530I110758J120269* -G01* -X2244358Y5372530D01* -X2530842Y5372530D01* -G75* -D01* -G02X2752358Y5372530I110758J120269* -G01* -X2752358Y5372530D01* -X2895490Y5372530D01* -G75* -D01* -G02X2928856Y5366109I110J-89330* -G01* -G74* -D01* -G02X2958845Y5346286I33257J82908* -G01* -X2958845Y5346286D01* -X3177126Y5128005D01* -G74* -D01* -G02X3203370Y5064760I63085J63245* -G01* -X3203370Y5064760D01* -X3203370Y4345940D01* -G75* -D01* -G02X3177126Y4282695I-89330J1* -G01* -X3177126Y4282695D01* -X3047745Y4153314D01* -G74* -D01* -G02X3021121Y4134921I63246J63084* -G01* -G74* -D01* -G02X3021430Y4127500I89020J7424* -G01* -X3021430Y4127500D01* -X3021430Y4051300D01* -G75* -D01* -G02X2940855Y3962400I-89330J0* -G01* -G74* -D01* -G02X3021148Y3880588I8755J88900* -G01* -X3021148Y3880588D01* -X2939188Y3880588D01* -X2939188Y3962252D01* -G74* -D01* -G02X2932100Y3961970I7092J89047* -G01* -X2932100Y3961970D01* -X2855900Y3961970D01* -G75* -D01* -G02X2848812Y3962252I4J89330* -G01* -X2848812Y3962252D01* -X2848812Y3880588D01* -X2810004Y3880588D01* -G74* -D01* -G02X2740855Y3835400I77904J43711* -G01* -G74* -D01* -G02X2810004Y3790212I8756J88900* -G01* -X2810004Y3790212D01* -X2848812Y3790212D01* -X2848812Y3708548D01* -G75* -D01* -G02X2855900Y3708830I7092J-89047* -G01* -X2855900Y3708830D01* -X2932100Y3708830D01* -G74* -D01* -G02X2939188Y3708548I4J89330* -G01* -X2939188Y3708548D01* -X2939188Y3790212D01* -X3021148Y3790212D01* -G74* -D01* -G02X2940855Y3708400I89047J7087* -G01* -G74* -D01* -G02X3021430Y3619500I8755J88900* -G01* -X3021430Y3619500D01* -X3021430Y3543300D01* -G75* -D01* -G02X2940855Y3454400I-89330J0* -G01* -G74* -D01* -G02X3021430Y3365500I8755J88900* -G01* -X3021430Y3365500D01* -X3021430Y3289300D01* -G75* -D01* -G02X3001389Y3232918I-89330J0* -G01* -X3001389Y3232918D01* -X3318326Y3232918D01* -G75* -D01* -G02X3364423Y3252607I46097J-44117* -G01* -X3364423Y3252607D01* -X3412122Y3252607D01* -G75* -D01* -G02X3496724Y3252607I42301J-153806* -G01* -X3496724Y3252607D01* -X3544423Y3252607D01* -G74* -D01* -G02X3608231Y3188800I1J63807* -G01* -X3608231Y3188800D01* -X3608231Y3141101D01* -G75* -D01* -G02X3608231Y3056499I-153807J-42301* -G01* -X3608231Y3056499D01* -X3608231Y3008800D01* -G75* -D01* -G02X3544423Y2944993I-63808J1* -G01* -X3544423Y2944993D01* -X3523668Y2944993D01* -G74* -D01* -G02X3429023Y2913882I94645J128407* -G01* -X3429023Y2913882D01* -X3001389Y2913882D01* -G74* -D01* -G02X3021430Y2857500I69289J56382* -G01* -X3021430Y2857500D01* -X3021430Y2781300D01* -G75* -D01* -G02X2940855Y2692400I-89330J0* -G01* -G74* -D01* -G02X3021148Y2610588I8755J88900* -G01* -X3021148Y2610588D01* -X2939188Y2610588D01* -X2939188Y2692252D01* -G74* -D01* -G02X2932100Y2691970I7092J89047* -G01* -X2932100Y2691970D01* -X2855900Y2691970D01* -G75* -D01* -G02X2848812Y2692252I4J89330* -G01* -X2848812Y2692252D01* -X2848812Y2610588D01* -X2810004Y2610588D01* -G74* -D01* -G02X2740855Y2565400I77904J43711* -G01* -G74* -D01* -G02X2810004Y2520212I8756J88900* -G01* -X2810004Y2520212D01* -X2848812Y2520212D01* -X2848812Y2438548D01* -G75* -D01* -G02X2855900Y2438830I7092J-89047* -G01* -X2855900Y2438830D01* -X2932100Y2438830D01* -G74* -D01* -G02X2939188Y2438548I4J89330* -G01* -X2939188Y2438548D01* -X2939188Y2520212D01* -X3021148Y2520212D01* -G74* -D01* -G02X2940855Y2438400I89047J7087* -G01* -G74* -D01* -G02X3021430Y2349500I8755J88900* -G01* -X3021430Y2349500D01* -X3021430Y2273300D01* -G75* -D01* -G02X2940855Y2184400I-89330J0* -G01* -G74* -D01* -G02X3021430Y2095500I8755J88900* -G01* -X3021430Y2095500D01* -X3021430Y2019300D01* -G75* -D01* -G02X2940855Y1930400I-89330J0* -G01* -G74* -D01* -G02X3021430Y1841500I8755J88900* -G01* -X3021430Y1841500D01* -X3021430Y1765300D01* -G75* -D01* -G02X2932100Y1675970I-89330J0* -G01* -X2932100Y1675970D01* -X2855900Y1675970D01* -G75* -D01* -G02X2821430Y1682888I-1J89330* -G01* -X2821430Y1682888D01* -X2821430Y1638300D01* -G75* -D01* -G02X2732100Y1548970I-89330J0* -G01* -X2732100Y1548970D01* -X2655900Y1548970D01* -G75* -D01* -G02X2566570Y1638300I0J89330* -G01* -X2566570Y1638300D01* -X2566570Y1714500D01* -G74* -D01* -G02X2647145Y1803400I89330J0* -G01* -G75* -D01* -G02X2566570Y1892300I8755J88900* -G01* -X2566570Y1892300D01* -X2566570Y1968500D01* -G74* -D01* -G02X2647145Y2057400I89330J0* -G01* -G74* -D01* -G02X2582720Y2095070I8755J88900* -G01* -X2582720Y2095070D01* -X2045877Y2095070D01* -G75* -D01* -G02X1982664Y2121234I-47J89330* -G01* -X1982664Y2121234D01* -X1562434Y2541464D01* -G75* -D01* -G02X1536270Y2604716I63166J63166* -G01* -X1536270Y2604716D01* -X1536270Y2878293D01* -X1490600Y2878293D01* -G75* -D01* -G02X1427407Y2933270I0J63807* -G01* -X1427407Y2933270D01* -X725170Y2933270D01* -G75* -D01* -G02X701417Y2936486I0J89330* -G01* -G74* -D01* -G02X662004Y2959434I23752J86113* -G01* -X662004Y2959434D01* -X597330Y3024108D01* -X597330Y2563005D01* -G74* -D01* -G02X648212Y2472271I55458J90734* -G01* -X648212Y2472271D01* -X648212Y2404529D01* -G75* -D01* -G02X597330Y2313795I-106341J0* -G01* -X597330Y2313795D01* -X597330Y2297269D01* -G75* -D01* -G02X597330Y2071531I-89329J-112869* -G01* -X597330Y2071531D01* -X597330Y2043269D01* -G75* -D01* -G02X597330Y1817531I-89329J-112869* -G01* -X597330Y1817531D01* -X597330Y1789269D01* -G75* -D01* -G02X597330Y1563531I-89329J-112869* -G01* -X597330Y1563531D01* -X597330Y1486330D01* -X1473200Y1486330D01* -G74* -D01* -G02X1536445Y1460086I1J89330* -G01* -X1536445Y1460086D01* -X1815686Y1180845D01* -G74* -D01* -G02X1827393Y1166446I63083J63248* -G01* -X1827393Y1166446D01* -X1827393Y1172700D01* -G75* -D01* -G02X1891200Y1236507I63807J0* -G01* -X1891200Y1236507D01* -X1891870Y1236507D01* -X1891870Y1239969D01* -G75* -D01* -G02X1918034Y1303266I89330J131* -G01* -X1918034Y1303266D01* -X2032334Y1417566D01* -G74* -D01* -G02X2052310Y1432595I63165J63167* -G01* -G75* -D01* -G02X2309229Y1443730I132090J-78194* -G01* -X2309229Y1443730D01* -X2362157Y1443730D01* -G75* -D01* -G02X2370371Y1443356I48J-89330* -G01* -G74* -D01* -G02X2425445Y1417486I8172J88955* -G01* -X2425445Y1417486D01* -X2603086Y1239845D01* -G74* -D01* -G02X2629330Y1176600I63085J63245* -G01* -X2629330Y1176600D01* -X2629330Y644332D01* -G74* -D01* -G02X2702712Y517549I89329J136332* -G01* -X2702712Y517549D01* -X2712261Y508000D01* -X2702712Y498451D01* -G75* -D01* -G02X2413000Y405837I-162711J9547* -G01* -G75* -D01* -G02X2196670Y644332I-126997J102166* -G01* -X2196670Y644332D01* -X2196670Y693893D01* -X2196000Y693893D01* -G75* -D01* -G02X2140699Y725870I0J63807* -G01* -X2140699Y725870D01* -X2126501Y725870D01* -G74* -D01* -G02X2071200Y693893I55301J31830* -G01* -X2071200Y693893D01* -X1891200Y693893D01* -G75* -D01* -G02X1835899Y725870I0J63807* -G01* -X1835899Y725870D01* -X1826450Y725870D01* -G75* -D01* -G02X1763234Y752034I-50J89330* -G01* -X1763234Y752034D01* -X1689434Y825834D01* -G75* -D01* -G02X1663270Y889088I63166J63166* -G01* -X1663270Y889088D01* -X1663270Y1080598D01* -X1625968Y1117900D01* -G74* -D01* -G02X1625969Y1117600I76567J405* -G01* -X1625969Y1117600D01* -X1625969Y736600D01* -G75* -D01* -G02X1567930Y662307I-76569J0* -G01* -G74* -D01* -G02X1550900Y659993I17028J61493* -G01* -X1550900Y659993D01* -X1293900Y659993D01* -G75* -D01* -G02X1276871Y662307I-1J63807* -G01* -G74* -D01* -G02X1266963Y665508I18531J74292* -G01* -X1266963Y665508D01* -X758963Y868708D01* -G75* -D01* -G02X710831Y939939I28436J71092* -G01* -X710831Y939939D01* -X710831Y1117600D01* -G75* -D01* -G02X787400Y1194169I76569J0* -G01* -X787400Y1194169D01* -X1549400Y1194169D01* -G74* -D01* -G02X1549700Y1194168I105J76569* -G01* -X1549700Y1194168D01* -X1436198Y1307670D01* -X508000Y1307670D01* -G75* -D01* -G02X418670Y1397000I0J89330* -G01* -X418670Y1397000D01* -X418670Y1563531D01* -G75* -D01* -G02X418670Y1789269I89329J112869* -G01* -X418670Y1789269D01* -X418670Y1817531D01* -G75* -D01* -G02X418670Y2043269I89329J112869* -G01* -X418670Y2043269D01* -X418670Y2071531D01* -G75* -D01* -G02X418670Y2297269I89329J112869* -G01* -X418670Y2297269D01* -X418670Y2313795D01* -G75* -D01* -G02X367788Y2404529I55458J90734* -G01* -X367788Y2404529D01* -X367788Y2472271D01* -G74* -D01* -G02X418670Y2563005I106341J0* -G01* -X418670Y2563005D01* -X418670Y4408331D01* -G75* -D01* -G02X418670Y4634069I89329J112869* -G01* -X418670Y4634069D01* -X418670Y4650595D01* -G75* -D01* -G02X367788Y4741329I55458J90734* -G01* -X367788Y4741329D01* -X367788Y4809071D01* -G74* -D01* -G02X418670Y4899805I106341J0* -G01* -X418670Y4899805D01* -X418670Y5029034D01* -G75* -D01* -G02X444834Y5092366I89330J166* -G01* -X444834Y5092366D01* -X698834Y5346366D01* -G74* -D01* -G02X749888Y5371705I63166J63166* -G01* -G75* -D01* -G02X762000Y5372530I12112J-88504* -G01* -X762000Y5372530D01* -X947893Y5372530D01* -X947893Y5418200D01* -G75* -D01* -G02X1011700Y5482007I63807J0* -G01* -X1011700Y5482007D01* -X1126700Y5482007D01* -G74* -D01* -G02X1190507Y5418200I0J63807* -G01* -X1190507Y5418200D01* -X1190507Y5148200D01* -G75* -D01* -G02X1126700Y5084393I-63807J0* -G01* -X1126700Y5084393D01* -X1011700Y5084393D01* -G75* -D01* -G02X947893Y5148200I0J63807* -G01* -X947893Y5148200D01* -X947893Y5193870D01* -X799002Y5193870D01* -X597330Y4992198D01* -X597330Y4899805D01* -G74* -D01* -G02X648212Y4809071I55458J90734* -G01* -X648212Y4809071D01* -X648212Y4741329D01* -G75* -D01* -G02X597330Y4650595I-106341J0* -G01* -X597330Y4650595D01* -X597330Y4634069D01* -G75* -D01* -G02X597330Y4408331I-89329J-112869* -G01* -X597330Y4408331D01* -X597330Y4379992D01* -X646904Y4429566D01* -G74* -D01* -G02X677608Y4449623I63166J63166* -G01* -G75* -D01* -G02X710070Y4455730I32462J-83223* -G01* -X710070Y4455730D01* -X1141809Y4455730D01* -G75* -D01* -G02X1202249Y4648200I128190J65470* -G01* -G75* -D01* -G02X1382869Y4864530I67754J126998* -G01* -X1382869Y4864530D01* -X2640232Y4864530D01* -G75* -D01* -G02X2669697Y4859565I98J-89330* -G01* -G74* -D01* -G02X2686444Y4851707I29368J84364* -G01* -X2686444Y4851707D01* -X2757500Y4851707D01* -G74* -D01* -G02X2821307Y4787900I0J63807* -G01* -D02* -G37* -G36* -X1202249Y2311400D02* -G75* -D01* -G02X1337751Y2311400I67751J126999* -G01* -G75* -D01* -G02X1337751Y2057400I-67750J-127000* -G01* -G75* -D01* -G02X1337751Y1803400I-67750J-127000* -G01* -G75* -D01* -G02X1202249Y1803400I-67751J-126999* -G01* -G75* -D01* -G02X1202249Y2057400I67750J127000* -G01* -G75* -D01* -G02X1202249Y2311400I67750J127000* -G01* -D02* -G37* -G36* -X2986100Y5492800D02* -G75* -D01* -G02X2986100Y5492800I163500J0* -G01* -D02* -G37* -G36* -X3494100Y5492800D02* -G75* -D01* -G02X3494100Y5492800I163500J0* -G01* -D02* -G37* -G36* -X2864000Y2565400D02* -G75* -D01* -G02X2864000Y2565400I30000J0* -G01* -D02* -G37* -G36* -X2864000Y3835400D02* -G75* -D01* -G02X2864000Y3835400I30000J0* -G01* -D02* -G37* -G36* -X2860893Y359367D02* -G75* -D01* -G02X2727107Y359367I-66893J148633* -G01* -X2727107Y359367D01* -X2794000Y426261D01* -X2860893Y359367D01* -D02* -G37* -G36* -X3300593Y351300D02* -X3300593Y351300D01* -X3300593Y365783D01* -X3411383Y365783D01* -X3411383Y287493D01* -X3364400Y287493D01* -G75* -D01* -G02X3300593Y351300I0J63807* -G01* -D02* -G37* -G36* -X3608207Y351300D02* -G75* -D01* -G02X3544400Y287493I-63807J0* -G01* -X3544400Y287493D01* -X3497417Y287493D01* -X3497417Y365783D01* -X3608207Y365783D01* -X3608207Y351300D01* -D02* -G37* -G36* -X2727107Y656633D02* -G75* -D01* -G02X2860893Y656633I66893J-148633* -G01* -X2860893Y656633D01* -X2794000Y589739D01* -X2727107Y656633D01* -D02* -G37* -G36* -X2734729Y508000D02* -G75* -D01* -G02X2734729Y508000I59271J0* -G01* -D02* -G37* -G36* -X2942633Y574893D02* -G75* -D01* -G02X2942633Y441107I-148633J-66893* -G01* -X2942633Y441107D01* -X2875739Y508000D01* -X2942633Y574893D01* -D02* -G37* -G36* -X3364400Y530107D02* -X3364400Y530107D01* -X3411383Y530107D01* -X3411383Y451817D01* -X3300593Y451817D01* -X3300593Y466300D01* -G75* -D01* -G02X3364400Y530107I63807J0* -G01* -D02* -G37* -G36* -X3543730Y873971D02* -X3543730Y873971D01* -X3543730Y830107D01* -X3544400Y830107D01* -G74* -D01* -G02X3608207Y766300I0J63807* -G01* -X3608207Y766300D01* -X3608207Y651300D01* -G75* -D01* -G02X3544400Y587493I-63807J0* -G01* -X3544400Y587493D01* -X3364400Y587493D01* -G75* -D01* -G02X3300593Y651300I0J63807* -G01* -X3300593Y651300D01* -X3300593Y766300D01* -G75* -D01* -G02X3364400Y830107I63807J0* -G01* -X3364400Y830107D01* -X3365070Y830107D01* -X3365070Y873971D01* -G75* -D01* -G02X3543730Y873971I89330J124828* -G01* -D02* -G37* -G36* -X3608207Y466300D02* -X3608207Y466300D01* -X3608207Y451817D01* -X3497417Y451817D01* -X3497417Y530107D01* -X3544400Y530107D01* -G74* -D01* -G02X3608207Y466300I0J63807* -G01* -D02* -G37* -G36* -X1230093Y393800D02* -G75* -D01* -G02X1293900Y457607I63807J0* -G01* -X1293900Y457607D01* -X1354206Y457607D01* -X1354206Y321994D01* -X1230093Y321994D01* -X1230093Y393800D01* -D02* -G37* -G36* -X1614707Y393800D02* -X1614707Y393800D01* -X1614707Y321994D01* -X1490594Y321994D01* -X1490594Y457607D01* -X1550900Y457607D01* -G74* -D01* -G02X1614707Y393800I0J63807* -G01* -D02* -G37* -G36* -X242900Y1009600D02* -G75* -D01* -G02X242900Y1009600I163500J0* -G01* -D02* -G37* -%LPD*% -G36* -X1760600Y2878293D02* -X1760600Y2878293D01* -X1714930Y2878293D01* -X1714930Y2641632D01* -X2020369Y2336193D01* -X2082832Y2273730D01* -X2582720Y2273730D01* -G74* -D01* -G02X2647145Y2311400I73180J51229* -G01* -G75* -D01* -G02X2566570Y2400300I8755J88900* -G01* -X2566570Y2400300D01* -X2566570Y2476500D01* -G74* -D01* -G02X2647145Y2565400I89330J0* -G01* -G75* -D01* -G02X2566570Y2654300I8755J88900* -G01* -X2566570Y2654300D01* -X2566570Y2730500D01* -G74* -D01* -G02X2647145Y2819400I89330J0* -G01* -G75* -D01* -G02X2566570Y2908300I8755J88900* -G01* -X2566570Y2908300D01* -X2566570Y2984500D01* -G74* -D01* -G02X2647145Y3073400I89330J0* -G01* -G75* -D01* -G02X2566570Y3162300I8755J88900* -G01* -X2566570Y3162300D01* -X2566570Y3238500D01* -G74* -D01* -G02X2586611Y3294882I89330J0* -G01* -X2586611Y3294882D01* -X2184423Y3294882D01* -G75* -D01* -G02X2142122Y3300593I0J159518* -G01* -X2142122Y3300593D01* -X2094423Y3300593D01* -G75* -D01* -G02X2030616Y3364400I0J63807* -G01* -X2030616Y3364400D01* -X2030616Y3412099D01* -G75* -D01* -G02X2030616Y3496701I153806J42301* -G01* -X2030616Y3496701D01* -X2030616Y3544400D01* -G75* -D01* -G02X2094423Y3608207I63807J0* -G01* -X2094423Y3608207D01* -X2142122Y3608207D01* -G75* -D01* -G02X2184423Y3613918I42301J-153806* -G01* -X2184423Y3613918D01* -X2586611Y3613918D01* -G75* -D01* -G02X2566570Y3670300I69289J56382* -G01* -X2566570Y3670300D01* -X2566570Y3746500D01* -G74* -D01* -G02X2647145Y3835400I89330J0* -G01* -G74* -D01* -G02X2582720Y3873070I8755J88900* -G01* -X2582720Y3873070D01* -X2495048Y3873070D01* -G74* -D01* -G02X2433500Y3826093I61547J16830* -G01* -X2433500Y3826093D01* -X2318500Y3826093D01* -G75* -D01* -G02X2286000Y3834990I0J63807* -G01* -G74* -D01* -G02X2253500Y3826093I32500J54910* -G01* -X2253500Y3826093D01* -X2138500Y3826093D01* -G75* -D01* -G02X2093648Y3844516I-1J63807* -G01* -X2093648Y3844516D01* -X1714930Y3465798D01* -X1714930Y3420907D01* -X1760600Y3420907D01* -G74* -D01* -G02X1824407Y3357100I0J63807* -G01* -X1824407Y3357100D01* -X1824407Y3242100D01* -G75* -D01* -G02X1760600Y3178293I-63807J0* -G01* -X1760600Y3178293D01* -X1490600Y3178293D01* -G75* -D01* -G02X1433985Y3212670I0J63807* -G01* -X1433985Y3212670D01* -X1252630Y3212670D01* -G75* -D01* -G02X1201009Y3212670I-25810J85519* -G01* -X1201009Y3212670D01* -X925669Y3212670D01* -G75* -D01* -G02X702740Y3209231I-112869J89329* -G01* -X702740Y3209231D01* -X702740Y3171362D01* -X762172Y3111930D01* -X1457965Y3111930D01* -G75* -D01* -G02X1490600Y3120907I32634J-54830* -G01* -X1490600Y3120907D01* -X1760600Y3120907D01* -G74* -D01* -G02X1824407Y3057100I0J63807* -G01* -X1824407Y3057100D01* -X1824407Y2942100D01* -G75* -D01* -G02X1760600Y2878293I-63807J0* -G01* -D02* -G37* -G36* -X699445Y3644713D02* -G74* -D01* -G03X646764Y3619166I10485J88713* -G01* -X646764Y3619166D01* -X597330Y3569732D01* -X597330Y4127328D01* -X747072Y4277070D01* -X1435299Y4277070D01* -G75* -D01* -G03X1490600Y4245093I55301J31830* -G01* -X1490600Y4245093D01* -X1760600Y4245093D01* -G75* -D01* -G03X1824407Y4308900I0J63807* -G01* -X1824407Y4308900D01* -X1824407Y4423900D01* -G74* -D01* -G03X1760600Y4487707I63807J0* -G01* -X1760600Y4487707D01* -X1490600Y4487707D01* -G75* -D01* -G03X1435299Y4455730I0J-63807* -G01* -X1435299Y4455730D01* -X1398191Y4455730D01* -G74* -D01* -G03X1413342Y4508070I128190J65470* -G01* -X1413342Y4508070D01* -X2056368Y4508070D01* -X1736005Y4187707D01* -X1490600Y4187707D01* -G75* -D01* -G03X1433985Y4153330I0J-63807* -G01* -X1433985Y4153330D01* -X934099Y4153330D01* -G74* -D01* -G03X839254Y4204248I94845J62875* -G01* -X839254Y4204248D01* -X786346Y4204248D01* -G75* -D01* -G03X672552Y4090454I0J-113794* -G01* -X672552Y4090454D01* -X672552Y4037546D01* -G75* -D01* -G03X739463Y3933859I113794J0* -G01* -G75* -D01* -G03X745049Y3683000I73337J-123859* -G01* -G74* -D01* -G03X699445Y3644713I67749J127000* -G01* -D02* -G37* -G36* -X2376000Y993893D02* -X2376000Y993893D01* -X2196000Y993893D01* -G75* -D01* -G02X2140699Y1025870I0J63807* -G01* -X2140699Y1025870D01* -X2126501Y1025870D01* -G74* -D01* -G02X2071200Y993893I55301J31830* -G01* -X2071200Y993893D01* -X1891200Y993893D01* -G75* -D01* -G02X1841930Y1017156I0J63807* -G01* -X1841930Y1017156D01* -X1841930Y926002D01* -X1848142Y919789D01* -G75* -D01* -G02X1891200Y936507I43058J-47089* -G01* -X1891200Y936507D01* -X2071200Y936507D01* -G74* -D01* -G02X2126501Y904530I0J63807* -G01* -X2126501Y904530D01* -X2140699Y904530D01* -G75* -D01* -G02X2196000Y936507I55301J-31830* -G01* -X2196000Y936507D01* -X2376000Y936507D01* -G74* -D01* -G02X2439807Y872700I0J63807* -G01* -X2439807Y872700D01* -X2439807Y757700D01* -G75* -D01* -G02X2376000Y693893I-63807J0* -G01* -X2376000Y693893D01* -X2375330Y693893D01* -X2375330Y644332D01* -G74* -D01* -G02X2413000Y610163I89329J136332* -G01* -G74* -D01* -G02X2450670Y644332I126999J102163* -G01* -X2450670Y644332D01* -X2450670Y1139598D01* -X2439807Y1150461D01* -X2439807Y1057700D01* -G75* -D01* -G02X2376000Y993893I-63807J0* -G01* -D02* -G37* -G36* -X787400Y1117600D02* -X787400Y1117600D01* -X1549400Y1117600D01* -X1549400Y736600D01* -X1295400Y736600D01* -X787400Y939800D01* -X787400Y1117600D01* -D02* -G37* -G36* -X925669Y3391330D02* -G74* -D01* -G03X880551Y3429000I112868J89330* -G01* -G75* -D01* -G03X880551Y3683000I-67750J127000* -G01* -G74* -D01* -G03X925669Y3720670I67750J127000* -G01* -X925669Y3720670D01* -X1129185Y3720670D01* -G75* -D01* -G03X1185800Y3686293I56615J29430* -G01* -X1185800Y3686293D01* -X1455800Y3686293D01* -G74* -D01* -G03X1512415Y3720670I0J63807* -G01* -X1512415Y3720670D01* -X1715696Y3720670D01* -G75* -D01* -G03X1717149Y3720681I50J89330* -G01* -X1717149Y3720681D01* -X1562434Y3565966D01* -G75* -D01* -G03X1536270Y3502656I63166J-63166* -G01* -X1536270Y3502656D01* -X1536270Y3420907D01* -X1512537Y3420907D01* -G75* -D01* -G03X1519607Y3450100I-56736J29193* -G01* -X1519607Y3450100D01* -X1519607Y3565100D01* -G74* -D01* -G03X1455800Y3628907I63807J0* -G01* -X1455800Y3628907D01* -X1185800Y3628907D01* -G75* -D01* -G03X1121993Y3565100I0J-63807* -G01* -X1121993Y3565100D01* -X1121993Y3450100D01* -G75* -D01* -G03X1160951Y3391330I63807J0* -G01* -X1160951Y3391330D01* -X925669Y3391330D01* -D02* -G37* -G36* -X2566570Y4178300D02* -X2566570Y4178300D01* -X2566570Y4254070D01* -X2286172Y4254070D01* -X2130277Y4098175D01* -G75* -D01* -G02X2138500Y4098707I8222J-63275* -G01* -X2138500Y4098707D01* -X2253500Y4098707D01* -G74* -D01* -G02X2286000Y4089810I0J63807* -G01* -G75* -D01* -G02X2318500Y4098707I32500J-54910* -G01* -X2318500Y4098707D01* -X2433500Y4098707D01* -G74* -D01* -G02X2495048Y4051730I1J63807* -G01* -X2495048Y4051730D01* -X2582720Y4051730D01* -G74* -D01* -G02X2647145Y4089400I73180J51229* -G01* -G75* -D01* -G02X2566570Y4178300I8755J88900* -G01* -D02* -G37* -G36* -X925669Y3899330D02* -G74* -D01* -G03X886137Y3933859I112869J89329* -G01* -G74* -D01* -G03X934099Y3974670I46883J103686* -G01* -X934099Y3974670D01* -X1436752Y3974670D01* -G75* -D01* -G03X1490600Y3945093I53848J34229* -G01* -X1490600Y3945093D01* -X1724531Y3945093D01* -X1678768Y3899330D01* -X1509648Y3899330D01* -G74* -D01* -G03X1455800Y3928907I53848J34229* -G01* -X1455800Y3928907D01* -X1185800Y3928907D01* -G75* -D01* -G03X1131952Y3899330I0J-63807* -G01* -X1131952Y3899330D01* -X925669Y3899330D01* -D02* -G37* -G36* -X2309229Y1265070D02* -X2309229Y1265070D01* -X2325198Y1265070D01* -X2353761Y1236507D01* -X2282703Y1236507D01* -G74* -D01* -G03X2309229Y1265070I98303J117891* -G01* -D02* -G37* -G54D11* -X787400Y1117600D02* -X1549400Y1117600D01* -X1549400Y736600D01* -X1295400Y736600D01* -X787400Y939800D01* -X787400Y1117600D01* -X2821307Y4787900D02* -X2821307Y4717860D01* -G75* -D01* -G02X2855900Y4724830I34593J-82359* -G01* -X2932100Y4724830D01* -G74* -D01* -G02X3021430Y4635500I0J89330* -G01* -X3021430Y4559300D01* -G75* -D01* -G02X2940855Y4470400I-89330J0* -G01* -G74* -D01* -G02X3021430Y4381500I8755J88900* -G01* -X3021430Y4379662D01* -X3024710Y4382942D01* -X3024710Y5027758D01* -X2858598Y5193870D01* -X1490507Y5193870D01* -X1490507Y5148200D01* -G75* -D01* -G02X1426700Y5084393I-63807J0* -G01* -X1311700Y5084393D01* -G75* -D01* -G02X1247893Y5148200I0J63807* -G01* -X1247893Y5418200D01* -G75* -D01* -G02X1311700Y5482007I63807J0* -G01* -X1426700Y5482007D01* -G74* -D01* -G02X1490507Y5418200I0J63807* -G01* -X1490507Y5372530D01* -X2022842Y5372530D01* -G75* -D01* -G02X2244358Y5372530I110758J120269* -G01* -X2530842Y5372530D01* -G75* -D01* -G02X2752358Y5372530I110758J120269* -G01* -X2895490Y5372530D01* -G75* -D01* -G02X2928856Y5366109I110J-89330* -G01* -G74* -D01* -G02X2958845Y5346286I33257J82908* -G01* -X3177126Y5128005D01* -G74* -D01* -G02X3203370Y5064760I63085J63245* -G01* -X3203370Y4345940D01* -G75* -D01* -G02X3177126Y4282695I-89330J1* -G01* -X3047745Y4153314D01* -G74* -D01* -G02X3021121Y4134921I63246J63084* -G01* -G74* -D01* -G02X3021430Y4127500I89020J7424* -G01* -X3021430Y4051300D01* -G75* -D01* -G02X2940855Y3962400I-89330J0* -G01* -G74* -D01* -G02X3021148Y3880588I8755J88900* -G01* -X2939188Y3880588D01* -X2939188Y3962252D01* -G74* -D01* -G02X2932100Y3961970I7092J89047* -G01* -X2855900Y3961970D01* -G75* -D01* -G02X2848812Y3962252I4J89330* -G01* -X2848812Y3880588D01* -X2810004Y3880588D01* -G74* -D01* -G02X2740855Y3835400I77904J43711* -G01* -G74* -D01* -G02X2810004Y3790212I8756J88900* -G01* -X2848812Y3790212D01* -X2848812Y3708548D01* -G75* -D01* -G02X2855900Y3708830I7092J-89047* -G01* -X2932100Y3708830D01* -G74* -D01* -G02X2939188Y3708548I4J89330* -G01* -X2939188Y3790212D01* -X3021148Y3790212D01* -G74* -D01* -G02X2940855Y3708400I89047J7087* -G01* -G74* -D01* -G02X3021430Y3619500I8755J88900* -G01* -X3021430Y3543300D01* -G75* -D01* -G02X2940855Y3454400I-89330J0* -G01* -G74* -D01* -G02X3021430Y3365500I8755J88900* -G01* -X3021430Y3289300D01* -G75* -D01* -G02X3001389Y3232918I-89330J0* -G01* -X3318326Y3232918D01* -G75* -D01* -G02X3364423Y3252607I46097J-44117* -G01* -X3412122Y3252607D01* -G75* -D01* -G02X3496724Y3252607I42301J-153806* -G01* -X3544423Y3252607D01* -G74* -D01* -G02X3608231Y3188800I1J63807* -G01* -X3608231Y3141101D01* -G75* -D01* -G02X3608231Y3056499I-153807J-42301* -G01* -X3608231Y3008800D01* -G75* -D01* -G02X3544423Y2944993I-63808J1* -G01* -X3523668Y2944993D01* -G74* -D01* -G02X3429023Y2913882I94645J128407* -G01* -X3001389Y2913882D01* -G74* -D01* -G02X3021430Y2857500I69289J56382* -G01* -X3021430Y2781300D01* -G75* -D01* -G02X2940855Y2692400I-89330J0* -G01* -G74* -D01* -G02X3021148Y2610588I8755J88900* -G01* -X2939188Y2610588D01* -X2939188Y2692252D01* -G74* -D01* -G02X2932100Y2691970I7092J89047* -G01* -X2855900Y2691970D01* -G75* -D01* -G02X2848812Y2692252I4J89330* -G01* -X2848812Y2610588D01* -X2810004Y2610588D01* -G74* -D01* -G02X2740855Y2565400I77904J43711* -G01* -G74* -D01* -G02X2810004Y2520212I8756J88900* -G01* -X2848812Y2520212D01* -X2848812Y2438548D01* -G75* -D01* -G02X2855900Y2438830I7092J-89047* -G01* -X2932100Y2438830D01* -G74* -D01* -G02X2939188Y2438548I4J89330* -G01* -X2939188Y2520212D01* -X3021148Y2520212D01* -G74* -D01* -G02X2940855Y2438400I89047J7087* -G01* -G74* -D01* -G02X3021430Y2349500I8755J88900* -G01* -X3021430Y2273300D01* -G75* -D01* -G02X2940855Y2184400I-89330J0* -G01* -G74* -D01* -G02X3021430Y2095500I8755J88900* -G01* -X3021430Y2019300D01* -G75* -D01* -G02X2940855Y1930400I-89330J0* -G01* -G74* -D01* -G02X3021430Y1841500I8755J88900* -G01* -X3021430Y1765300D01* -G75* -D01* -G02X2932100Y1675970I-89330J0* -G01* -X2855900Y1675970D01* -G75* -D01* -G02X2821430Y1682888I-1J89330* -G01* -X2821430Y1638300D01* -G75* -D01* -G02X2732100Y1548970I-89330J0* -G01* -X2655900Y1548970D01* -G75* -D01* -G02X2566570Y1638300I0J89330* -G01* -X2566570Y1714500D01* -G74* -D01* -G02X2647145Y1803400I89330J0* -G01* -G75* -D01* -G02X2566570Y1892300I8755J88900* -G01* -X2566570Y1968500D01* -G74* -D01* -G02X2647145Y2057400I89330J0* -G01* -G74* -D01* -G02X2582720Y2095070I8755J88900* -G01* -X2045877Y2095070D01* -G75* -D01* -G02X1982664Y2121234I-47J89330* -G01* -X1562434Y2541464D01* -G75* -D01* -G02X1536270Y2604716I63166J63166* -G01* -X1536270Y2878293D01* -X1490600Y2878293D01* -G75* -D01* -G02X1427407Y2933270I0J63807* -G01* -X725170Y2933270D01* -G75* -D01* -G02X701417Y2936486I0J89330* -G01* -G74* -D01* -G02X662004Y2959434I23752J86113* -G01* -X597330Y3024108D01* -X597330Y2563005D01* -G74* -D01* -G02X648212Y2472271I55458J90734* -G01* -X648212Y2404529D01* -G75* -D01* -G02X597330Y2313795I-106341J0* -G01* -X597330Y2297269D01* -G75* -D01* -G02X597330Y2071531I-89329J-112869* -G01* -X597330Y2043269D01* -G75* -D01* -G02X597330Y1817531I-89329J-112869* -G01* -X597330Y1789269D01* -G75* -D01* -G02X597330Y1563531I-89329J-112869* -G01* -X597330Y1486330D01* -X1473200Y1486330D01* -G74* -D01* -G02X1536445Y1460086I1J89330* -G01* -X1815686Y1180845D01* -G74* -D01* -G02X1827393Y1166446I63083J63248* -G01* -X1827393Y1172700D01* -G75* -D01* -G02X1891200Y1236507I63807J0* -G01* -X1891870Y1236507D01* -X1891870Y1239969D01* -G75* -D01* -G02X1918034Y1303266I89330J131* -G01* -X2032334Y1417566D01* -G74* -D01* -G02X2052310Y1432595I63165J63167* -G01* -G75* -D01* -G02X2309229Y1443730I132090J-78194* -G01* -X2362157Y1443730D01* -G75* -D01* -G02X2370371Y1443356I48J-89330* -G01* -G74* -D01* -G02X2425445Y1417486I8172J88955* -G01* -X2603086Y1239845D01* -G74* -D01* -G02X2629330Y1176600I63085J63245* -G01* -X2629330Y644332D01* -G74* -D01* -G02X2702712Y517549I89329J136332* -G01* -X2712261Y508000D01* -X2702712Y498451D01* -G75* -D01* -G02X2413000Y405837I-162711J9547* -G01* -G75* -D01* -G02X2196670Y644332I-126997J102166* -G01* -X2196670Y693893D01* -X2196000Y693893D01* -G75* -D01* -G02X2140699Y725870I0J63807* -G01* -X2126501Y725870D01* -G74* -D01* -G02X2071200Y693893I55301J31830* -G01* -X1891200Y693893D01* -G75* -D01* -G02X1835899Y725870I0J63807* -G01* -X1826450Y725870D01* -G75* -D01* -G02X1763234Y752034I-50J89330* -G01* -X1689434Y825834D01* -G75* -D01* -G02X1663270Y889088I63166J63166* -G01* -X1663270Y1080598D01* -X1625968Y1117900D01* -G74* -D01* -G02X1625969Y1117600I76567J405* -G01* -X1625969Y736600D01* -G75* -D01* -G02X1567930Y662307I-76569J0* -G01* -G74* -D01* -G02X1550900Y659993I17028J61493* -G01* -X1293900Y659993D01* -G75* -D01* -G02X1276871Y662307I-1J63807* -G01* -G74* -D01* -G02X1266963Y665508I18531J74292* -G01* -X758963Y868708D01* -G75* -D01* -G02X710831Y939939I28436J71092* -G01* -X710831Y1117600D01* -G75* -D01* -G02X787400Y1194169I76569J0* -G01* -X1549400Y1194169D01* -G74* -D01* -G02X1549700Y1194168I105J76569* -G01* -X1436198Y1307670D01* -X508000Y1307670D01* -G75* -D01* -G02X418670Y1397000I0J89330* -G01* -X418670Y1563531D01* -G75* -D01* -G02X418670Y1789269I89329J112869* -G01* -X418670Y1817531D01* -G75* -D01* -G02X418670Y2043269I89329J112869* -G01* -X418670Y2071531D01* -G75* -D01* -G02X418670Y2297269I89329J112869* -G01* -X418670Y2313795D01* -G75* -D01* -G02X367788Y2404529I55458J90734* -G01* -X367788Y2472271D01* -G74* -D01* -G02X418670Y2563005I106341J0* -G01* -X418670Y4408331D01* -G75* -D01* -G02X418670Y4634069I89329J112869* -G01* -X418670Y4650595D01* -G75* -D01* -G02X367788Y4741329I55458J90734* -G01* -X367788Y4809071D01* -G74* -D01* -G02X418670Y4899805I106341J0* -G01* -X418670Y5029034D01* -G75* -D01* -G02X444834Y5092366I89330J166* -G01* -X698834Y5346366D01* -G74* -D01* -G02X749888Y5371705I63166J63166* -G01* -G75* -D01* -G02X762000Y5372530I12112J-88504* -G01* -X947893Y5372530D01* -X947893Y5418200D01* -G75* -D01* -G02X1011700Y5482007I63807J0* -G01* -X1126700Y5482007D01* -G74* -D01* -G02X1190507Y5418200I0J63807* -G01* -X1190507Y5148200D01* -G75* -D01* -G02X1126700Y5084393I-63807J0* -G01* -X1011700Y5084393D01* -G75* -D01* -G02X947893Y5148200I0J63807* -G01* -X947893Y5193870D01* -X799002Y5193870D01* -X597330Y4992198D01* -X597330Y4899805D01* -G74* -D01* -G02X648212Y4809071I55458J90734* -G01* -X648212Y4741329D01* -G75* -D01* -G02X597330Y4650595I-106341J0* -G01* -X597330Y4634069D01* -G75* -D01* -G02X597330Y4408331I-89329J-112869* -G01* -X597330Y4379992D01* -X646904Y4429566D01* -G74* -D01* -G02X677608Y4449623I63166J63166* -G01* -G75* -D01* -G02X710070Y4455730I32462J-83223* -G01* -X1141809Y4455730D01* -G75* -D01* -G02X1202249Y4648200I128190J65470* -G01* -G75* -D01* -G02X1382869Y4864530I67754J126998* -G01* -X2640232Y4864530D01* -G75* -D01* -G02X2669697Y4859565I98J-89330* -G01* -G74* -D01* -G02X2686444Y4851707I29368J84364* -G01* -X2757500Y4851707D01* -G74* -D01* -G02X2821307Y4787900I0J63807* -G01* -X1202249Y2311400D02* -G75* -D01* -G02X1337751Y2311400I67751J126999* -G01* -G75* -D01* -G02X1337751Y2057400I-67750J-127000* -G01* -G75* -D01* -G02X1337751Y1803400I-67750J-127000* -G01* -G75* -D01* -G02X1202249Y1803400I-67751J-126999* -G01* -G75* -D01* -G02X1202249Y2057400I67750J127000* -G01* -G75* -D01* -G02X1202249Y2311400I67750J127000* -G01* -X2986100Y5492800D02* -G75* -D01* -G02X2986100Y5492800I163500J0* -G01* -X3494100Y5492800D02* -G75* -D01* -G02X3494100Y5492800I163500J0* -G01* -X2864000Y2565400D02* -G75* -D01* -G02X2864000Y2565400I30000J0* -G01* -X2864000Y3835400D02* -G75* -D01* -G02X2864000Y3835400I30000J0* -G01* -X2860893Y359367D02* -G75* -D01* -G02X2727107Y359367I-66893J148633* -G01* -X2794000Y426261D01* -X2860893Y359367D01* -X3300593Y351300D02* -X3300593Y365783D01* -X3411383Y365783D01* -X3411383Y287493D01* -X3364400Y287493D01* -G75* -D01* -G02X3300593Y351300I0J63807* -G01* -X3608207Y351300D02* -G75* -D01* -G02X3544400Y287493I-63807J0* -G01* -X3497417Y287493D01* -X3497417Y365783D01* -X3608207Y365783D01* -X3608207Y351300D01* -X2727107Y656633D02* -G75* -D01* -G02X2860893Y656633I66893J-148633* -G01* -X2794000Y589739D01* -X2727107Y656633D01* -X2734729Y508000D02* -G75* -D01* -G02X2734729Y508000I59271J0* -G01* -X2942633Y574893D02* -G75* -D01* -G02X2942633Y441107I-148633J-66893* -G01* -X2875739Y508000D01* -X2942633Y574893D01* -X3364400Y530107D02* -X3411383Y530107D01* -X3411383Y451817D01* -X3300593Y451817D01* -X3300593Y466300D01* -G75* -D01* -G02X3364400Y530107I63807J0* -G01* -X3543730Y873971D02* -X3543730Y830107D01* -X3544400Y830107D01* -G74* -D01* -G02X3608207Y766300I0J63807* -G01* -X3608207Y651300D01* -G75* -D01* -G02X3544400Y587493I-63807J0* -G01* -X3364400Y587493D01* -G75* -D01* -G02X3300593Y651300I0J63807* -G01* -X3300593Y766300D01* -G75* -D01* -G02X3364400Y830107I63807J0* -G01* -X3365070Y830107D01* -X3365070Y873971D01* -G75* -D01* -G02X3543730Y873971I89330J124828* -G01* -X3608207Y466300D02* -X3608207Y451817D01* -X3497417Y451817D01* -X3497417Y530107D01* -X3544400Y530107D01* -G74* -D01* -G02X3608207Y466300I0J63807* -G01* -X1230093Y393800D02* -G75* -D01* -G02X1293900Y457607I63807J0* -G01* -X1354206Y457607D01* -X1354206Y321994D01* -X1230093Y321994D01* -X1230093Y393800D01* -X1614707Y393800D02* -X1614707Y321994D01* -X1490594Y321994D01* -X1490594Y457607D01* -X1550900Y457607D01* -G74* -D01* -G02X1614707Y393800I0J63807* -G01* -X242900Y1009600D02* -G75* -D01* -G02X242900Y1009600I163500J0* -G01* -X1590549Y63807D02* -X3936193Y63807D01* -X3936193Y6436193D01* -X63807Y6436193D01* -X63807Y63807D01* -X1254251Y63807D01* -G75* -D01* -G02X1230093Y113800I39649J49993* -G01* -X1230093Y185606D01* -X1354206Y185606D01* -X1354206Y63807D01* -X1490594Y63807D01* -X1490594Y185606D01* -X1614707Y185606D01* -X1614707Y113800D01* -G75* -D01* -G02X1590549Y63807I-63807J0* -G01* -X2376000Y993893D02* -X2196000Y993893D01* -G75* -D01* -G02X2140699Y1025870I0J63807* -G01* -X2126501Y1025870D01* -G74* -D01* -G02X2071200Y993893I55301J31830* -G01* -X1891200Y993893D01* -G75* -D01* -G02X1841930Y1017156I0J63807* -G01* -X1841930Y926002D01* -X1848142Y919789D01* -G75* -D01* -G02X1891200Y936507I43058J-47089* -G01* -X2071200Y936507D01* -G74* -D01* -G02X2126501Y904530I0J63807* -G01* -X2140699Y904530D01* -G75* -D01* -G02X2196000Y936507I55301J-31830* -G01* -X2376000Y936507D01* -G74* -D01* -G02X2439807Y872700I0J63807* -G01* -X2439807Y757700D01* -G75* -D01* -G02X2376000Y693893I-63807J0* -G01* -X2375330Y693893D01* -X2375330Y644332D01* -G74* -D01* -G02X2413000Y610163I89329J136332* -G01* -G74* -D01* -G02X2450670Y644332I126999J102163* -G01* -X2450670Y1139598D01* -X2439807Y1150461D01* -X2439807Y1057700D01* -G75* -D01* -G02X2376000Y993893I-63807J0* -G01* -X2309229Y1265070D02* -X2325198Y1265070D01* -X2353761Y1236507D01* -X2282703Y1236507D01* -G74* -D01* -G03X2309229Y1265070I98303J117891* -G01* -X1760600Y2878293D02* -X1714930Y2878293D01* -X1714930Y2641632D01* -X2020369Y2336193D01* -X2082832Y2273730D01* -X2582720Y2273730D01* -G74* -D01* -G02X2647145Y2311400I73180J51229* -G01* -G75* -D01* -G02X2566570Y2400300I8755J88900* -G01* -X2566570Y2476500D01* -G74* -D01* -G02X2647145Y2565400I89330J0* -G01* -G75* -D01* -G02X2566570Y2654300I8755J88900* -G01* -X2566570Y2730500D01* -G74* -D01* -G02X2647145Y2819400I89330J0* -G01* -G75* -D01* -G02X2566570Y2908300I8755J88900* -G01* -X2566570Y2984500D01* -G74* -D01* -G02X2647145Y3073400I89330J0* -G01* -G75* -D01* -G02X2566570Y3162300I8755J88900* -G01* -X2566570Y3238500D01* -G74* -D01* -G02X2586611Y3294882I89330J0* -G01* -X2184423Y3294882D01* -G75* -D01* -G02X2142122Y3300593I0J159518* -G01* -X2094423Y3300593D01* -G75* -D01* -G02X2030616Y3364400I0J63807* -G01* -X2030616Y3412099D01* -G75* -D01* -G02X2030616Y3496701I153806J42301* -G01* -X2030616Y3544400D01* -G75* -D01* -G02X2094423Y3608207I63807J0* -G01* -X2142122Y3608207D01* -G75* -D01* -G02X2184423Y3613918I42301J-153806* -G01* -X2586611Y3613918D01* -G75* -D01* -G02X2566570Y3670300I69289J56382* -G01* -X2566570Y3746500D01* -G74* -D01* -G02X2647145Y3835400I89330J0* -G01* -G74* -D01* -G02X2582720Y3873070I8755J88900* -G01* -X2495048Y3873070D01* -G74* -D01* -G02X2433500Y3826093I61547J16830* -G01* -X2318500Y3826093D01* -G75* -D01* -G02X2286000Y3834990I0J63807* -G01* -G74* -D01* -G02X2253500Y3826093I32500J54910* -G01* -X2138500Y3826093D01* -G75* -D01* -G02X2093648Y3844516I-1J63807* -G01* -X1714930Y3465798D01* -X1714930Y3420907D01* -X1760600Y3420907D01* -G74* -D01* -G02X1824407Y3357100I0J63807* -G01* -X1824407Y3242100D01* -G75* -D01* -G02X1760600Y3178293I-63807J0* -G01* -X1490600Y3178293D01* -G75* -D01* -G02X1433985Y3212670I0J63807* -G01* -X1252630Y3212670D01* -G75* -D01* -G02X1201009Y3212670I-25810J85519* -G01* -X925669Y3212670D01* -G75* -D01* -G02X702740Y3209231I-112869J89329* -G01* -X702740Y3171362D01* -X762172Y3111930D01* -X1457965Y3111930D01* -G75* -D01* -G02X1490600Y3120907I32634J-54830* -G01* -X1760600Y3120907D01* -G74* -D01* -G02X1824407Y3057100I0J63807* -G01* -X1824407Y2942100D01* -G75* -D01* -G02X1760600Y2878293I-63807J0* -G01* -X925669Y3391330D02* -G74* -D01* -G03X880551Y3429000I112868J89330* -G01* -G75* -D01* -G03X880551Y3683000I-67750J127000* -G01* -G74* -D01* -G03X925669Y3720670I67750J127000* -G01* -X1129185Y3720670D01* -G75* -D01* -G03X1185800Y3686293I56615J29430* -G01* -X1455800Y3686293D01* -G74* -D01* -G03X1512415Y3720670I0J63807* -G01* -X1715696Y3720670D01* -G75* -D01* -G03X1717149Y3720681I50J89330* -G01* -X1562434Y3565966D01* -G75* -D01* -G03X1536270Y3502656I63166J-63166* -G01* -X1536270Y3420907D01* -X1512537Y3420907D01* -G75* -D01* -G03X1519607Y3450100I-56736J29193* -G01* -X1519607Y3565100D01* -G74* -D01* -G03X1455800Y3628907I63807J0* -G01* -X1185800Y3628907D01* -G75* -D01* -G03X1121993Y3565100I0J-63807* -G01* -X1121993Y3450100D01* -G75* -D01* -G03X1160951Y3391330I63807J0* -G01* -X925669Y3391330D01* -X699445Y3644713D02* -G74* -D01* -G03X646764Y3619166I10485J88713* -G01* -X597330Y3569732D01* -X597330Y4127328D01* -X747072Y4277070D01* -X1435299Y4277070D01* -G75* -D01* -G03X1490600Y4245093I55301J31830* -G01* -X1760600Y4245093D01* -G75* -D01* -G03X1824407Y4308900I0J63807* -G01* -X1824407Y4423900D01* -G74* -D01* -G03X1760600Y4487707I63807J0* -G01* -X1490600Y4487707D01* -G75* -D01* -G03X1435299Y4455730I0J-63807* -G01* -X1398191Y4455730D01* -G74* -D01* -G03X1413342Y4508070I128190J65470* -G01* -X2056368Y4508070D01* -X1736005Y4187707D01* -X1490600Y4187707D01* -G75* -D01* -G03X1433985Y4153330I0J-63807* -G01* -X934099Y4153330D01* -G74* -D01* -G03X839254Y4204248I94845J62875* -G01* -X786346Y4204248D01* -G75* -D01* -G03X672552Y4090454I0J-113794* -G01* -X672552Y4037546D01* -G75* -D01* -G03X739463Y3933859I113794J0* -G01* -G75* -D01* -G03X745049Y3683000I73337J-123859* -G01* -G74* -D01* -G03X699445Y3644713I67749J127000* -G01* -X925669Y3899330D02* -G74* -D01* -G03X886137Y3933859I112869J89329* -G01* -G74* -D01* -G03X934099Y3974670I46883J103686* -G01* -X1436752Y3974670D01* -G75* -D01* -G03X1490600Y3945093I53848J34229* -G01* -X1724531Y3945093D01* -X1678768Y3899330D01* -X1509648Y3899330D01* -G74* -D01* -G03X1455800Y3928907I53848J34229* -G01* -X1185800Y3928907D01* -G75* -D01* -G03X1131952Y3899330I0J-63807* -G01* -X925669Y3899330D01* -X2566570Y4178300D02* -X2566570Y4254070D01* -X2286172Y4254070D01* -X2130277Y4098175D01* -G75* -D01* -G02X2138500Y4098707I8222J-63275* -G01* -X2253500Y4098707D01* -G74* -D01* -G02X2286000Y4089810I0J63807* -G01* -G75* -D01* -G02X2318500Y4098707I32500J-54910* -G01* -X2433500Y4098707D01* -G74* -D01* -G02X2495048Y4051730I1J63807* -G01* -X2582720Y4051730D01* -G74* -D01* -G02X2647145Y4089400I73180J51229* -G01* -G75* -D01* -G02X2566570Y4178300I8755J88900* -G01* -G54D12* -X3454400Y304600D02* -X3302000Y152200D01* -X3454400Y408800D02* -X3454400Y304600D01* -X2286000Y815200D02* -X2286000Y508000D01* -X3454400Y408800D02* -X2893200Y408800D01* -X2794000Y508000D01* -X3454400Y708800D02* -X3454400Y998800D01* -X1752600Y1117600D02* -X1752600Y889000D01* -X1826400Y815200D01* -X2286000Y815200D01* -X1473200Y1397000D02* -X1752600Y1117600D01* -X508000Y5029200D02* -X508000Y1397000D01* -X1473200Y1397000D01* -X1459230Y3302000D02* -X812800Y3302000D01* -X1625600Y3299600D02* -X1461630Y3299600D01* -X1715770Y3810000D02* -X812800Y3810000D01* -X2249170Y4343400D02* -X1715770Y3810000D01* -X2694000Y3962400D02* -X2376000Y3962400D01* -X2894000Y4343400D02* -X2249170Y4343400D01* -X2694000Y4216400D02* -X2984500Y4216400D01* -X3114040Y4345940D01* -X3114040Y5064760D01* -X1346200Y4597400D02* -X1270000Y4521200D01* -X2894000Y4597400D02* -X1346200Y4597400D01* -X762000Y5283200D02* -X508000Y5029200D01* -X3114040Y5064760D02* -X2895600Y5283200D01* -X1069200Y5283200D02* -X762000Y5283200D01* -X2895600Y5283200D02* -X1369200Y5283200D01* -X1981200Y1240100D02* -X2095500Y1354400D01* -X1981200Y1115200D02* -X1981200Y1240100D01* -X710070Y4366400D02* -X511810Y4168140D01* -X1625600Y4366400D02* -X710070Y4366400D01* -X613410Y3459480D02* -X709930Y3556000D01* -X812800Y3556000D01* -X725170Y3022600D02* -X613410Y3134360D01* -X725170Y3022600D02* -X1602600Y3022600D01* -X613410Y3134360D02* -X613410Y3459480D01* -X1602600Y3022600D02* -X1625600Y2999600D01* -X1625600Y2604630D02* -X2045830Y2184400D01* -X1625600Y2604630D02* -X1625600Y2999600D01* -X2045830Y2184400D02* -X2694000Y2184400D01* -X2085200Y3962400D02* -X1625600Y3502800D01* -X2085200Y3962400D02* -X2196000Y3962400D01* -X1625600Y3502800D02* -X1625600Y3299600D01* -X1320800Y3392170D02* -X1226820Y3298190D01* -X1320800Y3507600D02* -X1320800Y3392170D01* -X2691130Y4724400D02* -X2640330Y4775200D01* -X2694000Y4724400D02* -X2691130Y4724400D01* -X2640330Y4775200D02* -X1270000Y4775200D01* -X1738630Y4064000D02* -X2145030Y4470400D01* -X2694000Y4470400D01* -X812800Y4064000D02* -X1738630Y4064000D01* -X1981200Y1115200D02* -X2286000Y1115200D01* -X2540000Y508000D02* -X2540000Y1176600D01* -X2362200Y1354400D01* -X2095500Y1354400D02* -X2362200Y1354400D01* -G54D13* -X3694230Y152200D02* -X3840480Y298450D01* -X3840480Y298450D02* -X3840480Y3542030D01* -X3562350Y2565400D02* -X3835400Y2292350D01* -X2894000Y2565400D02* -X3562350Y2565400D01* -X2894000Y3073400D02* -X3429023Y3073400D01* -X3454423Y3098800D01* -X2694000Y3454400D02* -X2184423Y3454400D01* -X3840480Y3542030D02* -X3547110Y3835400D01* -X2894000Y3835400D01* -X3694230Y152200D02* -X1524000Y152200D01* -X1422400Y253800D01* -G54D14* -X-63500Y-63500D02* -X-63500Y599200D01* -X-63500Y-63500D02* -X349200Y-63500D01* -X4063500Y-63500D02* -X3650800Y-63500D01* -X4063500Y-63500D02* -X4063500Y599200D01* -X4063500Y6563500D02* -X4063500Y5900800D01* -X4063500Y6563500D02* -X3650800Y6563500D01* -X-63500Y6563500D02* -X349200Y6563500D01* -X-63500Y6563500D02* -X-63500Y5900800D01* -X-563500Y-63500D02* -X-1563500Y-63500D01* -X-1063500Y-563500D02* -X-1063500Y436500D01* -X-1438500Y-63500D02* -G75* -D01* -G02X-1438500Y-63500I375000J0* -G01* -X4563500Y-63500D02* -X5563500Y-63500D01* -X5063500Y-563500D02* -X5063500Y436500D01* -X4688500Y-63500D02* -G75* -D01* -G02X4688500Y-63500I375000J0* -G01* -X4938500Y-63500D02* -G75* -D01* -G02X4938500Y-63500I125000J0* -G01* -X-563500Y6563500D02* -X-1563500Y6563500D01* -X-1063500Y6063500D02* -X-1063500Y7063500D01* -X-1438500Y6563500D02* -G75* -D01* -G02X-1438500Y6563500I375000J0* -G01* -X-1313500Y6563500D02* -G75* -D01* -G02X-1313500Y6563500I250000J0* -G01* -X-1188500Y6563500D02* -G75* -D01* -G02X-1188500Y6563500I125000J0* -G01* -G54D15* -X406400Y1009600D03* -X914400Y1009600D03* -X3657600Y5492800D03* -X3149600Y5492800D03* -X2641600Y5492800D03* -X2133600Y5492800D03* -G54D16* -X3454400Y408800D03* -X3454400Y708800D03* -X1981200Y815200D03* -X1981200Y1115200D03* -X2286000Y815200D03* -X2286000Y1115200D03* -G54D17* -X1270000Y2438400D03* -X508000Y1676400D03* -X1270000Y1676400D03* -X508000Y1930400D03* -X508000Y2184400D03* -X1270000Y1930400D03* -X1270000Y2184400D03* -X508000Y4521200D03* -X1270000Y4521200D03* -X1270000Y4775200D03* -X812800Y3556000D03* -X812800Y3810000D03* -X812800Y3302000D03* -G54D18* -X508000Y2438400D03* -X508000Y4775200D03* -G54D19* -X474129Y2404529D02* -X541871Y2404529D01* -X541871Y2472271D01* -X474129Y2472271D01* -X474129Y2404529D01*D02* -X474129Y4741329D02* -X541871Y4741329D01* -X541871Y4809071D01* -X474129Y4809071D01* -X474129Y4741329D01*D02* -G54D20* -X2694000Y4470400D03* -X2694000Y4216400D03* -X2694000Y3962400D03* -X2694000Y3708400D03* -X2694000Y3454400D03* -X2694000Y3200400D03* -X2694000Y2946400D03* -X2694000Y2692400D03* -X2694000Y2438400D03* -X2694000Y2184400D03* -X2694000Y1930400D03* -X2694000Y1676400D03* -X2894000Y4597400D03* -X2894000Y4343400D03* -X2894000Y4089400D03* -X2894000Y3835400D03* -X2894000Y3581400D03* -X2894000Y3327400D03* -X2894000Y3073400D03* -X2894000Y2819400D03* -X2894000Y2565400D03* -X2894000Y2311400D03* -X2894000Y2057400D03* -X2894000Y1803400D03* -G54D21* -X2655900Y4432300D02* -X2732100Y4432300D01* -X2732100Y4508500D01* -X2655900Y4508500D01* -X2655900Y4432300D01*D02* -X2655900Y4178300D02* -X2732100Y4178300D01* -X2732100Y4254500D01* -X2655900Y4254500D01* -X2655900Y4178300D01*D02* -X2655900Y3924300D02* -X2732100Y3924300D01* -X2732100Y4000500D01* -X2655900Y4000500D01* -X2655900Y3924300D01*D02* -X2655900Y3670300D02* -X2732100Y3670300D01* -X2732100Y3746500D01* -X2655900Y3746500D01* -X2655900Y3670300D01*D02* -X2655900Y3416300D02* -X2732100Y3416300D01* -X2732100Y3492500D01* -X2655900Y3492500D01* -X2655900Y3416300D01*D02* -X2655900Y3162300D02* -X2732100Y3162300D01* -X2732100Y3238500D01* -X2655900Y3238500D01* -X2655900Y3162300D01*D02* -X2655900Y2908300D02* -X2732100Y2908300D01* -X2732100Y2984500D01* -X2655900Y2984500D01* -X2655900Y2908300D01*D02* -X2655900Y2654300D02* -X2732100Y2654300D01* -X2732100Y2730500D01* -X2655900Y2730500D01* -X2655900Y2654300D01*D02* -X2655900Y2400300D02* -X2732100Y2400300D01* -X2732100Y2476500D01* -X2655900Y2476500D01* -X2655900Y2400300D01*D02* -X2655900Y2146300D02* -X2732100Y2146300D01* -X2732100Y2222500D01* -X2655900Y2222500D01* -X2655900Y2146300D01*D02* -X2655900Y1892300D02* -X2732100Y1892300D01* -X2732100Y1968500D01* -X2655900Y1968500D01* -X2655900Y1892300D01*D02* -X2655900Y1638300D02* -X2732100Y1638300D01* -X2732100Y1714500D01* -X2655900Y1714500D01* -X2655900Y1638300D01*D02* -X2855900Y4559300D02* -X2932100Y4559300D01* -X2932100Y4635500D01* -X2855900Y4635500D01* -X2855900Y4559300D01*D02* -X2855900Y4305300D02* -X2932100Y4305300D01* -X2932100Y4381500D01* -X2855900Y4381500D01* -X2855900Y4305300D01*D02* -X2855900Y4051300D02* -X2932100Y4051300D01* -X2932100Y4127500D01* -X2855900Y4127500D01* -X2855900Y4051300D01*D02* -X2855900Y3797300D02* -X2932100Y3797300D01* -X2932100Y3873500D01* -X2855900Y3873500D01* -X2855900Y3797300D01*D02* -X2855900Y3543300D02* -X2932100Y3543300D01* -X2932100Y3619500D01* -X2855900Y3619500D01* -X2855900Y3543300D01*D02* -X2855900Y3289300D02* -X2932100Y3289300D01* -X2932100Y3365500D01* -X2855900Y3365500D01* -X2855900Y3289300D01*D02* -X2855900Y3035300D02* -X2932100Y3035300D01* -X2932100Y3111500D01* -X2855900Y3111500D01* -X2855900Y3035300D01*D02* -X2855900Y2781300D02* -X2932100Y2781300D01* -X2932100Y2857500D01* -X2855900Y2857500D01* -X2855900Y2781300D01*D02* -X2855900Y2527300D02* -X2932100Y2527300D01* -X2932100Y2603500D01* -X2855900Y2603500D01* -X2855900Y2527300D01*D02* -X2855900Y2273300D02* -X2932100Y2273300D01* -X2932100Y2349500D01* -X2855900Y2349500D01* -X2855900Y2273300D01*D02* -X2855900Y2019300D02* -X2932100Y2019300D01* -X2932100Y2095500D01* -X2855900Y2095500D01* -X2855900Y2019300D01*D02* -X2855900Y1765300D02* -X2932100Y1765300D01* -X2932100Y1841500D01* -X2855900Y1841500D01* -X2855900Y1765300D01*D02* -G54D22* -X2694000Y4724400D03* -G54D23* -X1625600Y3299600D03* -X1625600Y2999600D03* -X1625600Y4366400D03* -X1625600Y4066400D03* -X1320800Y3507600D03* -X1320800Y3807600D03* -G54D24* -X2196000Y3962400D03* -X2376000Y3962400D03* -G54D25* -X1069200Y5283200D03* -X1369200Y5283200D03* -G54D26* -X812800Y4064000D03* -G54D27* -X786346Y4037546D02* -X839254Y4037546D01* -X839254Y4090454D01* -X786346Y4090454D01* -X786346Y4037546D01*D02* -G54D28* -X3454400Y998800D03* -X2184400Y1354400D03* -G54D29* -X3454423Y3098800D03* -X2184423Y3454400D03* -G54D30* -X1422400Y863800D03* -X1422400Y253800D03* -G54D31* -X2286000Y508000D03* -X2540000Y508000D03* -X2794000Y508000D03* - -M00* diff --git a/tests/gerber_parsing_profiling/gerber_parsing_line_profile_1.py b/tests/gerber_parsing_profiling/gerber_parsing_line_profile_1.py deleted file mode 100644 index 1ff32fb0..00000000 --- a/tests/gerber_parsing_profiling/gerber_parsing_line_profile_1.py +++ /dev/null @@ -1,13 +0,0 @@ -# This script is for profiling Gerber.parse_lines() line by line. -# Run kernprof -l -v gerber_parsing_line_profile_1.py - -import sys -sys.path.append('../../') - -from flatcamParsers.ParseGerber import * - -log = logging.getLogger('base2') -log.setLevel(logging.WARNING) - -g = Gerber() -g.parse_file("gerber1.gbr") \ No newline at end of file diff --git a/tests/gerber_parsing_profiling/gerber_parsing_profile_1.py b/tests/gerber_parsing_profiling/gerber_parsing_profile_1.py deleted file mode 100644 index 60e7d02a..00000000 --- a/tests/gerber_parsing_profiling/gerber_parsing_profile_1.py +++ /dev/null @@ -1,17 +0,0 @@ -import cProfile -import pstats -import sys -sys.path.append('../../') - -from flatcamParsers.ParseGerber import * - -log = logging.getLogger('base2') -log.setLevel(logging.WARNING) - -g = Gerber() - -#cProfile.run('g.parse_file("gerber1.gbr")', 'gerber1_profile', sort='cumtime') -cProfile.run('g.parse_file("/home/jpcaram/flatcam_test_files/Gerbers/AVR_Transistor_Tester_silkscreen_top.GTO")', - 'gerber1_profile', sort='cumtime') -p = pstats.Stats('gerber1_profile') -p.strip_dirs().sort_stats('cumulative').print_stats(.1) \ No newline at end of file diff --git a/tests/new_window_test.py b/tests/new_window_test.py deleted file mode 100644 index 9d0eb4d8..00000000 --- a/tests/new_window_test.py +++ /dev/null @@ -1,70 +0,0 @@ -import sys -from PyQt5.Qt import * -from PyQt5 import QtGui, QtWidgets - - -class MyPopup(QWidget): - def __init__(self): - QWidget.__init__(self) - lay = QtWidgets.QVBoxLayout() - self.setLayout(lay) - lay.setContentsMargins(0, 0, 0, 0) - le = QtWidgets.QLineEdit() - le.setText("Abracadabra") - le.setReadOnly(True) - # le.setStyleSheet("QLineEdit { qproperty-frame: false }") - le.setFrame(False) - le.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - - # lay.addStretch() - but = QtWidgets.QPushButton("OK") - hlay = QtWidgets.QHBoxLayout() - hlay.setContentsMargins(0, 5, 5, 5) - - hlay.addStretch() - hlay.addWidget(but) - - lay.addWidget(le) - lay.addLayout(hlay) - # def paintEvent(self, e): - # dc = QtGui.QPainter(self) - # dc.drawLine(0, 0, 100, 100) - # dc.drawLine(100, 0, 0, 100) - - -class MainWindow(QMainWindow): - def __init__(self, *args): - QtWidgets.QMainWindow.__init__(self, *args) - self.cw = QtWidgets.QWidget(self) - self.setCentralWidget(self.cw) - self.btn1 = QtWidgets.QPushButton("Click me", self.cw) - self.btn1.setGeometry(QRect(0, 0, 100, 30)) - self.btn1.clicked.connect(self.doit) - self.w = None - - def doit(self): - print("Opening a new popup window...") - self.w = MyPopup() - self.w.setGeometry(QRect(100, 100, 400, 200)) - self.w.show() - - -class App(QApplication): - def __init__(self, *args): - QtWidgets.QApplication.__init__(self, *args) - self.main = MainWindow() - # self.lastWindowClosed.connect(self.byebye) - self.main.show() - - def byebye(self): - self.exit(0) - - -def main(args): - global app - app = App(args) - app.exec_() - - -if __name__ == "__main__": - main(sys.argv) \ No newline at end of file diff --git a/tests/other/destructor_test.py b/tests/other/destructor_test.py deleted file mode 100644 index 28950277..00000000 --- a/tests/other/destructor_test.py +++ /dev/null @@ -1,34 +0,0 @@ -import sys -from PyQt5 import QtCore, QtGui, QtWidgets - - -class MyObj(): - - def __init__(self): - pass - - def __del__(self): - print("##### Destroyed ######") - - -def parse(): - o = MyObj() - raise Exception("Intentional Exception") - - -class Example(QtWidgets.QWidget): - - def __init__(self): - super(Example, self).__init__() - - qbtn = QtWidgets.QPushButton('Raise', self) - qbtn.clicked.connect(parse) - - self.setWindowTitle('Quit button') - self.show() - - -if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) - ex = Example() - sys.exit(app.exec_()) \ No newline at end of file diff --git a/tests/other/profile_gerber_parser.py b/tests/other/profile_gerber_parser.py deleted file mode 100644 index bdf671c2..00000000 --- a/tests/other/profile_gerber_parser.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -os.chdir('../') - -from flatcamParsers.ParseGerber import * - -g = Gerber() -g.parse_file(r'C:\Users\jpcaram\Dropbox\CNC\pcbcam\test_files\PlacaReles-F_Cu.gtl') - diff --git a/tests/other/test_excellon_1.py b/tests/other/test_excellon_1.py deleted file mode 100644 index c3895bd7..00000000 --- a/tests/other/test_excellon_1.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Jan 05 13:30:47 2014 - -@author: jpcaram -""" - -import os -os.chdir('../') - -from flatcamParsers.ParseGerber import * -from flatcamParsers.ParseExcellon import * - -from matplotlib import pyplot - -# Gerber. To see if the Excellon is correct -project_dir = "tests/gerber_files" -gerber_filename = project_dir + "KiCad_Squarer-F_Cu.gtl" -g = Gerber() -g.parse_file(gerber_filename) -g.create_geometry() - -excellon_filename = project_dir + "KiCad_Squarer.drl" -ex = Excellon() -ex.parse_file(excellon_filename) -ex.create_geometry() - -#fig = Figure() -fig = pyplot.figure() -ax = fig.add_subplot(111) -ax.set_aspect(1) - -# Plot gerber -for geo in g.solid_geometry: - x, y = geo.exterior.coords.xy - pyplot.plot(x, y, 'k-') - for ints in geo.interiors: - x, y = ints.coords.xy - ax.plot(x, y, 'k-') - -# Plot excellon -for geo in ex.solid_geometry: - x, y = geo.exterior.coords.xy - pyplot.plot(x, y, 'r-') - for ints in geo.interiors: - x, y = ints.coords.xy - ax.plot(x, y, 'g-') - -fig.show() \ No newline at end of file diff --git a/tests/other/test_fcrts.py b/tests/other/test_fcrts.py deleted file mode 100644 index 5c1222d5..00000000 --- a/tests/other/test_fcrts.py +++ /dev/null @@ -1,37 +0,0 @@ -from camlib import * -from shapely.geometry import LineString, LinearRing - -s = FlatCAMRTreeStorage() - -geoms = [ - LinearRing(((0.5699056603773586, 0.7216037735849057), - (0.9885849056603774, 0.7216037735849057), - (0.9885849056603774, 0.6689622641509434), - (0.5699056603773586, 0.6689622641509434), - (0.5699056603773586, 0.7216037735849057))), - LineString(((0.8684952830188680, 0.6952830188679245), - (0.8680655198743615, 0.6865349890935113), - (0.8667803692948564, 0.6778712076279851), - (0.8646522079829676, 0.6693751114229638), - (0.8645044888670096, 0.6689622641509434))), - LineString(((0.9874952830188680, 0.6952830188679245), - (0.9864925023483531, 0.6748709493942936), - (0.9856160316877274, 0.6689622641509434))), - -] - -for geo in geoms: - s.insert(geo) - -current_pt = (0, 0) -pt, geo = s.nearest(current_pt) -while geo is not None: - print((pt, geo)) - print(("OBJECTS BEFORE:", s.objects)) - - #geo.coords = list(geo.coords[::-1]) - s.remove(geo) - - print(("OBJECTS AFTER:", s.objects)) - current_pt = geo.coords[-1] - pt, geo = s.nearest(current_pt) diff --git a/tests/other/test_plotg.py b/tests/other/test_plotg.py deleted file mode 100644 index 6ec1a3a3..00000000 --- a/tests/other/test_plotg.py +++ /dev/null @@ -1,48 +0,0 @@ -from shapely.geometry import LineString, Polygon -from shapely.ops import unary_union -from matplotlib.pyplot import plot, subplot, show, axes -from matplotlib.axes import * -from camlib import * - - -def plotg2(geo, solid_poly=False, color="black", linestyle='solid'): - - try: - for sub_geo in geo: - plotg2(sub_geo, solid_poly=solid_poly, color=color, linestyle=linestyle) - except TypeError: - if type(geo) == Polygon: - if solid_poly: - patch = PolygonPatch(geo, - #facecolor="#BBF268", - facecolor=color, - edgecolor="#006E20", - alpha=0.5, - zorder=2) - ax = subplot(111) - ax.add_patch(patch) - else: - x, y = geo.exterior.coords.xy - plot(x, y, color=color, linestyle=linestyle) - for ints in geo.interiors: - x, y = ints.coords.xy - plot(x, y, color=color, linestyle=linestyle) - - if type(geo) == LineString or type(geo) == LinearRing: - x, y = geo.coords.xy - plot(x, y, color=color, linestyle=linestyle) - - if type(geo) == Point: - x, y = geo.coords.xy - plot(x, y, 'o') - - -if __name__ == "__main__": - p = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]]) - paths = [ - LineString([[0.5, 2], [2, 4.5]]), - LineString([[2, 0.5], [4.5, 2]]) - ] - plotg2(p, solid_poly=True) - plotg2(paths, linestyle="dashed") - show() \ No newline at end of file diff --git a/tests/other/test_rt.py b/tests/other/test_rt.py deleted file mode 100644 index 2df9bd1a..00000000 --- a/tests/other/test_rt.py +++ /dev/null @@ -1,24 +0,0 @@ -from rtree import index as rtindex - -def pt2rect(pt): - return pt[0], pt[1], pt[0], pt[1] - -pts = [(0.0, 0.0), (1.0, 1.0), (0.0, 1.0)] - -p = rtindex.Property() -p.buffering_capacity = 1 -p.dimension = 2 -rt = rtindex.Index(properties=p) -#rt = rtindex.Index() - -# If interleaved is True, the coordinates must be in -# the form [xmin, ymin, ..., kmin, xmax, ymax, ..., kmax]. -print((rt.interleaved)) - -[rt.add(0, pt2rect(pt)) for pt in pts] -print([r.bbox for r in list(rt.nearest((0, 0), 10, True))]) - -for pt in pts: - rt.delete(0, pt2rect(pt)) - print((pt2rect(pt), [r.bbox for r in list(rt.nearest((0, 0), 10, True))])) - diff --git a/tests/svg/7segment_9,9.svg b/tests/svg/7segment_9,9.svg deleted file mode 100644 index ffe7c653..00000000 --- a/tests/svg/7segment_9,9.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/svg/Arduino Nano3_pcb.svg b/tests/svg/Arduino Nano3_pcb.svg deleted file mode 100644 index f1f3b0c2..00000000 --- a/tests/svg/Arduino Nano3_pcb.svg +++ /dev/null @@ -1,468 +0,0 @@ - - - - -Fritzing footprint generated by brd2svg - - - - element:J1 - - package:HEAD15-NOSS - - - - element:J2 - - package:HEAD15-NOSS-1 - - - - element:U2 - - package:SSOP28 - - - - element:U3 - - package:SOT223 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - layer 21 - - text:TX1 - - - TX1 - - - - - text:RX0 - - - RX0 - - - - - text:RST - - - RST - - - - - text:GND - - - GND - - - - - text:D2 - - - D2 - - - - - text:D3 - - - D3 - - - - - text:D4 - - - D4 - - - - - text:D5 - - - D5 - - - - - text:D6 - - - D6 - - - - - text:D7 - - - D7 - - - - - text:D8 - - - D8 - - - - - text:D9 - - - D9 - - - - - text:D10 - - - D10 - - - - - text:D11 - - - D11 - - - - - text:D12 - - - D12 - - - - - text:D13 - - - D13 - - - - - text:3V3 - - - 3V3 - - - - - text:REF - - - REF - - - - - text:A0 - - - A0 - - - - - text:A1 - - - A1 - - - - - text:A2 - - - A2 - - - - - text:A3 - - - A3 - - - - - text:A4 - - - A4 - - - - - text:A5 - - - A5 - - - - - text:A6 - - - A6 - - - - - text:A7 - - - A7 - - - - - text:5V - - - 5V - - - - - text:RST - - - RST - - - - - text:GND - - - GND - - - - - text:VIN - - - VIN - - - - - text:* - - - * - - - - - text:* - - - * - - - - - text:* - - - * - - - - - text:* - - - * - - - - - text:* - - - * - - - - - text:* - - - * - - - - - element:C1 - - package:CAP0805-NP - - - - element:C2 - - package:TAN-A - - - - element:C3 - - package:CAP0805-NP - - - - element:C4 - - package:CAP0805-NP - - - - element:C7 - - package:CAP0805-NP - - - - element:C8 - - package:TAN-A - - - - element:C9 - - package:CAP0805-NP - - - - element:D1 - - package:SOD-123 - - - - element:J1 - - package:HEAD15-NOSS - - - - element:J2 - - package:HEAD15-NOSS-1 - - - - element:RP1 - - package:RES4NT - - - - element:RP2 - - package:RES4NT - - - - element:U$4 - - package:FIDUCIAL-1X2 - - - - element:U$37 - - package:FIDUCIAL-1X2 - - - - element:U$53 - - package:FIDUCIAL-1X2 - - - - element:U$54 - - package:FIDUCIAL-1X2 - - - - element:U2 - - package:SSOP28 - - - - element:U3 - - package:SOT223 - - - - diff --git a/tests/svg/drawing.svg b/tests/svg/drawing.svg deleted file mode 100644 index 7feb03a4..00000000 --- a/tests/svg/drawing.svg +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/svg/usb_connector.svg b/tests/svg/usb_connector.svg deleted file mode 100644 index 25db7071..00000000 --- a/tests/svg/usb_connector.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/tests/svg/use.svg b/tests/svg/use.svg deleted file mode 100644 index 735c5954..00000000 --- a/tests/svg/use.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/tests/test_excellon.py b/tests/test_excellon.py deleted file mode 100644 index da7e0233..00000000 --- a/tests/test_excellon.py +++ /dev/null @@ -1,331 +0,0 @@ -import unittest -from flatcamParsers.ParseExcellon import Excellon -from flatcamParsers.ParseGerber import Gerber - - -class ExcellonNumberParseTestInch(unittest.TestCase): - # Inch base format: 00.0000 - - # LEADING ZEROS - # With leading zeros, when you type in a coordinate, - # the leading zeros must always be included. Trailing zeros - # are unneeded and may be left off. The CNC-7 will automatically add them. - - # TRAILING ZEROS - # You must show all zeros to the right of the number and can omit - # all zeros to the left of the number. The CNC-7 will count the number - # of digits you typed and automatically fill in the missing zeros. - - def test_inch_leading_6digit(self): - excellon = Excellon() - self.assertEqual(excellon.zeros, "L") - self.assertEqual(excellon.parse_number("123456"), 12.3456) - - def test_inch_leading_5digit(self): - excellon = Excellon() - self.assertEqual(excellon.parse_number("12345"), 12.345) - - def test_inch_leading_15digit(self): - excellon = Excellon() - self.assertEqual(excellon.parse_number("012345"), 1.2345) - - def test_inch_leading_51digit(self): - excellon = Excellon() - self.assertEqual(excellon.parse_number("123450"), 12.345) - - def test_inch_trailing_6digit(self): - excellon = Excellon() - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("123456"), 12.3456) - - def test_inch_trailing_5digit(self): - excellon = Excellon() - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("12345"), 1.2345) - - def test_inch_trailing_15digit(self): - excellon = Excellon() - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("012345"), 1.2345) - - def test_inch_trailing_51digit(self): - excellon = Excellon() - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("123450"), 12.345) - - -class ExcellonNumberParseTestMetric(unittest.TestCase): - # Metric base format: 000.000 - - # LEADING ZEROS - # With leading zeros, when you type in a coordinate, - # the leading zeros must always be included. Trailing zeros - # are unneeded and may be left off. The CNC-7 will automatically add them. - - # TRAILING ZEROS - # You must show all zeros to the right of the number and can omit - # all zeros to the left of the number. The CNC-7 will count the number - # of digits you typed and automatically fill in the missing zeros. - - def test_inch_leading_6digit(self): - excellon = Excellon() - excellon.units = "mm" - self.assertEqual(excellon.parse_number("123456"), 123.456) - - def test_inch_leading_5digit(self): - excellon = Excellon() - excellon.units = "mm" - self.assertEqual(excellon.parse_number("12345"), 123.45) - - def test_inch_leading_15digit(self): - excellon = Excellon() - excellon.units = "mm" - self.assertEqual(excellon.parse_number("012345"), 12.345) - - def test_inch_leading_51digit(self): - excellon = Excellon() - excellon.units = "mm" - self.assertEqual(excellon.parse_number("123450"), 123.45) - - def test_inch_trailing_6digit(self): - excellon = Excellon() - excellon.units = "mm" - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("123456"), 123.456) - - def test_inch_trailing_5digit(self): - excellon = Excellon() - excellon.units = "mm" - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("12345"), 12.345) - - def test_inch_trailing_15digit(self): - excellon = Excellon() - excellon.units = "mm" - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("012345"), 12.345) - - def test_inch_trailing_51digit(self): - excellon = Excellon() - excellon.units = "mm" - excellon.zeros = "T" - self.assertEqual(excellon.parse_number("123450"), 123.45) - - -class ExcellonFormatM72Test(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - M72 - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "in") - self.assertEqual(self.excellon.zeros, "L") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (90.0, 11.75)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (30.25, 10.5)) - - -class ExcellonFormatM71Test(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - M71 - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "mm") - self.assertEqual(self.excellon.zeros, "L") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (900.0, 117.5)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (302.5, 105.0)) - - -class ExcellonFormatINCHLZTest(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - INCH,LZ - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "in") - self.assertEqual(self.excellon.zeros, "L") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (90.0, 11.75)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (30.25, 10.5)) - - -class ExcellonFormatINCHTest(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - INCH,LZ - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "in") - self.assertEqual(self.excellon.zeros, "L") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (90.0, 11.75)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (30.25, 10.5)) - - -class ExcellonFormatINCHTZTest(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - INCH,TZ - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "in") - self.assertEqual(self.excellon.zeros, "T") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (0.9, 1.175)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (3.025, 1.05)) - - -class ExcellonFormatMETRICLZTest(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - METRIC,LZ - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "mm") - self.assertEqual(self.excellon.zeros, "L") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (900.0, 117.5)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (302.5, 105.0)) - - -class ExcellonFormatMETRICTest(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - METRIC,LZ - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "mm") - self.assertEqual(self.excellon.zeros, "L") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (900.0, 117.5)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (302.5, 105.0)) - - -class ExcellonFormatMETRICTZTest(unittest.TestCase): - - def setUp(self): - self.excellon = Excellon() - code = """ - M48 - METRIC,TZ - T1C.02362F197S550 - T2C.03543F197S550 - M95 - T1 - X9000Y11750 - X30250Y10500 - """ - code = code.split('\n') - self.excellon.parse_lines(code) - - def test_format(self): - self.assertEqual(self.excellon.units.lower(), "mm") - self.assertEqual(self.excellon.zeros, "T") - - def test_coords(self): - # For X9000 add the missing 00 on the right. Then divide by 10000. - self.assertEqual(self.excellon.drills[0]["point"].coords[0], (9.0, 11.75)) - self.assertEqual(self.excellon.drills[1]["point"].coords[0], (30.25, 10.5)) - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_excellon_flow.py b/tests/test_excellon_flow.py deleted file mode 100644 index 946c2df6..00000000 --- a/tests/test_excellon_flow.py +++ /dev/null @@ -1,163 +0,0 @@ -import unittest -from PyQt5 import QtGui, QtWidgets -import sys -from FlatCAMApp import App -from FlatCAMObj import FlatCAMExcellon, FlatCAMCNCjob -from flatcamGUI.ObjectUI import ExcellonObjectUI -import tempfile -import os -from time import sleep - - -class ExcellonFlowTestCase(unittest.TestCase): - """ - This is a top-level test covering the Excellon-to-GCode - generation workflow. - - THIS IS A REQUIRED TEST FOR ANY UPDATES. - - """ - - filename = 'case1.drl' - - def setUp(self): - self.app = QtWidgets.QApplication(sys.argv) - - # Create App, keep app defaults (do not load - # user-defined defaults). - self.fc = App(user_defaults=False) - - self.fc.open_excellon('tests/excellon_files/' + self.filename) - - def tearDown(self): - del self.fc - del self.app - - def test_flow(self): - # Names of available objects. - names = self.fc.collection.get_names() - print(names) - - #-------------------------------------- - # Total of 1 objects. - #-------------------------------------- - self.assertEqual(len(names), 1, - "Expected 1 object, found %d" % len(names)) - - #-------------------------------------- - # Object's name matches the file name. - #-------------------------------------- - self.assertEqual(names[0], self.filename, - "Expected name == %s, got %s" % (self.filename, names[0])) - - #--------------------------------------- - # Get object by that name, make sure it's a FlatCAMExcellon. - #--------------------------------------- - excellon_name = names[0] - excellon_obj = self.fc.collection.get_by_name(excellon_name) - self.assertTrue(isinstance(excellon_obj, FlatCAMExcellon), - "Expected FlatCAMExcellon, instead, %s is %s" % - (excellon_name, type(excellon_obj))) - - #---------------------------------------- - # Object's GUI matches Object's options - #---------------------------------------- - # TODO: Open GUI with double-click on object. - # Opens the Object's GUI, populates it. - excellon_obj.build_ui() - for option, value in list(excellon_obj.options.items()): - try: - form_field = excellon_obj.form_fields[option] - except KeyError: - print(("**********************************************************\n" - "* WARNING: Option '{}' has no form field\n" - "**********************************************************" - "".format(option))) - continue - self.assertEqual(value, form_field.get_value(), - "Option '{}' == {} but form has {}".format( - option, value, form_field.get_value() - )) - - # -------------------------------------------------- - # Changes in the GUI should be read in when - # running any process. Changing something here. - # -------------------------------------------------- - - form_field = excellon_obj.form_fields['feedrate'] - value = form_field.get_value() - form_field.set_value(value * 1.1) # Increase by 10% - print(("'feedrate' == {}".format(value))) - - # -------------------------------------------------- - # Create GCode using all tools. - # -------------------------------------------------- - - assert isinstance(excellon_obj, FlatCAMExcellon) # Just for the IDE - ui = excellon_obj.ui - assert isinstance(ui, ExcellonObjectUI) - ui.tools_table.selectAll() # Select All - ui.generate_cnc_button.click() # Click - - # Work is done in a separate thread and results are - # passed via events to the main event loop which is - # not running. Run only for pending events. - # - # I'm not sure why, but running it only once does - # not catch the new object. Might be a timing issue. - # http://pyqt.sourceforge.net/Docs/PyQt4/qeventloop.html#details - for _ in range(2): - sleep(0.1) - self.app.processEvents() - - # --------------------------------------------- - # Check that GUI has been read in. - # --------------------------------------------- - - value = excellon_obj.options['feedrate'] - form_value = form_field.get_value() - self.assertEqual(value, form_value, - "Form value for '{}' == {} was not read into options" - "which has {}".format('feedrate', form_value, value)) - print(("'feedrate' == {}".format(value))) - - # --------------------------------------------- - # Check that only 1 object has been created. - # --------------------------------------------- - - names = self.fc.collection.get_names() - self.assertEqual(len(names), 2, - "Expected 2 objects, found %d" % len(names)) - - # ------------------------------------------------------- - # Make sure the CNCJob Object has the correct name - # ------------------------------------------------------- - - cncjob_name = excellon_name + "_cnc" - self.assertTrue(cncjob_name in names, - "Object named %s not found." % cncjob_name) - - # ------------------------------------------------------- - # Get the object make sure it's a cncjob object - # ------------------------------------------------------- - - cncjob_obj = self.fc.collection.get_by_name(cncjob_name) - self.assertTrue(isinstance(cncjob_obj, FlatCAMCNCjob), - "Expected a FlatCAMCNCjob, got %s" % type(cncjob_obj)) - - # ----------------------------------------- - # Export G-Code, check output - # ----------------------------------------- - assert isinstance(cncjob_obj, FlatCAMCNCjob) # For IDE - - # get system temporary file(try create it and delete) - with tempfile.NamedTemporaryFile(prefix='unittest.', - suffix="." + cncjob_name + '.gcode', - delete=True) as tmp_file: - output_filename = tmp_file.name - - cncjob_obj.export_gcode(output_filename) - self.assertTrue(os.path.isfile(output_filename)) - os.remove(output_filename) - - print(names) diff --git a/tests/test_gerber_buffer.py b/tests/test_gerber_buffer.py deleted file mode 100644 index b79893eb..00000000 --- a/tests/test_gerber_buffer.py +++ /dev/null @@ -1,35 +0,0 @@ -import unittest -import camlib -from flatcamParsers.ParseGerber import Gerber - - -class GerberBuffer(unittest.TestCase): - def setUp(self): - self.gerber1 = Gerber() - self.gerber1.use_buffer_for_union = True - self.gerber1.parse_file("tests/gerber_files/STM32F4-spindle.cmp") - geometry1 = self.gerber1.solid_geometry - self.geometry1_area = self.compute_area(geometry1) - self.gerber2 = Gerber() - self.gerber2.use_buffer_for_union = False - self.gerber2.parse_file("tests/gerber_files/STM32F4-spindle.cmp") - geometry2 = self.gerber2.solid_geometry - self.geometry2_area = self.compute_area (geometry2) - - def compute_area(self, geometry): - area = 0 - try: - for geo in geometry: - area += geo.area - - # Not iterable, do the actual indexing and add. - except TypeError: - area = geometry.area - return area - - def test_buffer(self): - self.assertLessEqual(abs(self.geometry2_area - self.geometry1_area), 0.000001) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_gerber_flow.py b/tests/test_gerber_flow.py deleted file mode 100644 index 4549afc0..00000000 --- a/tests/test_gerber_flow.py +++ /dev/null @@ -1,190 +0,0 @@ -import sys -import unittest -from PyQt5 import QtGui, QtWidgets -from FlatCAMApp import App, tclCommands -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob -from flatcamGUI.ObjectUI import GerberObjectUI, GeometryObjectUI -from time import sleep -import os -import tempfile - - -class GerberFlowTestCase(unittest.TestCase): - """ - This is a top-level test covering the Gerber-to-GCode - generation workflow. - - THIS IS A REQUIRED TEST FOR ANY UPDATES. - - """ - - filename = 'simple1.gbr' - - def setUp(self): - self.app = QtWidgets.QApplication(sys.argv) - - # Create App, keep app defaults (do not load - # user-defined defaults). - self.fc = App(user_defaults=False) - - self.fc.open_gerber('tests/gerber_files/' + self.filename) - - def tearDown(self): - del self.fc - del self.app - - def test_flow(self): - # Names of available objects. - names = self.fc.collection.get_names() - print(names) - - # -------------------------------------- - # Total of 1 objects. - # -------------------------------------- - self.assertEqual(len(names), 1, - "Expected 1 object, found %d" % len(names)) - - # -------------------------------------- - # Object's name matches the file name. - # -------------------------------------- - self.assertEqual(names[0], self.filename, - "Expected name == %s, got %s" % (self.filename, names[0])) - - # --------------------------------------- - # Get object by that name, make sure it's a FlatCAMGerber. - # --------------------------------------- - gerber_name = names[0] - gerber_obj = self.fc.collection.get_by_name(gerber_name) - self.assertTrue(isinstance(gerber_obj, FlatCAMGerber), - "Expected FlatCAMGerber, instead, %s is %s" % - (gerber_name, type(gerber_obj))) - - # ---------------------------------------- - # Object's GUI matches Object's options - # ---------------------------------------- - # TODO: Open GUI with double-click on object. - # Opens the Object's GUI, populates it. - gerber_obj.build_ui() - for option, value in list(gerber_obj.options.items()): - try: - form_field = gerber_obj.form_fields[option] - except KeyError: - print(("**********************************************************\n" - "* WARNING: Option '{}' has no form field\n" - "**********************************************************" - "".format(option))) - continue - self.assertEqual(value, form_field.get_value(), - "Option '{}' == {} but form has {}".format( - option, value, form_field.get_value() - )) - - # -------------------------------------------------- - # Changes in the GUI should be read in when - # running any process. Changing something here. - # -------------------------------------------------- - - form_field = gerber_obj.form_fields['isotooldia'] - value = form_field.get_value() - form_field.set_value(value * 1.1) # Increase by 10% - print(("'isotooldia' == {}".format(value))) - - # -------------------------------------------------- - # Create isolation routing using default values - # and by clicking on the button. - # -------------------------------------------------- - # Get the object's GUI and click on "Generate Geometry" under - # "Isolation Routing" - assert isinstance(gerber_obj, FlatCAMGerber) # Just for the IDE - # Changed: UI has been build already - #gerber_obj.build_ui() # Open the object's UI. - ui = gerber_obj.ui - assert isinstance(ui, GerberObjectUI) - ui.generate_iso_button.click() # Click - - # --------------------------------------------- - # Check that GUI has been read in. - # --------------------------------------------- - value = gerber_obj.options['isotooldia'] - form_value = form_field.get_value() - self.assertEqual(value, form_value, - "Form value for '{}' == {} was not read into options" - "which has {}".format('isotooldia', form_value, value)) - print(("'isotooldia' == {}".format(value))) - - # --------------------------------------------- - # Check that only 1 object has been created. - # --------------------------------------------- - names = self.fc.collection.get_names() - self.assertEqual(len(names), 2, - "Expected 2 objects, found %d" % len(names)) - - # ------------------------------------------------------- - # Make sure the Geometry Object has the correct name - # ------------------------------------------------------- - geo_name = gerber_name + "_iso" - self.assertTrue(geo_name in names, - "Object named %s not found." % geo_name) - - # ------------------------------------------------------- - # Get the object make sure it's a geometry object - # ------------------------------------------------------- - geo_obj = self.fc.collection.get_by_name(geo_name) - self.assertTrue(isinstance(geo_obj, FlatCAMGeometry), - "Expected a FlatCAMGeometry, got %s" % type(geo_obj)) - - # ------------------------------------ - # Open the UI, make CNCObject - # ------------------------------------ - geo_obj.build_ui() - ui = geo_obj.ui - assert isinstance(ui, GeometryObjectUI) # Just for the IDE - ui.generate_cnc_button.click() # Click - - # Work is done in a separate thread and results are - # passed via events to the main event loop which is - # not running. Run only for pending events. - # - # I'm not sure why, but running it only once does - # not catch the new object. Might be a timing issue. - # http://pyqt.sourceforge.net/Docs/PyQt4/qeventloop.html#details - for _ in range(2): - sleep(0.1) - self.app.processEvents() - - # --------------------------------------------- - # Check that only 1 object has been created. - # --------------------------------------------- - names = self.fc.collection.get_names() - self.assertEqual(len(names), 3, - "Expected 3 objects, found %d" % len(names)) - - # ------------------------------------------------------- - # Make sure the CNC Job Object has the correct name - # ------------------------------------------------------- - cnc_name = geo_name + "_cnc" - self.assertTrue(cnc_name in names, - "Object named %s not found." % geo_name) - - # ------------------------------------------------------- - # Get the object make sure it's a CNC Job object - # ------------------------------------------------------- - cnc_obj = self.fc.collection.get_by_name(cnc_name) - self.assertTrue(isinstance(cnc_obj, FlatCAMCNCjob), - "Expected a FlatCAMCNCJob, got %s" % type(geo_obj)) - - # ----------------------------------------- - # Export G-Code, check output - # ----------------------------------------- - assert isinstance(cnc_obj, FlatCAMCNCjob) - output_filename = "" - # get system temporary file(try create it and delete also) - with tempfile.NamedTemporaryFile(prefix='unittest.', - suffix="." + cnc_name + '.gcode', - delete=True) as tmp_file: - output_filename = tmp_file.name - cnc_obj.export_gcode(output_filename) - self.assertTrue(os.path.isfile(output_filename)) - os.remove(output_filename) - - print(names) diff --git a/tests/test_paint.py b/tests/test_paint.py deleted file mode 100644 index 2ee77be8..00000000 --- a/tests/test_paint.py +++ /dev/null @@ -1,213 +0,0 @@ -import unittest - -from shapely.geometry import LineString, Polygon -from shapely.ops import unary_union -from matplotlib.pyplot import plot, subplot, show, cla, clf, xlim, ylim, title -from matplotlib.axes import * -from camlib import * -from copy import deepcopy - - -def mkstorage(paths): - def get_pts(o): - return [o.coords[0], o.coords[-1]] - storage = FlatCAMRTreeStorage() - storage.get_points = get_pts - for p in paths: - storage.insert(p) - return storage - - -def plotg2(geo, solid_poly=False, color="black", linestyle='solid'): - - try: - for sub_geo in geo: - plotg2(sub_geo, solid_poly=solid_poly, color=color, linestyle=linestyle) - except TypeError: - if type(geo) == Polygon: - if solid_poly: - patch = PolygonPatch(geo, - #facecolor="#BBF268", - facecolor=color, - edgecolor="#006E20", - alpha=0.5, - zorder=2) - ax = subplot(111) - ax.add_patch(patch) - else: - x, y = geo.exterior.coords.xy - plot(x, y, color=color, linestyle=linestyle) - for ints in geo.interiors: - x, y = ints.coords.xy - plot(x, y, color=color, linestyle=linestyle) - - if type(geo) == LineString or type(geo) == LinearRing: - x, y = geo.coords.xy - plot(x, y, color=color, linestyle=linestyle) - - if type(geo) == Point: - x, y = geo.coords.xy - plot(x, y, 'o') - - -class PaintTestCase(unittest.TestCase): - # def __init__(self): - # super(PaintTestCase, self).__init__() - # self.boundary = None - # self.descr = None - - def plot_summary_A(self, paths, tooldia, result, msg): - plotg2(self.boundary, solid_poly=True, color="green") - plotg2(paths, color="red") - plotg2([r.buffer(tooldia / 2) for r in result], solid_poly=True, color="blue") - plotg2(result, color="black", linestyle='dashed') - title(msg) - xlim(0, 5) - ylim(0, 5) - show() - - -class PaintConnectTest(PaintTestCase): - """ - Simple rectangular boundary and paths inside. - """ - - def setUp(self): - self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]]) - - def test_jump(self): - print("Test: WALK Expected") - paths = [ - LineString([[0.5, 2], [2, 4.5]]), - LineString([[2, 0.5], [4.5, 2]]) - ] - for p in paths: - print(p) - - tooldia = 1.0 - - print("--") - result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia) - - result = list(result.get_objects()) - for r in result: - print(r) - - self.assertEqual(len(result), 1) - - # self.plot_summary_A(paths, tooldia, result, "WALK expected.") - - def test_no_jump1(self): - print("Test: FLY Expected") - paths = [ - LineString([[0, 2], [2, 5]]), - LineString([[2, 0], [5, 2]]) - ] - for p in paths: - print(p) - - tooldia = 1.0 - - print("--") - result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia) - - result = list(result.get_objects()) - for r in result: - print(r) - - self.assertEqual(len(result), len(paths)) - - # self.plot_summary_A(paths, tooldia, result, "FLY Expected") - - def test_no_jump2(self): - print("Test: FLY Expected") - paths = [ - LineString([[0.5, 2], [2, 4.5]]), - LineString([[2, 0.5], [4.5, 2]]) - ] - for p in paths: - print(p) - - tooldia = 1.1 - - print("--") - result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia) - - result = list(result.get_objects()) - for r in result: - print(r) - - self.assertEqual(len(result), len(paths)) - - # self.plot_summary_A(paths, tooldia, result, "FLY Expected") - - -class PaintConnectTest2(PaintTestCase): - """ - Boundary with an internal cutout. - """ - - def setUp(self): - self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]]) - self.boundary = self.boundary.difference( - Polygon([[2, 1], [3, 1], [3, 4], [2, 4]]) - ) - - def test_no_jump3(self): - print("TEST: No jump expected") - paths = [ - LineString([[0.5, 1], [1.5, 3]]), - LineString([[4, 1], [4, 4]]) - ] - for p in paths: - print(p) - - tooldia = 1.0 - - print("--") - result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia) - - result = list(result.get_objects()) - for r in result: - print(r) - - self.assertEqual(len(result), len(paths)) - - # self.plot_summary_A(paths, tooldia, result, "FLY Expected") - - -class PaintConnectTest3(PaintTestCase): - """ - Tests with linerings among elements. - """ - - def setUp(self): - self.boundary = Polygon([[0, 0], [0, 5], [5, 5], [5, 0]]) - print("TEST w/ LinearRings") - - def test_jump2(self): - print("Test: WALK Expected") - paths = [ - LineString([[0.5, 2], [2, 4.5]]), - LineString([[2, 0.5], [4.5, 2]]), - self.boundary.buffer(-0.5).exterior - ] - for p in paths: - print(p) - - tooldia = 1.0 - - print("--") - result = Geometry.paint_connect(mkstorage(deepcopy(paths)), self.boundary, tooldia) - - result = list(result.get_objects()) - for r in result: - print(r) - - self.assertEqual(len(result), 1) - - # self.plot_summary_A(paths, tooldia, result, "WALK Expected") - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_pathconnect.py b/tests/test_pathconnect.py deleted file mode 100644 index cf25389f..00000000 --- a/tests/test_pathconnect.py +++ /dev/null @@ -1,89 +0,0 @@ -import unittest - -from shapely.geometry import LineString, Polygon -from shapely.ops import unary_union -from matplotlib.pyplot import plot, subplot, show, cla, clf, xlim, ylim, title -from camlib import * -from random import random - - -def mkstorage(paths): - def get_pts(o): - return [o.coords[0], o.coords[-1]] - storage = FlatCAMRTreeStorage() - storage.get_points = get_pts - for p in paths: - storage.insert(p) - return storage - - -class PathConnectTest1(unittest.TestCase): - - def setUp(self): - print("PathConnectTest1.setUp()") - pass - - def test_simple_connect(self): - paths = [ - LineString([[0, 0], [1, 1]]), - LineString([[1, 1], [2, 1]]) - ] - - result = Geometry.path_connect(mkstorage(paths)) - - result = list(result.get_objects()) - self.assertEqual(len(result), 1) - self.assertTrue(result[0].equals(LineString([[0, 0], [1, 1], [2, 1]]))) - - def test_interfere_connect(self): - paths = [ - LineString([[0, 0], [1, 1]]), - LineString([[1, 1], [2, 1]]), - LineString([[-0.5, 0.5], [0.5, 0]]) - ] - - result = Geometry.path_connect(mkstorage(paths)) - - result = list(result.get_objects()) - self.assertEqual(len(result), 2) - matches = [p for p in result if p.equals(LineString([[0, 0], [1, 1], [2, 1]]))] - self.assertEqual(len(matches), 1) - - def test_simple_connect_offset1(self): - for i in range(20): - offset_x = random() - offset_y = random() - - paths = [ - LineString([[0 + offset_x, 0 + offset_y], [1 + offset_x, 1 + offset_y]]), - LineString([[1 + offset_x, 1 + offset_y], [2 + offset_x, 1 + offset_y]]) - ] - - result = Geometry.path_connect(mkstorage(paths)) - - result = list(result.get_objects()) - self.assertEqual(len(result), 1) - self.assertTrue(result[0].equals(LineString([[0 + offset_x, 0 + offset_y], - [1 + offset_x, 1 + offset_y], - [2 + offset_x, 1 + offset_y]]))) - - def test_ring_interfere_connect(self): - print() - print("TEST STARTING ...") - - paths = [ - LineString([[0, 0], [1, 1]]), - LineString([[1, 1], [2, 1]]), - LinearRing([[1, 1], [2, 2], [1, 3], [0, 2]]) - ] - - result = Geometry.path_connect(mkstorage(paths)) - - result = list(result.get_objects()) - self.assertEqual(len(result), 2) - matches = [p for p in result if p.equals(LineString([[0, 0], [1, 1], [2, 1]]))] - self.assertEqual(len(matches), 1) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_polygon_paint.py b/tests/test_polygon_paint.py deleted file mode 100644 index 34707fa2..00000000 --- a/tests/test_polygon_paint.py +++ /dev/null @@ -1,220 +0,0 @@ -import sys -import unittest -from PyQt5 import QtGui, QtWidgets -from FlatCAMApp import App -from FlatCAMObj import FlatCAMGeometry, FlatCAMCNCjob -from flatcamGUI.ObjectUI import GerberObjectUI, GeometryObjectUI -from time import sleep -import os -import tempfile -from shapely.geometry import LineString, LinearRing, Polygon, MultiPolygon - - -class PolyPaintTestCase(unittest.TestCase): - - def setUp(self): - self.app = QtWidgets.QApplication(sys.argv) - - # Create App, keep app defaults (do not load - # user-defined defaults). - self.fc = App(user_defaults=False) - - def tearDown(self): - - for _ in range(2): - self.app.processEvents() - - # NOTE: These are creating problems... - # del self.fc - # del self.app - - def test_poly_paint_svg_all(self): - - print("*********************************") - print("* svg_all *") - print("*********************************") - - # Clear workspace - self.fc.on_file_new() - for _ in range(2): - self.app.processEvents() - - # Open SVG with polygons - self.fc.import_svg('tests/svg/drawing.svg') - - name = self.fc.collection.get_names()[0] - - self.fc.collection.set_active(name) - - geo_obj = self.fc.collection.get_by_name(name) - - # Paint all polygons - geo_obj.paint_poly_all(5, 0.2, 1) - sleep(5) # Todo: Do not base it on fixed time. - for _ in range(2): - self.app.processEvents() - - # New object should be available - names = self.fc.collection.get_names() - - self.assertEqual(len(names), 2) - - # Verify new geometry makes sense - painted = self.fc.collection.get_by_name(names[-1]) - for geo in painted.solid_geometry: - # Correct Type - self.assertTrue(isinstance(geo, LineString)) - # Lots of points (Should be 1000s) - self.assertGreater(len(geo.coords), 2) - - def test_poly_paint_svg_click(self): - - print("*********************************") - print("* svg_click *") - print("*********************************") - - # Clear workspace - self.fc.on_file_new() - for _ in range(2): - self.app.processEvents() - - # Open SVG with polygons - self.fc.import_svg('tests/svg/drawing.svg') - - name = self.fc.collection.get_names()[0] - - self.fc.collection.set_active(name) - - geo_obj = self.fc.collection.get_by_name(name) - - # Paint all polygons - geo_obj.paint_poly_single_click([300, 700], 5, 0.2, 1) - sleep(5) - for _ in range(2): - self.app.processEvents() - - # New object should be available - names = self.fc.collection.get_names() - - sleep(1) - self.assertEqual(len(names), 2) - - # Verify new geometry makes sense - painted = self.fc.collection.get_by_name(names[-1]) - for geo in painted.solid_geometry: - # Correct Type - self.assertTrue(isinstance(geo, LineString)) - # Lots of points (Should be 1000s) - self.assertGreater(len(geo.coords), 2) - - def test_poly_paint_noncopper_all(self): - - print("*********************************") - print("* noncopper_all *") - print("*********************************") - - # Clear workspace - self.fc.on_file_new() - for _ in range(2): - self.app.processEvents() - - self.fc.open_gerber('tests/gerber_files/simple1.gbr') - sleep(1) - for _ in range(2): - self.app.processEvents() - - name = self.fc.collection.get_names()[0] - - gerber_obj = self.fc.collection.get_by_name(name) - - self.fc.collection.set_active(name) - - gerber_obj.on_generatenoncopper_button_click() - sleep(1) - for _ in range(2): - self.app.processEvents() - - # New object should be available - names = self.fc.collection.get_names() - - sleep(1) - self.assertEqual(len(names), 2) - - geoname = "simple1.gbr_noncopper" - geo_obj = self.fc.collection.get_by_name(geoname) - self.fc.collection.set_active(geoname) - - geo_obj.paint_poly_all(0.02, 0.2, 0) - sleep(5) - for _ in range(2): - self.app.processEvents() - - # New object should be available - names = self.fc.collection.get_names() - - sleep(1) - self.assertEqual(len(names), 3) - - # Verify new geometry makes sense - painted = self.fc.collection.get_by_name(names[-1]) - for geo in painted.solid_geometry: - # Correct Type - self.assertTrue(isinstance(geo, LineString)) - # Lots of points (Should be 1000s) - self.assertGreater(len(geo.coords), 2) - - def test_poly_paint_noncopper_click(self): - - print("*********************************") - print("* noncopper_click *") - print("*********************************") - - # Clear workspace - self.fc.on_file_new() - for _ in range(2): - self.app.processEvents() - - self.fc.open_gerber('tests/gerber_files/simple1.gbr') - sleep(1) - for _ in range(2): - self.app.processEvents() - - name = self.fc.collection.get_names()[0] - - gerber_obj = self.fc.collection.get_by_name(name) - - self.fc.collection.set_active(name) - - gerber_obj.on_generatenoncopper_button_click() - sleep(1) - for _ in range(2): - self.app.processEvents() - - # New object should be available - names = self.fc.collection.get_names() - - sleep(1) - self.assertEqual(len(names), 2) - - geoname = "simple1.gbr_noncopper" - geo_obj = self.fc.collection.get_by_name(geoname) - self.fc.collection.set_active(geoname) - - geo_obj.paint_poly_single_click([2.7, 1.0], 0.02, 0.2, 0) - sleep(5) - for _ in range(2): - self.app.processEvents() - - # New object should be available - names = self.fc.collection.get_names() - - sleep(1) - self.assertEqual(len(names), 3) - - # Verify new geometry makes sense - painted = self.fc.collection.get_by_name(names[-1]) - for geo in painted.solid_geometry: - # Correct Type - self.assertTrue(isinstance(geo, LineString)) - # Lots of points (Should be 1000s) - self.assertGreater(len(geo.coords), 2) diff --git a/tests/test_svg_flow.py b/tests/test_svg_flow.py deleted file mode 100644 index 7fbd6658..00000000 --- a/tests/test_svg_flow.py +++ /dev/null @@ -1,127 +0,0 @@ -import sys -import unittest -from PyQt5 import QtWidgets -from FlatCAMApp import App -from FlatCAMObj import FlatCAMGeometry, FlatCAMCNCjob -from flatcamGUI.ObjectUI import GerberObjectUI, GeometryObjectUI -from time import sleep -import os -import tempfile - - -class SVGFlowTestCase(unittest.TestCase): - - def setUp(self): - self.app = QtWidgets.QApplication(sys.argv) - - # Create App, keep app defaults (do not load - # user-defined defaults). - self.fc = App(user_defaults=False) - - self.filename = 'drawing.svg' - - def tearDown(self): - del self.fc - del self.app - - def test_flow(self): - - self.fc.import_svg('tests/svg/' + self.filename) - - names = self.fc.collection.get_names() - print(names) - - # -------------------------------------- - # Total of 1 objects. - # -------------------------------------- - self.assertEqual(len(names), 1, "Expected 1 object, found %d" % len(names)) - - # -------------------------------------- - # Object's name matches the file name. - # -------------------------------------- - self.assertEqual(names[0], self.filename, "Expected name == %s, got %s" % (self.filename, names[0])) - - # --------------------------------------- - # Get object by that name, make sure it's a FlatCAMGerber. - # --------------------------------------- - geo_name = names[0] - geo_obj = self.fc.collection.get_by_name(geo_name) - self.assertTrue(isinstance(geo_obj, FlatCAMGeometry), - "Expected FlatCAMGeometry, instead, %s is %s" % - (geo_name, type(geo_obj))) - - # ---------------------------------------- - # Object's GUI matches Object's options - # ---------------------------------------- - # TODO: Open GUI with double-click on object. - # Opens the Object's GUI, populates it. - geo_obj.build_ui() - for option, value in list(geo_obj.options.items()): - try: - form_field = geo_obj.form_fields[option] - except KeyError: - print(("**********************************************************\n" - "* WARNING: Option '{}' has no form field\n" - "**********************************************************" - "".format(option))) - continue - self.assertEqual(value, form_field.get_value(), - "Option '{}' == {} but form has {}".format( - option, value, form_field.get_value() - )) - - # ------------------------------------ - # Open the UI, make CNCObject - # ------------------------------------ - geo_obj.build_ui() - ui = geo_obj.ui - assert isinstance(ui, GeometryObjectUI) # Just for the IDE - ui.generate_cnc_button.click() # Click - - # Work is done in a separate thread and results are - # passed via events to the main event loop which is - # not running. Run only for pending events. - # - # I'm not sure why, but running it only once does - # not catch the new object. Might be a timing issue. - # http://pyqt.sourceforge.net/Docs/PyQt4/qeventloop.html#details - for _ in range(2): - sleep(0.1) - self.app.processEvents() - - # --------------------------------------------- - # Check that only 1 object has been created. - # --------------------------------------------- - names = self.fc.collection.get_names() - self.assertEqual(len(names), 2, - "Expected 2 objects, found %d" % len(names)) - - # ------------------------------------------------------- - # Make sure the CNC Job Object has the correct name - # ------------------------------------------------------- - cnc_name = geo_name + "_cnc" - self.assertTrue(cnc_name in names, - "Object named %s not found." % geo_name) - - # ------------------------------------------------------- - # Get the object make sure it's a CNC Job object - # ------------------------------------------------------- - cnc_obj = self.fc.collection.get_by_name(cnc_name) - self.assertTrue(isinstance(cnc_obj, FlatCAMCNCjob), - "Expected a FlatCAMCNCJob, got %s" % type(geo_obj)) - - # ----------------------------------------- - # Export G-Code, check output - # ----------------------------------------- - assert isinstance(cnc_obj, FlatCAMCNCjob) - output_filename = "" - # get system temporary file(try create it and delete also) - with tempfile.NamedTemporaryFile(prefix='unittest.', - suffix="." + cnc_name + '.gcode', - delete=True) as tmp_file: - output_filename = tmp_file.name - cnc_obj.export_gcode(output_filename) - self.assertTrue(os.path.isfile(output_filename)) - os.remove(output_filename) - - print(names) diff --git a/tests/test_tclCommands/__init__.py b/tests/test_tclCommands/__init__.py deleted file mode 100644 index 615ad03d..00000000 --- a/tests/test_tclCommands/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -import pkgutil -import sys - -# allowed command tests (please append them alphabetically ordered) -from .test_TclCommandAddPolygon import * -from .test_TclCommandAddPolyline import * -from .test_TclCommandCncjob import * -from .test_TclCommandDrillcncjob import * -from .test_TclCommandExportGcode import * -from .test_TclCommandExteriors import * -from .test_TclCommandImportSvg import * -from .test_TclCommandInteriors import * -from .test_TclCommandIsolate import * -from .test_TclCommandNew import * -from .test_TclCommandNewGeometry import * -from .test_TclCommandOpenExcellon import * -from .test_TclCommandOpenGerber import * -from .test_TclCommandPaintPolygon import * diff --git a/tests/test_tclCommands/test_TclCommandAddPolygon.py b/tests/test_tclCommands/test_TclCommandAddPolygon.py deleted file mode 100644 index e2099add..00000000 --- a/tests/test_tclCommands/test_TclCommandAddPolygon.py +++ /dev/null @@ -1,18 +0,0 @@ -from FlatCAMObj import FlatCAMGeometry - - -def test_add_polygon(self): - """ - Test add polygon into geometry - :param self: - :return: - """ - - self.fc.exec_command_test('new_geometry "%s"' % self.geometry_name) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) - - points = '0 0 20 0 10 10 0 10' - - self.fc.exec_command_test('add_polygon "%s" %s' % (self.geometry_name, points)) diff --git a/tests/test_tclCommands/test_TclCommandAddPolyline.py b/tests/test_tclCommands/test_TclCommandAddPolyline.py deleted file mode 100644 index 69c0577d..00000000 --- a/tests/test_tclCommands/test_TclCommandAddPolyline.py +++ /dev/null @@ -1,18 +0,0 @@ -from FlatCAMObj import FlatCAMGeometry - - -def test_add_polyline(self): - """ - Test add polyline into geometry - :param self: - :return: - """ - - self.fc.exec_command_test('new_geometry "%s"' % self.geometry_name) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) - - points = '0 0 20 0 10 10 0 10 33 33' - - self.fc.exec_command_test('add_polyline "%s" %s' % (self.geometry_name, points)) diff --git a/tests/test_tclCommands/test_TclCommandCncjob.py b/tests/test_tclCommands/test_TclCommandCncjob.py deleted file mode 100644 index 63ded99b..00000000 --- a/tests/test_tclCommands/test_TclCommandCncjob.py +++ /dev/null @@ -1,17 +0,0 @@ -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMObj -from .test_TclCommandIsolate import * - -def test_cncjob(self): - """ - Test cncjob - :param self: - :return: - """ - - # reuse isolate tests - test_isolate(self) - - self.fc.exec_command_test('cncjob %s_iso -tooldia 0.5 -z_cut 0.05 -z_move 3 -feedrate 300' % self.gerber_top_name) - cam_top_obj = self.fc.collection.get_by_name(self.gerber_top_name + '_iso_cnc') - self.assertTrue(isinstance(cam_top_obj, FlatCAMObj), "Expected FlatCAMObj, instead, %s is %s" - % (self.gerber_top_name + '_iso_cnc', type(cam_top_obj))) \ No newline at end of file diff --git a/tests/test_tclCommands/test_TclCommandDrillcncjob.py b/tests/test_tclCommands/test_TclCommandDrillcncjob.py deleted file mode 100644 index ce651a6e..00000000 --- a/tests/test_tclCommands/test_TclCommandDrillcncjob.py +++ /dev/null @@ -1,18 +0,0 @@ -from FlatCAMObj import FlatCAMObj -from .test_TclCommandOpenExcellon import * - - -def test_drillcncjob(self): - """ - Test cncjob - :param self: - :return: - """ - # reuse open excellontests - test_open_excellon(self) - - self.fc.exec_command_test('drillcncjob %s -tools all -drillz 0.5 -travelz 3 -feedrate 300' - % self.excellon_name) - cam_top_obj = self.fc.collection.get_by_name(self.excellon_name + '_cnc') - self.assertTrue(isinstance(cam_top_obj, FlatCAMObj), "Expected FlatCAMObj, instead, %s is %s" - % (self.excellon_name + '_cnc', type(cam_top_obj))) diff --git a/tests/test_tclCommands/test_TclCommandExportGcode.py b/tests/test_tclCommands/test_TclCommandExportGcode.py deleted file mode 100644 index 880b5bea..00000000 --- a/tests/test_tclCommands/test_TclCommandExportGcode.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -import tempfile - -from .test_TclCommandCncjob import * -from .test_TclCommandDrillcncjob import * - - -def test_export_gcodecncjob(self): - """ - Test cncjob - :param self: - :return: - """ - - # reuse tests - test_cncjob(self) - test_drillcncjob(self) - - with tempfile.NamedTemporaryFile(prefix='unittest.', suffix="." + self.excellon_name + '.gcode', delete=True)\ - as tmp_file: - output_filename = tmp_file.name - self.fc.exec_command_test('write_gcode "%s" "%s"' % (self.excellon_name + '_cnc', output_filename)) - self.assertTrue(os.path.isfile(output_filename)) - os.remove(output_filename) - - with tempfile.NamedTemporaryFile(prefix='unittest.', suffix="." + self.gerber_top_name + '.gcode', delete=True)\ - as tmp_file: - output_filename = tmp_file.name - self.fc.exec_command_test('write_gcode "%s" "%s"' % (self.gerber_top_name + '_iso_cnc', output_filename)) - self.assertTrue(os.path.isfile(output_filename)) - os.remove(output_filename) - - # TODO check what is inside files , it should be same every time \ No newline at end of file diff --git a/tests/test_tclCommands/test_TclCommandExteriors.py b/tests/test_tclCommands/test_TclCommandExteriors.py deleted file mode 100644 index da47be9f..00000000 --- a/tests/test_tclCommands/test_TclCommandExteriors.py +++ /dev/null @@ -1,24 +0,0 @@ -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry - - -def test_exteriors(self): - """ - Test exteriors - :param self: - :return: - """ - - self.fc.exec_command_test('open_gerber %s/%s -outname %s' - % (self.gerber_files, self.cutout_filename, self.gerber_cutout_name)) - gerber_cutout_obj = self.fc.collection.get_by_name(self.gerber_cutout_name) - self.assertTrue(isinstance(gerber_cutout_obj, FlatCAMGerber), "Expected FlatCAMGerber, instead, %s is %s" - % (self.gerber_cutout_name, type(gerber_cutout_obj))) - - # exteriors interiors and delete isolated traces - self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_cutout_name, self.engraver_diameter)) - self.fc.exec_command_test('exteriors %s -outname %s' - % (self.gerber_cutout_name + '_iso', self.gerber_cutout_name + '_iso_exterior')) - self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso')) - obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_iso_exterior') - self.assertTrue(isinstance(obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.gerber_cutout_name + '_iso_exterior', type(obj))) diff --git a/tests/test_tclCommands/test_TclCommandImportSvg.py b/tests/test_tclCommands/test_TclCommandImportSvg.py deleted file mode 100644 index 3db2590c..00000000 --- a/tests/test_tclCommands/test_TclCommandImportSvg.py +++ /dev/null @@ -1,60 +0,0 @@ -from os import listdir - -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry - - -def test_import_svg(self): - """ - Test all SVG files inside svg directory. - Problematic SVG files shold be put there as test reference. - :param self: - :return: - """ - - file_list = listdir(self.svg_files) - - for svg_file in file_list: - - # import without outname - self.fc.exec_command_test('import_svg "%s/%s"' % (self.svg_files, svg_file)) - - obj = self.fc.collection.get_by_name(svg_file) - self.assertTrue(isinstance(obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (svg_file, type(obj))) - - # import with outname - outname = '%s-%s' % (self.geometry_name, svg_file) - self.fc.exec_command_test('import_svg "%s/%s" -outname "%s"' % (self.svg_files, svg_file, outname)) - - obj = self.fc.collection.get_by_name(outname) - self.assertTrue(isinstance(obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (outname, type(obj))) - - names = self.fc.collection.get_names() - self.assertEqual(len(names), len(file_list)*2, - "Expected %d objects, found %d" % (len(file_list)*2, len(file_list))) - - -def test_import_svg_as_geometry(self): - - self.fc.exec_command_test('import_svg "%s/%s" -type geometry -outname "%s"' - % (self.svg_files, self.svg_filename, self.geometry_name)) - - obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(obj, FlatCAMGeometry) and not isinstance(obj, FlatCAMGerber), - "Expected FlatCAMGeometry, instead, %s is %s" % (self.geometry_name, type(obj))) - - -def test_import_svg_as_gerber(self): - - self.fc.exec_command_test('import_svg "%s/%s" -type gerber -outname "%s"' - % (self.svg_files, self.svg_filename, self.gerber_name)) - - obj = self.fc.collection.get_by_name(self.gerber_name) - self.assertTrue(isinstance(obj, FlatCAMGerber), - "Expected FlatCAMGerber, instead, %s is %s" % (self.gerber_name, type(obj))) - - self.fc.exec_command_test('isolate "%s"' % self.gerber_name) - obj = self.fc.collection.get_by_name(self.gerber_name+'_iso') - self.assertTrue(isinstance(obj, FlatCAMGeometry), - "Expected FlatCAMGeometry, instead, %s is %s" % (self.gerber_name+'_iso', type(obj))) diff --git a/tests/test_tclCommands/test_TclCommandInteriors.py b/tests/test_tclCommands/test_TclCommandInteriors.py deleted file mode 100644 index c58c380c..00000000 --- a/tests/test_tclCommands/test_TclCommandInteriors.py +++ /dev/null @@ -1,24 +0,0 @@ -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry - - -def test_interiors(self): - """ - Test interiors - :param self: - :return: - """ - - self.fc.exec_command_test('open_gerber %s/%s -outname %s' - % (self.gerber_files, self.cutout_filename, self.gerber_cutout_name)) - gerber_cutout_obj = self.fc.collection.get_by_name(self.gerber_cutout_name) - self.assertTrue(isinstance(gerber_cutout_obj, FlatCAMGerber), "Expected FlatCAMGerber, instead, %s is %s" - % (self.gerber_cutout_name, type(gerber_cutout_obj))) - - # interiors and delete isolated traces - self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_cutout_name, self.engraver_diameter)) - self.fc.exec_command_test('interiors %s -outname %s' - % (self.gerber_cutout_name + '_iso', self.gerber_cutout_name + '_iso_interior')) - self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso')) - obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_iso_interior') - self.assertTrue(isinstance(obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.gerber_cutout_name + '_iso_interior', type(obj))) diff --git a/tests/test_tclCommands/test_TclCommandIsolate.py b/tests/test_tclCommands/test_TclCommandIsolate.py deleted file mode 100644 index e61aa40b..00000000 --- a/tests/test_tclCommands/test_TclCommandIsolate.py +++ /dev/null @@ -1,21 +0,0 @@ -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry - - -def test_isolate(self): - """ - Test isolate gerber - :param self: - :return: - """ - - self.fc.exec_command_test('open_gerber %s/%s -outname %s' - % (self.gerber_files, self.copper_top_filename, self.gerber_top_name)) - gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name) - self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber), "Expected FlatCAMGerber, instead, %s is %s" - % (self.gerber_top_name, type(gerber_top_obj))) - - # isolate traces - self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_top_name, self.engraver_diameter)) - geometry_top_obj = self.fc.collection.get_by_name(self.gerber_top_name+'_iso') - self.assertTrue(isinstance(geometry_top_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.gerber_top_name+'_iso', type(geometry_top_obj))) \ No newline at end of file diff --git a/tests/test_tclCommands/test_TclCommandNew.py b/tests/test_tclCommands/test_TclCommandNew.py deleted file mode 100644 index 07eba0b7..00000000 --- a/tests/test_tclCommands/test_TclCommandNew.py +++ /dev/null @@ -1,48 +0,0 @@ -from FlatCAMObj import FlatCAMGeometry - - -def test_new(self): - """ - Test new project - :param self: - :return: - """ - - self.fc.exec_command_test('new_geometry "%s"' % self.geometry_name) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) - - self.fc.exec_command_test('proc testproc {} { puts "testresult" }') - - result = self.fc.exec_command_test('testproc') - - self.assertEqual(result, "testresult",'testproc should return "testresult"') - - self.fc.exec_command_test('set_sys units MM') - self.fc.exec_command_test('new') - - # object should not exists anymore - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertIsNone(geometry_obj, "Expected object to be None, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) - - # TODO after new it should delete all procedures and variables, we need to make sure "testproc" does not exists - - # Test it again with same names - - self.fc.exec_command_test('set_sys units MM') - self.fc.exec_command_test('new') - - self.fc.exec_command_test('new_geometry "%s"' % self.geometry_name) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) - - self.fc.exec_command_test('set_sys units MM') - self.fc.exec_command_test('new') - - # object should not exists anymore - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertIsNone(geometry_obj, "Expected object to be None, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) diff --git a/tests/test_tclCommands/test_TclCommandNewGeometry.py b/tests/test_tclCommands/test_TclCommandNewGeometry.py deleted file mode 100644 index 72e069c6..00000000 --- a/tests/test_tclCommands/test_TclCommandNewGeometry.py +++ /dev/null @@ -1,14 +0,0 @@ -from FlatCAMObj import FlatCAMGeometry - - -def test_new_geometry(self): - """ - Test create new geometry - :param self: - :return: - """ - - self.fc.exec_command_test('new_geometry "%s"' % self.geometry_name) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) diff --git a/tests/test_tclCommands/test_TclCommandOpenExcellon.py b/tests/test_tclCommands/test_TclCommandOpenExcellon.py deleted file mode 100644 index 7570ea53..00000000 --- a/tests/test_tclCommands/test_TclCommandOpenExcellon.py +++ /dev/null @@ -1,15 +0,0 @@ -from FlatCAMObj import FlatCAMExcellon - - -def test_open_excellon(self): - """ - Test open excellon file - :param self: - :return: - """ - - self.fc.exec_command_test('open_excellon %s/%s -outname %s' - % (self.gerber_files, self.excellon_filename, self.excellon_name)) - excellon_obj = self.fc.collection.get_by_name(self.excellon_name) - self.assertTrue(isinstance(excellon_obj, FlatCAMExcellon), "Expected FlatCAMExcellon, instead, %s is %s" - % (self.excellon_name, type(excellon_obj))) diff --git a/tests/test_tclCommands/test_TclCommandOpenGerber.py b/tests/test_tclCommands/test_TclCommandOpenGerber.py deleted file mode 100644 index 71d50b0d..00000000 --- a/tests/test_tclCommands/test_TclCommandOpenGerber.py +++ /dev/null @@ -1,25 +0,0 @@ -from FlatCAMObj import FlatCAMGerber - - -def test_open_gerber(self): - """ - Test open gerber file - :param self: - :return: - """ - - self.fc.exec_command_test('open_gerber %s/%s -outname %s' - % (self.gerber_files, self.copper_top_filename, self.gerber_top_name)) - gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name) - self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber), "Expected FlatCAMGerber, instead, %s is %s" - % (self.gerber_top_name, type(gerber_top_obj))) - - self.fc.exec_command_test('open_gerber %s/%s -outname %s' - % (self.gerber_files, self.copper_bottom_filename, self.gerber_bottom_name)) - gerber_bottom_obj = self.fc.collection.get_by_name(self.gerber_bottom_name) - self.assertTrue(isinstance(gerber_bottom_obj, FlatCAMGerber), "Expected FlatCAMGerber, instead, %s is %s" - % (self.gerber_bottom_name, type(gerber_bottom_obj))) - - #just read with original name - self.fc.exec_command_test('open_gerber %s/%s' - % (self.gerber_files, self.copper_top_filename)) diff --git a/tests/test_tclCommands/test_TclCommandPaintPolygon.py b/tests/test_tclCommands/test_TclCommandPaintPolygon.py deleted file mode 100644 index cc0c5616..00000000 --- a/tests/test_tclCommands/test_TclCommandPaintPolygon.py +++ /dev/null @@ -1,25 +0,0 @@ -from FlatCAMObj import FlatCAMGeometry - - -def test_paint_polygon(self): - """ - Test create paint polygon geometry - :param self: - :return: - """ - - self.fc.exec_command_test('new_geometry "%s"' % self.geometry_name) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name) - self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - % (self.geometry_name, type(geometry_obj))) - - points = '0 0 20 0 10 10 0 10' - - self.fc.exec_command_test('add_polygon "%s" %s' % (self.geometry_name, points)) - - # TODO rename to paint_polygon in future oop command implementation - self.fc.exec_command_test('paint_poly "%s" 5 5 2 0.5' % (self.geometry_name)) - geometry_obj = self.fc.collection.get_by_name(self.geometry_name+'_paint') - # TODO uncoment check after oop implementation, because of threading inside paint poly - #self.assertTrue(isinstance(geometry_obj, FlatCAMGeometry), "Expected FlatCAMGeometry, instead, %s is %s" - # % (self.geometry_name+'_paint', type(geometry_obj))) diff --git a/tests/test_tcl_shell.py b/tests/test_tcl_shell.py deleted file mode 100644 index 0933f66b..00000000 --- a/tests/test_tcl_shell.py +++ /dev/null @@ -1,272 +0,0 @@ -import sys -import unittest -from PyQt5 import QtWidgets, QtGui -from PyQt5.QtCore import QThread - -from FlatCAMApp import App -from os import listdir -from os.path import isfile -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob, FlatCAMExcellon -from flatcamGUI.ObjectUI import GerberObjectUI, GeometryObjectUI -from time import sleep -import os -import tempfile - - -class TclShellTest(unittest.TestCase): - - svg_files = 'tests/svg' - svg_filename = 'Arduino Nano3_pcb.svg' - gerber_files = 'tests/gerber_files' - copper_bottom_filename = 'detector_copper_bottom.gbr' - copper_top_filename = 'detector_copper_top.gbr' - cutout_filename = 'detector_contour.gbr' - excellon_filename = 'detector_drill.txt' - gerber_name = "gerber" - geometry_name = "geometry" - excellon_name = "excellon" - gerber_top_name = "top" - gerber_bottom_name = "bottom" - gerber_cutout_name = "cutout" - engraver_diameter = 0.3 - cutout_diameter = 3 - drill_diameter = 0.8 - - # load test methods to split huge test file into smaller pieces - # reason for this is reuse one test window only, - - # CANNOT DO THIS HERE!!! - # from tests.test_tclCommands import * - - @classmethod - def setUpClass(cls): - - cls.setup = True - cls.app = QtWidgets.QApplication(sys.argv) - - # Create App, keep app defaults (do not load - # user-defined defaults). - cls.fc = App(user_defaults=False) - cls.fc.ui.shell_dock.show() - - def setUp(self): - self.fc.exec_command_test('set_sys units MM') - self.fc.exec_command_test('new') - - @classmethod - def tearDownClass(cls): - - cls.fc.tcl = None - cls.app.closeAllWindows() - del cls.fc - del cls.app - pass - - def test_set_get_units(self): - """ - Tests setting and getting units via the ``set_sys`` command, - and persistance after ``new`` command. - - :return: None - """ - - # MM - self.fc.exec_command_test('set_sys units MM') - self.fc.exec_command_test('new') - - # IN - self.fc.exec_command_test('set_sys units IN') - self.fc.exec_command_test('new') - - # ---------------------------------------- - # Units must be IN - # ---------------------------------------- - units = self.fc.exec_command_test('get_sys units') - self.assertEqual(units, "IN") - - # MM - self.fc.exec_command_test('set_sys units MM') - self.fc.exec_command_test('new') - - # ---------------------------------------- - # Units must be MM - # ---------------------------------------- - units = self.fc.exec_command_test('get_sys units') - self.assertEqual(units, "MM") - - def test_gerber_flow(self): - """ - Typical workflow from Gerber to GCode. - - :return: None - """ - - gbr_cmd = 'open_gerber {path}/{filename} -outname {outname}' - - # ----------------------------------------- - # Open top layer and check for object type - # ----------------------------------------- - cmd = gbr_cmd.format( - path=self.gerber_files, - filename=self.copper_top_filename, - outname=self.gerber_top_name) - self.fc.exec_command_test(cmd) - gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name) - self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber), - "Expected FlatCAMGerber, instead, %s is %s" % - (self.gerber_top_name, type(gerber_top_obj))) - - # -------------------------------------------- - # Open bottom layer and check for object type - # -------------------------------------------- - cmd = gbr_cmd.format( - path=self.gerber_files, - filename=self.copper_bottom_filename, - outname=self.gerber_bottom_name) - self.fc.exec_command_test(cmd) - gerber_bottom_obj = self.fc.collection.get_by_name(self.gerber_bottom_name) - self.assertTrue(isinstance(gerber_bottom_obj, FlatCAMGerber), - "Expected FlatCAMGerber, instead, %s is %s" % - (self.gerber_bottom_name, type(gerber_bottom_obj))) - - # -------------------------------------------- - # Open cutout layer and check for object type - # -------------------------------------------- - cmd = gbr_cmd.format( - path=self.gerber_files, - filename=self.cutout_filename, - outname=self.gerber_cutout_name - ) - self.fc.exec_command_test(cmd) - gerber_cutout_obj = self.fc.collection.get_by_name(self.gerber_cutout_name) - self.assertTrue(isinstance(gerber_cutout_obj, FlatCAMGerber), - "Expected FlatCAMGerber, instead, %s is %s" % - (self.gerber_cutout_name, type(gerber_cutout_obj))) - - # exteriors delete and join geometries for top layer - cmd = 'isolate {objname} -dia {dia}'.format( - objname=self.gerber_cutout_name, - dia=self.engraver_diameter) - self.fc.exec_command_test(cmd) - - cmd = 'exteriors {objname} -outname {outname}'.format( - objname=self.gerber_cutout_name + '_iso', - outname=self.gerber_cutout_name + '_iso_exterior') - self.fc.exec_command_test(cmd) - - cmd = 'delete {objname}'.format( - objname=self.gerber_cutout_name + '_iso') - self.fc.exec_command_test(cmd) - - # TODO: Check deleteb object is gone. - - # -------------------------------------------- - # Exteriors of cutout layer, check type - # -------------------------------------------- - obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_iso_exterior') - self.assertTrue(isinstance(obj, FlatCAMGeometry), - "Expected FlatCAMGeometry, instead, %s is %s" % - (self.gerber_cutout_name + '_iso_exterior', type(obj))) - - # mirror bottom gerbers - self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.gerber_bottom_name, self.gerber_cutout_name)) - self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.gerber_cutout_name, self.gerber_cutout_name)) - - # exteriors delete and join geometries for bottom layer - self.fc.exec_command_test( - 'isolate %s -dia %f -outname %s' % - (self.gerber_cutout_name, self.engraver_diameter, self.gerber_cutout_name + '_bottom_iso') - ) - self.fc.exec_command_test( - 'exteriors %s -outname %s' % - (self.gerber_cutout_name + '_bottom_iso', self.gerber_cutout_name + '_bottom_iso_exterior') - ) - self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso')) - obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_bottom_iso_exterior') - self.assertTrue(isinstance(obj, FlatCAMGeometry), - "Expected FlatCAMGeometry, instead, %s is %s" % - (self.gerber_cutout_name + '_bottom_iso_exterior', type(obj))) - - # at this stage we should have 5 objects - names = self.fc.collection.get_names() - self.assertEqual(len(names), 5, - "Expected 5 objects, found %d" % len(names)) - - # isolate traces - self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_top_name, self.engraver_diameter)) - self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_bottom_name, self.engraver_diameter)) - - # join isolated geometries for top and bottom - self.fc.exec_command_test( - 'join_geometries %s %s %s' % - (self.gerber_top_name + '_join_iso', self.gerber_top_name + '_iso', - self.gerber_cutout_name + '_iso_exterior') - ) - self.fc.exec_command_test( - 'join_geometries %s %s %s' % - (self.gerber_bottom_name + '_join_iso', self.gerber_bottom_name + '_iso', - self.gerber_cutout_name + '_bottom_iso_exterior') - ) - - # at this stage we should have 9 objects - names = self.fc.collection.get_names() - self.assertEqual(len(names), 9, - "Expected 9 objects, found %d" % len(names)) - - # clean unused isolations - self.fc.exec_command_test('delete %s' % (self.gerber_bottom_name + '_iso')) - self.fc.exec_command_test('delete %s' % (self.gerber_top_name + '_iso')) - self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso_exterior')) - self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso_exterior')) - - # at this stage we should have 5 objects again - names = self.fc.collection.get_names() - self.assertEqual(len(names), 5, - "Expected 5 objects, found %d" % len(names)) - - # geocutout bottom test (it cuts to same object) - self.fc.exec_command_test( - 'isolate %s -dia %f -outname %s' % - (self.gerber_cutout_name, self.cutout_diameter, self.gerber_cutout_name + '_bottom_iso') - ) - self.fc.exec_command_test( - 'exteriors %s -outname %s' % - (self.gerber_cutout_name + '_bottom_iso', self.gerber_cutout_name + '_bottom_iso_exterior') - ) - self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso')) - obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_bottom_iso_exterior') - self.assertTrue(isinstance(obj, FlatCAMGeometry), - "Expected FlatCAMGeometry, instead, %s is %s" % - (self.gerber_cutout_name + '_bottom_iso_exterior', type(obj))) - self.fc.exec_command_test('geocutout %s -dia %f -gapsize 0.3 -gaps 4' % - (self.gerber_cutout_name + '_bottom_iso_exterior', self.cutout_diameter)) - - # at this stage we should have 6 objects - names = self.fc.collection.get_names() - self.assertEqual(len(names), 6, - "Expected 6 objects, found %d" % len(names)) - - # TODO: tests for tcl - - def test_open_gerber(self): - - self.fc.exec_command_test('open_gerber %s/%s -outname %s' % - (self.gerber_files, self.copper_top_filename, self.gerber_top_name)) - gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name) - self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber), - "Expected FlatCAMGerber, instead, %s is %s" % - (self.gerber_top_name, type(gerber_top_obj))) - - def test_excellon_flow(self): - - self.fc.exec_command_test('open_excellon %s/%s -outname %s' % - (self.gerber_files, self.excellon_filename, self.excellon_name)) - excellon_obj = self.fc.collection.get_by_name(self.excellon_name) - self.assertTrue(isinstance(excellon_obj, FlatCAMExcellon), - "Expected FlatCAMExcellon, instead, %s is %s" % - (self.excellon_name, type(excellon_obj))) - - # mirror bottom excellon - self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.excellon_name, self.gerber_cutout_name)) - - # TODO: tests for tcl diff --git a/tests/test_voronoi.py b/tests/test_voronoi.py deleted file mode 100644 index 71a62942..00000000 --- a/tests/test_voronoi.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Test cases for Voronoi Diagram creation. -Overall, I'm trying less to test the correctness of the result -and more to cover input cases and behavior, making sure -that we return a sane result without error or raise a useful one. -""" - -import pytest - -from shapely.geos import geos_version -from shapely.wkt import loads as load_wkt - -from shapely.ops import voronoi_diagram - -requires_geos_35 = pytest.mark.skipif(geos_version < (3, 5, 0), reason='GEOS >= 3.5.0 is required.') - -@requires_geos_35 -def test_from_multipoint_without_tolerace_with_floating_point_coordinates(): - """But it's fine without it.""" - mp = load_wkt('MULTIPOINT (20.1273 18.7303, 26.5107 18.7303, 20.1273 23.8437, 26.5107 23.8437)') - - regions = voronoi_diagram(mp) - print("Len: %d -> Regions: %s" % (len(regions), str(regions))) - -print(geos_version) -test_from_multipoint_without_tolerace_with_floating_point_coordinates() diff --git a/tests/titlebar_custom.py b/tests/titlebar_custom.py deleted file mode 100644 index f136d0a6..00000000 --- a/tests/titlebar_custom.py +++ /dev/null @@ -1,197 +0,0 @@ -# ######################################################## -# # customize Title bar -# # dotpy.ir -# # iraj.jelo@gmail.com -# ######################################################## -import sys -from PyQt6 import QtWidgets, QtGui -from PyQt6 import QtCore -from PyQt6.QtCore import Qt - -from datetime import datetime -import traceback - - -class TitleBar(QtWidgets.QDialog): - def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - self.setWindowFlags(Qt.WindowType.FramelessWindowHint) - css = """ - QWidget{ - Background: #0000FF; - color:white; - font:12px bold; - font-weight:bold; - border-radius: 1px; - height: 11px; - } - QDialog{ - Background-image:url('img/titlebar bg.png'); - font-size:12px; - color: black; - - } - QToolButton{ - Background:#AA00AA; - font-size:11px; - } - QToolButton:hover{ - Background: #FF00FF; - font-size:11px; - } - """ - self.setAutoFillBackground(True) - self.setBackgroundRole(QtGui.QPalette.ColorRole.Highlight) - self.setStyleSheet(css) - self.minimize = QtWidgets.QToolButton(self) - self.minimize.setIcon(QtGui.QIcon('img/min.png')) - self.maximize = QtWidgets.QToolButton(self) - self.maximize.setIcon(QtGui.QIcon('img/max.png')) - close = QtWidgets.QToolButton(self) - close.setIcon(QtGui.QIcon('img/close.png')) - self.minimize.setMinimumHeight(10) - close.setMinimumHeight(10) - self.maximize.setMinimumHeight(10) - label = QtWidgets.QLabel(self) - label.setText("Window Title") - self.setWindowTitle("Window Title") - hbox = QtWidgets.QHBoxLayout(self) - hbox.addWidget(label) - hbox.addWidget(self.minimize) - hbox.addWidget(self.maximize) - hbox.addWidget(close) - hbox.insertStretch(1, 500) - hbox.setSpacing(0) - self.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Fixed) - self.maxNormal = False - close.clicked.connect(self.close) - self.minimize.clicked.connect(self.showSmall) - self.maximize.clicked.connect(self.showMaxRestore) - - @staticmethod - def showSmall(): - box.showMinimized() - - def showMaxRestore(self): - if self.maxNormal: - box.showNormal() - self.maxNormal = False - self.maximize.setIcon(QtGui.QIcon('img/max.png')) - else: - box.showMaximized() - self.maxNormal = True - self.maximize.setIcon(QtGui.QIcon('img/max2.png')) - - def close(self): - box.close() - - def mousePressEvent(self, event): - if event.button() == Qt.MouseButton.LeftButton: - box.moving = True - box.offset = event.position() - if event.type() == QtCore.QEvent.Type.MouseButtonDblClick: - self.showMaxRestore() - - def mouseMoveEvent(self, event): - if box.isMaximized(): - self.showMaxRestore() - box.move(event.globalPosition().toPoint() - box.offset) - else: - if box.moving: - box.move(event.globalPosition().toPoint() - box.offset) - - -class Frame(QtWidgets.QFrame): - def __init__(self, parent=None): - QtWidgets.QFrame.__init__(self, parent) - - self.m_old_pos = None - self.m_mouse_down = False - self.setFrameShape(QtWidgets.QFrame.Shape.StyledPanel) - css = """ - QFrame{ - Background: #FFFFF0; - color:white; - font:13px ; - font-weight:bold; - } - """ - self.setStyleSheet(css) - self.setWindowFlags(Qt.WindowType.FramelessWindowHint) - self.setMouseTracking(True) - self.m_titleBar = TitleBar(self) - self.m_content = QtWidgets.QWidget(self) - vbox = QtWidgets.QVBoxLayout(self) - vbox.addWidget(self.m_titleBar) - vbox.setContentsMargins(0, 0, 0, 0) - vbox.setSpacing(0) - layout = QtWidgets.QVBoxLayout() - layout.addWidget(self.m_content) - layout.setContentsMargins(5, 5, 5, 5) - layout.setSpacing(0) - vbox.addLayout(layout) - # Allows you to access the content area of the frame - # where widgets and layouts can be added - - def contentWidget(self): - return self.m_content - - def titleBar(self): - return self.m_titleBar - - def mousePressEvent(self, event): - self.m_old_pos = event.pos() - self.m_mouse_down = event.button() == Qt.MouseButton.LeftButton - - def mouseMoveEvent(self, event): - event.position().x() - event.position().y() - - def mouseReleaseEvent(self, event): - self.m_mouse_down = False - - -if __name__ == '__main__': - def excepthook(exc_type, exc_value, exc_tb): - msg = '%s\n' % str(datetime.today()) - if exc_type != KeyboardInterrupt: - msg += "".join(traceback.format_exception(exc_type, exc_value, exc_tb)) - - # show the message - try: - msgbox = QtWidgets.QMessageBox() - displayed_msg = "The application encountered a critical error and it will close.\n" \ - "Please report this error to the developers." - - msgbox.setText(displayed_msg) - msgbox.setDetailedText(msg) - msgbox.setWindowTitle("Critical Error") - # msgbox.setWindowIcon() - msgbox.setIcon(QtWidgets.QMessageBox.Icon.Critical) - - bt_yes = msgbox.addButton("Quit", QtWidgets.QMessageBox.ButtonRole.YesRole) - - msgbox.setDefaultButton(bt_yes) - # msgbox.setTextFormat(Qt.TextFormat.RichText) - msgbox.exec() - except Exception: - pass - QtWidgets.QApplication.quit() - # or QtWidgets.QApplication.exit(0) - - - sys.excepthook = excepthook - - app = QtWidgets.QApplication(sys.argv) - box = Frame() - box.move(60, 60) - - le = QtWidgets.QVBoxLayout(box.contentWidget()) - - le.setContentsMargins(0, 0, 0, 0) - edit = QtWidgets.QLabel("""I would've did anything for you to show you how much I adored you -But it's over now, it's too late to save our loveJust promise me you'll think of me -Every time you look up in the sky and see a star 'cuz I'm your star.""") - le.addWidget(edit) - box.show() - app.exec() diff --git a/tests/toolpath_optimization_profiling/toollift_minimization_line_profile1.py b/tests/toolpath_optimization_profiling/toollift_minimization_line_profile1.py deleted file mode 100644 index c66245bc..00000000 --- a/tests/toolpath_optimization_profiling/toollift_minimization_line_profile1.py +++ /dev/null @@ -1,8 +0,0 @@ -# Run kernprof -l -v gerber_parsing_line_profile_1.py -import sys -sys.path.append('../../') -from camlib import * -from shapely.geometry import Polygon - -poly = Polygon([(0.0, 0.0), (1.0, 0.0), (1.0, 0.5), (0.0, 0.5)]) -result = Geometry.clear_polygon2(poly, 0.01) diff --git a/tests/toolpath_optimization_profiling/toollift_minimization_profile1.py b/tests/toolpath_optimization_profiling/toollift_minimization_profile1.py deleted file mode 100644 index 038e09b3..00000000 --- a/tests/toolpath_optimization_profiling/toollift_minimization_profile1.py +++ /dev/null @@ -1,11 +0,0 @@ -import cProfile -import pstats -from camlib import * -from shapely.geometry import Polygon - -poly = Polygon([(0.0, 0.0), (1.0, 0.0), (1.0, 0.5), (0.0, 0.5)]) - -cProfile.run('result = Geometry.clear_polygon2(poly, 0.01)', - 'toollist_minimization_profile', sort='cumtime') -p = pstats.Stats('toollist_minimization_profile') -p.sort_stats('cumulative').print_stats(.1) \ No newline at end of file From 1dff9cc56698c55de0c53762d1bcfe7b8f5d7f26 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Mon, 13 Mar 2023 23:08:57 +0200 Subject: [PATCH 37/55] - in Tcl Commands: export_dxf and export_gerber, fixed a mistake in declaring a wrong name of a required option - in Tcl Command set_path added an optional parameter which allows to create a directory where path is to be set, if the directory does not exist --- CHANGELOG.md | 5 +++++ tclCommands/TclCommand.py | 2 +- tclCommands/TclCommandExportDXF.py | 4 ++-- tclCommands/TclCommandExportGerber.py | 2 +- tclCommands/TclCommandExportSVG.py | 2 +- tclCommands/TclCommandSetPath.py | 18 +++++++++++++++++- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7050396f..d2823691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +13.03.2023 + +- in Tcl Commands: export_dxf and export_gerber, fixed a mistake in declaring a wrong name of a required option +- in Tcl Command set_path added an optional parameter which allows to create a directory where path is to be set, if the directory does not exist + 6.03.2023 - fixed some possible issues due of changes in version 2.0 of Shapely diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py index e016d783..84424f37 100644 --- a/tclCommands/TclCommand.py +++ b/tclCommands/TclCommand.py @@ -1,6 +1,6 @@ import sys import re -import appMain + import abc import collections from PyQt6 import QtCore diff --git a/tclCommands/TclCommandExportDXF.py b/tclCommands/TclCommandExportDXF.py index 4e20a7d7..0282bc9a 100644 --- a/tclCommands/TclCommandExportDXF.py +++ b/tclCommands/TclCommandExportDXF.py @@ -19,7 +19,7 @@ class TclCommandExportDXF(TclCommand): export_dxf path/my_geometry filename """ - # List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) + # List of all command aliases, to be able to use old names for backward compatibility (add_poly, add_polygon) aliases = ['export_dxf', 'edxf'] description = '%s %s' % ("--", "Export a Geometry object as a DXF File.") @@ -35,7 +35,7 @@ class TclCommandExportDXF(TclCommand): ]) # array of mandatory options for current Tcl command: required = ['name','outname'] - required = ['obj_name'] + required = ['name'] # structured help for current command, args needs to be ordered help = { diff --git a/tclCommands/TclCommandExportGerber.py b/tclCommands/TclCommandExportGerber.py index de219ebe..b2e04d24 100644 --- a/tclCommands/TclCommandExportGerber.py +++ b/tclCommands/TclCommandExportGerber.py @@ -27,7 +27,7 @@ class TclCommandExportGerber(TclCommand): ]) # array of mandatory options for current Tcl command: required = ['name','outname'] - required = ['obj_name'] + required = ['name'] # structured help for current command, args needs to be ordered help = { diff --git a/tclCommands/TclCommandExportSVG.py b/tclCommands/TclCommandExportSVG.py index 3c2d9ef4..c6f30d96 100644 --- a/tclCommands/TclCommandExportSVG.py +++ b/tclCommands/TclCommandExportSVG.py @@ -11,7 +11,7 @@ class TclCommandExportSVG(TclCommand): export_svg my_geometry filename """ - # List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) + # List of all command aliases, to be able to use old names for backward compatibility (add_poly, add_polygon) aliases = ['export_svg'] description = '%s %s' % ("--", "Export a Geometry object as a SVG File.") diff --git a/tclCommands/TclCommandSetPath.py b/tclCommands/TclCommandSetPath.py index c30f2d84..ca124d7b 100644 --- a/tclCommands/TclCommandSetPath.py +++ b/tclCommands/TclCommandSetPath.py @@ -29,7 +29,7 @@ class TclCommandSetPath(TclCommand): """ - # List of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon) + # List of all command aliases, to be able to use old names for backward compatibility (add_poly, add_polygon) aliases = ['set_path'] description = '%s %s' % ("--", "Set the folder path to the specified path.") @@ -41,6 +41,7 @@ class TclCommandSetPath(TclCommand): # Dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value option_types = collections.OrderedDict([ + ('create', str), ]) # array of mandatory options for current Tcl command: required = {'name','outname'} @@ -53,6 +54,7 @@ class TclCommandSetPath(TclCommand): 'args': collections.OrderedDict([ ('path', 'A folder path to where the user is supposed to have the file that he will work with.\n' 'WARNING: No spaces allowed. Use quotes around the path if it contains spaces.'), + ('create', 'If set to True, if the folder does not exist it will create it.'), ]), 'examples': ['set_path D:\\Project_storage_path'] } @@ -81,6 +83,20 @@ class TclCommandSetPath(TclCommand): "is a path to file and not a directory as expected.") self.app.inform_shell.emit(msg) return "Failed. The Tcl command set_path was used but it was not a directory." + + if 'create' in args: + option = args['create'].capitalize() + try: + eval_option = eval(option) + except NameError: + eval_option = False + + if eval_option: + self.app.inform_shell.emit("Path not found, let's create it.") + os.mkdir(path) + else: + return "Failed. The Tcl command set_path has options but could not be recognized: -create %s." % \ + str(args['create']) else: msg = '[ERROR] %s: %s, %s' % ( "The provided path", str(path), "do not exist. Check for typos.") From 72dd81848f970d1ff7290c40781b6a2188734ea0 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 22 Mar 2023 16:43:27 +0200 Subject: [PATCH 38/55] - in the toolbars now there is text under the icons (which can be turned off from the toolbar context menu) - the GUI state is now saved and restored through QSettings --- CHANGELOG.md | 5 + appGUI/MainGUI.py | 303 +++++++++------------ appGUI/preferences/PreferencesUIManager.py | 41 --- appHandlers/AppIO.py | 7 - appMain.py | 9 +- defaults.py | 21 +- 6 files changed, 141 insertions(+), 245 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2823691..79309118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +22.03.2023 + +- in the toolbars now there is text under the icons (which can be turned off from the toolbar context menu) +- the GUI state is now saved and restored through QSettings + 13.03.2023 - in Tcl Commands: export_dxf and export_gerber, fixed a mistake in declaring a wrong name of a required option diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 29888602..7cbb171c 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -49,8 +49,6 @@ if '_' not in builtins.__dict__: class MainGUI(QtWidgets.QMainWindow): - # Emitted when persistent window geometry needs to be retained - geom_update = QtCore.pyqtSignal(int, int, int, int, int, name='geomUpdate') final_save = QtCore.pyqtSignal(name='saveBeforeExit') # screenChanged = QtCore.pyqtSignal(QtGui.QScreen, QtGui.QScreen) @@ -1064,14 +1062,18 @@ class MainGUI(QtWidgets.QMainWindow): # ########################## File Toolbar# ############################### # ######################################################################## self.file_open_gerber_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/open_gerber32.png'), _("Open Gerber")) + QtGui.QIcon(self.app.resource_location + '/open_gerber32.png'), _("Gerber")) + self.file_open_gerber_btn.setToolTip(_("Open a Gerber file.")) self.file_open_excellon_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Open Excellon")) + QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Excellon")) + self.file_open_excellon_btn.setToolTip(_("Open a Excellon file.")) self.toolbarfile.addSeparator() self.file_open_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/folder32.png'), _("Open Project")) + QtGui.QIcon(self.app.resource_location + '/folder32.png'), _("Open")) + self.file_open_btn.setToolTip(_("Open a project.")) self.file_save_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/save_as.png'), _("Save project")) + QtGui.QIcon(self.app.resource_location + '/save_as.png'), _("Save")) + self.file_save_btn.setToolTip(_("Save the current project.")) # ######################################################################## # ########################## Edit Toolbar# ############################### @@ -1096,24 +1098,33 @@ class MainGUI(QtWidgets.QMainWindow): self.toolbaredit.addSeparator() self.copy_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/copy_file32.png'), _("Copy")) + self.copy_btn.setToolTip(_("Copy a selection of objects.")) self.delete_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("Delete")) + self.delete_btn.setToolTip(_("Delete a selection of objects.")) self.toolbaredit.addSeparator() self.distance_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/distance32.png'), _("Distance Tool")) - self.distance_min_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/distance_min32.png'), _("Distance Min Tool")) + QtGui.QIcon(self.app.resource_location + '/distance32.png'), _("Distance")) + self.distance_btn.setToolTip(_("Measure a distance between two or more points.")) + # self.distance_min_btn = self.toolbaredit.addAction( + # QtGui.QIcon(self.app.resource_location + '/distance_min32.png'), _("Min Distance")) + # self.distance_min_btn.setToolTip(_("Measure the minimum distance between two objects.")) self.origin_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin')) - self.move2origin_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/move2origin32.png'), _('Move to Origin')) - self.center_in_origin_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/custom_origin32.png'), _('Custom Origin')) + self.origin_btn.setToolTip(_("Set an origin to a custom location.")) + # self.move2origin_btn = self.toolbaredit.addAction( + # QtGui.QIcon(self.app.resource_location + '/move2origin32.png'), _('To Orig.')) + # self.move2origin_btn.setToolTip(_("Move selected objects to the origin.")) + # self.center_in_origin_btn = self.toolbaredit.addAction( + # QtGui.QIcon(self.app.resource_location + '/custom_origin32.png'), _('C Origin')) + # self.center_in_origin_btn.setToolTip(_("Move the selected objects to custom positions.")) self.jmp_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/jump_to32.png'), _('Jump to Location')) + QtGui.QIcon(self.app.resource_location + '/jump_to32.png'), _('Jump to')) + self.jmp_btn.setToolTip(_("Move the mouse cursor to a defined position and center into view.")) self.locate_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/locate32.png'), _('Locate in Object')) + QtGui.QIcon(self.app.resource_location + '/locate32.png'), _('Locate')) + self.locate_btn.setToolTip(_("Locate a predefined position on a selected object.")) # ######################################################################## # ########################## View Toolbar# ############################### @@ -1180,20 +1191,20 @@ class MainGUI(QtWidgets.QMainWindow): self.toolbarplugins.addSeparator() self.align_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/align32.png'), _("Align Objects")) + QtGui.QIcon(self.app.resource_location + '/align32.png'), _("Align")) # self.sub_btn = self.toolbarplugins.addAction( # QtGui.QIcon(self.app.resource_location + '/sub32.png'), _("Subtract Tool")) self.toolbarplugins.addSeparator() - self.extract_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/extract32.png'), _("Extract")) + # self.extract_btn = self.toolbarplugins.addAction( + # QtGui.QIcon(self.app.resource_location + '/extract32.png'), _("Extract")) self.copperfill_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/copperfill32.png'), _("Copper Thieving")) + QtGui.QIcon(self.app.resource_location + '/copperfill32.png'), _("Thieving")) self.markers_tool_btn = self.toolbarplugins.addAction( QtGui.QIcon(self.app.resource_location + '/corners_32.png'), _("Markers")) self.punch_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber")) + QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch")) self.calculators_btn = self.toolbarplugins.addAction( QtGui.QIcon(self.app.resource_location + '/calculator32.png'), _("Calculators")) @@ -1997,7 +2008,6 @@ class MainGUI(QtWidgets.QMainWindow): self.app_icon.addFile(self.app.resource_location + '/app256.png', QtCore.QSize(256, 256)) self.setWindowIcon(self.app_icon) - self.setGeometry(100, 100, 1024, 650) self.setWindowTitle('FlatCAM Evo %s %s - %s' % (self.app.version, ('BETA' if self.app.beta else ''), @@ -2022,6 +2032,20 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_editor_cmenu.menuAction().setVisible(False) self.e_editor_cmenu.menuAction().setVisible(False) + # ######################################################################## + # construct the Toolbar Lock menu entry to the context menu of the QMainWindow + # ######################################################################## + self.lock_action = QtGui.QAction() + self.lock_action.setText(_("Lock Toolbars")) + self.lock_action.setCheckable(True) + + # ######################################################################## + # construct the Show Text menu entry to the context menu of the QMainWindow + # ######################################################################## + self.show_text_action = QtGui.QAction() + self.show_text_action.setText(_("Show Text")) + self.show_text_action.setCheckable(True) + # ######################################################################## # ######################## BUILD PREFERENCES ############################# # ######################################################################## @@ -2039,19 +2063,18 @@ class MainGUI(QtWidgets.QMainWindow): QtCore.QCoreApplication.instance().installEventFilter(self) # ######################################################################## - # ################## RESTORE THE TOOLBAR STATE from file ################# + # ################## RESTORE UI from QSettings ################# # ######################################################################## - flat_settings = QSettings("Open Source", "FlatCAM") - if flat_settings.contains("saved_gui_state"): - saved_gui_state = flat_settings.value('saved_gui_state') - self.restoreState(saved_gui_state) - self.app.log.debug("MainGUI.__init__() --> UI state restored from QSettings.") + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("saved_gui_state"): + self.restoreState(qsettings.value('saved_gui_state'), 0) + tb_lock_state = qsettings.value('toolbar_lock', "true") + show_text_state = qsettings.value('menu_show_text', "true") + win_geo = qsettings.value('window_geometry', (100, 100, 800, 400)) + splitter_left = qsettings.value('splitter_left', 1) - self.corner_snap_btn.setVisible(False) - self.snap_magnet.setVisible(False) - - if flat_settings.contains("layout"): - layout = flat_settings.value('layout', type=str) + if qsettings.contains("layout"): + layout = qsettings.value('layout', type=str) self.exc_edit_toolbar.setDisabled(True) self.geo_edit_toolbar.setDisabled(True) self.grb_edit_toolbar.setDisabled(True) @@ -2062,36 +2085,27 @@ class MainGUI(QtWidgets.QMainWindow): self.geo_edit_toolbar.setDisabled(True) self.grb_edit_toolbar.setDisabled(True) - flat_settings.setValue('layout', "standard") - # This will write the setting to the platform specific storage. - del flat_settings - self.app.log.debug("MainGUI.__init__() --> UI layout restored from options. QSettings set to 'standard'") - - # construct the Toolbar Lock menu entry to the context menu of the QMainWindow - self.lock_action = QtGui.QAction() - self.lock_action.setText(_("Lock Toolbars")) - self.lock_action.setCheckable(True) - - qsettings = QSettings("Open Source", "FlatCAM") - if qsettings.contains("toolbar_lock"): - lock_val = qsettings.value('toolbar_lock') - if lock_val == 'true': - lock_state = True - self.lock_action.setChecked(True) - else: - - lock_state = False - self.lock_action.setChecked(False) - else: - lock_state = True - qsettings.setValue('toolbar_lock', lock_state) - + qsettings.setValue('layout', "standard") # This will write the setting to the platform specific storage. del qsettings + self.app.log.debug("MainGUI.__init__() --> UI layout restored from options. QSettings set to 'standard'") - self.lock_toolbar(lock=lock_state) + self.lock_action.setChecked(True if tb_lock_state == 'true' else False) + self.show_text_action.setChecked(True if show_text_state == 'true' else False) + + self.setGeometry(win_geo[0], win_geo[1], win_geo[2], win_geo[3]) + self.app.log.debug("MainGUI.__init__() --> UI state restored from QSettings.") + + self.splitter.setSizes([splitter_left, 0]) + + self.lock_toolbar(lock=True if tb_lock_state == 'true' else False) + self.show_text_under_action(show_text=True if show_text_state == 'true' else False) + + self.corner_snap_btn.setVisible(False) + self.snap_magnet.setVisible(False) self.lock_action.triggered[bool].connect(self.lock_toolbar) + self.show_text_action.triggered.connect(self.show_text_under_action) self.pref_open_button.clicked.connect(self.on_preferences_open_folder) self.clear_btn.clicked.connect(lambda: self.on_gui_clear()) @@ -2105,12 +2119,6 @@ class MainGUI(QtWidgets.QMainWindow): # self.plot_tab_area.tab_attached.connect(lambda x: print(x)) # self.plot_tab_area.tab_detached.connect(lambda x: print(x)) - # restore the toolbar view - self.restore_toolbar_view() - - # restore the GUI geometry - self.restore_main_win_geom() - # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # %%%%%%%%%%%%%%%%% GUI Building FINISHED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -2123,7 +2131,6 @@ class MainGUI(QtWidgets.QMainWindow): self.height = None self.titlebar_height = None - self.geom_update[int, int, int, int, int].connect(self.save_geometry) self.final_save.connect(self.app.final_save) # Notebook and Plot Tab Area signals @@ -2239,92 +2246,6 @@ class MainGUI(QtWidgets.QMainWindow): else: self.hide() - def save_geometry(self, x, y, width, height, notebook_width): - """ - Will save the application geometry and positions in the options dicitionary to be restored at the next - launch of the application. - - :param x: X position of the main window - :param y: Y position of the main window - :param width: width of the main window - :param height: height of the main window - :param notebook_width: the notebook width is adjustable so it get saved here, too. - - :return: None - """ - self.app.options["global_def_win_x"] = x - self.app.options["global_def_win_y"] = y - self.app.options["global_def_win_w"] = width - self.app.options["global_def_win_h"] = height - self.app.options["global_def_notebook_width"] = notebook_width - # self.app.preferencesUiManager.save_defaults() - - def restore_main_win_geom(self): - try: - self.setGeometry(self.app.options["global_def_win_x"], - self.app.options["global_def_win_y"], - self.app.options["global_def_win_w"], - self.app.options["global_def_win_h"]) - self.splitter.setSizes([self.app.options["global_def_notebook_width"], 0]) - except KeyError as e: - self.app.log.debug("appGUI.MainGUI.restore_main_win_geom() --> %s" % str(e)) - - def restore_toolbar_view(self): - """ - Some toolbars may be hidden by user and here we restore the state of the toolbars visibility that - was saved in the 'options' dictionary. - - :return: None - """ - tb = self.app.options["global_toolbar_view"] - - if tb & 1: - self.toolbarfile.setVisible(True) - else: - self.toolbarfile.setVisible(False) - - if tb & 2: - self.toolbaredit.setVisible(True) - else: - self.toolbaredit.setVisible(False) - - if tb & 4: - self.toolbarview.setVisible(True) - else: - self.toolbarview.setVisible(False) - - if tb & 8: - self.toolbarplugins.setVisible(True) - else: - self.toolbarplugins.setVisible(False) - - if tb & 16: - self.exc_edit_toolbar.setVisible(True) - else: - self.exc_edit_toolbar.setVisible(False) - - if tb & 32: - self.geo_edit_toolbar.setVisible(True) - else: - self.geo_edit_toolbar.setVisible(False) - - if tb & 64: - self.grb_edit_toolbar.setVisible(True) - else: - self.grb_edit_toolbar.setVisible(False) - - # if tb & 128: - # self.ui.grid_toolbar.setVisible(True) - # else: - # self.ui.grid_toolbar.setVisible(False) - - # Grid Toolbar is controlled by its own setting - - if tb & 256: - self.toolbarshell.setVisible(True) - else: - self.toolbarshell.setVisible(False) - def on_tab_setup_context_menu(self): initial_checked = self.app.defaults["global_tabs_detachable"] action_name = str(_("Detachable Tabs")) @@ -2462,14 +2383,18 @@ class MainGUI(QtWidgets.QMainWindow): # ##################### File Toolbar ##################################### # ######################################################################## self.file_open_gerber_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/open_gerber32.png'), _("Open Gerber")) + QtGui.QIcon(self.app.resource_location + '/open_gerber32.png'), _("Gerber")) + self.file_open_gerber_btn.setToolTip(_("Open a Gerber file.")) self.file_open_excellon_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Open Excellon")) + QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Excellon")) + self.file_open_excellon_btn.setToolTip(_("Open a Excellon file.")) self.toolbarfile.addSeparator() self.file_open_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/folder32.png'), _("Open Project")) + QtGui.QIcon(self.app.resource_location + '/folder32.png'), _("Open")) + self.file_open_btn.setToolTip(_("Open a project.")) self.file_save_btn = self.toolbarfile.addAction( - QtGui.QIcon(self.app.resource_location + '/save_as.png'), _("Save Project")) + QtGui.QIcon(self.app.resource_location + '/save_as.png'), _("Save")) + self.file_save_btn.setToolTip(_("Save the current project.")) # ######################################################################## # ######################### Edit Toolbar ################################# @@ -2491,27 +2416,35 @@ class MainGUI(QtWidgets.QMainWindow): # in order to hide it we hide the returned action self.editor_exit_btn_ret_action = self.toolbaredit.addWidget(self.editor_exit_btn) - self.toolbaredit.addSeparator() self.copy_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/copy_file32.png'), _("Copy")) + self.copy_btn.setToolTip(_("Copy a selection of objects.")) self.delete_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("Delete")) + self.delete_btn.setToolTip(_("Delete a selection of objects.")) self.toolbaredit.addSeparator() self.distance_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/distance32.png'), _("Distance Tool")) - self.distance_min_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/distance_min32.png'), _("Distance Min Tool")) + QtGui.QIcon(self.app.resource_location + '/distance32.png'), _("Distance")) + self.distance_btn.setToolTip(_("Measure a distance between two or more points.")) + # self.distance_min_btn = self.toolbaredit.addAction( + # QtGui.QIcon(self.app.resource_location + '/distance_min32.png'), _("Min Distance")) + # self.distance_min_btn.setToolTip(_("Measure the minimum distance between two objects.")) self.origin_btn = self.toolbaredit.addAction( QtGui.QIcon(self.app.resource_location + '/origin32.png'), _('Set Origin')) - self.move2origin_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/move2origin32.png'), _('Move to Origin')) - self.center_in_origin_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/custom_origin32.png'), _('Custom Origin')) + self.origin_btn.setToolTip(_("Set an origin to a custom location.")) + # self.move2origin_btn = self.toolbaredit.addAction( + # QtGui.QIcon(self.app.resource_location + '/move2origin32.png'), _('To Orig.')) + # self.move2origin_btn.setToolTip(_("Move selected objects to the origin.")) + # self.center_in_origin_btn = self.toolbaredit.addAction( + # QtGui.QIcon(self.app.resource_location + '/custom_origin32.png'), _('C Origin')) + # self.center_in_origin_btn.setToolTip(_("Move the selected objects to custom positions.")) self.jmp_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/jump_to32.png'), _('Jump to Location')) + QtGui.QIcon(self.app.resource_location + '/jump_to32.png'), _('Jump to')) + self.jmp_btn.setToolTip(_("Move the mouse cursor to a defined position and center into view.")) self.locate_btn = self.toolbaredit.addAction( - QtGui.QIcon(self.app.resource_location + '/locate32.png'), _('Locate in Object')) + QtGui.QIcon(self.app.resource_location + '/locate32.png'), _('Locate')) + self.locate_btn.setToolTip(_("Locate a predefined position on a selected object.")) # ######################################################################## # ########################## View Toolbar# ############################### @@ -2576,16 +2509,16 @@ class MainGUI(QtWidgets.QMainWindow): self.toolbarplugins.addSeparator() self.align_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/align32.png'), _("Align Objects")) + QtGui.QIcon(self.app.resource_location + '/align32.png'), _("Align")) # self.sub_btn = self.toolbarplugins.addAction( # QtGui.QIcon(self.app.resource_location + '/sub32.png'), _("Subtract")) self.toolbarplugins.addSeparator() - self.extract_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/extract32.png'), _("Extract")) + # self.extract_btn = self.toolbarplugins.addAction( + # QtGui.QIcon(self.app.resource_location + '/extract32.png'), _("Extract")) self.copperfill_btn = self.toolbarplugins.addAction( - QtGui.QIcon(self.app.resource_location + '/copperfill32.png'), _("Copper Thieving")) + QtGui.QIcon(self.app.resource_location + '/copperfill32.png'), _("Thieving")) self.markers_tool_btn = self.toolbarplugins.addAction( QtGui.QIcon(self.app.resource_location + '/corners_32.png'), _("Markers")) self.punch_btn = self.toolbarplugins.addAction( @@ -2798,6 +2731,7 @@ class MainGUI(QtWidgets.QMainWindow): menu.addSeparator() menu.addAction(self.lock_action) + menu.addAction(self.show_text_action) return menu def lock_toolbar(self, lock=False): @@ -2817,6 +2751,26 @@ class MainGUI(QtWidgets.QMainWindow): if isinstance(widget, QtWidgets.QToolBar): widget.setMovable(True) + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('toolbar_lock', lock) + # This will write the setting to the platform specific storage. + del qsettings + + def show_text_under_action(self, show_text=True): + if show_text: + for widget in self.children(): + if isinstance(widget, QtWidgets.QToolBar): + widget.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon) + else: + for widget in self.children(): + if isinstance(widget, QtWidgets.QToolBar): + widget.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) + + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('menu_show_text', show_text) + # This will write the setting to the platform specific storage. + del qsettings + def on_fullscreen(self, disable=False): """ @@ -4570,12 +4524,17 @@ class MainGUI(QtWidgets.QMainWindow): if self.app.save_in_progress: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Application is saving the project. Please wait ...")) else: - grect = self.geometry() + g_rect = self.geometry() - # self.splitter.sizes()[0] is actually the size of the "notebook" + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('saved_gui_state', self.saveState(0)) + qsettings.setValue('toolbar_lock', self.lock_action.isChecked()) + qsettings.setValue('menu_show_text', self.show_text_action.isChecked()) if not self.isMaximized(): - self.geom_update.emit(grect.x(), grect.y(), grect.width(), grect.height(), self.splitter.sizes()[0]) - + qsettings.setValue('window_geometry', (g_rect.x(), g_rect.y(), g_rect.width(), g_rect.height())) + qsettings.setValue('splitter_left', self.splitter.sizes()[0]) + # This will write the setting to the platform specific storage. + del qsettings try: self.final_save.emit() except SystemError: diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index c2d67100..9eed28de 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -1267,9 +1267,6 @@ class PreferencesUIManager(QtCore.QObject): self.defaults.propagate_defaults() - if first_time is False: - self.save_toolbar_view() - # Save the options to disk filename = os.path.join(data_path, "current_defaults_%s.FlatConfig" % self.defaults.version) @@ -1286,44 +1283,6 @@ class PreferencesUIManager(QtCore.QObject): # update the autosave timer self.ui.app.save_project_auto_update() - def save_toolbar_view(self): - """ - Will save the toolbar view state to the defaults - - :return: None - """ - - # Save the toolbar view - tb_status = 0 - if self.ui.toolbarfile.isVisible(): - tb_status += 1 - - if self.ui.toolbaredit.isVisible(): - tb_status += 2 - - if self.ui.toolbarview.isVisible(): - tb_status += 4 - - if self.ui.toolbarplugins.isVisible(): - tb_status += 8 - - if self.ui.exc_edit_toolbar.isVisible(): - tb_status += 16 - - if self.ui.geo_edit_toolbar.isVisible(): - tb_status += 32 - - if self.ui.grb_edit_toolbar.isVisible(): - tb_status += 64 - - if self.ui.status_toolbar.isVisible(): - tb_status += 128 - - if self.ui.toolbarshell.isVisible(): - tb_status += 256 - - self.defaults["global_toolbar_view"] = tb_status - def on_preferences_edited(self): """ Executed when a preference was changed in the Edit -> Preferences tab. diff --git a/appHandlers/AppIO.py b/appHandlers/AppIO.py index c786b227..e547ed3f 100644 --- a/appHandlers/AppIO.py +++ b/appHandlers/AppIO.py @@ -2854,13 +2854,6 @@ class AppIO(QtCore.QObject): str(filename), _("Retry to save it."))) # noqa - tb_settings = QSettings("Open Source", "FlatCAM") - lock_state = self.app.ui.lock_action.isChecked() - tb_settings.setValue('toolbar_lock', lock_state) - - # This will write the setting to the platform specific storage. - del tb_settings - # if quit: # t = threading.Thread(target=lambda: self.check_project_file_size(1, filename=filename)) # t.start() diff --git a/appMain.py b/appMain.py index 08ab60bd..27a6da86 100644 --- a/appMain.py +++ b/appMain.py @@ -2094,10 +2094,10 @@ class App(QtCore.QObject): self.ui.delete_btn.triggered.connect(self.on_delete) self.ui.distance_btn.triggered.connect(lambda: self.distance_tool.run(toggle=True)) - self.ui.distance_min_btn.triggered.connect(lambda: self.distance_min_tool.run(toggle=True)) + # self.ui.distance_min_btn.triggered.connect(lambda: self.distance_min_tool.run(toggle=True)) self.ui.origin_btn.triggered.connect(self.on_set_origin) - self.ui.move2origin_btn.triggered.connect(self.on_move2origin) - self.ui.center_in_origin_btn.triggered.connect(self.on_custom_origin) + # self.ui.move2origin_btn.triggered.connect(self.on_move2origin) + # self.ui.center_in_origin_btn.triggered.connect(self.on_custom_origin) self.ui.jmp_btn.triggered.connect(self.on_jump_to) self.ui.locate_btn.triggered.connect(lambda: self.on_locate(obj=self.collection.get_active())) @@ -3877,9 +3877,6 @@ class App(QtCore.QObject): 'hud_font_size', self.ui.general_pref_form.general_app_set_group.hud_font_size_spinner.get_value() ) - - stgs.setValue('toolbar_lock', self.ui.lock_action.isChecked()) - # This will write the setting to the platform specific storage. del stgs diff --git a/defaults.py b/defaults.py index 3cd24993..fa8a4b6d 100644 --- a/defaults.py +++ b/defaults.py @@ -42,7 +42,6 @@ class AppDefaults: "global_move_ref": 'abs', - "global_toolbar_view": 511, "global_gui_layout": 0, # can be 0:"normal" or 1:"columnar" "global_background_timeout": 300000, # Default value is 5 minutes @@ -60,13 +59,6 @@ class AppDefaults: "global_last_folder": None, "global_last_save_folder": None, - # Default window geometry - "global_def_win_x": 100, - "global_def_win_y": 100, - "global_def_win_w": 1024, - "global_def_win_h": 650, - "global_def_notebook_width": 1, - # Constants... "global_defaults_save_period_ms": 20000, # Time between default saves. "global_shell_shape": [500, 300], # Shape of the shell in pixels. @@ -952,18 +944,13 @@ class AppDefaults: except IOError: log.error("Could not load defaults file.") inform.emit('[ERROR] %s' % _("Could not load the file.")) - # in case the defaults file can't be loaded, show all toolbars - self.defaults["global_toolbar_view"] = 511 return # Parse the JSON try: defaults = simplejson.loads(options) except Exception: - # in case the defaults file can't be loaded, show all toolbars - self.defaults["global_toolbar_view"] = 511 - e = sys.exc_info()[0] - log.error(str(e)) + log.error(str(sys.exc_info()[0])) inform.emit('[ERROR] %s' % _("Failed to parse defaults file.")) return if defaults is None: @@ -1123,8 +1110,6 @@ class AppOptions: except IOError: log.error("Could not load defaults file.") inform.emit('[ERROR] %s' % _("Could not load the file.")) - # in case the defaults file can't be loaded, show all toolbars - self.options["global_toolbar_view"] = 511 return # Parse the JSON @@ -1132,9 +1117,7 @@ class AppOptions: options = simplejson.loads(options) except Exception: # in case the defaults file can't be loaded, show all toolbars - self.defaults["global_toolbar_view"] = 511 - e = sys.exc_info()[0] - log.error(str(e)) + log.error(str(sys.exc_info()[0])) inform.emit('[ERROR] %s' % _("Failed to parse defaults file.")) return if options is None: From b446ded9ab06462b3a33f8b636975b133a524a9e Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 8 Apr 2023 12:10:02 +0300 Subject: [PATCH 39/55] - fixed some really long strings in the Geometry Editor toolbar actions - modified the paint and buffer icons - optimized the editor menu/toolbar action names --- CHANGELOG.md | 6 + appGUI/MainGUI.py | 154 ++++++++++--------- appMain.py | 2 +- assets/resources/buffer32.png | Bin 0 -> 841 bytes assets/resources/dark_resources/buffer32.png | Bin 0 -> 873 bytes assets/resources/dark_resources/paint32.png | Bin 880 -> 1091 bytes assets/resources/paint32.png | Bin 443 -> 725 bytes 7 files changed, 85 insertions(+), 77 deletions(-) create mode 100644 assets/resources/buffer32.png create mode 100644 assets/resources/dark_resources/buffer32.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 79309118..cf4e5a01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ CHANGELOG for FlatCAM Evo beta ================================================= +08.04.2023 + +- fixed some really long strings in the Geometry Editor toolbar actions +- modified the paint and buffer icons +- optimized the editor menu/toolbar action names + 22.03.2023 - in the toolbars now there is text under the icons (which can be turned off from the toolbar context menu) diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 7cbb171c..3b758200 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -703,37 +703,37 @@ class MainGUI(QtWidgets.QMainWindow): self.geo_add_circle_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/circle32.png'), - '%s\t%s' % (_('Add Circle'), _('O')) + '%s\t%s' % (_('Circle'), _('O')) ) self.geo_add_arc_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/arc16.png'), - '%s\t%s' % (_('Add Arc'), _('A'))) + '%s\t%s' % (_('Arc'), _('A'))) self.geo_editor_menu.addSeparator() self.geo_add_rectangle_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), - '%s\t%s' % (_('Add Rectangle'), _('R')) + '%s\t%s' % (_('Rectangle'), _('R')) ) self.geo_add_polygon_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/polygon32.png'), - '%s\t%s' % (_('Add Polygon'), _('N')) + '%s\t%s' % (_('Polygon'), _('N')) ) self.geo_add_path_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/path32.png'), - '%s\t%s' % (_('Add Path'), _('P'))) + '%s\t%s' % (_('Path'), _('P'))) self.geo_editor_menu.addSeparator() self.geo_add_text_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/text32.png'), - '%s\t%s' % (_('Add Text'), _('T'))) + '%s\t%s' % (_('Text'), _('T'))) self.geo_editor_menu.addSeparator() self.geo_union_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/union16.png'), - '%s\t%s' % (_('Polygon Union'), _('U'))) + '%s\t%s' % (_('Union'), _('U'))) self.geo_intersection_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/intersection16.png'), - '%s\t%s' % (_('Polygon Intersection'), _('E'))) + '%s\t%s' % (_('Intersection'), _('E'))) self.geo_subtract_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/subtract16.png'), - '%s\t%s' % (_('Polygon Subtraction'), _('S')) + '%s\t%s' % (_('Subtraction'), _('S')) ) self.geo_subtract_alt_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/subtract16.png'), @@ -747,7 +747,7 @@ class MainGUI(QtWidgets.QMainWindow): # QtGui.QIcon(self.app.resource_location + '/move16.png'), "Move Objects 'm'") self.geo_copy_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/copy16.png'), - '%s\t%s' % (_("Copy Geom"), _('C'))) + '%s\t%s' % (_("Copy"), _('C'))) self.geo_delete_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/deleteshape16.png'), '%s\t%s' % (_("Delete"), _('DEL')) @@ -757,7 +757,7 @@ class MainGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/move32.png'), '%s\t%s' % (_("Move"), _('M'))) self.geo_buffer_menuitem = self.geo_editor_menu.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16.png'), + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), '%s\t%s' % (_("Buffer"), _('B')) ) self.geo_simplification_menuitem = self.geo_editor_menu.addAction( @@ -770,7 +770,7 @@ class MainGUI(QtWidgets.QMainWindow): ) self.geo_transform_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/transform.png'), - '%s\t%s' % (_("Transformation"), _('Alt+R')) + '%s\t%s' % (_("Transform"), _('Alt+R')) ) self.geo_editor_menu.addSeparator() self.geo_cornersnap_menuitem = self.geo_editor_menu.addAction( @@ -786,18 +786,18 @@ class MainGUI(QtWidgets.QMainWindow): self.exc_add_array_drill_menuitem = self.exc_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), - '%s\t%s' % (_('Add Drill Array'), _('A'))) + '%s\t%s' % (_('Drill Array'), _('A'))) self.exc_add_drill_menuitem = self.exc_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/plus16.png'), - '%s\t%s' % (_('Add Drill'), _('D'))) + '%s\t%s' % (_('Drill'), _('D'))) self.exc_editor_menu.addSeparator() self.exc_add_array_slot_menuitem = self.exc_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/slot_array26.png'), - '%s\t%s' % (_('Add Slot Array'), _('Q'))) + '%s\t%s' % (_('Slot Array'), _('Q'))) self.exc_add_slot_menuitem = self.exc_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/slot26.png'), - '%s\t%s' % (_('Add Slot'), _('W'))) + '%s\t%s' % (_('Slot'), _('W'))) self.exc_editor_menu.addSeparator() self.exc_resize_drill_menuitem = self.exc_editor_menu.addAction( @@ -825,16 +825,16 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_add_pad_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/aperture16.png'), - '%s\t%s' % (_('Add Pad'), _('P'))) + '%s\t%s' % (_('Pad'), _('P'))) self.grb_add_pad_array_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/padarray32.png'), - '%s\t%s' % (_('Add Pad Array'), _('A'))) + '%s\t%s' % (_('Pad Array'), _('A'))) self.grb_add_track_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/track32.png'), - '%s\t%s' % (_('Add Track'), _('T'))) + '%s\t%s' % (_('Track'), _('T'))) self.grb_add_region_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), - '%s\t%s' % (_('Add Region'), _('N'))) + '%s\t%s' % (_('Region'), _('N'))) self.grb_editor_menu.addSeparator() self.grb_convert_poly_menuitem = self.grb_editor_menu.addAction( @@ -842,12 +842,12 @@ class MainGUI(QtWidgets.QMainWindow): '%s\t%s' % (_("Poligonize"), _('Alt+N'))) self.grb_add_semidisc_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/semidisc32.png'), - '%s\t%s' % (_("Add SemiDisc"), _('E'))) + '%s\t%s' % (_("SemiDisc"), _('E'))) self.grb_add_disc_menuitem = self.grb_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/disc32.png'), - '%s\t%s' % (_("Add Disc"), _('D'))) + '%s\t%s' % (_("Disc"), _('D'))) self.grb_add_buffer_menuitem = self.grb_editor_menu.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), '%s\t%s' % (_('Buffer'), _('B'))) self.grb_simplification_menuitem = self.geo_editor_menu.addAction( QtGui.QIcon(self.app.resource_location + '/simplify32.png'), @@ -1266,44 +1266,46 @@ class MainGUI(QtWidgets.QMainWindow): self.geo_select_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/pointer32.png'), _("Select")) self.geo_add_circle_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/circle32.png'), _('Add Circle')) + QtGui.QIcon(self.app.resource_location + '/circle32.png'), _('Circle')) self.geo_add_arc_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/arc32.png'), _('Add Arc')) + QtGui.QIcon(self.app.resource_location + '/arc32.png'), _('Arc')) self.geo_add_rectangle_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), _('Add Rectangle')) + QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), _('Rectangle')) self.geo_edit_toolbar.addSeparator() self.geo_add_path_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/path32.png'), _('Add Path')) + QtGui.QIcon(self.app.resource_location + '/path32.png'), _('Path')) self.geo_add_polygon_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _('Add Polygon')) + QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _('Polygon')) self.geo_edit_toolbar.addSeparator() self.geo_add_text_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/text32.png'), _('Add Text')) + QtGui.QIcon(self.app.resource_location + '/text32.png'), _('Text')) self.geo_add_simplification_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/simplify32.png'), _('Simplify')) self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _('Add Buffer')) + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), _('Buffer')) self.geo_add_paint_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/paint32.png'), _('Paint Shape')) + QtGui.QIcon(self.app.resource_location + '/paint32.png'), _('Paint')) self.geo_eraser_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/eraser26.png'), _('Eraser')) self.geo_edit_toolbar.addSeparator() self.geo_union_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/union32.png'), _('Polygon Union')) + QtGui.QIcon(self.app.resource_location + '/union32.png'), _('Union')) self.geo_explode_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/explode32.png'), _('Polygon Explode')) + QtGui.QIcon(self.app.resource_location + '/explode32.png'), _('Explode')) self.geo_intersection_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _('Polygon Intersection')) + QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _('Intersection')) self.geo_subtract_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/subtract32.png'), + QtGui.QIcon(self.app.resource_location + '/subtract32.png'), _('Subtraction')) + self.geo_subtract_btn.setToolTip( _('Polygon Subtraction. First selected is the target.\n' 'The rest of the selected is subtracted from the first.\n' 'First selected is replaced by the result.')) self.geo_alt_subtract_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/subtract_alt32.png'), + QtGui.QIcon(self.app.resource_location + '/subtract_alt32.png'), _('Alt Subtraction')) + self.geo_alt_subtract_btn.setToolTip( _('Alt Subtraction. First selected is the target.\n' 'The rest of the selected is subtracted from the first.\n' 'First selected is kept besides the result.')) @@ -1312,15 +1314,15 @@ class MainGUI(QtWidgets.QMainWindow): self.geo_cutpath_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/cutpath32.png'), _('Cut Path')) self.geo_copy_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/copy32.png'), _("Copy Shape(s)")) + QtGui.QIcon(self.app.resource_location + '/copy32.png'), _("Copy")) self.geo_delete_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("Delete")) self.geo_transform_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transformations")) + QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transform")) self.geo_edit_toolbar.addSeparator() self.geo_move_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/move32.png'), _("Move Objects")) + QtGui.QIcon(self.app.resource_location + '/move32.png'), _("Move")) # ######################################################################## # ########################## Gerber Editor Toolbar# ###################### @@ -1328,13 +1330,13 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_select_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/pointer32.png'), _("Select")) self.grb_add_pad_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/aperture32.png'), _("Add Pad")) + QtGui.QIcon(self.app.resource_location + '/aperture32.png'), _("Pad")) self.add_pad_ar_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/padarray32.png'), _('Add Pad Array')) + QtGui.QIcon(self.app.resource_location + '/padarray32.png'), _('Pad Array')) self.grb_add_track_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/track32.png'), _("Add Track")) + QtGui.QIcon(self.app.resource_location + '/track32.png'), _("Track")) self.grb_add_region_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _("Add Region")) + QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _("Region")) self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/poligonize32.png'), _("Poligonize")) @@ -1345,7 +1347,7 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_edit_toolbar.addSeparator() self.aperture_buffer_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _('Buffer')) + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), _('Buffer')) self.aperture_simplify_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/simplify32.png'), _('Simplification')) self.aperture_scale_btn = self.grb_edit_toolbar.addAction( @@ -1353,7 +1355,7 @@ class MainGUI(QtWidgets.QMainWindow): self.aperture_markarea_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/markarea32.png'), _('Mark Area')) self.grb_import_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/import.png'), _('Import Shape')) + QtGui.QIcon(self.app.resource_location + '/import.png'), _('Import')) self.aperture_eraser_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/eraser26.png'), _('Eraser')) @@ -1364,7 +1366,7 @@ class MainGUI(QtWidgets.QMainWindow): self.aperture_delete_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("Delete")) self.grb_transform_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transformations")) + QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transform")) self.grb_edit_toolbar.addSeparator() self.aperture_move_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/move32.png'), _("Move")) @@ -1864,7 +1866,7 @@ class MainGUI(QtWidgets.QMainWindow): self.draw_simplification = self.g_editor_cmenu.addAction( QtGui.QIcon(self.app.resource_location + '/simplify32.png'), _("Simplification")) self.draw_buffer = self.g_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _("Buffer")) + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), _("Buffer")) self.draw_paint = self.g_editor_cmenu.addAction( QtGui.QIcon(self.app.resource_location + '/paint32.png'), _("Paint")) self.draw_eraser = self.g_editor_cmenu.addAction( @@ -1882,7 +1884,7 @@ class MainGUI(QtWidgets.QMainWindow): self.draw_cut = self.g_editor_cmenu.addAction( QtGui.QIcon(self.app.resource_location + '/cutpath32.png'), _("Cut")) self.draw_transform = self.g_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transformations")) + QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transform")) self.g_editor_cmenu.addSeparator() self.draw_move = self.g_editor_cmenu.addAction( @@ -1909,7 +1911,7 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_editor_cmenu.addSeparator() self.grb_draw_buffer = self.grb_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _("Buffer")) + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), _("Buffer")) self.grb_draw_scale = self.grb_editor_cmenu.addAction( QtGui.QIcon(self.app.resource_location + '/scale32.png'), _("Scale")) self.grb_draw_markarea = self.grb_editor_cmenu.addAction( @@ -1919,19 +1921,19 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_editor_cmenu.addSeparator() self.grb_draw_transformations = self.grb_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transformations")) + QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transform")) self.e_editor_cmenu = self.popMenu.addMenu( QtGui.QIcon(self.app.resource_location + '/drill32.png'), _("Exc Editor")) self.drill = self.e_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Add Drill")) + QtGui.QIcon(self.app.resource_location + '/open_excellon32.png'), _("Drill")) self.drill_array = self.e_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/addarray32.png'), _("Add Drill Array")) + QtGui.QIcon(self.app.resource_location + '/addarray32.png'), _("Drill Array")) self.e_editor_cmenu.addSeparator() self.slot = self.e_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/slot26.png'), _("Add Slot")) + QtGui.QIcon(self.app.resource_location + '/slot26.png'), _("Slot")) self.slot_array = self.e_editor_cmenu.addAction( - QtGui.QIcon(self.app.resource_location + '/slot_array26.png'), _("Add Slot Array")) + QtGui.QIcon(self.app.resource_location + '/slot_array26.png'), _("Slot Array")) self.e_editor_cmenu.addSeparator() self.drill_resize = self.e_editor_cmenu.addAction( QtGui.QIcon(self.app.resource_location + '/resize16.png'), _("Resize Drill")) @@ -2584,37 +2586,37 @@ class MainGUI(QtWidgets.QMainWindow): self.geo_select_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/pointer32.png'), _("Select")) self.geo_add_circle_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/circle32.png'), _('Add Circle')) + QtGui.QIcon(self.app.resource_location + '/circle32.png'), _('Circle')) self.geo_add_arc_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/arc32.png'), _('Add Arc')) + QtGui.QIcon(self.app.resource_location + '/arc32.png'), _('Arc')) self.geo_add_rectangle_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), _('Add Rectangle')) + QtGui.QIcon(self.app.resource_location + '/rectangle32.png'), _('Rectangle')) self.geo_edit_toolbar.addSeparator() self.geo_add_path_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/path32.png'), _('Add Path')) + QtGui.QIcon(self.app.resource_location + '/path32.png'), _('Path')) self.geo_add_polygon_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _('Add Polygon')) + QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _('Polygon')) self.geo_edit_toolbar.addSeparator() self.geo_add_text_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/text32.png'), _('Add Text')) + QtGui.QIcon(self.app.resource_location + '/text32.png'), _('Text')) self.geo_add_buffer_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _('Add Buffer')) + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), _('Buffer')) self.geo_add_paint_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/paint32.png'), _('Paint Shape')) + QtGui.QIcon(self.app.resource_location + '/paint32.png'), _('Paint')) self.geo_eraser_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/eraser26.png'), _('Eraser')) self.geo_edit_toolbar.addSeparator() self.geo_union_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/union32.png'), _('Polygon Union')) + QtGui.QIcon(self.app.resource_location + '/union32.png'), _('Union')) self.geo_explode_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/explode32.png'), _('Polygon Explode')) + QtGui.QIcon(self.app.resource_location + '/explode32.png'), _('Explode')) self.geo_intersection_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _('Polygon Intersection')) + QtGui.QIcon(self.app.resource_location + '/intersection32.png'), _('Intersection')) self.geo_subtract_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/subtract32.png'), _('Polygon Subtraction')) + QtGui.QIcon(self.app.resource_location + '/subtract32.png'), _('Subtraction')) self.geo_alt_subtract_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/subtract_alt32.png'), _('Alt Subtraction')) @@ -2622,15 +2624,15 @@ class MainGUI(QtWidgets.QMainWindow): self.geo_cutpath_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/cutpath32.png'), _('Cut Path')) self.geo_copy_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/copy32.png'), _("Copy Objects")) + QtGui.QIcon(self.app.resource_location + '/copy32.png'), _("Copy")) self.geo_delete_btn = self.geo_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("Delete")) self.geo_transform_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transformations")) + QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transform")) self.geo_edit_toolbar.addSeparator() self.geo_move_btn = self.geo_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/move32.png'), _("Move Objects")) + QtGui.QIcon(self.app.resource_location + '/move32.png'), _("Move")) # ######################################################################## # ################### Gerber Editor Toolbar ############################## @@ -2638,13 +2640,13 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_select_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/pointer32.png'), _("Select")) self.grb_add_pad_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/aperture32.png'), _("Add Pad")) + QtGui.QIcon(self.app.resource_location + '/aperture32.png'), _("Pad")) self.add_pad_ar_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/padarray32.png'), _('Add Pad Array')) + QtGui.QIcon(self.app.resource_location + '/padarray32.png'), _('Pad Array')) self.grb_add_track_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/track32.png'), _("Add Track")) + QtGui.QIcon(self.app.resource_location + '/track32.png'), _("Track")) self.grb_add_region_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _("Add Region")) + QtGui.QIcon(self.app.resource_location + '/polygon32.png'), _("Region")) self.grb_convert_poly_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/poligonize32.png'), _("Poligonize")) @@ -2655,7 +2657,7 @@ class MainGUI(QtWidgets.QMainWindow): self.grb_edit_toolbar.addSeparator() self.aperture_buffer_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/buffer16-2.png'), _('Buffer')) + QtGui.QIcon(self.app.resource_location + '/buffer32.png'), _('Buffer')) self.aperture_simplify_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/simplify32.png'), _('Simplification')) self.aperture_scale_btn = self.grb_edit_toolbar.addAction( @@ -2674,7 +2676,7 @@ class MainGUI(QtWidgets.QMainWindow): self.aperture_delete_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/trash32.png'), _("Delete")) self.grb_transform_btn = self.grb_edit_toolbar.addAction( - QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transformations")) + QtGui.QIcon(self.app.resource_location + '/transform.png'), _("Transform")) self.grb_edit_toolbar.addSeparator() self.aperture_move_btn = self.grb_edit_toolbar.addAction( QtGui.QIcon(self.app.resource_location + '/move32.png'), _("Move")) @@ -4991,7 +4993,7 @@ class ShortcutsTab(QtWidgets.QWidget): _('Alt+Q'), _("QRCode"), _('Alt+R'), _("Rules Check"), _('Alt+S'), _("View File Source"), - _('Alt+T'), _("Transformations"), + _('Alt+T'), _("Transform"), _('Alt+W'), _("Subtract"), _('Alt+X'), _("Cutout PCB"), _('Alt+Z'), _("Panelize PCB"), diff --git a/appMain.py b/appMain.py index 27a6da86..a3435e00 100644 --- a/appMain.py +++ b/appMain.py @@ -2034,7 +2034,7 @@ class App(QtCore.QObject): self.ui.align_btn.triggered.connect(lambda: self.align_objects_tool.run(toggle=True)) # self.ui.sub_btn.triggered.connect(lambda: self.sub_tool.run(toggle=True)) - self.ui.extract_btn.triggered.connect(lambda: self.extract_tool.run(toggle=True)) + # self.ui.extract_btn.triggered.connect(lambda: self.extract_tool.run(toggle=True)) self.ui.copperfill_btn.triggered.connect(lambda: self.copper_thieving_tool.run(toggle=True)) self.ui.markers_tool_btn.triggered.connect(lambda: self.markers_tool.run(toggle=True)) self.ui.punch_btn.triggered.connect(lambda: self.punch_tool.run(toggle=True)) diff --git a/assets/resources/buffer32.png b/assets/resources/buffer32.png new file mode 100644 index 0000000000000000000000000000000000000000..9c049559d7ae8ebc71f72ae685b675975d964cc1 GIT binary patch literal 841 zcmV-P1GfB$P)(*1K~z|UwbxIGRaG1Z@Xym3wHQz%b)20Uh2WqQg}6{?mr$Y5 zB1EFGO&kB9Bxup1f4C4_lrjscMQDgLITIuixQUP!#iZb1kuDtnFbq1w5aOsk7v~If z=e~RId-wU~3zy%0-}8RweBbZ=&hK{)`FIci;D1yLo+*sr7wo~a1#{M*lEZ(5PviS_ zJSdoB0hUJ#6ZjlQqQKHviTfDGx(0nJcm-cZ-zxg?R5Jp$#0kD`#u+bSIL>iWew4wy z3~phx6HN-Zf=#%J?Rcl?%WWklUWV7vp5pJ}5{44oSX}&qkW1eQ$=VRM3gJ7N;=fV6 z8&2Zsl+QpM+mg3fnFuUN2pLWA?RZo8#dokPF?J@7y@qp*E|eF6A0od#!{-?x>{IAS z@ZHg8FmJJ}By0wEgv@WpiUfZ*A#hFwti#K~&bgl8mqk0C=U65OrqPGV#OH-V5n6-; zW@r1xXfL)xz_n;MWQOF60jIcGoGxjcV5VWHk>fiuInPN5PhP!6#p50(-@57?`JJm zCIa2j4&#s1a|1m>wcMZbn?Bg=Xmp`wN-?~F|AgJJCc#&PWLy2>*HV z*H-Zt=3~`d3V>guU7g}@N4p}$w_Lz;(T=6~#nIOEgjNdZ#K-Y{AT{zBTSs?L>NA~2w^BNA~bREA!^x|(JyNkMDRXCDe# Tmd>CX00000NkvXXu0mjfL)eKc literal 0 HcmV?d00001 diff --git a/assets/resources/dark_resources/buffer32.png b/assets/resources/dark_resources/buffer32.png new file mode 100644 index 0000000000000000000000000000000000000000..28a7563d4d20beea244bfb1cc99ef4fda7e1c19b GIT binary patch literal 873 zcmV-v1D5=WP)7A*=}MG%=#kdmPbzfjb=hJhb(TFmqEPIG78 zcjkKUf#L9+Gyik`bLPx>=AlM{6s!n#2j_wqaGeVd1>XcKgRAOdHc4tA@H*D!WzVLz7P=|T5TZ5ue4i-$0!;`$rA62F&7oCu!84_Fs`k~rHo z(WAM1n=AH@hkOm&g3BwI9ohF_Q@+*}&=39rj+gA0VyU9sr|?3__Zc3+1NgdCmH`#m zp6z$kU`C=S`#(_->e+A0m@k|1)iq&fNyU@zQG9mSlw5yiJ!?`4`-u`kcMu>rdfco}ck*w3e8 zzxba7k7EILC%(UE0*MQX>DE@B%lfN9H%&+rHpm9W^m{G1e?kc4owDv4v+E_kRrJ6V zYzZ!Fo1|$FZ5%R+e+urciPsy)&)4PaQh|9{@4>Fh^#|r-XHBB@SubmqWjv!e`yUR@ z^lUztf(tJ0akDtu?`PbnP3kE!2mAh;DTDz@*wJB3JK=(*Cfu{F7QIO^4Gpcr!1D9Zo4m>_};S0BaJT zw&XvCKnk7;evaYC^9V+R--3^V$D)5y9xDF<`xZ_m+m8wy00000NkvXXu0mjfc1)6m literal 0 HcmV?d00001 diff --git a/assets/resources/dark_resources/paint32.png b/assets/resources/dark_resources/paint32.png index 2374b5e878fde1ce2f2424c2afae100f7e807d7b..4ce7ab2c002196f6c5c400b974d69a0db3b59aaf 100644 GIT binary patch delta 983 zcmeysc9>&=%0!dM`sXYh9G3j)o#)gT7?}AyT^vI)?!BGqt5Fgtb8Pt@4CCH)|%a%VZo+J zRpsA}cU(B@dv)L3=GP1X(cTN%+kYJB?^|*6=BmH9focvsUbt`1jHcHN1%(G+@3Fc1 zPqyj%mw@~CA6GyA{+-9cvoq^IU;ROMw-pyNY91^U*t*JVYkm8J$&)`aa@W>AmCnxz zd3#@N!!p5y!i!QnB!uMpv-oB|IC(N;|9z<)5$AvZ3Mr0Yzs{&&I+dZZ!L(rE^F@S!2bpW?j_mC(D+H{@%_J749x8v7l-%%lT8kRxD?BsJ5QJfA3MY__(mM$$V>= z>mBm+c;CPK_eZVS@!I=WlbFqHbgoB7FIH1s`OKKVfsw)5`r5G~3!UZLl7hbctgu&g zHJxs`gO9=2_nVsW!p4TIzKYZ4^OM}$H>drdf9CAhr+sV;YuBEe#mgp~8o;9(*U7SS z^W>;7w_cYO&!1metNZF0n}5%uz|Vb4zS_u?nX%NTr3o>|#p}Okvj6`1hevHy*WW*1 zOx{hIC3^bw+{X8BH_rW1lK$ZC-A~;Dr(gcD*}6hwTYcfAC!bI57uWpuVuk1N+}vvo z`S~k@gZ=uKJ^9yKxN%QdcyyjWdqWr3w&L2qf+yZ@yIOPMwl;HD&$;){KQC0w^7UPG zt$15-lve$vg%j#yKir66Wj(aLbK(}0!fR^tkI2Y$_@7y+xO4v;nO8R@O@##GofiGt zC}}vo+bt|C>HX#B&+L{TxOU)Z;rEXl@BBN`V)S7?Z}r|Qt5@qY8Zso*&b@WtKR0ih z`qF7)uim_w9vRKv(8_vsP9fW&x62+LJlMZ5F?*}PdYn_Ts_5-s1_lPz64!{5l*E!$tK_0o zAjM#0U}UUoV4`ba5n^CrWom9^XsT^sU}a$NqgAK?MMG|WN@iLmh6Z1_j?Npa&H#02 jz^y3B%uOvWNz5(4&;-|W?`*d)P!EHrtDnm{r-UW|VcN^& delta 755 zcmV-K}|sb0I`n?{9y%=AzXjv1q2r>EEbAxX8-^KUr9tkRA}Dq zn7c{?K@^7njiO*>qZUF6@d9GJpoL;%Ku9h42v&lm2)5!IXl*5kogjjRRu&dEqNGVD z0SiT4!CN%(GGp;O!bX$q&PEQ%p625u3wyq^v$Hb;L=i<4{~@#n0LRe^0A^>IudXuh z?UjG7?~lbtM=5%G%3NzNS5~C!tg~<|M&ZRp=B3i_;)XLb(skEKytS3w+p8{YczkSL z4e9zrig$I9xtu@oVv!sfk*+_acz-{6ap6zgFq4t4-(Fk+z}Of?TsN5b{=S(bq&K*G01OS;J@J3nS8{Sv+QU+OaFD#Y2_|khKQHZJDLybj zf$M8K@y$(X5Bmma09aloKR$wqo9}u{i?oM-VFdu)-DE1|{XLbU(EWXNaq}7M?3DKL zFaA|9na_KL*4Np9=4R&OG~^+X#nW!Be%B5yF2pnk-`TDKi+wdXn6x% z*Nhx#zsllfiK+nL>B--$trWSi@V$-I0C0Hdo&2Vzr2Q(3dxmB#$GdeUgIy5vZFfckWFuyn?L;wH)C3HntbYx+4WjbwdWNBu305UK#GA%GQ lEig1xGBY|fGdeUhD=;uRFfh(#ocI6$002ovPDHLkV1g~_T=4(^ diff --git a/assets/resources/paint32.png b/assets/resources/paint32.png index 8db9f3c62cd9d308d144708c2240a1e9aa0be9e9..2d9ad061f4f6d7d12425e917c07fbb14acf17c8c 100644 GIT binary patch delta 668 zcmdnZe3ezPGr-TCmrII^fq{Y7)59eQNGpIa2MdtAS-h!!qM>O$!>V3oBOsr%z$3C4 zNGE{this?wKn8n>r>`sfa~2K`bCKeFEf)p`#wDIEjv*e$k6y9O5OEY?c%VN|MevA% zm{PdL30G;?g8~QbH+-@Y369VZThy^=D#tC(Kmo@VlUp2_xhe@8M34AQXt3Ala9q2n zb-KV;i%mQ0ds=rEzh~bxSuI#M|Jm*B`S;#zxTHM2@~PEznXebuKUFpG0jQ^k%TACDauH)UCKM9w&u_oVqxxe4$o=~qv>FR?Mr)BIsd}_`l;cc_O z-!%5W`p^HXN0ir&tH-|26k)lX#(J>*%fovY55-5`KIHEia(An!NRP*vok8>UmVKBq zJ9m0m;G!J=;GE_8tG>$r=~<9gf5nwq)@uLREi=XVX7gT=zb+x}qfv5W7q8vS@R(iV z{H&toUrdjFzQ?&PJ^RIk^-W#s=PZ{z*l;_I_4}s67o`VXg6)@0+sD1@mQwVa>+7Q* zZGB#setv(@WBmgTdrtnV68->;Mb#45h?11Vl2ohYqEsNoU}RuqtZQJRYhV!~WME-s zYHDR7R6jcvfe{mL2 O4}+(xpUXO@geCwW1s1me delta 401 zcmcc0x|`XsGr-TCmrII^fq{Y7)59eQNQ(fmAO{POEcbOW1yU^Oj=qiz3>*8o|0J?b zR5Y#s-`;;_KTv|Rz$3Dlfq`2Xgc%uT&5-~KvX^-Jy0Sm!Ealo zalZH5elABwiMEf=U;HrcFw#!lDCy%i`{IHE##4Sqnj58a#LORviF8j$nR++n(#C)) zUG>{P`||&N-828NefT1&zL;Y%CtEh!RM*>dC9XNy%x@~KIz!pze43>nhvDYPR%<7n z&WJTbP0 Hl+XkK=6r_y From faf41d7bcab75f47a9502721fbc9b354d6759c35 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 8 Apr 2023 12:29:45 +0300 Subject: [PATCH 40/55] - major change: from now on the only dimensional units available are those from ISO METRIC system --- CHANGELOG.md | 1 + appGUI/MainGUI.py | 15 +- appGUI/preferences/PreferencesUIManager.py | 2 - .../general/GeneralAppPrefGroupUI.py | 31 +- appHandlers/AppIO.py | 2 - appMain.py | 313 +----------------- defaults.py | 1 - 7 files changed, 8 insertions(+), 357 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf4e5a01..8f715a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG for FlatCAM Evo beta - fixed some really long strings in the Geometry Editor toolbar actions - modified the paint and buffer icons - optimized the editor menu/toolbar action names +- major change: from now on the only dimensional units available are those from ISO METRIC system 22.03.2023 diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 3b758200..948e1588 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -491,9 +491,6 @@ class MainGUI(QtWidgets.QMainWindow): # Separator self.menuedit.addSeparator() - self.menuedittoggleunits = self.menuedit.addAction( - QtGui.QIcon(self.app.resource_location + '/toggle_units16.png'), - '%s\t%s' % (_('Toggle Units'), _('Q'))) self.menueditselectall = self.menuedit.addAction( QtGui.QIcon(self.app.resource_location + '/select_all.png'), '%s\t%s' % (_('Select All'), _('Ctrl+A'))) @@ -1981,7 +1978,7 @@ class MainGUI(QtWidgets.QMainWindow): self.status_toolbar.setVisible(self.app.defaults["global_statusbar_show"]) self.units_label = FCLabel("[mm]") - self.units_label.setToolTip(_("Application units")) + self.units_label.setToolTip(_("The application dimensional units is millimeter.")) self.units_label.setMargin(2) self.infobar.addWidget(self.units_label) @@ -3372,12 +3369,7 @@ class MainGUI(QtWidgets.QMainWindow): # Change Units if key == QtCore.Qt.Key.Key_Q: - # if self.app.app_units == 'MM': - # self.app.ui.general_pref_form.general_app_group.units_radio.set_value("IN") - # else: - # self.app.ui.general_pref_form.general_app_group.units_radio.set_value("MM") - # self.app.on_toggle_units(no_pref=True) - self.app.on_toggle_units_click() + pass # Rotate Object by 90 degree CW if key == QtCore.Qt.Key.Key_R: @@ -3960,7 +3952,6 @@ class MainGUI(QtWidgets.QMainWindow): # Add Track if key == QtCore.Qt.Key.Key_T or key == 'T': self.app.grb_editor.launched_from_shortcuts = True - # ## Current application units in Upper Case self.app.grb_editor.select_tool('track') return @@ -4230,7 +4221,7 @@ class MainGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key.Key_T or key == 'T': self.app.exc_editor.launched_from_shortcuts = True # ## Current application units in Upper Case - self.units = self.general_pref_form.general_app_group.units_radio.get_value().upper() + self.units = "MM" tool_add_popup = FCInputDoubleSpinner(title='%s ...' % _("New Tool"), text='%s:' % _('Enter a Tool Diameter'), min=0.0000, max=99.9999, decimals=self.decimals) diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 9eed28de..1d69df04 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -55,9 +55,7 @@ class PreferencesUIManager(QtCore.QObject): # def app_obj.new_object(self, kind, name, initialize, active=True, fit=True, plot=True) self.defaults_form_fields = { # General App - "decimals_inch": self.ui.general_pref_form.general_app_group.precision_inch_entry, "decimals_metric": self.ui.general_pref_form.general_app_group.precision_metric_entry, - "units": self.ui.general_pref_form.general_app_group.units_radio, "global_graphic_engine": self.ui.general_pref_form.general_app_group.ge_radio, "global_graphic_engine_3d_no_mp": self.ui.general_pref_form.general_app_group.ge_comp_cb, "global_app_level": self.ui.general_pref_form.general_app_group.app_level_radio, diff --git a/appGUI/preferences/general/GeneralAppPrefGroupUI.py b/appGUI/preferences/general/GeneralAppPrefGroupUI.py index b76400ff..6dc5d6cd 100644 --- a/appGUI/preferences/general/GeneralAppPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralAppPrefGroupUI.py @@ -30,9 +30,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): # Grid0 Frame # ############################################################################################################# self.unitslabel = FCLabel('%s' % _("Units"), color='red', bold=True) - self.unitslabel.setToolTip(_("The default value for the application units.\n" - "Whatever is selected here is set every time\n" - "the application is started.")) + self.unitslabel.setToolTip(_("The application dimensional units is millimeter.")) self.layout.addWidget(self.unitslabel) grid0_frame = FCFrame() @@ -41,19 +39,10 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): grid0 = GLay(v_spacing=5, h_spacing=3) grid0_frame.setLayout(grid0) - # Units for FlatCAM - self.units_radio = RadioSetDefaults( - choices=[{'label': _('mm'), 'value': 'MM'}, {'label': _('inch'), 'value': 'IN'}], - compact=True - ) - - grid0.addWidget(self.units_radio, 0, 0, 1, 2) - # Precision Metric - self.precision_metric_label = FCLabel('%s:' % _('Precision MM')) + self.precision_metric_label = FCLabel('%s:' % _('Dim. Precision')) self.precision_metric_label.setToolTip( - _("The number of decimals used throughout the application\n" - "when the set units are in METRIC system.\n" + _("The number of decimals used throughout the application.\n" "Any change here require an application restart.") ) self.precision_metric_entry = FCSpinner() @@ -63,20 +52,6 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.precision_metric_label, 2, 0) grid0.addWidget(self.precision_metric_entry, 2, 1) - # Precision Inch - self.precision_inch_label = FCLabel('%s:' % _('Precision Inch')) - self.precision_inch_label.setToolTip( - _("The number of decimals used throughout the application\n" - "when the set units are in INCH system.\n" - "Any change here require an application restart.") - ) - self.precision_inch_entry = FCSpinner() - self.precision_inch_entry.set_range(2, 16) - self.precision_inch_entry.setWrapping(True) - - grid0.addWidget(self.precision_inch_label, 4, 0) - grid0.addWidget(self.precision_inch_entry, 4, 1) - self.par_label = FCLabel('%s' % _("Parameters"), color='blue', bold=True) self.layout.addWidget(self.par_label) diff --git a/appHandlers/AppIO.py b/appHandlers/AppIO.py index e547ed3f..f871b7cc 100644 --- a/appHandlers/AppIO.py +++ b/appHandlers/AppIO.py @@ -2599,8 +2599,6 @@ class AppIO(QtCore.QObject): # for some reason, setting ui_title does not work when this method is called from Tcl Shell # it's because the TclCommand is run in another thread (it inherits TclCommandSignaled) - if cli is None: - self.app.set_screen_units(self.app.options["units"]) self.app.restore_project_objects_sig.emit(proj_dict, filename, cli, plot) diff --git a/appMain.py b/appMain.py index a3435e00..a0bd1aae 100644 --- a/appMain.py +++ b/appMain.py @@ -619,11 +619,7 @@ class App(QtCore.QObject): self.app_units = self.options["units"] self.default_units = self.defaults["units"] - - if self.app_units == 'MM': - self.decimals = int(self.options['decimals_metric']) - else: - self.decimals = int(self.options['decimals_inch']) + self.decimals = int(self.options['decimals_metric']) if self.options["global_theme"] == 'default': self.resource_location = 'assets/resources' @@ -803,8 +799,6 @@ class App(QtCore.QObject): self.ui = MainGUI(self) # ######################## - # set FlatCAM units in the Status bar - self.set_screen_units(self.app_units) # decide if to show or hide the Notebook side of the screen at startup if self.options["global_project_at_startup"] is True: @@ -1911,7 +1905,6 @@ class App(QtCore.QObject): self.ui.menueditjump.triggered.connect(self.on_jump_to) self.ui.menueditlocate.triggered.connect(lambda: self.on_locate(obj=self.collection.get_active())) - self.ui.menuedittoggleunits.triggered.connect(self.on_toggle_units_click) self.ui.menueditselectall.triggered.connect(self.on_selectall) self.ui.menueditpreferences.triggered.connect(self.on_preferences) @@ -4214,310 +4207,6 @@ class App(QtCore.QObject): """ self.preferencesUiManager.defaults_write_form_field(field=field) - if field == "units": - self.set_screen_units(self.app_units) - - def set_screen_units(self, units): - """ - Set the FlatCAM units on the status bar. - - :param units: the new measuring units to be displayed in FlatCAM's status bar. - :return: None - """ - self.ui.units_label.setText("[" + units.lower() + "]") - - def on_toggle_units_click(self): - try: - new_units, factor = self.on_toggle_units() - except TypeError: - # hen the self.on_toggle_units() return only one value (maybe None) it means something went wrong, - # it will end up in this exception, and we should return - return - - if self.ui.plot_tab_area.currentWidget().objectName() == "preferences_tab": - if factor != 1: # means we had a unit change in the rest of the app - if self.app_units != new_units: - pref_factor = 25.4 if new_units == 'MM' else 1 / 25.4 - self.scale_preferences(pref_factor, new_units) - - self.options["units"] = new_units - - # update th new units in the preferences storage - self.app_units = new_units - - def scale_preferences(self, sfactor, new_units): - self.preferencesUiManager.defaults_read_form() - - # update the defaults from form, some may assume that the conversion is enough, and it's not - self.on_defaults2options() - - # Keys in self.options for which to scale their values - dimensions = [ - # Global - 'global_gridx', 'global_gridy', 'global_snap_max', "global_tolerance", - 'global_tpdf_bmargin', 'global_tpdf_tmargin', 'global_tpdf_rmargin', 'global_tpdf_lmargin', - - # Gerber Object - 'gerber_noncoppermargin', 'gerber_bboxmargin', - - # Gerber Editor - "gerber_editor_newsize", "gerber_editor_lin_pitch", "gerber_editor_buff_f", - "gerber_editor_newdim", "gerber_editor_ma_low", "gerber_editor_ma_high", - - # Excellon Object - 'excellon_drill_tooldia', 'excellon_slot_tooldia', - - # Excellon Editor - "excellon_editor_newdia", "excellon_editor_lin_pitch", "excellon_editor_slot_lin_pitch", - "excellon_editor_slot_length", - - # Geometry Object - 'tools_mill_cutz', "tools_mill_depthperpass", 'tools_mill_travelz', 'tools_mill_feedrate', - 'tools_mill_feedrate_rapid', "tools_mill_toolchangez", "tools_mill_feedrate_z", - "tools_mill_toolchangexy", 'tools_mill_tooldia', 'tools_mill_endz', 'tools_mill_endxy', - "tools_mill_extracut_length", "tools_mill_z_pdepth", - "tools_mill_feedrate_probe", "tools_mill_startz", "geometry_segx", "geometry_segy", "tools_mill_area_overz", - - # CNCJob Object - 'cncjob_tooldia', 'cncjob_bed_max_x', 'cncjob_bed_max_y', 'cncjob_bed_offset_x', 'cncjob_bed_offset_y', - 'cncjob_bed_skew_x', 'cncjob_bed_skew_y', - - # AutoLevelling Plugin - "tools_al_travelz", "tools_al_probe_depth", "tools_al_grbl_jog_step", - "tools_al_grbl_jog_fr", "tools_al_grbl_travelz", - - # Isolation Plugin - "tools_iso_tool_cutz", - - # Drilling Plugin - 'tools_drill_cutz', 'tools_drill_depthperpass', 'tools_drill_travelz', 'tools_drill_endz', - 'tools_drill_endxy', 'tools_drill_feedrate_z', 'tools_drill_toolchangez', "tools_drill_drill_overlap", - 'tools_drill_offset', "tools_drill_toolchangexy", "tools_drill_startz", 'tools_drill_feedrate_rapid', - "tools_drill_feedrate_probe", "tools_drill_z_pdepth", "tools_drill_area_overz", - - # NCC Plugin - "tools_ncc_tools", "tools_ncc_margin", "tools_ncc_offset_value", "tools_ncc_cutz", "tools_ncc_tipdia", - "tools_ncc_newdia", - - # Cutout Plugin - "tools_cutout_tooldia", 'tools_cutout_margin', "tools_cutout_z", "tools_cutout_depthperpass", - 'tools_cutout_gapsize', 'tools_cutout_gap_depth', 'tools_cutout_mb_dia', 'tools_cutout_mb_spacing', - - # Paint Plugin - "tools_paint_tooldia", 'tools_paint_offset', "tools_paint_cutz", "tools_paint_tipdia", "tools_paint_newdia", - - # 2Sided Plugin - "tools_2sided_drilldia", - - # Film Plugin - "tools_film_boundary", "tools_film_scale_stroke", - - # Panel Plugin - "tools_panelize_spacing_columns", "tools_panelize_spacing_rows", "tools_panelize_constrainx", - "tools_panelize_constrainy", - - # Calculators Plugin - "tools_calc_vshape_tip_dia", "tools_calc_vshape_cut_z", - - # Transform Plugin - "tools_transform_ref_point", "tools_transform_offset_x", "tools_transform_offset_y", - "tools_transform_buffer_dis", - - # SolderPaste Plugin - "tools_solderpaste_tools", "tools_solderpaste_new", "tools_solderpaste_z_start", - "tools_solderpaste_z_dispense", "tools_solderpaste_z_stop", "tools_solderpaste_z_travel", - "tools_solderpaste_z_toolchange", "tools_solderpaste_xy_toolchange", "tools_solderpaste_frxy", - "tools_solderpaste_frz", "tools_solderpaste_frz_dispense", - - # Markers Plugin - "tools_markers_thickness", "tools_markers_length", "tools_markers_offset_x", "tools_markers_offset_y", - - # Check Rules Plugin - "tools_cr_trace_size_val", "tools_cr_c2c_val", "tools_cr_c2o_val", "tools_cr_s2s_val", "tools_cr_s2sm_val", - "tools_cr_s2o_val", "tools_cr_sm2sm_val", "tools_cr_ri_val", "tools_cr_h2h_val", "tools_cr_dh_val", - - # QRCode Plugin - "tools_qrcode_border_size", - - # Copper Thieving Plugin - "tools_copper_thieving_clearance", "tools_copper_thieving_margin", - "tools_copper_thieving_dots_dia", "tools_copper_thieving_dots_spacing", - "tools_copper_thieving_squares_size", "tools_copper_thieving_squares_spacing", - "tools_copper_thieving_lines_size", "tools_copper_thieving_lines_spacing", - "tools_copper_thieving_rb_margin", "tools_copper_thieving_rb_thickness", - "tools_copper_thieving_mask_clearance", - - # Fiducials Plugin - "tools_fiducials_dia", "tools_fiducials_margin", "tools_fiducials_line_thickness", - - # Drills Extraction Plugin - "tools_extract_hole_fixed_dia", "tools_extract_circular_ring", "tools_extract_oblong_ring", - "tools_extract_square_ring", "tools_extract_rectangular_ring", "tools_extract_others_ring", - - # Punch Gerber Plugin - "tools_punch_hole_fixed_dia", "tools_punch_circular_ring", "tools_punch_oblong_ring", - "tools_punch_square_ring", "tools_punch_rectangular_ring", "tools_punch_others_ring", - - # Invert Gerber Plugin - "tools_invert_margin", - - ] - for dim in dimensions: - if dim in ['tools_mill_tooldia', 'tools_ncc_tools', 'tools_solderpaste_tools', 'tools_iso_tooldia', - 'tools_paint_tooldia', 'tools_transform_ref_point', 'tools_cal_toolchange_xy', - 'gerber_editor_newdim', 'tools_drill_toolchangexy', 'tools_drill_endxy', - 'tools_mill_toolchangexy', 'tools_mill_endxy', 'tools_solderpaste_xy_toolchange']: - if not self.options[dim] or self.options[dim] == '': - continue - - if isinstance(self.options[dim], str): - try: - tools_diameters = eval(self.options[dim]) - except Exception as e: - self.log.error("App.on_toggle_units().scale_defaults() lists --> %s" % str(e)) - continue - elif isinstance(self.options[dim], (float, int)): - tools_diameters = [self.options[dim]] - else: - tools_diameters = list(self.options[dim]) - - if isinstance(tools_diameters, (tuple, list)): - pass - elif isinstance(tools_diameters, (int, float)): - tools_diameters = [self.options[dim]] - else: - continue - - td_len = len(tools_diameters) - conv_list = [] - for t in range(td_len): - conv_list.append(self.dec_format(float(tools_diameters[t]) * sfactor, self.decimals)) - - self.options[dim] = conv_list - elif dim in ['global_gridx', 'global_gridy']: - # format the number of decimals to the one specified in self.decimals - try: - val = float(self.options[dim]) * sfactor - except Exception as e: - self.log.error('App.on_toggle_units().scale_defaults() grids --> %s' % str(e)) - continue - - self.options[dim] = self.dec_format(val, self.decimals) - else: - # the number of decimals for the rest is kept unchanged - if self.options[dim]: - try: - val = float(self.options[dim]) * sfactor - except Exception as e: - self.log.error( - 'App.on_toggle_units().scale_defaults() standard --> Value: %s %s' % (str(dim), str(e)) - ) - continue - - self.options[dim] = self.dec_format(val, self.decimals) - - self.preferencesUiManager.defaults_write_form(fl_units=new_units) - - def on_toggle_units(self): - """ - Callback for the Units radio-button change in the Preferences tab. - Changes the application's default units adn for the project too. - If changing the project's units, the change propagates to all - the objects in the project. - - :return: The new application units. String: "IN" or "MM" with caps lock - """ - - if self.toggle_units_ignore: - return - - new_units = 'IN' if self.app_units.upper() == 'MM' else 'MM' - - # we can't change the units while inside the Editors - if self.call_source in ['geo_editor', 'grb_editor', 'exc_editor']: - msg = _("Units cannot be changed while the editor is active.") - self.inform.emit("[WARNING_NOTCL] %s" % msg) - return - - # ############################################################################################################## - # Changing project units. Ask the user. - # ############################################################################################################## - msgbox = FCMessageBox(parent=self.ui) - title = _("Toggle Units") - txt = _("Changing the units of the project\n" - "will scale all objects.\n\n" - "Do you want to continue?") - msgbox.setWindowTitle(title) # taskbar still shows it - msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/app128.png')) - msgbox.setText('%s' % title) - msgbox.setInformativeText(txt) - msgbox.setIconPixmap(QtGui.QPixmap(self.resource_location + '/toggle_units32.png')) - - bt_ok = msgbox.addButton(_('Ok'), QtWidgets.QMessageBox.ButtonRole.AcceptRole) - msgbox.addButton(_('Cancel'), QtWidgets.QMessageBox.ButtonRole.RejectRole) - - msgbox.setDefaultButton(bt_ok) - msgbox.exec() - response = msgbox.clickedButton() - - if response == bt_ok: - new_units = "IN" if self.app_units.upper() == "MM" else "MM" - - # The scaling factor depending on choice of units. - factor = 25.4 if new_units == 'MM' else 1 / 25.4 - - # update the Application objects with the new units - for obj in self.collection.get_list(): - obj.convert_units(new_units) - # make that the properties stored in the object are also updated - self.app_obj.object_changed.emit(obj) - - # rebuild the object UI - obj.build_ui() - - # update workspace if active - if self.options['global_workspace'] is True: - self.plotcanvas.draw_workspace(pagesize=self.options['global_workspaceT']) - - # adjust the grid values on the main toolbar - val_x = round(float(self.options['global_gridx']) * factor, self.decimals) - val_y = val_x if self.ui.grid_gap_link_cb.isChecked() else \ - round(float(self.options['global_gridy']) * factor, self.decimals) - - # update Object UI forms - current = self.collection.get_active() - if current is not None: - # the transfer of converted values to the UI form for Geometry is done local in the AppObjectTemplate.py - if not isinstance(current, GeometryObject): - current.to_form() - - # plot again all objects - self.plot_all() - self.set_screen_units(new_units) - - # flag for the app that we changed the object properties, and it should save the project - self.should_we_save = True - - self.inform.emit('[success] %s: %s' % (_("Converted units to"), new_units)) - else: - factor = 1 - - # store the grid values, so they are not changed in the next step - val_x = float(self.options['global_gridx']) - val_y = float(self.options['global_gridy']) - - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - - self.preferencesUiManager.defaults_read_form() - - # update the Grid snap values - self.options['global_gridx'] = val_x - self.options['global_gridy'] = val_y - self.ui.grid_gap_x_entry.set_value(val_x, decimals=self.decimals) - self.ui.grid_gap_y_entry.set_value(val_y, decimals=self.decimals) - - return new_units, factor - def on_deselect_all(self): self.collection.set_all_inactive() self.delete_selection_shape() diff --git a/defaults.py b/defaults.py index fa8a4b6d..f0779d7e 100644 --- a/defaults.py +++ b/defaults.py @@ -74,7 +74,6 @@ class AppDefaults: "global_tcl_path": '', # General APP Preferences - "decimals_inch": 4, "decimals_metric": 4, "global_graphic_engine": '3D', "global_graphic_engine_3d_no_mp": False, From 6f93734d3f109c45b67796f8de5b666d34b1bb3c Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 8 Apr 2023 12:43:34 +0300 Subject: [PATCH 41/55] - minor name changes --- CHANGELOG.md | 1 + appEditors/AppExcEditor.py | 12 ++++++------ appEditors/AppGeoEditor.py | 12 ++++++------ appEditors/AppGerberEditor.py | 8 ++++---- appGUI/MainGUI.py | 16 ++++++++-------- appGUI/ObjectUI.py | 12 ++++++------ appGUI/PlotCanvas.py | 6 +++--- appGUI/PlotCanvas3d.py | 4 ++-- appGUI/PlotCanvasLegacy.py | 4 ++-- appGUI/VisPyCanvas.py | 4 ++-- appGUI/preferences/OptionUI.py | 2 +- appGUI/preferences/PreferencesUIManager.py | 4 ++-- .../cncjob/CNCJobEditorPrefGroupUI.py | 2 +- .../preferences/cncjob/CNCJobOptPrefGroupUI.py | 2 +- .../preferences/general/GeneralAPPSetGroupUI.py | 12 ++++++------ .../preferences/general/GeneralAppPrefGroupUI.py | 4 ++-- .../general/GeneralAppSettingsGroupUI.py | 4 ++-- .../preferences/general/GeneralGUIPrefGroupUI.py | 4 ++-- .../utilities/AutoCompletePrefGroupUI.py | 2 +- appGUI/preferences/utilities/FAExcPrefGroupUI.py | 2 +- appGUI/preferences/utilities/FAGcoPrefGroupUI.py | 2 +- appGUI/preferences/utilities/FAGrbPrefGroupUI.py | 2 +- appMain.py | 12 ++++++------ appObjects/ObjectCollection.py | 4 ++-- appPlugins/ToolDistance.py | 2 +- appPlugins/ToolShell.py | 2 +- appTranslation.py | 10 +++++----- FlatCAM.py => flatcam.py | 2 +- 28 files changed, 77 insertions(+), 76 deletions(-) rename FlatCAM.py => flatcam.py (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f715a59..af6f32e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG for FlatCAM Evo beta - modified the paint and buffer icons - optimized the editor menu/toolbar action names - major change: from now on the only dimensional units available are those from ISO METRIC system +- minor name changes 22.03.2023 diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index c6fe2191..2e48deff 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -385,7 +385,7 @@ class DrillAdd(FCShapeTool): self.points = self.draw_app.snap_x, self.draw_app.snap_y # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -849,7 +849,7 @@ class DrillArray(FCShapeTool): self.points = self.draw_app.snap_x, self.draw_app.snap_y # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1187,7 +1187,7 @@ class SlotAdd(FCShapeTool): self.points = self.draw_app.snap_x, self.draw_app.snap_y # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1769,7 +1769,7 @@ class SlotArray(FCShapeTool): self.points = self.draw_app.snap_x, self.draw_app.snap_y # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -2196,7 +2196,7 @@ class ResizeEditorExc(FCShapeTool): self.points = self.draw_app.snap_x, self.draw_app.snap_y # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -2780,7 +2780,7 @@ class CopyEditorExc(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index 19db79e4..dcd15f4c 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -602,7 +602,7 @@ class FCCircle(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1136,7 +1136,7 @@ class FCRectangle(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1345,7 +1345,7 @@ class FCPolygon(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1550,7 +1550,7 @@ class FCPath(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -2108,7 +2108,7 @@ class FCMove(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -2624,7 +2624,7 @@ class FCCopy(FCShapeTool): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index 1612cd3f..c230232a 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -300,7 +300,7 @@ class PadEditorGrb(ShapeToolEditorGrb): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -896,7 +896,7 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.points = self.draw_app.snap_x, self.draw_app.snap_y # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1419,7 +1419,7 @@ class RegionEditorGrb(ShapeToolEditorGrb): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -1823,7 +1823,7 @@ class TrackEditorGrb(ShapeToolEditorGrb): return # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 948e1588..65cf0fe0 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -2064,7 +2064,7 @@ class MainGUI(QtWidgets.QMainWindow): # ######################################################################## # ################## RESTORE UI from QSettings ################# # ######################################################################## - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("saved_gui_state"): self.restoreState(qsettings.value('saved_gui_state'), 0) tb_lock_state = qsettings.value('toolbar_lock', "true") @@ -2233,7 +2233,7 @@ class MainGUI(QtWidgets.QMainWindow): def on_toggle_gui(self): if self.isHidden(): - mgui_settings = QSettings("Open Source", "FlatCAM") + mgui_settings = QSettings("Open Source", "FlatCAM_EVO") if mgui_settings.contains("maximized_gui"): maximized_ui = mgui_settings.value('maximized_gui', type=bool) if maximized_ui is True: @@ -2339,7 +2339,7 @@ class MainGUI(QtWidgets.QMainWindow): """ self.app.log.debug("Clearing the settings in QSettings. GUI settings cleared.") - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") theme_settings.setValue('theme', 'light') del theme_settings @@ -2364,7 +2364,7 @@ class MainGUI(QtWidgets.QMainWindow): response = msgbox.clickedButton() if forced_clear is True or response == bt_yes: - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") for key in qsettings.allKeys(): qsettings.remove(key) # This will write the setting to the platform specific storage. @@ -2682,7 +2682,7 @@ class MainGUI(QtWidgets.QMainWindow): self.snap_magnet.setVisible(False) self.editor_exit_btn_ret_action.setVisible(False) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("layout"): layout = qsettings.value('layout', type=str) @@ -2750,7 +2750,7 @@ class MainGUI(QtWidgets.QMainWindow): if isinstance(widget, QtWidgets.QToolBar): widget.setMovable(True) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") qsettings.setValue('toolbar_lock', lock) # This will write the setting to the platform specific storage. del qsettings @@ -2765,7 +2765,7 @@ class MainGUI(QtWidgets.QMainWindow): if isinstance(widget, QtWidgets.QToolBar): widget.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") qsettings.setValue('menu_show_text', show_text) # This will write the setting to the platform specific storage. del qsettings @@ -4519,7 +4519,7 @@ class MainGUI(QtWidgets.QMainWindow): else: g_rect = self.geometry() - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") qsettings.setValue('saved_gui_state', self.saveState(0)) qsettings.setValue('toolbar_lock', self.lock_action.isChecked()) qsettings.setValue('menu_show_text', self.show_text_action.isChecked()) diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py index b9b7e9dc..4946d706 100644 --- a/appGUI/ObjectUI.py +++ b/appGUI/ObjectUI.py @@ -36,7 +36,7 @@ class ObjectUI(QtWidgets.QWidget): self.app = app self.decimals = app.decimals - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -561,7 +561,7 @@ class ExcellonObjectUI(ObjectUI): self.decimals = app.decimals self.app = app - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -856,7 +856,7 @@ class GeometryObjectUI(ObjectUI): self.decimals = app.decimals self.app = app - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -1173,7 +1173,7 @@ class CNCObjectUI(ObjectUI): self.decimals = app.decimals self.app = app - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -1473,7 +1473,7 @@ class ScriptObjectUI(ObjectUI): self.decimals = app.decimals self.app = app - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -1536,7 +1536,7 @@ class DocumentObjectUI(ObjectUI): self.decimals = app.decimals self.app = app - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: diff --git a/appGUI/PlotCanvas.py b/appGUI/PlotCanvas.py index b01884d8..1b67a39e 100644 --- a/appGUI/PlotCanvas.py +++ b/appGUI/PlotCanvas.py @@ -50,7 +50,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): self.fcapp = fcapp - settings = QtCore.QSettings("Open Source", "FlatCAM") + settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if settings.contains("theme"): theme = settings.value('theme', type=str) else: @@ -337,7 +337,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): hud_text = '%s\n%s\n\n%s\n%s' % (l1_hud_text, l2_hud_text, l3_hud_text, l4_hud_text) # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: @@ -385,7 +385,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): def on_toggle_grid_lines(self, signal=None, silent=None): state = self.grid_lines_enabled - settings = QtCore.QSettings("Open Source", "FlatCAM") + settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if settings.contains("theme"): theme = settings.value('theme', type=str) else: diff --git a/appGUI/PlotCanvas3d.py b/appGUI/PlotCanvas3d.py index 748b459c..3671a73d 100644 --- a/appGUI/PlotCanvas3d.py +++ b/appGUI/PlotCanvas3d.py @@ -62,7 +62,7 @@ class PlotCanvas3d(QtCore.QObject, scene.SceneCanvas): # Parent container self.container = container - settings = QtCore.QSettings("Open Source", "FlatCAM") + settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if settings.contains("theme"): theme = settings.value('theme', type=str) else: @@ -162,7 +162,7 @@ class PlotCanvas3d(QtCore.QObject, scene.SceneCanvas): c_color = self.line_color # font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): fsize = qsettings.value('hud_font_size', type=int) else: diff --git a/appGUI/PlotCanvasLegacy.py b/appGUI/PlotCanvasLegacy.py index 706f1fb3..10fd338c 100644 --- a/appGUI/PlotCanvasLegacy.py +++ b/appGUI/PlotCanvasLegacy.py @@ -82,7 +82,7 @@ class CanvasCache(QtCore.QObject): self.axes.set_xticks([]) self.axes.set_yticks([]) - settings = QtCore.QSettings("Open Source", "FlatCAM") + settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if settings.contains("theme"): theme = settings.value('theme', type=str) else: @@ -463,7 +463,7 @@ class PlotCanvasLegacy(QtCore.QObject): self.on_update_text_hud() # set font size - qsettings = QtCore.QSettings("Open Source", "FlatCAM") + qsettings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): # I multiply with 2.5 because this seems to be the difference between the value taken by the VisPy (3D) # and Matplotlib (Legacy2D FlatCAM graphic engine) diff --git a/appGUI/VisPyCanvas.py b/appGUI/VisPyCanvas.py index ded3db81..a80d2fd2 100644 --- a/appGUI/VisPyCanvas.py +++ b/appGUI/VisPyCanvas.py @@ -30,7 +30,7 @@ class VisPyCanvas(scene.SceneCanvas): self.unfreeze() - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") if settings.contains("axis_font_size"): a_fsize = settings.value('axis_font_size', type=int) else: @@ -102,7 +102,7 @@ class VisPyCanvas(scene.SceneCanvas): # grid1 = scene.GridLines(parent=view.scene, color='dimgray') # grid1.set_gl_state(depth_test=False) - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") if settings.contains("theme"): theme = settings.value('theme', type=str) else: diff --git a/appGUI/preferences/OptionUI.py b/appGUI/preferences/OptionUI.py index be05ee55..3024c6a0 100644 --- a/appGUI/preferences/OptionUI.py +++ b/appGUI/preferences/OptionUI.py @@ -106,7 +106,7 @@ class TextAreaOptionUI(OptionUI): textarea = FCTextArea() textarea.setPlaceholderText(_(self.label_tooltip)) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py index 1d69df04..4b3a307c 100644 --- a/appGUI/preferences/PreferencesUIManager.py +++ b/appGUI/preferences/PreferencesUIManager.py @@ -1126,7 +1126,7 @@ class PreferencesUIManager(QtCore.QObject): self.defaults.current_defaults.update(self.defaults) # deal with appearance change - appearance_settings = QtCore.QSettings("Open Source", "FlatCAM") + appearance_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if appearance_settings.contains("appearance"): appearance = appearance_settings.value('appearance', type=str) else: @@ -1200,7 +1200,7 @@ class PreferencesUIManager(QtCore.QObject): saved_filename_path = os.path.join(self.data_path, 'current_defaults_%s.FlatConfig' % self.defaults.version) self.defaults.load(filename=saved_filename_path, inform=self.inform) - settgs = QSettings("Open Source", "FlatCAM") + settgs = QSettings("Open Source", "FlatCAM_EVO") # save the notebook font size fsize = self.ui.general_pref_form.general_app_set_group.notebook_font_size_spinner.get_value() diff --git a/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py b/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py index e439b7e2..1c99f477 100644 --- a/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobEditorPrefGroupUI.py @@ -32,7 +32,7 @@ class CNCJobEditorPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.param_label) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py b/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py index b38f160e..1dbd9696 100644 --- a/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py +++ b/appGUI/preferences/cncjob/CNCJobOptPrefGroupUI.py @@ -33,7 +33,7 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.export_gcode_label) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appGUI/preferences/general/GeneralAPPSetGroupUI.py b/appGUI/preferences/general/GeneralAPPSetGroupUI.py index 4752e50c..7bd815e1 100644 --- a/appGUI/preferences/general/GeneralAPPSetGroupUI.py +++ b/appGUI/preferences/general/GeneralAPPSetGroupUI.py @@ -23,7 +23,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.decimals = app.decimals self.options = app.options - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -213,7 +213,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.notebook_font_size_spinner.set_range(8, 40) self.notebook_font_size_spinner.setWrapping(True) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("notebook_font_size"): self.notebook_font_size_spinner.set_value(qsettings.value('notebook_font_size', type=int)) else: @@ -232,7 +232,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.axis_font_size_spinner.set_range(0, 40) self.axis_font_size_spinner.setWrapping(True) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("axis_font_size"): self.axis_font_size_spinner.set_value(qsettings.value('axis_font_size', type=int)) else: @@ -252,7 +252,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.textbox_font_size_spinner.set_range(8, 40) self.textbox_font_size_spinner.setWrapping(True) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): self.textbox_font_size_spinner.set_value(qsettings.value('textbox_font_size', type=int)) else: @@ -271,7 +271,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.hud_font_size_spinner.set_range(8, 40) self.hud_font_size_spinner.setWrapping(True) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("hud_font_size"): self.hud_font_size_spinner.set_value(qsettings.value('hud_font_size', type=int)) else: @@ -493,7 +493,7 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): if val: self.app.cursor_color_3D = self.app.options["global_cursor_color"] else: - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: diff --git a/appGUI/preferences/general/GeneralAppPrefGroupUI.py b/appGUI/preferences/general/GeneralAppPrefGroupUI.py index 6dc5d6cd..89492fd7 100644 --- a/appGUI/preferences/general/GeneralAppPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralAppPrefGroupUI.py @@ -226,7 +226,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("Enable display of the splash screen at application startup.") ) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.value("splash_screen"): self.splash_cb.set_value(True) else: @@ -439,7 +439,7 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): @staticmethod def on_splash_changed(state): - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") qsettings.setValue('splash_screen', 1) if state else qsettings.setValue('splash_screen', 0) # This will write the setting to the platform specific storage. diff --git a/appGUI/preferences/general/GeneralAppSettingsGroupUI.py b/appGUI/preferences/general/GeneralAppSettingsGroupUI.py index 785204c2..9a26283b 100644 --- a/appGUI/preferences/general/GeneralAppSettingsGroupUI.py +++ b/appGUI/preferences/general/GeneralAppSettingsGroupUI.py @@ -75,7 +75,7 @@ class GeneralAppSettingsGroupUI(OptionsGroupUI2): self.setTitle(str(_("App Settings"))) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") self.notebook_font_size_field = self.option_dict()["notebook_font_size"].get_field() if qsettings.contains("notebook_font_size"): @@ -293,7 +293,7 @@ class GeneralAppSettingsGroupUI(OptionsGroupUI2): if val: self.app.cursor_color_3D = self.app.options["global_cursor_color"] else: - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: diff --git a/appGUI/preferences/general/GeneralGUIPrefGroupUI.py b/appGUI/preferences/general/GeneralGUIPrefGroupUI.py index af532e2b..08cfe09a 100644 --- a/appGUI/preferences/general/GeneralGUIPrefGroupUI.py +++ b/appGUI/preferences/general/GeneralGUIPrefGroupUI.py @@ -96,7 +96,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.layout_combo, 6, 1) # Set the current index for layout_combo - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("layout"): layout = qsettings.value('layout', type=str) idx = self.layout_combo.findText(layout.capitalize()) @@ -394,7 +394,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): @staticmethod def handle_style(style): # set current style - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") qsettings.setValue('style', str(style)) new_style = QtWidgets.QStyleFactory.keys()[int(style)] diff --git a/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py b/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py index 70fbd118..6334d33c 100644 --- a/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py +++ b/appGUI/preferences/utilities/AutoCompletePrefGroupUI.py @@ -43,7 +43,7 @@ class AutoCompletePrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.grb_list_label) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appGUI/preferences/utilities/FAExcPrefGroupUI.py b/appGUI/preferences/utilities/FAExcPrefGroupUI.py index 674ca6a3..ba363212 100644 --- a/appGUI/preferences/utilities/FAExcPrefGroupUI.py +++ b/appGUI/preferences/utilities/FAExcPrefGroupUI.py @@ -51,7 +51,7 @@ class FAExcPrefGroupUI(OptionsGroupUI): ) self.vertical_lay.addWidget(list_label) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appGUI/preferences/utilities/FAGcoPrefGroupUI.py b/appGUI/preferences/utilities/FAGcoPrefGroupUI.py index eaa99a60..787207c3 100644 --- a/appGUI/preferences/utilities/FAGcoPrefGroupUI.py +++ b/appGUI/preferences/utilities/FAGcoPrefGroupUI.py @@ -42,7 +42,7 @@ class FAGcoPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.gco_list_label) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appGUI/preferences/utilities/FAGrbPrefGroupUI.py b/appGUI/preferences/utilities/FAGrbPrefGroupUI.py index 459bbdae..3f503af1 100644 --- a/appGUI/preferences/utilities/FAGrbPrefGroupUI.py +++ b/appGUI/preferences/utilities/FAGrbPrefGroupUI.py @@ -41,7 +41,7 @@ class FAGrbPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.grb_list_label) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appMain.py b/appMain.py index a0bd1aae..604f456d 100644 --- a/appMain.py +++ b/appMain.py @@ -658,7 +658,7 @@ class App(QtCore.QObject): # ########################################################################################################### if self.options["first_run"] is True: # on first run clear the previous QSettings, therefore clearing the GUI settings - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") for key in qsettings.allKeys(): qsettings.remove(key) # This will write the setting to the platform specific storage. @@ -667,7 +667,7 @@ class App(QtCore.QObject): # ########################################################################################################### # ###################################### Setting the Splash Screen ########################################## # ########################################################################################################### - splash_settings = QSettings("Open Source", "FlatCAM") + splash_settings = QSettings("Open Source", "FlatCAM_EVO") if splash_settings.contains("splash_screen"): show_splash = splash_settings.value("splash_screen") else: @@ -779,7 +779,7 @@ class App(QtCore.QObject): self.FC_light_blue = '#a5a5ffbf' self.FC_dark_blue = '#0000ffbf' - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") theme_settings.setValue("appearance", self.options["global_appearance"]) theme_settings.setValue("theme", self.options["global_theme"]) theme_settings.setValue("dark_canvas", self.options["global_dark_canvas"]) @@ -1300,7 +1300,7 @@ class App(QtCore.QObject): # finish the splash self.splash.finish(self.ui) - mgui_settings = QSettings("Open Source", "FlatCAM") + mgui_settings = QSettings("Open Source", "FlatCAM_EVO") if mgui_settings.contains("maximized_gui"): maximized_ui = mgui_settings.value('maximized_gui', type=bool) if maximized_ui is True: @@ -2124,7 +2124,7 @@ class App(QtCore.QObject): else: current_layout = self.ui.general_pref_form.general_gui_group.layout_combo.get_value() - lay_settings = QSettings("Open Source", "FlatCAM") + lay_settings = QSettings("Open Source", "FlatCAM_EVO") lay_settings.setValue('layout', current_layout) # This will write the setting to the platform specific storage. @@ -3847,7 +3847,7 @@ class App(QtCore.QObject): if self.cmd_line_headless != 1: # save app state to file - stgs = QSettings("Open Source", "FlatCAM") + stgs = QSettings("Open Source", "FlatCAM_EVO") stgs.setValue('saved_gui_state', self.ui.saveState()) stgs.setValue('maximized_gui', self.ui.isMaximized()) stgs.setValue( diff --git a/appObjects/ObjectCollection.py b/appObjects/ObjectCollection.py index 88216cb4..28066f4f 100644 --- a/appObjects/ObjectCollection.py +++ b/appObjects/ObjectCollection.py @@ -351,7 +351,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): # self.view.setAcceptDrops(True) # self.view.setDropIndicatorShown(True) - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") if settings.contains("notebook_font_size"): fsize = settings.value('notebook_font_size', type=int) else: @@ -504,7 +504,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): return index.internalPointer().data(index.column()) if role == Qt.ItemDataRole.ForegroundRole: - theme_settings = QtCore.QSettings("Open Source", "FlatCAM") + theme_settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") theme = theme_settings.value('theme', type=str) if theme == 'dark': diff --git a/appPlugins/ToolDistance.py b/appPlugins/ToolDistance.py index 56c4f898..58a8c3dd 100644 --- a/appPlugins/ToolDistance.py +++ b/appPlugins/ToolDistance.py @@ -781,7 +781,7 @@ class Distance(AppTool): else: meas_line = LineString([start_pos, end_pos]) - settings = QtCore.QSettings("Open Source", "FlatCAM") + settings = QtCore.QSettings("Open Source", "FlatCAM_EVO") if settings.contains("theme"): theme = settings.value('theme', type=str) else: diff --git a/appPlugins/ToolShell.py b/appPlugins/ToolShell.py index 710c3607..63dbe259 100644 --- a/appPlugins/ToolShell.py +++ b/appPlugins/ToolShell.py @@ -44,7 +44,7 @@ class TermWidget(QWidget): self._browser = _BrowserTextEdit(version=version, app=app) - qsettings = QSettings("Open Source", "FlatCAM") + qsettings = QSettings("Open Source", "FlatCAM_EVO") if qsettings.contains("textbox_font_size"): tb_fsize = qsettings.value('textbox_font_size', type=int) else: diff --git a/appTranslation.py b/appTranslation.py index 7024752f..f0805ecb 100644 --- a/appTranslation.py +++ b/appTranslation.py @@ -89,7 +89,7 @@ def on_language_apply_click(app, restart=False): """ name = app.ui.general_pref_form.general_app_group.language_combo.currentText() - theme_settings = QSettings("Open Source", "FlatCAM") + theme_settings = QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: @@ -101,7 +101,7 @@ def on_language_apply_click(app, restart=False): resource_loc = 'assets/resources/dark_resources' # do nothing if trying to apply the language that is the current language (already applied). - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") if settings.contains("language"): current_language = settings.value('language', type=str) if current_language == name: @@ -127,7 +127,7 @@ def on_language_apply_click(app, restart=False): if response == bt_no: return else: - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") saved_language = name settings.setValue('language', saved_language) # This will write the setting to the platform specific storage. @@ -140,7 +140,7 @@ def apply_language(domain, lang=None): lang_code = '' if lang is None: - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") if settings.contains("language"): name = settings.value('language') else: @@ -186,7 +186,7 @@ def restart_program(app, ask=None): """ log.debug("FlatCAMTranslation.restart_program()") - theme_settings = QSettings("Open Source", "FlatCAM") + theme_settings = QSettings("Open Source", "FlatCAM_EVO") if theme_settings.contains("theme"): theme = theme_settings.value('theme', type=str) else: diff --git a/FlatCAM.py b/flatcam.py similarity index 99% rename from FlatCAM.py rename to flatcam.py index 61f7498a..037dda62 100644 --- a/FlatCAM.py +++ b/flatcam.py @@ -155,7 +155,7 @@ if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) # apply style - settings = QSettings("Open Source", "FlatCAM") + settings = QSettings("Open Source", "FlatCAM_EVO") if settings.contains("style"): style_index = settings.value('style', type=str) try: From ccc71eabc2fd9b05dc1e7347cde2d35e4e24c35b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 15 Apr 2023 21:03:30 +0300 Subject: [PATCH 42/55] - changed the shapely imports a bit according to the specifications of Shapely 2.0 - changed the requirements.txt file to reflect the need for at least Shapely in version 2.0 --- CHANGELOG.md | 5 +++++ Utils/vispy_example_qt6_draw.py | 2 +- appCommon/Common.py | 2 +- appEditors/AppExcEditor.py | 2 +- appEditors/AppGeoEditor.py | 2 +- appEditors/AppGerberEditor.py | 2 +- appEditors/geo_plugins/GeoBufferPlugin.py | 2 +- appEditors/geo_plugins/GeoCirclePlugin.py | 2 +- appEditors/geo_plugins/GeoPaintPlugin.py | 2 +- appEditors/geo_plugins/GeoRectanglePlugin.py | 3 ++- appEditors/grb_plugins/GrbCommon.py | 2 +- appGUI/PlotCanvasLegacy.py | 2 +- appGUI/VisPyVisuals.py | 2 +- appMain.py | 2 +- appObjects/AppObjectTemplate.py | 2 +- appObjects/ExcellonObject.py | 2 +- appObjects/GeometryObject.py | 2 +- appObjects/GerberObject.py | 2 +- appParsers/ParseDXF.py | 2 +- appParsers/ParseExcellon.py | 2 +- appParsers/ParseFont.py | 2 +- appParsers/ParseGerber.py | 4 ++-- appParsers/ParseHPGL2.py | 2 +- appParsers/ParsePDF.py | 2 +- appParsers/ParseSVG.py | 2 +- appPlugins/ToolAlignObjects.py | 2 +- appPlugins/ToolCopperThieving.py | 2 +- appPlugins/ToolCutOut.py | 2 +- appPlugins/ToolDblSided.py | 2 +- appPlugins/ToolDistance.py | 2 +- appPlugins/ToolDrilling.py | 2 +- appPlugins/ToolExtract.py | 2 +- appPlugins/ToolFiducials.py | 3 ++- appPlugins/ToolFilm.py | 2 +- appPlugins/ToolFollow.py | 2 +- appPlugins/ToolImage.py | 2 +- appPlugins/ToolInvertGerber.py | 2 +- appPlugins/ToolIsolation.py | 2 +- appPlugins/ToolLevelling.py | 2 +- appPlugins/ToolMarkers.py | 2 +- appPlugins/ToolMilling.py | 2 +- appPlugins/ToolMove.py | 2 +- appPlugins/ToolNCC.py | 3 ++- appPlugins/ToolObjectDistance.py | 2 +- appPlugins/ToolOptimal.py | 2 +- appPlugins/ToolPDF.py | 2 +- appPlugins/ToolPaint.py | 3 ++- appPlugins/ToolPanelize.py | 2 +- appPlugins/ToolPunchGerber.py | 2 +- appPlugins/ToolQRCode.py | 2 +- appPlugins/ToolReport.py | 2 +- appPlugins/ToolRulesCheck.py | 2 +- appPlugins/ToolSolderPaste.py | 2 +- appPlugins/ToolSub.py | 2 +- appTool.py | 2 +- camlib.py | 4 ++-- requirements.txt | 2 +- tclCommands/TclCommandAddDrill.py | 2 +- tclCommands/TclCommandAddSlot.py | 2 +- tclCommands/TclCommandAlignDrill.py | 2 +- tclCommands/TclCommandAlignDrillGrid.py | 2 +- tclCommands/TclCommandCutout.py | 2 +- tclCommands/TclCommandGeoCutout.py | 2 +- tclCommands/TclCommandPanelize.py | 2 +- 64 files changed, 74 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af6f32e5..c068ed0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +15.04.2023 + +- changed the shapely imports a bit according to the specifications of Shapely 2.0 +- changed the requirements.txt file to reflect the need for at least Shapely in version 2.0 + 08.04.2023 - fixed some really long strings in the Geometry Editor toolbar actions diff --git a/Utils/vispy_example_qt6_draw.py b/Utils/vispy_example_qt6_draw.py index a20d4428..47b9612d 100644 --- a/Utils/vispy_example_qt6_draw.py +++ b/Utils/vispy_example_qt6_draw.py @@ -14,7 +14,7 @@ from vispy.visuals import CompoundVisual, MeshVisual, LineVisual from vispy.scene.visuals import VisualNode, generate_docstring, visuals from vispy.gloo import set_state -from shapely.geometry import Polygon, LineString, LinearRing +from shapely import Polygon, LineString, LinearRing import sys diff --git a/appCommon/Common.py b/appCommon/Common.py index 54c1265f..ed51aff7 100644 --- a/appCommon/Common.py +++ b/appCommon/Common.py @@ -12,7 +12,7 @@ # ########################################################## from PyQt6 import QtCore -from shapely.geometry import Polygon, Point, LineString +from shapely import Polygon, Point, LineString from shapely.ops import unary_union from appGUI.VisPyVisuals import ShapeCollection diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 2e48deff..3c563507 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -19,7 +19,7 @@ from appEditors.exc_plugins.ExcCopyPlugin import ExcCopyEditorTool from appGUI.GUIElements import FCEntry, FCTable, FCDoubleSpinner, FCButton, FCLabel, GLay from appEditors.AppGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, AppGeoEditor -from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point +from shapely import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point from shapely.geometry.base import BaseGeometry from shapely.affinity import scale, rotate, translate # from appCommon.Common import LoudDict diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index dcd15f4c..71831c2e 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -33,7 +33,7 @@ from appEditors.geo_plugins.GeoCopyPlugin import CopyEditorTool from vispy.geometry import Rect -from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point, box, base +from shapely import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point, box, base from shapely.ops import unary_union, linemerge from shapely.affinity import translate, scale, skew, rotate from shapely.geometry.polygon import orient diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index c230232a..caafd10a 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -32,7 +32,7 @@ from numpy.linalg import norm as numpy_norm import math from copy import deepcopy -from shapely.geometry import Point, Polygon, MultiPolygon, LineString, LinearRing, box +from shapely import Point, Polygon, MultiPolygon, LineString, LinearRing, box from shapely.ops import unary_union from shapely.affinity import translate, scale, skew, rotate diff --git a/appEditors/geo_plugins/GeoBufferPlugin.py b/appEditors/geo_plugins/GeoBufferPlugin.py index caa2bc8b..6640787c 100644 --- a/appEditors/geo_plugins/GeoBufferPlugin.py +++ b/appEditors/geo_plugins/GeoBufferPlugin.py @@ -3,7 +3,7 @@ from PyQt6 import QtWidgets from appTool import AppToolEditor from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner, FCComboBox -from shapely.geometry import Polygon +from shapely import Polygon import gettext import appTranslation as fcTranslate diff --git a/appEditors/geo_plugins/GeoCirclePlugin.py b/appEditors/geo_plugins/GeoCirclePlugin.py index f162147c..342d3f40 100644 --- a/appEditors/geo_plugins/GeoCirclePlugin.py +++ b/appEditors/geo_plugins/GeoCirclePlugin.py @@ -3,7 +3,7 @@ from PyQt6 import QtWidgets, QtGui from appTool import AppToolEditor from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCDoubleSpinner -from shapely.geometry import Point +from shapely import Point from shapely.affinity import scale, rotate import gettext diff --git a/appEditors/geo_plugins/GeoPaintPlugin.py b/appEditors/geo_plugins/GeoPaintPlugin.py index 9c1fd6d7..b12e2e84 100644 --- a/appEditors/geo_plugins/GeoPaintPlugin.py +++ b/appEditors/geo_plugins/GeoPaintPlugin.py @@ -5,7 +5,7 @@ from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCFrame, FCButton, G FCCheckBox from camlib import Geometry -from shapely.geometry import Polygon +from shapely import Polygon from shapely.ops import unary_union import gettext diff --git a/appEditors/geo_plugins/GeoRectanglePlugin.py b/appEditors/geo_plugins/GeoRectanglePlugin.py index 87bbba08..42ae2590 100644 --- a/appEditors/geo_plugins/GeoRectanglePlugin.py +++ b/appEditors/geo_plugins/GeoRectanglePlugin.py @@ -4,7 +4,8 @@ from appTool import AppToolEditor from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, GLay, FCFrame, FCDoubleSpinner, RadioSetCross, \ RadioSet, NumericalEvalEntry -from shapely.geometry import box, base +from shapely import box +from shapely.geometry import base import gettext import appTranslation as fcTranslate diff --git a/appEditors/grb_plugins/GrbCommon.py b/appEditors/grb_plugins/GrbCommon.py index 9b433ba3..78724841 100644 --- a/appEditors/grb_plugins/GrbCommon.py +++ b/appEditors/grb_plugins/GrbCommon.py @@ -4,7 +4,7 @@ # ########################################################################################### from PyQt6.QtCore import Qt -from shapely.geometry import MultiLineString, Polygon +from shapely import MultiLineString, Polygon import numpy as np diff --git a/appGUI/PlotCanvasLegacy.py b/appGUI/PlotCanvasLegacy.py index 10fd338c..bb052cff 100644 --- a/appGUI/PlotCanvasLegacy.py +++ b/appGUI/PlotCanvasLegacy.py @@ -14,7 +14,7 @@ from PyQt6.QtCore import pyqtSignal # Used for solid polygons in Matplotlib from descartes.patch import PolygonPatch -from shapely.geometry import Polygon, LineString, LinearRing +from shapely import Polygon, LineString, LinearRing from copy import deepcopy diff --git a/appGUI/VisPyVisuals.py b/appGUI/VisPyVisuals.py index 08818141..b2098f78 100644 --- a/appGUI/VisPyVisuals.py +++ b/appGUI/VisPyVisuals.py @@ -10,7 +10,7 @@ from vispy.visuals import CompoundVisual, LineVisual, MeshVisual, TextVisual, Ma from vispy.scene.visuals import VisualNode, generate_docstring, visuals from vispy.gloo import set_state from vispy.color import Color -from shapely.geometry import Polygon, LineString, LinearRing +from shapely import Polygon, LineString, LinearRing import threading import numpy as np from appGUI.VisPyTesselators import GLUTess diff --git a/appMain.py b/appMain.py index 604f456d..dc7e780c 100644 --- a/appMain.py +++ b/appMain.py @@ -35,7 +35,7 @@ import platform import re import subprocess -from shapely.geometry import Point, MultiPolygon, MultiLineString, Polygon, LinearRing, LineString +from shapely import Point, MultiPolygon, MultiLineString, Polygon, LinearRing, LineString from shapely.ops import unary_union from io import StringIO diff --git a/appObjects/AppObjectTemplate.py b/appObjects/AppObjectTemplate.py index 73db2086..32ca0fd9 100644 --- a/appObjects/AppObjectTemplate.py +++ b/appObjects/AppObjectTemplate.py @@ -18,7 +18,7 @@ from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy from appGUI.VisPyVisuals import ShapeCollection from shapely.ops import unary_union -from shapely.geometry import Polygon, MultiPolygon, Point, LineString +from shapely import Polygon, MultiPolygon, Point, LineString from copy import deepcopy, copy import sys diff --git a/appObjects/ExcellonObject.py b/appObjects/ExcellonObject.py index bcba1428..9168ce84 100644 --- a/appObjects/ExcellonObject.py +++ b/appObjects/ExcellonObject.py @@ -21,7 +21,7 @@ import itertools import numpy as np from copy import deepcopy -from shapely.geometry import LineString +from shapely import LineString import gettext import appTranslation as fcTranslate diff --git a/appObjects/GeometryObject.py b/appObjects/GeometryObject.py index 04a72390..084fe9d0 100644 --- a/appObjects/GeometryObject.py +++ b/appObjects/GeometryObject.py @@ -15,7 +15,7 @@ from appObjects.AppObjectTemplate import FlatCAMObj, ObjectDeleted from appGUI.GUIElements import FCCheckBox from appGUI.ObjectUI import GeometryObjectUI -from shapely.geometry import MultiLineString, LinearRing, Polygon, MultiPolygon, LineString +from shapely import MultiLineString, LinearRing, Polygon, MultiPolygon, LineString from shapely.affinity import scale, translate from shapely.ops import unary_union diff --git a/appObjects/GerberObject.py b/appObjects/GerberObject.py index 4e5c038c..f610bdfa 100644 --- a/appObjects/GerberObject.py +++ b/appObjects/GerberObject.py @@ -18,7 +18,7 @@ from appObjects.AppObjectTemplate import FlatCAMObj, ObjectDeleted, ValidationEr from camlib import flatten_shapely_geometry -from shapely.geometry import MultiLineString, LinearRing, MultiPolygon, Polygon, LineString, Point +from shapely import MultiLineString, LinearRing, MultiPolygon, Polygon, LineString, Point from shapely.ops import unary_union import numpy as np diff --git a/appParsers/ParseDXF.py b/appParsers/ParseDXF.py index 9fe62ca8..6b628aeb 100644 --- a/appParsers/ParseDXF.py +++ b/appParsers/ParseDXF.py @@ -8,7 +8,7 @@ from appParsers.ParseDXF_Spline import spline2Polyline, normalize_2 from appParsers.ParseDXF_Spline import Vector as DxfVector -from shapely.geometry import LineString, Point, Polygon +from shapely import LineString, Point, Polygon from shapely.affinity import rotate, translate, scale # from ezdxf.math import Vector as ezdxf_vector from ezdxf.math import Vec3 as ezdxf_vector diff --git a/appParsers/ParseExcellon.py b/appParsers/ParseExcellon.py index 5e660e19..9bf68f1b 100644 --- a/appParsers/ParseExcellon.py +++ b/appParsers/ParseExcellon.py @@ -9,7 +9,7 @@ from camlib import Geometry, grace import shapely.affinity as affinity -from shapely.geometry import Point, LineString, LinearRing, MultiLineString, MultiPolygon +from shapely import Point, LineString, LinearRing, MultiLineString, MultiPolygon import numpy as np import re diff --git a/appParsers/ParseFont.py b/appParsers/ParseFont.py index c0dcb23b..e24e2988 100644 --- a/appParsers/ParseFont.py +++ b/appParsers/ParseFont.py @@ -15,7 +15,7 @@ import os import sys import glob -from shapely.geometry import Polygon, MultiPolygon +from shapely import Polygon, MultiPolygon from shapely.affinity import translate, scale import freetype as ft diff --git a/appParsers/ParseGerber.py b/appParsers/ParseGerber.py index 490c4107..d1a29d1a 100644 --- a/appParsers/ParseGerber.py +++ b/appParsers/ParseGerber.py @@ -11,8 +11,8 @@ from copy import deepcopy from shapely.ops import unary_union, linemerge import shapely.affinity as affinity -from shapely.geometry import box as shply_box -from shapely.geometry import LinearRing, MultiLineString, LineString, Polygon, MultiPolygon, Point +from shapely import box as shply_box +from shapely import LinearRing, MultiLineString, LineString, Polygon, MultiPolygon, Point from lxml import etree as ET import ezdxf diff --git a/appParsers/ParseHPGL2.py b/appParsers/ParseHPGL2.py index 81d1d554..0a93505c 100644 --- a/appParsers/ParseHPGL2.py +++ b/appParsers/ParseHPGL2.py @@ -16,7 +16,7 @@ from copy import deepcopy import sys from shapely.ops import unary_union -from shapely.geometry import LineString, Point +from shapely import LineString, Point import gettext import builtins diff --git a/appParsers/ParsePDF.py b/appParsers/ParsePDF.py index 3450e388..e3d6430e 100644 --- a/appParsers/ParsePDF.py +++ b/appParsers/ParsePDF.py @@ -9,7 +9,7 @@ from PyQt6 import QtCore from appCommon.Common import GracefulException as grace -from shapely.geometry import Polygon, LineString, MultiPolygon +from shapely import Polygon, LineString, MultiPolygon from copy import copy, deepcopy import numpy as np diff --git a/appParsers/ParseSVG.py b/appParsers/ParseSVG.py index c38ddef2..29550d6e 100644 --- a/appParsers/ParseSVG.py +++ b/appParsers/ParseSVG.py @@ -24,7 +24,7 @@ from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path # from svg.path.path import Move # from svg.path.path import Close import svg.path -from shapely.geometry import LineString, MultiLineString, Point +from shapely import LineString, MultiLineString, Point from shapely.affinity import skew, affine_transform, rotate import numpy as np diff --git a/appPlugins/ToolAlignObjects.py b/appPlugins/ToolAlignObjects.py index cfa82c13..61f2fa6c 100644 --- a/appPlugins/ToolAlignObjects.py +++ b/appPlugins/ToolAlignObjects.py @@ -9,7 +9,7 @@ from PyQt6 import QtWidgets, QtGui, QtCore from appTool import AppTool from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, RadioSet -from shapely.geometry import Point +from shapely import Point from shapely.affinity import translate import logging diff --git a/appPlugins/ToolCopperThieving.py b/appPlugins/ToolCopperThieving.py index efa3a4a2..e58b3028 100644 --- a/appPlugins/ToolCopperThieving.py +++ b/appPlugins/ToolCopperThieving.py @@ -20,7 +20,7 @@ import numpy as np from typing import Iterable import shapely.geometry.base as base -from shapely.geometry import Polygon, MultiPolygon, box, Point, LineString +from shapely import Polygon, MultiPolygon, box, Point, LineString from shapely.ops import unary_union from shapely.affinity import translate diff --git a/appPlugins/ToolCutOut.py b/appPlugins/ToolCutOut.py index 4ebc328c..e702b7cf 100644 --- a/appPlugins/ToolCutOut.py +++ b/appPlugins/ToolCutOut.py @@ -18,7 +18,7 @@ import simplejson as json import sys from numpy import Inf -from shapely.geometry import Polygon, MultiPolygon, box, Point, LineString, MultiLineString, LinearRing +from shapely import Polygon, MultiPolygon, box, Point, LineString, MultiLineString, LinearRing from shapely.ops import unary_union, linemerge from shapely.affinity import rotate diff --git a/appPlugins/ToolDblSided.py b/appPlugins/ToolDblSided.py index 5d0e8719..65db329a 100644 --- a/appPlugins/ToolDblSided.py +++ b/appPlugins/ToolDblSided.py @@ -8,7 +8,7 @@ import logging from copy import deepcopy from numpy import Inf -from shapely.geometry import Point +from shapely import Point from shapely.affinity import scale import gettext diff --git a/appPlugins/ToolDistance.py b/appPlugins/ToolDistance.py index 58a8c3dd..8e11f8f0 100644 --- a/appPlugins/ToolDistance.py +++ b/appPlugins/ToolDistance.py @@ -17,7 +17,7 @@ import logging from copy import copy import numpy as np -from shapely.geometry import Polygon, Point, LineString, MultiLineString +from shapely import Polygon, Point, LineString, MultiLineString from shapely.strtree import STRtree import gettext diff --git a/appPlugins/ToolDrilling.py b/appPlugins/ToolDrilling.py index d7ef7ccd..d56ea5f4 100644 --- a/appPlugins/ToolDrilling.py +++ b/appPlugins/ToolDrilling.py @@ -22,7 +22,7 @@ import sys import platform import re -from shapely.geometry import LineString +from shapely import LineString import gettext import appTranslation as fcTranslate diff --git a/appPlugins/ToolExtract.py b/appPlugins/ToolExtract.py index fe4c46d7..ae28bc38 100644 --- a/appPlugins/ToolExtract.py +++ b/appPlugins/ToolExtract.py @@ -13,7 +13,7 @@ from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, G import logging from copy import deepcopy -from shapely.geometry import Polygon, MultiPolygon, Point, box +from shapely import Polygon, MultiPolygon, Point, box import gettext import appTranslation as fcTranslate diff --git a/appPlugins/ToolFiducials.py b/appPlugins/ToolFiducials.py index 957da7ff..bab14589 100644 --- a/appPlugins/ToolFiducials.py +++ b/appPlugins/ToolFiducials.py @@ -15,7 +15,8 @@ import logging from copy import deepcopy import math -from shapely.geometry import LineString, Polygon, MultiPolygon, box, Point, base +from shapely import LineString, Polygon, MultiPolygon, box, Point, base +from shapely.geometry import base from shapely.ops import unary_union import gettext diff --git a/appPlugins/ToolFilm.py b/appPlugins/ToolFilm.py index f467bb7b..39b3d469 100644 --- a/appPlugins/ToolFilm.py +++ b/appPlugins/ToolFilm.py @@ -14,7 +14,7 @@ import logging from copy import deepcopy import math -from shapely.geometry import LineString, MultiPolygon, Point, Polygon, LinearRing +from shapely import LineString, MultiPolygon, Point, Polygon, LinearRing from shapely.affinity import scale, skew from shapely.ops import unary_union diff --git a/appPlugins/ToolFollow.py b/appPlugins/ToolFollow.py index a67f9e8e..292b278c 100644 --- a/appPlugins/ToolFollow.py +++ b/appPlugins/ToolFollow.py @@ -13,7 +13,7 @@ import logging from copy import deepcopy import numpy as np -from shapely.geometry import Polygon +from shapely import Polygon from shapely.ops import unary_union import gettext diff --git a/appPlugins/ToolImage.py b/appPlugins/ToolImage.py index 96756e7e..4d33e2d8 100644 --- a/appPlugins/ToolImage.py +++ b/appPlugins/ToolImage.py @@ -14,7 +14,7 @@ from copy import deepcopy import numpy as np import os -from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, shape +from shapely import LineString, MultiLineString, Polygon, MultiPolygon, shape from shapely.affinity import scale, translate import gettext import appTranslation as fcTranslate diff --git a/appPlugins/ToolInvertGerber.py b/appPlugins/ToolInvertGerber.py index 52e4e4d6..355617b7 100644 --- a/appPlugins/ToolInvertGerber.py +++ b/appPlugins/ToolInvertGerber.py @@ -13,7 +13,7 @@ from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, G import logging from copy import deepcopy -from shapely.geometry import box +from shapely import box import gettext import appTranslation as fcTranslate diff --git a/appPlugins/ToolIsolation.py b/appPlugins/ToolIsolation.py index 3290117c..3a2e3b50 100644 --- a/appPlugins/ToolIsolation.py +++ b/appPlugins/ToolIsolation.py @@ -20,7 +20,7 @@ import simplejson as json import sys import math -from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, Point, LinearRing +from shapely import LineString, MultiLineString, Polygon, MultiPolygon, Point, LinearRing from shapely.ops import unary_union, nearest_points import gettext diff --git a/appPlugins/ToolLevelling.py b/appPlugins/ToolLevelling.py index 8950cff9..4e502e93 100644 --- a/appPlugins/ToolLevelling.py +++ b/appPlugins/ToolLevelling.py @@ -16,7 +16,7 @@ import logging from copy import deepcopy import sys -from shapely.geometry import Point, MultiPoint, MultiPolygon, box +from shapely import Point, MultiPoint, MultiPolygon, box from shapely.ops import unary_union from shapely.affinity import translate from datetime import datetime as dt diff --git a/appPlugins/ToolMarkers.py b/appPlugins/ToolMarkers.py index 1e858668..b415f85c 100644 --- a/appPlugins/ToolMarkers.py +++ b/appPlugins/ToolMarkers.py @@ -13,7 +13,7 @@ from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, G import logging from copy import deepcopy -from shapely.geometry import LineString, Point, MultiPolygon +from shapely import LineString, Point, MultiPolygon from shapely.ops import unary_union import gettext diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index d2c0d90d..aa2b3fa5 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -19,7 +19,7 @@ import sys import math import traceback -from shapely.geometry import LineString, box +from shapely import LineString, box import gettext import appTranslation as fcTranslate diff --git a/appPlugins/ToolMove.py b/appPlugins/ToolMove.py index f513c59a..bce0f424 100644 --- a/appPlugins/ToolMove.py +++ b/appPlugins/ToolMove.py @@ -12,7 +12,7 @@ from appGUI.VisPyVisuals import ShapeCollection import logging from copy import copy -from shapely.geometry import Polygon +from shapely import Polygon import gettext import appTranslation as fcTranslate diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 5941fb1b..765e638f 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -18,7 +18,8 @@ import simplejson as json import sys import traceback -from shapely.geometry import LineString, Polygon, MultiPolygon, MultiLineString, base, LinearRing +from shapely import LineString, Polygon, MultiPolygon, MultiLineString, LinearRing +from shapely.geometry import base from shapely.ops import unary_union, nearest_points import gettext diff --git a/appPlugins/ToolObjectDistance.py b/appPlugins/ToolObjectDistance.py index bd0950af..8633c76f 100644 --- a/appPlugins/ToolObjectDistance.py +++ b/appPlugins/ToolObjectDistance.py @@ -13,7 +13,7 @@ import logging from copy import deepcopy import math -from shapely.geometry import Point, MultiPolygon +from shapely import Point, MultiPolygon from shapely.ops import nearest_points, unary_union import gettext diff --git a/appPlugins/ToolOptimal.py b/appPlugins/ToolOptimal.py index 998114cd..fb46f6ce 100644 --- a/appPlugins/ToolOptimal.py +++ b/appPlugins/ToolOptimal.py @@ -14,7 +14,7 @@ from camlib import grace import logging import numpy as np -from shapely.geometry import MultiPolygon +from shapely import MultiPolygon from shapely.ops import nearest_points import gettext diff --git a/appPlugins/ToolPDF.py b/appPlugins/ToolPDF.py index 765469f5..d26ea143 100644 --- a/appPlugins/ToolPDF.py +++ b/appPlugins/ToolPDF.py @@ -15,7 +15,7 @@ import time import re import traceback -from shapely.geometry import Point, MultiPolygon +from shapely import Point, MultiPolygon from shapely.ops import unary_union import gettext diff --git a/appPlugins/ToolPaint.py b/appPlugins/ToolPaint.py index e2c6a213..bb214bfc 100644 --- a/appPlugins/ToolPaint.py +++ b/appPlugins/ToolPaint.py @@ -22,7 +22,8 @@ import sys import traceback from numpy import Inf -from shapely.geometry import LineString, Polygon, MultiLineString, MultiPolygon, Point, LinearRing, base +from shapely import LineString, Polygon, MultiLineString, MultiPolygon, Point, LinearRing +from shapely.geometry import base from shapely.ops import unary_union, linemerge import gettext diff --git a/appPlugins/ToolPanelize.py b/appPlugins/ToolPanelize.py index fff9a7c6..64fbc55d 100644 --- a/appPlugins/ToolPanelize.py +++ b/appPlugins/ToolPanelize.py @@ -15,7 +15,7 @@ import logging from copy import deepcopy import numpy as np -from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon +from shapely import LineString, MultiLineString, Polygon, MultiPolygon from shapely.ops import unary_union, linemerge, snap from shapely.affinity import translate diff --git a/appPlugins/ToolPunchGerber.py b/appPlugins/ToolPunchGerber.py index e4566241..075a5928 100644 --- a/appPlugins/ToolPunchGerber.py +++ b/appPlugins/ToolPunchGerber.py @@ -15,7 +15,7 @@ from matplotlib.backend_bases import KeyEvent as mpl_key_event import logging from copy import deepcopy -from shapely.geometry import Point, MultiPolygon +from shapely import Point, MultiPolygon from shapely.ops import unary_union import gettext diff --git a/appPlugins/ToolQRCode.py b/appPlugins/ToolQRCode.py index 6f3b39a2..a2020ee0 100644 --- a/appPlugins/ToolQRCode.py +++ b/appPlugins/ToolQRCode.py @@ -18,7 +18,7 @@ from io import StringIO, BytesIO from typing import Iterable import math -from shapely.geometry import MultiPolygon, box, Polygon +from shapely import MultiPolygon, box, Polygon from shapely.ops import unary_union from shapely.affinity import translate, scale import gettext diff --git a/appPlugins/ToolReport.py b/appPlugins/ToolReport.py index 53d16fb2..cd2653d6 100644 --- a/appPlugins/ToolReport.py +++ b/appPlugins/ToolReport.py @@ -12,7 +12,7 @@ import logging from copy import deepcopy import math -from shapely.geometry import MultiPolygon, Polygon, MultiLineString +from shapely import MultiPolygon, Polygon, MultiLineString from shapely.ops import unary_union import gettext diff --git a/appPlugins/ToolRulesCheck.py b/appPlugins/ToolRulesCheck.py index 74200339..7876feb0 100644 --- a/appPlugins/ToolRulesCheck.py +++ b/appPlugins/ToolRulesCheck.py @@ -14,7 +14,7 @@ from appObjects import GerberObject import logging from copy import deepcopy -from shapely.geometry import Polygon, MultiPolygon +from shapely import Polygon, MultiPolygon from shapely.ops import nearest_points import gettext diff --git a/appPlugins/ToolSolderPaste.py b/appPlugins/ToolSolderPaste.py index cacbca3b..e866399d 100644 --- a/appPlugins/ToolSolderPaste.py +++ b/appPlugins/ToolSolderPaste.py @@ -14,7 +14,7 @@ import traceback from copy import deepcopy import re -from shapely.geometry import LineString, MultiLineString, Polygon, MultiPolygon, Point +from shapely import LineString, MultiLineString, Polygon, MultiPolygon, Point from shapely.ops import unary_union from datetime import datetime as dt diff --git a/appPlugins/ToolSub.py b/appPlugins/ToolSub.py index fdda6e53..e651cc82 100644 --- a/appPlugins/ToolSub.py +++ b/appPlugins/ToolSub.py @@ -14,7 +14,7 @@ from copy import deepcopy import time import traceback -from shapely.geometry import LineString, Polygon, MultiPolygon, MultiLineString +from shapely import LineString, Polygon, MultiPolygon, MultiLineString from shapely.ops import unary_union import gettext diff --git a/appTool.py b/appTool.py index aadd8b59..215abcdf 100644 --- a/appTool.py +++ b/appTool.py @@ -7,7 +7,7 @@ # ########################################################## ## from PyQt6 import QtGui, QtWidgets, QtCore -from shapely.geometry import Polygon, LineString +from shapely import Polygon, LineString import gettext import appTranslation as fcTranslate diff --git a/camlib.py b/camlib.py index 892b824d..1a2f3c05 100644 --- a/camlib.py +++ b/camlib.py @@ -31,9 +31,9 @@ from io import StringIO import ezdxf # See: http://toblerity.org/shapely/manual.html -from shapely.geometry import Polygon, Point, LinearRing, MultiPoint, MultiLineString, MultiPolygon, LineString +from shapely import Polygon, Point, LinearRing, MultiPoint, MultiLineString, MultiPolygon, LineString -from shapely.geometry import box as shply_box +from shapely import box as shply_box from shapely.ops import unary_union, substring, linemerge import shapely.affinity as affinity from shapely.affinity import scale, translate diff --git a/requirements.txt b/requirements.txt index a27d4040..5fc81166 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,7 @@ qrcode>=6.1 rtree # foronoi>=1.0.3 -shapely>=1.8.0 +shapely>=2.0 # ############################### # OR-TOOLS package is now optional diff --git a/tclCommands/TclCommandAddDrill.py b/tclCommands/TclCommandAddDrill.py index 2acfddab..1eced020 100644 --- a/tclCommands/TclCommandAddDrill.py +++ b/tclCommands/TclCommandAddDrill.py @@ -1,6 +1,6 @@ from tclCommands.TclCommand import * from copy import deepcopy -from shapely.geometry import Point +from shapely import Point class TclCommandAddDrill(TclCommandSignaled): diff --git a/tclCommands/TclCommandAddSlot.py b/tclCommands/TclCommandAddSlot.py index 4f43e8ab..5c60de44 100644 --- a/tclCommands/TclCommandAddSlot.py +++ b/tclCommands/TclCommandAddSlot.py @@ -1,6 +1,6 @@ from tclCommands.TclCommand import * from copy import deepcopy -from shapely.geometry import Point +from shapely import Point class TclCommandAddSlot(TclCommandSignaled): diff --git a/tclCommands/TclCommandAlignDrill.py b/tclCommands/TclCommandAlignDrill.py index e424dbe6..1fb041e9 100644 --- a/tclCommands/TclCommandAlignDrill.py +++ b/tclCommands/TclCommandAlignDrill.py @@ -1,7 +1,7 @@ import collections from tclCommands.TclCommand import TclCommandSignaled -from shapely.geometry import Point +from shapely import Point import shapely.affinity as affinity from copy import deepcopy diff --git a/tclCommands/TclCommandAlignDrillGrid.py b/tclCommands/TclCommandAlignDrillGrid.py index 6908b10c..e35a71eb 100644 --- a/tclCommands/TclCommandAlignDrillGrid.py +++ b/tclCommands/TclCommandAlignDrillGrid.py @@ -1,7 +1,7 @@ import collections from tclCommands.TclCommand import TclCommandSignaled -from shapely.geometry import Point +from shapely import Point from copy import deepcopy diff --git a/tclCommands/TclCommandCutout.py b/tclCommands/TclCommandCutout.py index c7513113..a9ad85e9 100644 --- a/tclCommands/TclCommandCutout.py +++ b/tclCommands/TclCommandCutout.py @@ -5,7 +5,7 @@ from tclCommands.TclCommand import TclCommand import collections from copy import deepcopy -from shapely.geometry import box +from shapely import box from shapely.ops import linemerge from camlib import flatten_shapely_geometry diff --git a/tclCommands/TclCommandGeoCutout.py b/tclCommands/TclCommandGeoCutout.py index e64ec8bd..134b70f9 100644 --- a/tclCommands/TclCommandGeoCutout.py +++ b/tclCommands/TclCommandGeoCutout.py @@ -4,7 +4,7 @@ import logging import collections from copy import deepcopy from shapely.ops import unary_union -from shapely.geometry import Polygon, LineString, LinearRing, MultiPolygon, MultiLineString +from shapely import Polygon, LineString, LinearRing, MultiPolygon, MultiLineString import gettext import appTranslation as fcTranslate diff --git a/tclCommands/TclCommandPanelize.py b/tclCommands/TclCommandPanelize.py index 6e3f4f58..d9a62854 100644 --- a/tclCommands/TclCommandPanelize.py +++ b/tclCommands/TclCommandPanelize.py @@ -1,7 +1,7 @@ from tclCommands.TclCommand import TclCommand import shapely.affinity as affinity -from shapely.geometry import MultiPolygon, MultiLineString +from shapely import MultiPolygon, MultiLineString import logging from copy import deepcopy From b557df8fdcdb68103f0d91c533c85775b6aa456d Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 15 Apr 2023 21:08:48 +0300 Subject: [PATCH 43/55] - minor fixes --- appEditors/AppGeoEditor.py | 3 ++- appPlugins/ToolFiducials.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index 71831c2e..e18c2fce 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -33,7 +33,8 @@ from appEditors.geo_plugins.GeoCopyPlugin import CopyEditorTool from vispy.geometry import Rect -from shapely import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point, box, base +from shapely import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point, box +from shapely.geometry import base from shapely.ops import unary_union, linemerge from shapely.affinity import translate, scale, skew, rotate from shapely.geometry.polygon import orient diff --git a/appPlugins/ToolFiducials.py b/appPlugins/ToolFiducials.py index bab14589..f5803e0f 100644 --- a/appPlugins/ToolFiducials.py +++ b/appPlugins/ToolFiducials.py @@ -15,7 +15,7 @@ import logging from copy import deepcopy import math -from shapely import LineString, Polygon, MultiPolygon, box, Point, base +from shapely import LineString, Polygon, MultiPolygon, box, Point from shapely.geometry import base from shapely.ops import unary_union From 6a29bcbe7ca39f352f396f83d6d209d03c019192 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 15 Apr 2023 21:29:32 +0300 Subject: [PATCH 44/55] - some code leftovers are commented --- CHANGELOG.md | 1 + appEditors/AppGerberEditor.py | 8 ++++---- appGUI/MainGUI.py | 2 +- appMain.py | 3 +-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c068ed0e..31e6cf16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG for FlatCAM Evo beta - changed the shapely imports a bit according to the specifications of Shapely 2.0 - changed the requirements.txt file to reflect the need for at least Shapely in version 2.0 +- some code leftovers are commented 08.04.2023 diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index caafd10a..ec363717 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -5361,10 +5361,10 @@ class AppGerberEditor(QtCore.QObject): new_grb_name = self.edited_obj_name # if the 'delayed plot' malfunctioned stop the QTimer - try: - self.plot_thread.stop() - except Exception as e: - self.app.log.warning("AppGerberEditor.update_fcgerber() Timer malfunctioned --> %s" % str(e)) + # try: + # self.plot_thread.stop() + # except Exception as e: + # self.app.log.warning("AppGerberEditor.update_fcgerber() Timer malfunctioned --> %s" % str(e)) if "_edit" in self.edited_obj_name: try: diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py index 65cf0fe0..32f66141 100644 --- a/appGUI/MainGUI.py +++ b/appGUI/MainGUI.py @@ -648,7 +648,7 @@ class MainGUI(QtWidgets.QMainWindow): self.menuhelp = self.menu.addMenu(_('Help')) self.menuhelp_manual = self.menuhelp.addAction( QtGui.QIcon(self.app.resource_location + '/globe16.png'), - '%s\t%s' % (_('Online Help'), _('F1'))) + '%s\t%s' % (_('Obsolete Online Help'), _('F1'))) self.menuhelp_bookmarks = self.menuhelp.addMenu( QtGui.QIcon(self.app.resource_location + '/bookmarks16.png'), _('Bookmarks')) diff --git a/appMain.py b/appMain.py index dc7e780c..16550e3c 100644 --- a/appMain.py +++ b/appMain.py @@ -173,9 +173,8 @@ class App(QtCore.QObject): # ############################################################################################################### version = "Unstable" # version = 1.0 - version_date = "2022/4/31" + version_date = "2023/6/31" beta = True - engine = '3D' # current date now From 765d7cc05bbcd3b68eb7cda63dba669d9e80e168 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sat, 15 Apr 2023 22:15:33 +0300 Subject: [PATCH 45/55] - when parsing a new Gerber object, I am "preparing" each geometry added therefore making the Gerber object geometry possible faster to process --- CHANGELOG.md | 1 + appParsers/ParseGerber.py | 45 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e6cf16..e8ca1c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG for FlatCAM Evo beta - changed the shapely imports a bit according to the specifications of Shapely 2.0 - changed the requirements.txt file to reflect the need for at least Shapely in version 2.0 - some code leftovers are commented +- when parsing a new Gerber object, I am "preparing" each geometry added therefore making the Gerber object geometry possible faster to process 08.04.2023 diff --git a/appParsers/ParseGerber.py b/appParsers/ParseGerber.py index d1a29d1a..a075cfbc 100644 --- a/appParsers/ParseGerber.py +++ b/appParsers/ParseGerber.py @@ -12,7 +12,7 @@ from copy import deepcopy from shapely.ops import unary_union, linemerge import shapely.affinity as affinity from shapely import box as shply_box -from shapely import LinearRing, MultiLineString, LineString, Polygon, MultiPolygon, Point +from shapely import LinearRing, MultiLineString, LineString, Polygon, MultiPolygon, Point, prepare, is_prepared from lxml import etree as ET import ezdxf @@ -509,6 +509,7 @@ class Gerber(Geometry): geo_dict = {} geo_f = LineString(path) + prepare(geo_f) if not geo_f.is_empty: follow_buffer.append(geo_f) geo_dict['follow'] = geo_f @@ -517,6 +518,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) if not geo_s.is_empty and geo_s.is_valid: + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -737,8 +739,9 @@ class Gerber(Geometry): if not flash.is_empty: if self.app.options['gerber_simplification']: flash = flash.simplify(s_tol) - poly_buffer.append(flash) + prepare(flash) + poly_buffer.append(flash) if self.is_lpc is True: geo_dict['clear'] = flash else: @@ -788,6 +791,7 @@ class Gerber(Geometry): else: geo_dict = {} geo_f = LineString(path) + prepare(geo_f) if not geo_f.is_empty: follow_buffer.append(geo_f) geo_dict['follow'] = geo_f @@ -797,6 +801,7 @@ class Gerber(Geometry): geo_s = LineString(path).buffer(width / 1.999, int(self.steps_per_circle)) if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) if not geo_s.is_empty: poly_buffer.append(geo_s) @@ -855,6 +860,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: pol = pol.simplify(s_tol) + prepare(pol) poly_buffer.append(pol) if self.is_lpc is True: geo_dict['clear'] = pol @@ -869,6 +875,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: geo_dict['clear'] = geo_s @@ -911,6 +918,7 @@ class Gerber(Geometry): geo_dict = {} if geo_f: if not geo_f.is_empty: + prepare(geo_f) follow_buffer.append(geo_f) geo_dict['follow'] = geo_f if geo_s: @@ -920,6 +928,7 @@ class Gerber(Geometry): if not geo_s.is_valid: print("Not valid: ", line_num) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: geo_dict['clear'] = geo_s @@ -974,7 +983,9 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: pol = pol.simplify(s_tol) + prepare(pol) pol_f = pol.exterior + prepare(pol_f) if not pol_f.is_empty: follow_buffer.append(pol_f) geo_dict['follow'] = pol @@ -995,9 +1006,11 @@ class Gerber(Geometry): region_f = region_s.exterior if not region_f.is_empty: + prepare(region_f) follow_buffer.append(region_f) geo_dict['follow'] = region_f + prepare(region_s) poly_buffer.append(region_s) if self.is_lpc is True: @@ -1014,9 +1027,11 @@ class Gerber(Geometry): region_f = region_s.exterior if not region_f.is_empty: + prepare(region_f) follow_buffer.append(region_f) geo_dict['follow'] = region_f + prepare(region_s) poly_buffer.append(region_s) if self.is_lpc is True: @@ -1111,6 +1126,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: flash = flash.simplify(s_tol) + prepare(flash) poly_buffer.append(flash) if self.is_lpc is True: @@ -1140,6 +1156,7 @@ class Gerber(Geometry): geo_dict = {} geo_f = Point([current_x, current_y]) + prepare(geo_f) follow_buffer.append(geo_f) geo_dict['follow'] = geo_f @@ -1147,6 +1164,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -1244,6 +1262,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -1255,6 +1274,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -1312,6 +1332,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -1322,6 +1343,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -1343,6 +1365,7 @@ class Gerber(Geometry): # this treats the case when we are storing geometry as paths geo_dict = {} geo_flash = Point([linear_x, linear_y]) + prepare(geo_flash) follow_buffer.append(geo_flash) geo_dict['follow'] = geo_flash @@ -1357,6 +1380,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: flash = flash.simplify(s_tol) + prepare(flash) poly_buffer.append(flash) if self.is_lpc is True: @@ -1462,6 +1486,7 @@ class Gerber(Geometry): # this treats the case when we are storing geometry as paths geo_f = LineString(path) if not geo_f.is_empty: + prepare(geo_f) follow_buffer.append(geo_f) geo_dict['follow'] = geo_f @@ -1471,6 +1496,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: buffered = buffered.simplify(s_tol) + prepare(buffered) poly_buffer.append(buffered) if self.is_lpc is True: @@ -1614,6 +1640,7 @@ class Gerber(Geometry): # this treats the case when we are storing geometry as paths geo_f = LineString(path) if not geo_f.is_empty: + prepare(geo_f) follow_buffer.append(geo_f) geo_dict['follow'] = geo_f @@ -1624,6 +1651,7 @@ class Gerber(Geometry): if self.app.options['gerber_simplification']: geo_s = geo_s.simplify(s_tol) + prepare(geo_s) poly_buffer.append(geo_s) if self.is_lpc is True: @@ -1683,6 +1711,10 @@ class Gerber(Geometry): new_poly = new_poly.buffer(0, int(self.steps_per_circle)) self.app.log.warning("Union done.") + # ######################################################################################################### + prepare(new_poly) + # ######################################################################################################### + if current_polarity == 'D': self.app.inform.emit('%s' % _("Gerber processing. Applying Gerber polarity.")) if new_poly.is_valid: @@ -1727,6 +1759,13 @@ class Gerber(Geometry): # flatten the solid geometry self.solid_geometry = flatten_shapely_geometry(self.solid_geometry) + # import time + # start = time.time() + # ######################################################################################################### + prepare(self.solid_geometry) + # ######################################################################################################### + # print(f"Time elapsed: {time.time() - start}; Is prepared? {is_prepared(self.solid_geometry)}") + if self.app.options['gerber_clean_apertures']: # clean the Gerber file of apertures with no geometry for apid, apvalue in list(self.tools.items()): @@ -2013,6 +2052,7 @@ class Gerber(Geometry): } for pol in self.solid_geometry: + prepare(pol) new_el = {'solid': pol, 'follow': LineString(pol.exterior.coords)} self.tools[0]['geometry'].append(new_el) @@ -2070,6 +2110,7 @@ class Gerber(Geometry): flat_geo = list(self.flatten_list(self.solid_geometry)) if flat_geo: self.solid_geometry = unary_union(flat_geo) + prepare(self.solid_geometry) self.follow_geometry = self.solid_geometry else: return "fail" From 412872bc09e6ee8ca017597c3d5316e46615733f Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 5 May 2023 02:40:41 +0300 Subject: [PATCH 46/55] - fixed an erroneous opening for a Gerber file generated by KiCAD 6.0.11, due of a rectangular flash being eliminated by the global unary_union operation --- CHANGELOG.md | 4 ++++ appParsers/ParseGerber.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ca1c81..6335fe23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +05.05.2023 + +- fixed an erroneous opening for a Gerber file generated by KiCAD 6.0.11, due of a rectangular flash being eliminated by the global unary_union operation + 15.04.2023 - changed the shapely imports a bit according to the specifications of Shapely 2.0 diff --git a/appParsers/ParseGerber.py b/appParsers/ParseGerber.py index a075cfbc..20197721 100644 --- a/appParsers/ParseGerber.py +++ b/appParsers/ParseGerber.py @@ -681,7 +681,7 @@ class Gerber(Geometry): # ################################################################ if current_macro is None: # No macro started yet match = self.am1_re.search(gline) - # Start macro if match, else not an AM, carry on. + # Start macro if there is a match, else not an AM, carry on. if match: self.app.log.debug("Starting macro. Line %d: %s" % (line_num, gline)) current_macro = match.group(1) @@ -1806,7 +1806,7 @@ class Gerber(Geometry): maxx = loc[0] + width / 2 miny = loc[1] - height / 2 maxy = loc[1] + height / 2 - return shply_box(minx, miny, maxx, maxy) + return shply_box(minx, miny, maxx, maxy).buffer(0.0000001) if aperture['type'] == 'O': # Obround loc = location.coords[0] From 1d68dea61c082da1cda89d0fe94b4874c26cf5f4 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 5 May 2023 22:31:18 +0300 Subject: [PATCH 47/55] - replaced some type hints with using the Union --- CHANGELOG.md | 1 + appMain.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6335fe23..82832882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 05.05.2023 - fixed an erroneous opening for a Gerber file generated by KiCAD 6.0.11, due of a rectangular flash being eliminated by the global unary_union operation +- replaced some type hints with using the Union 15.04.2023 diff --git a/appMain.py b/appMain.py index 16550e3c..026a2c9f 100644 --- a/appMain.py +++ b/appMain.py @@ -51,6 +51,8 @@ import qdarktheme import qdarktheme.themes.dark.stylesheet as qdarksheet import qdarktheme.themes.light.stylesheet as qlightsheet +from typing import Union + # #################################################################################################################### # ################################### Imports part of FlatCAM ############################################# # #################################################################################################################### @@ -6826,7 +6828,7 @@ class App(QtCore.QObject): return [self._mouse_click_pos[0], self._mouse_click_pos[1]] @mouse_click_pos.setter - def mouse_click_pos(self, m_pos: list[float] | tuple[float]): + def mouse_click_pos(self, m_pos: Union[list[float], tuple[float]]): self._mouse_click_pos = m_pos @property @@ -6834,7 +6836,7 @@ class App(QtCore.QObject): return [self._mouse_pos[0], self._mouse_pos[1]] @mouse_pos.setter - def mouse_pos(self, m_pos: list[float] | tuple[float]): + def mouse_pos(self, m_pos: Union[list[float], tuple[float]]): self._mouse_pos = m_pos def selection_area_handler(self, start_pos, end_pos, sel_type): From 5d274dd522cb97574cb8f6f1d661541bff59f418 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 14 May 2023 21:48:14 +0300 Subject: [PATCH 48/55] - Isolation Plugin, NCC Plugin and Find Optimal Plugin: fixed not finding the optimal tool diameter (the one that will isolate completely a Gerber object) --- CHANGELOG.md | 4 ++++ appPlugins/ToolIsolation.py | 13 +++++++------ appPlugins/ToolNCC.py | 19 +++++++++++-------- appPlugins/ToolOptimal.py | 15 ++++++++------- 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82832882..37ed263c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +14.05.2023 + +- Isolation Plugin, NCC Plugin and Find Optimal Plugin: fixed not finding the optimal tool diameter (the one that will isolate completely a Gerber object) + 05.05.2023 - fixed an erroneous opening for a Gerber file generated by KiCAD 6.0.11, due of a rectangular flash being eliminated by the global unary_union operation diff --git a/appPlugins/ToolIsolation.py b/appPlugins/ToolIsolation.py index 3a2e3b50..4b773054 100644 --- a/appPlugins/ToolIsolation.py +++ b/appPlugins/ToolIsolation.py @@ -29,7 +29,7 @@ import builtins from appParsers.ParseGerber import Gerber from matplotlib.backend_bases import KeyEvent as mpl_key_event -from camlib import grace +from camlib import grace, flatten_shapely_geometry fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: @@ -1142,7 +1142,7 @@ class ToolIsolation(AppTool, Gerber): min_dict = {} idx = 1 - w_geo = total_geo.geoms if isinstance(total_geo, (MultiLineString, MultiPolygon)) else total_geo + w_geo = flatten_shapely_geometry(total_geo) for geo in w_geo: for s_geo in w_geo[idx:]: # minimize the number of distances by not taking into considerations @@ -1271,16 +1271,17 @@ class ToolIsolation(AppTool, Gerber): total_geo = MultiPolygon(total_geo) total_geo = total_geo.buffer(0) + total_geo = flatten_shapely_geometry(total_geo) - if isinstance(total_geo, MultiPolygon): - geo_len = len(total_geo.geoms) - geo_len = (geo_len * (geo_len - 1)) / 2 - elif isinstance(total_geo, Polygon): + geo_len = len(total_geo) + if geo_len == 1: msg = _("The Gerber object has one Polygon as geometry.\n" "There are no distances between geometry elements to be found.") app_obj.inform.emit('[ERROR_NOTCL] %s' % msg) return 'fail' + geo_len = (geo_len * (geo_len - 1)) / 2 + min_dict = {} idx = 1 for geo in total_geo: diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 765e638f..17d76b7e 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -972,6 +972,7 @@ class NonCopperClear(AppTool, Gerber): total_geo = MultiPolygon(total_geo) total_geo = total_geo.buffer(0) + total_geo = flatten_shapely_geometry(total_geo) if isinstance(total_geo, Polygon): msg = ('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n" @@ -979,8 +980,8 @@ class NonCopperClear(AppTool, Gerber): return msg, np.Inf min_dict = {} idx = 1 - for geo in total_geo.geoms: - for s_geo in total_geo.geoms[idx:]: + for geo in total_geo: + for s_geo in total_geo[idx:]: # minimize the number of distances by not taking into considerations # those that are too small dist = geo.distance(s_geo) @@ -1112,20 +1113,21 @@ class NonCopperClear(AppTool, Gerber): total_geo = MultiPolygon(total_geo) total_geo = total_geo.buffer(0) + total_geo = flatten_shapely_geometry(total_geo) - if isinstance(total_geo, MultiPolygon): - geo_len = len(total_geo.geoms) - geo_len = (geo_len * (geo_len - 1)) / 2 - elif isinstance(total_geo, Polygon): + geo_len = len(total_geo) + if geo_len == 1: app_obj.inform.emit('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n" "There are no distances between geometry elements to be found.")) return 'fail' + geo_len = (geo_len * (geo_len - 1)) / 2 + min_dict = {} idx = 1 - for geo in total_geo.geoms: - for s_geo in total_geo.geoms[idx:]: + for geo in total_geo: + for s_geo in total_geo[idx:]: if self.app.abort_flag: # graceful abort requested by the user raise grace @@ -1537,6 +1539,7 @@ class NonCopperClear(AppTool, Gerber): self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name))) return + # Check tool validity if self.ui.valid_cb.get_value() is True: # this is done in another Process self.find_safe_tooldia_multiprocessing() diff --git a/appPlugins/ToolOptimal.py b/appPlugins/ToolOptimal.py index fb46f6ce..586a675a 100644 --- a/appPlugins/ToolOptimal.py +++ b/appPlugins/ToolOptimal.py @@ -9,7 +9,7 @@ from PyQt6 import QtWidgets, QtCore, QtGui from appTool import AppTool from appGUI.GUIElements import VerticalScrollArea, FCLabel, FCButton, FCFrame, GLay, FCComboBox, FCCheckBox, \ FCEntry, FCTextArea, FCSpinner, OptionalHideInputSection -from camlib import grace +from camlib import grace, flatten_shapely_geometry import logging import numpy as np @@ -253,24 +253,25 @@ class ToolOptimal(AppTool): _("Optimal Tool. Creating a buffer for the object geometry.")) total_geo = MultiPolygon(total_geo) total_geo = total_geo.buffer(0) + total_geo = flatten_shapely_geometry(total_geo) - if isinstance(total_geo, MultiPolygon): - geo_len = len(total_geo.geoms) - geo_len = (geo_len * (geo_len - 1)) / 2 - else: + geo_len = len(total_geo) + if geo_len == 1: app_obj.inform.emit('[ERROR_NOTCL] %s' % _("The Gerber object has one Polygon as geometry.\n" "There are no distances between geometry elements to be found.")) return 'fail' + geo_len = (geo_len * (geo_len - 1)) / 2 + app_obj.inform.emit( '%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"), str(geo_len))) plugin_instance.min_dict = {} idx = 1 - for geo in total_geo.geoms: - for s_geo in total_geo.geoms[idx:]: + for geo in total_geo: + for s_geo in total_geo[idx:]: if app_obj.abort_flag: # graceful abort requested by the user raise grace From 6719e2ef8e2c8b6736b4d2ee51724c58f52d249b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Sun, 14 May 2023 22:14:43 +0300 Subject: [PATCH 49/55] - NCC Plugin - minor code refactoring --- CHANGELOG.md | 1 + appObjects/GerberObject.py | 2 +- appPlugins/ToolNCC.py | 33 ++++++++++++++++----------------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ed263c..d076648a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG for FlatCAM Evo beta 14.05.2023 - Isolation Plugin, NCC Plugin and Find Optimal Plugin: fixed not finding the optimal tool diameter (the one that will isolate completely a Gerber object) +- NCC Plugin - minor code refactoring 05.05.2023 diff --git a/appObjects/GerberObject.py b/appObjects/GerberObject.py index f610bdfa..ca8a3abb 100644 --- a/appObjects/GerberObject.py +++ b/appObjects/GerberObject.py @@ -907,7 +907,7 @@ class GerberObject(FlatCAMObj, Gerber): Gerber.convert_units(self, units) - # self.obj_options['isotooldia'] = float(self.obj_options['isotooldia']) * factor + # self.obj_options['isotd_list'] = float(self.obj_options['isotd_list']) * factor # self.obj_options['bboxmargin'] = float(self.obj_options['bboxmargin']) * factor def plot(self, kind=None, **kwargs): diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 17d76b7e..4d523cd6 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -1563,7 +1563,7 @@ class NonCopperClear(AppTool, Gerber): self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) continue - # find out which tools is for isolation and which are for copper clearing + # find out which tools are for isolation and which are for copper clearing for uid_k, uid_v in self.ncc_tools.items(): if round(uid_v['tooldia'], self.decimals) == round(self.tooldia, self.decimals): if uid_v['data']['tools_ncc_operation'] == "iso": @@ -1587,8 +1587,8 @@ class NonCopperClear(AppTool, Gerber): return "Could not retrieve object: %s with error: %s" % (self.bound_obj_name, str(e)) self.clear_copper(ncc_obj=self.ncc_obj, - ncctooldia=self.ncc_dia_list, - isotooldia=self.iso_dia_list, + ncctd_list=self.ncc_dia_list, + isotd_list=self.iso_dia_list, outname=self.o_name, tools_storage=self.ncc_tools) elif self.select_method == 1: # Area Selection @@ -1611,7 +1611,6 @@ class NonCopperClear(AppTool, Gerber): self.area_sel_disconnect_flag = True # disable the "notebook UI" until finished self.app.ui.notebook.setDisabled(True) - elif self.select_method == 2: # Reference Object self.bound_obj_name = self.ui.reference_combo.currentText() # Get source object. @@ -1623,8 +1622,8 @@ class NonCopperClear(AppTool, Gerber): self.clear_copper(ncc_obj=self.ncc_obj, sel_obj=self.bound_obj, - ncctooldia=self.ncc_dia_list, - isotooldia=self.iso_dia_list, + ncctd_list=self.ncc_dia_list, + isotd_list=self.iso_dia_list, outname=self.o_name) # To be called after clicking on the plot. @@ -1741,8 +1740,8 @@ class NonCopperClear(AppTool, Gerber): self.sel_rect = unary_union(self.sel_rect) - self.clear_copper(ncc_obj=self.ncc_obj, sel_obj=self.bound_obj, ncctooldia=self.ncc_dia_list, - isotooldia=self.iso_dia_list, outname=self.o_name) + self.clear_copper(ncc_obj=self.ncc_obj, sel_obj=self.bound_obj, ncctd_list=self.ncc_dia_list, + isotd_list=self.iso_dia_list, outname=self.o_name) self.app.ui.notebook.setDisabled(False) @@ -2254,17 +2253,17 @@ class NonCopperClear(AppTool, Gerber): self.app.inform_shell.emit('%s %s' % (_('Polygon could not be cleared. Location:'), str(coords))) return None - def clear_copper(self, ncc_obj, ncctooldia, isotooldia, sel_obj=None, outname=None, order=None, + def clear_copper(self, ncc_obj, ncctd_list, isotd_list, sel_obj=None, outname=None, order=None, tools_storage=None, run_threaded=True): """ Clear the excess copper from the entire object. :param ncc_obj: ncc cleared object :type ncc_obj: appObjects.GerberObject.GerberObject - :param ncctooldia: a list of diameters of the tools to be used to ncc clear - :type ncctooldia: list - :param isotooldia: a list of diameters of the tools to be used for isolation - :type isotooldia: list + :param ncctd_list: a list of diameters of the tools to be used to ncc clear + :type ncctd_list: list + :param isotd_list: a list of diameters of the tools to be used for isolation + :type isotd_list: list :param sel_obj: :type sel_obj: :param outname: name of the resulting object @@ -2301,7 +2300,7 @@ class NonCopperClear(AppTool, Gerber): prog_plot = True if self.app.options["tools_ncc_plotting"] == 'progressive' else False tools_storage = tools_storage if tools_storage is not None else self.ncc_tools - sorted_clear_tools = ncctooldia + sorted_clear_tools = ncctd_list if not sorted_clear_tools: self.app.inform.emit('[ERROR_NOTCL] %s' % _("There is no copper clearing tool in the selection " @@ -2389,8 +2388,8 @@ class NonCopperClear(AppTool, Gerber): ncc_offset = float(self.ncc_tools[tool_uid]["data"]["tools_ncc_offset_value"]) # Area to clear - result = self.get_tool_empty_area(name=name, ncc_obj=ncc_obj, geo_obj=geo_obj, isotooldia=isotooldia, - ncc_margin=ncc_margin, has_offset=has_offset, ncc_offset=ncc_offset, + result = self.get_tool_empty_area(name=name, ncc_obj=ncc_obj, geo_obj=geo_obj, isotooldia=isotd_list, + ncc_margin=ncc_margin, has_offset=has_offset, ncc_offset=ncc_offset, tools_storage=tools_storage, bounding_box=bbox) area, warning_flag = result @@ -2603,7 +2602,7 @@ class NonCopperClear(AppTool, Gerber): # Area to clear area, warning_flag = self.get_tool_empty_area(name=name, ncc_obj=ncc_obj, geo_obj=geo_obj, - isotooldia=isotooldia, + isotooldia=isotd_list, has_offset=has_offset, ncc_offset=ncc_offset, ncc_margin=ncc_margin, tools_storage=tools_storage, bounding_box=bbox) From dea367b18511bbae1f5f36cc13f9efd9841f7a05 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Tue, 16 May 2023 04:34:32 +0300 Subject: [PATCH 50/55] - NCC Plugin: fixed the Standard method of copper clearing (both normal and with Rest machining) --- CHANGELOG.md | 4 ++ appPlugins/ToolNCC.py | 153 +++++++++++++++++++++--------------------- camlib.py | 101 +++++++++++++++------------- 3 files changed, 135 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d076648a..cdbdbf94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +16.05.2023 + +- NCC Plugin: fixed the Standard method of copper clearing (both normal and with Rest machining) + 14.05.2023 - Isolation Plugin, NCC Plugin and Find Optimal Plugin: fixed not finding the optimal tool diameter (the one that will isolate completely a Gerber object) diff --git a/appPlugins/ToolNCC.py b/appPlugins/ToolNCC.py index 4d523cd6..9ffb3c01 100644 --- a/appPlugins/ToolNCC.py +++ b/appPlugins/ToolNCC.py @@ -2408,87 +2408,74 @@ class NonCopperClear(AppTool, Gerber): old_disp_number = 0 self.app.log.warning("Total number of polygons to be cleared. %s" % str(geo_len)) + tool_empty_area = [] if area.geoms: - if len(area.geoms) > 0: - pol_nr = 0 - for p in area.geoms: + tool_empty_area = flatten_shapely_geometry(area.geoms) + + if tool_empty_area: + pol_nr = 0 + for p in tool_empty_area: + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + + if self.app.abort_flag: + # graceful abort requested by the user + raise grace + + # clean the polygon + p = p.buffer(0.0000001) + p = flatten_shapely_geometry(p) + + poly_failed = 0 + for pol in p: # provide the app with a way to process the GUI events when in a blocking loop - if not run_threaded: - QtWidgets.QApplication.processEvents() + QtWidgets.QApplication.processEvents() - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + if pol is not None and pol.is_valid and isinstance(pol, Polygon): + res = self.clear_polygon_worker(pol=pol, tooldia=tool, + ncc_method=ncc_method, + ncc_overlap=ncc_overlap, + ncc_connect=ncc_connect, + ncc_contour=ncc_contour, + prog_plot=prog_plot) + if res is not None: + cleared_geo += res + else: + poly_failed += 1 + else: + self.app.log.warning( + "Expected geo is a Polygon. Instead got a %s" % str(type(pol))) - # clean the polygon - p = p.buffer(0) + if poly_failed > 0: + app_obj.poly_not_cleared = True - if p is not None and p.is_valid: - poly_failed = 0 - try: - for pol in p: - # provide the app with a way to process the GUI events when in a blocking loop - QtWidgets.QApplication.processEvents() + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + # log.debug("Polygons cleared: %d" % pol_nr) - if pol is not None and isinstance(pol, Polygon): - res = self.clear_polygon_worker(pol=pol, tooldia=tool, - ncc_method=ncc_method, - ncc_overlap=ncc_overlap, - ncc_connect=ncc_connect, - ncc_contour=ncc_contour, - prog_plot=prog_plot) - if res is not None: - cleared_geo += res - else: - poly_failed += 1 - else: - self.app.log.warning( - "Expected geo is a Polygon. Instead got a %s" % str(type(pol))) - except TypeError: - if isinstance(p, Polygon): - res = self.clear_polygon_worker(pol=p, tooldia=tool, - ncc_method=ncc_method, - ncc_overlap=ncc_overlap, - ncc_connect=ncc_connect, - ncc_contour=ncc_contour, - prog_plot=prog_plot) - if res is not None: - cleared_geo += res - else: - poly_failed += 1 - else: - self.app.log.warning( - "Expected geo is a Polygon. Instead got a %s" % str(type(p))) + if old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + old_disp_number = disp_number + # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) - if poly_failed > 0: - app_obj.poly_not_cleared = True + # check if there is a geometry at all in the cleared geometry + if cleared_geo: + formatted_tool = self.app.dec_format(tool, self.decimals) + # find the tooluid associated with the current tool_dia so we know where to add the tool + # solid_geometry + for k, v in tools_storage.items(): + if self.app.dec_format(v['tooldia'], self.decimals) == formatted_tool: + current_uid = int(k) - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - # log.debug("Polygons cleared: %d" % pol_nr) - - if old_disp_number < disp_number <= 100: - self.app.proc_container.update_view_text(' %d%%' % disp_number) - old_disp_number = disp_number - # log.debug("Polygons cleared: %d. Percentage done: %d%%" % (pol_nr, disp_number)) - - # check if there is a geometry at all in the cleared geometry - if cleared_geo: - formatted_tool = self.app.dec_format(tool, self.decimals) - # find the tooluid associated with the current tool_dia so we know where to add the tool - # solid_geometry - for k, v in tools_storage.items(): - if self.app.dec_format(v['tooldia'], self.decimals) == formatted_tool: - current_uid = int(k) - - # add the solid_geometry to the current too in self.paint_tools dictionary - # and then reset the temporary list that stored that solid_geometry - v['solid_geometry'] = deepcopy(cleared_geo) - v['data']['name'] = name - geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) - break - else: - self.app.log.debug("There are no geometries in the cleared polygon.") + # add the solid_geometry to the current too in self.paint_tools dictionary + # and then reset the temporary list that stored that solid_geometry + v['solid_geometry'] = deepcopy(cleared_geo) + v['data']['name'] = name + geo_obj.tools[current_uid] = dict(tools_storage[current_uid]) + break + else: + self.app.log.debug("There are no geometries in the cleared polygon.") # clean the progressive plotted shapes if it was used if self.app.options["tools_ncc_plotting"] == 'progressive': @@ -2656,10 +2643,18 @@ class NonCopperClear(AppTool, Gerber): # store here the geometry generated by clear operation cleared_geo = [] - poly_failed = 0 - if area.geoms and len(area.geoms) > 0: + tool_empty_area = [] + if area.geoms: + tool_empty_area = flatten_shapely_geometry(area.geoms) + + if tool_empty_area: + poly_failed = 0 pol_nr = 0 - for p in area.geoms: + for p in tool_empty_area: + # provide the app with a way to process the GUI events when in a blocking loop + if not run_threaded: + QtWidgets.QApplication.processEvents() + if self.app.abort_flag: # graceful abort requested by the user raise grace @@ -2668,11 +2663,12 @@ class NonCopperClear(AppTool, Gerber): # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() - # speedup the clearing by not trying to clear polygons that is clear they can't be + # speedup the clearing by not trying to clear polygons that is obvious they can't be # cleared with the current tool. this tremendously reduce the clearing time check_dist = -tool / 2 check_buff = p.buffer(check_dist, self.circle_steps) - if not check_buff or check_buff.is_empty: + check_buff = flatten_shapely_geometry(check_buff) + if not check_buff: continue # if self.app.dec_format(float(tool), self.decimals) == 0.15: @@ -2740,6 +2736,7 @@ class NonCopperClear(AppTool, Gerber): new_area = new_area.buffer(0.0000001) area = area.difference(new_area) + area = flatten_shapely_geometry(area) new_area = [pol for pol in area if pol.is_valid and not pol.is_empty] area = MultiPolygon(new_area) diff --git a/camlib.py b/camlib.py index 1a2f3c05..676b5e55 100644 --- a/camlib.py +++ b/camlib.py @@ -1446,10 +1446,8 @@ class Geometry(object): """ # log.debug("camlib.clear_polygon()") - assert type(polygon) == Polygon or type(polygon) == MultiPolygon, \ - "Expected a Polygon or MultiPolygon, got %s" % type(polygon) - # ## The toolpaths + # The toolpaths # Index first and last points in paths def get_pts(o): return [o.coords[0], o.coords[-1]] @@ -1460,58 +1458,71 @@ class Geometry(object): # Can only result in a Polygon or MultiPolygon # NOTE: The resulting polygon can be "empty". current = polygon.buffer((-tooldia / 1.999999), int(steps_per_circle)) - if current.area == 0: - # Otherwise, trying to to insert current.exterior == None - # into the FlatCAMStorage will fail. - # print("Area is None") - return None + current = flatten_shapely_geometry(current) - # current can be a MultiPolygon - try: - for p in current: - geoms.insert(p.exterior) - for i in p.interiors: - geoms.insert(i) + # if current.area == 0: + # # Otherwise, trying to insert current.exterior == None + # # into the FlatCAMStorage will fail. + # # print("Area is None") + # return None - # Not a Multipolygon. Must be a Polygon - except TypeError: - geoms.insert(current.exterior) - for i in current.interiors: + for p in current: + geoms.insert(p.exterior) + for i in p.interiors: geoms.insert(i) - while True: - if self.app.abort_flag: - # graceful abort requested by the user - raise grace + if self.app.abort_flag: + # graceful abort requested by the user + raise grace - # provide the app with a way to process the GUI events when in a blocking loop - QtWidgets.QApplication.processEvents() + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() - # Can only result in a Polygon or MultiPolygon - current = current.buffer(-tooldia * (1 - overlap), int(steps_per_circle)) - if current.area > 0: + for cl_pol in current: + while True: + if self.app.abort_flag: + # graceful abort requested by the user + raise grace - # current can be a MultiPolygon - try: - for p in current.geoms: - geoms.insert(p.exterior) - for i in p.interiors: + # provide the app with a way to process the GUI events when in a blocking loop + QtWidgets.QApplication.processEvents() + + cl_pol = cl_pol.buffer(-tooldia * (1 - overlap), int(steps_per_circle)) + if isinstance(cl_pol, MultiPolygon): + cl_pol = flatten_shapely_geometry(cl_pol) + + added_flag = False + for tiny_pol in cl_pol: + if tiny_pol.area > 0: + added_flag = True + geoms.insert(tiny_pol.exterior) + if prog_plot: + self.plot_temp_shapes(tiny_pol.exterior) + + for i in tiny_pol.interiors: + geoms.insert(i) + if prog_plot: + self.plot_temp_shapes(i) + if added_flag is False: + break + + cl_pol = MultiPolygon(cl_pol) + else: + if cl_pol.area > 0: + geoms.insert(cl_pol.exterior) + if prog_plot: + self.plot_temp_shapes(cl_pol.exterior) + + for i in cl_pol.interiors: geoms.insert(i) if prog_plot: - self.plot_temp_shapes(p) + self.plot_temp_shapes(i) + else: + break - # Not a Multipolygon. Must be a Polygon - except (TypeError, AttributeError): - geoms.insert(current.exterior) - if prog_plot: - self.plot_temp_shapes(current.exterior) - for i in current.interiors: - geoms.insert(i) - if prog_plot: - self.plot_temp_shapes(i) - else: - self.app.log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero") - break + if not geoms.objects: + self.app.log.debug("camlib.Geometry.clear_polygon() --> Current Area is zero") + return if prog_plot: self.temp_shapes.redraw() From 761b61668a06f377f03e8afdf4d220a99bd1ae9b Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 17 May 2023 22:48:39 +0300 Subject: [PATCH 51/55] - Thieving Plugin: fixed errors due of changes in the Shapely module --- CHANGELOG.md | 4 ++ appPlugins/ToolCopperThieving.py | 108 +++++++++++-------------------- 2 files changed, 41 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdbdbf94..fe33626a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +17.05.2023 + +- Thieving Plugin: fixed errors due of changes in the Shapely module + 16.05.2023 - NCC Plugin: fixed the Standard method of copper clearing (both normal and with Rest machining) diff --git a/appPlugins/ToolCopperThieving.py b/appPlugins/ToolCopperThieving.py index e58b3028..9e916377 100644 --- a/appPlugins/ToolCopperThieving.py +++ b/appPlugins/ToolCopperThieving.py @@ -722,7 +722,7 @@ class ToolCopperThieving(AppTool): # ######################################################################################################### # Generate solid filling geometry. Effectively it's a NEGATIVE of the source object # ######################################################################################################### - tool_obj.thief_solid_geometry = bounding_box.difference(clearance_geometry) + tool_obj.thief_solid_geometry = flatten_shapely_geometry(bounding_box.difference(clearance_geometry)) temp_geo = [] try: @@ -803,47 +803,31 @@ class ToolCopperThieving(AppTool): # create a thick polygon-line that surrounds the copper features outline_geometry = [] - try: - for pol in tool_obj.grb_object.solid_geometry: - if tool_obj.app.abort_flag: - # graceful abort requested by the user - raise grace + gerb_geometry = flatten_shapely_geometry(tool_obj.grb_object.solid_geometry) + for pol in gerb_geometry: + if tool_obj.app.abort_flag: + # graceful abort requested by the user + raise grace - outline_geometry.append( - pol.buffer(c_val+half_thick_line, int(int(tool_obj.geo_steps_per_circle) / 4)) - ) - - pol_nr += 1 - disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) - - if old_disp_number < disp_number <= 100: - msg = ' %s ... %d%%' % (_("Buffering"), int(disp_number)) - tool_obj.app.proc_container.update_view_text(msg) - old_disp_number = disp_number - except TypeError: - # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a - # MultiPolygon (not an iterable) outline_geometry.append( - tool_obj.grb_object.solid_geometry.buffer( - c_val+half_thick_line, - int(int(tool_obj.geo_steps_per_circle) / 4) - ) + pol.buffer(c_val + half_thick_line, int(int(tool_obj.geo_steps_per_circle) / 4)) ) + pol_nr += 1 + disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) + + if old_disp_number < disp_number <= 100: + msg = ' %s ... %d%%' % (_("Buffering"), int(disp_number)) + tool_obj.app.proc_container.update_view_text(msg) + old_disp_number = disp_number + tool_obj.app.proc_container.update_view_text(' %s' % _("Buffering")) - outline_geometry = unary_union(outline_geometry) outline_line = [] - try: - for geo_o in outline_geometry: - outline_line.append( - geo_o.exterior.buffer( - half_thick_line, resolution=int(int(tool_obj.geo_steps_per_circle) / 4) - ) - ) - except TypeError: + outline_geometry = flatten_shapely_geometry(unary_union(outline_geometry)) + for geo_o in outline_geometry: outline_line.append( - outline_geometry.exterior.buffer( + geo_o.exterior.buffer( half_thick_line, resolution=int(int(tool_obj.geo_steps_per_circle) / 4) ) ) @@ -888,9 +872,10 @@ class ToolCopperThieving(AppTool): tool_obj.app.proc_container.update_view_text(' %s' % _("Append geometry")) # create a list of the source geometry - geo_list = deepcopy(tool_obj.grb_object.solid_geometry) - if isinstance(tool_obj.grb_object.solid_geometry, MultiPolygon): - geo_list = list(geo_list.geoms) + # geo_list = deepcopy(tool_obj.grb_object.solid_geometry) + # if isinstance(tool_obj.grb_object.solid_geometry, MultiPolygon): + # geo_list = list(geo_list.geoms) + geo_list = flatten_shapely_geometry(tool_obj.grb_object.solid_geometry) # create a new dictionary to hold the source object apertures allowing us to tamper with without altering # the original source object's apertures @@ -903,20 +888,13 @@ class ToolCopperThieving(AppTool): } # add the thieving geometry in the 0 aperture of the new_apertures dict - try: - for poly in tool_obj.thief_solid_geometry: - # append to the new solid geometry - geo_list.append(poly) - - # append into the 0 aperture - geo_elem = {'solid': poly, 'follow': poly.exterior} - new_apertures[0]['geometry'].append(deepcopy(geo_elem)) - except TypeError: + t_geometry = flatten_shapely_geometry(tool_obj.thief_solid_geometry) + for poly in t_geometry: # append to the new solid geometry - geo_list.append(tool_obj.thief_solid_geometry) + geo_list.append(poly) # append into the 0 aperture - geo_elem = {'solid': tool_obj.thief_solid_geometry, 'follow': tool_obj.thief_solid_geometry.exterior} + geo_elem = {'solid': poly, 'follow': poly.exterior} new_apertures[0]['geometry'].append(deepcopy(geo_elem)) # prepare also the solid_geometry for the new object having the thieving geometry @@ -934,7 +912,7 @@ class ToolCopperThieving(AppTool): grb_obj.multigeo = False grb_obj.follow = deepcopy(self.grb_object.follow) grb_obj.tools = new_apertures - grb_obj.solid_geometry = deepcopy(new_solid_geo) + grb_obj.solid_geometry = deepcopy(flatten_shapely_geometry(new_solid_geo)) grb_obj.follow_geometry = deepcopy(self.grb_object.follow_geometry) app_obj.proc_container.update_view_text(' %s' % _("Append source file")) @@ -1039,12 +1017,12 @@ class ToolCopperThieving(AppTool): for geo in geo_list: plated_area += geo.area - thieving_solid_geo = deepcopy(self.thief_solid_geometry) - robber_solid_geo = deepcopy(self.robber_geo) - robber_line = deepcopy(self.robber_line) + thieving_solid_geo = deepcopy(flatten_shapely_geometry(self.thief_solid_geometry)) + robber_solid_geo = deepcopy(flatten_shapely_geometry(self.robber_geo)) + robber_line = deepcopy(flatten_shapely_geometry(self.robber_line)) # store here the chosen follow geometry - new_follow_geo = deepcopy(self.sm_object.follow_geometry) + new_follow_geo = deepcopy(flatten_shapely_geometry(self.sm_object.follow_geometry)) # if we have copper thieving geometry, add it if thieving_solid_geo and geo_choice in [0, 1]: # ['b', 't'] @@ -1059,28 +1037,16 @@ class ToolCopperThieving(AppTool): 'geometry': [] } - try: - for poly in thieving_solid_geo: - poly_b = poly.buffer(ppm_clearance) + for poly in thieving_solid_geo: + poly_b = poly.buffer(ppm_clearance) - # append to the new solid geometry - geo_list.append(poly_b) - - # append into the 0 aperture - geo_elem = { - 'solid': poly_b, - 'follow': poly_b.exterior - } - new_apertures[0]['geometry'].append(deepcopy(geo_elem)) - except TypeError: # append to the new solid geometry - assert isinstance(thieving_solid_geo, Polygon) - geo_list.append(thieving_solid_geo.buffer(ppm_clearance)) + geo_list.append(poly_b) # append into the 0 aperture geo_elem = { - 'solid': thieving_solid_geo.buffer(ppm_clearance), - 'follow': thieving_solid_geo.buffer(ppm_clearance).exterior + 'solid': poly_b, + 'follow': poly_b.exterior } new_apertures[0]['geometry'].append(deepcopy(geo_elem)) @@ -1143,7 +1109,7 @@ class ToolCopperThieving(AppTool): grb_obj.follow = False grb_obj.follow_geometry = deepcopy(new_follow_geo) grb_obj.tools = deepcopy(new_apertures) - grb_obj.solid_geometry = deepcopy(new_solid_geometry) + grb_obj.solid_geometry = deepcopy(flatten_shapely_geometry(new_solid_geometry)) app_obj.proc_container.update_view_text(' %s' % _("Append source file")) # update the source file with the new geometry: From ba3152e5f9968321baeb444d19d4a50b6808baba Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 19 May 2023 02:23:40 +0300 Subject: [PATCH 52/55] - CutOut Plugin - fixed the manual adding of gaps --- CHANGELOG.md | 4 ++++ appPlugins/ToolCutOut.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe33626a..759d99c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ CHANGELOG for FlatCAM Evo beta ================================================= +19.05.2023 + +- CutOut Plugin - fixed the manual adding of gaps + 17.05.2023 - Thieving Plugin: fixed errors due of changes in the Shapely module diff --git a/appPlugins/ToolCutOut.py b/appPlugins/ToolCutOut.py index e702b7cf..7c37041a 100644 --- a/appPlugins/ToolCutOut.py +++ b/appPlugins/ToolCutOut.py @@ -1669,7 +1669,7 @@ class CutOut(AppTool): self.mb_manual_cuts.append(rests_geo) # first subtract geometry for the total solid_geometry - new_solid_geometry = CutOut.subtract_geo(self.man_cutout_obj.solid_geometry, cut_poly) + new_solid_geometry = self.subtract_geo(self.man_cutout_obj.solid_geometry, cut_poly) try: new_solid_geometry = linemerge(new_solid_geometry) except ValueError: @@ -2176,7 +2176,7 @@ class CutOut(AppTool): Results are placed in self.flat_geometry - :param geometry: Shapely type or list or list of list of such. + :param geometry: Shapely type or list or a list of lists of such. """ flat_geo = [] work_geo = geometry.geoms if isinstance(geometry, (MultiPolygon, MultiLineString)) else geometry From a49f54d63fedc56117a4755e0b74183799bcf675 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 19 May 2023 13:09:08 +0300 Subject: [PATCH 53/55] - the selection shapes are now moved from Move Plugin to AppMain - Fiducials Plugin: fixed errors due of changes in the Shapely module - Fiducials Plugin: fixed an error where in Basic mode the soldermask opening is added incorrectly --- CHANGELOG.md | 3 +++ appGUI/VisPyVisuals.py | 3 ++- appMain.py | 43 ++++++++++++++++++++++++------------- appPlugins/ToolDrilling.py | 5 ++--- appPlugins/ToolFiducials.py | 37 ++++++++++++++++--------------- appPlugins/ToolMarkers.py | 2 +- appPlugins/ToolMilling.py | 5 ++--- appPlugins/ToolMove.py | 7 +----- appPlugins/ToolQRCode.py | 2 +- appTool.py | 4 ++-- 10 files changed, 60 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 759d99c2..7b04c43e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ CHANGELOG for FlatCAM Evo beta 19.05.2023 - CutOut Plugin - fixed the manual adding of gaps +- the selection shapes are now moved from Move Plugin to AppMain +- Fiducials Plugin: fixed errors due of changes in the Shapely module +- Fiducials Plugin: fixed an error where in Basic mode the soldermask opening is added incorrectly 17.05.2023 diff --git a/appGUI/VisPyVisuals.py b/appGUI/VisPyVisuals.py index b2098f78..4b58b564 100644 --- a/appGUI/VisPyVisuals.py +++ b/appGUI/VisPyVisuals.py @@ -360,7 +360,8 @@ class ShapeCollectionVisual(CompoundVisual): self.results_lock.release() # Remove data - del self.data[key] + if key in self.data: + del self.data[key] if update: self.__update() diff --git a/appMain.py b/appMain.py index 026a2c9f..731c9c48 100644 --- a/appMain.py +++ b/appMain.py @@ -930,9 +930,13 @@ class App(QtCore.QObject): # add he PlotCanvas setup to the UI self.on_plotcanvas_add(self.plotcanvas, self.ui.right_layout) + # ############################################################################################################# + # ################ SHAPES STORAGE ######################################################################### + # ############################################################################################################# + # Storage for shapes, storage that can be used by FlatCAm tools for utility geometry - # VisPy visuals if self.use_3d_engine: + # VisPy visuals try: self.tool_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1, pool=self.pool) except AttributeError: @@ -940,6 +944,9 @@ class App(QtCore.QObject): # Storage for Hover Shapes self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1, pool=self.pool) + + # Storage for Selection shapes + self.sel_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1, pool=self.pool) else: from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy self.tool_shapes = ShapeCollectionLegacy(obj=self, app=self, name="tool") @@ -947,6 +954,10 @@ class App(QtCore.QObject): # Storage for Hover Shapes will use the default Matplotlib axes self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover') + # Storage for Selection shapes + self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="selection") + # ############################################################################################################# + end_plot_time = time.time() self.used_time = end_plot_time - start_plot_time self.log.debug("Finished Canvas initialization in %s seconds." % str(self.used_time)) @@ -6678,6 +6689,7 @@ class App(QtCore.QObject): ctrl_modifier_key = QtCore.Qt.KeyboardModifier.ControlModifier ctrl_shift_modifier_key = ctrl_modifier_key | shift_modifier_key + # this will do click release action for the Plugins if key_modifier == shift_modifier_key or key_modifier == ctrl_shift_modifier_key: self.on_mouse_and_key_modifiers(position=self.mouse_click_pos, modifiers=key_modifier) self.on_plugin_mouse_click_release(pos=pos) @@ -6691,6 +6703,7 @@ class App(QtCore.QObject): self.mouse_click_pos = [pos[0], pos[1]] return + # it was a double click if self.doubleclick is True: self.doubleclick = False if self.collection.get_selected(): @@ -6712,7 +6725,11 @@ class App(QtCore.QObject): if self.dx == 0 or self.dy == 0: self.selection_type = None + # End moving selection if self.selection_type is not None: + # delete previous selection shape + self.delete_selection_shape() + try: self.selection_area_handler(self.mouse_click_pos, pos, self.selection_type) self.selection_type = None @@ -6848,11 +6865,11 @@ class App(QtCore.QObject): :param sel_type: if True it's a left to right selection (enclosure), if False it's a 'touch' selection :return: None """ - poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) - # delete previous selection shape self.delete_selection_shape() + poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) + # make all objects inactive self.collection.set_all_inactive() @@ -7163,8 +7180,8 @@ class App(QtCore.QObject): self.hover_shapes.redraw() def delete_selection_shape(self): - self.move_tool.sel_shapes.clear() - self.move_tool.sel_shapes.redraw() + self.sel_shapes.clear() + self.sel_shapes.redraw() def draw_selection_shape(self, sel_obj, color=None): """ @@ -7219,14 +7236,11 @@ class App(QtCore.QObject): face = self.options['global_sel_fill'][:-2] + str(hex(int(0.4 * 255)))[2:] outline = self.options['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:] - self.sel_objects_list.append(self.move_tool.sel_shapes.add(b_sel_rect, - color=outline, - face_color=face, - update=True, - layer=0, - tolerance=None)) + self.sel_objects_list.append( + self.sel_shapes.add(b_sel_rect,color=outline, face_color=face, update=True, layer=0, tolerance=None) + ) if self.use_3d_engine is False: - self.move_tool.sel_shapes.redraw() + self.sel_shapes.redraw() def draw_moving_selection_shape(self, old_coords, coords, **kwargs): """ @@ -7270,10 +7284,9 @@ class App(QtCore.QObject): color_t = face_color[:-2] + str(hex(int(face_alpha * 255)))[2:] - self.move_tool.sel_shapes.add(sel_rect, color=color, face_color=color_t, update=True, - layer=0, tolerance=None) + self.sel_shapes.add(sel_rect, color=color, face_color=color_t, update=True, layer=0, tolerance=None) if self.use_3d_engine is False: - self.move_tool.sel_shapes.redraw() + self.sel_shapes.redraw() def obj_properties(self): """ diff --git a/appPlugins/ToolDrilling.py b/appPlugins/ToolDrilling.py index d56ea5f4..c9353fc3 100644 --- a/appPlugins/ToolDrilling.py +++ b/appPlugins/ToolDrilling.py @@ -1754,10 +1754,9 @@ class ToolDrilling(AppTool, Excellon): for row in sel_rows: sel_rect = self.app.exc_areas.exclusion_areas_storage[row]['shape'] - self.app.move_tool.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0, - tolerance=None) + self.app.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0, tolerance=None) if self.app.use_3d_engine: - self.app.move_tool.sel_shapes.redraw() + self.app.sel_shapes.redraw() def clear_selection(self): self.app.delete_selection_shape() diff --git a/appPlugins/ToolFiducials.py b/appPlugins/ToolFiducials.py index f5803e0f..f7ce5f7b 100644 --- a/appPlugins/ToolFiducials.py +++ b/appPlugins/ToolFiducials.py @@ -23,6 +23,8 @@ import gettext import appTranslation as fcTranslate import builtins +from camlib import flatten_shapely_geometry + fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext @@ -84,7 +86,7 @@ class ToolFiducials(AppTool): self.handlers_connected = False # storage for temporary shapes when adding manual markers - self.temp_shapes = self.app.move_tool.sel_shapes + self.temp_shapes = self.app.sel_shapes def run(self, toggle=True): self.app.defaults.report_usage("ToolFiducials()") @@ -402,6 +404,8 @@ class ToolFiducials(AppTool): """ fid_size = self.ui.fid_size_entry.get_value() if fid_size is None else fid_size fid_type = 0 if fid_type is None else fid_type # default is 'circular' <=> 0 + if fid_type == "circular": + fid_type = 0 line_thickness = self.ui.line_thickness_entry.get_value() if line_size is None else line_size radius = fid_size / 2.0 @@ -439,12 +443,10 @@ class ToolFiducials(AppTool): new_apertures[new_apid]['geometry'].append(deepcopy(dict_el)) s_list = [] - if g_obj.solid_geometry: - try: - for poly in g_obj.solid_geometry: - s_list.append(poly) - except TypeError: - s_list.append(g_obj.solid_geometry) + flat_geo = flatten_shapely_geometry(g_obj.solid_geometry) + if flat_geo: + for poly in flat_geo: + s_list.append(poly) s_list += geo_list elif fid_type == 1: # 'cross' @@ -504,15 +506,14 @@ class ToolFiducials(AppTool): new_apertures[new_apid]['geometry'].append(deepcopy(dict_el)) s_list = [] - if g_obj.solid_geometry: - try: - for poly in g_obj.solid_geometry: - s_list.append(poly) - except TypeError: - s_list.append(g_obj.solid_geometry) + flat_geo = flatten_shapely_geometry(g_obj.solid_geometry) + if flat_geo: + for poly in flat_geo: + s_list.append(poly) geo_buff_list = MultiPolygon(geo_buff_list) geo_buff_list = geo_buff_list.buffer(0) + geo_buff_list = flatten_shapely_geometry(geo_buff_list) for poly in geo_buff_list: s_list.append(poly) else: @@ -583,12 +584,10 @@ class ToolFiducials(AppTool): new_apertures[new_apid]['geometry'].append(deepcopy(dict_el)) s_list = [] - if g_obj.solid_geometry: - try: - for poly in g_obj.solid_geometry: - s_list.append(poly) - except TypeError: - s_list.append(g_obj.solid_geometry) + flat_geo = flatten_shapely_geometry(g_obj.solid_geometry) + if flat_geo: + for poly in flat_geo: + s_list.append(poly) for poly in geo_buff_list: s_list.append(poly) diff --git a/appPlugins/ToolMarkers.py b/appPlugins/ToolMarkers.py index b415f85c..bf480c7a 100644 --- a/appPlugins/ToolMarkers.py +++ b/appPlugins/ToolMarkers.py @@ -71,7 +71,7 @@ class ToolMarkers(AppTool): self.handlers_connected = False # storage for temporary shapes when adding manual markers - self.temp_shapes = self.app.move_tool.sel_shapes + self.temp_shapes = self.app.sel_shapes def on_insert_type_changed(self, val): obj_type = 2 if val == 'geo' else 0 diff --git a/appPlugins/ToolMilling.py b/appPlugins/ToolMilling.py index aa2b3fa5..fcba89a2 100644 --- a/appPlugins/ToolMilling.py +++ b/appPlugins/ToolMilling.py @@ -3829,10 +3829,9 @@ class ToolMilling(AppTool, Excellon): for row in sel_rows: sel_rect = self.app.exc_areas.exclusion_areas_storage[row]['shape'] - self.app.move_tool.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0, - tolerance=None) + self.app.sel_shapes.add(sel_rect, color=outline, face_color=face, update=True, layer=0, tolerance=None) if self.app.use_3d_engine: - self.app.move_tool.sel_shapes.redraw() + self.app.sel_shapes.redraw() def clear_selection(self): self.app.delete_selection_shape() diff --git a/appPlugins/ToolMove.py b/appPlugins/ToolMove.py index bce0f424..388f7c92 100644 --- a/appPlugins/ToolMove.py +++ b/appPlugins/ToolMove.py @@ -49,12 +49,7 @@ class ToolMove(AppTool): self.sel_rect = None self.old_coords = [] - # VisPy visuals - if self.app.use_3d_engine: - self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1, pool=self.app.pool) - else: - from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy - self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="move") + self.sel_shapes = self.app.sel_shapes self.mm = None self.mp = None diff --git a/appPlugins/ToolQRCode.py b/appPlugins/ToolQRCode.py index a2020ee0..08c0c5ce 100644 --- a/appPlugins/ToolQRCode.py +++ b/appPlugins/ToolQRCode.py @@ -67,7 +67,7 @@ class QRCode(AppTool): self.mr = None self.kr = None - self.shapes = self.app.move_tool.sel_shapes + self.shapes = self.app.sel_shapes self.qrcode_geometry = MultiPolygon() self.qrcode_utility_geometry = MultiPolygon() diff --git a/appTool.py b/appTool.py index 215abcdf..fbb19da6 100644 --- a/appTool.py +++ b/appTool.py @@ -236,7 +236,7 @@ class AppTool(QtWidgets.QWidget): if 'shapes_storage' in kwargs: s_storage = kwargs['shapes_storage'] else: - s_storage = self.app.move_tool.sel_shapes + s_storage = self.app.sel_shapes if 'color' in kwargs: color = kwargs['color'] @@ -289,7 +289,7 @@ class AppTool(QtWidgets.QWidget): if 'shapes_storage' in kwargs: s_storage = kwargs['shapes_storage'] else: - s_storage = self.app.move_tool.sel_shapes + s_storage = self.app.sel_shapes s_storage.clear() s_storage.redraw() From e5dd2945760a31ffb3ce65c43db86e8a24672638 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Fri, 19 May 2023 18:18:38 +0300 Subject: [PATCH 54/55] - Film Tool: fixed the export to PNG to be done at correct DPI --- CHANGELOG.md | 1 + appPlugins/ToolFilm.py | 47 ++++++++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b04c43e..b040e816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG for FlatCAM Evo beta - the selection shapes are now moved from Move Plugin to AppMain - Fiducials Plugin: fixed errors due of changes in the Shapely module - Fiducials Plugin: fixed an error where in Basic mode the soldermask opening is added incorrectly +- Film Tool: fixed the export to PNG to be done at correct DPI 17.05.2023 diff --git a/appPlugins/ToolFilm.py b/appPlugins/ToolFilm.py index 39b3d469..57455c13 100644 --- a/appPlugins/ToolFilm.py +++ b/appPlugins/ToolFilm.py @@ -655,7 +655,8 @@ class Film(AppTool): color = obj.obj_options['tools_film_color'] transparency_level = opacity_val - def make_negative_film(color, transparency_level, scale_factor_x, scale_factor_y, use_convex_hull, rounded_box): + def make_negative_film(color, transparency_level, scale_factor_x, scale_factor_y, use_convex_hull, + rounded_box, scale_type): self.app.log.debug("FilmTool.export_negative_handler().make_negative_film()") self.screen_dpi = self.app.qapp.screens()[0].logicalDotsPerInch() @@ -664,8 +665,13 @@ class Film(AppTool): dpi_rate = new_png_dpi / self.screen_dpi if dpi_rate != 1 and ftype == 'png': - scale_factor_x += dpi_rate - scale_factor_y += dpi_rate + if scale_factor_x is None: + scale_factor_x = 1 + if scale_factor_y is None: + scale_factor_y = 1 + scale_factor_x *= dpi_rate + scale_factor_y *= dpi_rate + scale_type = 1 transformed_box_geo = self.transform_geometry(box_obj, scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, @@ -689,10 +695,12 @@ class Film(AppTool): doc_final = self.create_negative_svg(svg_geo=exported_svg, box_bounds=bounds, r_box=rounded_box, box_geo=transformed_box_geo, c_hull=use_convex_hull, margin=boundary, color=color, opacity=transparency_level, svg_units=svg_units) - + # with open("d://a.svg", 'w') as f: + # f.write(doc_final) obj_bounds = obj.bounds() ret = self.write_output_file(content2save=doc_final, filename=filename, file_type=ftype, p_size=p_size, - orientation=orientation, source_bounds=obj_bounds, box_bounds=bounds) + orientation=orientation, source_bounds=obj_bounds, box_bounds=bounds, + dpi=self.screen_dpi) if ret == 'fail': return 'fail' @@ -708,7 +716,8 @@ class Film(AppTool): try: make_negative_film(color=color, transparency_level=transparency_level, scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, - use_convex_hull=use_convex_hull, rounded_box=rounded_box) + use_convex_hull=use_convex_hull, rounded_box=rounded_box, + scale_type=scale_type) except Exception as e: self.app.log.error("export_negative_handler() process -> %s" % str(e)) return @@ -716,7 +725,7 @@ class Film(AppTool): self.app.worker_task.emit({'fcn': job_thread_film, 'params': []}) else: make_negative_film(scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, - use_convex_hull=use_convex_hull, rounded_box=rounded_box) + use_convex_hull=use_convex_hull, rounded_box=rounded_box, scale_type=scale_type) def create_negative_svg(self, svg_geo, box_bounds, r_box, box_geo, c_hull, margin, color, opacity, svg_units): # Change the attributes of the exported SVG @@ -882,7 +891,7 @@ class Film(AppTool): color = obj.obj_options['tools_film_color'] transparency_level = opacity_val - def make_positive_film(color, transparency_level, scale_factor_x, scale_factor_y): + def make_positive_film(color, transparency_level, scale_factor_x, scale_factor_y, scale_type): self.app.log.debug("FilmTool.export_positive_handler().make_positive_film()") self.screen_dpi = self.app.qapp.screens()[0].logicalDotsPerInch() @@ -891,8 +900,13 @@ class Film(AppTool): dpi_rate = new_png_dpi / self.screen_dpi if dpi_rate != 1 and ftype == 'png': - scale_factor_x += dpi_rate - scale_factor_y += dpi_rate + if scale_factor_x is None: + scale_factor_x = 1 + if scale_factor_y is None: + scale_factor_y = 1 + scale_factor_x *= dpi_rate + scale_factor_y *= dpi_rate + scale_type = 1 transformed_box_geo = self.transform_geometry(box_obj, scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, @@ -920,7 +934,8 @@ class Film(AppTool): obj_bounds = obj.bounds() ret = self.write_output_file(content2save=doc_final, filename=filename, file_type=ftype, p_size=p_size, - orientation=orientation, source_bounds=obj_bounds, box_bounds=bounds) + orientation=orientation, source_bounds=obj_bounds, box_bounds=bounds, + dpi=self.screen_dpi) if ret == 'fail': return 'fail' @@ -935,7 +950,8 @@ class Film(AppTool): with self.app.proc_container.new(_("Working...")): try: make_positive_film(color=color, transparency_level=transparency_level, - scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y) + scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, + scale_type=scale_type) except Exception as e: self.app.log.error("export_positive_handler() process -> %s" % str(e)) return @@ -943,7 +959,7 @@ class Film(AppTool): self.app.worker_task.emit({'fcn': job_thread_film, 'params': []}) else: make_positive_film(color=color, transparency_level=transparency_level, - scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y) + scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y, scale_type=scale_type) def create_positive_svg(self, svg_geo, box_bounds, margin, color, opacity, svg_units): # Change the attributes of the exported SVG @@ -993,7 +1009,8 @@ class Film(AppTool): doc = parse_xml_string(svg_elem) return doc.toprettyxml() - def write_output_file(self, content2save, filename, file_type, p_size, orientation, source_bounds, box_bounds): + def write_output_file(self, content2save, filename, file_type, p_size, orientation, source_bounds, box_bounds, + dpi=72): p_msg = '[ERROR_NOTCL] %s' % _("Permission denied, saving not possible.\n" "Most likely another app is holding the file open and not accessible.") if file_type == 'svg': @@ -1007,7 +1024,7 @@ class Film(AppTool): try: doc_final = StringIO(content2save) drawing = svg2rlg(doc_final) - renderPM.drawToFile(drawing, filename, 'PNG') + renderPM.drawToFile(drawing, fn=filename, fmt='PNG', dpi=dpi) except PermissionError: self.app.inform.emit(p_msg) return 'fail' From 7fda555fc0f1e38f5d634f3665348bcdb1740a11 Mon Sep 17 00:00:00 2001 From: Marius Stanciu Date: Wed, 24 May 2023 17:46:25 +0300 Subject: [PATCH 55/55] - for all Editors now there is a shortcut key (key "C") that will toggle the cursor data in case the cursor data is in the way - Geometry Editor: the Path tool now has a new behavior: when key modifier SHIFT is pressed and the Grid is OFF, when the line is drawn and it is near the 0, 45 and 90 degrees, in each quadrant, then the drawn line will follow the nearest target angle (0 or 45 or 90 degrees) allowing the drawing of straight lines. --- CHANGELOG.md | 5 ++ appEditors/AppExcEditor.py | 57 +++++++++++++++ appEditors/AppGeoEditor.py | 129 +++++++++++++++++++++++++++++++++- appEditors/AppGerberEditor.py | 36 ++++++++++ 4 files changed, 224 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b040e816..043a1a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ CHANGELOG for FlatCAM Evo beta ================================================= +24.05.2023 + +- for all Editors now there is a shortcut key (key "C") that will toggle the cursor data in case the cursor data is in the way +- Geometry Editor: the Path tool now has a new behavior: when key modifier SHIFT is pressed and the Grid is OFF, when the line is drawn and it is near the 0, 45 and 90 degrees, in each quadrant, then the drawn line will follow the nearest target angle (0 or 45 or 90 degrees) allowing the drawing of straight lines. + 19.05.2023 - CutOut Plugin - fixed the manual adding of gaps diff --git a/appEditors/AppExcEditor.py b/appEditors/AppExcEditor.py index 3c563507..18a9e050 100644 --- a/appEditors/AppExcEditor.py +++ b/appEditors/AppExcEditor.py @@ -245,6 +245,8 @@ class DrillAdd(FCShapeTool): self.draw_app = draw_app self.app = self.draw_app.app + self.cursor_data_control = True + self.selected_dia = None try: self.draw_app.app.inform.emit(_("Click to place ...")) @@ -372,6 +374,10 @@ class DrillAdd(FCShapeTool): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -469,6 +475,9 @@ class DrillAdd(FCShapeTool): self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) self.add_drill(drill_pos=(new_x, new_y)) + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + def add_drill(self, drill_pos): curr_pos = self.draw_app.app.geo_editor.snap(drill_pos[0], drill_pos[1]) self.draw_app.snap_x = curr_pos[0] @@ -533,6 +542,8 @@ class DrillArray(FCShapeTool): self.pt = [] + self.cursor_data_control = True + try: self.draw_app.app.inform.emit(_("Click to place ...")) self.selected_dia = self.draw_app.tool2tooldia[self.draw_app.last_tool_selected] @@ -836,6 +847,10 @@ class DrillArray(FCShapeTool): pass def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -908,6 +923,9 @@ class DrillArray(FCShapeTool): # ## Utility geometry (animated) self.draw_app.update_utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -972,6 +990,9 @@ class SlotAdd(FCShapeTool): self.half_width = 0.0 self.selected_dia = None + + self.cursor_data_control = True + try: self.draw_app.app.inform.emit(_("Click to place ...")) self.selected_dia = self.draw_app.tool2tooldia[self.draw_app.last_tool_selected] @@ -1174,6 +1195,10 @@ class SlotAdd(FCShapeTool): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1283,6 +1308,9 @@ class SlotAdd(FCShapeTool): self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) self.add_slot(slot_pos=(new_x, new_y)) + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + def add_slot(self, slot_pos): curr_pos = self.draw_app.app.geo_editor.snap(slot_pos[0], slot_pos[1]) self.draw_app.snap_x = curr_pos[0] @@ -1353,6 +1381,8 @@ class SlotArray(FCShapeTool): self.pt = [] + self.cursor_data_control = True + try: self.draw_app.app.inform.emit(_("Click to place ...")) self.selected_dia = self.draw_app.tool2tooldia[self.draw_app.last_tool_selected] @@ -1756,6 +1786,10 @@ class SlotArray(FCShapeTool): pass def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1828,6 +1862,9 @@ class SlotArray(FCShapeTool): # ## Utility geometry (animated) self.draw_app.update_utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) elif mod_key is None: + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -1840,6 +1877,7 @@ class SlotArray(FCShapeTool): self.ui.slot_direction_radio.set_value('A') elif self.ui.slot_direction_radio.get_value() == 'A': self.ui.slot_direction_radio.set_value('X') + # ## Utility geometry (animated) self.draw_app.update_utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) @@ -1913,6 +1951,8 @@ class ResizeEditorExc(FCShapeTool): self.geometry = [] self.destination_storage = None + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception: @@ -2183,6 +2223,10 @@ class ResizeEditorExc(FCShapeTool): self.clean_up() def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -2232,6 +2276,10 @@ class ResizeEditorExc(FCShapeTool): if self.draw_app.app.plotcanvas.text_cursor.parent is None: self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene + def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + def clean_up(self): self.draw_app.selected = [] self.draw_app.ui.tools_table_exc.clearSelection() @@ -2417,6 +2465,8 @@ class CopyEditorExc(FCShapeTool): self.current_storage = None self.geometry = [] + self.cursor_data_control = True + for index in self.draw_app.ui.tools_table_exc.selectedIndexes(): row = index.row() # on column 1 in tool tables we hold the diameters, and we retrieve them as strings @@ -2770,6 +2820,10 @@ class CopyEditorExc(FCShapeTool): return circular_geo def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -2818,6 +2872,9 @@ class CopyEditorExc(FCShapeTool): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() diff --git a/appEditors/AppGeoEditor.py b/appEditors/AppGeoEditor.py index e18c2fce..68fec469 100644 --- a/appEditors/AppGeoEditor.py +++ b/appEditors/AppGeoEditor.py @@ -49,6 +49,8 @@ from rtree import index as rtindex from copy import deepcopy # from vispy.io import read_png +from typing import Union + import gettext import appTranslation as fcTranslate import builtins @@ -136,6 +138,10 @@ class DrawToolShape(object): # fixed issue of getting bounds only for one level lists of objects # now it can get bounds for nested lists of objects + + if self.geo is None: + return 0, 0, 0, 0 + def bounds_rec(shape_el): if type(shape_el) is list: minx = np.Inf @@ -388,7 +394,7 @@ class DrawTool(object): self.points = [] self.geometry = None # DrawToolShape or None - def click(self, point): + def click(self, point: Union[list[float, float], tuple[float, float]]): """ :param point: [x, y] Coordinate pair. """ @@ -467,6 +473,8 @@ class FCCircle(FCShapeTool): self.storage = self.draw_app.storage self.util_geo = None + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception: @@ -593,6 +601,10 @@ class FCCircle(FCShapeTool): return None def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -703,6 +715,9 @@ class FCCircle(FCShapeTool): self.draw_app.select_tool("select") return "Done." + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + def make(self): try: QtGui.QGuiApplication.restoreOverrideCursor() @@ -997,6 +1012,8 @@ class FCRectangle(FCShapeTool): self.util_geo = None + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception: @@ -1127,6 +1144,10 @@ class FCRectangle(FCShapeTool): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1174,6 +1195,9 @@ class FCRectangle(FCShapeTool): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -1281,6 +1305,8 @@ class FCPolygon(FCShapeTool): self.polygon_tool.run() self.new_segment = True + self.cursor_data_control = True + self.app.ui.notebook.setTabText(2, self.plugin_name) if self.draw_app.app.ui.splitter.sizes()[0] == 0: self.draw_app.app.ui.splitter.setSizes([1, 1]) @@ -1336,6 +1362,10 @@ class FCPolygon(FCShapeTool): return None def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1383,6 +1413,9 @@ class FCPolygon(FCShapeTool): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -1477,6 +1510,9 @@ class FCPath(FCShapeTool): self.name = 'path' self.app = self.draw_app.app + # show the cursor data + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception: @@ -1534,13 +1570,79 @@ class FCPath(FCShapeTool): def utility_geometry(self, data=None): if len(self.points) > 0: - temp_points = [x for x in self.points] - temp_points.append(data) + modifier = QtWidgets.QApplication.keyboardModifiers() + if modifier == Qt.KeyboardModifier.ShiftModifier: + temp_points = [x for x in self.points] + if not temp_points: + return DrawToolUtilityShape(None) + + x_start = temp_points[-1][0] + y_start = temp_points[-1][1] + x_end = data[0] + y_end = data[1] + dx = x_end - x_start + dy = y_end - y_start + det_angle = self.update_angle(dx, dy) + + new_x, new_y = data + + if 0 <= det_angle <= 10: + new_x = data[0] + new_y = y_start + + if 80 <= det_angle <= 90: + new_x = x_start + new_y = data[1] + + if 35 <= det_angle <= 55: + new_x, new_y = self.closest_point_to_45_degrees(origin=temp_points[-1], current=data) + + temp_points.append([new_x, new_y]) + else: + temp_points = [x for x in self.points] + temp_points.append(data) + return DrawToolUtilityShape(LineString(temp_points)) return None + @staticmethod + def closest_point_to_45_degrees(origin, current): + # Calculate vector from origin to current point + vector_x = current[0] - origin[0] + vector_y = current[1] - origin[1] + + # Calculate the angle between the vector and the x-axis + angle = math.atan2(vector_y, vector_x) + + # Calculate the angle of the 45-degree line + angle_45 = math.radians(45) + + # Determine the angle to the closest point on the 45-degree line + closest_angle = round(angle / angle_45) * angle_45 + + # Calculate the coordinates of the closest point + closest_distance = math.sqrt(vector_x ** 2 + vector_y ** 2) + closest_point_x = origin[0] + closest_distance * math.cos(closest_angle) + closest_point_y = origin[1] + closest_distance * math.sin(closest_angle) + + return closest_point_x, closest_point_y + + def update_angle(self, dx, dy): + try: + angle = math.degrees(math.atan2(abs(dy), abs(dx))) + # if angle < 0: + # angle += 360 + except Exception as e: + self.app.log.error("FCPath.update_angle() -> %s" % str(e)) + return None + return angle + def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1588,6 +1690,9 @@ class FCPath(FCShapeTool): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -1899,6 +2004,8 @@ class FCMove(FCShapeTool): self.sel_limit = self.draw_app.app.options["geometry_editor_sel_limit"] self.selection_shape = self.selection_bbox() + self.cursor_data_control = True + if len(self.draw_app.get_selected()) == 0: self.has_selection = False self.draw_app.app.inform.emit('[WARNING_NOTCL] %s %s' % @@ -2099,6 +2206,10 @@ class FCMove(FCShapeTool): raise def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -2146,6 +2257,9 @@ class FCMove(FCShapeTool): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -2237,6 +2351,8 @@ class FCCopy(FCShapeTool): self.clicked_postion = None + self.cursor_data_control = True + if len(self.draw_app.get_selected()) == 0: self.has_selection = False self.draw_app.app.inform.emit('[WARNING_NOTCL] %s %s' % @@ -2615,6 +2731,10 @@ class FCCopy(FCShapeTool): raise def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -2662,6 +2782,9 @@ class FCCopy(FCShapeTool): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() diff --git a/appEditors/AppGerberEditor.py b/appEditors/AppGerberEditor.py index ec363717..7b63a922 100644 --- a/appEditors/AppGerberEditor.py +++ b/appEditors/AppGerberEditor.py @@ -57,6 +57,8 @@ class PadEditorGrb(ShapeToolEditorGrb): self.app = self.draw_app.app self.dont_execute = False + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception: @@ -290,6 +292,10 @@ class PadEditorGrb(ShapeToolEditorGrb): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -393,6 +399,8 @@ class PadEditorGrb(ShapeToolEditorGrb): self.draw_app.app.inform.emit(msg) # self.interpolate_length = '' # return "Click on next point or hit ENTER to complete ..." + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control def clean_up(self): self.draw_app.selected = [] @@ -421,6 +429,8 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): self.app = self.draw_app.app self.dont_execute = False + self.cursor_data_control = True + try: self.radius = float(self.draw_app.storage_dict[self.draw_app.last_aperture_selected]['size']) / 2 except KeyError: @@ -883,6 +893,10 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): pass def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -957,6 +971,9 @@ class PadArrayEditorGrb(ShapeToolEditorGrb): # ## Utility geometry (animated) self.draw_app.update_utility_geometry(data=(self.draw_app.snap_x, self.draw_app.snap_y)) + elif key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + def add_pad_array(self, array_pos): self.radius = self.ui.radius_entry.get_value() self.array_type = self.ui.array_type_radio.get_value() @@ -1141,6 +1158,8 @@ class RegionEditorGrb(ShapeToolEditorGrb): # this will store the inflexion point in the geometry self.inter_point = None + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception as e: @@ -1409,6 +1428,10 @@ class RegionEditorGrb(ShapeToolEditorGrb): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1459,6 +1482,9 @@ class RegionEditorGrb(ShapeToolEditorGrb): self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene def on_key(self, key): + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_J or key == 'J': self.draw_app.app.on_jump_to() @@ -1622,6 +1648,9 @@ class TrackEditorGrb(ShapeToolEditorGrb): self.current_point = None self.final_click = False + + self.cursor_data_control = True + try: QtGui.QGuiApplication.restoreOverrideCursor() except Exception as e: @@ -1813,6 +1842,10 @@ class TrackEditorGrb(ShapeToolEditorGrb): self.draw_app.app.inform.emit('[success] %s' % _("Done.")) def draw_cursor_data(self, pos=None, delete=False): + if self.cursor_data_control is False: + self.draw_app.app.plotcanvas.text_cursor.text = "" + return + if pos is None: pos = self.draw_app.snap_x, self.draw_app.snap_y @@ -1876,6 +1909,9 @@ class TrackEditorGrb(ShapeToolEditorGrb): self.draw_app.draw_utility_geometry(geo_shape=geo) return _("Backtracked one point ...") + if key == 'C' or key == QtCore.Qt.Key.Key_C: + self.cursor_data_control = not self.cursor_data_control + # Jump to coords if key == QtCore.Qt.Key.Key_G or key == 'G': self.draw_app.app.ui.grid_snap_btn.trigger()