diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ac9e74..22118d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,69 @@ CHANGELOG for FlatCAM beta ================================================= +29.04.2020 + +- added a try-except clause in the FlatCAMTranslation.restart_program() when closing the Listener and the thread that runs it to adjust to MacOS usage +- more PEP8 changes +- in PreferencesUI.PreferencesUIManager class I removed the need to pass reference to the App class since it this was available through the 'ui' parameter +- some fixes due to recent refactoring +- minor bugs fixed (not so visible) +- promoted some methods to be static +- set the default layout on first run to the 'minimal' value +- modified the method that detects which tab was closed in the Plot Area so it will no longer depend on it's translated text but on it's objectName set on the QTab creation +- fixed the merge methods for all FlatCAM objects +- fixed a SyntaxError Exception when checking for types of found old preferences +- updated the French, German and Spanish Google translations +- updated the Romanian translation + +28.04.2020 + +- handled a possible situation in App.load_defaults() method +- fixed some issues in FlatCAMDB that may appear in certain scenarios +- some minor changes in the Python version detection +- added a new Tcl Command named SetPath which will set a path to be used by the Tcl commands. Once set will serve as a fallback path in case that the files fail to be opened first time. It will be persistent, saved in preferences. +- added the GUI for the new Open Example in the FIle -> Scripting menu. +- I am modifying all the open ... handlers to add a parameter that will flag if the method was launched from Tcl Shell. This way if the method will fail to open the filename (which include the path) it will try to open from a set fallback path. +- fixed issue #406, bug introduced recently (leftover changes). +- modified the ImportSVG Tcl command name to OpenSVG (open_svg alias) +- added a new Tcl command named OpenDXF (open_dxf alias) +- fixed some errors in Scripting features +- added a new Tcl command named GetPath as a convenient way to get the current default path stored in App.defaults['global_tcl_path'] +- added a new package to be installed in Linux to make available the black theme for FlatCAM beta +- moved all the 'share' resources (icons) to the 'assets/resources' folder +- some more fixes to problems generated by latest changes in the open handlers +- modified the make_freezed.py script for the new location of the icons +- added a fix for the ConnectionRefusedError in Linux that is issued when first running after a FlatCAM crash +- in SVG parser modified some imports to be one on each line +- fixed the Tcl Command BBox (leftovers from recent global changes) +- fixed some typos in strings reported by @pcb-hobbyst on FlatCAM forum +- disabled a skip_quotes method in ToolShell.FCShell class so I can now use quotes to enclose file paths with spaces inside + +27.04.2020 + +- finished the moving of all Tcl Shell stuff out of the FlatCAAMApp class to flatcamTools.ToolShell class +- updated the requirements.txt file to request that the Shapely package needs to be at least version 1.7.0 as it is needed in the latest versions of FlatCAM beta +- some TOOD cleanups +- minor changes +- replaced the testing if instance of FlatCAMObj with testing the obj.kind attribute +- removed the import of the whole FlatCAMApp file only for the usage of GracefulException +- remove the import of FlatCAMApp and used alternate ways +- optimized the imports in some files +- moved the Bookmarksmanager and ToolDB classes into their own files +- solved some bugs that were not so visible in the Editors and HPGL parser +- split the FlatCAMObj file into multiple files located in the flatcamObjects folder and renamed the contained classes with names more suggestive +- updated the Google Translation for the German language +- added support for Hungarian language - no translation for now +- minor changes +- moved the ObjectCollection class to the flatcamObjects folder where it belongs +- Linux Makefile + 25.04.2020 - ensured that on Graceful Exit (CTRL+ALT+X key combo) if using Progressive Plotting, the eventual residual plotted lines are deleted. This apply for Tool NCC and Tool Paint - fixed links in Attributions tab in Help -> About FlatCAM to be able to open external links. - updated Google Translations for French and Spanish languages +- added some '\n' chars in the Help Tcl command to make the help more readable 24.04.2020 @@ -282,7 +340,7 @@ CHANGELOG for FlatCAM beta 12.02.2020 -- working on fixing a bug in FlatCAMGeometry.merge() - FIXED issue #380 +- working on fixing a bug in GeometryObject.merge() - FIXED issue #380 - fixed bug: when deleting a FlatCAMCNCJob with annotations enabled, the annotations are not deleted from canvas; fixed issue #379 - fixed bug: creating a new project while a project is open and it contain CNCJob annotations and/or Gerber mark shapes, did not delete them from canvas @@ -637,7 +695,7 @@ CHANGELOG for FlatCAM beta - modified the Jump To method such that now allows relative jump from the current mouse location - fixed the Defaults upgrade overwriting the new version number with the old one - fixed issue with clear_polygon3() - the one who makes 'lines' and fixed the NCC Tool -- some small changes in the FlatCAMGeometry.on_tool_add() method +- some small changes in the GeometryObject.on_tool_add() method - made sure that in Geometry Editor the self.app.mouse attribute is updated with the current mouse position (x, y) - updated the preprocessor files - fixed the HPGL preprocessor @@ -680,7 +738,7 @@ CHANGELOG for FlatCAM beta - changed the Scale Entry in Object UI to FCEntry() GUI element in order to allow expressions to be entered. E.g: 1/25.4 - some small changes in the Scale button handler in FlatCAMObj() class - added option to save objects as PDF files in File -> Save menu -- optimized the FlatCAMGerber.clear_plot_apertures() method +- optimized the GerberObject.clear_plot_apertures() method - some changes in the ObjectUI and for the Geometry UI - finished a very rough and limited HPGL2 file import @@ -710,7 +768,7 @@ CHANGELOG for FlatCAM beta - reverted this change: "selected object in Project used to ask twice for UI build" because it will not build the UI when a tab is closed for Document object and the object is selected - fixed issue after Geometry object edit; the GCode made from an edited object did not reflect the changes in the object - in Object UI, the Scale FCDoubleSpinner will no longer work for Return key press due of issues of unwanted scaling on focusOut event -- in FlatCAMGeometry fixed the scale and offset methods to always process the self.solid_geometry +- in GeometryObject fixed the scale and offset methods to always process the self.solid_geometry - Calibration Tool - finished the calibrated object creation method - updated the POT file - fixed an error in the German PO file @@ -801,7 +859,7 @@ CHANGELOG for FlatCAM beta 28.11.2019 -- small fixes in NCC Tool and in the FlatCAMGeometry class +- small fixes in NCC Tool and in the GeometryObject class 27.11.2019 @@ -819,7 +877,7 @@ CHANGELOG for FlatCAM beta - In Gerber isolation changed the UI - in Gerber isolation added the option to selectively isolate only certain polygons -- made some optimizations in FlatCAMGerber.isolate() method +- made some optimizations in GerberObject.isolate() method - updated the 'single' isolation of Gerber polygons to remove the polygon if clicked on it and it is already in the list of single polygons to be isolated - clicking to add a polygon when doing Single type isolation will add a blue shape marking the selected polygon, second click will remove that shape - fixed bugs in Paint Tool when painting single polygon @@ -830,7 +888,7 @@ CHANGELOG for FlatCAM beta - in Tool Fiducials added a new fiducial type: chess pattern - work in Calibrate Excellon Tool -- fixed the line numbers in the TextPlainEdit to fit all digits of the line number; activated the line numbers for FlatCAMScript objects too +- fixed the line numbers in the TextPlainEdit to fit all digits of the line number; activated the line numbers for ScriptObject objects too - line numbers in the TextPlainEdit for the selected line are bold - made sure that the self.defaults dictionary is deepcopy-ed in the self.options dictionary - made sure that the units are read from the self.defaults and not from the GUI @@ -842,7 +900,7 @@ CHANGELOG for FlatCAM beta - Tool Fiducials - updated the source_file object for the modified Gerber files - working on adding line numbers to the TextPlainEdit - GCode view now has line numbers -- solved a bug that made selection of objects on canvas impossible if there is an object of type FlatCAMScript or FlatCAMDocument opened +- solved a bug that made selection of objects on canvas impossible if there is an object of type ScriptObject or DocumentObject opened 21.11.2019 @@ -899,7 +957,7 @@ CHANGELOG for FlatCAM beta - trying to improve the performance of View CNC Code command by using QPlainTextEdit; made the mods for it - when using the Find function in the TextEditor and the result reach the bottom of the document, the next find will be the first in the document (before it defaulted to the beginning of the document) - finished improving the show of text files in FlatCAM (CNC Code, Source files) -- fixed an issue in the FlatCAMObj.FlatCAMGerber.convert_units() which needed to be updated after changes elsewhere +- fixed an issue in the FlatCAMObj.GerberObject.convert_units() which needed to be updated after changes elsewhere 12.11.2019 @@ -937,7 +995,7 @@ CHANGELOG for FlatCAM beta - the "CRTL+S" key combo when the Preferences Tab is in focus will save the Preferences instead of saving the Project - fixed bug in the Paint Tool that did not allow choosing a Paint Method that was not Standard -- made sure that in the FlatCAMGeometry.merge() all the source data is deepcopy-ed in the final object +- made sure that in the GeometryObject.merge() all the source data is deepcopy-ed in the final object - the font color of the Preferences tab will change to red if settings are not saved and it will revert to default when saved - fixed issue #333. The Geometry Editor Paint tool was not working and using it resulted in an error @@ -1149,7 +1207,7 @@ CHANGELOG for FlatCAM beta - added a dark theme to FlatCAM (only for canvas). The selection is done in Edit -> Preferences -> General -> GUI Settings - updated the .POT file and worked a bit in the romanian translation - small changes: reduced the thickness of the axis in 3D mode from 3 pixels to 1 pixel -- made sure that is the text in the source file of a FlatCAMDocument is HTML is loaded as such +- made sure that is the text in the source file of a DocumentObject is HTML is loaded as such - added inverted icons 6.10.2019 @@ -1193,20 +1251,20 @@ CHANGELOG for FlatCAM beta 3.10.2019 -- previously I've added the initial layout for the FlatCAMDocument object -- added more editing features in the Selected Tab for the FlatCAMDocument object +- previously I've added the initial layout for the DocumentObject object +- added more editing features in the Selected Tab for the DocumentObject object 2.10.2019 - fixed bug in Geometry Editor that did not allow the copy of geometric elements - created a new class that holds all the Code Editor functionality and integrated as a Editor in FlatCAM, the location is in flatcamEditors folder - remade all the functions for view_source, scripts and view_code to use the new TextEditor class; now all the Code Editor tabs are being kept alive, before only one could be in an open state -- changed the name of the new object FlatCAMNotes to a more general one FlatCAMDocument -- changed the way a new FlatCAMScript object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.FlatCAMScript() class +- changed the name of the new object FlatCAMNotes to a more general one DocumentObject +- changed the way a new ScriptObject object is made, the method that is processing the Tcl commands when the Run button is clicked is moved to the FlatCAMObj.ScriptObject() class - reused the Multiprocessing Pool declared in the App for the ToolRulesCheck() class - adapted the Project context menu for the new types of FLatCAM objects - modified the setup_recent_files to accommodate the new FlatCAM objects -- made sure that when an FlatCAMScript object is deleted, it's associated Tab is closed +- made sure that when an ScriptObject object is deleted, it's associated Tab is closed - fixed the FlatCMAScript object saving when project is saved (loading a project with this script object is not working yet) - fixed the FlatCMAScript object when loading it from a project @@ -1220,7 +1278,7 @@ CHANGELOG for FlatCAM beta - added new settings for the Gerber newly introduced feature to isolate with the V-Shape tools (tip dia, tip angle, tool_type and cut Z) in Edit -> Preferences -> Gerber Advanced - made those settings just added for Gerber, to be updated on object creation - added the Geo Tolerance parameter to those that are converted from MM to INCH -- added two new FlatCAM objects: FlatCAMScript and FlatCAMNotes +- added two new FlatCAM objects: ScriptObject and FlatCAMNotes 30.09.2019 @@ -1443,7 +1501,7 @@ CHANGELOG for FlatCAM beta 15.09.2019 -- refactored FlatCAMGeometry.mtool_gen_cncjob() method +- refactored GeometryObject.mtool_gen_cncjob() method - fixed the TclCommandCncjob to work for multigeometry Geometry objects; still I had to fix the list of tools parameter, right now I am setting it to an empty list - update the Tcl Command isolate to be able to isolate exteriors, interiors besides the full isolation, using the iso_type parameter - fixed issue in ToolPaint that could not allow area painting of a geometry that was a list and not a Geometric element (polygon or MultiPolygon) @@ -1819,7 +1877,7 @@ CHANGELOG for FlatCAM beta - done regression to solve the bug with multiple passes cutting from the copper features (I should remember not to make mods here) - if 'combine' is checked in Gerber isolation but there is only one pass, the resulting geometry will still be single geo - the 'passes' entry was changed to a IntSpinner so it will allow passes to be entered only in range (1, 999) - it will not allow entry of 0 which may create some issues -- improved the FlatCAMGerber.isolate() function to work for geometry in the form of list and also in case that the elements of the list are LinearRings (like when doing the Exterior Isolation) +- improved the GerberObject.isolate() function to work for geometry in the form of list and also in case that the elements of the list are LinearRings (like when doing the Exterior Isolation) - in NCC Tool made sure that at each run the old objects are deleted - fixed bug in camlib.Gerber.parse_lines() Gerber parser where for Allegro Gerber files the Gerber units were incorrectly detected - improved Mark Area Tool in Gerber Editor such that at each launch the previous markings are deleted @@ -1914,7 +1972,7 @@ CHANGELOG for FlatCAM beta 19.07.2019 -- fixed bug in FlatCAMObj.FlatCAMGeometry.ui_disconnect(); the widgets signals were not disconnected from handlers when required therefore the signals were connected in an exponential way +- fixed bug in FlatCAMObj.GeometryObject.ui_disconnect(); the widgets signals were not disconnected from handlers when required therefore the signals were connected in an exponential way - some changes in the widgets used in the Selected tab for Geometry object - some PEP8 cleanup in FlatCAMObj.py - updated languages @@ -2045,7 +2103,7 @@ CHANGELOG for FlatCAM beta - fixed bug in ToolCutout where creating a cutout object geometry from another external isolation geometry failed - fixed bug in cncjob TclCommand where the gcode could not be correctly generated due of missing bounds params in obj.options dict -- fixed a hardcoded tolerance in FlatCAMGeometry.generatecncjob() and in FlatCAMGeometry.mtool_gen_cncjob() to use the parameter from Preferences +- fixed a hardcoded tolerance in GeometryObject.generatecncjob() and in GeometryObject.mtool_gen_cncjob() to use the parameter from Preferences - updated translations 5.06.2019 @@ -2219,7 +2277,7 @@ CHANGELOG for FlatCAM beta - fixed some bugs related to moving an Gerber object with the aperture table in view - added a new parameter in the Edit -> Preferences -> App Preferences named Geo Tolerance. This parameter control the level of geometric detail throughout FlatCAM. It directly influence the effect of Circle Steps parameter. - solved a bug in Excellon Editor that caused app crash when trying to edit a tool in Tool Table due of missing a tool offset -- updated the ToolPanelize tool so the Gerber panel of type FlatCAMGerber can be isolated like any other FlatCAMGerber object +- updated the ToolPanelize tool so the Gerber panel of type GerberObject can be isolated like any other GerberObject object - updated the ToolPanelize tool so it can be edited - modified the default values for toolchangez and endz parameters so they are now safe in all cases @@ -2703,7 +2761,7 @@ CHANGELOG for FlatCAM beta - added ability to mark individual apertures in Gerber file using the Gerber Aperture Table - more modifications for the Gerber UI layout; made 'follow' an advanced Gerber option - added in Preferences a new Category: Gerber Advanced Options. For now it controls the display of Gerber Aperture Table and the "follow" attribute4 -- fixed FlatCAMGerber.merge() to merge the self.apertures[ap]['solid_geometry'] too +- fixed GerberObject.merge() to merge the self.apertures[ap]['solid_geometry'] too - started to work on a new feature that allow adding a ToolChange GCode macro - GUI added both in CNCJob Selected tab and in CNCJob Preferences - added a limited 'sort-of' Gerber Editor: it allows buffering and scaling of apertures @@ -2846,7 +2904,7 @@ CHANGELOG for FlatCAM beta - added total travel distance for CNCJob object created from Excellon Object in the CNCJob Selected tab - added 'FlatCAM ' prefix to any detached tab, for easy identification - remade the Grids context menu (right mouse button click on canvas). Now it has values linked to the units type (inch or mm). Added ability to add or delete grid values and they are persistent. -- updated the function for the project context menu 'Generate CNC' menu entry (Action) to use the modernized function FlatCAMObj.FlatCAMGeometry.on_generatecnc_button_click() +- updated the function for the project context menu 'Generate CNC' menu entry (Action) to use the modernized function FlatCAMObj.GeometryObject.on_generatecnc_button_click() - when linked, the grid snap on Y will copy the value in grid snap on X in real time - in Gerber aperture table now the values are displayed in the current units set in FlatCAM - added shortcut key 'J' (jump to location) in Editors and added an icon to the dialog popup window @@ -2863,7 +2921,7 @@ CHANGELOG for FlatCAM beta - finished Gerber aperture table display - made the Gerber aperture table not visible as default and added a checkbox that can toggle the visibility - fixed issue with plotting in CNCJob; with Plot kind set to something else than 'all' when toggling Plot, it was defaulting to kind = 'all' -- added (and commented) an experimental FlatCAMObj.FlatCAMGerber.plot_aperture() +- added (and commented) an experimental FlatCAMObj.GerberObject.plot_aperture() 12.02.2019 @@ -3052,7 +3110,7 @@ CHANGELOG for FlatCAM beta 28.01.2018 -- fixed the FlatCAMGerber.merge() function +- fixed the GerberObject.merge() function - added a new menu entry for the Gerber Join function: Edit -> Conversions -> "Join Gerber(s) to Gerber" allowing joining Gerber objects into a final Gerber object - moved Paint Tool defaults from Geometry section to the Tools section in Edit -> Preferences - added key shortcuts for Open Manual = F1 and for Open Online VideoHelp = F2 @@ -3075,13 +3133,13 @@ CHANGELOG for FlatCAM beta - added new entries to the Canvas context menu (Copy, Delete, Edit/Save, Move, New Excellon, New Geometry, New Project) - fixed GRBL_laser preprocessor file - updated function for copy of an Excellon object for the case when the object has slots -- updated FlatCAMExcellon.merge() function to work in case some (or all) of the merged objects have slots +- updated ExcellonObject.merge() function to work in case some (or all) of the merged objects have slots 25.01.2019 - deleted junk folders - remade the Panelize Tool: now it is much faster, it is multi-threaded, it works with multitool geometries and it works with multigeo geometries too. -- made sure to copy the options attribute to the final object in the case of: FlatCAMGeometry.merge(), FlatCAMGerber.merge() and for the Panelize Tool +- made sure to copy the options attribute to the final object in the case of: GeometryObject.merge(), GerberObject.merge() and for the Panelize Tool - modified the panelize TclCommand to take advantage of the new panelize() function; added a 'threaded' parameter (default value is 1) which controls the execution of the panelize TclCommand: threaded or non-threaded - fixed TclCommand Cutout - added a new TclCommand named CutoutAny. Keyword: cutout_any @@ -3164,7 +3222,7 @@ CHANGELOG for FlatCAM beta - fixed the initial text in the ToolShell - reactivated the version check in case the release is not BETA; FlatCAMApp.App has now a beta object that when set True the application will show in the Title and help-> About that is Beta (and it disable version checking) - added a new name (mine: for good and/or bad) to the contributors list -- fixed the Join function to work on Gerber and Excellon, Gerber and Gerber, Excellon and Excelon combination of objects. The merged property is the solid_geometry and the result is a FlatCAMGeometry object. +- fixed the Join function to work on Gerber and Excellon, Gerber and Gerber, Excellon and Excelon combination of objects. The merged property is the solid_geometry and the result is a GeometryObject object. 3.01.2019 @@ -3246,8 +3304,8 @@ CHANGELOG for FlatCAM beta 18.12.2018 -- small changes in FlatCAMGeometry.plot() -- updated the FlatCAMGeometry.merge() function and the Join Geometry feature to accommodate the different types of geometries: singlegeo and multigeo type +- small changes in GeometryObject.plot() +- updated the GeometryObject.merge() function and the Join Geometry feature to accommodate the different types of geometries: singlegeo and multigeo type - added Conversion submenu in Edit where I moved the Join features and added the Convert from MultiGeo to SingleGeo type and the reverse - added Copy Tool (on a selection of tools) feature in Geometry Object UI - fixed the bounds() method for the MultiGeo geometry object so the canvas selection is working and also the Properties Tool diff --git a/FlatCAM.py b/FlatCAM.py index cd961119..c944970a 100644 --- a/FlatCAM.py +++ b/FlatCAM.py @@ -14,6 +14,8 @@ if sys.platform == "win32": # cx_freeze 'module win32' workaround pass +MIN_VERSION_MAJOR = 3 +MIN_VERSION_MINOR = 5 def debug_trace(): """ @@ -32,17 +34,19 @@ if __name__ == '__main__': # NOTE: Never talk to the GUI from threads! This is why I commented the above. freeze_support() + major_v = sys.version_info.major + minor_v = sys.version_info.minor # Supported Python version is >= 3.5 - if sys.version_info.major >= 3: - if sys.version_info.minor >= 5: + if major_v >= MIN_VERSION_MAJOR: + if minor_v >= MIN_VERSION_MINOR: pass else: - print("FlatCAM BETA uses PYTHON 3. The version minimum is 3.5\n" - "Your Python version is: %s" % str(sys.version_info)) + print("FlatCAM BETA uses PYTHON 3 or later. The version minimum is %s.%s\n" + "Your Python version is: %s.%s" % (MIN_VERSION_MAJOR, MIN_VERSION_MINOR, str(major_v), str(minor_v))) os._exit(0) else: - print("FlatCAM BETA uses PYTHON 3. The version minimum is 3.5\n" - "Your Python version is: %s" % str(sys.version_info)) + print("FlatCAM BETA uses PYTHON 3 or later. The version minimum is %s.%s\n" + "Your Python version is: %s.%s" % (MIN_VERSION_MAJOR, MIN_VERSION_MINOR, str(major_v), str(minor_v))) os._exit(0) debug_trace() @@ -83,4 +87,4 @@ if __name__ == '__main__': fc = App() # sys.exit(app.exec_()) - app.exec_() \ No newline at end of file + app.exec_() diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 0cd6a882..b8b50ba9 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -14,24 +14,21 @@ import getopt import random import simplejson as json import lzma -# import threading import shutil -import stat - -from stat import S_IREAD, S_IRGRP, S_IROTH +from datetime import datetime +import time import ctypes +import traceback -# import tkinter as tk -# from PyQt5 import QtPrintSupport +from shapely.geometry import Point, MultiPolygon +from io import StringIO from reportlab.graphics import renderPDF from reportlab.pdfgen import canvas -# from reportlab.graphics import renderPM from reportlab.lib.units import inch, mm from reportlab.lib.pagesizes import landscape, portrait from svglib.svglib import svg2rlg -# from contextlib import contextmanager import gc from xml.dom.minidom import parseString as parse_xml_string @@ -39,42 +36,59 @@ from xml.dom.minidom import parseString as parse_xml_string from multiprocessing.connection import Listener, Client from multiprocessing import Pool import socket -# from array import array -# import vispy.scene as scene +# #################################################################################################################### +# ################################### Imports part of FlatCAM ############################################# +# #################################################################################################################### -# ####################################### -# # Imports part of FlatCAM ## -# ####################################### -from ObjectCollection import * -from FlatCAMObj import * -from camlib import to_dict, dict2obj, ET, ParseError +# Diverse +from FlatCAMCommon import LoudDict, color_variant +from FlatCAMBookmark import BookmarkManager +from FlatCAMDB import ToolsDB2 +from vispy.gloo.util import _screenshot +from vispy.io import write_png + +# FlatCAM Objects +from defaults import FlatCAMDefaults +from flatcamObjects.ObjectCollection import * +from flatcamObjects.FlatCAMObj import FlatCAMObj +from flatcamObjects.FlatCAMCNCJob import CNCJobObject +from flatcamObjects.FlatCAMDocument import DocumentObject +from flatcamObjects.FlatCAMExcellon import ExcellonObject +from flatcamObjects.FlatCAMGeometry import GeometryObject +from flatcamObjects.FlatCAMGerber import GerberObject +from flatcamObjects.FlatCAMScript import ScriptObject + +# FlatCAM Parsing files +from flatcamParsers.ParseExcellon import Excellon +from flatcamParsers.ParseGerber import Gerber +from camlib import to_dict, dict2obj, ET, ParseError, Geometry, CNCjob + +# FlatCAM GUI from flatcamGUI.PlotCanvas import * from flatcamGUI.PlotCanvasLegacy import * from flatcamGUI.FlatCAMGUI import * from flatcamGUI.GUIElements import FCFileSaveDialog -from FlatCAMCommon import LoudDict, BookmarkManager, ToolsDB, ToolsDB2, color_variant +# FlatCAM Pre-processors from FlatCAMPostProc import load_preprocessors +# FlatCAM Editors from flatcamEditors.FlatCAMGeoEditor import FlatCAMGeoEditor from flatcamEditors.FlatCAMExcEditor import FlatCAMExcEditor from flatcamEditors.FlatCAMGrbEditor import FlatCAMGrbEditor from flatcamEditors.FlatCAMTextEditor import TextEditor - from flatcamParsers.ParseHPGL2 import HPGL2 +# FlatCAM Workers from FlatCAMProcess import * from FlatCAMWorkerStack import WorkerStack -# from flatcamGUI.VisPyVisuals import Color -from vispy.gloo.util import _screenshot -from vispy.io import write_png +# FlatCAM Tools from flatcamTools import * -import tclCommands - +# FlatCAM Translation import gettext import FlatCAMTranslation as fcTranslate import builtins @@ -296,9 +310,9 @@ class App(QtCore.QObject): else: App.log.debug("Win64!") - # ######################################################################### - # ####### CONFIG FILE WITH PARAMETERS REGARDING PORTABILITY ############### - # ######################################################################### + # ####################################################################################################### + # ####### CONFIG FILE WITH PARAMETERS REGARDING PORTABILITY ############################################# + # ####################################################################################################### config_file = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '\\config\\configuration.txt' try: with open(config_file, 'r'): @@ -374,15 +388,8 @@ class App(QtCore.QObject): json.dump({}, f) f.close() - # create factory_defaults.FlatConfig file if there is none - try: - f = open(self.data_path + '/factory_defaults.FlatConfig') - f.close() - except IOError: - App.log.debug('Creating empty factory_defaults.FlatConfig') - f = open(self.data_path + '/factory_defaults.FlatConfig', 'w') - json.dump({}, f) - f.close() + # Write factory_defaults.FlatConfig file to disk + FlatCAMDefaults.save_factory_defaults(os.path.join(self.data_path, "factory_defaults.FlatConfig")) # create a recent files json file if there is none try: @@ -421,658 +428,10 @@ class App(QtCore.QObject): # ############################################################################################################ # ################################# DEFAULTS - PREFERENCES STORAGE ########################################### # ############################################################################################################ - self.defaults = LoudDict() - self.defaults.update({ - # Global APP Preferences - "decimals_inch": 4, - "decimals_metric": 4, - "version": self.version, - "first_run": True, - "units": "MM", - "global_serial": 0, - "global_stats": dict(), - "global_tabs_detachable": True, - "global_jump_ref": 'abs', - "global_locate_pt": 'bl', - "global_tpdf_tmargin": 15.0, - "global_tpdf_bmargin": 10.0, - "global_tpdf_lmargin": 20.0, - "global_tpdf_rmargin": 20.0, - "global_autosave": False, - "global_autosave_timeout": 300000, - - # General - "global_graphic_engine": '3D', - "global_app_level": 'b', - "global_portable": False, - "global_language": 'English', - "global_version_check": True, - "global_send_stats": True, - "global_pan_button": '2', - "global_mselect_key": 'Control', - "global_project_at_startup": False, - "global_systray_icon": True, - "global_project_autohide": True, - "global_toggle_tooltips": True, - "global_worker_number": 2, - "global_tolerance": 0.005, - "global_open_style": True, - "global_delete_confirmation": True, - "global_compression_level": 3, - "global_save_compressed": True, - - "global_machinist_setting": False, - - # Global GUI Preferences - "global_gridx": 1.0, - "global_gridy": 1.0, - "global_snap_max": 0.05, - "global_workspace": False, - "global_workspaceT": "A4", - "global_workspace_orientation": 'p', - - "global_grid_context_menu": { - 'in': [0.01, 0.02, 0.025, 0.05, 0.1], - 'mm': [0.1, 0.2, 0.5, 1, 2.54] - }, - - "global_sel_fill": '#a5a5ffbf', - "global_sel_line": '#0000ffbf', - "global_alt_sel_fill": '#BBF268BF', - "global_alt_sel_line": '#006E20BF', - "global_draw_color": '#FF0000', - "global_sel_draw_color": '#0000FF', - "global_proj_item_color": '#000000', - "global_proj_item_dis_color": '#b7b7cb', - "global_activity_icon": 'Ball green', - - "global_toolbar_view": 511, - - "global_background_timeout": 300000, # Default value is 5 minutes - "global_verbose_error_level": 0, # Shell verbosity 0 = default - # (python trace only for unknown errors), - # 1 = show trace(show trace always), - # 2 = (For the future). - - # Persistence - "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. - "global_shell_at_startup": False, # Show the shell at startup. - "global_recent_limit": 10, # Max. items in recent list. - - "global_bookmarks": dict(), - "global_bookmarks_limit": 10, - - "fit_key": 'V', - "zoom_out_key": '-', - "zoom_in_key": '=', - "grid_toggle_key": 'G', - "global_zoom_ratio": 1.5, - "global_point_clipboard_format": "(%.*f, %.*f)", - "global_zdownrate": None, - - # General GUI Settings - "global_theme": 'white', - "global_gray_icons": False, - "global_hover": False, - "global_selection_shape": True, - "global_layout": "compact", - "global_cursor_type": "small", - "global_cursor_size": 20, - "global_cursor_width": 2, - "global_cursor_color": '#FF0000', - "global_cursor_color_enabled": True, - - # Gerber General - "gerber_plot": True, - "gerber_solid": True, - "gerber_multicolored": False, - "gerber_circle_steps": 64, - "gerber_use_buffer_for_union": True, - "gerber_clean_apertures": True, - "gerber_extra_buffering": True, - - "gerber_plot_fill": '#BBF268BF', - "gerber_plot_line": '#006E20BF', - - "gerber_def_units": 'IN', - "gerber_def_zeros": 'L', - "gerber_save_filters": "Gerber File .gbr (*.gbr);;Gerber File .bot (*.bot);;Gerber File .bsm (*.bsm);;" - "Gerber File .cmp (*.cmp);;Gerber File .crc (*.crc);;Gerber File .crs (*.crs);;" - "Gerber File .gb0 (*.gb0);;Gerber File .gb1 (*.gb1);;Gerber File .gb2 (*.gb2);;" - "Gerber File .gb3 (*.gb3);;Gerber File .gb4 (*.gb4);;Gerber File .gb5 (*.gb5);;" - "Gerber File .gb6 (*.gb6);;Gerber File .gb7 (*.gb7);;Gerber File .gb8 (*.gb8);;" - "Gerber File .gb9 (*.gb9);;Gerber File .gbd (*.gbd);;Gerber File .gbl (*.gbl);;" - "Gerber File .gbo (*.gbo);;Gerber File .gbp (*.gbp);;Gerber File .gbs (*.gbs);;" - "Gerber File .gdo (*.gdo);;Gerber File .ger (*.ger);;Gerber File .gko (*.gko);;" - "Gerber File .gm1 (*.gm1);;Gerber File .gm2 (*.gm2);;Gerber File .gm3 (*.gm3);;" - "Gerber File .grb (*.grb);;Gerber File .gtl (*.gtl);;Gerber File .gto (*.gto);;" - "Gerber File .gtp (*.gtp);;Gerber File .gts (*.gts);;Gerber File .ly15 (*.ly15);;" - "Gerber File .ly2 (*.ly2);;Gerber File .mil (*.mil);;" - "Gerber File .outline (*.outline);;Gerber File .pho (*.pho);;" - "Gerber File .plc (*.plc);;Gerber File .pls (*.pls);;Gerber File .smb (*.smb);;" - "Gerber File .smt (*.smt);;Gerber File .sol (*.sol);;Gerber File .spb (*.spb);;" - "Gerber File .spt (*.spt);;Gerber File .ssb (*.ssb);;Gerber File .sst (*.sst);;" - "Gerber File .stc (*.stc);;Gerber File .sts (*.sts);;Gerber File .top (*.top);;" - "Gerber File .tsm (*.tsm);;Gerber File .art (*.art)" - "All Files (*.*)", - - # Gerber Options - "gerber_isotooldia": 0.1, - "gerber_isopasses": 1, - "gerber_isooverlap": 10, - "gerber_milling_type": "cl", - "gerber_combine_passes": False, - "gerber_iso_scope": 'all', - "gerber_noncoppermargin": 0.1, - "gerber_noncopperrounded": False, - "gerber_bboxmargin": 0.1, - "gerber_bboxrounded": False, - - # Gerber Advanced Options - "gerber_aperture_display": False, - "gerber_aperture_scale_factor": 1.0, - "gerber_aperture_buffer_factor": 0.0, - "gerber_follow": False, - "gerber_tool_type": 'circular', - "gerber_vtipdia": 0.1, - "gerber_vtipangle": 30, - "gerber_vcutz": -0.05, - "gerber_iso_type": "full", - "gerber_buffering": "full", - "gerber_simplification": False, - "gerber_simp_tolerance": 0.0005, - - # Gerber Export - "gerber_exp_units": 'IN', - "gerber_exp_integer": 2, - "gerber_exp_decimals": 4, - "gerber_exp_zeros": 'L', - - # Gerber Editor - "gerber_editor_sel_limit": 30, - "gerber_editor_newcode": 10, - "gerber_editor_newsize": 0.8, - "gerber_editor_newtype": 'C', - "gerber_editor_newdim": "0.5, 0.5", - "gerber_editor_array_size": 5, - "gerber_editor_lin_axis": 'X', - "gerber_editor_lin_pitch": 0.1, - "gerber_editor_lin_angle": 0.0, - "gerber_editor_circ_dir": 'CW', - "gerber_editor_circ_angle": 0.0, - "gerber_editor_scale_f": 1.0, - "gerber_editor_buff_f": 0.1, - "gerber_editor_ma_low": 0.0, - "gerber_editor_ma_high": 1.0, - - # Excellon General - "excellon_plot": True, - "excellon_solid": True, - "excellon_format_upper_in": 2, - "excellon_format_lower_in": 4, - "excellon_format_upper_mm": 3, - "excellon_format_lower_mm": 3, - "excellon_zeros": "L", - "excellon_units": "INCH", - "excellon_update": True, - - "excellon_optimization_type": 'B', - - "excellon_search_time": 3, - "excellon_save_filters": "Excellon File .txt (*.txt);;Excellon File .drd (*.drd);;" - "Excellon File .drill (*.drill);;" - "Excellon File .drl (*.drl);;Excellon File .exc (*.exc);;" - "Excellon File .ncd (*.ncd);;Excellon File .tap (*.tap);;" - "Excellon File .xln (*.xln);;All Files (*.*)", - "excellon_plot_fill": '#C40000BF', - "excellon_plot_line": '#750000BF', - - # Excellon Options - "excellon_operation": "drill", - "excellon_milling_type": "drills", - - "excellon_milling_dia": 0.8, - - "excellon_cutz": -1.7, - "excellon_multidepth": False, - "excellon_depthperpass": 0.7, - "excellon_travelz": 2, - "excellon_endz": 0.5, - "excellon_endxy": None, - "excellon_feedrate_z": 300, - "excellon_spindlespeed": 0, - "excellon_dwell": False, - "excellon_dwelltime": 1, - "excellon_toolchange": False, - "excellon_toolchangez": 15, - "excellon_ppname_e": 'default', - "excellon_tooldia": 0.8, - "excellon_slot_tooldia": 1.8, - "excellon_gcode_type": "drills", - - # Excellon Advanced Options - "excellon_offset": 0.0, - "excellon_toolchangexy": "0.0, 0.0", - "excellon_startz": None, - "excellon_feedrate_rapid": 1500, - "excellon_z_pdepth": -0.02, - "excellon_feedrate_probe": 75, - "excellon_spindledir": 'CW', - "excellon_f_plunge": False, - "excellon_f_retract": False, - - # Excellon Export - "excellon_exp_units": 'INCH', - "excellon_exp_format": 'ndec', - "excellon_exp_integer": 2, - "excellon_exp_decimals": 4, - "excellon_exp_zeros": 'LZ', - "excellon_exp_slot_type": 'routing', - - # Excellon Editor - "excellon_editor_sel_limit": 30, - "excellon_editor_newdia": 1.0, - "excellon_editor_array_size": 5, - "excellon_editor_lin_dir": 'X', - "excellon_editor_lin_pitch": 2.54, - "excellon_editor_lin_angle": 0.0, - "excellon_editor_circ_dir": 'CW', - "excellon_editor_circ_angle": 12, - # Excellon Slots - "excellon_editor_slot_direction": 'X', - "excellon_editor_slot_angle": 0.0, - "excellon_editor_slot_length": 5.0, - # Excellon Slot Array - "excellon_editor_slot_array_size": 5, - "excellon_editor_slot_lin_dir": 'X', - "excellon_editor_slot_lin_pitch": 2.54, - "excellon_editor_slot_lin_angle": 0.0, - "excellon_editor_slot_circ_dir": 'CW', - "excellon_editor_slot_circ_angle": 0.0, - - # Geometry General - "geometry_plot": True, - "geometry_circle_steps": 64, - "geometry_cnctooldia": "2.4", - "geometry_plot_line": "#FF0000", - - # Geometry Options - "geometry_cutz": -2.4, - "geometry_vtipdia": 0.1, - "geometry_vtipangle": 30, - "geometry_multidepth": False, - "geometry_depthperpass": 0.8, - "geometry_travelz": 2, - "geometry_toolchange": False, - "geometry_toolchangez": 15.0, - "geometry_endz": 15.0, - "geometry_endxy": None, - - "geometry_feedrate": 120, - "geometry_feedrate_z": 60, - "geometry_spindlespeed": 0, - "geometry_dwell": False, - "geometry_dwelltime": 1, - "geometry_ppname_g": 'default', - - # Geometry Advanced Options - "geometry_toolchangexy": "0.0, 0.0", - "geometry_startz": None, - "geometry_feedrate_rapid": 1500, - "geometry_extracut": False, - "geometry_extracut_length": 0.1, - "geometry_z_pdepth": -0.02, - "geometry_f_plunge": False, - "geometry_spindledir": 'CW', - "geometry_feedrate_probe": 75, - "geometry_segx": 0.0, - "geometry_segy": 0.0, - - # Geometry Editor - "geometry_editor_sel_limit": 30, - "geometry_editor_milling_type": "cl", - - # CNC Job General - "cncjob_plot": True, - "cncjob_plot_kind": 'all', - "cncjob_annotation": True, - "cncjob_tooldia": 1.0, - "cncjob_coords_type": "G90", - "cncjob_coords_decimals": 4, - "cncjob_fr_decimals": 2, - "cncjob_steps_per_circle": 64, - "cncjob_footer": False, - "cncjob_line_ending": False, - "cncjob_save_filters": "G-Code Files .nc (*.nc);;G-Code Files .din (*.din);;G-Code Files .dnc (*.dnc);;" - "G-Code Files .ecs (*.ecs);;G-Code Files .eia (*.eia);;G-Code Files .fan (*.fan);;" - "G-Code Files .fgc (*.fgc);;G-Code Files .fnc (*.fnc);;G-Code Files . gc (*.gc);;" - "G-Code Files .gcd (*.gcd);;G-Code Files .gcode (*.gcode);;G-Code Files .h (*.h);;" - "G-Code Files .hnc (*.hnc);;G-Code Files .i (*.i);;G-Code Files .min (*.min);;" - "G-Code Files .mpf (*.mpf);;G-Code Files .mpr (*.mpr);;G-Code Files .cnc (*.cnc);;" - "G-Code Files .ncc (*.ncc);;G-Code Files .ncg (*.ncg);;G-Code Files .ncp (*.ncp);;" - "G-Code Files .ngc (*.ngc);;G-Code Files .out (*.out);;G-Code Files .ply (*.ply);;" - "G-Code Files .sbp (*.sbp);;G-Code Files .tap (*.tap);;G-Code Files .xpi (*.xpi);;" - "All Files (*.*)", - "cncjob_plot_line": '#4650BDFF', - "cncjob_plot_fill": '#5E6CFFFF', - "cncjob_travel_line": '#B5AB3A4C', - "cncjob_travel_fill": '#F0E24D4C', - - # CNC Job Options - "cncjob_prepend": "", - "cncjob_append": "", - - # CNC Job Advanced Options - "cncjob_toolchange_macro": "", - "cncjob_toolchange_macro_enable": False, - "cncjob_annotation_fontsize": 9, - "cncjob_annotation_fontcolor": '#990000', - - # NCC Tool - "tools_ncctools": "1.0, 0.5", - "tools_nccorder": 'rev', - "tools_nccoperation": 'clear', - "tools_nccoverlap": 40, - "tools_nccmargin": 1.0, - "tools_nccmethod": _("Seed"), - "tools_nccconnect": True, - "tools_ncccontour": True, - "tools_nccrest": False, - "tools_ncc_offset_choice": False, - "tools_ncc_offset_value": 0.0000, - "tools_nccref": _('Itself'), - "tools_ncc_area_shape": "square", - "tools_ncc_plotting": 'normal', - "tools_nccmilling_type": 'cl', - "tools_ncctool_type": 'C1', - "tools_ncccutz": -0.05, - "tools_ncctipdia": 0.1, - "tools_ncctipangle": 30, - "tools_nccnewdia": 0.1, - - # Cutout Tool - "tools_cutouttooldia": 2.4, - "tools_cutoutkind": "single", - "tools_cutoutmargin": 0.1, - "tools_cutout_z": -1.8, - "tools_cutout_depthperpass": 0.6, - "tools_cutout_mdepth": True, - "tools_cutoutgapsize": 4, - "tools_gaps_ff": "4", - "tools_cutout_convexshape": False, - - # Paint Tool - "tools_painttooldia": 0.3, - "tools_paintorder": 'rev', - "tools_paintoverlap": 20, - "tools_paintmargin": 0.0, - "tools_paintmethod": _("Seed"), - "tools_selectmethod": _("All Polygons"), - "tools_paint_area_shape": "square", - "tools_pathconnect": True, - "tools_paintcontour": True, - "tools_paint_plotting": 'normal', - "tools_paintrest": False, - "tools_painttool_type": 'C1', - "tools_paintcutz": -0.05, - "tools_painttipdia": 0.1, - "tools_painttipangle": 30, - "tools_paintnewdia": 0.1, - - # 2-Sided Tool - "tools_2sided_mirror_axis": "X", - "tools_2sided_axis_loc": "point", - "tools_2sided_drilldia": 3.125, - "tools_2sided_allign_axis": "X", - - # Film Tool - "tools_film_type": 'neg', - "tools_film_boundary": 1.0, - "tools_film_scale_stroke": 0, - "tools_film_color": '#000000', - "tools_film_scale_cb": False, - "tools_film_scale_x_entry": 1.0, - "tools_film_scale_y_entry": 1.0, - "tools_film_skew_cb": False, - "tools_film_skew_x_entry": 0.0, - "tools_film_skew_y_entry": 0.0, - "tools_film_skew_ref_radio": 'bottomleft', - "tools_film_mirror_cb": False, - "tools_film_mirror_axis_radio": 'none', - "tools_film_file_type_radio": 'svg', - "tools_film_orientation": 'p', - "tools_film_pagesize": 'A4', - - # Panel Tool - "tools_panelize_spacing_columns": 0, - "tools_panelize_spacing_rows": 0, - "tools_panelize_columns": 1, - "tools_panelize_rows": 1, - "tools_panelize_constrain": False, - "tools_panelize_constrainx": 200.0, - "tools_panelize_constrainy": 290.0, - "tools_panelize_panel_type": 'gerber', - - # Calculators Tool - "tools_calc_vshape_tip_dia": 0.2, - "tools_calc_vshape_tip_angle": 30, - "tools_calc_vshape_cut_z": 0.05, - "tools_calc_electro_length": 10.0, - "tools_calc_electro_width": 10.0, - "tools_calc_electro_cdensity": 13.0, - "tools_calc_electro_growth": 10.0, - - # Transform Tool - "tools_transform_rotate": 90, - "tools_transform_skew_x": 0.0, - "tools_transform_skew_y": 0.0, - "tools_transform_scale_x": 1.0, - "tools_transform_scale_y": 1.0, - "tools_transform_scale_link": True, - "tools_transform_scale_reference": True, - "tools_transform_offset_x": 0.0, - "tools_transform_offset_y": 0.0, - "tools_transform_mirror_reference": False, - "tools_transform_mirror_point": (0, 0), - "tools_transform_buffer_dis": 0.0, - "tools_transform_buffer_factor": 100.0, - "tools_transform_buffer_corner": True, - - # SolderPaste Tool - "tools_solderpaste_tools": "1.0, 0.3", - "tools_solderpaste_new": 0.3, - "tools_solderpaste_z_start": 0.05, - "tools_solderpaste_z_dispense": 0.1, - "tools_solderpaste_z_stop": 0.05, - "tools_solderpaste_z_travel": 0.1, - "tools_solderpaste_z_toolchange": 1.0, - "tools_solderpaste_xy_toolchange": "0.0, 0.0", - "tools_solderpaste_frxy": 150, - "tools_solderpaste_frz": 150, - "tools_solderpaste_frz_dispense": 1.0, - "tools_solderpaste_speedfwd": 300, - "tools_solderpaste_dwellfwd": 1, - "tools_solderpaste_speedrev": 200, - "tools_solderpaste_dwellrev": 1, - "tools_solderpaste_pp": 'Paste_1', - - # Subtract Tool - "tools_sub_close_paths": True, - - # Distance Tool - "tools_dist_snap_center": False, - - # ################################################################################### - # ################################ TOOLS 2 ########################################## - # ################################################################################### - - # Optimal Tool - "tools_opt_precision": 4, - - # Check Rules Tool - "tools_cr_trace_size": True, - "tools_cr_trace_size_val": 0.25, - "tools_cr_c2c": True, - "tools_cr_c2c_val": 0.25, - "tools_cr_c2o": True, - "tools_cr_c2o_val": 1.0, - "tools_cr_s2s": True, - "tools_cr_s2s_val": 0.25, - "tools_cr_s2sm": True, - "tools_cr_s2sm_val": 0.25, - "tools_cr_s2o": True, - "tools_cr_s2o_val": 1.0, - "tools_cr_sm2sm": True, - "tools_cr_sm2sm_val": 0.25, - "tools_cr_ri": True, - "tools_cr_ri_val": 0.3, - "tools_cr_h2h": True, - "tools_cr_h2h_val": 0.3, - "tools_cr_dh": True, - "tools_cr_dh_val": 0.3, - - # QRCode Tool - "tools_qrcode_version": 1, - "tools_qrcode_error": 'L', - "tools_qrcode_box_size": 3, - "tools_qrcode_border_size": 4, - "tools_qrcode_qrdata": '', - "tools_qrcode_polarity": 'pos', - "tools_qrcode_rounded": 's', - "tools_qrcode_fill_color": '#000000', - "tools_qrcode_back_color": '#FFFFFF', - "tools_qrcode_sel_limit": 330, - - # Copper Thieving Tool - "tools_copper_thieving_clearance": 0.25, - "tools_copper_thieving_margin": 1.0, - "tools_copper_thieving_reference": 'itself', - "tools_copper_thieving_box_type": 'rect', - "tools_copper_thieving_circle_steps": 64, - "tools_copper_thieving_fill_type": 'solid', - "tools_copper_thieving_dots_dia": 1.0, - "tools_copper_thieving_dots_spacing": 2.0, - "tools_copper_thieving_squares_size": 1.0, - "tools_copper_thieving_squares_spacing": 2.0, - "tools_copper_thieving_lines_size": 0.25, - "tools_copper_thieving_lines_spacing": 2.0, - "tools_copper_thieving_rb_margin": 1.0, - "tools_copper_thieving_rb_thickness": 1.0, - "tools_copper_thieving_mask_clearance": 0.0, - - # Fiducials Tool - "tools_fiducials_dia": 1.0, - "tools_fiducials_margin": 1.0, - "tools_fiducials_mode": 'auto', - "tools_fiducials_second_pos": 'up', - "tools_fiducials_type": 'circular', - "tools_fiducials_line_thickness": 0.25, - - # Calibration Tool - "tools_cal_calsource": 'object', - "tools_cal_travelz": 2.0, - "tools_cal_verz": 0.1, - "tools_cal_zeroz": False, - "tools_cal_toolchangez": 15, - "tools_cal_toolchange_xy": '', - "tools_cal_sec_point": 'tl', - - # Drills Extraction Tool - "tools_edrills_hole_type": 'fixed', - "tools_edrills_hole_fixed_dia": 0.5, - "tools_edrills_hole_prop_factor": 80.0, - "tools_edrills_circular_ring": 0.2, - "tools_edrills_oblong_ring": 0.2, - "tools_edrills_square_ring": 0.2, - "tools_edrills_rectangular_ring": 0.2, - "tools_edrills_others_ring": 0.2, - "tools_edrills_circular": True, - "tools_edrills_oblong": False, - "tools_edrills_square": False, - "tools_edrills_rectangular": False, - "tools_edrills_others": False, - - # Punch Gerber Tool - "tools_punch_hole_type": 'exc', - "tools_punch_hole_fixed_dia": 0.5, - "tools_punch_hole_prop_factor": 80.0, - "tools_punch_circular_ring": 0.2, - "tools_punch_oblong_ring": 0.2, - "tools_punch_square_ring": 0.2, - "tools_punch_rectangular_ring": 0.2, - "tools_punch_others_ring": 0.2, - "tools_punch_circular": True, - "tools_punch_oblong": False, - "tools_punch_square": True, - "tools_punch_rectangular": False, - "tools_punch_others": False, - - # Align Objects Tool - "tools_align_objects_align_type": 'sp', - - # Invert Gerber Tool - "tools_invert_margin": 0.1, - "tools_invert_join_style": 's', - - # Utilities - # file associations - "fa_excellon": 'drd, drill, drl, exc, ncd, tap, xln', - "fa_gcode": 'cnc, din, dnc, ecs, eia, fan, fgc, fnc, gc, gcd, gcode, h, hnc, i, min, mpf, mpr, nc, ncc, ' - 'ncg, ncp, ngc, out, ply, rol, sbp, tap, xpi', - "fa_gerber": 'art, bot, bsm, cmp, crc, crs, dim, gb0, gb1, gb2, gb3, gb4, gb5, gb6, gb7, gb8, gb9, gbd, ' - 'gbl, gbo, gbp, gbr, gbs, gdo, ger, gko, 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', - # Keyword list - "util_autocomplete_keywords": 'Desktop, Documents, FlatConfig, FlatPrj, False, ' - 'Marius, My Documents, Paste_1, ' - 'Repetier, Roland_MDX_20, True, Users, Toolchange_Custom, ' - 'Toolchange_Probe_MACH3, ' - 'Toolchange_manual, Users, all, axis, auto, axisoffset, ' - 'box, center_x, center_y, columns, combine, connect, contour, default, ' - 'depthperpass, dia, diatol, dist, drilled_dias, drillz, dpp, dwelltime, ' - 'endxy, endz, extracut_length, f, feedrate, ' - 'feedrate_z, grbl_11, GRBL_laser, gridoffsety, gridx, gridy, has_offset, ' - 'holes, hpgl, iso_type, line_xyz, margin, marlin, method, milled_dias, ' - 'minoffset, name, offset, opt_type, order, outname, overlap, ' - '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, ' - 'tooldia, travelz, use_threads, value, x, x0, x1, y, y0, y1, z_cut, ' - 'z_move', - "script_autocompleter": True, - "script_text": "", - "script_plot": True, - "script_source_file": "", - - "document_autocompleter": False, - "document_text": "", - "document_plot": True, - "document_source_file": "", - "document_font_color": '#000000', - "document_sel_color": '#0055ff', - "document_font_size": 6, - "document_tab_size": 80, - }) - - # ############################################################ - # ############### Load defaults from file #################### - # ############################################################ - self.old_defaults_found = False - + self.defaults = FlatCAMDefaults() + current_defaults_path = os.path.join(self.data_path, "current_defaults.FlatConfig") if user_defaults: - self.load_defaults(filename='current_defaults') + self.defaults.load(filename=current_defaults_path) if self.defaults['units'] == 'MM': self.decimals = int(self.defaults['decimals_metric']) @@ -1080,29 +439,25 @@ class App(QtCore.QObject): self.decimals = int(self.defaults['decimals_inch']) if self.defaults["global_gray_icons"] is False: - self.resource_location = 'share' + self.resource_location = 'assets/resources' else: - self.resource_location = 'share/dark_resources' + self.resource_location = 'assets/resources/dark_resources' self.current_units = self.defaults['units'] - # store here the current self.defaults so it can be restored if Preferences changes are cancelled - self.current_defaults = {} - self.current_defaults.update(self.defaults) - - # ########################################################################## - # ##################### SETUP OBJECT CLASSES ############################### - # ########################################################################## + # ########################################################################################################### + # #################################### SETUP OBJECT CLASSES ################################################# + # ########################################################################################################### self.setup_obj_classes() - # ########################################################################## - # ##################### CREATE MULTIPROCESSING POOL ######################## - # ########################################################################## + # ########################################################################################################### + # ###################################### CREATE MULTIPROCESSING POOL ####################################### + # ########################################################################################################### self.pool = Pool() - # ########################################################################## - # ################## Setting the Splash Screen ############################# - # ########################################################################## + # ########################################################################################################### + # ###################################### Setting the Splash Screen ########################################## + # ########################################################################################################### splash_settings = QSettings("Open Source", "FlatCAM") if splash_settings.contains("splash_screen"): @@ -1132,9 +487,9 @@ class App(QtCore.QObject): else: show_splash = 0 - # ############################################################################# - # ##################### Initialize GUI ######################################## - # ############################################################################# + # ########################################################################################################### + # ######################################### Initialize GUI ################################################## + # ########################################################################################################### # FlatCAM colors used in plotting self.FC_light_green = '#BBF268BF' @@ -1173,570 +528,35 @@ class App(QtCore.QObject): # set FlatCAM units in the Status bar self.set_screen_units(self.defaults['units']) - # ############################################################################# - # ######################## UPDATE PREFERENCES GUI FORMS ####################### - # ############################################################################# + # ########################################################################################################### + # ########################################### AUTOSAVE SETUP ################################################ + # ########################################################################################################### - # when adding entries here read the comments in the method found bellow named: - # def new_object(self, kind, name, initialize, active=True, fit=True, plot=True) - self.defaults_form_fields = { - # General App - "decimals_inch": self.ui.general_defaults_form.general_app_group.precision_inch_entry, - "decimals_metric": self.ui.general_defaults_form.general_app_group.precision_metric_entry, - "units": self.ui.general_defaults_form.general_app_group.units_radio, - "global_graphic_engine": self.ui.general_defaults_form.general_app_group.ge_radio, - "global_app_level": self.ui.general_defaults_form.general_app_group.app_level_radio, - "global_portable": self.ui.general_defaults_form.general_app_group.portability_cb, - "global_language": self.ui.general_defaults_form.general_app_group.language_cb, + self.block_autosave = False + self.autosave_timer = QtCore.QTimer(self) + self.save_project_auto_update() + self.autosave_timer.timeout.connect(self.save_project_auto) - "global_systray_icon": self.ui.general_defaults_form.general_app_group.systray_cb, - "global_shell_at_startup": self.ui.general_defaults_form.general_app_group.shell_startup_cb, - "global_project_at_startup": self.ui.general_defaults_form.general_app_group.project_startup_cb, - "global_version_check": self.ui.general_defaults_form.general_app_group.version_check_cb, - "global_send_stats": self.ui.general_defaults_form.general_app_group.send_stats_cb, + # ########################################################################################################### + # ##################################### UPDATE PREFERENCES GUI FORMS ######################################## + # ########################################################################################################### - "global_worker_number": self.ui.general_defaults_form.general_app_group.worker_number_sb, - "global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry, - - "global_compression_level": self.ui.general_defaults_form.general_app_group.compress_spinner, - "global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb, - "global_autosave": self.ui.general_defaults_form.general_app_group.autosave_cb, - "global_autosave_timeout": self.ui.general_defaults_form.general_app_group.autosave_entry, - - "global_tpdf_tmargin": self.ui.general_defaults_form.general_app_group.tmargin_entry, - "global_tpdf_bmargin": self.ui.general_defaults_form.general_app_group.bmargin_entry, - "global_tpdf_lmargin": self.ui.general_defaults_form.general_app_group.lmargin_entry, - "global_tpdf_rmargin": self.ui.general_defaults_form.general_app_group.rmargin_entry, - - # General GUI Preferences - "global_theme": self.ui.general_defaults_form.general_gui_group.theme_radio, - "global_gray_icons": self.ui.general_defaults_form.general_gui_group.gray_icons_cb, - "global_layout": self.ui.general_defaults_form.general_gui_group.layout_combo, - "global_hover": self.ui.general_defaults_form.general_gui_group.hover_cb, - "global_selection_shape": self.ui.general_defaults_form.general_gui_group.selection_cb, - - "global_sel_fill": self.ui.general_defaults_form.general_gui_group.sf_color_entry, - "global_sel_line": self.ui.general_defaults_form.general_gui_group.sl_color_entry, - "global_alt_sel_fill": self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry, - "global_alt_sel_line": self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry, - "global_draw_color": self.ui.general_defaults_form.general_gui_group.draw_color_entry, - "global_sel_draw_color": self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry, - - "global_proj_item_color": self.ui.general_defaults_form.general_gui_group.proj_color_entry, - "global_proj_item_dis_color": self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry, - "global_project_autohide": self.ui.general_defaults_form.general_gui_group.project_autohide_cb, - - # General GUI Settings - "global_gridx": self.ui.general_defaults_form.general_app_set_group.gridx_entry, - "global_gridy": self.ui.general_defaults_form.general_app_set_group.gridy_entry, - "global_snap_max": self.ui.general_defaults_form.general_app_set_group.snap_max_dist_entry, - "global_workspace": self.ui.general_defaults_form.general_app_set_group.workspace_cb, - "global_workspaceT": self.ui.general_defaults_form.general_app_set_group.wk_cb, - "global_workspace_orientation": self.ui.general_defaults_form.general_app_set_group.wk_orientation_radio, - - "global_cursor_type": self.ui.general_defaults_form.general_app_set_group.cursor_radio, - "global_cursor_size": self.ui.general_defaults_form.general_app_set_group.cursor_size_entry, - "global_cursor_width": self.ui.general_defaults_form.general_app_set_group.cursor_width_entry, - "global_cursor_color_enabled": self.ui.general_defaults_form.general_app_set_group.mouse_cursor_color_cb, - "global_cursor_color": self.ui.general_defaults_form.general_app_set_group.mouse_cursor_entry, - "global_pan_button": self.ui.general_defaults_form.general_app_set_group.pan_button_radio, - "global_mselect_key": self.ui.general_defaults_form.general_app_set_group.mselect_radio, - "global_delete_confirmation": self.ui.general_defaults_form.general_app_set_group.delete_conf_cb, - "global_open_style": self.ui.general_defaults_form.general_app_set_group.open_style_cb, - "global_toggle_tooltips": self.ui.general_defaults_form.general_app_set_group.toggle_tooltips_cb, - "global_machinist_setting": self.ui.general_defaults_form.general_app_set_group.machinist_cb, - - "global_bookmarks_limit": self.ui.general_defaults_form.general_app_set_group.bm_limit_spinner, - "global_activity_icon": self.ui.general_defaults_form.general_app_set_group.activity_combo, - - # Gerber General - "gerber_plot": self.ui.gerber_defaults_form.gerber_gen_group.plot_cb, - "gerber_solid": self.ui.gerber_defaults_form.gerber_gen_group.solid_cb, - "gerber_multicolored": self.ui.gerber_defaults_form.gerber_gen_group.multicolored_cb, - "gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry, - "gerber_def_units": self.ui.gerber_defaults_form.gerber_gen_group.gerber_units_radio, - "gerber_def_zeros": self.ui.gerber_defaults_form.gerber_gen_group.gerber_zeros_radio, - "gerber_clean_apertures": self.ui.gerber_defaults_form.gerber_gen_group.gerber_clean_cb, - "gerber_extra_buffering": self.ui.gerber_defaults_form.gerber_gen_group.gerber_extra_buffering, - "gerber_plot_fill": self.ui.gerber_defaults_form.gerber_gen_group.pf_color_entry, - "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry, - - # Gerber Options - "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry, - "gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry, - "gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry, - "gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb, - "gerber_iso_scope": self.ui.gerber_defaults_form.gerber_opt_group.iso_scope_radio, - "gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio, - "gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry, - "gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb, - "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry, - "gerber_bboxrounded": self.ui.gerber_defaults_form.gerber_opt_group.bbrounded_cb, - - # Gerber Advanced Options - "gerber_aperture_display": self.ui.gerber_defaults_form.gerber_adv_opt_group.aperture_table_visibility_cb, - # "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry, - # "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry, - "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb, - "gerber_tool_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.tool_type_radio, - "gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner, - "gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner, - "gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner, - "gerber_iso_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.iso_type_radio, - - "gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio, - "gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb, - "gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner, - - # Gerber Export - "gerber_exp_units": self.ui.gerber_defaults_form.gerber_exp_group.gerber_units_radio, - "gerber_exp_integer": self.ui.gerber_defaults_form.gerber_exp_group.format_whole_entry, - "gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry, - "gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio, - - # Gerber Editor - "gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry, - "gerber_editor_newcode": self.ui.gerber_defaults_form.gerber_editor_group.addcode_entry, - "gerber_editor_newsize": self.ui.gerber_defaults_form.gerber_editor_group.addsize_entry, - "gerber_editor_newtype": self.ui.gerber_defaults_form.gerber_editor_group.addtype_combo, - "gerber_editor_newdim": self.ui.gerber_defaults_form.gerber_editor_group.adddim_entry, - "gerber_editor_array_size": self.ui.gerber_defaults_form.gerber_editor_group.grb_array_size_entry, - "gerber_editor_lin_axis": self.ui.gerber_defaults_form.gerber_editor_group.grb_axis_radio, - "gerber_editor_lin_pitch": self.ui.gerber_defaults_form.gerber_editor_group.grb_pitch_entry, - "gerber_editor_lin_angle": self.ui.gerber_defaults_form.gerber_editor_group.grb_angle_entry, - "gerber_editor_circ_dir": self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_dir_radio, - "gerber_editor_circ_angle": - self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_angle_entry, - "gerber_editor_scale_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_scale_entry, - "gerber_editor_buff_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_buff_entry, - "gerber_editor_ma_low": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_low_entry, - "gerber_editor_ma_high": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_high_entry, - - # Excellon General - "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb, - "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb, - "excellon_format_upper_in": - self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry, - "excellon_format_lower_in": - self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry, - "excellon_format_upper_mm": - self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry, - "excellon_format_lower_mm": - self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry, - "excellon_zeros": self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio, - "excellon_units": self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio, - "excellon_update": self.ui.excellon_defaults_form.excellon_gen_group.update_excellon_cb, - "excellon_optimization_type": self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio, - "excellon_search_time": self.ui.excellon_defaults_form.excellon_gen_group.optimization_time_entry, - "excellon_plot_fill": self.ui.excellon_defaults_form.excellon_gen_group.fill_color_entry, - "excellon_plot_line": self.ui.excellon_defaults_form.excellon_gen_group.line_color_entry, - - # Excellon Options - "excellon_operation": self.ui.excellon_defaults_form.excellon_opt_group.operation_radio, - "excellon_milling_type": self.ui.excellon_defaults_form.excellon_opt_group.milling_type_radio, - - "excellon_milling_dia": self.ui.excellon_defaults_form.excellon_opt_group.mill_dia_entry, - - "excellon_cutz": self.ui.excellon_defaults_form.excellon_opt_group.cutz_entry, - "excellon_multidepth": self.ui.excellon_defaults_form.excellon_opt_group.mpass_cb, - "excellon_depthperpass": self.ui.excellon_defaults_form.excellon_opt_group.maxdepth_entry, - "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry, - "excellon_endz": self.ui.excellon_defaults_form.excellon_opt_group.endz_entry, - "excellon_endxy": self.ui.excellon_defaults_form.excellon_opt_group.endxy_entry, - - "excellon_feedrate_z": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_z_entry, - "excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry, - "excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb, - "excellon_dwelltime": self.ui.excellon_defaults_form.excellon_opt_group.dwelltime_entry, - "excellon_toolchange": self.ui.excellon_defaults_form.excellon_opt_group.toolchange_cb, - "excellon_toolchangez": self.ui.excellon_defaults_form.excellon_opt_group.toolchangez_entry, - "excellon_ppname_e": self.ui.excellon_defaults_form.excellon_opt_group.pp_excellon_name_cb, - "excellon_tooldia": self.ui.excellon_defaults_form.excellon_opt_group.tooldia_entry, - "excellon_slot_tooldia": self.ui.excellon_defaults_form.excellon_opt_group.slot_tooldia_entry, - "excellon_gcode_type": self.ui.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio, - - # Excellon Advanced Options - "excellon_offset": self.ui.excellon_defaults_form.excellon_adv_opt_group.offset_entry, - "excellon_toolchangexy": self.ui.excellon_defaults_form.excellon_adv_opt_group.toolchangexy_entry, - "excellon_startz": self.ui.excellon_defaults_form.excellon_adv_opt_group.estartz_entry, - "excellon_feedrate_rapid": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_rapid_entry, - "excellon_z_pdepth": self.ui.excellon_defaults_form.excellon_adv_opt_group.pdepth_entry, - "excellon_feedrate_probe": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_probe_entry, - "excellon_spindledir": self.ui.excellon_defaults_form.excellon_adv_opt_group.spindledir_radio, - "excellon_f_plunge": self.ui.excellon_defaults_form.excellon_adv_opt_group.fplunge_cb, - "excellon_f_retract": self.ui.excellon_defaults_form.excellon_adv_opt_group.fretract_cb, - - # Excellon Export - "excellon_exp_units": self.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio, - "excellon_exp_format": self.ui.excellon_defaults_form.excellon_exp_group.format_radio, - "excellon_exp_integer": self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry, - "excellon_exp_decimals": self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry, - "excellon_exp_zeros": self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio, - "excellon_exp_slot_type": self.ui.excellon_defaults_form.excellon_exp_group.slot_type_radio, - - # Excellon Editor - "excellon_editor_sel_limit": self.ui.excellon_defaults_form.excellon_editor_group.sel_limit_entry, - "excellon_editor_newdia": self.ui.excellon_defaults_form.excellon_editor_group.addtool_entry, - "excellon_editor_array_size": self.ui.excellon_defaults_form.excellon_editor_group.drill_array_size_entry, - "excellon_editor_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_axis_radio, - "excellon_editor_lin_pitch": self.ui.excellon_defaults_form.excellon_editor_group.drill_pitch_entry, - "excellon_editor_lin_angle": self.ui.excellon_defaults_form.excellon_editor_group.drill_angle_entry, - "excellon_editor_circ_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_dir_radio, - "excellon_editor_circ_angle": - self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_angle_entry, - # Excellon Slots - "excellon_editor_slot_direction": - self.ui.excellon_defaults_form.excellon_editor_group.slot_axis_radio, - "excellon_editor_slot_angle": - self.ui.excellon_defaults_form.excellon_editor_group.slot_angle_spinner, - "excellon_editor_slot_length": - self.ui.excellon_defaults_form.excellon_editor_group.slot_length_entry, - # Excellon Slots - "excellon_editor_slot_array_size": - self.ui.excellon_defaults_form.excellon_editor_group.slot_array_size_entry, - "excellon_editor_slot_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.slot_array_axis_radio, - "excellon_editor_slot_lin_pitch": - self.ui.excellon_defaults_form.excellon_editor_group.slot_array_pitch_entry, - "excellon_editor_slot_lin_angle": - self.ui.excellon_defaults_form.excellon_editor_group.slot_array_angle_entry, - "excellon_editor_slot_circ_dir": - self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_dir_radio, - "excellon_editor_slot_circ_angle": - self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_angle_entry, - - # Geometry General - "geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb, - "geometry_circle_steps": self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry, - "geometry_cnctooldia": self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry, - "geometry_plot_line": self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry, - - # Geometry Options - "geometry_cutz": self.ui.geometry_defaults_form.geometry_opt_group.cutz_entry, - "geometry_travelz": self.ui.geometry_defaults_form.geometry_opt_group.travelz_entry, - "geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry, - "geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.feedrate_z_entry, - "geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry, - "geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb, - "geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry, - "geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb, - "geometry_toolchange": self.ui.geometry_defaults_form.geometry_opt_group.toolchange_cb, - "geometry_toolchangez": self.ui.geometry_defaults_form.geometry_opt_group.toolchangez_entry, - "geometry_endz": self.ui.geometry_defaults_form.geometry_opt_group.endz_entry, - "geometry_endxy": self.ui.geometry_defaults_form.geometry_opt_group.endxy_entry, - "geometry_depthperpass": self.ui.geometry_defaults_form.geometry_opt_group.depthperpass_entry, - "geometry_multidepth": self.ui.geometry_defaults_form.geometry_opt_group.multidepth_cb, - - # Geometry Advanced Options - "geometry_toolchangexy": self.ui.geometry_defaults_form.geometry_adv_opt_group.toolchangexy_entry, - "geometry_startz": self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry, - "geometry_feedrate_rapid": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_rapid_entry, - "geometry_extracut": self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb, - "geometry_extracut_length": self.ui.geometry_defaults_form.geometry_adv_opt_group.e_cut_entry, - "geometry_z_pdepth": self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry, - "geometry_feedrate_probe": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry, - "geometry_spindledir": self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio, - "geometry_f_plunge": self.ui.geometry_defaults_form.geometry_adv_opt_group.fplunge_cb, - "geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry, - "geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry, - - # Geometry Editor - "geometry_editor_sel_limit": self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry, - "geometry_editor_milling_type": self.ui.geometry_defaults_form.geometry_editor_group.milling_type_radio, - - # CNCJob General - "cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb, - "cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio, - "cncjob_annotation": self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_cb, - - "cncjob_tooldia": self.ui.cncjob_defaults_form.cncjob_gen_group.tooldia_entry, - "cncjob_coords_type": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_type_radio, - "cncjob_coords_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_dec_entry, - "cncjob_fr_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry, - "cncjob_steps_per_circle": self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry, - "cncjob_line_ending": self.ui.cncjob_defaults_form.cncjob_gen_group.line_ending_cb, - "cncjob_plot_line": self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry, - "cncjob_plot_fill": self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry, - "cncjob_travel_line": self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry, - "cncjob_travel_fill": self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry, - - # CNC Job Options - "cncjob_prepend": self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text, - "cncjob_append": self.ui.cncjob_defaults_form.cncjob_opt_group.append_text, - - # CNC Job Advanced Options - "cncjob_toolchange_macro": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text, - "cncjob_toolchange_macro_enable": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_cb, - "cncjob_annotation_fontsize": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp, - "cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry, - - # NCC Tool - "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry, - "tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio, - "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry, - "tools_nccmargin": self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry, - "tools_nccmethod": self.ui.tools_defaults_form.tools_ncc_group.ncc_method_combo, - "tools_nccconnect": self.ui.tools_defaults_form.tools_ncc_group.ncc_connect_cb, - "tools_ncccontour": self.ui.tools_defaults_form.tools_ncc_group.ncc_contour_cb, - "tools_nccrest": self.ui.tools_defaults_form.tools_ncc_group.ncc_rest_cb, - "tools_ncc_offset_choice": self.ui.tools_defaults_form.tools_ncc_group.ncc_choice_offset_cb, - "tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner, - "tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.select_combo, - "tools_ncc_area_shape": self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio, - "tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio, - "tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio, - "tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio, - "tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry, - "tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry, - "tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry, - "tools_nccnewdia": self.ui.tools_defaults_form.tools_ncc_group.newdia_entry, - - # CutOut Tool - "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry, - "tools_cutoutkind": self.ui.tools_defaults_form.tools_cutout_group.obj_kind_combo, - "tools_cutoutmargin": self.ui.tools_defaults_form.tools_cutout_group.cutout_margin_entry, - "tools_cutout_z": self.ui.tools_defaults_form.tools_cutout_group.cutz_entry, - "tools_cutout_depthperpass": self.ui.tools_defaults_form.tools_cutout_group.maxdepth_entry, - "tools_cutout_mdepth": self.ui.tools_defaults_form.tools_cutout_group.mpass_cb, - "tools_cutoutgapsize": self.ui.tools_defaults_form.tools_cutout_group.cutout_gap_entry, - "tools_gaps_ff": self.ui.tools_defaults_form.tools_cutout_group.gaps_combo, - "tools_cutout_convexshape": self.ui.tools_defaults_form.tools_cutout_group.convex_box, - - # Paint Area Tool - "tools_painttooldia": self.ui.tools_defaults_form.tools_paint_group.painttooldia_entry, - "tools_paintorder": self.ui.tools_defaults_form.tools_paint_group.paint_order_radio, - "tools_paintoverlap": self.ui.tools_defaults_form.tools_paint_group.paintoverlap_entry, - "tools_paintmargin": self.ui.tools_defaults_form.tools_paint_group.paintmargin_entry, - "tools_paintmethod": self.ui.tools_defaults_form.tools_paint_group.paintmethod_combo, - "tools_selectmethod": self.ui.tools_defaults_form.tools_paint_group.selectmethod_combo, - "tools_paint_area_shape": self.ui.tools_defaults_form.tools_paint_group.area_shape_radio, - "tools_pathconnect": self.ui.tools_defaults_form.tools_paint_group.pathconnect_cb, - "tools_paintcontour": self.ui.tools_defaults_form.tools_paint_group.contour_cb, - "tools_paint_plotting": self.ui.tools_defaults_form.tools_paint_group.paint_plotting_radio, - - "tools_paintrest": self.ui.tools_defaults_form.tools_paint_group.rest_cb, - "tools_painttool_type": self.ui.tools_defaults_form.tools_paint_group.tool_type_radio, - "tools_paintcutz": self.ui.tools_defaults_form.tools_paint_group.cutz_entry, - "tools_painttipdia": self.ui.tools_defaults_form.tools_paint_group.tipdia_entry, - "tools_painttipangle": self.ui.tools_defaults_form.tools_paint_group.tipangle_entry, - "tools_paintnewdia": self.ui.tools_defaults_form.tools_paint_group.newdia_entry, - - # 2-sided Tool - "tools_2sided_mirror_axis": self.ui.tools_defaults_form.tools_2sided_group.mirror_axis_radio, - "tools_2sided_axis_loc": self.ui.tools_defaults_form.tools_2sided_group.axis_location_radio, - "tools_2sided_drilldia": self.ui.tools_defaults_form.tools_2sided_group.drill_dia_entry, - "tools_2sided_allign_axis": self.ui.tools_defaults_form.tools_2sided_group.align_axis_radio, - - # Film Tool - "tools_film_type": self.ui.tools_defaults_form.tools_film_group.film_type_radio, - "tools_film_boundary": self.ui.tools_defaults_form.tools_film_group.film_boundary_entry, - "tools_film_scale_stroke": self.ui.tools_defaults_form.tools_film_group.film_scale_stroke_entry, - "tools_film_color": self.ui.tools_defaults_form.tools_film_group.film_color_entry, - "tools_film_scale_cb": self.ui.tools_defaults_form.tools_film_group.film_scale_cb, - "tools_film_scale_x_entry": self.ui.tools_defaults_form.tools_film_group.film_scalex_entry, - "tools_film_scale_y_entry": self.ui.tools_defaults_form.tools_film_group.film_scaley_entry, - "tools_film_skew_cb": self.ui.tools_defaults_form.tools_film_group.film_skew_cb, - "tools_film_skew_x_entry": self.ui.tools_defaults_form.tools_film_group.film_skewx_entry, - "tools_film_skew_y_entry": self.ui.tools_defaults_form.tools_film_group.film_skewy_entry, - "tools_film_skew_ref_radio": self.ui.tools_defaults_form.tools_film_group.film_skew_reference, - "tools_film_mirror_cb": self.ui.tools_defaults_form.tools_film_group.film_mirror_cb, - "tools_film_mirror_axis_radio": self.ui.tools_defaults_form.tools_film_group.film_mirror_axis, - "tools_film_file_type_radio": self.ui.tools_defaults_form.tools_film_group.file_type_radio, - "tools_film_orientation": self.ui.tools_defaults_form.tools_film_group.orientation_radio, - "tools_film_pagesize": self.ui.tools_defaults_form.tools_film_group.pagesize_combo, - - # Panelize Tool - "tools_panelize_spacing_columns": self.ui.tools_defaults_form.tools_panelize_group.pspacing_columns, - "tools_panelize_spacing_rows": self.ui.tools_defaults_form.tools_panelize_group.pspacing_rows, - "tools_panelize_columns": self.ui.tools_defaults_form.tools_panelize_group.pcolumns, - "tools_panelize_rows": self.ui.tools_defaults_form.tools_panelize_group.prows, - "tools_panelize_constrain": self.ui.tools_defaults_form.tools_panelize_group.pconstrain_cb, - "tools_panelize_constrainx": self.ui.tools_defaults_form.tools_panelize_group.px_width_entry, - "tools_panelize_constrainy": self.ui.tools_defaults_form.tools_panelize_group.py_height_entry, - "tools_panelize_panel_type": self.ui.tools_defaults_form.tools_panelize_group.panel_type_radio, - - # Calculators Tool - "tools_calc_vshape_tip_dia": self.ui.tools_defaults_form.tools_calculators_group.tip_dia_entry, - "tools_calc_vshape_tip_angle": self.ui.tools_defaults_form.tools_calculators_group.tip_angle_entry, - "tools_calc_vshape_cut_z": self.ui.tools_defaults_form.tools_calculators_group.cut_z_entry, - "tools_calc_electro_length": self.ui.tools_defaults_form.tools_calculators_group.pcblength_entry, - "tools_calc_electro_width": self.ui.tools_defaults_form.tools_calculators_group.pcbwidth_entry, - "tools_calc_electro_cdensity": self.ui.tools_defaults_form.tools_calculators_group.cdensity_entry, - "tools_calc_electro_growth": self.ui.tools_defaults_form.tools_calculators_group.growth_entry, - - # Transformations Tool - "tools_transform_rotate": self.ui.tools_defaults_form.tools_transform_group.rotate_entry, - "tools_transform_skew_x": self.ui.tools_defaults_form.tools_transform_group.skewx_entry, - "tools_transform_skew_y": self.ui.tools_defaults_form.tools_transform_group.skewy_entry, - "tools_transform_scale_x": self.ui.tools_defaults_form.tools_transform_group.scalex_entry, - "tools_transform_scale_y": self.ui.tools_defaults_form.tools_transform_group.scaley_entry, - "tools_transform_scale_link": self.ui.tools_defaults_form.tools_transform_group.link_cb, - "tools_transform_scale_reference": self.ui.tools_defaults_form.tools_transform_group.reference_cb, - "tools_transform_offset_x": self.ui.tools_defaults_form.tools_transform_group.offx_entry, - "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry, - "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb, - "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry, - "tools_transform_buffer_dis": self.ui.tools_defaults_form.tools_transform_group.buffer_entry, - "tools_transform_buffer_factor": self.ui.tools_defaults_form.tools_transform_group.buffer_factor_entry, - "tools_transform_buffer_corner": self.ui.tools_defaults_form.tools_transform_group.buffer_rounded_cb, - - # SolderPaste Dispensing Tool - "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry, - "tools_solderpaste_new": self.ui.tools_defaults_form.tools_solderpaste_group.addtool_entry, - "tools_solderpaste_z_start": self.ui.tools_defaults_form.tools_solderpaste_group.z_start_entry, - "tools_solderpaste_z_dispense": self.ui.tools_defaults_form.tools_solderpaste_group.z_dispense_entry, - "tools_solderpaste_z_stop": self.ui.tools_defaults_form.tools_solderpaste_group.z_stop_entry, - "tools_solderpaste_z_travel": self.ui.tools_defaults_form.tools_solderpaste_group.z_travel_entry, - "tools_solderpaste_z_toolchange": self.ui.tools_defaults_form.tools_solderpaste_group.z_toolchange_entry, - "tools_solderpaste_xy_toolchange": self.ui.tools_defaults_form.tools_solderpaste_group.xy_toolchange_entry, - "tools_solderpaste_frxy": self.ui.tools_defaults_form.tools_solderpaste_group.frxy_entry, - "tools_solderpaste_frz": self.ui.tools_defaults_form.tools_solderpaste_group.frz_entry, - "tools_solderpaste_frz_dispense": self.ui.tools_defaults_form.tools_solderpaste_group.frz_dispense_entry, - "tools_solderpaste_speedfwd": self.ui.tools_defaults_form.tools_solderpaste_group.speedfwd_entry, - "tools_solderpaste_dwellfwd": self.ui.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry, - "tools_solderpaste_speedrev": self.ui.tools_defaults_form.tools_solderpaste_group.speedrev_entry, - "tools_solderpaste_dwellrev": self.ui.tools_defaults_form.tools_solderpaste_group.dwellrev_entry, - "tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo, - "tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb, - - # ################################################################################### - # ################################ TOOLS 2 ########################################## - # ################################################################################### - - # Optimal Tool - "tools_opt_precision": self.ui.tools2_defaults_form.tools2_optimal_group.precision_sp, - - # Check Rules Tool - "tools_cr_trace_size": self.ui.tools2_defaults_form.tools2_checkrules_group.trace_size_cb, - "tools_cr_trace_size_val": self.ui.tools2_defaults_form.tools2_checkrules_group.trace_size_entry, - "tools_cr_c2c": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2copper_cb, - "tools_cr_c2c_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2copper_entry, - "tools_cr_c2o": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2ol_cb, - "tools_cr_c2o_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2ol_entry, - "tools_cr_s2s": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2silk_cb, - "tools_cr_s2s_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2silk_entry, - "tools_cr_s2sm": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2sm_cb, - "tools_cr_s2sm_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2sm_entry, - "tools_cr_s2o": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2ol_cb, - "tools_cr_s2o_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2ol_entry, - "tools_cr_sm2sm": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_sm2sm_cb, - "tools_cr_sm2sm_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_sm2sm_entry, - "tools_cr_ri": self.ui.tools2_defaults_form.tools2_checkrules_group.ring_integrity_cb, - "tools_cr_ri_val": self.ui.tools2_defaults_form.tools2_checkrules_group.ring_integrity_entry, - "tools_cr_h2h": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_d2d_cb, - "tools_cr_h2h_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_d2d_entry, - "tools_cr_dh": self.ui.tools2_defaults_form.tools2_checkrules_group.drill_size_cb, - "tools_cr_dh_val": self.ui.tools2_defaults_form.tools2_checkrules_group.drill_size_entry, - - # QRCode Tool - "tools_qrcode_version": self.ui.tools2_defaults_form.tools2_qrcode_group.version_entry, - "tools_qrcode_error": self.ui.tools2_defaults_form.tools2_qrcode_group.error_radio, - "tools_qrcode_box_size": self.ui.tools2_defaults_form.tools2_qrcode_group.bsize_entry, - "tools_qrcode_border_size": self.ui.tools2_defaults_form.tools2_qrcode_group.border_size_entry, - "tools_qrcode_qrdata": self.ui.tools2_defaults_form.tools2_qrcode_group.text_data, - "tools_qrcode_polarity": self.ui.tools2_defaults_form.tools2_qrcode_group.pol_radio, - "tools_qrcode_rounded": self.ui.tools2_defaults_form.tools2_qrcode_group.bb_radio, - "tools_qrcode_fill_color": self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry, - "tools_qrcode_back_color": self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry, - "tools_qrcode_sel_limit": self.ui.tools2_defaults_form.tools2_qrcode_group.sel_limit_entry, - - # Copper Thieving Tool - "tools_copper_thieving_clearance": self.ui.tools2_defaults_form.tools2_cfill_group.clearance_entry, - "tools_copper_thieving_margin": self.ui.tools2_defaults_form.tools2_cfill_group.margin_entry, - "tools_copper_thieving_reference": self.ui.tools2_defaults_form.tools2_cfill_group.reference_radio, - "tools_copper_thieving_box_type": self.ui.tools2_defaults_form.tools2_cfill_group.bbox_type_radio, - "tools_copper_thieving_circle_steps": self.ui.tools2_defaults_form.tools2_cfill_group.circlesteps_entry, - "tools_copper_thieving_fill_type": self.ui.tools2_defaults_form.tools2_cfill_group.fill_type_radio, - "tools_copper_thieving_dots_dia": self.ui.tools2_defaults_form.tools2_cfill_group.dot_dia_entry, - "tools_copper_thieving_dots_spacing": self.ui.tools2_defaults_form.tools2_cfill_group.dot_spacing_entry, - "tools_copper_thieving_squares_size": self.ui.tools2_defaults_form.tools2_cfill_group.square_size_entry, - "tools_copper_thieving_squares_spacing": - self.ui.tools2_defaults_form.tools2_cfill_group.squares_spacing_entry, - "tools_copper_thieving_lines_size": self.ui.tools2_defaults_form.tools2_cfill_group.line_size_entry, - "tools_copper_thieving_lines_spacing": self.ui.tools2_defaults_form.tools2_cfill_group.lines_spacing_entry, - "tools_copper_thieving_rb_margin": self.ui.tools2_defaults_form.tools2_cfill_group.rb_margin_entry, - "tools_copper_thieving_rb_thickness": self.ui.tools2_defaults_form.tools2_cfill_group.rb_thickness_entry, - "tools_copper_thieving_mask_clearance": self.ui.tools2_defaults_form.tools2_cfill_group.clearance_ppm_entry, - - # Fiducials Tool - "tools_fiducials_dia": self.ui.tools2_defaults_form.tools2_fiducials_group.dia_entry, - "tools_fiducials_margin": self.ui.tools2_defaults_form.tools2_fiducials_group.margin_entry, - "tools_fiducials_mode": self.ui.tools2_defaults_form.tools2_fiducials_group.mode_radio, - "tools_fiducials_second_pos": self.ui.tools2_defaults_form.tools2_fiducials_group.pos_radio, - "tools_fiducials_type": self.ui.tools2_defaults_form.tools2_fiducials_group.fid_type_radio, - "tools_fiducials_line_thickness": self.ui.tools2_defaults_form.tools2_fiducials_group.line_thickness_entry, - - # Calibration Tool - "tools_cal_calsource": self.ui.tools2_defaults_form.tools2_cal_group.cal_source_radio, - "tools_cal_travelz": self.ui.tools2_defaults_form.tools2_cal_group.travelz_entry, - "tools_cal_verz": self.ui.tools2_defaults_form.tools2_cal_group.verz_entry, - "tools_cal_zeroz": self.ui.tools2_defaults_form.tools2_cal_group.zeroz_cb, - "tools_cal_toolchangez": self.ui.tools2_defaults_form.tools2_cal_group.toolchangez_entry, - "tools_cal_toolchange_xy": self.ui.tools2_defaults_form.tools2_cal_group.toolchange_xy_entry, - "tools_cal_sec_point": self.ui.tools2_defaults_form.tools2_cal_group.second_point_radio, - - # Extract Drills Tool - "tools_edrills_hole_type": self.ui.tools2_defaults_form.tools2_edrills_group.hole_size_radio, - "tools_edrills_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_edrills_group.dia_entry, - "tools_edrills_hole_prop_factor": self.ui.tools2_defaults_form.tools2_edrills_group.factor_entry, - "tools_edrills_circular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.circular_ring_entry, - "tools_edrills_oblong_ring": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_ring_entry, - "tools_edrills_square_ring": self.ui.tools2_defaults_form.tools2_edrills_group.square_ring_entry, - "tools_edrills_rectangular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_ring_entry, - "tools_edrills_others_ring": self.ui.tools2_defaults_form.tools2_edrills_group.other_ring_entry, - "tools_edrills_circular": self.ui.tools2_defaults_form.tools2_edrills_group.circular_cb, - "tools_edrills_oblong": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_cb, - "tools_edrills_square": self.ui.tools2_defaults_form.tools2_edrills_group.square_cb, - "tools_edrills_rectangular": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_cb, - "tools_edrills_others": self.ui.tools2_defaults_form.tools2_edrills_group.other_cb, - - # Punch Gerber Tool - "tools_punch_hole_type": self.ui.tools2_defaults_form.tools2_punch_group.hole_size_radio, - "tools_punch_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_punch_group.dia_entry, - "tools_punch_hole_prop_factor": self.ui.tools2_defaults_form.tools2_punch_group.factor_entry, - "tools_punch_circular_ring": self.ui.tools2_defaults_form.tools2_punch_group.circular_ring_entry, - "tools_punch_oblong_ring": self.ui.tools2_defaults_form.tools2_punch_group.oblong_ring_entry, - "tools_punch_square_ring": self.ui.tools2_defaults_form.tools2_punch_group.square_ring_entry, - "tools_punch_rectangular_ring": self.ui.tools2_defaults_form.tools2_punch_group.rectangular_ring_entry, - "tools_punch_others_ring": self.ui.tools2_defaults_form.tools2_punch_group.other_ring_entry, - "tools_punch_circular": self.ui.tools2_defaults_form.tools2_punch_group.circular_cb, - "tools_punch_oblong": self.ui.tools2_defaults_form.tools2_punch_group.oblong_cb, - "tools_punch_square": self.ui.tools2_defaults_form.tools2_punch_group.square_cb, - "tools_punch_rectangular": self.ui.tools2_defaults_form.tools2_punch_group.rectangular_cb, - "tools_punch_others": self.ui.tools2_defaults_form.tools2_punch_group.other_cb, - - # Invert Gerber Tool - "tools_invert_margin": self.ui.tools2_defaults_form.tools2_invert_group.margin_entry, - "tools_invert_join_style": self.ui.tools2_defaults_form.tools2_invert_group.join_radio, - - # Utilities - # File associations - "fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text, - "fa_gcode": self.ui.util_defaults_form.fa_gcode_group.gco_list_text, - # "fa_geometry": self.ui.util_defaults_form.fa_geometry_group.close_paths_cb, - "fa_gerber": self.ui.util_defaults_form.fa_gerber_group.grb_list_text, - "util_autocomplete_keywords": self.ui.util_defaults_form.kw_group.kw_list_text, - - } - - # update the Preferences GUI elements with the values in the self.defaults - self.defaults_write_form() + self.preferencesUiManager = PreferencesUIManager(defaults=self.defaults, data_path=self.data_path, ui=self.ui, + inform=self.inform) + self.preferencesUiManager.defaults_write_form() # When the self.defaults dictionary changes will update the Preferences GUI forms self.defaults.set_change_callback(self.on_defaults_dict_change) - # ############################################################################## - # ########################## FIRST RUN SECTION ################################# - # ##################### It's done only once after install #################### - # ############################################################################## + # ########################################################################################################### + # ##################################### FIRST RUN SECTION ################################################### + # ################################ It's done only once after install ##################################### + # ########################################################################################################### if self.defaults["first_run"] is True: - self.save_factory_defaults(silent_message=False) - # and then make the factory_defaults.FlatConfig file read_only so it can't be modified after creation. - filename_factory = self.data_path + '/factory_defaults.FlatConfig' - os.chmod(filename_factory, S_IREAD | S_IRGRP | S_IROTH) # ONLY AT FIRST STARTUP INIT THE GUI LAYOUT TO 'COMPACT' - initial_lay = 'compact' + initial_lay = 'minimal' self.ui.general_defaults_form.general_gui_group.on_layout(lay=initial_lay) # Set the combobox in Preferences to the current layout @@ -1745,11 +565,11 @@ class App(QtCore.QObject): # after the first run, this object should be False self.defaults["first_run"] = False - self.save_defaults(silent=True) + self.preferencesUiManager.save_defaults(silent=True) - # ############################################################################# - # ############################## Data ######################################### - # ############################################################################# + # ########################################################################################################### + # ############################################ Data ######################################################### + # ########################################################################################################### self.recent = [] self.recent_projects = [] @@ -1759,9 +579,9 @@ class App(QtCore.QObject): self.project_filename = None self.toggle_units_ignore = False - # ############################################################################# - # ########################## LOAD PREPROCESSORS ############################### - # ############################################################################# + # ########################################################################################################### + # #################################### LOAD PREPROCESSORS ################################################### + # ########################################################################################################### # a dictionary that have as keys the name of the preprocessor files and the value is the class from # the preprocessor file @@ -1795,84 +615,59 @@ class App(QtCore.QObject): self.ui.excellon_defaults_form.excellon_opt_group.pp_excellon_name_cb.addItem(name) - # ############################################################################# - # ########################## LOAD LANGUAGES ################################## - # ############################################################################# + # ########################################################################################################### + # ########################################## LOAD LANGUAGES ################################################ + # ########################################################################################################### self.languages = fcTranslate.load_languages() for name in sorted(self.languages.values()): self.ui.general_defaults_form.general_app_group.language_cb.addItem(name) - # ############################################################################# - # ############################ APPLY APP LANGUAGE ############################# - # ############################################################################# + # ########################################################################################################### + # ####################################### APPLY APP LANGUAGE ################################################ + # ########################################################################################################### ret_val = fcTranslate.apply_language('strings') if ret_val == "no language": - self.inform.emit('[ERROR] %s' % - _("Could not find the Language files. The App strings are missing.")) + self.inform.emit('[ERROR] %s' % _("Could not find the Language files. The App strings are missing.")) log.debug("Could not find the Language files. The App strings are missing.") else: # make the current language the current selection on the language combobox self.ui.general_defaults_form.general_app_group.language_cb.setCurrentText(ret_val) log.debug("App.__init__() --> Applied %s language." % str(ret_val).capitalize()) - # ############################################################################# - # ########################### CREATE UNIQUE SERIAL NUMBER ##################### - # ############################################################################# + # ########################################################################################################### + # ###################################### CREATE UNIQUE SERIAL NUMBER ######################################## + # ########################################################################################################### chars = 'abcdefghijklmnopqrstuvwxyz0123456789' if self.defaults['global_serial'] == 0 or len(str(self.defaults['global_serial'])) < 10: self.defaults['global_serial'] = ''.join([random.choice(chars) for __ in range(20)]) - self.save_defaults(silent=True, first_time=True) + self.preferencesUiManager.save_defaults(silent=True, first_time=True) - self.propagate_defaults(silent=True) + self.defaults.propagate_defaults() - # def auto_save_defaults(): - # try: - # self.save_defaults(silent=True) - # self.propagate_defaults(silent=True) - # finally: - # QtCore.QTimer.singleShot(self.defaults["global_defaults_save_period_ms"], auto_save_defaults) - - # the following lines activates automatic defaults save - # if user_defaults: - # QtCore.QTimer.singleShot(self.defaults["global_defaults_save_period_ms"], auto_save_defaults) - - # ############################################################################# - # ########################### UPDATE THE OPTIONS ############################## - # ############################################################################# + # ########################################################################################################### + # ######################################## UPDATE THE OPTIONS ############################################### + # ########################################################################################################### self.options = LoudDict() - # ---------------------------------------------------------------------------------------------------- + # ----------------------------------------------------------------------------------------------------------- # Update the self.options from the self.defaults # The self.defaults holds the application defaults while the self.options holds the object defaults - # ----------------------------------------------------------------------------------------------------- + # ----------------------------------------------------------------------------------------------------------- # Copy app defaults to project options for def_key, def_val in self.defaults.items(): self.options[def_key] = deepcopy(def_val) - # self.options.update(self.defaults) - self.gen_form = None - self.ger_form = None - self.exc_form = None - self.geo_form = None - self.cnc_form = None - self.tools_form = None - self.tools2_form = None - self.fa_form = None - - # Will show the Preferences GUI - self.show_preferences_gui() - # Initialize the color box's color in Preferences -> Global -> Color - self.init_color_pickers_in_preferences_gui() + self.preferencesUiManager.show_preferences_gui() # ### End of Data #### - # ############################################################################# - # ######################## SETUP OBJECT COLLECTION ############################ - # ############################################################################# + # ########################################################################################################### + # #################################### SETUP OBJECT COLLECTION ############################################## + # ########################################################################################################### self.collection = ObjectCollection(self) self.ui.project_tab_layout.addWidget(self.collection.view) @@ -1883,9 +678,9 @@ class App(QtCore.QObject): self.collection.view.setMinimumWidth(290) self.log.debug("Finished creating Object Collection.") - # ############################################################################# - # ############################## SETUP Plot Area ############################## - # ############################################################################# + # ########################################################################################################### + # ######################################## SETUP Plot Area ################################################## + # ########################################################################################################### # determine if the Legacy Graphic Engine is to be used or the OpenGL one if self.defaults["global_graphic_engine"] == '3D': @@ -1932,9 +727,9 @@ class App(QtCore.QObject): color=QtGui.QColor("gray")) self.ui.splitter.setStretchFactor(1, 2) - # ############################################################################# - # ################################### SYS TRAY ################################ - # ############################################################################# + # ########################################################################################################### + # ############################################### SYS TRAY ################################################## + # ########################################################################################################### if self.defaults["global_systray_icon"]: self.parent_w = QtWidgets.QWidget() @@ -1950,9 +745,9 @@ class App(QtCore.QObject): '/flatcam_icon32_green.png'), parent=self.parent_w) - # ############################################################################# - # ################################## Worker SETUP ############################# - # ############################################################################# + # ########################################################################################################### + # ############################################### Worker SETUP ############################################## + # ########################################################################################################### if self.defaults["global_worker_number"]: self.workers = WorkerStack(workers_number=int(self.defaults["global_worker_number"])) else: @@ -1960,27 +755,18 @@ class App(QtCore.QObject): self.worker_task.connect(self.workers.add_task) self.log.debug("Finished creating Workers crew.") - # ############################################################################# - # ################################ AUTOSAVE SETUP ############################# - # ############################################################################# - - self.block_autosave = False - self.autosave_timer = QtCore.QTimer(self) - self.save_project_auto_update() - self.autosave_timer.timeout.connect(self.save_project_auto) - - # ############################################################################# - # ################################# Activity Monitor ########################## - # ############################################################################# + # ########################################################################################################### + # ############################################# Activity Monitor ########################################### + # ########################################################################################################### self.activity_view = FlatCAMActivityView(app=self) self.ui.infobar.addWidget(self.activity_view) self.proc_container = FCVisibleProcessContainer(self.activity_view) - # ############################################################################# - # ################################## Signal handling ########################## - # ############################################################################# + # ########################################################################################################### + # ############################################# Signal handling ############################################# + # ########################################################################################################### - # ################################# Custom signals ########################### + # ########################################## Custom signals ################################################ # signal for displaying messages in status bar self.inform.connect(self.info) # signal to be called when the app is quiting @@ -1999,7 +785,7 @@ class App(QtCore.QObject): self.file_opened.connect(lambda kind, filename: self.register_folder(filename)) self.file_saved.connect(lambda kind, filename: self.register_save_folder(filename)) - # ############# Standard signals ################### + # ########################################## Standard signals ############################################### # ### Menu self.ui.menufilenewproject.triggered.connect(self.on_file_new_click) self.ui.menufilenewgeo.triggered.connect(self.new_geometry_object) @@ -2015,6 +801,7 @@ class App(QtCore.QObject): self.ui.menufilenewscript.triggered.connect(self.on_filenewscript) self.ui.menufileopenscript.triggered.connect(self.on_fileopenscript) + self.ui.menufileopenscriptexample.triggered.connect(self.on_fileopenscript_example) self.ui.menufilerunscript.triggered.connect(self.on_filerunscript) @@ -2163,21 +950,14 @@ class App(QtCore.QObject): for act in self.ui.menuprojectcolor.actions(): act.triggered.connect(self.on_set_color_action_triggered) - # Preferences Plot Area TAB - self.ui.pref_save_button.clicked.connect(lambda: self.on_save_button(save_to_file=True)) - self.ui.pref_apply_button.clicked.connect(lambda: self.on_save_button(save_to_file=False)) - self.ui.pref_close_button.clicked.connect(self.on_pref_close_button) - - self.ui.pref_defaults_button.clicked.connect(self.on_restore_defaults_preferences) - - # ############################################################################# - # ######################### GUI PREFERENCES SIGNALS ########################### - # ############################################################################# + # ########################################################################################################### + # #################################### GUI PREFERENCES SIGNALS ############################################## + # ########################################################################################################### self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect( lambda: self.on_toggle_units(no_pref=False)) - # ############################# Workspace Setting Signals ##################### + # ##################################### Workspace Setting Signals ########################################### self.ui.general_defaults_form.general_app_set_group.wk_cb.currentIndexChanged.connect( self.on_workspace_modified) self.ui.general_defaults_form.general_app_set_group.wk_orientation_radio.activated_custom.connect( @@ -2186,13 +966,13 @@ class App(QtCore.QObject): self.ui.general_defaults_form.general_app_set_group.workspace_cb.stateChanged.connect(self.on_workspace) - # ############################################################################# - # ############################# GUI SETTINGS SIGNALS ########################## - # ############################################################################# + # ########################################################################################################### + # ######################################## GUI SETTINGS SIGNALS ############################################# + # ########################################################################################################### self.ui.general_defaults_form.general_app_group.ge_radio.activated_custom.connect(self.on_app_restart) self.ui.general_defaults_form.general_app_set_group.cursor_radio.activated_custom.connect(self.on_cursor_type) - # ########## Tools related signals ############# + # ######################################## Tools related signals ############################################ # Film Tool self.ui.tools_defaults_form.tools_film_group.film_color_entry.editingFinished.connect( self.on_film_color_entry) @@ -2230,9 +1010,9 @@ 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 ######################## - # ############################################################################# + # ########################################################################################################### + # ####################################### FILE ASSOCIATIONS SIGNALS ######################################### + # ########################################################################################################### self.ui.util_defaults_form.fa_excellon_group.restore_btn.clicked.connect( lambda: self.restore_extensions(ext_type='excellon')) @@ -2270,9 +1050,9 @@ class App(QtCore.QObject): self.ui.util_defaults_form.fa_gerber_group.grb_list_btn.clicked.connect( lambda: self.on_register_files(obj_type='gerber')) - # ############################################################################# - # ################################ KEYWORDS SIGNALS ########################### - # ############################################################################# + # ########################################################################################################### + # ########################################### KEYWORDS SIGNALS ############################################## + # ########################################################################################################### self.ui.util_defaults_form.kw_group.restore_btn.clicked.connect( lambda: self.restore_extensions(ext_type='keyword')) self.ui.util_defaults_form.kw_group.del_all_btn.clicked.connect( @@ -2293,14 +1073,16 @@ class App(QtCore.QObject): # signal to close the application self.close_app_signal.connect(self.kill_app) - # ##################################################################################### - # ########### FINISHED CONNECTING SIGNALS ############################################# - # ##################################################################################### + # ################################# FINISHED CONNECTING SIGNALS ############################################# + # ########################################################################################################### + # ########################################################################################################### + # ########################################################################################################### + self.log.debug("Finished connecting Signals.") - # ##################################################################################### - # ########################## Other setups ############################################# - # ##################################################################################### + # ########################################################################################################### + # ########################################## Other setups ################################################### + # ########################################################################################################### # to use for tools like Distance tool who depends on the event sources who are changed inside the Editors # depending on from where those tools are called different actions can be done @@ -2318,23 +1100,24 @@ class App(QtCore.QObject): # Sets up FlatCAMObj, FCProcess and FCProcessContainer. self.setup_component_editor() - # ##################################################################################### - # ######################### Auto-complete KEYWORDS #################################### - # ##################################################################################### + # ########################################################################################################### + # ####################################### Auto-complete KEYWORDS ############################################ + # ########################################################################################################### self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle', 'aligndrill', 'aligndrillgrid', 'bbox', '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_bounds', 'get_names', 'get_sys', 'help', 'import_svg', + 'geo_union', 'geocutout', 'get_bounds', 'get_names', 'get_path', 'get_sys', 'help', 'interiors', 'isolate', 'join_excellon', 'join_geometry', 'list_sys', 'milld', 'mills', 'milldrills', 'millslots', 'mirror', 'ncc', 'ncr', 'new', 'new_geometry', 'non_copper_regions', 'offset', - 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'options', 'origin', + 'open_dxf', 'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'open_svg', + 'options', 'origin', 'paint', 'panelize', 'plot_all', 'plot_objects', 'plot_status', 'quit_flatcam', 'save', 'save_project', - 'save_sys', 'scale', 'set_active', 'set_origin', 'set_sys', + 'save_sys', 'scale', 'set_active', 'set_origin', 'set_path', 'set_sys', 'skew', 'subtract_poly', 'subtract_rectangle', 'version', 'write_gcode' ] @@ -2550,26 +1333,14 @@ class App(QtCore.QObject): self.autocomplete_kw_list = self.defaults['util_autocomplete_keywords'].replace(' ', '').split(',') self.myKeywords = self.tcl_commands_list + self.autocomplete_kw_list + self.tcl_keywords - # #################################################################################### - # ####################### Shell SETUP ################################################ - # #################################################################################### - # this will hold the TCL instance - self.tcl = None + # ########################################################################################################### + # ############################################## Shell SETUP ################################################ + # ########################################################################################################### - # the actual variable will be redeclared in setup_tcl() - self.tcl_commands_storage = None - - self.init_tcl() - - self.shell = FCShell(self, version=self.version) - self.shell._edit.set_model_data(self.myKeywords) - self.shell.setWindowIcon(self.ui.app_icon) - self.shell.setWindowTitle("FlatCAM Shell") - self.shell.resize(*self.defaults["global_shell_shape"]) - self.shell._append_to_browser('in', "FlatCAM %s - " % self.version) - self.shell.append_output('%s\n\n' % _("Type >help< to get started")) + self.shell = FCShell(app=self, version=self.version) self.ui.shell_dock.setWidget(self.shell) + self.log.debug("TCL Shell has been initialized.") # show TCL shell at start-up based on the Menu -? Edit -> Preferences setting. if self.defaults["global_shell_at_startup"]: @@ -2577,9 +1348,9 @@ class App(QtCore.QObject): else: self.ui.shell_dock.hide() - # ################################################################################## - # ###################### Tools and Plugins ######################################### - # ################################################################################## + # ########################################################################################################### + # ########################################## Tools and Plugins ############################################## + # ########################################################################################################### self.dblsidedtool = None self.distance_tool = None @@ -2615,22 +1386,22 @@ class App(QtCore.QObject): except AttributeError as e: log.debug("App.__init__() install tools() --> %s" % str(e)) - # ################################################################################## - # ########################### SETUP RECENT ITEMS ################################### - # ################################################################################## + # ########################################################################################################### + # ############################################ SETUP RECENT ITEMS ########################################### + # ########################################################################################################### self.setup_recent_items() - # ################################################################################## - # ########################### BookMarks Manager #################################### - # ################################################################################## + # ########################################################################################################### + # ######################################### BookMarks Manager ############################################### + # ########################################################################################################### # install Bookmark Manager and populate bookmarks in the Help -> Bookmarks self.install_bookmarks() self.book_dialog_tab = BookmarkManager(app=self, storage=self.defaults["global_bookmarks"]) - # ################################################################################## - # ############################## Tools Database #################################### - # ################################################################################## + # ########################################################################################################### + # ########################################### Tools Database ################################################ + # ########################################################################################################### self.tools_db_tab = None @@ -2638,9 +1409,9 @@ class App(QtCore.QObject): # self.f_parse = ParseFont(self) # self.parse_system_fonts() - # ##################################################################################### - # ######################## Check for updates ########################################## - # ##################################################################################### + # ########################################################################################################### + # ######################################### Check for updates ############################################### + # ########################################################################################################### # Separate thread (Not worker) # Check for updates on startup but only if the user consent and the app is not in Beta version @@ -2648,21 +1419,21 @@ class App(QtCore.QObject): self.ui.general_defaults_form.general_app_group.version_check_cb.get_value() is True: App.log.info("Checking for updates in backgroud (this is version %s)." % str(self.version)) - self.thr2 = QtCore.QThread() + # self.thr2 = QtCore.QThread() self.worker_task.emit({'fcn': self.version_check, 'params': []}) - self.thr2.start(QtCore.QThread.LowPriority) + # self.thr2.start(QtCore.QThread.LowPriority) - # ##################################################################################### - # ######################### Register files with FlatCAM; ############################# - # ######################### It works only for Windows for now ######################## - # ##################################################################################### + # ########################################################################################################### + # ##################################### Register files with FlatCAM; ####################################### + # ################################### It works only for Windows for now #################################### + # ########################################################################################################### if sys.platform == 'win32' and self.defaults["first_run"] is True: self.on_register_files() - # ##################################################################################### - # ###################### Variables for global usage ################################### - # ##################################################################################### + # ########################################################################################################### + # ######################################## Variables for global usage ####################################### + # ########################################################################################################### # hold the App units self.units = 'MM' @@ -2735,9 +1506,6 @@ class App(QtCore.QObject): self.reference_code_editor = None self.script_code = '' - # if Preferences are changed in the Edit -> Preferences tab the value will be set to True - self.preferences_changed_flag = False - # 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 @@ -2797,9 +1565,9 @@ class App(QtCore.QObject): # used in the delayed shutdown self.start_delayed_quit() method self.save_timer = None - # ############################################################################### - # ################# ADDING FlatCAM EDITORS section ############################## - # ############################################################################### + # ########################################################################################################### + # ################################## ADDING FlatCAM EDITORS section ######################################### + # ########################################################################################################### # watch out for the position of the editors 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. @@ -2827,14 +1595,14 @@ class App(QtCore.QObject): self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio.set_value('T') self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio.setDisabled(True) - # ############################################################################### - # ####################### Finished the CONSTRUCTOR ############################## - # ############################################################################### + # ########################################################################################################### + # ##################################### Finished the CONSTRUCTOR ############################################ + # ########################################################################################################### App.log.debug("END of constructor. Releasing control.") - # ##################################################################################### - # ########################## SHOW GUI ################################################# - # ##################################################################################### + # ########################################################################################################### + # ########################################## SHOW GUI ####################################################### + # ########################################################################################################### # if the app is not started as headless, show it if self.cmd_line_headless != 1: @@ -2857,9 +1625,9 @@ class App(QtCore.QObject): else: log.warning("******************* RUNNING HEADLESS *******************") - # ##################################################################################### - # ########################## START-UP ARGUMENTS ####################################### - # ##################################################################################### + # ########################################################################################################### + # ######################################## START-UP ARGUMENTS ############################################### + # ########################################################################################################### # test if the program was started with a script as parameter if self.cmd_line_shellvar: @@ -2914,10 +1682,10 @@ class App(QtCore.QObject): if App.args: self.args_at_startup.emit(App.args) - if self.old_defaults_found is True: + if self.defaults.old_defaults_found is True: self.inform.emit('[WARNING_NOTCL] %s' % _("Found old default preferences files. " "Please reboot the application to update.")) - self.old_defaults_found = False + self.defaults.old_defaults_found = False # ######################################### INIT FINISHED ####################################################### # ################################################################################################################# @@ -3012,7 +1780,7 @@ class App(QtCore.QObject): elif 'save'.lower() in argument.lower(): log.debug("App.on_startup_args() --> Save event. App Defaults saved.") - self.save_defaults() + self.preferencesUiManager.save_defaults() else: exc_list = self.ui.util_defaults_form.fa_excellon_group.exc_list_text.get_value().split(',') proc_arg = argument.lower() @@ -3025,7 +1793,7 @@ class App(QtCore.QObject): if silent is False: self.inform.emit(_("Open Excellon file failed.")) else: - self.on_fileopenexcellon(name=file_name) + self.on_fileopenexcellon(name=file_name, signal=None) return gco_list = self.ui.util_defaults_form.fa_gcode_group.gco_list_text.get_value().split(',') @@ -3038,7 +1806,7 @@ class App(QtCore.QObject): if silent is False: self.inform.emit(_("Open GCode file failed.")) else: - self.on_fileopengcode(name=file_name) + self.on_fileopengcode(name=file_name, signal=None) return grb_list = self.ui.util_defaults_form.fa_gerber_group.grb_list_text.get_value().split(',') @@ -3051,7 +1819,7 @@ class App(QtCore.QObject): if silent is False: self.inform.emit(_("Open Gerber file failed.")) else: - self.on_fileopengerber(name=file_name) + self.on_fileopengerber(name=file_name, signal=None) return # if it reached here without already returning then the app was registered with a file that it does not @@ -3090,75 +1858,6 @@ class App(QtCore.QObject): fcTranslate.restart_program(app=self) - def defaults_read_form(self): - """ - Will read all the values in the Preferences GUI and update the defaults dictionary. - - :return: None - """ - for option in self.defaults_form_fields: - try: - self.defaults[option] = self.defaults_form_fields[option].get_value() - except Exception as e: - log.debug("App.defaults_read_form() --> %s" % str(e)) - - def defaults_write_form(self, factor=None, fl_units=None, source_dict=None): - """ - Will set the values for all the GUI elements in Preferences GUI based on the values found in the - self.defaults dictionary. - - :param factor: will apply a factor to the values that written in the GUI elements - :param fl_units: current measuring units in FlatCAM: Metric or Inch - :param source_dict: the repository of options, usually is the self.defaults - :return: None - """ - - options_storage = self.defaults if source_dict is None else source_dict - - for option in options_storage: - if source_dict: - self.defaults_write_form_field(option, factor=factor, units=fl_units, defaults_dict=source_dict) - else: - self.defaults_write_form_field(option, factor=factor, units=fl_units) - - # try: - # self.defaults_form_fields[option].set_value(self.defaults[option]) - # except KeyError: - # #self.log.debug("defaults_write_form(): No field for: %s" % option) - # pass - - def defaults_write_form_field(self, field, factor=None, units=None, defaults_dict=None): - """ - Basically it is the worker in the self.defaults_write_form() - - :param field: the GUI element in Preferences GUI to be updated - :param factor: factor to be applied to the field parameter - :param units: current FLatCAM measuring units - :param defaults_dict: the defaults storage - :return: None, it updates GUI elements - """ - - def_dict = self.defaults if defaults_dict is None else defaults_dict - try: - if factor is None: - if units is None: - self.defaults_form_fields[field].set_value(def_dict[field]) - elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value(def_dict[field]) - elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value(def_dict[field]) - else: - if units is None: - self.defaults_form_fields[field].set_value(def_dict[field] * factor) - elif units == 'IN' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value((def_dict[field] * factor)) - elif units == 'MM' and (field == 'global_gridx' or field == 'global_gridy'): - self.defaults_form_fields[field].set_value((def_dict[field] * factor)) - except KeyError: - pass - except AttributeError: - log.debug(field) - def clear_pool(self): """ Clear the multiprocessing pool and calls garbage collector. @@ -3415,21 +2114,21 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("object2editor()") + self.defaults.report_usage("object2editor()") # disable the objects menu as it may interfere with the Editors self.ui.menuobjects.setDisabled(True) edited_object = self.collection.get_active() - if isinstance(edited_object, FlatCAMGerber) or isinstance(edited_object, FlatCAMGeometry) or \ - isinstance(edited_object, FlatCAMExcellon): + if isinstance(edited_object, GerberObject) or isinstance(edited_object, GeometryObject) or \ + isinstance(edited_object, ExcellonObject): pass else: self.inform.emit('[WARNING_NOTCL] %s' % _("Select a Geometry, Gerber or Excellon Object to edit.")) return - if isinstance(edited_object, FlatCAMGeometry): + if isinstance(edited_object, GeometryObject): # store the Geometry Editor Toolbar visibility before entering in the Editor self.geo_editor.toolbar_old_state = True if self.ui.geo_edit_toolbar.isVisible() else False @@ -3462,7 +2161,7 @@ class App(QtCore.QObject): # set call source to the Editor we go into self.call_source = 'geo_editor' - elif isinstance(edited_object, FlatCAMExcellon): + elif isinstance(edited_object, ExcellonObject): # store the Excellon Editor Toolbar visibility before entering in the Editor self.exc_editor.toolbar_old_state = True if self.ui.exc_edit_toolbar.isVisible() else False @@ -3474,7 +2173,7 @@ class App(QtCore.QObject): # set call source to the Editor we go into self.call_source = 'exc_editor' - elif isinstance(edited_object, FlatCAMGerber): + elif isinstance(edited_object, GerberObject): # store the Gerber Editor Toolbar visibility before entering in the Editor self.grb_editor.toolbar_old_state = True if self.ui.grb_edit_toolbar.isVisible() else False @@ -3509,7 +2208,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("editor2object()") + self.defaults.report_usage("editor2object()") # re-enable the objects menu that was disabled on entry in Editor mode self.ui.menuobjects.setDisabled(False) @@ -3538,7 +2237,7 @@ class App(QtCore.QObject): self.ui.tool_scroll_area.setWidget(QtWidgets.QWidget()) self.ui.notebook.setTabText(2, "Tool") - if isinstance(edited_obj, FlatCAMGeometry): + if isinstance(edited_obj, GeometryObject): obj_type = "Geometry" if cleanup is None: self.geo_editor.update_fcgeometry(edited_obj) @@ -3564,7 +2263,7 @@ class App(QtCore.QObject): edited_obj.build_ui() self.inform.emit('[success] %s' % _("Editor exited. Editor content saved.")) - elif isinstance(edited_obj, FlatCAMGerber): + elif isinstance(edited_obj, GerberObject): obj_type = "Gerber" if cleanup is None: self.grb_editor.update_fcgerber() @@ -3589,7 +2288,7 @@ class App(QtCore.QObject): # Remove anything else in the GUI self.ui.selected_scroll_area.takeWidget() - elif isinstance(edited_obj, FlatCAMExcellon): + elif isinstance(edited_obj, ExcellonObject): obj_type = "Excellon" if cleanup is None: self.exc_editor.update_fcexcellon(edited_obj) @@ -3621,13 +2320,13 @@ class App(QtCore.QObject): self.inform.emit('[WARNING_NOTCL] %s' % _("Editor exited. Editor content was not saved.")) - if isinstance(edited_obj, FlatCAMGeometry): + if isinstance(edited_obj, GeometryObject): self.geo_editor.deactivate() edited_obj.build_ui() - elif isinstance(edited_obj, FlatCAMGerber): + elif isinstance(edited_obj, GerberObject): self.grb_editor.deactivate_grb_editor() edited_obj.build_ui() - elif isinstance(edited_obj, FlatCAMExcellon): + elif isinstance(edited_obj, ExcellonObject): self.exc_editor.deactivate() edited_obj.build_ui() else: @@ -3642,11 +2341,11 @@ class App(QtCore.QObject): # Switch notebook to Selected page self.ui.notebook.setCurrentWidget(self.ui.selected_tab) else: - if isinstance(edited_obj, FlatCAMGeometry): + if isinstance(edited_obj, GeometryObject): self.geo_editor.deactivate() - elif isinstance(edited_obj, FlatCAMGerber): + elif isinstance(edited_obj, GerberObject): self.grb_editor.deactivate_grb_editor() - elif isinstance(edited_obj, FlatCAMExcellon): + elif isinstance(edited_obj, ExcellonObject): self.exc_editor.deactivate() else: self.inform.emit('[WARNING_NOTCL] %s' % @@ -3687,20 +2386,6 @@ class App(QtCore.QObject): loc = os.path.dirname(__file__) return loc - def report_usage(self, resource): - """ - Increments usage counter for the given resource - in self.defaults['global_stats']. - - :param resource: Name of the resource. - :return: None - """ - - if resource in self.defaults['global_stats']: - self.defaults['global_stats'][resource] += 1 - else: - self.defaults['global_stats'][resource] = 1 - def info(self, msg): """ Informs the user. Normally on the status bar, optionally @@ -3799,119 +2484,6 @@ class App(QtCore.QObject): else: self.ui.toolbarshell.setVisible(False) - def load_defaults(self, filename): - """ - Loads the aplication's default settings from current_defaults.FlatConfig into - ``self.defaults``. - - :return: None - """ - try: - f = open(self.data_path + "/" + filename + ".FlatConfig") - options = f.read() - f.close() - except IOError: - self.log.error("Could not load defaults file.") - self.inform.emit('[ERROR] %s' % _("Could not load defaults file.")) - # in case the defaults file can't be loaded, show all toolbars - self.defaults["global_toolbar_view"] = 511 - return - - try: - defaults = json.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] - App.log.error(str(e)) - self.inform.emit('[ERROR] %s' % _("Failed to parse defaults file.")) - return - - if defaults: - if 'version' not in defaults or defaults['version'] != self.defaults['version']: - for k, v in defaults.items(): - if k in self.defaults and k != 'version': - - # check if the types are the same. Because some types (tuple, float, int etc) - # may be stored as strings we check their types. - try: - target = eval(self.defaults[k]) - except NameError: - # it's an unknown string leave it as it is - target = deepcopy(self.defaults[k]) - - try: - source = eval(v) - except NameError: - # it's an unknown string leave it as it is - source = deepcopy(v) - - if type(target) == type(source): - self.defaults[k] = v - - # delete old factory defaults - try: - fact_def_file_path = os.path.join(self.data_path, 'factory_defaults.FlatConfig') - os.chmod(fact_def_file_path, stat.S_IRWXO | stat.S_IWRITE | stat.S_IWGRP) - os.remove(fact_def_file_path) - - # recreate a new factory defaults file and save the factory defaults data into it - f_f_def_s = open(self.data_path + "/factory_defaults.FlatConfig", "w") - json.dump(self.defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True) - f_f_def_s.close() - - # and then make the factory_defaults.FlatConfig file read_only - # so it can't be modified after creation. - os.chmod(fact_def_file_path, S_IREAD | S_IRGRP | S_IROTH) - except Exception as e: - log.debug("App.load_defaults() -> deleting old factory defaults file -> %s" % str(e)) - - self.old_defaults_found = True - else: - self.old_defaults_found = False - self.defaults.update(defaults) - - log.debug("FlatCAM defaults loaded from: %s" % filename) - - def on_restore_defaults_preferences(self): - """ - Loads the application's factory default settings from factory_defaults.FlatConfig into - ``self.defaults``. - - :return: None - """ - - App.log.debug("App.on_restore_defaults_preferences()") - - filename = self.data_path + '/factory_defaults.FlatConfig' - - if filename == "": - self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - try: - f = open(filename) - options = f.read() - f.close() - except IOError: - self.log.error("Could not load factory defaults file.") - self.inform.emit('[ERROR_NOTCL] %s' % _("Could not load factory defaults file.")) - return - - try: - defaults_from_file = json.loads(options) - except Exception: - e = sys.exc_info()[0] - App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to parse factory defaults file.")) - return - self.defaults.update(defaults_from_file) - # update the dict that is used to restore the values in the defaults form if Cancel is clicked in the - # Preferences window - self.current_defaults.update(defaults_from_file) - - self.on_preferences_edited() - self.inform.emit('[success] %s' % _("Preferences default values are restored.")) - def on_import_preferences(self): """ Loads the application default settings from a saved file into @@ -3920,9 +2492,10 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_import_preferences") + self.defaults.report_usage("on_import_preferences") App.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"), @@ -3931,35 +2504,16 @@ class App(QtCore.QObject): 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.")) - else: - try: - f = open(filename) - options = f.read() - f.close() - except IOError: - self.log.error("Could not load defaults file.") - self.inform.emit('[ERROR_NOTCL] %s' % _("Could not load defaults file.")) - return + return - try: - defaults_from_file = json.loads(options) - except Exception: - e = sys.exc_info()[0] - App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to parse defaults file.")) - return - self.defaults.update(defaults_from_file) - # update the dict that is used to restore the values in the defaults form if Cancel is clicked in the - # Preferences window - self.current_defaults.update(defaults_from_file) + # Load in the defaults from the chosen file + self.defaults.load(filename=filename) - self.on_preferences_edited() - self.inform.emit('[success] %s: %s' % (_("Imported Defaults from"), filename)) + self.preferencesUiManager.on_preferences_edited() + self.inform.emit('[success] %s: %s' % (_("Imported Defaults from"), filename)) def on_export_preferences(self): """ @@ -3967,71 +2521,40 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_export_preferences") + self.defaults.report_usage("on_export_preferences") App.log.debug("on_export_preferences()") - defaults_file_content = None - - self.date = str(datetime.today()).rpartition('.')[0] - self.date = ''.join(c for c in self.date if c not in ':-') - self.date = self.date.replace(' ', '_') + # 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=self.data_path + '/preferences_' + self.date, + directory=self.data_path + '/preferences_' + date, filter=filter__ ) except TypeError: filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export FlatCAM Preferences"), filter=filter__) - filename = str(filename) - defaults_from_file = {} - if filename == "": self.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) return - else: - try: - f = open(filename, 'w') - defaults_file_content = f.read() - f.close() - 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 - except IOError: - App.log.debug('Creating a new preferences file ...') - f = open(filename, 'w') - json.dump({}, f) - f.close() - except Exception: - e = sys.exc_info()[0] - App.log.error("Could not load defaults file.") - App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Could not load preferences file.")) - return - try: - defaults_from_file = json.loads(defaults_file_content) - except Exception: - App.log.warning("Trying to read an empty Preferences file. Continue.") + # Update options + self.preferencesUiManager.defaults_read_form() + self.defaults.propagate_defaults() - # Update options - self.defaults_read_form() - defaults_from_file.update(self.defaults) - self.propagate_defaults(silent=True) + # Save update options + try: + self.defaults.write(filename=filename) + except Exception: + self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename))) + return - # Save update options - try: - f = open(filename, "w") - json.dump(defaults_from_file, f, default=to_dict, indent=2, sort_keys=True) - f.close() - except Exception: - self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename))) - return if self.defaults["global_open_style"] is False: self.file_opened.emit("preferences", filename) self.file_saved.emit("preferences", filename) @@ -4043,7 +2566,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("save_to_file") + self.defaults.report_usage("save_to_file") App.log.debug("save_to_file()") self.date = str(datetime.today()).rpartition('.')[0] @@ -4120,7 +2643,7 @@ class App(QtCore.QObject): self.defaults["global_def_win_w"] = width self.defaults["global_def_win_h"] = height self.defaults["global_def_notebook_width"] = notebook_width - self.save_defaults() + self.preferencesUiManager.save_defaults() def restore_main_win_geom(self): try: @@ -4238,12 +2761,12 @@ class App(QtCore.QObject): # ## Create object classdict = { - "gerber": FlatCAMGerber, - "excellon": FlatCAMExcellon, - "cncjob": FlatCAMCNCjob, - "geometry": FlatCAMGeometry, - "script": FlatCAMScript, - "document": FlatCAMDocument + "gerber": GerberObject, + "excellon": ExcellonObject, + "cncjob": CNCJobObject, + "geometry": GeometryObject, + "script": ScriptObject, + "document": DocumentObject } App.log.debug("Calling object constructor...") @@ -4251,7 +2774,7 @@ class App(QtCore.QObject): # Object creation/instantiation obj = classdict[kind](name) - obj.units = self.options["units"] # TODO: The constructor should look at defaults. + obj.units = self.options["units"] # IMPORTANT # The key names in defaults and options dictionary's are not random: @@ -4327,7 +2850,7 @@ class App(QtCore.QObject): # update the KeyWords list with the name of the file self.myKeywords.append(obj.options['name']) - FlatCAMApp.App.log.debug("Moving new object back to main thread.") + log.debug("Moving new object back to main thread.") # Move the object to the main thread and let the app know that it is available. obj.moveToThread(self.main_thread) @@ -4341,7 +2864,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("new_excellon_object()") + self.defaults.report_usage("new_excellon_object()") self.new_object('excellon', 'new_exc', lambda x, y: None, plot=False) @@ -4351,7 +2874,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("new_geometry_object()") + self.defaults.report_usage("new_geometry_object()") def initialize(obj, app): obj.multitool = False @@ -4364,7 +2887,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("new_gerber_object()") + self.defaults.report_usage("new_gerber_object()") def initialize(grb_obj, app): grb_obj.multitool = False @@ -4391,28 +2914,29 @@ class App(QtCore.QObject): :param text: pass a source file to the newly created script to be loaded in it :return: None """ - self.report_usage("new_script_object()") + self.defaults.report_usage("new_script_object()") if text is not None: new_source_file = text else: - commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \ - "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \ - "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \ - "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \ - "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \ - "ListSys, MillDrills,\n" \ - "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \ - "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \ - "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \ - "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \ - "# SubtractRectangle, Version, WriteGCode\n" + # commands_list = "# AddCircle, AddPolygon, AddPolyline, AddRectangle, AlignDrill, " \ + # "AlignDrillGrid, Bbox, Bounds, ClearShell, CopperClear,\n" \ + # "# Cncjob, Cutout, Delete, Drillcncjob, ExportDXF, ExportExcellon, ExportGcode,\n" \ + # "# ExportGerber, ExportSVG, Exteriors, Follow, GeoCutout, GeoUnion, GetNames,\n" \ + # "# GetSys, ImportSvg, Interiors, Isolate, JoinExcellon, JoinGeometry, " \ + # "ListSys, MillDrills,\n" \ + # "# MillSlots, Mirror, New, NewExcellon, NewGeometry, NewGerber, Nregions, " \ + # "Offset, OpenExcellon, OpenGCode, OpenGerber, OpenProject,\n" \ + # "# Options, Paint, Panelize, PlotAl, PlotObjects, SaveProject, " \ + # "SaveSys, Scale, SetActive, SetSys, SetOrigin, Skew, SubtractPoly,\n" \ + # "# SubtractRectangle, Version, WriteGCode\n" new_source_file = '# %s\n' % _('CREATE A NEW FLATCAM TCL SCRIPT') + \ '# %s:\n' % _('TCL Tutorial is here') + \ '# https://www.tcl.tk/man/tcl8.5/tutorial/tcltutorial.html\n' + '\n\n' + \ '# %s:\n' % _("FlatCAM commands list") - new_source_file += commands_list + '\n' + new_source_file += '# %s\n\n' % _("Type >help< followed by Run Code for a list of FlatCAM Tcl Commands " + "(displayed in Tcl Shell).") def initialize(obj, app): obj.source_file = deepcopy(new_source_file) @@ -4430,7 +2954,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("new_document_object()") + self.defaults.report_usage("new_document_object()") def initialize(obj, app): obj.source_file = "" @@ -4508,7 +3032,7 @@ class App(QtCore.QObject): # here it is done the object plotting def worker_task(t_obj): with self.proc_container.new(_("Plotting")): - if isinstance(t_obj, FlatCAMCNCjob): + if isinstance(t_obj, CNCJobObject): t_obj.plot(kind=self.defaults["cncjob_plot_kind"]) else: t_obj.plot() @@ -4556,7 +3080,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_about") + self.defaults.report_usage("on_about") version = self.version version_date = self.version_date @@ -4732,11 +3256,11 @@ class App(QtCore.QObject): self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % _("Programmer")), 0, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % _("Status")), 0, 1) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % _("E-mail")), 0, 2) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Juan Pablo Caram"), 1, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Program Author"), 1, 1) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "<>"), 1, 2) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Denis Hayrullin"), 2, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Kamil Sopko"), 3, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marius Stanciu"), 4, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % _("BETA Maintainer >= 2019")), 4, 1) @@ -4749,36 +3273,37 @@ class App(QtCore.QObject): self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Victor Benso"), 9, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 10, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Barnaby Walters"), 11, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jørn Sandvik Nilsson"), 12, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Lei Zheng"), 13, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marco A Quezada"), 14, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 12, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Leandro Heck"), 14, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Marco A Quezada"), 15, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 16, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Cedric Dussud"), 15, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Hemingway"), 16, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Damian Wrobel"), 17, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Daniel Sallin"), 18, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 19, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Cedric Dussud"), 20, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Hemingway"), 22, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Damian Wrobel"), 24, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Daniel Sallin"), 28, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 32, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Bruno Vunderl"), 20, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Gonzalo Lopez"), 21, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jakob Staudt"), 22, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Mike Smith"), 23, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Bruno Vunderl"), 40, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Gonzalo Lopez"), 42, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Jakob Staudt"), 45, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Mike Smith"), 49, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 24, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 52, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Lubos Medovarsky"), 25, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Steve Martina"), 26, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Thomas Duffin"), 27, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 28, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Barnaby Walters"), 55, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Steve Martina"), 57, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Thomas Duffin"), 59, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Andrey Kultyapov"), 61, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 29, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 63, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Breneman"), 30, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Eric Varsanyi"), 31, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Chris Breneman"), 65, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Eric Varsanyi"), 67, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "Lubos Medovarsky"), 69, 0) - self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 34, 0) + self.prog_grid_lay.addWidget(QtWidgets.QLabel(''), 74, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "@Idechix"), 100, 0) self.prog_grid_lay.addWidget(QtWidgets.QLabel('%s' % "@SM"), 101, 0) @@ -4923,6 +3448,7 @@ class App(QtCore.QObject): # BookDialog(app=self, storage=self.defaults["global_bookmarks"], parent=self.ui).exec_() self.book_dialog_tab = BookmarkManager(app=self, storage=self.defaults["global_bookmarks"], parent=self.ui) + self.book_dialog_tab.setObjectName("bookmarks_tab") # add the tab if it was closed self.ui.plot_tab_area.addTab(self.book_dialog_tab, _("Bookmarks Manager")) @@ -4958,195 +3484,7 @@ class App(QtCore.QObject): :return: None """ - - self.save_defaults() - - # def on_app_exit(self): - # self.report_usage("on_app_exit()") - # - # if self.collection.get_list(): - # msgbox = QtWidgets.QMessageBox() - # # msgbox.setText("Save changes ...") - # msgbox.setText("There are files/objects opened in FlatCAM. " - # "\n" - # "Do you want to Save the project?") - # msgbox.setWindowTitle("Save changes") - # msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png')) - # msgbox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | - # QtWidgets.QMessageBox.Cancel) - # msgbox.setDefaultButton(QtWidgets.QMessageBox.Yes) - # - # response = msgbox.exec_() - # - # if response == QtWidgets.QMessageBox.Yes: - # self.on_file_saveprojectas(thread=False) - # elif response == QtWidgets.QMessageBox.Cancel: - # return - # self.save_defaults() - # else: - # self.save_defaults() - # log.debug("Application defaults saved ... Exit event.") - # QtWidgets.qApp.quit() - - def save_defaults(self, silent=False, data_path=None, first_time=False): - """ - Saves application default options - ``self.defaults`` to current_defaults.FlatConfig file. - Save the toolbars visibility status to the preferences file (current_defaults.FlatConfig) to be - used at the next launch of the application. - - :param silent: Whether to display a message in status bar or not; boolean - :param data_path: The path where to save the preferences file (current_defaults.FlatConfig) - When the application is portable it should be a mobile location. - :param first_time: Boolean. If True will execute some code when the app is run first time - :return: None - """ - self.report_usage("save_defaults") - - if data_path is None: - data_path = self.data_path - - # Read options from file - try: - f = open(data_path + "/current_defaults.FlatConfig") - defaults_file_content = f.read() - f.close() - except Exception: - e = sys.exc_info()[0] - App.log.error("Could not load defaults file.") - App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Could not load defaults file.")) - return - - try: - defaults = json.loads(defaults_file_content) - except Exception: - e = sys.exc_info()[0] - App.log.error("Failed to parse defaults file.") - App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to parse defaults file.")) - return - - # Update options - self.defaults_read_form() - defaults.update(self.defaults) - self.propagate_defaults(silent=True) - - if first_time is False: - self.save_toolbar_view() - - # Save update options - filename = data_path + "/current_defaults.FlatConfig" - try: - f = open(filename, "w") - json.dump(defaults, f, default=to_dict, indent=2, sort_keys=True) - f.close() - except Exception as e: - log.debug("App.save_defaults() --> %s" % str(e)) - self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename))) - return - - if not silent: - self.inform.emit('[success] %s' % _("Preferences saved.")) - - # update the autosave timer - self.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.toolbargeo.isVisible(): - tb_status += 2 - - if self.ui.toolbarview.isVisible(): - tb_status += 4 - - if self.ui.toolbartools.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.snap_toolbar.isVisible(): - tb_status += 128 - - if self.ui.toolbarshell.isVisible(): - tb_status += 256 - - self.defaults["global_toolbar_view"] = tb_status - - def save_factory_defaults(self, silent_message=False, data_path=None): - """ - Saves application factory default options - ``self.defaults`` to factory_defaults.FlatConfig. - It's a one time job done just after the first install. - - :param silent_message: whether to display a message in status bar or not; boolean - :param data_path: the path where to save the default preferences file (factory_defaults.FlatConfig) - When the application is portable it should be a mobile location. - :return: None - """ - - self.report_usage("save_factory_defaults") - - if data_path is None: - data_path = self.data_path - - # Read options from file - try: - f_f_def = open(data_path + "/factory_defaults.FlatConfig") - factory_defaults_file_content = f_f_def.read() - f_f_def.close() - except Exception: - e = sys.exc_info()[0] - App.log.error("Could not load factory defaults file.") - App.log.error(str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Could not load factory defaults file.")) - return - - try: - factory_defaults = json.loads(factory_defaults_file_content) - except Exception: - e = sys.exc_info()[0] - App.log.error("Failed to parse factory defaults file.") - App.log.error(str(e)) - if silent_message is False: - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to parse factory defaults file.")) - return - - # Update options - self.defaults_read_form() - factory_defaults.update(self.defaults) - self.propagate_defaults(silent=True) - - # Save update options - try: - f_f_def_s = open(data_path + "/factory_defaults.FlatConfig", "w") - json.dump(factory_defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True) - f_f_def_s.close() - except Exception as e: - log.debug("App.save_factory_default() save update --> %s" % str(e)) - if silent_message is False: - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write factory defaults to file.")) - return - - if silent_message is False: - self.inform.emit(_("Factory defaults saved.")) + self.preferencesUiManager.save_defaults() def final_save(self): """ @@ -5155,6 +3493,7 @@ class App(QtCore.QObject): :return: None """ + if self.save_in_progress: self.inform.emit('[WARNING_NOTCL] %s' % _("Application is saving the project. Please wait ...")) return @@ -5201,7 +3540,7 @@ class App(QtCore.QObject): :return: None """ - self.save_defaults(silent=True) + self.preferencesUiManager.save_defaults(silent=True) log.debug("App.quit_application() --> App Defaults saved.") if self.cmd_line_headless != 1: @@ -5236,25 +3575,30 @@ class App(QtCore.QObject): log.debug("App.quit_application() --> App UI state saved.") - # try to quit the QThread that run ArgsThread class - try: - self.th.quit() - except Exception as e: - log.debug("App.quit_application() --> %s" % str(e)) - # try to quit the Socket opened by ArgsThread class try: + self.new_launch.thread_exit = True self.new_launch.listener.close() except Exception as err: log.debug("App.quit_application() --> %s" % str(err)) + # try to quit the QThread that run ArgsThread class + try: + self.th.terminate() + except Exception as e: + log.debug("App.quit_application() --> %s" % str(e)) + + # terminate workers + self.workers.__del__() + # quit app by signalling for self.kill_app() method # self.close_app_signal.emit() - QtWidgets.qApp.quit() + # When the main event loop is not started yet in which case the qApp.quit() will do nothing # we use the following command # sys.exit(0) + os._exit(0) # fix to work with Python 3.8 @staticmethod @@ -5348,8 +3692,7 @@ class App(QtCore.QObject): fp.close() # save the current defaults to the new defaults file - self.save_defaults(silent=True, data_path=current_data_path) - self.save_factory_defaults(silent_message=True, data_path=current_data_path) + self.preferencesUiManager.save_defaults(silent=True, data_path=current_data_path) else: data[line_no] = 'portable=False\n' @@ -5382,20 +3725,20 @@ class App(QtCore.QObject): root_path = winreg.HKEY_CURRENT_USER # create the keys - def set_reg(name, root_path, new_reg_path, value): + def set_reg(name, root_pth, new_reg_path, value): try: - winreg.CreateKey(root_path, new_reg_path) - with winreg.OpenKey(root_path, new_reg_path, 0, winreg.KEY_WRITE) as registry_key: + winreg.CreateKey(root_pth, new_reg_path) + with winreg.OpenKey(root_pth, new_reg_path, 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_path, reg_path, key_to_del): + def delete_reg(root_pth, reg_path, key_to_del): key_to_del_path = reg_path + key_to_del try: - winreg.DeleteKey(root_path, key_to_del_path) + winreg.DeleteKey(root_pth, key_to_del_path) return True except WindowsError: return False @@ -5641,7 +3984,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_edit_join()") + self.defaults.report_usage("on_edit_join()") obj_name_single = str(name) if name else "Combo_SingleGeo" obj_name_multi = str(name) if name else "Combo_MultiGeo" @@ -5672,7 +4015,7 @@ class App(QtCore.QObject): # if at least one True object is in the list then due of the previous check, all list elements are True objects if True in geo_type_set: def initialize(geo_obj, app): - FlatCAMGeometry.merge(self, geo_list=objs, geo_final=geo_obj, multigeo=True) + GeometryObject.merge(geo_list=objs, geo_final=geo_obj, multigeo=True) app.inform.emit('[success] %s.' % _("Geometry merging finished")) # rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi @@ -5682,7 +4025,7 @@ class App(QtCore.QObject): self.new_object("geometry", obj_name_multi, initialize) else: def initialize(geo_obj, app): - FlatCAMGeometry.merge(self, geo_list=objs, geo_final=geo_obj, multigeo=False) + GeometryObject.merge(geo_list=objs, geo_final=geo_obj, multigeo=False) app.inform.emit('[success] %s.' % _("Geometry merging finished")) # rename all the ['name] key in obj.tools[tooluid]['data'] to the obj_name_multi @@ -5700,12 +4043,12 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_edit_join_exc()") + self.defaults.report_usage("on_edit_join_exc()") objs = self.collection.get_selected() for obj in objs: - if not isinstance(obj, FlatCAMExcellon): + if not isinstance(obj, ExcellonObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Excellon joining works only on Excellon objects.")) return @@ -5715,7 +4058,7 @@ class App(QtCore.QObject): return 'fail' def initialize(exc_obj, app): - FlatCAMExcellon.merge(self, exc_list=objs, exc_final=exc_obj) + ExcellonObject.merge(exc_list=objs, exc_final=exc_obj, decimals=self.decimals) app.inform.emit('[success] %s.' % _("Excellon merging finished")) self.new_object("excellon", 'Combo_Excellon', initialize) @@ -5728,12 +4071,12 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_edit_join_grb()") + self.defaults.report_usage("on_edit_join_grb()") objs = self.collection.get_selected() for obj in objs: - if not isinstance(obj, FlatCAMGerber): + if not isinstance(obj, GerberObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Gerber joining works only on Gerber objects.")) return @@ -5743,7 +4086,7 @@ class App(QtCore.QObject): return 'fail' def initialize(grb_obj, app): - FlatCAMGerber.merge(self, grb_list=objs, grb_final=grb_obj) + GerberObject.merge(grb_list=objs, grb_final=grb_obj) app.inform.emit('[success] %s.' % _("Gerber merging finished")) self.new_object("gerber", 'Combo_Gerber', initialize) @@ -5759,7 +4102,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_convert_singlegeo_to_multigeo()") + self.defaults.report_usage("on_convert_singlegeo_to_multigeo()") obj = self.collection.get_active() @@ -5767,8 +4110,8 @@ class App(QtCore.QObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Select a Geometry Object and try again.")) return - if not isinstance(obj, FlatCAMGeometry): - self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Expected a FlatCAMGeometry, got"), type(obj))) + if not isinstance(obj, GeometryObject): + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Expected a GeometryObject, got"), type(obj))) return obj.multigeo = True @@ -5793,7 +4136,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_convert_multigeo_to_singlegeo()") + self.defaults.report_usage("on_convert_multigeo_to_singlegeo()") obj = self.collection.get_active() @@ -5802,9 +4145,9 @@ class App(QtCore.QObject): _("Failed. Select a Geometry Object and try again.")) return - if not isinstance(obj, FlatCAMGeometry): + if not isinstance(obj, GeometryObject): self.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Expected a FlatCAMGeometry, got"), type(obj))) + (_("Expected a GeometryObject, got"), type(obj))) return obj.multigeo = False @@ -5829,7 +4172,7 @@ class App(QtCore.QObject): :param field: the key of the self.defaults dictionary that was changed. :return: None """ - self.defaults_write_form_field(field) + self.preferencesUiManager.defaults_write_form_field(field=field) if field == "units": self.set_screen_units(self.defaults['units']) @@ -5869,7 +4212,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_toggle_units") + self.defaults.report_usage("on_toggle_units") if self.toggle_units_ignore: return @@ -6060,9 +4403,9 @@ class App(QtCore.QObject): if response == bt_ok: if no_pref is False: - self.defaults_read_form() + self.preferencesUiManager.defaults_read_form() scale_defaults(factor) - self.defaults_write_form(fl_units=new_units) + self.preferencesUiManager.defaults_write_form(fl_units=new_units) self.defaults["units"] = new_units @@ -6089,7 +4432,7 @@ class App(QtCore.QObject): 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 FlatCAMObj.py - if not isinstance(current, FlatCAMGeometry): + if not isinstance(current, GeometryObject): current.to_form() # replot all objects @@ -6116,9 +4459,9 @@ class App(QtCore.QObject): self.inform.emit('[WARNING_NOTCL]%s' % _("Cancelled.")) - self.defaults_read_form() + self.preferencesUiManager.defaults_read_form() - # the self.defaults_read_form() will update all defaults values in self.defaults from the GUI elements but + # the self.preferencesUiManager.defaults_read_form() will update all defaults values in self.defaults from the GUI elements but # I don't want it for the grid values, so I update them here self.defaults['global_gridx'] = val_x self.defaults['global_gridy'] = val_y @@ -6126,11 +4469,12 @@ class App(QtCore.QObject): self.ui.grid_gap_y_entry.set_value(val_y, decimals=self.decimals) def on_fullscreen(self, disable=False): - self.report_usage("on_fullscreen()") + self.defaults.report_usage("on_fullscreen()") + flags = self.ui.windowFlags() if self.toggle_fscreen is False and disable is False: # self.ui.showFullScreen() - self.ui.setWindowFlags(self.ui.windowFlags() | Qt.FramelessWindowHint) + self.ui.setWindowFlags(flags | Qt.FramelessWindowHint) a = self.ui.geometry() self.x_pos = a.x() self.y_pos = a.y() @@ -6158,7 +4502,7 @@ class App(QtCore.QObject): self.ui.splitter_left.setVisible(False) self.toggle_fscreen = True elif self.toggle_fscreen is True or disable is True: - self.ui.setWindowFlags(self.ui.windowFlags() & ~Qt.FramelessWindowHint) + self.ui.setWindowFlags(flags & ~Qt.FramelessWindowHint) self.ui.setGeometry(self.x_pos, self.y_pos, self.width, self.height) self.ui.showNormal() self.restore_toolbar_view() @@ -6166,7 +4510,7 @@ class App(QtCore.QObject): self.toggle_fscreen = False def on_toggle_plotarea(self): - self.report_usage("on_toggle_plotarea()") + self.defaults.report_usage("on_toggle_plotarea()") try: name = self.ui.plot_tab_area.widget(0).objectName() @@ -6192,7 +4536,7 @@ class App(QtCore.QObject): self.ui.menu_toggle_nb.setChecked(False) def on_toggle_axis(self): - self.report_usage("on_toggle_axis()") + self.defaults.report_usage("on_toggle_axis()") if self.toggle_axis is False: if self.is_legacy is False: @@ -6222,13 +4566,13 @@ class App(QtCore.QObject): self.toggle_axis = False def on_toggle_grid(self): - self.report_usage("on_toggle_grid()") + self.defaults.report_usage("on_toggle_grid()") self.ui.grid_snap_btn.trigger() self.on_grid_snap_triggered(state=True) def on_toggle_grid_lines(self): - self.report_usage("on_toggle_grd_lines()") + self.defaults.report_usage("on_toggle_grd_lines()") tt_settings = QtCore.QSettings("Open Source", "FlatCAM") if tt_settings.contains("theme"): @@ -6274,245 +4618,10 @@ class App(QtCore.QObject): self.app_cursor.enabled = True self.app_cursor.enabled = False - def show_preferences_gui(self): - """ - Called to initialize and show the Preferences GUI - :return: None - """ - self.gen_form = self.ui.general_defaults_form - self.ger_form = self.ui.gerber_defaults_form - self.exc_form = self.ui.excellon_defaults_form - self.geo_form = self.ui.geometry_defaults_form - self.cnc_form = self.ui.cncjob_defaults_form - self.tools_form = self.ui.tools_defaults_form - self.tools2_form = self.ui.tools2_defaults_form - self.fa_form = self.ui.util_defaults_form - try: - self.ui.general_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.general_scroll_area.setWidget(self.gen_form) - self.gen_form.show() - try: - self.ui.gerber_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.gerber_scroll_area.setWidget(self.ger_form) - self.ger_form.show() - - try: - self.ui.excellon_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.excellon_scroll_area.setWidget(self.exc_form) - self.exc_form.show() - - try: - self.ui.geometry_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.geometry_scroll_area.setWidget(self.geo_form) - self.geo_form.show() - - try: - self.ui.cncjob_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.cncjob_scroll_area.setWidget(self.cnc_form) - self.cnc_form.show() - - try: - self.ui.tools_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.tools_scroll_area.setWidget(self.tools_form) - self.tools_form.show() - - try: - self.ui.tools2_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.tools2_scroll_area.setWidget(self.tools2_form) - self.tools2_form.show() - - try: - self.ui.fa_scroll_area.takeWidget() - except Exception: - self.log.debug("Nothing to remove") - self.ui.fa_scroll_area.setWidget(self.fa_form) - self.fa_form.show() - - self.log.debug("Finished Preferences GUI form initialization.") - - # self.options2form() - - def init_color_pickers_in_preferences_gui(self): - # Init Gerber Plot Colors - self.ui.gerber_defaults_form.gerber_gen_group.pf_color_entry.set_value(self.defaults['gerber_plot_fill']) - self.ui.gerber_defaults_form.gerber_gen_group.pf_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['gerber_plot_fill'])[:7]) - self.ui.gerber_defaults_form.gerber_gen_group.pf_color_alpha_spinner.set_value( - int(self.defaults['gerber_plot_fill'][7:9], 16)) - self.ui.gerber_defaults_form.gerber_gen_group.pf_color_alpha_slider.setValue( - int(self.defaults['gerber_plot_fill'][7:9], 16)) - - self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry.set_value(self.defaults['gerber_plot_line']) - self.ui.gerber_defaults_form.gerber_gen_group.pl_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['gerber_plot_line'])[:7]) - - # Init Excellon Plot Colors - self.ui.excellon_defaults_form.excellon_gen_group.fill_color_entry.set_value( - self.defaults['excellon_plot_fill']) - self.ui.excellon_defaults_form.excellon_gen_group.fill_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['excellon_plot_fill'])[:7]) - self.ui.excellon_defaults_form.excellon_gen_group.color_alpha_spinner.set_value( - int(self.defaults['excellon_plot_fill'][7:9], 16)) - self.ui.excellon_defaults_form.excellon_gen_group.color_alpha_slider.setValue( - int(self.defaults['excellon_plot_fill'][7:9], 16)) - - self.ui.excellon_defaults_form.excellon_gen_group.line_color_entry.set_value( - self.defaults['excellon_plot_line']) - self.ui.excellon_defaults_form.excellon_gen_group.line_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['excellon_plot_line'])[:7]) - - # Init Geometry Plot Colors - self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry.set_value( - self.defaults['geometry_plot_line']) - self.ui.geometry_defaults_form.geometry_gen_group.line_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['geometry_plot_line'])[:7]) - - # Init CNCJob Travel Line Colors - self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry.set_value( - self.defaults['cncjob_travel_fill']) - self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['cncjob_travel_fill'])[:7]) - self.ui.cncjob_defaults_form.cncjob_gen_group.tcolor_alpha_spinner.set_value( - int(self.defaults['cncjob_travel_fill'][7:9], 16)) - self.ui.cncjob_defaults_form.cncjob_gen_group.tcolor_alpha_slider.setValue( - int(self.defaults['cncjob_travel_fill'][7:9], 16)) - - self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry.set_value( - self.defaults['cncjob_travel_line']) - self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['cncjob_travel_line'])[:7]) - - # Init CNCJob Plot Colors - self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry.set_value( - self.defaults['cncjob_plot_fill']) - self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['cncjob_plot_fill'])[:7]) - - self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry.set_value( - self.defaults['cncjob_plot_line']) - self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['cncjob_plot_line'])[:7]) - - # Init Left-Right Selection colors - self.ui.general_defaults_form.general_gui_group.sf_color_entry.set_value(self.defaults['global_sel_fill']) - self.ui.general_defaults_form.general_gui_group.sf_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_sel_fill'])[:7]) - self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.set_value( - int(self.defaults['global_sel_fill'][7:9], 16)) - self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.setValue( - int(self.defaults['global_sel_fill'][7:9], 16)) - - self.ui.general_defaults_form.general_gui_group.sl_color_entry.set_value(self.defaults['global_sel_line']) - self.ui.general_defaults_form.general_gui_group.sl_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_sel_line'])[:7]) - - # Init Right-Left Selection colors - self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value( - self.defaults['global_alt_sel_fill']) - self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_alt_sel_fill'])[:7]) - self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.set_value( - int(self.defaults['global_sel_fill'][7:9], 16)) - self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.setValue( - int(self.defaults['global_sel_fill'][7:9], 16)) - - self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value( - self.defaults['global_alt_sel_line']) - self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_alt_sel_line'])[:7]) - - # Init Draw color and Selection Draw Color - self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value( - self.defaults['global_draw_color']) - self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_draw_color'])[:7]) - - self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value( - self.defaults['global_sel_draw_color']) - self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_sel_draw_color'])[:7]) - - # Init Project Items color - self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value( - self.defaults['global_proj_item_color']) - self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_proj_item_color'])[:7]) - - # Init Project Disabled Items color - self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value( - self.defaults['global_proj_item_dis_color']) - self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_proj_item_dis_color'])[:7]) - - # Init Project Disabled Items color - self.ui.general_defaults_form.general_app_set_group.mouse_cursor_entry.set_value( - self.defaults['global_cursor_color']) - self.ui.general_defaults_form.general_app_set_group.mouse_cursor_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['global_cursor_color'])[:7]) - - # Init the Annotation CNC Job color - self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.set_value( - self.defaults['cncjob_annotation_fontcolor']) - self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['cncjob_annotation_fontcolor'])[:7]) - - # Init the Tool Film color - self.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value( - self.defaults['tools_film_color']) - self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['tools_film_color'])[:7] - ) - - # Init the Tool QRCode colors - self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.set_value( - self.defaults['tools_qrcode_fill_color']) - self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['tools_qrcode_fill_color'])[:7]) - - self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.set_value( - self.defaults['tools_qrcode_back_color']) - self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet( - "background-color:%s;" - "border-color: dimgray" % str(self.defaults['tools_qrcode_back_color'])[:7]) def on_update_exc_export(self, state): """ @@ -6661,7 +4770,7 @@ class App(QtCore.QObject): # if new color is different then mark that the Preferences are changed if film_color != current_color: - self.on_preferences_edited() + self.preferencesUiManager.on_preferences_edited() self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet( "background-color:%s;" @@ -6690,7 +4799,7 @@ class App(QtCore.QObject): # if new color is different then mark that the Preferences are changed if fill_color != current_color: - self.on_preferences_edited() + self.preferencesUiManager.on_preferences_edited() self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet( "background-color:%s;" @@ -6720,7 +4829,7 @@ class App(QtCore.QObject): # if new color is different then mark that the Preferences are changed if back_color != current_color: - self.on_preferences_edited() + self.preferencesUiManager.on_preferences_edited() self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet( "background-color:%s;" @@ -6763,7 +4872,7 @@ class App(QtCore.QObject): # self.save_defaults(silent=True) if self.is_legacy is True: self.plotcanvas.delete_workspace() - self.defaults_read_form() + self.preferencesUiManager.defaults_read_form() self.plotcanvas.draw_workspace(workspace_size=self.defaults['global_workspaceT']) def on_workspace(self): @@ -6771,7 +4880,7 @@ class App(QtCore.QObject): self.plotcanvas.draw_workspace(workspace_size=self.defaults['global_workspaceT']) else: self.plotcanvas.delete_workspace() - self.defaults_read_form() + self.preferencesUiManager.defaults_read_form() # self.save_defaults(silent=True) def on_workspace_toggle(self): @@ -6806,93 +4915,6 @@ class App(QtCore.QObject): else: self.app_cursor.enabled = False - def on_save_button(self, save_to_file=True): - log.debug("App.on_save_button() --> Applying preferences to file.") - - # Preferences saved, update flag - self.preferences_changed_flag = False - - # Preferences save, update the color of the Preferences Tab text - for idx in range(self.ui.plot_tab_area.count()): - if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): - self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) - - # restore the default stylesheet by setting a blank one - self.ui.pref_apply_button.setStyleSheet("") - - self.inform.emit('%s' % _("Preferences applied.")) - - # make sure we update the self.current_defaults dict used to undo changes to self.defaults - self.current_defaults.update(self.defaults) - - if save_to_file: - self.save_defaults(silent=False) - # load the defaults so they are updated into the app - self.load_defaults(filename='current_defaults') - - # Re-fresh project options - self.on_options_app2project() - - settgs = QSettings("Open Source", "FlatCAM") - - # save the notebook font size - fsize = self.ui.general_defaults_form.general_app_set_group.notebook_font_size_spinner.get_value() - settgs.setValue('notebook_font_size', fsize) - - # save the axis font size - g_fsize = self.ui.general_defaults_form.general_app_set_group.axis_font_size_spinner.get_value() - settgs.setValue('axis_font_size', g_fsize) - - # save the textbox font size - tb_fsize = self.ui.general_defaults_form.general_app_set_group.textbox_font_size_spinner.get_value() - settgs.setValue('textbox_font_size', tb_fsize) - - settgs.setValue( - 'machinist', - 1 if self.ui.general_defaults_form.general_app_set_group.machinist_cb.get_value() else 0 - ) - - # This will write the setting to the platform specific storage. - del settgs - - if save_to_file: - # close the tab and delete it - for idx in range(self.ui.plot_tab_area.count()): - if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): - self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) - self.ui.plot_tab_area.closeTab(idx) - break - - def on_pref_close_button(self): - # Preferences saved, update flag - self.preferences_changed_flag = False - try: - self.ui.plot_tab_area.tab_closed_signal.disconnect(self.on_plot_area_tab_closed) - except TypeError: - pass - - try: - self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect() - except (TypeError, AttributeError): - pass - self.defaults_write_form(source_dict=self.current_defaults) - self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect( - lambda: self.on_toggle_units(no_pref=False)) - self.defaults.update(self.current_defaults) - - # shared_items = {k: self.defaults[k] for k in self.defaults if k in self.current_defaults and - # self.defaults[k] == self.current_defaults[k]} - # print(len(self.defaults), len(shared_items)) - - # Preferences save, update the color of the Preferences Tab text - for idx in range(self.ui.plot_tab_area.count()): - if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): - self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) - self.ui.plot_tab_area.closeTab(idx) - break - - self.inform.emit('%s' % _("Preferences closed without saving.")) - self.ui.plot_tab_area.tab_closed_signal.connect(self.on_plot_area_tab_closed) def on_tool_add_keypress(self): # ## Current application units in Upper Case @@ -6953,7 +4975,7 @@ class App(QtCore.QObject): # work only if the notebook tab on focus is the Selected_Tab and only if the object is Geometry if notebook_widget_name == 'selected_tab': - if str(type(self.collection.get_active())) == "": + if str(type(self.collection.get_active())) == "": self.collection.get_active().on_tool_delete() # work only if the notebook tab on focus is the Tools_Tab @@ -6986,7 +5008,7 @@ class App(QtCore.QObject): :param force_deletion: used by Tcl command :return: None """ - self.report_usage("on_delete()") + self.defaults.report_usage("on_delete()") response = None bt_ok = None @@ -7017,14 +5039,14 @@ class App(QtCore.QObject): self.log.debug("App.on_delete()") for obj_active in self.collection.get_selected(): - # if the deleted object is FlatCAMGerber then make sure to delete the possible mark shapes - if isinstance(obj_active, FlatCAMGerber): + # if the deleted object is GerberObject then make sure to delete the possible mark shapes + if isinstance(obj_active, GerberObject): for el in obj_active.mark_shapes: obj_active.mark_shapes[el].clear(update=True) obj_active.mark_shapes[el].enabled = False # obj_active.mark_shapes[el] = None del el - elif isinstance(obj_active, FlatCAMCNCjob): + elif isinstance(obj_active, CNCJobObject): try: obj_active.text_col.enabled = False del obj_active.text_col @@ -7084,7 +5106,7 @@ class App(QtCore.QObject): # display the message for the user # and ask him to click on the desired position - self.report_usage("on_set_origin()") + self.defaults.report_usage("on_set_origin()") def origin_replot(): @@ -7254,7 +5276,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_jump_to()") + self.defaults.report_usage("on_jump_to()") if not custom_location: dia_box_location = None @@ -7375,7 +5397,7 @@ class App(QtCore.QObject): :return: A point location. (x, y) tuple. """ - self.report_usage("on_locate()") + self.defaults.report_usage("on_locate()") if obj is None: self.inform.emit('[WARNING_NOTCL] %s' % _("No object selected.")) @@ -7520,7 +5542,7 @@ class App(QtCore.QObject): Will copy a selection of objects, creating new objects. :return: """ - self.report_usage("on_copy_command()") + self.defaults.report_usage("on_copy_command()") def initialize(obj_init, app): obj_init.solid_geometry = deepcopy(obj.solid_geometry) @@ -7566,21 +5588,20 @@ class App(QtCore.QObject): obj_name = obj.options["name"] try: - if isinstance(obj, FlatCAMExcellon): + if isinstance(obj, ExcellonObject): self.new_object("excellon", str(obj_name) + "_copy", initialize_excellon) - elif isinstance(obj, FlatCAMGerber): + elif isinstance(obj, GerberObject): self.new_object("gerber", str(obj_name) + "_copy", initialize) - elif isinstance(obj, FlatCAMGeometry): + elif isinstance(obj, GeometryObject): self.new_object("geometry", str(obj_name) + "_copy", initialize) - elif isinstance(obj, FlatCAMScript): + elif isinstance(obj, ScriptObject): self.new_object("script", str(obj_name) + "_copy", initialize_script) - elif isinstance(obj, FlatCAMDocument): + elif isinstance(obj, DocumentObject): self.new_object("document", str(obj_name) + "_copy", initialize_document) except Exception as e: return "Operation failed: %s" % str(e) - def on_paste_command(self): - pass + def on_copy_object2(self, custom_name): @@ -7618,11 +5639,11 @@ class App(QtCore.QObject): for obj in self.collection.get_selected(): obj_name = obj.options["name"] try: - if isinstance(obj, FlatCAMExcellon): + if isinstance(obj, ExcellonObject): self.new_object("excellon", str(obj_name) + custom_name, initialize_excellon) - elif isinstance(obj, FlatCAMGerber): + elif isinstance(obj, GerberObject): self.new_object("gerber", str(obj_name) + custom_name, initialize_gerber) - elif isinstance(obj, FlatCAMGeometry): + elif isinstance(obj, GeometryObject): self.new_object("geometry", str(obj_name) + custom_name, initialize_geometry) except Exception as er: return "Operation failed: %s" % str(er) @@ -7634,7 +5655,7 @@ class App(QtCore.QObject): :param text: New name for the object. :return: """ - self.report_usage("on_rename_object()") + self.defaults.report_usage("on_rename_object()") named_obj = self.collection.get_active() for obj in named_obj: @@ -7651,7 +5672,7 @@ class App(QtCore.QObject): Will convert any object out of Gerber, Excellon, Geometry to Geometry object. :return: """ - self.report_usage("convert_any2geo()") + self.defaults.report_usage("convert_any2geo()") def initialize(obj_init, app): obj_init.solid_geometry = obj.solid_geometry @@ -7672,7 +5693,7 @@ class App(QtCore.QObject): def initialize_excellon(obj_init, app): # objs = self.collection.get_selected() - # FlatCAMGeometry.merge(objs, obj) + # GeometryObject.merge(objs, obj) solid_geo = [] for tool in obj.tools: for geo in obj.tools[tool]['solid_geometry']: @@ -7689,7 +5710,7 @@ class App(QtCore.QObject): obj_name = obj.options["name"] try: - if isinstance(obj, FlatCAMExcellon): + if isinstance(obj, ExcellonObject): self.new_object("geometry", str(obj_name) + "_conv", initialize_excellon) else: self.new_object("geometry", str(obj_name) + "_conv", initialize) @@ -7703,7 +5724,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("convert_any2gerber()") + self.defaults.report_usage("convert_any2gerber()") def initialize_geometry(obj_init, app): apertures = {} @@ -7767,9 +5788,9 @@ class App(QtCore.QObject): obj_name = obj.options["name"] try: - if isinstance(obj, FlatCAMExcellon): + if isinstance(obj, ExcellonObject): self.new_object("gerber", str(obj_name) + "_conv", initialize_excellon) - elif isinstance(obj, FlatCAMGeometry): + elif isinstance(obj, GeometryObject): self.new_object("gerber", str(obj_name) + "_conv", initialize_geometry) else: log.warning("App.convert_any2gerber --> This is no vaild object for conversion.") @@ -7800,7 +5821,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_selectall()") + self.defaults.report_usage("on_selectall()") # delete the possible selection box around a possible selected object self.delete_selection_shape() @@ -7834,65 +5855,49 @@ class App(QtCore.QObject): for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject): try: try: - tb.textEdited.disconnect(self.on_preferences_edited) + tb.textEdited.disconnect(self.preferencesUiManager.on_preferences_edited) except (TypeError, AttributeError): pass - tb.textEdited.connect(self.on_preferences_edited) + tb.textEdited.connect(self.preferencesUiManager.on_preferences_edited) except AttributeError: pass try: try: - tb.modificationChanged.disconnect(self.on_preferences_edited) + tb.modificationChanged.disconnect(self.preferencesUiManager.on_preferences_edited) except (TypeError, AttributeError): pass - tb.modificationChanged.connect(self.on_preferences_edited) + tb.modificationChanged.connect(self.preferencesUiManager.on_preferences_edited) except AttributeError: pass try: try: - tb.toggled.disconnect(self.on_preferences_edited) + tb.toggled.disconnect(self.preferencesUiManager.on_preferences_edited) except (TypeError, AttributeError): pass - tb.toggled.connect(self.on_preferences_edited) + tb.toggled.connect(self.preferencesUiManager.on_preferences_edited) except AttributeError: pass try: try: - tb.valueChanged.disconnect(self.on_preferences_edited) + tb.valueChanged.disconnect(self.preferencesUiManager.on_preferences_edited) except (TypeError, AttributeError): pass - tb.valueChanged.connect(self.on_preferences_edited) + tb.valueChanged.connect(self.preferencesUiManager.on_preferences_edited) except AttributeError: pass try: try: - tb.currentIndexChanged.disconnect(self.on_preferences_edited) + tb.currentIndexChanged.disconnect(self.preferencesUiManager.on_preferences_edited) except (TypeError, AttributeError): pass - tb.currentIndexChanged.connect(self.on_preferences_edited) + tb.currentIndexChanged.connect(self.preferencesUiManager.on_preferences_edited) except AttributeError: pass - def on_preferences_edited(self): - """ - Executed when a preference was changed in the Edit -> Preferences tab. - Will color the Preferences tab text to Red color. - :return: - """ - if self.preferences_changed_flag is False: - self.inform.emit('[WARNING_NOTCL] %s' % _("Preferences edited but not saved.")) - - for idx in range(self.ui.plot_tab_area.count()): - if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): - self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('red')) - - self.ui.pref_apply_button.setStyleSheet("QPushButton {color: red;}") - - self.preferences_changed_flag = True def on_tools_database(self, source='app'): """ @@ -7970,7 +5975,7 @@ class App(QtCore.QObject): tool_from_db = deepcopy(tool) obj = self.collection.get_active() - if isinstance(obj, FlatCAMGeometry): + if isinstance(obj, GeometryObject): obj.on_tool_from_db_inserted(tool=tool_from_db) # close the tab and delete it @@ -7983,66 +5988,17 @@ class App(QtCore.QObject): else: self.inform.emit('[ERROR_NOTCL] %s' % _("Adding tool from DB is not allowed for this object.")) - def on_plot_area_tab_closed(self, title): + def on_plot_area_tab_closed(self, tab_obj_name): """ - Executed whenever a tab is closed in the Plot Area. + Executed whenever a QTab is closed in the Plot Area. - :param title: The name of the tab that was closed. + :param title: The objectName of the Tab that was closed. This objectName is assigned on Tab creation :return: """ - if title == _("Preferences"): - # disconnect - for idx in range(self.ui.pref_tab_area.count()): - for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject): - try: - tb.textEdited.disconnect(self.on_preferences_edited) - except (TypeError, AttributeError): - pass - - try: - tb.modificationChanged.disconnect(self.on_preferences_edited) - except (TypeError, AttributeError): - pass - - try: - tb.toggled.disconnect(self.on_preferences_edited) - except (TypeError, AttributeError): - pass - - try: - tb.valueChanged.disconnect(self.on_preferences_edited) - except (TypeError, AttributeError): - pass - - try: - tb.currentIndexChanged.disconnect(self.on_preferences_edited) - except (TypeError, AttributeError): - pass - - if self.preferences_changed_flag is True: - msgbox = QtWidgets.QMessageBox() - msgbox.setText(_("One or more values are changed.\n" - "Do you want to save the Preferences?")) - msgbox.setWindowTitle(_("Save Preferences")) - msgbox.setWindowIcon(QtGui.QIcon(self.resource_location + '/save_as.png')) - - bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole) - msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole) - - msgbox.setDefaultButton(bt_yes) - msgbox.exec_() - response = msgbox.clickedButton() - - if response == bt_yes: - self.on_save_button(save_to_file=True) - self.inform.emit('[success] %s' % _("Preferences saved.")) - else: - self.preferences_changed_flag = False - self.inform.emit('') - return - - if title == _("Tools Database"): + if tab_obj_name == "preferences_tab": + self.preferencesUiManager.on_close_preferences_tab() + elif tab_obj_name == "database_tab": # disconnect the signals from the table widget in tab self.tools_db_tab.ui_disconnect() @@ -8068,13 +6024,25 @@ class App(QtCore.QObject): self.inform.emit('') return self.tools_db_tab.deleteLater() - - if title == _("Code Editor"): + elif tab_obj_name == "text_editor_tab": self.toggle_codeeditor = False - - if title == _("Bookmarks Manager"): + elif tab_obj_name == "bookmarks_tab": self.book_dialog_tab.rebuild_actions() self.book_dialog_tab.deleteLater() + else: + return + + def on_plotarea_tab_closed(self, tab_idx): + """ + + :param tab_idx: Index of the Tab from the plotarea that was closed + :return: + """ + widget = self.ui.plot_tab_area.widget(tab_idx) + + if widget is not None: + widget.deleteLater() + self.ui.plot_tab_area.removeTab(tab_idx) def on_flipy(self): """ @@ -8082,7 +6050,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_flipy()") + self.defaults.report_usage("on_flipy()") obj_list = self.collection.get_selected() xminlist = [] @@ -8129,7 +6097,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_flipx()") + self.defaults.report_usage("on_flipx()") obj_list = self.collection.get_selected() xminlist = [] @@ -8178,7 +6146,7 @@ class App(QtCore.QObject): :param preset: A value to be used as predefined angle for rotation. :return: """ - self.report_usage("on_rotate()") + self.defaults.report_usage("on_rotate()") obj_list = self.collection.get_selected() xminlist = [] @@ -8233,7 +6201,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_skewx()") + self.defaults.report_usage("on_skewx()") obj_list = self.collection.get_selected() xminlist = [] @@ -8272,7 +6240,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_skewy()") + self.defaults.report_usage("on_skewy()") obj_list = self.collection.get_selected() xminlist = [] @@ -8312,7 +6280,7 @@ class App(QtCore.QObject): :return: None """ if self.is_legacy is False: - self.plotcanvas.update() # TODO: Need update canvas? + self.plotcanvas.update() else: self.plotcanvas.auto_adjust_axes() @@ -8320,7 +6288,6 @@ class App(QtCore.QObject): self.collection.update_view() # self.inform.emit(_("Plots updated ...")) - # TODO: Rework toolbar 'clear', 'replot' functions def on_toolbar_replot(self): """ Callback for toolbar button. Re-plots all objects. @@ -8328,7 +6295,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_toolbar_replot") + self.defaults.report_usage("on_toolbar_replot") self.log.debug("on_toolbar_replot()") try: @@ -8372,7 +6339,6 @@ class App(QtCore.QObject): def on_collection_updated(self, obj, state, old_name): """ Create a menu from the object loaded in the collection. - TODO: should use the collection model to do this :param obj: object that was changed (added, deleted, renamed) :param state: what was done with the object. Can be: added, deleted, delete_all, renamed @@ -8572,8 +6538,11 @@ class App(QtCore.QObject): grid_toggle.triggered.connect(lambda: self.ui.grid_snap_btn.trigger()) def set_grid(self): - self.ui.grid_gap_x_entry.setText(self.sender().text()) - self.ui.grid_gap_y_entry.setText(self.sender().text()) + menu_action = self.sender() + assert isinstance(menu_action, QtWidgets.QAction), "Expected QAction got %s" % type(menu_action) + + self.ui.grid_gap_x_entry.setText(menu_action.text()) + self.ui.grid_gap_y_entry.setText(menu_action.text()) def on_grid_add(self): # ## Current application units in lower Case @@ -8631,7 +6600,7 @@ class App(QtCore.QObject): _("Delete Grid value cancelled")) def on_shortcut_list(self): - self.report_usage("on_shortcut_list()") + self.defaults.report_usage("on_shortcut_list()") # add the tab if it was closed self.ui.plot_tab_area.addTab(self.ui.shortcuts_tab, _("Key Shortcut List")) @@ -8660,7 +6629,7 @@ class App(QtCore.QObject): self.ui.notebook.setCurrentWidget(self.ui.tool_tab) def on_copy_name(self): - self.report_usage("on_copy_name()") + self.defaults.report_usage("on_copy_name()") obj = self.collection.get_active() try: @@ -9003,8 +6972,8 @@ class App(QtCore.QObject): curr_x, curr_y = self.pos for obj in self.all_objects_list: - # FlatCAMScript and FlatCAMDocument objects can't be selected - if isinstance(obj, FlatCAMScript) or isinstance(obj, FlatCAMDocument): + # ScriptObject and DocumentObject objects can't be selected + if isinstance(obj, ScriptObject) or isinstance(obj, DocumentObject): continue if key == 'multisel' and obj.options['name'] in self.objects_under_the_click_list: @@ -9324,10 +7293,11 @@ class App(QtCore.QObject): """ Returns the application to its startup state. This method is thread-safe. - :return: None + :param cli: Boolean. If True this method was run from command line + :return: None """ - self.report_usage("on_file_new") + self.defaults.report_usage("on_file_new") # Remove everything from memory App.log.debug("on_file_new()") @@ -9344,7 +7314,7 @@ class App(QtCore.QObject): for obj in self.collection.get_list(): # delete shapes left drawn from mark shape_collections, if any - if isinstance(obj, FlatCAMGerber): + if isinstance(obj, GerberObject): try: for el in obj.mark_shapes: obj.mark_shapes[el].clear(update=True) @@ -9354,7 +7324,7 @@ class App(QtCore.QObject): pass # also delete annotation shapes, if any - elif isinstance(obj, FlatCAMCNCjob): + elif isinstance(obj, CNCJobObject): try: obj.text_col.enabled = False del obj.text_col @@ -9364,7 +7334,7 @@ class App(QtCore.QObject): pass # tcl needs to be reinitialized, otherwise old shell variables etc remains - self.init_tcl() + self.shell.init_tcl() self.delete_selection_shape() self.collection.delete_all() @@ -9375,7 +7345,7 @@ class App(QtCore.QObject): self.project_filename = None # Load the application defaults - self.load_defaults(filename='current_defaults') + self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig')) # Re-fresh project options self.on_options_app2project() @@ -9406,7 +7376,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("obj_properties()") + self.defaults.report_usage("obj_properties()") self.properties_tool.run(toggle=False) def on_project_context_save(self): @@ -9417,17 +7387,17 @@ class App(QtCore.QObject): """ obj = self.collection.get_active() - if type(obj) == FlatCAMGeometry: + if type(obj) == GeometryObject: self.on_file_exportdxf() - elif type(obj) == FlatCAMExcellon: + elif type(obj) == ExcellonObject: self.on_file_saveexcellon() - elif type(obj) == FlatCAMCNCjob: + elif type(obj) == CNCJobObject: obj.on_exportgcode_button_click() - elif type(obj) == FlatCAMGerber: + elif type(obj) == GerberObject: self.on_file_savegerber() - elif type(obj) == FlatCAMScript: + elif type(obj) == ScriptObject: self.on_file_savescript() - elif type(obj) == FlatCAMDocument: + elif type(obj) == DocumentObject: self.on_file_savedocument() def obj_move(self): @@ -9437,10 +7407,10 @@ class App(QtCore.QObject): :return: """ - self.report_usage("obj_move()") + self.defaults.report_usage("obj_move()") self.move_tool.run(toggle=False) - def on_fileopengerber(self, signal: bool = None, name=None): + def on_fileopengerber(self, signal, name=None): """ File menu callback for opening a Gerber. @@ -9449,7 +7419,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_fileopengerber") + self.defaults.report_usage("on_fileopengerber") App.log.debug("on_fileopengerber()") _filter_ = "Gerber Files (*.gbr *.ger *.gtl *.gbl *.gts *.gbs *.gtp *.gbp *.gto *.gbo *.gm1 *.gml *.gm3 *" \ @@ -9487,7 +7457,7 @@ class App(QtCore.QObject): if filename != '': self.worker_task.emit({'fcn': self.open_gerber, 'params': [filename]}) - def on_fileopenexcellon(self, signal: bool = None, name=None): + def on_fileopenexcellon(self, signal, name=None): """ File menu callback for opening an Excellon file. @@ -9496,7 +7466,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_fileopenexcellon") + self.defaults.report_usage("on_fileopenexcellon") App.log.debug("on_fileopenexcellon()") _filter_ = "Excellon Files (*.drl *.txt *.xln *.drd *.tap *.exc *.ncd);;" \ @@ -9524,7 +7494,7 @@ class App(QtCore.QObject): if filename != '': self.worker_task.emit({'fcn': self.open_excellon, 'params': [filename]}) - def on_fileopengcode(self, signal: bool = None, name=None): + def on_fileopengcode(self, signal, name=None): """ File menu call back for opening gcode. @@ -9534,7 +7504,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_fileopengcode") + self.defaults.report_usage("on_fileopengcode") App.log.debug("on_fileopengcode()") # https://bobcadsupport.com/helpdesk/index.php?/Knowledgebase/Article/View/13/5/known-g-code-file-extensions @@ -9566,7 +7536,7 @@ class App(QtCore.QObject): if filename != '': self.worker_task.emit({'fcn': self.open_gcode, 'params': [filename, None, True]}) - def on_file_openproject(self, signal: bool = None): + def on_file_openproject(self, signal): """ File menu callback for opening a project. @@ -9574,7 +7544,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_openproject") + self.defaults.report_usage("on_file_openproject") App.log.debug("on_file_openproject()") _filter_ = "FlatCAM Project (*.FlatPrj);;All Files (*.*)" try: @@ -9597,15 +7567,16 @@ class App(QtCore.QObject): # thread safe. The new_project() self.open_project(filename) - def on_fileopenhpgl2(self, signal: bool = None, name=None): + def on_fileopenhpgl2(self, signal, name=None): """ File menu callback for opening a HPGL2. - :param signal: required because clicking the entry will generate a checked signal which needs a container - :return: None + :param signal: required because clicking the entry will generate a checked signal which needs a container + :param name: + :return: None """ - self.report_usage("on_fileopenhpgl2") + self.defaults.report_usage("on_fileopenhpgl2") App.log.debug("on_fileopenhpgl2()") _filter_ = "HPGL2 Files (*.plt);;" \ @@ -9635,15 +7606,15 @@ class App(QtCore.QObject): if filename != '': self.worker_task.emit({'fcn': self.open_hpgl2, 'params': [filename]}) - def on_file_openconfig(self, signal: bool = None): + def on_file_openconfig(self, signal): """ File menu callback for opening a config file. - :param signal: required because clicking the entry will generate a checked signal which needs a container - :return: None + :param signal: required because clicking the entry will generate a checked signal which needs a container + :return: None """ - self.report_usage("on_file_openconfig") + self.defaults.report_usage("on_file_openconfig") App.log.debug("on_file_openconfig()") _filter_ = "FlatCAM Config (*.FlatConfig);;FlatCAM Config (*.json);;All Files (*.*)" try: @@ -9664,7 +7635,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_exportsvg") + self.defaults.report_usage("on_file_exportsvg") App.log.debug("on_file_exportsvg()") obj = self.collection.get_active() @@ -9679,10 +7650,10 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if (not isinstance(obj, FlatCAMGeometry) - and not isinstance(obj, FlatCAMGerber) - and not isinstance(obj, FlatCAMCNCjob) - and not isinstance(obj, FlatCAMExcellon)): + if (not isinstance(obj, GeometryObject) + and not isinstance(obj, GerberObject) + and not isinstance(obj, CNCJobObject) + and not isinstance(obj, ExcellonObject)): msg = '[ERROR_NOTCL] %s' % \ _("Only Geometry, Gerber and CNCJob objects can be used.") msgbox = QtWidgets.QMessageBox() @@ -9715,7 +7686,7 @@ class App(QtCore.QObject): self.file_saved.emit("SVG", filename) def on_file_exportpng(self): - self.report_usage("on_file_exportpng") + self.defaults.report_usage("on_file_exportpng") App.log.debug("on_file_exportpng()") self.date = str(datetime.today()).rpartition('.')[0] @@ -9726,8 +7697,7 @@ class App(QtCore.QObject): image = _screenshot() 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')) + 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 (*.*)" @@ -9760,7 +7730,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_savegerber") + self.defaults.report_usage("on_file_savegerber") App.log.debug("on_file_savegerber()") obj = self.collection.get_active() @@ -9769,9 +7739,8 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMGerber): - self.inform.emit('[ERROR_NOTCL] %s' % - _("Failed. Only Gerber objects can be saved as Gerber files...")) + if not isinstance(obj, GerberObject): + self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Gerber objects can be saved as Gerber files...")) return name = self.collection.get_active().options["name"] @@ -9802,7 +7771,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_savescript") + self.defaults.report_usage("on_file_savescript") App.log.debug("on_file_savescript()") obj = self.collection.get_active() @@ -9811,7 +7780,7 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMScript): + if not isinstance(obj, ScriptObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Script objects can be saved as TCL Script files...")) return @@ -9843,7 +7812,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_savedocument") + self.defaults.report_usage("on_file_savedocument") App.log.debug("on_file_savedocument()") obj = self.collection.get_active() @@ -9852,7 +7821,7 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMScript): + if not isinstance(obj, ScriptObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Document objects can be saved as Document files...")) return @@ -9884,7 +7853,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_saveexcellon") + self.defaults.report_usage("on_file_saveexcellon") App.log.debug("on_file_saveexcellon()") obj = self.collection.get_active() @@ -9893,7 +7862,7 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMExcellon): + if not isinstance(obj, ExcellonObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) return @@ -9925,7 +7894,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_exportexcellon") + self.defaults.report_usage("on_file_exportexcellon") App.log.debug("on_file_exportexcellon()") obj = self.collection.get_active() @@ -9934,7 +7903,7 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMExcellon): + if not isinstance(obj, ExcellonObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) return @@ -9969,7 +7938,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_exportgerber") + self.defaults.report_usage("on_file_exportgerber") App.log.debug("on_file_exportgerber()") obj = self.collection.get_active() @@ -9978,7 +7947,7 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMGerber): + if not isinstance(obj, GerberObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Gerber objects can be saved as Gerber files...")) return @@ -10013,7 +7982,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_exportdxf") + self.defaults.report_usage("on_file_exportdxf") App.log.debug("on_file_exportdxf()") obj = self.collection.get_active() @@ -10028,7 +7997,7 @@ class App(QtCore.QObject): return # Check for more compatible types and add as required - if not isinstance(obj, FlatCAMGeometry): + if not isinstance(obj, GeometryObject): msg = '[ERROR_NOTCL] %s' % _("Only Geometry objects can be used.") msgbox = QtWidgets.QMessageBox() msgbox.setInformativeText(msg) @@ -10067,7 +8036,7 @@ class App(QtCore.QObject): :type type_of_obj: str :return: None """ - self.report_usage("on_file_importsvg") + self.defaults.report_usage("on_file_importsvg") App.log.debug("on_file_importsvg()") _filter_ = "SVG File .svg (*.svg);;All Files (*.*)" @@ -10098,7 +8067,7 @@ class App(QtCore.QObject): :type type_of_obj: str :return: None """ - self.report_usage("on_file_importdxf") + self.defaults.report_usage("on_file_importdxf") App.log.debug("on_file_importdxf()") _filter_ = "DXF File .dxf (*.DXF);;All Files (*.*)" @@ -10253,7 +8222,7 @@ class App(QtCore.QObject): # self.ui.show() def on_toggle_code_editor(self): - self.report_usage("on_toggle_code_editor()") + self.defaults.report_usage("on_toggle_code_editor()") if self.toggle_codeeditor is False: self.init_code_editor(name=_("Code Editor")) @@ -10336,7 +8305,7 @@ class App(QtCore.QObject): :return: """ - self.report_usage("on_fileopenscript") + self.defaults.report_usage("on_fileopenscript") App.log.debug("on_fileopenscript()") _filter_ = "TCL script .FlatScript (*.FlatScript);;TCL script .tcl (*.TCL);;TCL script .txt (*.TXT);;" \ @@ -10359,6 +8328,45 @@ class App(QtCore.QObject): 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.report_usage("on_fileopenscript_example") + App.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. @@ -10368,7 +8376,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_filerunscript") + self.defaults.report_usage("on_filerunscript") App.log.debug("on_file_runscript()") if name: @@ -10377,7 +8385,7 @@ class App(QtCore.QObject): self.splash.showMessage('%s: %ssec\n%s' % (_("Canvas initialization started.\n" "Canvas initialization finished in"), '%.2f' % self.used_time, - _("Executing FlatCAMScript file.") + _("Executing ScriptObject file.") ), alignment=Qt.AlignBottom | Qt.AlignLeft, color=QtGui.QColor("gray")) @@ -10392,7 +8400,6 @@ class App(QtCore.QObject): # The Qt methods above will return a QString which can cause problems later. # So far json.dump() will fail to serialize it. - # TODO: Improve the serialization methods and remove this fix. filename = str(filename) if filename == "": @@ -10427,7 +8434,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_saveproject") + self.defaults.report_usage("on_file_saveproject") if self.project_filename is None: self.on_file_saveprojectas() @@ -10454,7 +8461,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_file_saveprojectas") + self.defaults.report_usage("on_file_saveprojectas") self.date = str(datetime.today()).rpartition('.')[0] self.date = ''.join(c for c in self.date if c not in ':-') @@ -10723,7 +8730,7 @@ class App(QtCore.QObject): :param scale_stroke_factor: factor by which to change/scale the thickness of the features :return: """ - self.report_usage("export_svg()") + self.defaults.report_usage("export_svg()") if filename is None: filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \ @@ -10791,7 +8798,7 @@ class App(QtCore.QObject): :param use_thread: if to be run in a separate thread :return: """ - self.report_usage("save source file()") + self.defaults.report_usage("save source file()") if filename is None: filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \ @@ -10835,7 +8842,7 @@ class App(QtCore.QObject): :param use_thread: if to be run in a separate thread :return: """ - self.report_usage("export_excellon()") + self.defaults.report_usage("export_excellon()") if filename is None: if self.defaults["global_last_save_folder"]: @@ -10853,12 +8860,11 @@ class App(QtCore.QObject): try: obj = self.collection.get_by_name(str(obj_name)) except Exception: - # TODO: The return behavior has not been established... should raise exception? return "Could not retrieve object: %s" % obj_name else: obj = local_use - if not isinstance(obj, FlatCAMExcellon): + if not isinstance(obj, ExcellonObject): self.inform.emit('[ERROR_NOTCL] %s' % _("Failed. Only Excellon objects can be saved as Excellon files...")) return @@ -10991,7 +8997,7 @@ class App(QtCore.QObject): :param use_thread: if to be run in a separate thread :return: """ - self.report_usage("export_gerber()") + self.defaults.report_usage("export_gerber()") if filename is None: filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \ @@ -11003,7 +9009,6 @@ class App(QtCore.QObject): try: obj = self.collection.get_by_name(str(obj_name)) except Exception: - # TODO: The return behavior has not been established... should raise exception? return "Could not retrieve object: %s" % obj_name else: obj = local_use @@ -11125,7 +9130,7 @@ class App(QtCore.QObject): :param use_thread: if to be run in a separate thread :return: """ - self.report_usage("export_dxf()") + self.defaults.report_usage("export_dxf()") if filename is None: filename = self.defaults["global_last_save_folder"] if self.defaults["global_last_save_folder"] \ @@ -11167,7 +9172,7 @@ class App(QtCore.QObject): self.inform.emit('[WARNING_NOTCL] %s' % _('Could not export DXF file.')) return - def import_svg(self, filename, geo_type='geometry', outname=None): + 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. @@ -11177,7 +9182,7 @@ class App(QtCore.QObject): :param outname: :return: """ - self.report_usage("import_svg()") + self.defaults.report_usage("import_svg()") log.debug("App.import_svg()") obj_type = "" @@ -11202,7 +9207,11 @@ class App(QtCore.QObject): # Object name name = outname or filename.split('/')[-1].split('\\')[-1] - self.new_object(obj_type, name, obj_init, autoselected=False) + ret = self.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.file_opened.emit("svg", filename) @@ -11210,7 +9219,7 @@ class App(QtCore.QObject): # GUI feedback self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - def import_dxf(self, filename, geo_type='geometry', outname=None): + 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. @@ -11220,7 +9229,7 @@ class App(QtCore.QObject): :param outname: Name for the imported Geometry :return: """ - self.report_usage("import_dxf()") + self.defaults.report_usage("import_dxf()") obj_type = "" if geo_type is None or geo_type == "geometry": @@ -11238,35 +9247,42 @@ class App(QtCore.QObject): geo_obj.import_dxf(filename, obj_type, units=units) geo_obj.multigeo = False - with self.proc_container.new(_("Importing DXF")) as proc: + with self.proc_container.new(_("Importing DXF")): # Object name name = outname or filename.split('/')[-1].split('\\')[-1] - self.new_object(obj_type, name, obj_init, autoselected=False) + ret = self.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.file_opened.emit("dxf", filename) # GUI feedback self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - def open_gerber(self, filename, outname=None): + 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 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, FlatCAMGerber), \ - "Expected to initialize a FlatCAMGerber but got %s" % type(gerber_obj) + assert isinstance(gerber_obj, GerberObject), \ + "Expected to initialize a GerberObject but got %s" % type(gerber_obj) # Opening the file happens here try: @@ -11292,15 +9308,19 @@ class App(QtCore.QObject): App.log.debug("open_gerber()") - with self.proc_container.new(_("Opening Gerber")) as proc: + with self.proc_container.new(_("Opening Gerber")): # Object name name = outname or filename.split('/')[-1].split('\\')[-1] # # ## Object creation # ## - ret = self.new_object("gerber", name, obj_init, autoselected=False) - if ret == 'fail': - self.inform.emit('[ERROR_NOTCL]%s' % _(' Open Gerber failed. Probable not a Gerber file.')) - return 'fail' + ret_val = self.new_object("gerber", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + if from_tcl: + filename = self.defaults['global_tcl_path'] + '/' + name + ret_val = self.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.file_opened.emit("gerber", filename) @@ -11308,7 +9328,7 @@ class App(QtCore.QObject): # GUI feedback self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - def open_excellon(self, filename, outname=None, plot=True): + 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. @@ -11317,6 +9337,7 @@ class App(QtCore.QObject): :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 """ @@ -11352,28 +9373,29 @@ class App(QtCore.QObject): 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)) + app_obj.inform.emit('[ERROR_NOTCL] %s: %s' % (_("No geometry found in file"), filename)) return "fail" with self.proc_container.new(_("Opening Excellon.")): - # Object name name = outname or filename.split('/')[-1].split('\\')[-1] ret_val = self.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 + if from_tcl: + filename = self.defaults['global_tcl_path'] + '/' + name + ret_val = self.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.file_opened.emit("excellon", filename) # GUI feedback - self.inform.emit('[success] %s: %s' % - (_("Opened"), filename)) + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) - def open_gcode(self, filename, outname=None, force_parsing=None, plot=True): + 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. @@ -11381,7 +9403,8 @@ class App(QtCore.QObject): :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: + :param plot: If True plot the object on canvas + :param from_tcl: True if run from Tcl Shell :return: None """ App.log.debug("open_gcode()") @@ -11401,16 +9424,14 @@ class App(QtCore.QObject): gcode = f.read() f.close() except IOError: - app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Failed to open"), filename)) + app_obj_.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open"), filename)) return "fail" 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")) + self.inform.emit('[ERROR_NOTCL] %s' % _("This is not GCODE")) return "fail" job_obj.create_geometry() @@ -11421,21 +9442,24 @@ class App(QtCore.QObject): name = outname or filename.split('/')[-1].split('\\')[-1] # New object creation and file processing - obj_ret = self.new_object("cncjob", name, obj_init, autoselected=False, plot=plot) - if obj_ret == '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" + ret_val = self.new_object("cncjob", name, obj_init, autoselected=False, plot=plot) + if ret_val == 'fail': + if from_tcl: + filename = self.defaults['global_tcl_path'] + '/' + name + ret_val = self.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.file_opened.emit("cncjob", filename) # GUI feedback - self.inform.emit('[success] %s: %s' % - (_("Opened"), filename)) + self.inform.emit('[success] %s: %s' % (_("Opened"), filename)) def open_hpgl2(self, filename, outname=None): """ @@ -11451,8 +9475,8 @@ class App(QtCore.QObject): # How the object should be initialized def obj_init(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ - "Expected to initialize a FlatCAMGeometry but got %s" % type(geo_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) @@ -11583,7 +9607,7 @@ class App(QtCore.QObject): (_("Failed to open config file"), filename)) return - def open_project(self, filename, run_from_arg=None, plot=True, cli=None): + def open_project(self, filename, run_from_arg=None, plot=True, cli=None, from_tcl=False): """ Loads a project from the specified file. @@ -11598,6 +9622,7 @@ class App(QtCore.QObject): :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 """ App.log.debug("Opening project: " + filename) @@ -11621,16 +9646,25 @@ class App(QtCore.QObject): try: f = open(filename, 'r') except IOError: - App.log.error("Failed to open project file: %s" % filename) - self.inform.emit('[ERROR_NOTCL] %s: %s' % - (_("Failed to open project file"), filename)) - return + if from_tcl: + name = filename.split('/')[-1].split('\\')[-1] + filename = self.defaults['global_tcl_path'] + '/' + name + try: + f = open(filename, 'r') + except IOError: + log.error("Failed to open project file: %s" % filename) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), filename)) + return + else: + log.error("Failed to open project file: %s" % filename) + self.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Failed to open project file"), filename)) + return try: d = json.load(f, object_hook=dict2obj) except Exception as e: - App.log.error("Failed to parse project file, trying to see if it loads as an LZMA archive: %s because %s" % - (filename, str(e))) + log.error("Failed to parse project file, trying to see if it loads as an LZMA archive: %s because %s" % + (filename, str(e))) f.close() # Open and parse a compressed Project file @@ -11703,50 +9737,7 @@ class App(QtCore.QObject): App.log.debug(" **************** Finished PROJECT loading... **************** ") - def propagate_defaults(self, silent=False): - """ - This method is used to set default values in classes. It's - an alternative to project options but allows the use - of values invisible to the user. - :param silent: No messages - :return: None - """ - - if silent is False: - self.log.debug("propagate_defaults()") - - # Which objects to update the given parameters. - routes = { - "global_zdownrate": CNCjob, - "excellon_zeros": Excellon, - "excellon_format_upper_in": Excellon, - "excellon_format_lower_in": Excellon, - "excellon_format_upper_mm": Excellon, - "excellon_format_lower_mm": Excellon, - "excellon_units": Excellon, - "gerber_use_buffer_for_union": Gerber, - "geometry_multidepth": Geometry - } - - for param in routes: - if param in routes[param].defaults: - try: - routes[param].defaults[param] = self.defaults[param] - if silent is False: - self.log.debug(" " + param + " OK") - except KeyError: - if silent is False: - self.log.debug("FlatCAMApp.propagate_defaults() --> ERROR: " + param + " not in defaults.") - else: - # Try extracting the name: - # classname_param here is param in the object - if param.find(routes[param].__name__.lower() + "_") == 0: - p = param[len(routes[param].__name__) + 1:] - if p in routes[param].defaults: - routes[param].defaults[p] = self.defaults[param] - if silent is False: - self.log.debug(" " + param + " OK!") def plot_all(self, fit_view=True, use_thread=True): """ @@ -11806,7 +9797,6 @@ class App(QtCore.QObject): :return: """ - # TODO: Move this to constructor icons = { "gerber": self.resource_location + "/flatcam_icon16.png", "excellon": self.resource_location + "/drill16.png", @@ -11990,32 +9980,38 @@ class App(QtCore.QObject): tsize = fsize + int(fsize / 2) # selected_text = (_(''' - #

Selected Tab - Choose an Item from Project Tab

+ #

Selected Tab - Choose an Item from Project Tab + #

# #

Details:
# The normal flow when working in FlatCAM is the following:

# #
    - #
  1. Loat/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into + #
  2. Loat/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG + # file into # FlatCAM using either the menu's, toolbars, key shortcuts or # even dragging and dropping the files on the GUI.
    #
    - # You can also load a FlatCAM project by double clicking on the project file, drag & drop of the + # You can also load a FlatCAM project by double clicking on the project file, drag & + # drop of the # file into the FLATCAM GUI or through the menu/toolbar links offered within the app.

    #  
  3. - #
  4. Once an object is available in the Project Tab, by selecting it and then + #
  5. Once an object is available in the Project Tab, by selecting it + # and then # focusing on SELECTED TAB (more simpler is to double click the object name in the # Project Tab), SELECTED TAB will be updated with the object properties according to # it's kind: Gerber, Excellon, Geometry or CNCJob object.
    #
    - # If the selection of the object is done on the canvas by single click instead, and the SELECTED TAB + # If the selection of the object is done on the canvas by single click instead, and the + # SELECTED TAB # is in focus, again the object properties will be displayed into the Selected Tab. Alternatively, # double clicking on the object on the canvas will bring the SELECTED TAB and populate # it even if it was out of focus.
    #
    # You can change the parameters in this screen and the flow direction is like this:
    #
    - # Gerber/Excellon Object -> Change Param -> Generate Geometry -> Geometry Object + # Gerber/Excellon Object -> Change Param -> Generate Geometry -> + # Geometry Object # -> Add tools (change param in Selected Tab) -> Generate CNCJob -> CNCJob Object # -> Verify GCode (through Edit CNC Code) and/or append/prepend to GCode (again, done in # SELECTED TAB) -> Save GCode
  6. @@ -12128,7 +10124,7 @@ class App(QtCore.QObject): # no_stats dict; just so it won't break things on website no_ststs_dict = {} no_ststs_dict["global_ststs"] = {} - full_url = App.version_url + "?s=" + str(self.defaults['global_serial']) + "&v=" + str(self.version) + \ + full_url = App.version_url + "?s=" + str(self.defaults['global_serial']) + "&v=" + str(self.version) +\ "&os=" + str(self.os) + "&" + urllib.parse.urlencode(no_ststs_dict["global_ststs"]) App.log.debug("Checking for updates @ %s" % full_url) @@ -12269,21 +10265,21 @@ class App(QtCore.QObject): self.plotcanvas.zoom(float(self.defaults['global_zoom_ratio'])) def disable_all_plots(self): - self.report_usage("disable_all_plots()") + self.defaults.report_usage("disable_all_plots()") self.disable_plots(self.collection.get_list()) self.inform.emit('[success] %s' % _("All plots disabled.")) def disable_other_plots(self): - self.report_usage("disable_other_plots()") + self.defaults.report_usage("disable_other_plots()") self.disable_plots(self.collection.get_non_selected()) self.inform.emit('[success] %s' % _("All non selected plots disabled.")) def enable_all_plots(self): - self.report_usage("enable_all_plots()") + self.defaults.report_usage("enable_all_plots()") self.enable_plots(self.collection.get_list()) self.inform.emit('[success] %s' % @@ -12337,7 +10333,7 @@ class App(QtCore.QObject): with self.proc_container.new(_("Enabling plots ...")): for plot_obj in objs: # obj.options['plot'] = True - if isinstance(plot_obj, FlatCAMCNCjob): + if isinstance(plot_obj, CNCJobObject): plot_obj.plot(visible=True, kind=self.defaults["cncjob_plot_kind"]) else: plot_obj.plot(visible=True) @@ -12389,7 +10385,7 @@ class App(QtCore.QObject): with self.proc_container.new(_("Disabling plots ...")): for plot_obj in objs: # obj.options['plot'] = True - if isinstance(plot_obj, FlatCAMCNCjob): + if isinstance(plot_obj, CNCJobObject): plot_obj.plot(visible=False, kind=self.defaults["cncjob_plot_kind"]) else: plot_obj.plot(visible=False) @@ -12441,7 +10437,7 @@ class App(QtCore.QObject): new_color = self.defaults['gerber_plot_fill'] clicked_action = self.sender() - assert isinstance(clicked_action, QAction), "Expected a QAction, got %s" % isinstance(clicked_action, QAction) + assert isinstance(clicked_action, QAction), "Expected a QAction, got %s" % type(clicked_action) act_name = clicked_action.text() sel_obj_list = self.collection.get_selected() @@ -12574,14 +10570,14 @@ class App(QtCore.QObject): :param objects: Selected objects in the Project Tab :return: """ - self.report_usage("generate_cnc_job()") + self.defaults.report_usage("generate_cnc_job()") # for obj in objects: # obj.generatecncjob() for obj in objects: obj.on_generatecnc_button_click() - def save_project(self, filename, quit_action=False, silent=False): + def save_project(self, filename, quit_action=False, silent=False, from_tcl=False): """ Saves the current project to the specified file. @@ -12589,6 +10585,7 @@ class App(QtCore.QObject): :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()") @@ -12717,24 +10714,16 @@ class App(QtCore.QObject): :return: """ log.debug("App.save_project_auto_update() --> updated the interval timeout.") - if self.autosave_timer.isActive(): - self.autosave_timer.stop() + try: + if self.autosave_timer.isActive(): + self.autosave_timer.stop() + except Exception: + pass + if self.defaults['global_autosave'] is True: self.autosave_timer.setInterval(int(self.defaults['global_autosave_timeout'])) self.autosave_timer.start() - def on_plotarea_tab_closed(self, tab_idx): - """ - - :param tab_idx: Index of the Tab from the plotarea that was closed - :return: - """ - widget = self.ui.plot_tab_area.widget(tab_idx) - - if widget is not None: - widget.deleteLater() - self.ui.plot_tab_area.removeTab(tab_idx) - def on_options_app2project(self): """ Callback for Options->Transfer Options->App=>Project. Copies options @@ -12743,216 +10732,10 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_options_app2project") + self.defaults.report_usage("on_options_app2project") - self.defaults_read_form() + self.preferencesUiManager.defaults_read_form() self.options.update(self.defaults) - # self.options_write_form() - - def init_tcl(self): - """ - Initialize the TCL Shell. A dock widget that holds the GUI interface to the FlatCAM command line. - :return: None - """ - if hasattr(self, 'tcl') and self.tcl is not None: - # self.tcl = None - # TODO we need to clean non default variables and procedures here - # new object cannot be used here as it will not remember values created for next passes, - # because tcl was executed in old instance of TCL - pass - else: - self.tcl = tk.Tcl() - self.setup_shell() - self.log.debug("TCL Shell has been initialized.") - - def setup_shell(self): - """ - Creates shell functions. Runs once at startup. - - :return: None - """ - - self.log.debug("setup_shell()") - - # def shelp(p=None): - # pass - - # --- Migrated to new architecture --- - # def options(name): - # ops = self.collection.get_by_name(str(name)).options - # return '\n'.join(["%s: %s" % (o, ops[o]) for o in ops]) - - # def h(*args): - # """ - # Pre-processes arguments to detect '-keyword value' pairs into dictionary - # and standalone parameters into list. - # """ - # - # kwa = {} - # a = [] - # n = len(args) - # name = None - # for i in range(n): - # match = re.search(r'^-([a-zA-Z].*)', args[i]) - # if match: - # assert name is None - # name = match.group(1) - # continue - # - # if name is None: - # a.append(args[i]) - # else: - # kwa[name] = args[i] - # name = None - # - # return a, kwa - - # @contextmanager - # def wait_signal(signal, timeout=10000): - # """ - # Block loop until signal emitted, timeout (ms) elapses - # or unhandled exception happens in a thread. - # - # :param timeout: time after which the loop is exited - # :param signal: Signal to wait for. - # """ - # loop = QtCore.QEventLoop() - # - # # Normal termination - # signal.connect(loop.quit) - # - # # Termination by exception in thread - # self.thread_exception.connect(loop.quit) - # - # status = {'timed_out': False} - # - # def report_quit(): - # status['timed_out'] = True - # loop.quit() - # - # yield - # - # # Temporarily change how exceptions are managed. - # oeh = sys.excepthook - # ex = [] - # - # def except_hook(type_, value, traceback_): - # ex.append(value) - # oeh(type_, value, traceback_) - # - # sys.excepthook = except_hook - # - # # Terminate on timeout - # if timeout is not None: - # QtCore.QTimer.singleShot(timeout, report_quit) - # - # # # ## Block ## ## - # loop.exec_() - # - # # Restore exception management - # sys.excepthook = oeh - # if ex: - # self.raise_tcl_error(str(ex[0])) - # - # if status['timed_out']: - # raise Exception('Timed out!') - # - # def make_docs(): - # output = '' - # import collections - # od = collections.OrderedDict(sorted(self.tcl_commands_storage.items())) - # for cmd_, val in od.items(): - # output += cmd_ + ' \n' + ''.join(['~'] * len(cmd_)) + '\n' - # - # t = val['help'] - # usage_i = t.find('>') - # if usage_i < 0: - # expl = t - # output += expl + '\n\n' - # continue - # - # expl = t[:usage_i - 1] - # output += expl + '\n\n' - # - # end_usage_i = t[usage_i:].find('\n') - # - # if end_usage_i < 0: - # end_usage_i = len(t[usage_i:]) - # output += ' ' + t[usage_i:] + '\n No parameters.\n' - # else: - # extras = t[usage_i + end_usage_i + 1:] - # parts = [s.strip() for s in extras.split('\n')] - # - # output += ' ' + t[usage_i:usage_i + end_usage_i] + '\n' - # for p in parts: - # output += ' ' + p + '\n\n' - # - # return output - - ''' - Howto implement TCL shell commands: - - All parameters passed to command should be possible to set as None and test it afterwards. - This is because we need to see error caused in tcl, - if None value as default parameter is not allowed TCL will return empty error. - Use: - def mycommand(name=None,...): - - Test it like this: - if name is None: - - self.raise_tcl_error('Argument name is missing.') - - When error ocurre, always use raise_tcl_error, never return "sometext" on error, - otherwise we will miss it and processing will silently continue. - Method raise_tcl_error pass error into TCL interpreter, then raise python exception, - which is catched in exec_command and displayed in TCL shell console with red background. - Error in console is displayed with TCL trace. - - This behavior works only within main thread, - errors with promissed tasks can be catched and detected only with log. - TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for - TCL shell. - - Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules. - - ''' - - self.tcl_commands_storage = {} - # commands = { - # 'help': { - # 'fcn': shelp, - # 'help': _("Shows list of commands."), - # 'description': '' - # }, - # } - - # Import/overwrite tcl commands as objects of TclCommand descendants - # This modifies the variable 'commands'. - tclCommands.register_all_commands(self, self.tcl_commands_storage) - - # Add commands to the tcl interpreter - for cmd in self.tcl_commands_storage: - self.tcl.createcommand(cmd, self.tcl_commands_storage[cmd]['fcn']) - - # Make the tcl puts function return instead of print to stdout - self.tcl.eval(''' - rename puts original_puts - proc puts {args} { - if {[llength $args] == 1} { - return "[lindex $args 0]" - } else { - eval original_puts $args - } - } - ''') - - # TODO: This shouldn't be here. - class TclErrorException(Exception): - """ - this exception is defined here, to be able catch it if we successfully handle all errors from shell command - """ - pass def toggle_shell(self): """ @@ -12960,7 +10743,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("toggle_shell()") + self.defaults.report_usage("toggle_shell()") if self.ui.shell_dock.isVisible(): self.ui.shell_dock.hide() @@ -12988,7 +10771,7 @@ class App(QtCore.QObject): :return: None """ - self.report_usage("on_toggle_shell_from_settings()") + self.defaults.report_usage("on_toggle_shell_from_settings()") if state is True: if not self.ui.shell_dock.isVisible(): @@ -13025,62 +10808,6 @@ class App(QtCore.QObject): except AttributeError: log.debug("shell_message() is called before Shell Class is instantiated. The message is: %s", str(msg)) - def raise_tcl_unknown_error(self, unknownException): - """ - Raise exception if is different type than TclErrorException - this is here mainly to show unknown errors inside TCL shell console. - - :param unknownException: - :return: - """ - - if not isinstance(unknownException, self.TclErrorException): - self.raise_tcl_error("Unknown error: %s" % str(unknownException)) - else: - raise unknownException - - def display_tcl_error(self, error, error_info=None): - """ - Escape bracket [ with '\' otherwise there is error - "ERROR: missing close-bracket" instead of real error - - :param error: it may be text or exception - :param error_info: Some informations about the error - :return: None - """ - - if isinstance(error, Exception): - exc_type, exc_value, exc_traceback = error_info - if not isinstance(error, self.TclErrorException): - show_trace = 1 - else: - show_trace = int(self.defaults['global_verbose_error_level']) - - if show_trace > 0: - trc = traceback.format_list(traceback.extract_tb(exc_traceback)) - trc_formated = [] - for a in reversed(trc): - trc_formated.append(a.replace(" ", " > ").replace("\n", "")) - text = "%s\nPython traceback: %s\n%s" % (exc_value, exc_type, "\n".join(trc_formated)) - else: - text = "%s" % error - else: - text = error - - text = text.replace('[', '\\[').replace('"', '\\"') - self.tcl.eval('return -code error "%s"' % text) - - def raise_tcl_error(self, text): - """ - This method pass exception from python into TCL as error, so we get stacktrace and reason - - :param text: text of error - :return: raise exception - """ - - self.display_tcl_error(text) - raise self.TclErrorException(text) - class ArgsThread(QtCore.QObject): open_signal = pyqtSignal(list) @@ -13094,29 +10821,40 @@ class ArgsThread(QtCore.QObject): def __init__(self): super(ArgsThread, self).__init__() self.listener = None + self.thread_exit = False self.start.connect(self.run) def my_loop(self, address): try: self.listener = Listener(*address) - while True: + while self.thread_exit is False: conn = self.listener.accept() self.serve(conn) except socket.error: - conn = Client(*address) - conn.send(sys.argv) - conn.send('close') - # close the current instance only if there are args - if len(sys.argv) > 1: - try: - self.listener.close() - except Exception: + try: + conn = Client(*address) + conn.send(sys.argv) + conn.send('close') + # close the current instance only if there are args + if len(sys.argv) > 1: + try: + self.listener.close() + except Exception: + pass + sys.exit() + except ConnectionRefusedError: + if sys.platform == 'win32': pass - sys.exit() + else: + os.system('rm /tmp/testipc') + self.listener = Listener(*address) + while True: + conn = self.listener.accept() + self.serve(conn) def serve(self, conn): - while True: + while self.thread_exit is False: msg = conn.recv() if msg == 'close': break @@ -13129,13 +10867,4 @@ class ArgsThread(QtCore.QObject): def run(self): self.my_loop(self.address) - -class GracefulException(Exception): - # Graceful Exception raised when the user is requesting to cancel the current threaded task - def __init__(self): - super().__init__() - - def __str__(self): - return '\n\n%s' % _("The user requested a graceful exit of the current task.") - # end of file diff --git a/FlatCAMBookmark.py b/FlatCAMBookmark.py new file mode 100644 index 00000000..3ed5a74d --- /dev/null +++ b/FlatCAMBookmark.py @@ -0,0 +1,381 @@ +from PyQt5 import QtGui, QtCore, QtWidgets +from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCFileSaveDialog + +import sys +import webbrowser + +from copy import deepcopy +from datetime import datetime +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class BookmarkManager(QtWidgets.QWidget): + + mark_rows = QtCore.pyqtSignal() + + def __init__(self, app, storage, parent=None): + super(BookmarkManager, self).__init__(parent) + + self.app = app + + assert isinstance(storage, dict), "Storage argument is not a dictionary" + + self.bm_dict = deepcopy(storage) + + # Icon and title + # self.setWindowIcon(parent.app_icon) + # self.setWindowTitle(_("Bookmark Manager")) + # self.resize(600, 400) + + # title = QtWidgets.QLabel( + # "FlatCAM
    " + # ) + # title.setOpenExternalLinks(True) + + # layouts + layout = QtWidgets.QVBoxLayout() + self.setLayout(layout) + + table_hlay = QtWidgets.QHBoxLayout() + layout.addLayout(table_hlay) + + self.table_widget = FCTable(drag_drop=True, protected_rows=[0, 1]) + self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + table_hlay.addWidget(self.table_widget) + + self.table_widget.setColumnCount(3) + self.table_widget.setColumnWidth(0, 20) + self.table_widget.setHorizontalHeaderLabels( + [ + '#', + _('Title'), + _('Web Link') + ] + ) + self.table_widget.horizontalHeaderItem(0).setToolTip( + _("Index.\n" + "The rows in gray color will populate the Bookmarks menu.\n" + "The number of gray colored rows is set in Preferences.")) + self.table_widget.horizontalHeaderItem(1).setToolTip( + _("Description of the link that is set as an menu action.\n" + "Try to keep it short because it is installed as a menu item.")) + self.table_widget.horizontalHeaderItem(2).setToolTip( + _("Web Link. E.g: https://your_website.org ")) + + # pal = QtGui.QPalette() + # pal.setColor(QtGui.QPalette.Background, Qt.white) + + # New Bookmark + new_vlay = QtWidgets.QVBoxLayout() + layout.addLayout(new_vlay) + + new_title_lbl = QtWidgets.QLabel('%s' % _("New Bookmark")) + new_vlay.addWidget(new_title_lbl) + + form0 = QtWidgets.QFormLayout() + new_vlay.addLayout(form0) + + title_lbl = QtWidgets.QLabel('%s:' % _("Title")) + self.title_entry = FCEntry() + form0.addRow(title_lbl, self.title_entry) + + link_lbl = QtWidgets.QLabel('%s:' % _("Web Link")) + self.link_entry = FCEntry() + self.link_entry.set_value('http://') + form0.addRow(link_lbl, self.link_entry) + + # Buttons Layout + button_hlay = QtWidgets.QHBoxLayout() + layout.addLayout(button_hlay) + + add_entry_btn = FCButton(_("Add Entry")) + remove_entry_btn = FCButton(_("Remove Entry")) + export_list_btn = FCButton(_("Export List")) + import_list_btn = FCButton(_("Import List")) + # closebtn = QtWidgets.QPushButton(_("Close")) + + # button_hlay.addStretch() + button_hlay.addWidget(add_entry_btn) + button_hlay.addWidget(remove_entry_btn) + + button_hlay.addWidget(export_list_btn) + button_hlay.addWidget(import_list_btn) + # button_hlay.addWidget(closebtn) + # ############################################################################## + # ######################## SIGNALS ############################################# + # ############################################################################## + + add_entry_btn.clicked.connect(self.on_add_entry) + remove_entry_btn.clicked.connect(self.on_remove_entry) + export_list_btn.clicked.connect(self.on_export_bookmarks) + import_list_btn.clicked.connect(self.on_import_bookmarks) + self.title_entry.returnPressed.connect(self.on_add_entry) + self.link_entry.returnPressed.connect(self.on_add_entry) + # closebtn.clicked.connect(self.accept) + + self.table_widget.drag_drop_sig.connect(self.mark_table_rows_for_actions) + self.build_bm_ui() + + def build_bm_ui(self): + + self.table_widget.setRowCount(len(self.bm_dict)) + + nr_crt = 0 + sorted_bookmarks = sorted(list(self.bm_dict.items()), key=lambda x: int(x[0])) + for entry, bookmark in sorted_bookmarks: + row = nr_crt + nr_crt += 1 + + title = bookmark[0] + weblink = bookmark[1] + + id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt)) + # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.table_widget.setItem(row, 0, id_item) # Tool name/id + + title_item = QtWidgets.QTableWidgetItem(title) + self.table_widget.setItem(row, 1, title_item) + + weblink_txt = QtWidgets.QTextBrowser() + weblink_txt.setOpenExternalLinks(True) + weblink_txt.setFrameStyle(QtWidgets.QFrame.NoFrame) + weblink_txt.document().setDefaultStyleSheet("a{ text-decoration: none; }") + + weblink_txt.setHtml('%s' % (weblink, weblink)) + + self.table_widget.setCellWidget(row, 2, weblink_txt) + + vertical_header = self.table_widget.verticalHeader() + vertical_header.hide() + + horizontal_header = self.table_widget.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 20) + horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) + + self.mark_table_rows_for_actions() + + self.app.defaults["global_bookmarks"].clear() + for key, val in self.bm_dict.items(): + self.app.defaults["global_bookmarks"][key] = deepcopy(val) + + def on_add_entry(self, **kwargs): + """ + Add a entry in the Bookmark Table and in the menu actions + :return: None + """ + if 'title' in kwargs: + title = kwargs['title'] + else: + title = self.title_entry.get_value() + if title == '': + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Title entry is empty.")) + return 'fail' + + if 'link' in kwargs: + link = kwargs['link'] + else: + link = self.link_entry.get_value() + + if link == 'http://': + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Web link entry is empty.")) + return 'fail' + + # if 'http' not in link or 'https' not in link: + # link = 'http://' + link + + for bookmark in self.bm_dict.values(): + if title == bookmark[0] or link == bookmark[1]: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Either the Title or the Weblink already in the table.")) + return 'fail' + + # for some reason if the last char in the weblink is a slash it does not make the link clickable + # so I remove it + if link[-1] == '/': + link = link[:-1] + # add the new entry to storage + new_entry = len(self.bm_dict) + 1 + self.bm_dict[str(new_entry)] = [title, link] + + # add the link to the menu but only if it is within the set limit + bm_limit = int(self.app.defaults["global_bookmarks_limit"]) + if len(self.bm_dict) < bm_limit: + act = QtWidgets.QAction(parent=self.app.ui.menuhelp_bookmarks) + act.setText(title) + act.setIcon(QtGui.QIcon(self.app.resource_location + '/link16.png')) + act.triggered.connect(lambda: webbrowser.open(link)) + self.app.ui.menuhelp_bookmarks.insertAction(self.app.ui.menuhelp_bookmarks_manager, act) + + self.app.inform.emit('[success] %s' % _("Bookmark added.")) + + # add the new entry to the bookmark manager table + self.build_bm_ui() + + def on_remove_entry(self): + """ + Remove an Entry in the Bookmark table and from the menu actions + :return: + """ + index_list = [] + for model_index in self.table_widget.selectionModel().selectedRows(): + index = QtCore.QPersistentModelIndex(model_index) + index_list.append(index) + title_to_remove = self.table_widget.item(model_index.row(), 1).text() + + if title_to_remove == 'FlatCAM' or title_to_remove == 'Backup Site': + self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed")) + self.build_bm_ui() + return + else: + for k, bookmark in list(self.bm_dict.items()): + if title_to_remove == bookmark[0]: + # remove from the storage + self.bm_dict.pop(k, None) + + for act in self.app.ui.menuhelp_bookmarks.actions(): + if act.text() == title_to_remove: + # disconnect the signal + try: + act.triggered.disconnect() + except TypeError: + pass + # remove the action from the menu + self.app.ui.menuhelp_bookmarks.removeAction(act) + + # house keeping: it pays to have keys increased by one + new_key = 0 + new_dict = {} + for k, v in self.bm_dict.items(): + # we start with key 1 so we can use the len(self.bm_dict) + # when adding bookmarks (keys in bm_dict) + new_key += 1 + new_dict[str(new_key)] = v + + self.bm_dict = deepcopy(new_dict) + new_dict.clear() + + self.app.inform.emit('[success] %s' % _("Bookmark removed.")) + + # for index in index_list: + # self.table_widget.model().removeRow(index.row()) + self.build_bm_ui() + + def on_export_bookmarks(self): + self.app.defaults.report_usage("on_export_bookmarks") + self.app.log.debug("on_export_bookmarks()") + + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + + filter__ = "Text File (*.TXT);;All Files (*.*)" + filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export FlatCAM Bookmarks"), + directory='{l_save}/FlatCAM_{n}_{date}'.format( + l_save=str(self.app.get_last_save_folder()), + n=_("Bookmarks"), + date=date), + filter=filter__) + + filename = str(filename) + + if filename == "": + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + try: + f = open(filename, 'w') + f.close() + except PermissionError: + self.app.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return + except IOError: + self.app.log.debug('Creating a new bookmarks file ...') + f = open(filename, 'w') + f.close() + except Exception: + e = sys.exc_info()[0] + self.app.log.error("Could not load defaults file.") + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file.")) + return + + # Save Bookmarks to a file + try: + with open(filename, "w") as f: + for title, link in self.bm_dict.items(): + line2write = str(title) + ':' + str(link) + '\n' + f.write(line2write) + except Exception: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write bookmarks to file.")) + return + self.app.inform.emit('[success] %s: %s' % (_("Exported bookmarks to"), filename)) + + def on_import_bookmarks(self): + self.app.log.debug("on_import_bookmarks()") + + filter_ = "Text File (*.txt);;All Files (*.*)" + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_) + + filename = str(filename) + + if filename == "": + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + try: + with open(filename) as f: + bookmarks = f.readlines() + except IOError: + self.app.log.error("Could not load bookmarks file.") + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file.")) + return + + for line in bookmarks: + proc_line = line.replace(' ', '').partition(':') + self.on_add_entry(title=proc_line[0], link=proc_line[2]) + + self.app.inform.emit('[success] %s: %s' % (_("Imported Bookmarks from"), filename)) + + def mark_table_rows_for_actions(self): + for row in range(self.table_widget.rowCount()): + item_to_paint = self.table_widget.item(row, 0) + if row < self.app.defaults["global_bookmarks_limit"]: + item_to_paint.setBackground(QtGui.QColor('gray')) + # item_to_paint.setForeground(QtGui.QColor('black')) + else: + item_to_paint.setBackground(QtGui.QColor('white')) + # item_to_paint.setForeground(QtGui.QColor('black')) + + def rebuild_actions(self): + # rebuild the storage to reflect the order of the lines + self.bm_dict.clear() + for row in range(self.table_widget.rowCount()): + title = self.table_widget.item(row, 1).text() + wlink = self.table_widget.cellWidget(row, 2).toPlainText() + + entry = int(row) + 1 + self.bm_dict.update( + { + str(entry): [title, wlink] + } + ) + + self.app.install_bookmarks(book_dict=self.bm_dict) + + # def accept(self): + # self.rebuild_actions() + # super().accept() + + def closeEvent(self, QCloseEvent): + self.rebuild_actions() + super().closeEvent(QCloseEvent) diff --git a/FlatCAMCommon.py b/FlatCAMCommon.py index cabf0e47..952ed0cd 100644 --- a/FlatCAMCommon.py +++ b/FlatCAMCommon.py @@ -11,17 +11,6 @@ # Date: 11/4/2019 # # ########################################################## -from PyQt5 import QtGui, QtCore, QtWidgets -from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \ - FCTree, RadioSet, FCFileSaveDialog -from camlib import to_dict - -import sys -import webbrowser -import json - -from copy import deepcopy -from datetime import datetime import gettext import FlatCAMTranslation as fcTranslate import builtins @@ -31,10 +20,18 @@ if '_' not in builtins.__dict__: _ = gettext.gettext +class GracefulException(Exception): + # Graceful Exception raised when the user is requesting to cancel the current threaded task + def __init__(self): + super().__init__() + + def __str__(self): + return '\n\n%s' % _("The user requested a graceful exit of the current task.") + + class LoudDict(dict): """ - A Dictionary with a callback for - item changes. + A Dictionary with a callback for item changes. """ def __init__(self, *args, **kwargs): @@ -43,8 +40,7 @@ class LoudDict(dict): def __setitem__(self, key, value): """ - Overridden __setitem__ method. Will emit 'changed(QString)' - if the item was changed, with key as parameter. + Overridden __setitem__ method. Will emit 'changed(QString)' if the item was changed, with key as parameter. """ if key in self and self.__getitem__(key) == value: return @@ -95,2752 +91,6 @@ class FCSignal: 'from signal %s' % (func, self)) -class BookmarkManager(QtWidgets.QWidget): - - mark_rows = QtCore.pyqtSignal() - - def __init__(self, app, storage, parent=None): - super(BookmarkManager, self).__init__(parent) - - self.app = app - - assert isinstance(storage, dict), "Storage argument is not a dictionary" - - self.bm_dict = deepcopy(storage) - - # Icon and title - # self.setWindowIcon(parent.app_icon) - # self.setWindowTitle(_("Bookmark Manager")) - # self.resize(600, 400) - - # title = QtWidgets.QLabel( - # "FlatCAM
    " - # ) - # title.setOpenExternalLinks(True) - - # layouts - layout = QtWidgets.QVBoxLayout() - self.setLayout(layout) - - table_hlay = QtWidgets.QHBoxLayout() - layout.addLayout(table_hlay) - - self.table_widget = FCTable(drag_drop=True, protected_rows=[0, 1]) - self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - table_hlay.addWidget(self.table_widget) - - self.table_widget.setColumnCount(3) - self.table_widget.setColumnWidth(0, 20) - self.table_widget.setHorizontalHeaderLabels( - [ - '#', - _('Title'), - _('Web Link') - ] - ) - self.table_widget.horizontalHeaderItem(0).setToolTip( - _("Index.\n" - "The rows in gray color will populate the Bookmarks menu.\n" - "The number of gray colored rows is set in Preferences.")) - self.table_widget.horizontalHeaderItem(1).setToolTip( - _("Description of the link that is set as an menu action.\n" - "Try to keep it short because it is installed as a menu item.")) - self.table_widget.horizontalHeaderItem(2).setToolTip( - _("Web Link. E.g: https://your_website.org ")) - - # pal = QtGui.QPalette() - # pal.setColor(QtGui.QPalette.Background, Qt.white) - - # New Bookmark - new_vlay = QtWidgets.QVBoxLayout() - layout.addLayout(new_vlay) - - new_title_lbl = QtWidgets.QLabel('%s' % _("New Bookmark")) - new_vlay.addWidget(new_title_lbl) - - form0 = QtWidgets.QFormLayout() - new_vlay.addLayout(form0) - - title_lbl = QtWidgets.QLabel('%s:' % _("Title")) - self.title_entry = FCEntry() - form0.addRow(title_lbl, self.title_entry) - - link_lbl = QtWidgets.QLabel('%s:' % _("Web Link")) - self.link_entry = FCEntry() - self.link_entry.set_value('http://') - form0.addRow(link_lbl, self.link_entry) - - # Buttons Layout - button_hlay = QtWidgets.QHBoxLayout() - layout.addLayout(button_hlay) - - add_entry_btn = FCButton(_("Add Entry")) - remove_entry_btn = FCButton(_("Remove Entry")) - export_list_btn = FCButton(_("Export List")) - import_list_btn = FCButton(_("Import List")) - # closebtn = QtWidgets.QPushButton(_("Close")) - - # button_hlay.addStretch() - button_hlay.addWidget(add_entry_btn) - button_hlay.addWidget(remove_entry_btn) - - button_hlay.addWidget(export_list_btn) - button_hlay.addWidget(import_list_btn) - # button_hlay.addWidget(closebtn) - # ############################################################################## - # ######################## SIGNALS ############################################# - # ############################################################################## - - add_entry_btn.clicked.connect(self.on_add_entry) - remove_entry_btn.clicked.connect(self.on_remove_entry) - export_list_btn.clicked.connect(self.on_export_bookmarks) - import_list_btn.clicked.connect(self.on_import_bookmarks) - self.title_entry.returnPressed.connect(self.on_add_entry) - self.link_entry.returnPressed.connect(self.on_add_entry) - # closebtn.clicked.connect(self.accept) - - self.table_widget.drag_drop_sig.connect(self.mark_table_rows_for_actions) - self.build_bm_ui() - - def build_bm_ui(self): - - self.table_widget.setRowCount(len(self.bm_dict)) - - nr_crt = 0 - sorted_bookmarks = sorted(list(self.bm_dict.items()), key=lambda x: int(x[0])) - for entry, bookmark in sorted_bookmarks: - row = nr_crt - nr_crt += 1 - - title = bookmark[0] - weblink = bookmark[1] - - id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt)) - # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.table_widget.setItem(row, 0, id_item) # Tool name/id - - title_item = QtWidgets.QTableWidgetItem(title) - self.table_widget.setItem(row, 1, title_item) - - weblink_txt = QtWidgets.QTextBrowser() - weblink_txt.setOpenExternalLinks(True) - weblink_txt.setFrameStyle(QtWidgets.QFrame.NoFrame) - weblink_txt.document().setDefaultStyleSheet("a{ text-decoration: none; }") - - weblink_txt.setHtml('%s' % (weblink, weblink)) - - self.table_widget.setCellWidget(row, 2, weblink_txt) - - vertical_header = self.table_widget.verticalHeader() - vertical_header.hide() - - horizontal_header = self.table_widget.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(0, 20) - horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) - - self.mark_table_rows_for_actions() - - self.app.defaults["global_bookmarks"].clear() - for key, val in self.bm_dict.items(): - self.app.defaults["global_bookmarks"][key] = deepcopy(val) - - def on_add_entry(self, **kwargs): - """ - Add a entry in the Bookmark Table and in the menu actions - :return: None - """ - if 'title' in kwargs: - title = kwargs['title'] - else: - title = self.title_entry.get_value() - if title == '': - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Title entry is empty.")) - return 'fail' - - if 'link' in kwargs: - link = kwargs['link'] - else: - link = self.link_entry.get_value() - - if link == 'http://': - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Web link entry is empty.")) - return 'fail' - - # if 'http' not in link or 'https' not in link: - # link = 'http://' + link - - for bookmark in self.bm_dict.values(): - if title == bookmark[0] or link == bookmark[1]: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Either the Title or the Weblink already in the table.")) - return 'fail' - - # for some reason if the last char in the weblink is a slash it does not make the link clickable - # so I remove it - if link[-1] == '/': - link = link[:-1] - # add the new entry to storage - new_entry = len(self.bm_dict) + 1 - self.bm_dict[str(new_entry)] = [title, link] - - # add the link to the menu but only if it is within the set limit - bm_limit = int(self.app.defaults["global_bookmarks_limit"]) - if len(self.bm_dict) < bm_limit: - act = QtWidgets.QAction(parent=self.app.ui.menuhelp_bookmarks) - act.setText(title) - act.setIcon(QtGui.QIcon(self.app.resource_location + '/link16.png')) - act.triggered.connect(lambda: webbrowser.open(link)) - self.app.ui.menuhelp_bookmarks.insertAction(self.app.ui.menuhelp_bookmarks_manager, act) - - self.app.inform.emit('[success] %s' % _("Bookmark added.")) - - # add the new entry to the bookmark manager table - self.build_bm_ui() - - def on_remove_entry(self): - """ - Remove an Entry in the Bookmark table and from the menu actions - :return: - """ - index_list = [] - for model_index in self.table_widget.selectionModel().selectedRows(): - index = QtCore.QPersistentModelIndex(model_index) - index_list.append(index) - title_to_remove = self.table_widget.item(model_index.row(), 1).text() - - if title_to_remove == 'FlatCAM' or title_to_remove == 'Backup Site': - self.app.inform.emit('[WARNING_NOTCL] %s.' % _("This bookmark can not be removed")) - self.build_bm_ui() - return - else: - for k, bookmark in list(self.bm_dict.items()): - if title_to_remove == bookmark[0]: - # remove from the storage - self.bm_dict.pop(k, None) - - for act in self.app.ui.menuhelp_bookmarks.actions(): - if act.text() == title_to_remove: - # disconnect the signal - try: - act.triggered.disconnect() - except TypeError: - pass - # remove the action from the menu - self.app.ui.menuhelp_bookmarks.removeAction(act) - - # house keeping: it pays to have keys increased by one - new_key = 0 - new_dict = {} - for k, v in self.bm_dict.items(): - # we start with key 1 so we can use the len(self.bm_dict) - # when adding bookmarks (keys in bm_dict) - new_key += 1 - new_dict[str(new_key)] = v - - self.bm_dict = deepcopy(new_dict) - new_dict.clear() - - self.app.inform.emit('[success] %s' % _("Bookmark removed.")) - - # for index in index_list: - # self.table_widget.model().removeRow(index.row()) - self.build_bm_ui() - - def on_export_bookmarks(self): - self.app.report_usage("on_export_bookmarks") - self.app.log.debug("on_export_bookmarks()") - - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - - filter__ = "Text File (*.TXT);;All Files (*.*)" - filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export FlatCAM Bookmarks"), - directory='{l_save}/FlatCAM_{n}_{date}'.format( - l_save=str(self.app.get_last_save_folder()), - n=_("Bookmarks"), - date=date), - filter=filter__) - - filename = str(filename) - - if filename == "": - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - try: - f = open(filename, 'w') - f.close() - except PermissionError: - self.app.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return - except IOError: - self.app.log.debug('Creating a new bookmarks file ...') - f = open(filename, 'w') - f.close() - except Exception: - e = sys.exc_info()[0] - self.app.log.error("Could not load defaults file.") - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file.")) - return - - # Save Bookmarks to a file - try: - with open(filename, "w") as f: - for title, link in self.bm_dict.items(): - line2write = str(title) + ':' + str(link) + '\n' - f.write(line2write) - except Exception: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write bookmarks to file.")) - return - self.app.inform.emit('[success] %s: %s' % (_("Exported bookmarks to"), filename)) - - def on_import_bookmarks(self): - self.app.log.debug("on_import_bookmarks()") - - filter_ = "Text File (*.txt);;All Files (*.*)" - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Bookmarks"), filter=filter_) - - filename = str(filename) - - if filename == "": - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - try: - with open(filename) as f: - bookmarks = f.readlines() - except IOError: - self.app.log.error("Could not load bookmarks file.") - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load bookmarks file.")) - return - - for line in bookmarks: - proc_line = line.replace(' ', '').partition(':') - self.on_add_entry(title=proc_line[0], link=proc_line[2]) - - self.app.inform.emit('[success] %s: %s' % (_("Imported Bookmarks from"), filename)) - - def mark_table_rows_for_actions(self): - for row in range(self.table_widget.rowCount()): - item_to_paint = self.table_widget.item(row, 0) - if row < self.app.defaults["global_bookmarks_limit"]: - item_to_paint.setBackground(QtGui.QColor('gray')) - # item_to_paint.setForeground(QtGui.QColor('black')) - else: - item_to_paint.setBackground(QtGui.QColor('white')) - # item_to_paint.setForeground(QtGui.QColor('black')) - - def rebuild_actions(self): - # rebuild the storage to reflect the order of the lines - self.bm_dict.clear() - for row in range(self.table_widget.rowCount()): - title = self.table_widget.item(row, 1).text() - wlink = self.table_widget.cellWidget(row, 2).toPlainText() - - entry = int(row) + 1 - self.bm_dict.update( - { - str(entry): [title, wlink] - } - ) - - self.app.install_bookmarks(book_dict=self.bm_dict) - - # def accept(self): - # self.rebuild_actions() - # super().accept() - - def closeEvent(self, QCloseEvent): - self.rebuild_actions() - super().closeEvent(QCloseEvent) - - -class ToolsDB(QtWidgets.QWidget): - - mark_tools_rows = QtCore.pyqtSignal() - - def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None): - super(ToolsDB, self).__init__(parent) - - self.app = app - self.decimals = 4 - self.callback_app = callback_on_edited - - self.on_tool_request = callback_on_tool_request - - self.offset_item_options = ["Path", "In", "Out", "Custom"] - self.type_item_options = ["Iso", "Rough", "Finish"] - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] - - ''' - dict to hold all the tools in the Tools DB - format: - { - tool_id: { - 'name': 'new_tool' - 'tooldia': self.app.defaults["geometry_cnctooldia"] - 'offset': 'Path' - 'offset_value': 0.0 - 'type': _('Rough'), - 'tool_type': 'C1' - 'data': dict() - } - } - ''' - self.db_tool_dict = {} - - # layouts - layout = QtWidgets.QVBoxLayout() - self.setLayout(layout) - - table_hlay = QtWidgets.QHBoxLayout() - layout.addLayout(table_hlay) - - self.table_widget = FCTable(drag_drop=True) - self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - table_hlay.addWidget(self.table_widget) - - # set the number of columns and the headers tool tips - self.configure_table() - - # pal = QtGui.QPalette() - # pal.setColor(QtGui.QPalette.Background, Qt.white) - - # New Bookmark - new_vlay = QtWidgets.QVBoxLayout() - layout.addLayout(new_vlay) - - # new_tool_lbl = QtWidgets.QLabel('%s' % _("New Tool")) - # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom) - - self.buttons_frame = QtWidgets.QFrame() - self.buttons_frame.setContentsMargins(0, 0, 0, 0) - layout.addWidget(self.buttons_frame) - self.buttons_box = QtWidgets.QHBoxLayout() - self.buttons_box.setContentsMargins(0, 0, 0, 0) - self.buttons_frame.setLayout(self.buttons_box) - self.buttons_frame.show() - - add_entry_btn = FCButton(_("Add Geometry Tool in DB")) - add_entry_btn.setToolTip( - _("Add a new tool in the Tools Database.\n" - "It will be used in the Geometry UI.\n" - "You can edit it after it is added.") - ) - self.buttons_box.addWidget(add_entry_btn) - - # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB")) - # add_fct_entry_btn.setToolTip( - # _("Add a new tool in the Tools Database.\n" - # "It will be used in the Paint/NCC Tools UI.\n" - # "You can edit it after it is added.") - # ) - # self.buttons_box.addWidget(add_fct_entry_btn) - - remove_entry_btn = FCButton(_("Delete Tool from DB")) - remove_entry_btn.setToolTip( - _("Remove a selection of tools in the Tools Database.") - ) - self.buttons_box.addWidget(remove_entry_btn) - - export_db_btn = FCButton(_("Export DB")) - export_db_btn.setToolTip( - _("Save the Tools Database to a custom text file.") - ) - self.buttons_box.addWidget(export_db_btn) - - import_db_btn = FCButton(_("Import DB")) - import_db_btn.setToolTip( - _("Load the Tools Database information's from a custom text file.") - ) - self.buttons_box.addWidget(import_db_btn) - - self.add_tool_from_db = FCButton(_("Add Tool from Tools DB")) - self.add_tool_from_db.setToolTip( - _("Add a new tool in the Tools Table of the\n" - "active Geometry object after selecting a tool\n" - "in the Tools Database.") - ) - self.add_tool_from_db.hide() - - self.cancel_tool_from_db = FCButton(_("Cancel")) - self.cancel_tool_from_db.hide() - - hlay = QtWidgets.QHBoxLayout() - layout.addLayout(hlay) - hlay.addWidget(self.add_tool_from_db) - hlay.addWidget(self.cancel_tool_from_db) - hlay.addStretch() - - # ############################################################################## - # ######################## SIGNALS ############################################# - # ############################################################################## - - add_entry_btn.clicked.connect(self.on_tool_add) - remove_entry_btn.clicked.connect(self.on_tool_delete) - export_db_btn.clicked.connect(self.on_export_tools_db_file) - import_db_btn.clicked.connect(self.on_import_tools_db_file) - # closebtn.clicked.connect(self.accept) - - self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app) - self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool) - - self.setup_db_ui() - - def configure_table(self): - self.table_widget.setColumnCount(27) - # self.table_widget.setColumnWidth(0, 20) - self.table_widget.setHorizontalHeaderLabels( - [ - '#', - _("Tool Name"), - _("Tool Dia"), - _("Tool Offset"), - _("Custom Offset"), - _("Tool Type"), - _("Tool Shape"), - _("Cut Z"), - _("MultiDepth"), - _("DPP"), - _("V-Dia"), - _("V-Angle"), - _("Travel Z"), - _("FR"), - _("FR Z"), - _("FR Rapids"), - _("Spindle Speed"), - _("Dwell"), - _("Dwelltime"), - _("Preprocessor"), - _("ExtraCut"), - _("E-Cut Length"), - _("Toolchange"), - _("Toolchange XY"), - _("Toolchange Z"), - _("Start Z"), - _("End Z"), - ] - ) - self.table_widget.horizontalHeaderItem(0).setToolTip( - _("Tool Index.")) - self.table_widget.horizontalHeaderItem(1).setToolTip( - _("Tool name.\n" - "This is not used in the app, it's function\n" - "is to serve as a note for the user.")) - self.table_widget.horizontalHeaderItem(2).setToolTip( - _("Tool Diameter.")) - self.table_widget.horizontalHeaderItem(3).setToolTip( - _("Tool Offset.\n" - "Can be of a few types:\n" - "Path = zero offset\n" - "In = offset inside by half of tool diameter\n" - "Out = offset outside by half of tool diameter\n" - "Custom = custom offset using the Custom Offset value")) - self.table_widget.horizontalHeaderItem(4).setToolTip( - _("Custom Offset.\n" - "A value to be used as offset from the current path.")) - self.table_widget.horizontalHeaderItem(5).setToolTip( - _("Tool Type.\n" - "Can be:\n" - "Iso = isolation cut\n" - "Rough = rough cut, low feedrate, multiple passes\n" - "Finish = finishing cut, high feedrate")) - self.table_widget.horizontalHeaderItem(6).setToolTip( - _("Tool Shape. \n" - "Can be:\n" - "C1 ... C4 = circular tool with x flutes\n" - "B = ball tip milling tool\n" - "V = v-shape milling tool")) - self.table_widget.horizontalHeaderItem(7).setToolTip( - _("Cutting Depth.\n" - "The depth at which to cut into material.")) - self.table_widget.horizontalHeaderItem(8).setToolTip( - _("Multi Depth.\n" - "Selecting this will allow cutting in multiple passes,\n" - "each pass adding a DPP parameter depth.")) - self.table_widget.horizontalHeaderItem(9).setToolTip( - _("DPP. Depth per Pass.\n" - "The value used to cut into material on each pass.")) - self.table_widget.horizontalHeaderItem(10).setToolTip( - _("V-Dia.\n" - "Diameter of the tip for V-Shape Tools.")) - self.table_widget.horizontalHeaderItem(11).setToolTip( - _("V-Agle.\n" - "Angle at the tip for the V-Shape Tools.")) - self.table_widget.horizontalHeaderItem(12).setToolTip( - _("Clearance Height.\n" - "Height at which the milling bit will travel between cuts,\n" - "above the surface of the material, avoiding all fixtures.")) - self.table_widget.horizontalHeaderItem(13).setToolTip( - _("FR. Feedrate\n" - "The speed on XY plane used while cutting into material.")) - self.table_widget.horizontalHeaderItem(14).setToolTip( - _("FR Z. Feedrate Z\n" - "The speed on Z plane.")) - self.table_widget.horizontalHeaderItem(15).setToolTip( - _("FR Rapids. Feedrate Rapids\n" - "Speed used while moving as fast as possible.\n" - "This is used only by some devices that can't use\n" - "the G0 g-code command. Mostly 3D printers.")) - self.table_widget.horizontalHeaderItem(16).setToolTip( - _("Spindle Speed.\n" - "If it's left empty it will not be used.\n" - "The speed of the spindle in RPM.")) - self.table_widget.horizontalHeaderItem(17).setToolTip( - _("Dwell.\n" - "Check this if a delay is needed to allow\n" - "the spindle motor to reach it's set speed.")) - self.table_widget.horizontalHeaderItem(18).setToolTip( - _("Dwell Time.\n" - "A delay used to allow the motor spindle reach it's set speed.")) - self.table_widget.horizontalHeaderItem(19).setToolTip( - _("Preprocessor.\n" - "A selection of files that will alter the generated G-code\n" - "to fit for a number of use cases.")) - self.table_widget.horizontalHeaderItem(20).setToolTip( - _("Extra Cut.\n" - "If checked, after a isolation is finished an extra cut\n" - "will be added where the start and end of isolation meet\n" - "such as that this point is covered by this extra cut to\n" - "ensure a complete isolation.")) - self.table_widget.horizontalHeaderItem(21).setToolTip( - _("Extra Cut length.\n" - "If checked, after a isolation is finished an extra cut\n" - "will be added where the start and end of isolation meet\n" - "such as that this point is covered by this extra cut to\n" - "ensure a complete isolation. This is the length of\n" - "the extra cut.")) - self.table_widget.horizontalHeaderItem(22).setToolTip( - _("Toolchange.\n" - "It will create a toolchange event.\n" - "The kind of toolchange is determined by\n" - "the preprocessor file.")) - self.table_widget.horizontalHeaderItem(23).setToolTip( - _("Toolchange XY.\n" - "A set of coordinates in the format (x, y).\n" - "Will determine the cartesian position of the point\n" - "where the tool change event take place.")) - self.table_widget.horizontalHeaderItem(24).setToolTip( - _("Toolchange Z.\n" - "The position on Z plane where the tool change event take place.")) - self.table_widget.horizontalHeaderItem(25).setToolTip( - _("Start Z.\n" - "If it's left empty it will not be used.\n" - "A position on Z plane to move immediately after job start.")) - self.table_widget.horizontalHeaderItem(26).setToolTip( - _("End Z.\n" - "A position on Z plane to move immediately after job stop.")) - - def setup_db_ui(self): - filename = self.app.data_path + '/geo_tools_db.FlatDB' - - # load the database tools from the file - try: - with open(filename) as f: - tools = f.read() - except IOError: - self.app.log.error("Could not load tools DB file.") - self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file.")) - return - - try: - self.db_tool_dict = json.loads(tools) - except Exception: - e = sys.exc_info()[0] - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) - return - - self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) - - self.build_db_ui() - - self.table_widget.setupContextMenu() - self.table_widget.addContextMenu( - _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")) - self.table_widget.addContextMenu( - _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png")) - self.table_widget.addContextMenu( - _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png")) - - def build_db_ui(self): - self.ui_disconnect() - self.table_widget.setRowCount(len(self.db_tool_dict)) - - nr_crt = 0 - - for toolid, dict_val in self.db_tool_dict.items(): - row = nr_crt - nr_crt += 1 - - t_name = dict_val['name'] - try: - self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val) - except Exception as e: - self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e)) - vertical_header = self.table_widget.verticalHeader() - vertical_header.hide() - - horizontal_header = self.table_widget.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - - self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) - for x in range(27): - self.table_widget.resizeColumnToContents(x) - - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed) - - horizontal_header.resizeSection(0, 20) - # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) - # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) - - self.ui_connect() - - def add_tool_table_line(self, row, name, widget, tooldict): - data = tooldict['data'] - - nr_crt = row + 1 - id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt)) - # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable - id_item.setFlags(flags) - widget.setItem(row, 0, id_item) # Tool name/id - - tool_name_item = QtWidgets.QTableWidgetItem(name) - widget.setItem(row, 1, tool_name_item) - - dia_item = FCDoubleSpinner() - dia_item.set_precision(self.decimals) - dia_item.setSingleStep(0.1) - dia_item.set_range(0.0, 9999.9999) - dia_item.set_value(float(tooldict['tooldia'])) - widget.setCellWidget(row, 2, dia_item) - - tool_offset_item = FCComboBox() - for item in self.offset_item_options: - tool_offset_item.addItem(item) - tool_offset_item.set_value(tooldict['offset']) - widget.setCellWidget(row, 3, tool_offset_item) - - c_offset_item = FCDoubleSpinner() - c_offset_item.set_precision(self.decimals) - c_offset_item.setSingleStep(0.1) - c_offset_item.set_range(-9999.9999, 9999.9999) - c_offset_item.set_value(float(tooldict['offset_value'])) - widget.setCellWidget(row, 4, c_offset_item) - - tt_item = FCComboBox() - for item in self.type_item_options: - tt_item.addItem(item) - tt_item.set_value(tooldict['type']) - widget.setCellWidget(row, 5, tt_item) - - tshape_item = FCComboBox() - for item in self.tool_type_item_options: - tshape_item.addItem(item) - tshape_item.set_value(tooldict['tool_type']) - widget.setCellWidget(row, 6, tshape_item) - - cutz_item = FCDoubleSpinner() - cutz_item.set_precision(self.decimals) - cutz_item.setSingleStep(0.1) - if self.app.defaults['global_machinist_setting']: - cutz_item.set_range(-9999.9999, 9999.9999) - else: - cutz_item.set_range(-9999.9999, -0.0000) - - cutz_item.set_value(float(data['cutz'])) - widget.setCellWidget(row, 7, cutz_item) - - multidepth_item = FCCheckBox() - 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() - # multidepth_item = QtWidgets.QWidget() - # cb = FCCheckBox() - # cb.set_value(data['multidepth']) - # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item) - # qhboxlayout.addWidget(cb) - # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter) - # qhboxlayout.setContentsMargins(0, 0, 0, 0) - # widget.setCellWidget(row, 8, multidepth_item) - - depth_per_pass_item = FCDoubleSpinner() - depth_per_pass_item.set_precision(self.decimals) - depth_per_pass_item.setSingleStep(0.1) - depth_per_pass_item.set_range(0.0, 9999.9999) - depth_per_pass_item.set_value(float(data['depthperpass'])) - widget.setCellWidget(row, 9, depth_per_pass_item) - - vtip_dia_item = FCDoubleSpinner() - vtip_dia_item.set_precision(self.decimals) - vtip_dia_item.setSingleStep(0.1) - vtip_dia_item.set_range(0.0, 9999.9999) - vtip_dia_item.set_value(float(data['vtipdia'])) - widget.setCellWidget(row, 10, vtip_dia_item) - - vtip_angle_item = FCDoubleSpinner() - vtip_angle_item.set_precision(self.decimals) - vtip_angle_item.setSingleStep(0.1) - vtip_angle_item.set_range(-360.0, 360.0) - vtip_angle_item.set_value(float(data['vtipangle'])) - widget.setCellWidget(row, 11, vtip_angle_item) - - travelz_item = FCDoubleSpinner() - travelz_item.set_precision(self.decimals) - travelz_item.setSingleStep(0.1) - if self.app.defaults['global_machinist_setting']: - travelz_item.set_range(-9999.9999, 9999.9999) - else: - travelz_item.set_range(0.0000, 9999.9999) - - travelz_item.set_value(float(data['travelz'])) - widget.setCellWidget(row, 12, travelz_item) - - fr_item = FCDoubleSpinner() - fr_item.set_precision(self.decimals) - fr_item.set_range(0.0, 9999.9999) - fr_item.set_value(float(data['feedrate'])) - widget.setCellWidget(row, 13, fr_item) - - frz_item = FCDoubleSpinner() - frz_item.set_precision(self.decimals) - frz_item.set_range(0.0, 9999.9999) - frz_item.set_value(float(data['feedrate_z'])) - widget.setCellWidget(row, 14, frz_item) - - frrapids_item = FCDoubleSpinner() - frrapids_item.set_precision(self.decimals) - frrapids_item.set_range(0.0, 9999.9999) - frrapids_item.set_value(float(data['feedrate_rapid'])) - widget.setCellWidget(row, 15, frrapids_item) - - spindlespeed_item = FCSpinner() - spindlespeed_item.set_range(0, 1000000) - spindlespeed_item.set_value(int(data['spindlespeed'])) - spindlespeed_item.set_step(100) - widget.setCellWidget(row, 16, spindlespeed_item) - - dwell_item = FCCheckBox() - dwell_item.set_value(data['dwell']) - widget.setCellWidget(row, 17, dwell_item) - - dwelltime_item = FCDoubleSpinner() - dwelltime_item.set_precision(self.decimals) - dwelltime_item.set_range(0.0000, 9999.9999) - dwelltime_item.set_value(float(data['dwelltime'])) - widget.setCellWidget(row, 18, dwelltime_item) - - pp_item = FCComboBox() - for item in self.app.preprocessors: - pp_item.addItem(item) - pp_item.set_value(data['ppname_g']) - widget.setCellWidget(row, 19, pp_item) - - ecut_item = FCCheckBox() - ecut_item.set_value(data['extracut']) - widget.setCellWidget(row, 20, ecut_item) - - ecut_length_item = FCDoubleSpinner() - ecut_length_item.set_precision(self.decimals) - ecut_length_item.set_range(0.0000, 9999.9999) - ecut_length_item.set_value(data['extracut_length']) - widget.setCellWidget(row, 21, ecut_length_item) - - toolchange_item = FCCheckBox() - toolchange_item.set_value(data['toolchange']) - widget.setCellWidget(row, 22, toolchange_item) - - toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '') - widget.setItem(row, 23, toolchangexy_item) - - toolchangez_item = FCDoubleSpinner() - toolchangez_item.set_precision(self.decimals) - toolchangez_item.setSingleStep(0.1) - if self.app.defaults['global_machinist_setting']: - toolchangez_item.set_range(-9999.9999, 9999.9999) - else: - toolchangez_item.set_range(0.0000, 9999.9999) - - toolchangez_item.set_value(float(data['toolchangez'])) - widget.setCellWidget(row, 24, toolchangez_item) - - startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '') - widget.setItem(row, 25, startz_item) - - endz_item = FCDoubleSpinner() - endz_item.set_precision(self.decimals) - endz_item.setSingleStep(0.1) - if self.app.defaults['global_machinist_setting']: - endz_item.set_range(-9999.9999, 9999.9999) - else: - endz_item.set_range(0.0000, 9999.9999) - - endz_item.set_value(float(data['endz'])) - widget.setCellWidget(row, 26, endz_item) - - def on_tool_add(self): - """ - Add a tool in the DB Tool Table - :return: None - """ - - default_data = {} - default_data.update({ - "cutz": float(self.app.defaults["geometry_cutz"]), - "multidepth": self.app.defaults["geometry_multidepth"], - "depthperpass": float(self.app.defaults["geometry_depthperpass"]), - "vtipdia": float(self.app.defaults["geometry_vtipdia"]), - "vtipangle": float(self.app.defaults["geometry_vtipangle"]), - "travelz": float(self.app.defaults["geometry_travelz"]), - "feedrate": float(self.app.defaults["geometry_feedrate"]), - "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]), - "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]), - "spindlespeed": self.app.defaults["geometry_spindlespeed"], - "dwell": self.app.defaults["geometry_dwell"], - "dwelltime": float(self.app.defaults["geometry_dwelltime"]), - "ppname_g": self.app.defaults["geometry_ppname_g"], - "extracut": self.app.defaults["geometry_extracut"], - "extracut_length": float(self.app.defaults["geometry_extracut_length"]), - "toolchange": self.app.defaults["geometry_toolchange"], - "toolchangexy": self.app.defaults["geometry_toolchangexy"], - "toolchangez": float(self.app.defaults["geometry_toolchangez"]), - "startz": self.app.defaults["geometry_startz"], - "endz": float(self.app.defaults["geometry_endz"]) - }) - - dict_elem = {} - dict_elem['name'] = 'new_tool' - if type(self.app.defaults["geometry_cnctooldia"]) == float: - dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"] - else: - try: - tools_string = self.app.defaults["geometry_cnctooldia"].split(",") - tools_diameters = [eval(a) for a in tools_string if a != ''] - dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0 - except Exception as e: - self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e)) - return - - dict_elem['offset'] = 'Path' - dict_elem['offset_value'] = 0.0 - dict_elem['type'] = 'Rough' - dict_elem['tool_type'] = 'C1' - dict_elem['data'] = default_data - - new_toolid = len(self.db_tool_dict) + 1 - self.db_tool_dict[new_toolid] = deepcopy(dict_elem) - - # add the new entry to the Tools DB table - self.build_db_ui() - self.callback_on_edited() - self.app.inform.emit('[success] %s' % _("Tool added to DB.")) - - def on_tool_copy(self): - """ - Copy a selection of Tools in the Tools DB table - :return: - """ - new_tool_id = self.table_widget.rowCount() + 1 - for model_index in self.table_widget.selectionModel().selectedRows(): - # index = QtCore.QPersistentModelIndex(model_index) - old_tool_id = self.table_widget.item(model_index.row(), 0).text() - new_tool_id += 1 - - for toolid, dict_val in list(self.db_tool_dict.items()): - if int(old_tool_id) == int(toolid): - self.db_tool_dict.update({ - new_tool_id: deepcopy(dict_val) - }) - - self.build_db_ui() - self.callback_on_edited() - self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB.")) - - def on_tool_delete(self): - """ - Delete a selection of Tools in the Tools DB table - :return: - """ - for model_index in self.table_widget.selectionModel().selectedRows(): - # index = QtCore.QPersistentModelIndex(model_index) - toolname_to_remove = self.table_widget.item(model_index.row(), 0).text() - - for toolid, dict_val in list(self.db_tool_dict.items()): - if int(toolname_to_remove) == int(toolid): - # remove from the storage - self.db_tool_dict.pop(toolid, None) - - self.build_db_ui() - self.callback_on_edited() - self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB.")) - - def on_export_tools_db_file(self): - self.app.report_usage("on_export_tools_db_file") - self.app.log.debug("on_export_tools_db_file()") - - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - - filter__ = "Text File (*.TXT);;All Files (*.*)" - filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export Tools Database"), - directory='{l_save}/FlatCAM_{n}_{date}'.format( - l_save=str(self.app.get_last_save_folder()), - n=_("Tools_Database"), - date=date), - filter=filter__) - - filename = str(filename) - - if filename == "": - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - try: - f = open(filename, 'w') - f.close() - except PermissionError: - self.app.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return - except IOError: - self.app.log.debug('Creating a new Tools DB file ...') - f = open(filename, 'w') - f.close() - except Exception: - e = sys.exc_info()[0] - self.app.log.error("Could not load Tools DB file.") - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) - return - - # Save update options - try: - # Save Tools DB in a file - try: - with open(filename, "w") as f: - json.dump(self.db_tool_dict, f, default=to_dict, indent=2) - except Exception as e: - self.app.log.debug("App.on_save_tools_db() --> %s" % str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) - return - except Exception: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) - return - - self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename)) - - def on_import_tools_db_file(self): - self.app.report_usage("on_import_tools_db_file") - self.app.log.debug("on_import_tools_db_file()") - - filter__ = "Text File (*.TXT);;All Files (*.*)" - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__) - - if filename == "": - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - try: - with open(filename) as f: - tools_in_db = f.read() - except IOError: - self.app.log.error("Could not load Tools DB file.") - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) - return - - try: - self.db_tool_dict = json.loads(tools_in_db) - except Exception: - e = sys.exc_info()[0] - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) - return - - self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) - self.build_db_ui() - self.callback_on_edited() - - def on_save_tools_db(self, silent=False): - self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.") - - filename = self.app.data_path + "/geo_tools_db.FlatDB" - - # Preferences save, update the color of the Tools DB Tab text - for idx in range(self.app.ui.plot_tab_area.count()): - if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): - self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) - - # Save Tools DB in a file - try: - f = open(filename, "w") - json.dump(self.db_tool_dict, f, default=to_dict, indent=2) - f.close() - except Exception as e: - self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) - return - - if not silent: - self.app.inform.emit('[success] %s' % _("Saved Tools DB.")) - - def ui_connect(self): - try: - try: - self.table_widget.itemChanged.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - self.table_widget.itemChanged.connect(self.callback_on_edited) - except AttributeError: - pass - - for row in range(self.table_widget.rowCount()): - for col in range(self.table_widget.columnCount()): - # ComboBox - try: - try: - self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited) - except AttributeError: - pass - - # CheckBox - try: - try: - self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited) - except AttributeError: - pass - - # SpinBox, DoubleSpinBox - try: - try: - self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited) - except AttributeError: - pass - - def ui_disconnect(self): - try: - self.table_widget.itemChanged.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - - for row in range(self.table_widget.rowCount()): - for col in range(self.table_widget.columnCount()): - # ComboBox - try: - self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - - # CheckBox - try: - self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - - # SpinBox, DoubleSpinBox - try: - self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited) - except (TypeError, AttributeError): - pass - - def callback_on_edited(self): - - # update the dictionary storage self.db_tool_dict - self.db_tool_dict.clear() - dict_elem = {} - default_data = {} - - for row in range(self.table_widget.rowCount()): - new_toolid = row + 1 - for col in range(self.table_widget.columnCount()): - column_header_text = self.table_widget.horizontalHeaderItem(col).text() - if column_header_text == _('Tool Name'): - dict_elem['name'] = self.table_widget.item(row, col).text() - elif column_header_text == _('Tool Dia'): - dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Tool Offset'): - dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Custom Offset'): - dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Tool Type'): - dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Tool Shape'): - dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value() - else: - if column_header_text == _('Cut Z'): - default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('MultiDepth'): - default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('DPP'): - default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('V-Dia'): - default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('V-Angle'): - default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Travel Z'): - default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('FR'): - default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('FR Z'): - default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('FR Rapids'): - default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Spindle Speed'): - default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Dwell'): - default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Dwelltime'): - default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Preprocessor'): - default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('ExtraCut'): - default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _("E-Cut Length"): - default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Toolchange'): - default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Toolchange XY'): - default_data['toolchangexy'] = self.table_widget.item(row, col).text() - elif column_header_text == _('Toolchange Z'): - default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value() - elif column_header_text == _('Start Z'): - default_data['startz'] = float(self.table_widget.item(row, col).text()) \ - if self.table_widget.item(row, col).text() != '' else None - elif column_header_text == _('End Z'): - default_data['endz'] = self.table_widget.cellWidget(row, col).get_value() - - dict_elem['data'] = default_data - self.db_tool_dict.update( - { - new_toolid: deepcopy(dict_elem) - } - ) - - self.callback_app() - - def on_tool_requested_from_app(self): - if not self.table_widget.selectionModel().selectedRows(): - self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table")) - return - - model_index_list = self.table_widget.selectionModel().selectedRows() - for model_index in model_index_list: - selected_row = model_index.row() - tool_uid = selected_row + 1 - for key in self.db_tool_dict.keys(): - if str(key) == str(tool_uid): - selected_tool = self.db_tool_dict[key] - self.on_tool_request(tool=selected_tool) - - def on_cancel_tool(self): - for idx in range(self.app.ui.plot_tab_area.count()): - if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): - wdg = self.app.ui.plot_tab_area.widget(idx) - wdg.deleteLater() - self.app.ui.plot_tab_area.removeTab(idx) - self.app.inform.emit('%s' % _("Cancelled adding tool from DB.")) - - def resize_new_tool_table_widget(self, min_size, max_size): - """ - Resize the table widget responsible for adding new tool in the Tool Database - - :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() - :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() - :return: - """ - t_height = self.t_height - if max_size > min_size: - t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height() - - self.new_tool_table_widget.setMaximumHeight(t_height) - - def closeEvent(self, QCloseEvent): - super().closeEvent(QCloseEvent) - - -class ToolsDB2(QtWidgets.QWidget): - - mark_tools_rows = QtCore.pyqtSignal() - - def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None): - super(ToolsDB2, self).__init__(parent) - - self.app = app - self.decimals = self.app.decimals - self.callback_app = callback_on_edited - - self.on_tool_request = callback_on_tool_request - - self.offset_item_options = ["Path", "In", "Out", "Custom"] - self.type_item_options = ["Iso", "Rough", "Finish"] - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] - - ''' - dict to hold all the tools in the Tools DB - format: - { - tool_id: { - 'name': 'new_tool' - 'tooldia': self.app.defaults["geometry_cnctooldia"] - 'offset': 'Path' - 'offset_value': 0.0 - 'type': _('Rough'), - 'tool_type': 'C1' - 'data': dict() - } - } - ''' - self.db_tool_dict = {} - - # layouts - grid_layout = QtWidgets.QGridLayout() - grid_layout.setColumnStretch(0, 0) - grid_layout.setColumnStretch(1, 1) - - self.setLayout(grid_layout) - - tree_layout = QtWidgets.QVBoxLayout() - grid_layout.addLayout(tree_layout, 0, 0) - - self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0]) - self.tree_widget.setHeaderLabels(["ID", "Tool Name"]) - self.tree_widget.setIndentation(0) - self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - - # set alternating colors - # self.tree_widget.setAlternatingRowColors(True) - # p = QtGui.QPalette() - # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) ) - # self.tree_widget.setPalette(p) - - self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) - tree_layout.addWidget(self.tree_widget) - - param_hlay = QtWidgets.QHBoxLayout() - param_area = QtWidgets.QScrollArea() - param_widget = QtWidgets.QWidget() - param_widget.setLayout(param_hlay) - - param_area.setWidget(param_widget) - param_area.setWidgetResizable(True) - - grid_layout.addWidget(param_area, 0, 1) - - # ########################################################################### - # ############## The UI form ################################################ - # ########################################################################### - self.basic_box = QtWidgets.QGroupBox() - self.basic_box.setStyleSheet(""" - QGroupBox - { - font-size: 16px; - font-weight: bold; - } - """) - self.basic_vlay = QtWidgets.QVBoxLayout() - self.basic_box.setTitle(_("Basic Geo Parameters")) - self.basic_box.setFixedWidth(250) - - self.advanced_box = QtWidgets.QGroupBox() - self.advanced_box.setStyleSheet(""" - QGroupBox - { - font-size: 16px; - font-weight: bold; - } - """) - self.advanced_vlay = QtWidgets.QVBoxLayout() - self.advanced_box.setTitle(_("Advanced Geo Parameters")) - self.advanced_box.setFixedWidth(250) - - self.ncc_box = QtWidgets.QGroupBox() - self.ncc_box.setStyleSheet(""" - QGroupBox - { - font-size: 16px; - font-weight: bold; - } - """) - self.ncc_vlay = QtWidgets.QVBoxLayout() - self.ncc_box.setTitle(_("NCC Parameters")) - self.ncc_box.setFixedWidth(250) - - self.paint_box = QtWidgets.QGroupBox() - self.paint_box.setStyleSheet(""" - QGroupBox - { - font-size: 16px; - font-weight: bold; - } - """) - self.paint_vlay = QtWidgets.QVBoxLayout() - self.paint_box.setTitle(_("Paint Parameters")) - self.paint_box.setFixedWidth(250) - - self.basic_box.setLayout(self.basic_vlay) - self.advanced_box.setLayout(self.advanced_vlay) - self.ncc_box.setLayout(self.ncc_vlay) - self.paint_box.setLayout(self.paint_vlay) - - geo_vlay = QtWidgets.QVBoxLayout() - geo_vlay.addWidget(self.basic_box) - geo_vlay.addWidget(self.advanced_box) - geo_vlay.addStretch() - - tools_vlay = QtWidgets.QVBoxLayout() - tools_vlay.addWidget(self.ncc_box) - tools_vlay.addWidget(self.paint_box) - tools_vlay.addStretch() - - param_hlay.addLayout(geo_vlay) - param_hlay.addLayout(tools_vlay) - param_hlay.addStretch() - - # ########################################################################### - # ############### BASIC UI form ############################################# - # ########################################################################### - - self.grid0 = QtWidgets.QGridLayout() - self.basic_vlay.addLayout(self.grid0) - self.grid0.setColumnStretch(0, 0) - self.grid0.setColumnStretch(1, 1) - self.basic_vlay.addStretch() - - # Tool Name - self.name_label = QtWidgets.QLabel('%s:' % _('Tool Name')) - self.name_label.setToolTip( - _("Tool name.\n" - "This is not used in the app, it's function\n" - "is to serve as a note for the user.")) - - self.name_entry = FCEntry() - self.name_entry.setObjectName('gdb_name') - - self.grid0.addWidget(self.name_label, 0, 0) - self.grid0.addWidget(self.name_entry, 0, 1) - - # Tool Dia - self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia')) - self.dia_label.setToolTip( - _("Tool Diameter.")) - - self.dia_entry = FCDoubleSpinner() - self.dia_entry.set_range(-9999.9999, 9999.9999) - self.dia_entry.set_precision(self.decimals) - self.dia_entry.setObjectName('gdb_dia') - - self.grid0.addWidget(self.dia_label, 1, 0) - self.grid0.addWidget(self.dia_entry, 1, 1) - - # Tool Shape - self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape')) - self.shape_label.setToolTip( - _("Tool Shape. \n" - "Can be:\n" - "C1 ... C4 = circular tool with x flutes\n" - "B = ball tip milling tool\n" - "V = v-shape milling tool")) - - self.shape_combo = FCComboBox() - self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"]) - self.shape_combo.setObjectName('gdb_shape') - - self.grid0.addWidget(self.shape_label, 2, 0) - self.grid0.addWidget(self.shape_combo, 2, 1) - - # Cut Z - self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z")) - self.cutz_label.setToolTip( - _("Cutting Depth.\n" - "The depth at which to cut into material.")) - - self.cutz_entry = FCDoubleSpinner() - self.cutz_entry.set_range(-9999.9999, 9999.9999) - self.cutz_entry.set_precision(self.decimals) - self.cutz_entry.setObjectName('gdb_cutz') - - self.grid0.addWidget(self.cutz_label, 4, 0) - self.grid0.addWidget(self.cutz_entry, 4, 1) - - # Multi Depth - self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth")) - self.multidepth_label.setToolTip( - _("Multi Depth.\n" - "Selecting this will allow cutting in multiple passes,\n" - "each pass adding a DPP parameter depth.")) - - self.multidepth_cb = FCCheckBox() - self.multidepth_cb.setObjectName('gdb_multidepth') - - self.grid0.addWidget(self.multidepth_label, 5, 0) - self.grid0.addWidget(self.multidepth_cb, 5, 1) - - # Depth Per Pass - self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP")) - self.dpp_label.setToolTip( - _("DPP. Depth per Pass.\n" - "The value used to cut into material on each pass.")) - - self.multidepth_entry = FCDoubleSpinner() - self.multidepth_entry.set_range(-9999.9999, 9999.9999) - self.multidepth_entry.set_precision(self.decimals) - self.multidepth_entry.setObjectName('gdb_multidepth_entry') - - self.grid0.addWidget(self.dpp_label, 7, 0) - self.grid0.addWidget(self.multidepth_entry, 7, 1) - - # Travel Z - self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z")) - self.travelz_label.setToolTip( - _("Clearance Height.\n" - "Height at which the milling bit will travel between cuts,\n" - "above the surface of the material, avoiding all fixtures.")) - - self.travelz_entry = FCDoubleSpinner() - self.travelz_entry.set_range(-9999.9999, 9999.9999) - self.travelz_entry.set_precision(self.decimals) - self.travelz_entry.setObjectName('gdb_travel') - - self.grid0.addWidget(self.travelz_label, 9, 0) - self.grid0.addWidget(self.travelz_entry, 9, 1) - - # Feedrate X-Y - self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y")) - self.frxy_label.setToolTip( - _("Feedrate X-Y. Feedrate\n" - "The speed on XY plane used while cutting into material.")) - - self.frxy_entry = FCDoubleSpinner() - self.frxy_entry.set_range(-999999.9999, 999999.9999) - self.frxy_entry.set_precision(self.decimals) - self.frxy_entry.setObjectName('gdb_frxy') - - self.grid0.addWidget(self.frxy_label, 12, 0) - self.grid0.addWidget(self.frxy_entry, 12, 1) - - # Feedrate Z - self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z")) - self.frz_label.setToolTip( - _("Feedrate Z\n" - "The speed on Z plane.")) - - self.frz_entry = FCDoubleSpinner() - self.frz_entry.set_range(-999999.9999, 999999.9999) - self.frz_entry.set_precision(self.decimals) - self.frz_entry.setObjectName('gdb_frz') - - self.grid0.addWidget(self.frz_label, 14, 0) - self.grid0.addWidget(self.frz_entry, 14, 1) - - # Spindle Spped - self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed")) - self.spindle_label.setToolTip( - _("Spindle Speed.\n" - "If it's left empty it will not be used.\n" - "The speed of the spindle in RPM.")) - - self.spindle_entry = FCDoubleSpinner() - self.spindle_entry.set_range(-999999.9999, 999999.9999) - self.spindle_entry.set_precision(self.decimals) - self.spindle_entry.setObjectName('gdb_spindle') - - self.grid0.addWidget(self.spindle_label, 15, 0) - self.grid0.addWidget(self.spindle_entry, 15, 1) - - # Dwell - self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell")) - self.dwell_label.setToolTip( - _("Dwell.\n" - "Check this if a delay is needed to allow\n" - "the spindle motor to reach it's set speed.")) - - self.dwell_cb = FCCheckBox() - self.dwell_cb.setObjectName('gdb_dwell') - - self.grid0.addWidget(self.dwell_label, 16, 0) - self.grid0.addWidget(self.dwell_cb, 16, 1) - - # Dwell Time - self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime")) - self.dwelltime_label.setToolTip( - _("Dwell Time.\n" - "A delay used to allow the motor spindle reach it's set speed.")) - - self.dwelltime_entry = FCDoubleSpinner() - self.dwelltime_entry.set_range(0.0000, 9999.9999) - self.dwelltime_entry.set_precision(self.decimals) - self.dwelltime_entry.setObjectName('gdb_dwelltime') - - self.grid0.addWidget(self.dwelltime_label, 17, 0) - self.grid0.addWidget(self.dwelltime_entry, 17, 1) - - # ########################################################################### - # ############### ADVANCED UI form ########################################## - # ########################################################################### - - self.grid1 = QtWidgets.QGridLayout() - self.advanced_vlay.addLayout(self.grid1) - self.grid1.setColumnStretch(0, 0) - self.grid1.setColumnStretch(1, 1) - self.advanced_vlay.addStretch() - - # Tool Type - self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type")) - self.type_label.setToolTip( - _("Tool Type.\n" - "Can be:\n" - "Iso = isolation cut\n" - "Rough = rough cut, low feedrate, multiple passes\n" - "Finish = finishing cut, high feedrate")) - - self.type_combo = FCComboBox() - self.type_combo.addItems(["Iso", "Rough", "Finish"]) - self.type_combo.setObjectName('gdb_type') - - self.grid1.addWidget(self.type_label, 0, 0) - self.grid1.addWidget(self.type_combo, 0, 1) - - # Tool Offset - self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset')) - self.tooloffset_label.setToolTip( - _("Tool Offset.\n" - "Can be of a few types:\n" - "Path = zero offset\n" - "In = offset inside by half of tool diameter\n" - "Out = offset outside by half of tool diameter\n" - "Custom = custom offset using the Custom Offset value")) - - self.tooloffset_combo = FCComboBox() - self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"]) - self.tooloffset_combo.setObjectName('gdb_tool_offset') - - self.grid1.addWidget(self.tooloffset_label, 2, 0) - self.grid1.addWidget(self.tooloffset_combo, 2, 1) - - # Custom Offset - self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset")) - self.custom_offset_label.setToolTip( - _("Custom Offset.\n" - "A value to be used as offset from the current path.")) - - self.custom_offset_entry = FCDoubleSpinner() - self.custom_offset_entry.set_range(-9999.9999, 9999.9999) - self.custom_offset_entry.set_precision(self.decimals) - self.custom_offset_entry.setObjectName('gdb_custom_offset') - - self.grid1.addWidget(self.custom_offset_label, 5, 0) - self.grid1.addWidget(self.custom_offset_entry, 5, 1) - - # V-Dia - self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia")) - self.vdia_label.setToolTip( - _("V-Dia.\n" - "Diameter of the tip for V-Shape Tools.")) - - self.vdia_entry = FCDoubleSpinner() - self.vdia_entry.set_range(0.0000, 9999.9999) - self.vdia_entry.set_precision(self.decimals) - self.vdia_entry.setObjectName('gdb_vdia') - - self.grid1.addWidget(self.vdia_label, 7, 0) - self.grid1.addWidget(self.vdia_entry, 7, 1) - - # V-Angle - self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle")) - self.vangle_label.setToolTip( - _("V-Agle.\n" - "Angle at the tip for the V-Shape Tools.")) - - self.vangle_entry = FCDoubleSpinner() - self.vangle_entry.set_range(-360.0, 360.0) - self.vangle_entry.set_precision(self.decimals) - self.vangle_entry.setObjectName('gdb_vangle') - - self.grid1.addWidget(self.vangle_label, 8, 0) - self.grid1.addWidget(self.vangle_entry, 8, 1) - - # Feedrate Rapids - self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids")) - self.frapids_label.setToolTip( - _("FR Rapids. Feedrate Rapids\n" - "Speed used while moving as fast as possible.\n" - "This is used only by some devices that can't use\n" - "the G0 g-code command. Mostly 3D printers.")) - - self.frapids_entry = FCDoubleSpinner() - self.frapids_entry.set_range(0.0000, 9999.9999) - self.frapids_entry.set_precision(self.decimals) - self.frapids_entry.setObjectName('gdb_frapids') - - self.grid1.addWidget(self.frapids_label, 10, 0) - self.grid1.addWidget(self.frapids_entry, 10, 1) - - # Extra Cut - self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut")) - self.ecut_label.setToolTip( - _("Extra Cut.\n" - "If checked, after a isolation is finished an extra cut\n" - "will be added where the start and end of isolation meet\n" - "such as that this point is covered by this extra cut to\n" - "ensure a complete isolation.")) - - self.ecut_cb = FCCheckBox() - self.ecut_cb.setObjectName('gdb_ecut') - - self.grid1.addWidget(self.ecut_label, 12, 0) - self.grid1.addWidget(self.ecut_cb, 12, 1) - - # Extra Cut Length - self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length")) - self.ecut_length_label.setToolTip( - _("Extra Cut length.\n" - "If checked, after a isolation is finished an extra cut\n" - "will be added where the start and end of isolation meet\n" - "such as that this point is covered by this extra cut to\n" - "ensure a complete isolation. This is the length of\n" - "the extra cut.")) - - self.ecut_length_entry = FCDoubleSpinner() - self.ecut_length_entry.set_range(0.0000, 9999.9999) - self.ecut_length_entry.set_precision(self.decimals) - self.ecut_length_entry.setObjectName('gdb_ecut_length') - - self.grid1.addWidget(self.ecut_length_label, 13, 0) - self.grid1.addWidget(self.ecut_length_entry, 13, 1) - - # ########################################################################### - # ############### NCC UI form ############################################### - # ########################################################################### - - self.grid2 = QtWidgets.QGridLayout() - self.ncc_vlay.addLayout(self.grid2) - self.grid2.setColumnStretch(0, 0) - self.grid2.setColumnStretch(1, 1) - self.ncc_vlay.addStretch() - - # Operation - op_label = QtWidgets.QLabel('%s:' % _('Operation')) - op_label.setToolTip( - _("The 'Operation' can be:\n" - "- Isolation -> will ensure that the non-copper clearing is always complete.\n" - "If it's not successful then the non-copper clearing will fail, too.\n" - "- Clear -> the regular non-copper clearing.") - ) - - self.op_radio = RadioSet([ - {"label": _("Clear"), "value": "clear"}, - {"label": _("Isolation"), "value": "iso"} - ], orientation='horizontal', stretch=False) - self.op_radio.setObjectName("gdb_n_operation") - - self.grid2.addWidget(op_label, 13, 0) - self.grid2.addWidget(self.op_radio, 13, 1) - - # Milling Type Radio Button - self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type')) - self.milling_type_label.setToolTip( - _("Milling type when the selected tool is of type: 'iso_op':\n" - "- climb / best for precision milling and to reduce tool usage\n" - "- conventional / useful when there is no backlash compensation") - ) - - self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'}, - {'label': _('Conventional'), 'value': 'cv'}]) - self.milling_type_radio.setToolTip( - _("Milling type when the selected tool is of type: 'iso_op':\n" - "- climb / best for precision milling and to reduce tool usage\n" - "- conventional / useful when there is no backlash compensation") - ) - self.milling_type_radio.setObjectName("gdb_n_milling_type") - - self.grid2.addWidget(self.milling_type_label, 14, 0) - self.grid2.addWidget(self.milling_type_radio, 14, 1) - - # Overlap Entry - nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap')) - nccoverlabel.setToolTip( - _("How much (percentage) of the tool width to overlap each tool pass.\n" - "Adjust the value starting with lower values\n" - "and increasing it if areas that should be cleared are still \n" - "not cleared.\n" - "Lower values = faster processing, faster execution on CNC.\n" - "Higher values = slow processing and slow execution on CNC\n" - "due of too many paths.") - ) - self.ncc_overlap_entry = FCDoubleSpinner(suffix='%') - self.ncc_overlap_entry.set_precision(self.decimals) - self.ncc_overlap_entry.setWrapping(True) - self.ncc_overlap_entry.setRange(0.000, 99.9999) - self.ncc_overlap_entry.setSingleStep(0.1) - self.ncc_overlap_entry.setObjectName("gdb_n_overlap") - - self.grid2.addWidget(nccoverlabel, 15, 0) - self.grid2.addWidget(self.ncc_overlap_entry, 15, 1) - - # Margin - nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin')) - nccmarginlabel.setToolTip( - _("Bounding box margin.") - ) - self.ncc_margin_entry = FCDoubleSpinner() - self.ncc_margin_entry.set_precision(self.decimals) - self.ncc_margin_entry.set_range(-9999.9999, 9999.9999) - self.ncc_margin_entry.setObjectName("gdb_n_margin") - - self.grid2.addWidget(nccmarginlabel, 16, 0) - self.grid2.addWidget(self.ncc_margin_entry, 16, 1) - - # Method - methodlabel = QtWidgets.QLabel('%s:' % _('Method')) - methodlabel.setToolTip( - _("Algorithm for copper clearing:\n" - "- Standard: Fixed step inwards.\n" - "- Seed-based: Outwards from seed.\n" - "- Line-based: Parallel lines.") - ) - - self.ncc_method_combo = FCComboBox() - self.ncc_method_combo.addItems( - [_("Standard"), _("Seed"), _("Lines")] - ) - self.ncc_method_combo.setObjectName("gdb_n_method") - - self.grid2.addWidget(methodlabel, 17, 0) - self.grid2.addWidget(self.ncc_method_combo, 17, 1) - - # Connect lines - self.ncc_connect_cb = FCCheckBox('%s' % _("Connect")) - self.ncc_connect_cb.setObjectName("gdb_n_connect") - - self.ncc_connect_cb.setToolTip( - _("Draw lines between resulting\n" - "segments to minimize tool lifts.") - ) - self.grid2.addWidget(self.ncc_connect_cb, 18, 0) - - # Contour - self.ncc_contour_cb = FCCheckBox('%s' % _("Contour")) - self.ncc_contour_cb.setObjectName("gdb_n_contour") - - self.ncc_contour_cb.setToolTip( - _("Cut around the perimeter of the polygon\n" - "to trim rough edges.") - ) - self.grid2.addWidget(self.ncc_contour_cb, 18, 1) - - # ## NCC Offset choice - self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset")) - self.ncc_choice_offset_cb.setObjectName("gdb_n_offset") - - self.ncc_choice_offset_cb.setToolTip( - _("If used, it will add an offset to the copper features.\n" - "The copper clearing will finish to a distance\n" - "from the copper features.\n" - "The value can be between 0 and 10 FlatCAM units.") - ) - self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0) - - # ## NCC Offset Entry - self.ncc_offset_spinner = FCDoubleSpinner() - self.ncc_offset_spinner.set_range(0.00, 10.00) - self.ncc_offset_spinner.set_precision(4) - self.ncc_offset_spinner.setWrapping(True) - self.ncc_offset_spinner.setObjectName("gdb_n_offset_value") - - units = self.app.defaults['units'].upper() - if units == 'MM': - self.ncc_offset_spinner.setSingleStep(0.1) - else: - self.ncc_offset_spinner.setSingleStep(0.01) - - self.grid2.addWidget(self.ncc_offset_spinner, 19, 1) - - # ########################################################################### - # ############### Paint UI form ############################################# - # ########################################################################### - - self.grid3 = QtWidgets.QGridLayout() - self.paint_vlay.addLayout(self.grid3) - self.grid3.setColumnStretch(0, 0) - self.grid3.setColumnStretch(1, 1) - self.paint_vlay.addStretch() - - # Overlap - ovlabel = QtWidgets.QLabel('%s:' % _('Overlap')) - ovlabel.setToolTip( - _("How much (percentage) of the tool width to overlap each tool pass.\n" - "Adjust the value starting with lower values\n" - "and increasing it if areas that should be painted are still \n" - "not painted.\n" - "Lower values = faster processing, faster execution on CNC.\n" - "Higher values = slow processing and slow execution on CNC\n" - "due of too many paths.") - ) - self.paintoverlap_entry = FCDoubleSpinner(suffix='%') - self.paintoverlap_entry.set_precision(3) - self.paintoverlap_entry.setWrapping(True) - self.paintoverlap_entry.setRange(0.0000, 99.9999) - self.paintoverlap_entry.setSingleStep(0.1) - self.paintoverlap_entry.setObjectName('gdb_p_overlap') - - self.grid3.addWidget(ovlabel, 1, 0) - self.grid3.addWidget(self.paintoverlap_entry, 1, 1) - - # Margin - marginlabel = QtWidgets.QLabel('%s:' % _('Margin')) - marginlabel.setToolTip( - _("Distance by which to avoid\n" - "the edges of the polygon to\n" - "be painted.") - ) - self.paintmargin_entry = FCDoubleSpinner() - self.paintmargin_entry.set_precision(self.decimals) - self.paintmargin_entry.set_range(-9999.9999, 9999.9999) - self.paintmargin_entry.setObjectName('gdb_p_margin') - - self.grid3.addWidget(marginlabel, 2, 0) - self.grid3.addWidget(self.paintmargin_entry, 2, 1) - - # Method - methodlabel = QtWidgets.QLabel('%s:' % _('Method')) - methodlabel.setToolTip( - _("Algorithm for painting:\n" - "- Standard: Fixed step inwards.\n" - "- Seed-based: Outwards from seed.\n" - "- Line-based: Parallel lines.\n" - "- Laser-lines: Active only for Gerber objects.\n" - "Will create lines that follow the traces.\n" - "- Combo: In case of failure a new method will be picked from the above\n" - "in the order specified.") - ) - - self.paintmethod_combo = FCComboBox() - self.paintmethod_combo.addItems( - [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")] - ) - idx = self.paintmethod_combo.findText(_("Laser_lines")) - self.paintmethod_combo.model().item(idx).setEnabled(False) - - self.paintmethod_combo.setObjectName('gdb_p_method') - - self.grid3.addWidget(methodlabel, 7, 0) - self.grid3.addWidget(self.paintmethod_combo, 7, 1) - - # Connect lines - self.pathconnect_cb = FCCheckBox('%s' % _("Connect")) - self.pathconnect_cb.setObjectName('gdb_p_connect') - self.pathconnect_cb.setToolTip( - _("Draw lines between resulting\n" - "segments to minimize tool lifts.") - ) - - self.paintcontour_cb = FCCheckBox('%s' % _("Contour")) - self.paintcontour_cb.setObjectName('gdb_p_contour') - self.paintcontour_cb.setToolTip( - _("Cut around the perimeter of the polygon\n" - "to trim rough edges.") - ) - - self.grid3.addWidget(self.pathconnect_cb, 10, 0) - self.grid3.addWidget(self.paintcontour_cb, 10, 1) - - # #################################################################### - # #################################################################### - # GUI for the lower part of the window - # #################################################################### - # #################################################################### - - new_vlay = QtWidgets.QVBoxLayout() - grid_layout.addLayout(new_vlay, 1, 0, 1, 2) - - self.buttons_frame = QtWidgets.QFrame() - self.buttons_frame.setContentsMargins(0, 0, 0, 0) - new_vlay.addWidget(self.buttons_frame) - self.buttons_box = QtWidgets.QHBoxLayout() - self.buttons_box.setContentsMargins(0, 0, 0, 0) - self.buttons_frame.setLayout(self.buttons_box) - self.buttons_frame.show() - - add_entry_btn = FCButton(_("Add Tool in DB")) - add_entry_btn.setToolTip( - _("Add a new tool in the Tools Database.\n" - "It will be used in the Geometry UI.\n" - "You can edit it after it is added.") - ) - self.buttons_box.addWidget(add_entry_btn) - - # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB")) - # add_fct_entry_btn.setToolTip( - # _("Add a new tool in the Tools Database.\n" - # "It will be used in the Paint/NCC Tools UI.\n" - # "You can edit it after it is added.") - # ) - # self.buttons_box.addWidget(add_fct_entry_btn) - - remove_entry_btn = FCButton(_("Delete Tool from DB")) - remove_entry_btn.setToolTip( - _("Remove a selection of tools in the Tools Database.") - ) - self.buttons_box.addWidget(remove_entry_btn) - - export_db_btn = FCButton(_("Export DB")) - export_db_btn.setToolTip( - _("Save the Tools Database to a custom text file.") - ) - self.buttons_box.addWidget(export_db_btn) - - import_db_btn = FCButton(_("Import DB")) - import_db_btn.setToolTip( - _("Load the Tools Database information's from a custom text file.") - ) - self.buttons_box.addWidget(import_db_btn) - - self.add_tool_from_db = FCButton(_("Add Tool from Tools DB")) - self.add_tool_from_db.setToolTip( - _("Add a new tool in the Tools Table of the\n" - "active Geometry object after selecting a tool\n" - "in the Tools Database.") - ) - self.add_tool_from_db.hide() - - self.cancel_tool_from_db = FCButton(_("Cancel")) - self.cancel_tool_from_db.hide() - - hlay = QtWidgets.QHBoxLayout() - tree_layout.addLayout(hlay) - hlay.addWidget(self.add_tool_from_db) - hlay.addWidget(self.cancel_tool_from_db) - hlay.addStretch() - - # ############################################################################## - # ############################################################################## - # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS ##################### - # ############################################################################## - # ############################################################################## - - self.form_fields = { - # Basic - "name": self.name_entry, - "tooldia": self.dia_entry, - "tool_type": self.shape_combo, - "cutz": self.cutz_entry, - "multidepth": self.multidepth_cb, - "depthperpass": self.multidepth_entry, - "travelz": self.travelz_entry, - "feedrate": self.frxy_entry, - "feedrate_z": self.frz_entry, - "spindlespeed": self.spindle_entry, - "dwell": self.dwell_cb, - "dwelltime": self.dwelltime_entry, - - # Advanced - "type": self.type_combo, - "offset": self.tooloffset_combo, - "offset_value": self.custom_offset_entry, - "vtipdia": self.vdia_entry, - "vtipangle": self.vangle_entry, - "feedrate_rapid": self.frapids_entry, - "extracut": self.ecut_cb, - "extracut_length": self.ecut_length_entry, - - # NCC - "tools_nccoperation": self.op_radio, - "tools_nccmilling_type": self.milling_type_radio, - "tools_nccoverlap": self.ncc_overlap_entry, - "tools_nccmargin": self.ncc_margin_entry, - "tools_nccmethod": self.ncc_method_combo, - "tools_nccconnect": self.ncc_connect_cb, - "tools_ncccontour": self.ncc_contour_cb, - "tools_ncc_offset_choice": self.ncc_choice_offset_cb, - "tools_ncc_offset_value": self.ncc_offset_spinner, - - # Paint - "tools_paintoverlap": self.paintoverlap_entry, - "tools_paintmargin": self.paintmargin_entry, - "tools_paintmethod": self.paintmethod_combo, - "tools_pathconnect": self.pathconnect_cb, - "tools_paintcontour": self.paintcontour_cb, - } - - self.name2option = { - # Basic - "gdb_name": "name", - "gdb_dia": "tooldia", - "gdb_shape": "tool_type", - "gdb_cutz": "cutz", - "gdb_multidepth": "multidepth", - "gdb_multidepth_entry": "depthperpass", - "gdb_travel": "travelz", - "gdb_frxy": "feedrate", - "gdb_frz": "feedrate_z", - "gdb_spindle": "spindlespeed", - "gdb_dwell": "dwell", - "gdb_dwelltime": "dwelltime", - - # Advanced - "gdb_type": "type", - "gdb_tool_offset": "offset", - "gdb_custom_offset": "offset_value", - "gdb_vdia": "vtipdia", - "gdb_vangle": "vtipangle", - "gdb_frapids": "feedrate_rapid", - "gdb_ecut": "extracut", - "gdb_ecut_length": "extracut_length", - - # NCC - "gdb_n_operation": "tools_nccoperation", - "gdb_n_overlap": "tools_nccoverlap", - "gdb_n_margin": "tools_nccmargin", - "gdb_n_method": "tools_nccmethod", - "gdb_n_connect": "tools_nccconnect", - "gdb_n_contour": "tools_ncccontour", - "gdb_n_offset": "tools_ncc_offset_choice", - "gdb_n_offset_value": "tools_ncc_offset_value", - "gdb_n_milling_type": "tools_nccmilling_type", - - # Paint - 'gdb_p_overlap': "tools_paintoverlap", - 'gdb_p_margin': "tools_paintmargin", - 'gdb_p_method': "tools_paintmethod", - 'gdb_p_connect': "tools_pathconnect", - 'gdb_p_contour': "tools_paintcontour", - } - - self.current_toolid = None - - # variable to show if double clicking and item will trigger adding a tool from DB - self.ok_to_add = False - - # ############################################################################## - # ######################## SIGNALS ############################################# - # ############################################################################## - - add_entry_btn.clicked.connect(self.on_tool_add) - remove_entry_btn.clicked.connect(self.on_tool_delete) - export_db_btn.clicked.connect(self.on_export_tools_db_file) - import_db_btn.clicked.connect(self.on_import_tools_db_file) - # closebtn.clicked.connect(self.accept) - - self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app) - self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool) - - # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change) - self.tree_widget.currentItemChanged.connect(self.on_list_selection_change) - self.tree_widget.itemChanged.connect(self.on_list_item_edited) - self.tree_widget.customContextMenuRequested.connect(self.on_menu_request) - - self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked) - - self.setup_db_ui() - - def on_menu_request(self, pos): - - menu = QtWidgets.QMenu() - add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB")) - add_tool.triggered.connect(self.on_tool_add) - - copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB")) - copy_tool.triggered.connect(self.on_tool_copy) - - delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB")) - delete_tool.triggered.connect(self.on_tool_delete) - - # tree_item = self.tree_widget.itemAt(pos) - menu.exec(self.tree_widget.viewport().mapToGlobal(pos)) - - def on_item_double_clicked(self, item, column): - if column == 0 and self.ok_to_add is True: - self.ok_to_add = False - self.on_tool_requested_from_app() - - def on_list_selection_change(self, current, previous): - # for idx in current.indexes(): - # print(idx.data()) - # print(current.text(0)) - self.current_toolid = int(current.text(0)) - - self.storage_to_form(self.db_tool_dict[current.text(0)]) - - def on_list_item_edited(self, item, column): - if column == 0: - return - - self.name_entry.set_value(item.text(1)) - - def storage_to_form(self, dict_storage): - for form_key in self.form_fields: - for storage_key in dict_storage: - if form_key == storage_key: - try: - self.form_fields[form_key].set_value(dict_storage[form_key]) - except Exception as e: - print(str(e)) - if storage_key == 'data': - for data_key in dict_storage[storage_key]: - if form_key == data_key: - try: - self.form_fields[form_key].set_value(dict_storage['data'][data_key]) - except Exception as e: - print(str(e)) - - def form_to_storage(self, tool): - self.blockSignals(True) - - widget_changed = self.sender() - wdg_objname = widget_changed.objectName() - option_changed = self.name2option[wdg_objname] - - tooluid_item = int(tool) - - for tooluid_key, tooluid_val in self.db_tool_dict.items(): - if int(tooluid_key) == tooluid_item: - new_option_value = self.form_fields[option_changed].get_value() - if option_changed in tooluid_val: - tooluid_val[option_changed] = new_option_value - if option_changed in tooluid_val['data']: - tooluid_val['data'][option_changed] = new_option_value - - self.blockSignals(False) - - def setup_db_ui(self): - filename = self.app.data_path + '/geo_tools_db.FlatDB' - - # load the database tools from the file - try: - with open(filename) as f: - tools = f.read() - except IOError: - self.app.log.error("Could not load tools DB file.") - self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file.")) - return - - try: - self.db_tool_dict = json.loads(tools) - except Exception: - e = sys.exc_info()[0] - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) - return - - self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) - - self.build_db_ui() - - def build_db_ui(self): - self.ui_disconnect() - nr_crt = 0 - - parent = self.tree_widget - self.tree_widget.blockSignals(True) - self.tree_widget.clear() - self.tree_widget.blockSignals(False) - - for toolid, dict_val in self.db_tool_dict.items(): - row = nr_crt - nr_crt += 1 - - t_name = dict_val['name'] - try: - # self.add_tool_table_line(row, name=t_name, tooldict=dict_val) - self.tree_widget.blockSignals(True) - try: - self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True) - except Exception as e: - print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e)) - self.tree_widget.blockSignals(False) - except Exception as e: - self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e)) - - if self.current_toolid is None or self.current_toolid < 1: - if self.db_tool_dict: - self.storage_to_form(self.db_tool_dict['1']) - - # Enable GUI - self.basic_box.setEnabled(True) - self.advanced_box.setEnabled(True) - self.ncc_box.setEnabled(True) - self.paint_box.setEnabled(True) - - self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0)) - # self.tree_widget.setFocus() - - else: - # Disable GUI - self.basic_box.setEnabled(False) - self.advanced_box.setEnabled(False) - self.ncc_box.setEnabled(False) - self.paint_box.setEnabled(False) - else: - self.storage_to_form(self.db_tool_dict[str(self.current_toolid)]) - - self.ui_connect() - - def on_tool_add(self): - """ - Add a tool in the DB Tool Table - :return: None - """ - - default_data = {} - default_data.update({ - "plot": True, - "cutz": float(self.app.defaults["geometry_cutz"]), - "multidepth": self.app.defaults["geometry_multidepth"], - "depthperpass": float(self.app.defaults["geometry_depthperpass"]), - "vtipdia": float(self.app.defaults["geometry_vtipdia"]), - "vtipangle": float(self.app.defaults["geometry_vtipangle"]), - "travelz": float(self.app.defaults["geometry_travelz"]), - "feedrate": float(self.app.defaults["geometry_feedrate"]), - "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]), - "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]), - "spindlespeed": self.app.defaults["geometry_spindlespeed"], - "dwell": self.app.defaults["geometry_dwell"], - "dwelltime": float(self.app.defaults["geometry_dwelltime"]), - "ppname_g": self.app.defaults["geometry_ppname_g"], - "extracut": self.app.defaults["geometry_extracut"], - "extracut_length": float(self.app.defaults["geometry_extracut_length"]), - "toolchange": self.app.defaults["geometry_toolchange"], - "toolchangexy": self.app.defaults["geometry_toolchangexy"], - "toolchangez": float(self.app.defaults["geometry_toolchangez"]), - "startz": self.app.defaults["geometry_startz"], - "endz": float(self.app.defaults["geometry_endz"]), - - # NCC - "tools_nccoperation": self.app.defaults["tools_nccoperation"], - "tools_nccmilling_type": self.app.defaults["tools_nccmilling_type"], - "tools_nccoverlap": float(self.app.defaults["tools_nccoverlap"]), - "tools_nccmargin": float(self.app.defaults["tools_nccmargin"]), - "tools_nccmethod": self.app.defaults["tools_nccmethod"], - "tools_nccconnect": self.app.defaults["tools_nccconnect"], - "tools_ncccontour": self.app.defaults["tools_ncccontour"], - "tools_ncc_offset_choice": self.app.defaults["tools_ncc_offset_choice"], - "tools_ncc_offset_value": float(self.app.defaults["tools_ncc_offset_value"]), - - # Paint - "tools_paintoverlap": float(self.app.defaults["tools_paintoverlap"]), - "tools_paintmargin": float(self.app.defaults["tools_paintmargin"]), - "tools_paintmethod": self.app.defaults["tools_paintmethod"], - "tools_pathconnect": self.app.defaults["tools_pathconnect"], - "tools_paintcontour": self.app.defaults["tools_paintcontour"], - }) - - dict_elem = {} - dict_elem['name'] = 'new_tool' - if type(self.app.defaults["geometry_cnctooldia"]) == float: - dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"] - else: - try: - tools_string = self.app.defaults["geometry_cnctooldia"].split(",") - tools_diameters = [eval(a) for a in tools_string if a != ''] - dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0 - except Exception as e: - self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e)) - return - - dict_elem['offset'] = 'Path' - dict_elem['offset_value'] = 0.0 - dict_elem['type'] = 'Rough' - dict_elem['tool_type'] = 'C1' - dict_elem['data'] = default_data - - new_toolid = len(self.db_tool_dict) + 1 - self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem) - - # add the new entry to the Tools DB table - self.update_storage() - self.build_db_ui() - self.app.inform.emit('[success] %s' % _("Tool added to DB.")) - - def on_tool_copy(self): - """ - Copy a selection of Tools in the Tools DB table - :return: - """ - new_tool_id = len(self.db_tool_dict) - for item in self.tree_widget.selectedItems(): - old_tool_id = item.data(0, QtCore.Qt.DisplayRole) - - for toolid, dict_val in list(self.db_tool_dict.items()): - if int(old_tool_id) == int(toolid): - new_tool_id += 1 - new_key = str(new_tool_id) - - self.db_tool_dict.update({ - new_key: deepcopy(dict_val) - }) - - self.current_toolid = new_tool_id - - self.update_storage() - self.build_db_ui() - self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB.")) - - def on_tool_delete(self): - """ - Delete a selection of Tools in the Tools DB table - :return: - """ - for item in self.tree_widget.selectedItems(): - toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole) - - for toolid, dict_val in list(self.db_tool_dict.items()): - if int(toolname_to_remove) == int(toolid): - # remove from the storage - self.db_tool_dict.pop(toolid, None) - - self.current_toolid -= 1 - - self.update_storage() - self.build_db_ui() - self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB.")) - - def on_export_tools_db_file(self): - self.app.report_usage("on_export_tools_db_file") - self.app.log.debug("on_export_tools_db_file()") - - date = str(datetime.today()).rpartition('.')[0] - date = ''.join(c for c in date if c not in ':-') - date = date.replace(' ', '_') - - filter__ = "Text File (*.TXT);;All Files (*.*)" - filename, _f = FCFileSaveDialog.get_saved_filename( caption=_("Export Tools Database"), - directory='{l_save}/FlatCAM_{n}_{date}'.format( - l_save=str(self.app.get_last_save_folder()), - n=_("Tools_Database"), - date=date), - filter=filter__) - - filename = str(filename) - - if filename == "": - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - return - else: - try: - f = open(filename, 'w') - f.close() - except PermissionError: - self.app.inform.emit('[WARNING] %s' % - _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.")) - return - except IOError: - self.app.log.debug('Creating a new Tools DB file ...') - f = open(filename, 'w') - f.close() - except Exception: - e = sys.exc_info()[0] - self.app.log.error("Could not load Tools DB file.") - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) - return - - # Save update options - try: - # Save Tools DB in a file - try: - with open(filename, "w") as f: - json.dump(self.db_tool_dict, f, default=to_dict, indent=2) - except Exception as e: - self.app.log.debug("App.on_save_tools_db() --> %s" % str(e)) - self.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) - return - except Exception: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) - return - - self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename)) - - def on_import_tools_db_file(self): - self.app.report_usage("on_import_tools_db_file") - self.app.log.debug("on_import_tools_db_file()") - - filter__ = "Text File (*.TXT);;All Files (*.*)" - filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__) - - if filename == "": - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) - else: - try: - with open(filename) as f: - tools_in_db = f.read() - except IOError: - self.app.log.error("Could not load Tools DB file.") - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) - return - - try: - self.db_tool_dict = json.loads(tools_in_db) - except Exception: - e = sys.exc_info()[0] - self.app.log.error(str(e)) - self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) - return - - self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) - self.build_db_ui() - self.update_storage() - - def on_save_tools_db(self, silent=False): - self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.") - - filename = self.app.data_path + "/geo_tools_db.FlatDB" - - # Preferences save, update the color of the Tools DB Tab text - for idx in range(self.app.ui.plot_tab_area.count()): - if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): - self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) - - # Save Tools DB in a file - try: - f = open(filename, "w") - json.dump(self.db_tool_dict, f, default=to_dict, indent=2) - f.close() - except Exception as e: - self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e)) - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) - return - - if not silent: - self.app.inform.emit('[success] %s' % _("Saved Tools DB.")) - - def ui_connect(self): - # make sure that we don't make multiple connections to the widgets - self.ui_disconnect() - - self.name_entry.editingFinished.connect(self.update_tree_name) - - for key in self.form_fields: - wdg = self.form_fields[key] - - # FCEntry - if isinstance(wdg, FCEntry): - wdg.textChanged.connect(self.update_storage) - - # ComboBox - if isinstance(wdg, FCComboBox): - wdg.currentIndexChanged.connect(self.update_storage) - - # CheckBox - if isinstance(wdg, FCCheckBox): - wdg.toggled.connect(self.update_storage) - - # FCRadio - if isinstance(wdg, RadioSet): - wdg.activated_custom.connect(self.update_storage) - - # SpinBox, DoubleSpinBox - if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner): - wdg.valueChanged.connect(self.update_storage) - - def ui_disconnect(self): - try: - self.name_entry.editingFinished.disconnect(self.update_tree_name) - except (TypeError, AttributeError): - pass - - for key in self.form_fields: - wdg = self.form_fields[key] - - # FCEntry - if isinstance(wdg, FCEntry): - try: - wdg.textChanged.disconnect(self.update_storage) - except (TypeError, AttributeError): - pass - - # ComboBox - if isinstance(wdg, FCComboBox): - try: - wdg.currentIndexChanged.disconnect(self.update_storage) - except (TypeError, AttributeError): - pass - - # CheckBox - if isinstance(wdg, FCCheckBox): - try: - wdg.toggled.disconnect(self.update_storage) - except (TypeError, AttributeError): - pass - - # FCRadio - if isinstance(wdg, RadioSet): - try: - wdg.activated_custom.disconnect(self.update_storage) - except (TypeError, AttributeError): - pass - - # SpinBox, DoubleSpinBox - if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner): - try: - wdg.valueChanged.disconnect(self.update_storage) - except (TypeError, AttributeError): - pass - - def update_tree_name(self): - val = self.name_entry.get_value() - - item = self.tree_widget.currentItem() - # I'm setting the value for the second column (designated by 1) because first column holds the ID - # and second column holds the Name (this behavior is set in the build_ui method) - item.setData(1, QtCore.Qt.DisplayRole, val) - - def update_storage(self): - """ - Update the dictionary that is the storage of the tools 'database' - :return: - """ - tool_id = str(self.current_toolid) - - wdg = self.sender() - if wdg is None: - return - - wdg_name = wdg.objectName() - - try: - val = wdg.get_value() - except AttributeError: - return - - if wdg_name == "gdb_name": - self.db_tool_dict[tool_id]['name'] = val - elif wdg_name == "gdb_dia": - self.db_tool_dict[tool_id]['tooldia'] = val - elif wdg_name == "gdb_tool_offset": - self.db_tool_dict[tool_id]['offset'] = val - elif wdg_name == "gdb_custom_offset": - self.db_tool_dict[tool_id]['offset_value'] = val - elif wdg_name == "gdb_type": - self.db_tool_dict[tool_id]['type'] = val - elif wdg_name == "gdb_shape": - self.db_tool_dict[tool_id]['tool_type'] = val - else: - if wdg_name == "gdb_cutz": - self.db_tool_dict[tool_id]['data']['cutz'] = val - elif wdg_name == "gdb_multidepth": - self.db_tool_dict[tool_id]['data']['multidepth'] = val - elif wdg_name == "gdb_multidepth_entry": - self.db_tool_dict[tool_id]['data']['depthperpass'] = val - - elif wdg_name == "gdb_travel": - self.db_tool_dict[tool_id]['data']['travelz'] = val - elif wdg_name == "gdb_frxy": - self.db_tool_dict[tool_id]['data']['feedrate'] = val - elif wdg_name == "gdb_frz": - self.db_tool_dict[tool_id]['data']['feedrate_z'] = val - elif wdg_name == "gdb_spindle": - self.db_tool_dict[tool_id]['data']['spindlespeed'] = val - elif wdg_name == "gdb_dwell": - self.db_tool_dict[tool_id]['data']['dwell'] = val - elif wdg_name == "gdb_dwelltime": - self.db_tool_dict[tool_id]['data']['dwelltime'] = val - - elif wdg_name == "gdb_vdia": - self.db_tool_dict[tool_id]['data']['vtipdia'] = val - elif wdg_name == "gdb_vangle": - self.db_tool_dict[tool_id]['data']['vtipangle'] = val - elif wdg_name == "gdb_frapids": - self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val - elif wdg_name == "gdb_ecut": - self.db_tool_dict[tool_id]['data']['extracut'] = val - elif wdg_name == "gdb_ecut_length": - self.db_tool_dict[tool_id]['data']['extracut_length'] = val - - # NCC Tool - elif wdg_name == "gdb_n_operation": - self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val - elif wdg_name == "gdb_n_overlap": - self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val - elif wdg_name == "gdb_n_margin": - self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val - elif wdg_name == "gdb_n_method": - self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val - elif wdg_name == "gdb_n_connect": - self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val - elif wdg_name == "gdb_n_contour": - self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val - elif wdg_name == "gdb_n_offset": - self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val - elif wdg_name == "gdb_n_offset_value": - self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val - elif wdg_name == "gdb_n_milling_type": - self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val - - # Paint Tool - elif wdg_name == "gdb_p_overlap": - self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val - elif wdg_name == "gdb_p_margin": - self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val - elif wdg_name == "gdb_p_method": - self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val - elif wdg_name == "gdb_p_connect": - self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val - elif wdg_name == "gdb_p_contour": - self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val - - self.callback_app() - - def on_tool_requested_from_app(self): - if not self.tree_widget.selectedItems(): - self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table")) - return - - for item in self.tree_widget.selectedItems(): - tool_uid = item.data(0, QtCore.Qt.DisplayRole) - - for key in self.db_tool_dict.keys(): - if str(key) == str(tool_uid): - selected_tool = self.db_tool_dict[key] - self.on_tool_request(tool=selected_tool) - - def on_cancel_tool(self): - for idx in range(self.app.ui.plot_tab_area.count()): - if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): - wdg = self.app.ui.plot_tab_area.widget(idx) - wdg.deleteLater() - self.app.ui.plot_tab_area.removeTab(idx) - self.app.inform.emit('%s' % _("Cancelled adding tool from DB.")) - - def resize_new_tool_table_widget(self, min_size, max_size): - """ - Resize the table widget responsible for adding new tool in the Tool Database - - :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() - :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() - :return: - """ - t_height = self.t_height - if max_size > min_size: - t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height() - - self.new_tool_table_widget.setMaximumHeight(t_height) - - def closeEvent(self, QCloseEvent): - super().closeEvent(QCloseEvent) - - def color_variant(hex_color, bright_factor=1): """ Takes a color in HEX format #FF00FF and produces a lighter or darker variant diff --git a/FlatCAMDB.py b/FlatCAMDB.py new file mode 100644 index 00000000..187bfef4 --- /dev/null +++ b/FlatCAMDB.py @@ -0,0 +1,2399 @@ +from PyQt5 import QtGui, QtCore, QtWidgets +from flatcamGUI.GUIElements import FCTable, FCEntry, FCButton, FCDoubleSpinner, FCComboBox, FCCheckBox, FCSpinner, \ + FCTree, RadioSet, FCFileSaveDialog +from camlib import to_dict + +import sys +import json + +from copy import deepcopy +from datetime import datetime +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class ToolsDB(QtWidgets.QWidget): + + mark_tools_rows = QtCore.pyqtSignal() + + def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None): + super(ToolsDB, self).__init__(parent) + + self.app = app + self.decimals = 4 + self.callback_app = callback_on_edited + + self.on_tool_request = callback_on_tool_request + + self.offset_item_options = ["Path", "In", "Out", "Custom"] + self.type_item_options = ["Iso", "Rough", "Finish"] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + + ''' + dict to hold all the tools in the Tools DB + format: + { + tool_id: { + 'name': 'new_tool' + 'tooldia': self.app.defaults["geometry_cnctooldia"] + 'offset': 'Path' + 'offset_value': 0.0 + 'type': _('Rough'), + 'tool_type': 'C1' + 'data': dict() + } + } + ''' + self.db_tool_dict = {} + + # layouts + layout = QtWidgets.QVBoxLayout() + self.setLayout(layout) + + table_hlay = QtWidgets.QHBoxLayout() + layout.addLayout(table_hlay) + + self.table_widget = FCTable(drag_drop=True) + self.table_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + table_hlay.addWidget(self.table_widget) + + # set the number of columns and the headers tool tips + self.configure_table() + + # pal = QtGui.QPalette() + # pal.setColor(QtGui.QPalette.Background, Qt.white) + + # New Bookmark + new_vlay = QtWidgets.QVBoxLayout() + layout.addLayout(new_vlay) + + # new_tool_lbl = QtWidgets.QLabel('%s' % _("New Tool")) + # new_vlay.addWidget(new_tool_lbl, alignment=QtCore.Qt.AlignBottom) + + self.buttons_frame = QtWidgets.QFrame() + self.buttons_frame.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.buttons_frame) + self.buttons_box = QtWidgets.QHBoxLayout() + self.buttons_box.setContentsMargins(0, 0, 0, 0) + self.buttons_frame.setLayout(self.buttons_box) + self.buttons_frame.show() + + add_entry_btn = FCButton(_("Add Geometry Tool in DB")) + add_entry_btn.setToolTip( + _("Add a new tool in the Tools Database.\n" + "It will be used in the Geometry UI.\n" + "You can edit it after it is added.") + ) + self.buttons_box.addWidget(add_entry_btn) + + # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB")) + # add_fct_entry_btn.setToolTip( + # _("Add a new tool in the Tools Database.\n" + # "It will be used in the Paint/NCC Tools UI.\n" + # "You can edit it after it is added.") + # ) + # self.buttons_box.addWidget(add_fct_entry_btn) + + remove_entry_btn = FCButton(_("Delete Tool from DB")) + remove_entry_btn.setToolTip( + _("Remove a selection of tools in the Tools Database.") + ) + self.buttons_box.addWidget(remove_entry_btn) + + export_db_btn = FCButton(_("Export DB")) + export_db_btn.setToolTip( + _("Save the Tools Database to a custom text file.") + ) + self.buttons_box.addWidget(export_db_btn) + + import_db_btn = FCButton(_("Import DB")) + import_db_btn.setToolTip( + _("Load the Tools Database information's from a custom text file.") + ) + self.buttons_box.addWidget(import_db_btn) + + self.add_tool_from_db = FCButton(_("Add Tool from Tools DB")) + self.add_tool_from_db.setToolTip( + _("Add a new tool in the Tools Table of the\n" + "active Geometry object after selecting a tool\n" + "in the Tools Database.") + ) + self.add_tool_from_db.hide() + + self.cancel_tool_from_db = FCButton(_("Cancel")) + self.cancel_tool_from_db.hide() + + hlay = QtWidgets.QHBoxLayout() + layout.addLayout(hlay) + hlay.addWidget(self.add_tool_from_db) + hlay.addWidget(self.cancel_tool_from_db) + hlay.addStretch() + + # ############################################################################## + # ######################## SIGNALS ############################################# + # ############################################################################## + + add_entry_btn.clicked.connect(self.on_tool_add) + remove_entry_btn.clicked.connect(self.on_tool_delete) + export_db_btn.clicked.connect(self.on_export_tools_db_file) + import_db_btn.clicked.connect(self.on_import_tools_db_file) + # closebtn.clicked.connect(self.accept) + + self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app) + self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool) + + self.setup_db_ui() + + def configure_table(self): + self.table_widget.setColumnCount(27) + # self.table_widget.setColumnWidth(0, 20) + self.table_widget.setHorizontalHeaderLabels( + [ + '#', + _("Tool Name"), + _("Tool Dia"), + _("Tool Offset"), + _("Custom Offset"), + _("Tool Type"), + _("Tool Shape"), + _("Cut Z"), + _("MultiDepth"), + _("DPP"), + _("V-Dia"), + _("V-Angle"), + _("Travel Z"), + _("FR"), + _("FR Z"), + _("FR Rapids"), + _("Spindle Speed"), + _("Dwell"), + _("Dwelltime"), + _("Preprocessor"), + _("ExtraCut"), + _("E-Cut Length"), + _("Toolchange"), + _("Toolchange XY"), + _("Toolchange Z"), + _("Start Z"), + _("End Z"), + ] + ) + self.table_widget.horizontalHeaderItem(0).setToolTip( + _("Tool Index.")) + self.table_widget.horizontalHeaderItem(1).setToolTip( + _("Tool name.\n" + "This is not used in the app, it's function\n" + "is to serve as a note for the user.")) + self.table_widget.horizontalHeaderItem(2).setToolTip( + _("Tool Diameter.")) + self.table_widget.horizontalHeaderItem(3).setToolTip( + _("Tool Offset.\n" + "Can be of a few types:\n" + "Path = zero offset\n" + "In = offset inside by half of tool diameter\n" + "Out = offset outside by half of tool diameter\n" + "Custom = custom offset using the Custom Offset value")) + self.table_widget.horizontalHeaderItem(4).setToolTip( + _("Custom Offset.\n" + "A value to be used as offset from the current path.")) + self.table_widget.horizontalHeaderItem(5).setToolTip( + _("Tool Type.\n" + "Can be:\n" + "Iso = isolation cut\n" + "Rough = rough cut, low feedrate, multiple passes\n" + "Finish = finishing cut, high feedrate")) + self.table_widget.horizontalHeaderItem(6).setToolTip( + _("Tool Shape. \n" + "Can be:\n" + "C1 ... C4 = circular tool with x flutes\n" + "B = ball tip milling tool\n" + "V = v-shape milling tool")) + self.table_widget.horizontalHeaderItem(7).setToolTip( + _("Cutting Depth.\n" + "The depth at which to cut into material.")) + self.table_widget.horizontalHeaderItem(8).setToolTip( + _("Multi Depth.\n" + "Selecting this will allow cutting in multiple passes,\n" + "each pass adding a DPP parameter depth.")) + self.table_widget.horizontalHeaderItem(9).setToolTip( + _("DPP. Depth per Pass.\n" + "The value used to cut into material on each pass.")) + self.table_widget.horizontalHeaderItem(10).setToolTip( + _("V-Dia.\n" + "Diameter of the tip for V-Shape Tools.")) + self.table_widget.horizontalHeaderItem(11).setToolTip( + _("V-Agle.\n" + "Angle at the tip for the V-Shape Tools.")) + self.table_widget.horizontalHeaderItem(12).setToolTip( + _("Clearance Height.\n" + "Height at which the milling bit will travel between cuts,\n" + "above the surface of the material, avoiding all fixtures.")) + self.table_widget.horizontalHeaderItem(13).setToolTip( + _("FR. Feedrate\n" + "The speed on XY plane used while cutting into material.")) + self.table_widget.horizontalHeaderItem(14).setToolTip( + _("FR Z. Feedrate Z\n" + "The speed on Z plane.")) + self.table_widget.horizontalHeaderItem(15).setToolTip( + _("FR Rapids. Feedrate Rapids\n" + "Speed used while moving as fast as possible.\n" + "This is used only by some devices that can't use\n" + "the G0 g-code command. Mostly 3D printers.")) + self.table_widget.horizontalHeaderItem(16).setToolTip( + _("Spindle Speed.\n" + "If it's left empty it will not be used.\n" + "The speed of the spindle in RPM.")) + self.table_widget.horizontalHeaderItem(17).setToolTip( + _("Dwell.\n" + "Check this if a delay is needed to allow\n" + "the spindle motor to reach it's set speed.")) + self.table_widget.horizontalHeaderItem(18).setToolTip( + _("Dwell Time.\n" + "A delay used to allow the motor spindle reach it's set speed.")) + self.table_widget.horizontalHeaderItem(19).setToolTip( + _("Preprocessor.\n" + "A selection of files that will alter the generated G-code\n" + "to fit for a number of use cases.")) + self.table_widget.horizontalHeaderItem(20).setToolTip( + _("Extra Cut.\n" + "If checked, after a isolation is finished an extra cut\n" + "will be added where the start and end of isolation meet\n" + "such as that this point is covered by this extra cut to\n" + "ensure a complete isolation.")) + self.table_widget.horizontalHeaderItem(21).setToolTip( + _("Extra Cut length.\n" + "If checked, after a isolation is finished an extra cut\n" + "will be added where the start and end of isolation meet\n" + "such as that this point is covered by this extra cut to\n" + "ensure a complete isolation. This is the length of\n" + "the extra cut.")) + self.table_widget.horizontalHeaderItem(22).setToolTip( + _("Toolchange.\n" + "It will create a toolchange event.\n" + "The kind of toolchange is determined by\n" + "the preprocessor file.")) + self.table_widget.horizontalHeaderItem(23).setToolTip( + _("Toolchange XY.\n" + "A set of coordinates in the format (x, y).\n" + "Will determine the cartesian position of the point\n" + "where the tool change event take place.")) + self.table_widget.horizontalHeaderItem(24).setToolTip( + _("Toolchange Z.\n" + "The position on Z plane where the tool change event take place.")) + self.table_widget.horizontalHeaderItem(25).setToolTip( + _("Start Z.\n" + "If it's left empty it will not be used.\n" + "A position on Z plane to move immediately after job start.")) + self.table_widget.horizontalHeaderItem(26).setToolTip( + _("End Z.\n" + "A position on Z plane to move immediately after job stop.")) + + def setup_db_ui(self): + filename = self.app.data_path + '/geo_tools_db.FlatDB' + + # load the database tools from the file + try: + with open(filename) as f: + tools = f.read() + except IOError: + self.app.log.error("Could not load tools DB file.") + self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file.")) + return + + try: + self.db_tool_dict = json.loads(tools) + except Exception: + e = sys.exc_info()[0] + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) + return + + self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) + + self.build_db_ui() + + self.table_widget.setupContextMenu() + self.table_widget.addContextMenu( + _("Add to DB"), self.on_tool_add, icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")) + self.table_widget.addContextMenu( + _("Copy from DB"), self.on_tool_copy, icon=QtGui.QIcon(self.app.resource_location + "/copy16.png")) + self.table_widget.addContextMenu( + _("Delete from DB"), self.on_tool_delete, icon=QtGui.QIcon(self.app.resource_location + "/delete32.png")) + + def build_db_ui(self): + self.ui_disconnect() + self.table_widget.setRowCount(len(self.db_tool_dict)) + + nr_crt = 0 + + for toolid, dict_val in self.db_tool_dict.items(): + row = nr_crt + nr_crt += 1 + + t_name = dict_val['name'] + try: + self.add_tool_table_line(row, name=t_name, widget=self.table_widget, tooldict=dict_val) + except Exception as e: + self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e)) + vertical_header = self.table_widget.verticalHeader() + vertical_header.hide() + + horizontal_header = self.table_widget.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + + self.table_widget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + for x in range(27): + self.table_widget.resizeColumnToContents(x) + + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + # horizontal_header.setSectionResizeMode(13, QtWidgets.QHeaderView.Fixed) + + horizontal_header.resizeSection(0, 20) + # horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) + # horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch) + + self.ui_connect() + + def add_tool_table_line(self, row, name, widget, tooldict): + data = tooldict['data'] + + nr_crt = row + 1 + id_item = QtWidgets.QTableWidgetItem('%d' % int(nr_crt)) + # id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + flags = id_item.flags() & ~QtCore.Qt.ItemIsEditable + id_item.setFlags(flags) + widget.setItem(row, 0, id_item) # Tool name/id + + tool_name_item = QtWidgets.QTableWidgetItem(name) + widget.setItem(row, 1, tool_name_item) + + dia_item = FCDoubleSpinner() + dia_item.set_precision(self.decimals) + dia_item.setSingleStep(0.1) + dia_item.set_range(0.0, 9999.9999) + dia_item.set_value(float(tooldict['tooldia'])) + widget.setCellWidget(row, 2, dia_item) + + tool_offset_item = FCComboBox() + for item in self.offset_item_options: + tool_offset_item.addItem(item) + tool_offset_item.set_value(tooldict['offset']) + widget.setCellWidget(row, 3, tool_offset_item) + + c_offset_item = FCDoubleSpinner() + c_offset_item.set_precision(self.decimals) + c_offset_item.setSingleStep(0.1) + c_offset_item.set_range(-9999.9999, 9999.9999) + c_offset_item.set_value(float(tooldict['offset_value'])) + widget.setCellWidget(row, 4, c_offset_item) + + tt_item = FCComboBox() + for item in self.type_item_options: + tt_item.addItem(item) + tt_item.set_value(tooldict['type']) + widget.setCellWidget(row, 5, tt_item) + + tshape_item = FCComboBox() + for item in self.tool_type_item_options: + tshape_item.addItem(item) + tshape_item.set_value(tooldict['tool_type']) + widget.setCellWidget(row, 6, tshape_item) + + cutz_item = FCDoubleSpinner() + cutz_item.set_precision(self.decimals) + cutz_item.setSingleStep(0.1) + if self.app.defaults['global_machinist_setting']: + cutz_item.set_range(-9999.9999, 9999.9999) + else: + cutz_item.set_range(-9999.9999, -0.0000) + + cutz_item.set_value(float(data['cutz'])) + widget.setCellWidget(row, 7, cutz_item) + + multidepth_item = FCCheckBox() + 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() + # multidepth_item = QtWidgets.QWidget() + # cb = FCCheckBox() + # cb.set_value(data['multidepth']) + # qhboxlayout = QtWidgets.QHBoxLayout(multidepth_item) + # qhboxlayout.addWidget(cb) + # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter) + # qhboxlayout.setContentsMargins(0, 0, 0, 0) + # widget.setCellWidget(row, 8, multidepth_item) + + depth_per_pass_item = FCDoubleSpinner() + depth_per_pass_item.set_precision(self.decimals) + depth_per_pass_item.setSingleStep(0.1) + depth_per_pass_item.set_range(0.0, 9999.9999) + depth_per_pass_item.set_value(float(data['depthperpass'])) + widget.setCellWidget(row, 9, depth_per_pass_item) + + vtip_dia_item = FCDoubleSpinner() + vtip_dia_item.set_precision(self.decimals) + vtip_dia_item.setSingleStep(0.1) + vtip_dia_item.set_range(0.0, 9999.9999) + vtip_dia_item.set_value(float(data['vtipdia'])) + widget.setCellWidget(row, 10, vtip_dia_item) + + vtip_angle_item = FCDoubleSpinner() + vtip_angle_item.set_precision(self.decimals) + vtip_angle_item.setSingleStep(0.1) + vtip_angle_item.set_range(-360.0, 360.0) + vtip_angle_item.set_value(float(data['vtipangle'])) + widget.setCellWidget(row, 11, vtip_angle_item) + + travelz_item = FCDoubleSpinner() + travelz_item.set_precision(self.decimals) + travelz_item.setSingleStep(0.1) + if self.app.defaults['global_machinist_setting']: + travelz_item.set_range(-9999.9999, 9999.9999) + else: + travelz_item.set_range(0.0000, 9999.9999) + + travelz_item.set_value(float(data['travelz'])) + widget.setCellWidget(row, 12, travelz_item) + + fr_item = FCDoubleSpinner() + fr_item.set_precision(self.decimals) + fr_item.set_range(0.0, 9999.9999) + fr_item.set_value(float(data['feedrate'])) + widget.setCellWidget(row, 13, fr_item) + + frz_item = FCDoubleSpinner() + frz_item.set_precision(self.decimals) + frz_item.set_range(0.0, 9999.9999) + frz_item.set_value(float(data['feedrate_z'])) + widget.setCellWidget(row, 14, frz_item) + + frrapids_item = FCDoubleSpinner() + frrapids_item.set_precision(self.decimals) + frrapids_item.set_range(0.0, 9999.9999) + frrapids_item.set_value(float(data['feedrate_rapid'])) + widget.setCellWidget(row, 15, frrapids_item) + + spindlespeed_item = FCSpinner() + spindlespeed_item.set_range(0, 1000000) + spindlespeed_item.set_value(int(data['spindlespeed'])) + spindlespeed_item.set_step(100) + widget.setCellWidget(row, 16, spindlespeed_item) + + dwell_item = FCCheckBox() + dwell_item.set_value(data['dwell']) + widget.setCellWidget(row, 17, dwell_item) + + dwelltime_item = FCDoubleSpinner() + dwelltime_item.set_precision(self.decimals) + dwelltime_item.set_range(0.0000, 9999.9999) + dwelltime_item.set_value(float(data['dwelltime'])) + widget.setCellWidget(row, 18, dwelltime_item) + + pp_item = FCComboBox() + for item in self.app.preprocessors: + pp_item.addItem(item) + pp_item.set_value(data['ppname_g']) + widget.setCellWidget(row, 19, pp_item) + + ecut_item = FCCheckBox() + ecut_item.set_value(data['extracut']) + widget.setCellWidget(row, 20, ecut_item) + + ecut_length_item = FCDoubleSpinner() + ecut_length_item.set_precision(self.decimals) + ecut_length_item.set_range(0.0000, 9999.9999) + ecut_length_item.set_value(data['extracut_length']) + widget.setCellWidget(row, 21, ecut_length_item) + + toolchange_item = FCCheckBox() + toolchange_item.set_value(data['toolchange']) + widget.setCellWidget(row, 22, toolchange_item) + + toolchangexy_item = QtWidgets.QTableWidgetItem(str(data['toolchangexy']) if data['toolchangexy'] else '') + widget.setItem(row, 23, toolchangexy_item) + + toolchangez_item = FCDoubleSpinner() + toolchangez_item.set_precision(self.decimals) + toolchangez_item.setSingleStep(0.1) + if self.app.defaults['global_machinist_setting']: + toolchangez_item.set_range(-9999.9999, 9999.9999) + else: + toolchangez_item.set_range(0.0000, 9999.9999) + + toolchangez_item.set_value(float(data['toolchangez'])) + widget.setCellWidget(row, 24, toolchangez_item) + + startz_item = QtWidgets.QTableWidgetItem(str(data['startz']) if data['startz'] else '') + widget.setItem(row, 25, startz_item) + + endz_item = FCDoubleSpinner() + endz_item.set_precision(self.decimals) + endz_item.setSingleStep(0.1) + if self.app.defaults['global_machinist_setting']: + endz_item.set_range(-9999.9999, 9999.9999) + else: + endz_item.set_range(0.0000, 9999.9999) + + endz_item.set_value(float(data['endz'])) + widget.setCellWidget(row, 26, endz_item) + + def on_tool_add(self): + """ + Add a tool in the DB Tool Table + :return: None + """ + + default_data = {} + default_data.update({ + "cutz": float(self.app.defaults["geometry_cutz"]), + "multidepth": self.app.defaults["geometry_multidepth"], + "depthperpass": float(self.app.defaults["geometry_depthperpass"]), + "vtipdia": float(self.app.defaults["geometry_vtipdia"]), + "vtipangle": float(self.app.defaults["geometry_vtipangle"]), + "travelz": float(self.app.defaults["geometry_travelz"]), + "feedrate": float(self.app.defaults["geometry_feedrate"]), + "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]), + "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]), + "spindlespeed": self.app.defaults["geometry_spindlespeed"], + "dwell": self.app.defaults["geometry_dwell"], + "dwelltime": float(self.app.defaults["geometry_dwelltime"]), + "ppname_g": self.app.defaults["geometry_ppname_g"], + "extracut": self.app.defaults["geometry_extracut"], + "extracut_length": float(self.app.defaults["geometry_extracut_length"]), + "toolchange": self.app.defaults["geometry_toolchange"], + "toolchangexy": self.app.defaults["geometry_toolchangexy"], + "toolchangez": float(self.app.defaults["geometry_toolchangez"]), + "startz": self.app.defaults["geometry_startz"], + "endz": float(self.app.defaults["geometry_endz"]) + }) + + dict_elem = {} + dict_elem['name'] = 'new_tool' + if type(self.app.defaults["geometry_cnctooldia"]) == float: + dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"] + else: + try: + tools_string = self.app.defaults["geometry_cnctooldia"].split(",") + tools_diameters = [eval(a) for a in tools_string if a != ''] + dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0 + except Exception as e: + self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e)) + return + + dict_elem['offset'] = 'Path' + dict_elem['offset_value'] = 0.0 + dict_elem['type'] = 'Rough' + dict_elem['tool_type'] = 'C1' + dict_elem['data'] = default_data + + new_toolid = len(self.db_tool_dict) + 1 + self.db_tool_dict[new_toolid] = deepcopy(dict_elem) + + # add the new entry to the Tools DB table + self.build_db_ui() + self.callback_on_edited() + self.app.inform.emit('[success] %s' % _("Tool added to DB.")) + + def on_tool_copy(self): + """ + Copy a selection of Tools in the Tools DB table + :return: + """ + new_tool_id = self.table_widget.rowCount() + 1 + for model_index in self.table_widget.selectionModel().selectedRows(): + # index = QtCore.QPersistentModelIndex(model_index) + old_tool_id = self.table_widget.item(model_index.row(), 0).text() + new_tool_id += 1 + + for toolid, dict_val in list(self.db_tool_dict.items()): + if int(old_tool_id) == int(toolid): + self.db_tool_dict.update({ + new_tool_id: deepcopy(dict_val) + }) + + self.build_db_ui() + self.callback_on_edited() + self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB.")) + + def on_tool_delete(self): + """ + Delete a selection of Tools in the Tools DB table + :return: + """ + for model_index in self.table_widget.selectionModel().selectedRows(): + # index = QtCore.QPersistentModelIndex(model_index) + toolname_to_remove = self.table_widget.item(model_index.row(), 0).text() + + for toolid, dict_val in list(self.db_tool_dict.items()): + if int(toolname_to_remove) == int(toolid): + # remove from the storage + self.db_tool_dict.pop(toolid, None) + + self.build_db_ui() + self.callback_on_edited() + self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB.")) + + def on_export_tools_db_file(self): + self.app.defaults.report_usage("on_export_tools_db_file") + self.app.log.debug("on_export_tools_db_file()") + + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + + filter__ = "Text File (*.TXT);;All Files (*.*)" + filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"), + directory='{l_save}/FlatCAM_{n}_{date}'.format( + l_save=str(self.app.get_last_save_folder()), + n=_("Tools_Database"), + date=date), + filter=filter__) + + filename = str(filename) + + if filename == "": + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + try: + f = open(filename, 'w') + f.close() + except PermissionError: + self.app.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return + except IOError: + self.app.log.debug('Creating a new Tools DB file ...') + f = open(filename, 'w') + f.close() + except Exception: + e = sys.exc_info()[0] + self.app.log.error("Could not load Tools DB file.") + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) + return + + # Save update options + try: + # Save Tools DB in a file + try: + with open(filename, "w") as f: + json.dump(self.db_tool_dict, f, default=to_dict, indent=2) + except Exception as e: + self.app.log.debug("App.on_save_tools_db() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) + return + except Exception: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) + return + + self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename)) + + def on_import_tools_db_file(self): + self.app.defaults.report_usage("on_import_tools_db_file") + self.app.log.debug("on_import_tools_db_file()") + + filter__ = "Text File (*.TXT);;All Files (*.*)" + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__) + + if filename == "": + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + try: + with open(filename) as f: + tools_in_db = f.read() + except IOError: + self.app.log.error("Could not load Tools DB file.") + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) + return + + try: + self.db_tool_dict = json.loads(tools_in_db) + except Exception: + e = sys.exc_info()[0] + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) + return + + self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) + self.build_db_ui() + self.callback_on_edited() + + def on_save_tools_db(self, silent=False): + self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.") + + filename = self.app.data_path + "/geo_tools_db.FlatDB" + + # Preferences save, update the color of the Tools DB Tab text + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): + self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) + + # Save Tools DB in a file + try: + f = open(filename, "w") + json.dump(self.db_tool_dict, f, default=to_dict, indent=2) + f.close() + except Exception as e: + self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) + return + + if not silent: + self.app.inform.emit('[success] %s' % _("Saved Tools DB.")) + + def ui_connect(self): + try: + try: + self.table_widget.itemChanged.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + self.table_widget.itemChanged.connect(self.callback_on_edited) + except AttributeError: + pass + + for row in range(self.table_widget.rowCount()): + for col in range(self.table_widget.columnCount()): + # ComboBox + try: + try: + self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + self.table_widget.cellWidget(row, col).currentIndexChanged.connect(self.callback_on_edited) + except AttributeError: + pass + + # CheckBox + try: + try: + self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + self.table_widget.cellWidget(row, col).toggled.connect(self.callback_on_edited) + except AttributeError: + pass + + # SpinBox, DoubleSpinBox + try: + try: + self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + self.table_widget.cellWidget(row, col).valueChanged.connect(self.callback_on_edited) + except AttributeError: + pass + + def ui_disconnect(self): + try: + self.table_widget.itemChanged.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + + for row in range(self.table_widget.rowCount()): + for col in range(self.table_widget.columnCount()): + # ComboBox + try: + self.table_widget.cellWidget(row, col).currentIndexChanged.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + + # CheckBox + try: + self.table_widget.cellWidget(row, col).toggled.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + + # SpinBox, DoubleSpinBox + try: + self.table_widget.cellWidget(row, col).valueChanged.disconnect(self.callback_on_edited) + except (TypeError, AttributeError): + pass + + def callback_on_edited(self): + + # update the dictionary storage self.db_tool_dict + self.db_tool_dict.clear() + dict_elem = {} + default_data = {} + + for row in range(self.table_widget.rowCount()): + new_toolid = row + 1 + for col in range(self.table_widget.columnCount()): + column_header_text = self.table_widget.horizontalHeaderItem(col).text() + if column_header_text == _('Tool Name'): + dict_elem['name'] = self.table_widget.item(row, col).text() + elif column_header_text == _('Tool Dia'): + dict_elem['tooldia'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Tool Offset'): + dict_elem['offset'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Custom Offset'): + dict_elem['offset_value'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Tool Type'): + dict_elem['type'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Tool Shape'): + dict_elem['tool_type'] = self.table_widget.cellWidget(row, col).get_value() + else: + if column_header_text == _('Cut Z'): + default_data['cutz'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('MultiDepth'): + default_data['multidepth'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('DPP'): + default_data['depthperpass'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('V-Dia'): + default_data['vtipdia'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('V-Angle'): + default_data['vtipangle'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Travel Z'): + default_data['travelz'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('FR'): + default_data['feedrate'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('FR Z'): + default_data['feedrate_z'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('FR Rapids'): + default_data['feedrate_rapid'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Spindle Speed'): + default_data['spindlespeed'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Dwell'): + default_data['dwell'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Dwelltime'): + default_data['dwelltime'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Preprocessor'): + default_data['ppname_g'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('ExtraCut'): + default_data['extracut'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _("E-Cut Length"): + default_data['extracut_length'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Toolchange'): + default_data['toolchange'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Toolchange XY'): + default_data['toolchangexy'] = self.table_widget.item(row, col).text() + elif column_header_text == _('Toolchange Z'): + default_data['toolchangez'] = self.table_widget.cellWidget(row, col).get_value() + elif column_header_text == _('Start Z'): + default_data['startz'] = float(self.table_widget.item(row, col).text()) \ + if self.table_widget.item(row, col).text() != '' else None + elif column_header_text == _('End Z'): + default_data['endz'] = self.table_widget.cellWidget(row, col).get_value() + + dict_elem['data'] = default_data + self.db_tool_dict.update( + { + new_toolid: deepcopy(dict_elem) + } + ) + + self.callback_app() + + def on_tool_requested_from_app(self): + if not self.table_widget.selectionModel().selectedRows(): + self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table")) + return + + model_index_list = self.table_widget.selectionModel().selectedRows() + for model_index in model_index_list: + selected_row = model_index.row() + tool_uid = selected_row + 1 + for key in self.db_tool_dict.keys(): + if str(key) == str(tool_uid): + selected_tool = self.db_tool_dict[key] + self.on_tool_request(tool=selected_tool) + + def on_cancel_tool(self): + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): + wdg = self.app.ui.plot_tab_area.widget(idx) + wdg.deleteLater() + self.app.ui.plot_tab_area.removeTab(idx) + self.app.inform.emit('%s' % _("Cancelled adding tool from DB.")) + + # def resize_new_tool_table_widget(self, min_size, max_size): + # """ + # Resize the table widget responsible for adding new tool in the Tool Database + # + # :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() + # :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() + # :return: + # """ + # t_height = self.t_height + # if max_size > min_size: + # t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height() + # + # self.new_tool_table_widget.setMaximumHeight(t_height) + + def closeEvent(self, QCloseEvent): + super().closeEvent(QCloseEvent) + + +class ToolsDB2(QtWidgets.QWidget): + + mark_tools_rows = QtCore.pyqtSignal() + + def __init__(self, app, callback_on_edited, callback_on_tool_request, parent=None): + super(ToolsDB2, self).__init__(parent) + + self.app = app + self.decimals = self.app.decimals + self.callback_app = callback_on_edited + + self.on_tool_request = callback_on_tool_request + + self.offset_item_options = ["Path", "In", "Out", "Custom"] + self.type_item_options = ["Iso", "Rough", "Finish"] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + + ''' + dict to hold all the tools in the Tools DB + format: + { + tool_id: { + 'name': 'new_tool' + 'tooldia': self.app.defaults["geometry_cnctooldia"] + 'offset': 'Path' + 'offset_value': 0.0 + 'type': _('Rough'), + 'tool_type': 'C1' + 'data': dict() + } + } + ''' + self.db_tool_dict = {} + + # layouts + grid_layout = QtWidgets.QGridLayout() + grid_layout.setColumnStretch(0, 0) + grid_layout.setColumnStretch(1, 1) + + self.setLayout(grid_layout) + + tree_layout = QtWidgets.QVBoxLayout() + grid_layout.addLayout(tree_layout, 0, 0) + + self.tree_widget = FCTree(columns=2, header_hidden=False, protected_column=[0]) + self.tree_widget.setHeaderLabels(["ID", "Tool Name"]) + self.tree_widget.setIndentation(0) + self.tree_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.tree_widget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + + # set alternating colors + # self.tree_widget.setAlternatingRowColors(True) + # p = QtGui.QPalette() + # p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(226, 237, 253) ) + # self.tree_widget.setPalette(p) + + self.tree_widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + tree_layout.addWidget(self.tree_widget) + + param_hlay = QtWidgets.QHBoxLayout() + param_area = QtWidgets.QScrollArea() + param_widget = QtWidgets.QWidget() + param_widget.setLayout(param_hlay) + + param_area.setWidget(param_widget) + param_area.setWidgetResizable(True) + + grid_layout.addWidget(param_area, 0, 1) + + # ########################################################################### + # ############## The UI form ################################################ + # ########################################################################### + self.basic_box = QtWidgets.QGroupBox() + self.basic_box.setStyleSheet(""" + QGroupBox + { + font-size: 16px; + font-weight: bold; + } + """) + self.basic_vlay = QtWidgets.QVBoxLayout() + self.basic_box.setTitle(_("Basic Geo Parameters")) + self.basic_box.setFixedWidth(250) + + self.advanced_box = QtWidgets.QGroupBox() + self.advanced_box.setStyleSheet(""" + QGroupBox + { + font-size: 16px; + font-weight: bold; + } + """) + self.advanced_vlay = QtWidgets.QVBoxLayout() + self.advanced_box.setTitle(_("Advanced Geo Parameters")) + self.advanced_box.setFixedWidth(250) + + self.ncc_box = QtWidgets.QGroupBox() + self.ncc_box.setStyleSheet(""" + QGroupBox + { + font-size: 16px; + font-weight: bold; + } + """) + self.ncc_vlay = QtWidgets.QVBoxLayout() + self.ncc_box.setTitle(_("NCC Parameters")) + self.ncc_box.setFixedWidth(250) + + self.paint_box = QtWidgets.QGroupBox() + self.paint_box.setStyleSheet(""" + QGroupBox + { + font-size: 16px; + font-weight: bold; + } + """) + self.paint_vlay = QtWidgets.QVBoxLayout() + self.paint_box.setTitle(_("Paint Parameters")) + self.paint_box.setFixedWidth(250) + + self.basic_box.setLayout(self.basic_vlay) + self.advanced_box.setLayout(self.advanced_vlay) + self.ncc_box.setLayout(self.ncc_vlay) + self.paint_box.setLayout(self.paint_vlay) + + geo_vlay = QtWidgets.QVBoxLayout() + geo_vlay.addWidget(self.basic_box) + geo_vlay.addWidget(self.advanced_box) + geo_vlay.addStretch() + + tools_vlay = QtWidgets.QVBoxLayout() + tools_vlay.addWidget(self.ncc_box) + tools_vlay.addWidget(self.paint_box) + tools_vlay.addStretch() + + param_hlay.addLayout(geo_vlay) + param_hlay.addLayout(tools_vlay) + param_hlay.addStretch() + + # ########################################################################### + # ############### BASIC UI form ############################################# + # ########################################################################### + + self.grid0 = QtWidgets.QGridLayout() + self.basic_vlay.addLayout(self.grid0) + self.grid0.setColumnStretch(0, 0) + self.grid0.setColumnStretch(1, 1) + self.basic_vlay.addStretch() + + # Tool Name + self.name_label = QtWidgets.QLabel('%s:' % _('Tool Name')) + self.name_label.setToolTip( + _("Tool name.\n" + "This is not used in the app, it's function\n" + "is to serve as a note for the user.")) + + self.name_entry = FCEntry() + self.name_entry.setObjectName('gdb_name') + + self.grid0.addWidget(self.name_label, 0, 0) + self.grid0.addWidget(self.name_entry, 0, 1) + + # Tool Dia + self.dia_label = QtWidgets.QLabel('%s:' % _('Tool Dia')) + self.dia_label.setToolTip( + _("Tool Diameter.")) + + self.dia_entry = FCDoubleSpinner() + self.dia_entry.set_range(-9999.9999, 9999.9999) + self.dia_entry.set_precision(self.decimals) + self.dia_entry.setObjectName('gdb_dia') + + self.grid0.addWidget(self.dia_label, 1, 0) + self.grid0.addWidget(self.dia_entry, 1, 1) + + # Tool Shape + self.shape_label = QtWidgets.QLabel('%s:' % _('Tool Shape')) + self.shape_label.setToolTip( + _("Tool Shape. \n" + "Can be:\n" + "C1 ... C4 = circular tool with x flutes\n" + "B = ball tip milling tool\n" + "V = v-shape milling tool")) + + self.shape_combo = FCComboBox() + self.shape_combo.addItems(["C1", "C2", "C3", "C4", "B", "V"]) + self.shape_combo.setObjectName('gdb_shape') + + self.grid0.addWidget(self.shape_label, 2, 0) + self.grid0.addWidget(self.shape_combo, 2, 1) + + # Cut Z + self.cutz_label = QtWidgets.QLabel('%s:' % _("Cut Z")) + self.cutz_label.setToolTip( + _("Cutting Depth.\n" + "The depth at which to cut into material.")) + + self.cutz_entry = FCDoubleSpinner() + self.cutz_entry.set_range(-9999.9999, 9999.9999) + self.cutz_entry.set_precision(self.decimals) + self.cutz_entry.setObjectName('gdb_cutz') + + self.grid0.addWidget(self.cutz_label, 4, 0) + self.grid0.addWidget(self.cutz_entry, 4, 1) + + # Multi Depth + self.multidepth_label = QtWidgets.QLabel('%s:' % _("MultiDepth")) + self.multidepth_label.setToolTip( + _("Multi Depth.\n" + "Selecting this will allow cutting in multiple passes,\n" + "each pass adding a DPP parameter depth.")) + + self.multidepth_cb = FCCheckBox() + self.multidepth_cb.setObjectName('gdb_multidepth') + + self.grid0.addWidget(self.multidepth_label, 5, 0) + self.grid0.addWidget(self.multidepth_cb, 5, 1) + + # Depth Per Pass + self.dpp_label = QtWidgets.QLabel('%s:' % _("DPP")) + self.dpp_label.setToolTip( + _("DPP. Depth per Pass.\n" + "The value used to cut into material on each pass.")) + + self.multidepth_entry = FCDoubleSpinner() + self.multidepth_entry.set_range(-9999.9999, 9999.9999) + self.multidepth_entry.set_precision(self.decimals) + self.multidepth_entry.setObjectName('gdb_multidepth_entry') + + self.grid0.addWidget(self.dpp_label, 7, 0) + self.grid0.addWidget(self.multidepth_entry, 7, 1) + + # Travel Z + self.travelz_label = QtWidgets.QLabel('%s:' % _("Travel Z")) + self.travelz_label.setToolTip( + _("Clearance Height.\n" + "Height at which the milling bit will travel between cuts,\n" + "above the surface of the material, avoiding all fixtures.")) + + self.travelz_entry = FCDoubleSpinner() + self.travelz_entry.set_range(-9999.9999, 9999.9999) + self.travelz_entry.set_precision(self.decimals) + self.travelz_entry.setObjectName('gdb_travel') + + self.grid0.addWidget(self.travelz_label, 9, 0) + self.grid0.addWidget(self.travelz_entry, 9, 1) + + # Feedrate X-Y + self.frxy_label = QtWidgets.QLabel('%s:' % _("Feedrate X-Y")) + self.frxy_label.setToolTip( + _("Feedrate X-Y. Feedrate\n" + "The speed on XY plane used while cutting into material.")) + + self.frxy_entry = FCDoubleSpinner() + self.frxy_entry.set_range(-999999.9999, 999999.9999) + self.frxy_entry.set_precision(self.decimals) + self.frxy_entry.setObjectName('gdb_frxy') + + self.grid0.addWidget(self.frxy_label, 12, 0) + self.grid0.addWidget(self.frxy_entry, 12, 1) + + # Feedrate Z + self.frz_label = QtWidgets.QLabel('%s:' % _("Feedrate Z")) + self.frz_label.setToolTip( + _("Feedrate Z\n" + "The speed on Z plane.")) + + self.frz_entry = FCDoubleSpinner() + self.frz_entry.set_range(-999999.9999, 999999.9999) + self.frz_entry.set_precision(self.decimals) + self.frz_entry.setObjectName('gdb_frz') + + self.grid0.addWidget(self.frz_label, 14, 0) + self.grid0.addWidget(self.frz_entry, 14, 1) + + # Spindle Spped + self.spindle_label = QtWidgets.QLabel('%s:' % _("Spindle Speed")) + self.spindle_label.setToolTip( + _("Spindle Speed.\n" + "If it's left empty it will not be used.\n" + "The speed of the spindle in RPM.")) + + self.spindle_entry = FCDoubleSpinner() + self.spindle_entry.set_range(-999999.9999, 999999.9999) + self.spindle_entry.set_precision(self.decimals) + self.spindle_entry.setObjectName('gdb_spindle') + + self.grid0.addWidget(self.spindle_label, 15, 0) + self.grid0.addWidget(self.spindle_entry, 15, 1) + + # Dwell + self.dwell_label = QtWidgets.QLabel('%s:' % _("Dwell")) + self.dwell_label.setToolTip( + _("Dwell.\n" + "Check this if a delay is needed to allow\n" + "the spindle motor to reach it's set speed.")) + + self.dwell_cb = FCCheckBox() + self.dwell_cb.setObjectName('gdb_dwell') + + self.grid0.addWidget(self.dwell_label, 16, 0) + self.grid0.addWidget(self.dwell_cb, 16, 1) + + # Dwell Time + self.dwelltime_label = QtWidgets.QLabel('%s:' % _("Dwelltime")) + self.dwelltime_label.setToolTip( + _("Dwell Time.\n" + "A delay used to allow the motor spindle reach it's set speed.")) + + self.dwelltime_entry = FCDoubleSpinner() + self.dwelltime_entry.set_range(0.0000, 9999.9999) + self.dwelltime_entry.set_precision(self.decimals) + self.dwelltime_entry.setObjectName('gdb_dwelltime') + + self.grid0.addWidget(self.dwelltime_label, 17, 0) + self.grid0.addWidget(self.dwelltime_entry, 17, 1) + + # ########################################################################### + # ############### ADVANCED UI form ########################################## + # ########################################################################### + + self.grid1 = QtWidgets.QGridLayout() + self.advanced_vlay.addLayout(self.grid1) + self.grid1.setColumnStretch(0, 0) + self.grid1.setColumnStretch(1, 1) + self.advanced_vlay.addStretch() + + # Tool Type + self.type_label = QtWidgets.QLabel('%s:' % _("Tool Type")) + self.type_label.setToolTip( + _("Tool Type.\n" + "Can be:\n" + "Iso = isolation cut\n" + "Rough = rough cut, low feedrate, multiple passes\n" + "Finish = finishing cut, high feedrate")) + + self.type_combo = FCComboBox() + self.type_combo.addItems(["Iso", "Rough", "Finish"]) + self.type_combo.setObjectName('gdb_type') + + self.grid1.addWidget(self.type_label, 0, 0) + self.grid1.addWidget(self.type_combo, 0, 1) + + # Tool Offset + self.tooloffset_label = QtWidgets.QLabel('%s:' % _('Tool Offset')) + self.tooloffset_label.setToolTip( + _("Tool Offset.\n" + "Can be of a few types:\n" + "Path = zero offset\n" + "In = offset inside by half of tool diameter\n" + "Out = offset outside by half of tool diameter\n" + "Custom = custom offset using the Custom Offset value")) + + self.tooloffset_combo = FCComboBox() + self.tooloffset_combo.addItems(["Path", "In", "Out", "Custom"]) + self.tooloffset_combo.setObjectName('gdb_tool_offset') + + self.grid1.addWidget(self.tooloffset_label, 2, 0) + self.grid1.addWidget(self.tooloffset_combo, 2, 1) + + # Custom Offset + self.custom_offset_label = QtWidgets.QLabel('%s:' % _("Custom Offset")) + self.custom_offset_label.setToolTip( + _("Custom Offset.\n" + "A value to be used as offset from the current path.")) + + self.custom_offset_entry = FCDoubleSpinner() + self.custom_offset_entry.set_range(-9999.9999, 9999.9999) + self.custom_offset_entry.set_precision(self.decimals) + self.custom_offset_entry.setObjectName('gdb_custom_offset') + + self.grid1.addWidget(self.custom_offset_label, 5, 0) + self.grid1.addWidget(self.custom_offset_entry, 5, 1) + + # V-Dia + self.vdia_label = QtWidgets.QLabel('%s:' % _("V-Dia")) + self.vdia_label.setToolTip( + _("V-Dia.\n" + "Diameter of the tip for V-Shape Tools.")) + + self.vdia_entry = FCDoubleSpinner() + self.vdia_entry.set_range(0.0000, 9999.9999) + self.vdia_entry.set_precision(self.decimals) + self.vdia_entry.setObjectName('gdb_vdia') + + self.grid1.addWidget(self.vdia_label, 7, 0) + self.grid1.addWidget(self.vdia_entry, 7, 1) + + # V-Angle + self.vangle_label = QtWidgets.QLabel('%s:' % _("V-Angle")) + self.vangle_label.setToolTip( + _("V-Agle.\n" + "Angle at the tip for the V-Shape Tools.")) + + self.vangle_entry = FCDoubleSpinner() + self.vangle_entry.set_range(-360.0, 360.0) + self.vangle_entry.set_precision(self.decimals) + self.vangle_entry.setObjectName('gdb_vangle') + + self.grid1.addWidget(self.vangle_label, 8, 0) + self.grid1.addWidget(self.vangle_entry, 8, 1) + + # Feedrate Rapids + self.frapids_label = QtWidgets.QLabel('%s:' % _("FR Rapids")) + self.frapids_label.setToolTip( + _("FR Rapids. Feedrate Rapids\n" + "Speed used while moving as fast as possible.\n" + "This is used only by some devices that can't use\n" + "the G0 g-code command. Mostly 3D printers.")) + + self.frapids_entry = FCDoubleSpinner() + self.frapids_entry.set_range(0.0000, 9999.9999) + self.frapids_entry.set_precision(self.decimals) + self.frapids_entry.setObjectName('gdb_frapids') + + self.grid1.addWidget(self.frapids_label, 10, 0) + self.grid1.addWidget(self.frapids_entry, 10, 1) + + # Extra Cut + self.ecut_label = QtWidgets.QLabel('%s:' % _("ExtraCut")) + self.ecut_label.setToolTip( + _("Extra Cut.\n" + "If checked, after a isolation is finished an extra cut\n" + "will be added where the start and end of isolation meet\n" + "such as that this point is covered by this extra cut to\n" + "ensure a complete isolation.")) + + self.ecut_cb = FCCheckBox() + self.ecut_cb.setObjectName('gdb_ecut') + + self.grid1.addWidget(self.ecut_label, 12, 0) + self.grid1.addWidget(self.ecut_cb, 12, 1) + + # Extra Cut Length + self.ecut_length_label = QtWidgets.QLabel('%s:' % _("E-Cut Length")) + self.ecut_length_label.setToolTip( + _("Extra Cut length.\n" + "If checked, after a isolation is finished an extra cut\n" + "will be added where the start and end of isolation meet\n" + "such as that this point is covered by this extra cut to\n" + "ensure a complete isolation. This is the length of\n" + "the extra cut.")) + + self.ecut_length_entry = FCDoubleSpinner() + self.ecut_length_entry.set_range(0.0000, 9999.9999) + self.ecut_length_entry.set_precision(self.decimals) + self.ecut_length_entry.setObjectName('gdb_ecut_length') + + self.grid1.addWidget(self.ecut_length_label, 13, 0) + self.grid1.addWidget(self.ecut_length_entry, 13, 1) + + # ########################################################################### + # ############### NCC UI form ############################################### + # ########################################################################### + + self.grid2 = QtWidgets.QGridLayout() + self.ncc_vlay.addLayout(self.grid2) + self.grid2.setColumnStretch(0, 0) + self.grid2.setColumnStretch(1, 1) + self.ncc_vlay.addStretch() + + # Operation + op_label = QtWidgets.QLabel('%s:' % _('Operation')) + op_label.setToolTip( + _("The 'Operation' can be:\n" + "- Isolation -> will ensure that the non-copper clearing is always complete.\n" + "If it's not successful then the non-copper clearing will fail, too.\n" + "- Clear -> the regular non-copper clearing.") + ) + + self.op_radio = RadioSet([ + {"label": _("Clear"), "value": "clear"}, + {"label": _("Isolation"), "value": "iso"} + ], orientation='horizontal', stretch=False) + self.op_radio.setObjectName("gdb_n_operation") + + self.grid2.addWidget(op_label, 13, 0) + self.grid2.addWidget(self.op_radio, 13, 1) + + # Milling Type Radio Button + self.milling_type_label = QtWidgets.QLabel('%s:' % _('Milling Type')) + self.milling_type_label.setToolTip( + _("Milling type when the selected tool is of type: 'iso_op':\n" + "- climb / best for precision milling and to reduce tool usage\n" + "- conventional / useful when there is no backlash compensation") + ) + + self.milling_type_radio = RadioSet([{'label': _('Climb'), 'value': 'cl'}, + {'label': _('Conventional'), 'value': 'cv'}]) + self.milling_type_radio.setToolTip( + _("Milling type when the selected tool is of type: 'iso_op':\n" + "- climb / best for precision milling and to reduce tool usage\n" + "- conventional / useful when there is no backlash compensation") + ) + self.milling_type_radio.setObjectName("gdb_n_milling_type") + + self.grid2.addWidget(self.milling_type_label, 14, 0) + self.grid2.addWidget(self.milling_type_radio, 14, 1) + + # Overlap Entry + nccoverlabel = QtWidgets.QLabel('%s:' % _('Overlap')) + nccoverlabel.setToolTip( + _("How much (percentage) of the tool width to overlap each tool pass.\n" + "Adjust the value starting with lower values\n" + "and increasing it if areas that should be cleared are still \n" + "not cleared.\n" + "Lower values = faster processing, faster execution on CNC.\n" + "Higher values = slow processing and slow execution on CNC\n" + "due of too many paths.") + ) + self.ncc_overlap_entry = FCDoubleSpinner(suffix='%') + self.ncc_overlap_entry.set_precision(self.decimals) + self.ncc_overlap_entry.setWrapping(True) + self.ncc_overlap_entry.setRange(0.000, 99.9999) + self.ncc_overlap_entry.setSingleStep(0.1) + self.ncc_overlap_entry.setObjectName("gdb_n_overlap") + + self.grid2.addWidget(nccoverlabel, 15, 0) + self.grid2.addWidget(self.ncc_overlap_entry, 15, 1) + + # Margin + nccmarginlabel = QtWidgets.QLabel('%s:' % _('Margin')) + nccmarginlabel.setToolTip( + _("Bounding box margin.") + ) + self.ncc_margin_entry = FCDoubleSpinner() + self.ncc_margin_entry.set_precision(self.decimals) + self.ncc_margin_entry.set_range(-9999.9999, 9999.9999) + self.ncc_margin_entry.setObjectName("gdb_n_margin") + + self.grid2.addWidget(nccmarginlabel, 16, 0) + self.grid2.addWidget(self.ncc_margin_entry, 16, 1) + + # Method + methodlabel = QtWidgets.QLabel('%s:' % _('Method')) + methodlabel.setToolTip( + _("Algorithm for copper clearing:\n" + "- Standard: Fixed step inwards.\n" + "- Seed-based: Outwards from seed.\n" + "- Line-based: Parallel lines.") + ) + + self.ncc_method_combo = FCComboBox() + self.ncc_method_combo.addItems( + [_("Standard"), _("Seed"), _("Lines")] + ) + self.ncc_method_combo.setObjectName("gdb_n_method") + + self.grid2.addWidget(methodlabel, 17, 0) + self.grid2.addWidget(self.ncc_method_combo, 17, 1) + + # Connect lines + self.ncc_connect_cb = FCCheckBox('%s' % _("Connect")) + self.ncc_connect_cb.setObjectName("gdb_n_connect") + + self.ncc_connect_cb.setToolTip( + _("Draw lines between resulting\n" + "segments to minimize tool lifts.") + ) + self.grid2.addWidget(self.ncc_connect_cb, 18, 0) + + # Contour + self.ncc_contour_cb = FCCheckBox('%s' % _("Contour")) + self.ncc_contour_cb.setObjectName("gdb_n_contour") + + self.ncc_contour_cb.setToolTip( + _("Cut around the perimeter of the polygon\n" + "to trim rough edges.") + ) + self.grid2.addWidget(self.ncc_contour_cb, 18, 1) + + # ## NCC Offset choice + self.ncc_choice_offset_cb = FCCheckBox('%s' % _("Offset")) + self.ncc_choice_offset_cb.setObjectName("gdb_n_offset") + + self.ncc_choice_offset_cb.setToolTip( + _("If used, it will add an offset to the copper features.\n" + "The copper clearing will finish to a distance\n" + "from the copper features.\n" + "The value can be between 0 and 10 FlatCAM units.") + ) + self.grid2.addWidget(self.ncc_choice_offset_cb, 19, 0) + + # ## NCC Offset Entry + self.ncc_offset_spinner = FCDoubleSpinner() + self.ncc_offset_spinner.set_range(0.00, 10.00) + self.ncc_offset_spinner.set_precision(4) + self.ncc_offset_spinner.setWrapping(True) + self.ncc_offset_spinner.setObjectName("gdb_n_offset_value") + + units = self.app.defaults['units'].upper() + if units == 'MM': + self.ncc_offset_spinner.setSingleStep(0.1) + else: + self.ncc_offset_spinner.setSingleStep(0.01) + + self.grid2.addWidget(self.ncc_offset_spinner, 19, 1) + + # ########################################################################### + # ############### Paint UI form ############################################# + # ########################################################################### + + self.grid3 = QtWidgets.QGridLayout() + self.paint_vlay.addLayout(self.grid3) + self.grid3.setColumnStretch(0, 0) + self.grid3.setColumnStretch(1, 1) + self.paint_vlay.addStretch() + + # Overlap + ovlabel = QtWidgets.QLabel('%s:' % _('Overlap')) + ovlabel.setToolTip( + _("How much (percentage) of the tool width to overlap each tool pass.\n" + "Adjust the value starting with lower values\n" + "and increasing it if areas that should be painted are still \n" + "not painted.\n" + "Lower values = faster processing, faster execution on CNC.\n" + "Higher values = slow processing and slow execution on CNC\n" + "due of too many paths.") + ) + self.paintoverlap_entry = FCDoubleSpinner(suffix='%') + self.paintoverlap_entry.set_precision(3) + self.paintoverlap_entry.setWrapping(True) + self.paintoverlap_entry.setRange(0.0000, 99.9999) + self.paintoverlap_entry.setSingleStep(0.1) + self.paintoverlap_entry.setObjectName('gdb_p_overlap') + + self.grid3.addWidget(ovlabel, 1, 0) + self.grid3.addWidget(self.paintoverlap_entry, 1, 1) + + # Margin + marginlabel = QtWidgets.QLabel('%s:' % _('Margin')) + marginlabel.setToolTip( + _("Distance by which to avoid\n" + "the edges of the polygon to\n" + "be painted.") + ) + self.paintmargin_entry = FCDoubleSpinner() + self.paintmargin_entry.set_precision(self.decimals) + self.paintmargin_entry.set_range(-9999.9999, 9999.9999) + self.paintmargin_entry.setObjectName('gdb_p_margin') + + self.grid3.addWidget(marginlabel, 2, 0) + self.grid3.addWidget(self.paintmargin_entry, 2, 1) + + # Method + methodlabel = QtWidgets.QLabel('%s:' % _('Method')) + methodlabel.setToolTip( + _("Algorithm for painting:\n" + "- Standard: Fixed step inwards.\n" + "- Seed-based: Outwards from seed.\n" + "- Line-based: Parallel lines.\n" + "- Laser-lines: Active only for Gerber objects.\n" + "Will create lines that follow the traces.\n" + "- Combo: In case of failure a new method will be picked from the above\n" + "in the order specified.") + ) + + self.paintmethod_combo = FCComboBox() + self.paintmethod_combo.addItems( + [_("Standard"), _("Seed"), _("Lines"), _("Laser_lines"), _("Combo")] + ) + idx = self.paintmethod_combo.findText(_("Laser_lines")) + self.paintmethod_combo.model().item(idx).setEnabled(False) + + self.paintmethod_combo.setObjectName('gdb_p_method') + + self.grid3.addWidget(methodlabel, 7, 0) + self.grid3.addWidget(self.paintmethod_combo, 7, 1) + + # Connect lines + self.pathconnect_cb = FCCheckBox('%s' % _("Connect")) + self.pathconnect_cb.setObjectName('gdb_p_connect') + self.pathconnect_cb.setToolTip( + _("Draw lines between resulting\n" + "segments to minimize tool lifts.") + ) + + self.paintcontour_cb = FCCheckBox('%s' % _("Contour")) + self.paintcontour_cb.setObjectName('gdb_p_contour') + self.paintcontour_cb.setToolTip( + _("Cut around the perimeter of the polygon\n" + "to trim rough edges.") + ) + + self.grid3.addWidget(self.pathconnect_cb, 10, 0) + self.grid3.addWidget(self.paintcontour_cb, 10, 1) + + # #################################################################### + # #################################################################### + # GUI for the lower part of the window + # #################################################################### + # #################################################################### + + new_vlay = QtWidgets.QVBoxLayout() + grid_layout.addLayout(new_vlay, 1, 0, 1, 2) + + self.buttons_frame = QtWidgets.QFrame() + self.buttons_frame.setContentsMargins(0, 0, 0, 0) + new_vlay.addWidget(self.buttons_frame) + self.buttons_box = QtWidgets.QHBoxLayout() + self.buttons_box.setContentsMargins(0, 0, 0, 0) + self.buttons_frame.setLayout(self.buttons_box) + self.buttons_frame.show() + + add_entry_btn = FCButton(_("Add Tool in DB")) + add_entry_btn.setToolTip( + _("Add a new tool in the Tools Database.\n" + "It will be used in the Geometry UI.\n" + "You can edit it after it is added.") + ) + self.buttons_box.addWidget(add_entry_btn) + + # add_fct_entry_btn = FCButton(_("Add Paint/NCC Tool in DB")) + # add_fct_entry_btn.setToolTip( + # _("Add a new tool in the Tools Database.\n" + # "It will be used in the Paint/NCC Tools UI.\n" + # "You can edit it after it is added.") + # ) + # self.buttons_box.addWidget(add_fct_entry_btn) + + remove_entry_btn = FCButton(_("Delete Tool from DB")) + remove_entry_btn.setToolTip( + _("Remove a selection of tools in the Tools Database.") + ) + self.buttons_box.addWidget(remove_entry_btn) + + export_db_btn = FCButton(_("Export DB")) + export_db_btn.setToolTip( + _("Save the Tools Database to a custom text file.") + ) + self.buttons_box.addWidget(export_db_btn) + + import_db_btn = FCButton(_("Import DB")) + import_db_btn.setToolTip( + _("Load the Tools Database information's from a custom text file.") + ) + self.buttons_box.addWidget(import_db_btn) + + self.add_tool_from_db = FCButton(_("Add Tool from Tools DB")) + self.add_tool_from_db.setToolTip( + _("Add a new tool in the Tools Table of the\n" + "active Geometry object after selecting a tool\n" + "in the Tools Database.") + ) + self.add_tool_from_db.hide() + + self.cancel_tool_from_db = FCButton(_("Cancel")) + self.cancel_tool_from_db.hide() + + hlay = QtWidgets.QHBoxLayout() + tree_layout.addLayout(hlay) + hlay.addWidget(self.add_tool_from_db) + hlay.addWidget(self.cancel_tool_from_db) + hlay.addStretch() + + # ############################################################################## + # ############################################################################## + # ########## SETUP THE DICTIONARIES THAT HOLD THE WIDGETS ##################### + # ############################################################################## + # ############################################################################## + + self.form_fields = { + # Basic + "name": self.name_entry, + "tooldia": self.dia_entry, + "tool_type": self.shape_combo, + "cutz": self.cutz_entry, + "multidepth": self.multidepth_cb, + "depthperpass": self.multidepth_entry, + "travelz": self.travelz_entry, + "feedrate": self.frxy_entry, + "feedrate_z": self.frz_entry, + "spindlespeed": self.spindle_entry, + "dwell": self.dwell_cb, + "dwelltime": self.dwelltime_entry, + + # Advanced + "type": self.type_combo, + "offset": self.tooloffset_combo, + "offset_value": self.custom_offset_entry, + "vtipdia": self.vdia_entry, + "vtipangle": self.vangle_entry, + "feedrate_rapid": self.frapids_entry, + "extracut": self.ecut_cb, + "extracut_length": self.ecut_length_entry, + + # NCC + "tools_nccoperation": self.op_radio, + "tools_nccmilling_type": self.milling_type_radio, + "tools_nccoverlap": self.ncc_overlap_entry, + "tools_nccmargin": self.ncc_margin_entry, + "tools_nccmethod": self.ncc_method_combo, + "tools_nccconnect": self.ncc_connect_cb, + "tools_ncccontour": self.ncc_contour_cb, + "tools_ncc_offset_choice": self.ncc_choice_offset_cb, + "tools_ncc_offset_value": self.ncc_offset_spinner, + + # Paint + "tools_paintoverlap": self.paintoverlap_entry, + "tools_paintmargin": self.paintmargin_entry, + "tools_paintmethod": self.paintmethod_combo, + "tools_pathconnect": self.pathconnect_cb, + "tools_paintcontour": self.paintcontour_cb, + } + + self.name2option = { + # Basic + "gdb_name": "name", + "gdb_dia": "tooldia", + "gdb_shape": "tool_type", + "gdb_cutz": "cutz", + "gdb_multidepth": "multidepth", + "gdb_multidepth_entry": "depthperpass", + "gdb_travel": "travelz", + "gdb_frxy": "feedrate", + "gdb_frz": "feedrate_z", + "gdb_spindle": "spindlespeed", + "gdb_dwell": "dwell", + "gdb_dwelltime": "dwelltime", + + # Advanced + "gdb_type": "type", + "gdb_tool_offset": "offset", + "gdb_custom_offset": "offset_value", + "gdb_vdia": "vtipdia", + "gdb_vangle": "vtipangle", + "gdb_frapids": "feedrate_rapid", + "gdb_ecut": "extracut", + "gdb_ecut_length": "extracut_length", + + # NCC + "gdb_n_operation": "tools_nccoperation", + "gdb_n_overlap": "tools_nccoverlap", + "gdb_n_margin": "tools_nccmargin", + "gdb_n_method": "tools_nccmethod", + "gdb_n_connect": "tools_nccconnect", + "gdb_n_contour": "tools_ncccontour", + "gdb_n_offset": "tools_ncc_offset_choice", + "gdb_n_offset_value": "tools_ncc_offset_value", + "gdb_n_milling_type": "tools_nccmilling_type", + + # Paint + 'gdb_p_overlap': "tools_paintoverlap", + 'gdb_p_margin': "tools_paintmargin", + 'gdb_p_method': "tools_paintmethod", + 'gdb_p_connect': "tools_pathconnect", + 'gdb_p_contour': "tools_paintcontour", + } + + self.current_toolid = None + + # variable to show if double clicking and item will trigger adding a tool from DB + self.ok_to_add = False + + # ############################################################################## + # ######################## SIGNALS ############################################# + # ############################################################################## + + add_entry_btn.clicked.connect(self.on_tool_add) + remove_entry_btn.clicked.connect(self.on_tool_delete) + export_db_btn.clicked.connect(self.on_export_tools_db_file) + import_db_btn.clicked.connect(self.on_import_tools_db_file) + # closebtn.clicked.connect(self.accept) + + self.add_tool_from_db.clicked.connect(self.on_tool_requested_from_app) + self.cancel_tool_from_db.clicked.connect(self.on_cancel_tool) + + # self.tree_widget.selectionModel().selectionChanged.connect(self.on_list_selection_change) + self.tree_widget.currentItemChanged.connect(self.on_list_selection_change) + self.tree_widget.itemChanged.connect(self.on_list_item_edited) + self.tree_widget.customContextMenuRequested.connect(self.on_menu_request) + + self.tree_widget.itemDoubleClicked.connect(self.on_item_double_clicked) + + self.setup_db_ui() + + def on_menu_request(self, pos): + + menu = QtWidgets.QMenu() + add_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/plus16.png'), _("Add to DB")) + add_tool.triggered.connect(self.on_tool_add) + + copy_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/copy16.png'), _("Copy from DB")) + copy_tool.triggered.connect(self.on_tool_copy) + + delete_tool = menu.addAction(QtGui.QIcon(self.app.resource_location + '/delete32.png'), _("Delete from DB")) + delete_tool.triggered.connect(self.on_tool_delete) + + # tree_item = self.tree_widget.itemAt(pos) + menu.exec(self.tree_widget.viewport().mapToGlobal(pos)) + + def on_item_double_clicked(self, item, column): + if column == 0 and self.ok_to_add is True: + self.ok_to_add = False + self.on_tool_requested_from_app() + + def on_list_selection_change(self, current, previous): + # for idx in current.indexes(): + # print(idx.data()) + # print(current.text(0)) + self.current_toolid = int(current.text(0)) + + self.storage_to_form(self.db_tool_dict[current.text(0)]) + + def on_list_item_edited(self, item, column): + if column == 0: + return + + self.name_entry.set_value(item.text(1)) + + def storage_to_form(self, dict_storage): + for form_key in self.form_fields: + for storage_key in dict_storage: + if form_key == storage_key: + try: + self.form_fields[form_key].set_value(dict_storage[form_key]) + except Exception as e: + print(str(e)) + if storage_key == 'data': + for data_key in dict_storage[storage_key]: + if form_key == data_key: + try: + self.form_fields[form_key].set_value(dict_storage['data'][data_key]) + except Exception as e: + print(str(e)) + + def form_to_storage(self, tool): + self.blockSignals(True) + + widget_changed = self.sender() + wdg_objname = widget_changed.objectName() + option_changed = self.name2option[wdg_objname] + + tooluid_item = int(tool) + + for tooluid_key, tooluid_val in self.db_tool_dict.items(): + if int(tooluid_key) == tooluid_item: + new_option_value = self.form_fields[option_changed].get_value() + if option_changed in tooluid_val: + tooluid_val[option_changed] = new_option_value + if option_changed in tooluid_val['data']: + tooluid_val['data'][option_changed] = new_option_value + + self.blockSignals(False) + + def setup_db_ui(self): + filename = self.app.data_path + '/geo_tools_db.FlatDB' + + # load the database tools from the file + try: + with open(filename) as f: + tools = f.read() + except IOError: + self.app.log.error("Could not load tools DB file.") + self.app.inform.emit('[ERROR] %s' % _("Could not load Tools DB file.")) + return + + try: + self.db_tool_dict = json.loads(tools) + except Exception: + e = sys.exc_info()[0] + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) + return + + self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) + + self.build_db_ui() + + def build_db_ui(self): + self.ui_disconnect() + nr_crt = 0 + + parent = self.tree_widget + self.tree_widget.blockSignals(True) + self.tree_widget.clear() + self.tree_widget.blockSignals(False) + + for toolid, dict_val in self.db_tool_dict.items(): + row = nr_crt + nr_crt += 1 + + t_name = dict_val['name'] + try: + # self.add_tool_table_line(row, name=t_name, tooldict=dict_val) + self.tree_widget.blockSignals(True) + try: + self.tree_widget.addParentEditable(parent=parent, title=[str(row+1), t_name], editable=True) + except Exception as e: + print('FlatCAMCoomn.ToolDB2.build_db_ui() -> ', str(e)) + self.tree_widget.blockSignals(False) + except Exception as e: + self.app.log.debug("ToolDB.build_db_ui.add_tool_table_line() --> %s" % str(e)) + + if self.current_toolid is None or self.current_toolid < 1: + if self.db_tool_dict: + self.storage_to_form(self.db_tool_dict['1']) + + # Enable GUI + self.basic_box.setEnabled(True) + self.advanced_box.setEnabled(True) + self.ncc_box.setEnabled(True) + self.paint_box.setEnabled(True) + + self.tree_widget.setCurrentItem(self.tree_widget.topLevelItem(0)) + # self.tree_widget.setFocus() + + else: + # Disable GUI + self.basic_box.setEnabled(False) + self.advanced_box.setEnabled(False) + self.ncc_box.setEnabled(False) + self.paint_box.setEnabled(False) + else: + self.storage_to_form(self.db_tool_dict[str(self.current_toolid)]) + + self.ui_connect() + + def on_tool_add(self): + """ + Add a tool in the DB Tool Table + :return: None + """ + + default_data = {} + default_data.update({ + "plot": True, + "cutz": float(self.app.defaults["geometry_cutz"]), + "multidepth": self.app.defaults["geometry_multidepth"], + "depthperpass": float(self.app.defaults["geometry_depthperpass"]), + "vtipdia": float(self.app.defaults["geometry_vtipdia"]), + "vtipangle": float(self.app.defaults["geometry_vtipangle"]), + "travelz": float(self.app.defaults["geometry_travelz"]), + "feedrate": float(self.app.defaults["geometry_feedrate"]), + "feedrate_z": float(self.app.defaults["geometry_feedrate_z"]), + "feedrate_rapid": float(self.app.defaults["geometry_feedrate_rapid"]), + "spindlespeed": self.app.defaults["geometry_spindlespeed"], + "dwell": self.app.defaults["geometry_dwell"], + "dwelltime": float(self.app.defaults["geometry_dwelltime"]), + "ppname_g": self.app.defaults["geometry_ppname_g"], + "extracut": self.app.defaults["geometry_extracut"], + "extracut_length": float(self.app.defaults["geometry_extracut_length"]), + "toolchange": self.app.defaults["geometry_toolchange"], + "toolchangexy": self.app.defaults["geometry_toolchangexy"], + "toolchangez": float(self.app.defaults["geometry_toolchangez"]), + "startz": self.app.defaults["geometry_startz"], + "endz": float(self.app.defaults["geometry_endz"]), + + # NCC + "tools_nccoperation": self.app.defaults["tools_nccoperation"], + "tools_nccmilling_type": self.app.defaults["tools_nccmilling_type"], + "tools_nccoverlap": float(self.app.defaults["tools_nccoverlap"]), + "tools_nccmargin": float(self.app.defaults["tools_nccmargin"]), + "tools_nccmethod": self.app.defaults["tools_nccmethod"], + "tools_nccconnect": self.app.defaults["tools_nccconnect"], + "tools_ncccontour": self.app.defaults["tools_ncccontour"], + "tools_ncc_offset_choice": self.app.defaults["tools_ncc_offset_choice"], + "tools_ncc_offset_value": float(self.app.defaults["tools_ncc_offset_value"]), + + # Paint + "tools_paintoverlap": float(self.app.defaults["tools_paintoverlap"]), + "tools_paintmargin": float(self.app.defaults["tools_paintmargin"]), + "tools_paintmethod": self.app.defaults["tools_paintmethod"], + "tools_pathconnect": self.app.defaults["tools_pathconnect"], + "tools_paintcontour": self.app.defaults["tools_paintcontour"], + }) + + dict_elem = {} + dict_elem['name'] = 'new_tool' + if type(self.app.defaults["geometry_cnctooldia"]) == float: + dict_elem['tooldia'] = self.app.defaults["geometry_cnctooldia"] + else: + try: + tools_string = self.app.defaults["geometry_cnctooldia"].split(",") + tools_diameters = [eval(a) for a in tools_string if a != ''] + dict_elem['tooldia'] = tools_diameters[0] if tools_diameters else 0.0 + except Exception as e: + self.app.log.debug("ToolDB.on_tool_add() --> %s" % str(e)) + return + + dict_elem['offset'] = 'Path' + dict_elem['offset_value'] = 0.0 + dict_elem['type'] = 'Rough' + dict_elem['tool_type'] = 'C1' + dict_elem['data'] = default_data + + new_toolid = len(self.db_tool_dict) + 1 + self.db_tool_dict[str(new_toolid)] = deepcopy(dict_elem) + + # add the new entry to the Tools DB table + self.update_storage() + self.build_db_ui() + self.app.inform.emit('[success] %s' % _("Tool added to DB.")) + + def on_tool_copy(self): + """ + Copy a selection of Tools in the Tools DB table + :return: + """ + new_tool_id = len(self.db_tool_dict) + for item in self.tree_widget.selectedItems(): + old_tool_id = item.data(0, QtCore.Qt.DisplayRole) + + for toolid, dict_val in list(self.db_tool_dict.items()): + if int(old_tool_id) == int(toolid): + new_tool_id += 1 + new_key = str(new_tool_id) + + self.db_tool_dict.update({ + new_key: deepcopy(dict_val) + }) + + self.current_toolid = new_tool_id + + self.update_storage() + self.build_db_ui() + self.app.inform.emit('[success] %s' % _("Tool copied from Tools DB.")) + + def on_tool_delete(self): + """ + Delete a selection of Tools in the Tools DB table + :return: + """ + for item in self.tree_widget.selectedItems(): + toolname_to_remove = item.data(0, QtCore.Qt.DisplayRole) + + for toolid, dict_val in list(self.db_tool_dict.items()): + if int(toolname_to_remove) == int(toolid): + # remove from the storage + self.db_tool_dict.pop(toolid, None) + + self.current_toolid -= 1 + + self.update_storage() + self.build_db_ui() + self.app.inform.emit('[success] %s' % _("Tool removed from Tools DB.")) + + def on_export_tools_db_file(self): + self.app.defaults.report_usage("on_export_tools_db_file") + self.app.log.debug("on_export_tools_db_file()") + + date = str(datetime.today()).rpartition('.')[0] + date = ''.join(c for c in date if c not in ':-') + date = date.replace(' ', '_') + + filter__ = "Text File (*.TXT);;All Files (*.*)" + filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Tools Database"), + directory='{l_save}/FlatCAM_{n}_{date}'.format( + l_save=str(self.app.get_last_save_folder()), + n=_("Tools_Database"), + date=date), + filter=filter__) + + filename = str(filename) + + if filename == "": + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + return + else: + try: + f = open(filename, 'w') + f.close() + except PermissionError: + self.app.inform.emit('[WARNING] %s' % + _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.")) + return + except IOError: + self.app.log.debug('Creating a new Tools DB file ...') + f = open(filename, 'w') + f.close() + except Exception: + e = sys.exc_info()[0] + self.app.log.error("Could not load Tools DB file.") + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) + return + + # Save update options + try: + # Save Tools DB in a file + try: + with open(filename, "w") as f: + json.dump(self.db_tool_dict, f, default=to_dict, indent=2) + except Exception as e: + self.app.log.debug("App.on_save_tools_db() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) + return + except Exception: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) + return + + self.app.inform.emit('[success] %s: %s' % (_("Exported Tools DB to"), filename)) + + def on_import_tools_db_file(self): + self.app.defaults.report_usage("on_import_tools_db_file") + self.app.log.debug("on_import_tools_db_file()") + + filter__ = "Text File (*.TXT);;All Files (*.*)" + filename, _f = QtWidgets.QFileDialog.getOpenFileName(caption=_("Import FlatCAM Tools DB"), filter=filter__) + + if filename == "": + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled.")) + else: + try: + with open(filename) as f: + tools_in_db = f.read() + except IOError: + self.app.log.error("Could not load Tools DB file.") + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not load Tools DB file.")) + return + + try: + self.db_tool_dict = json.loads(tools_in_db) + except Exception: + e = sys.exc_info()[0] + self.app.log.error(str(e)) + self.app.inform.emit('[ERROR] %s' % _("Failed to parse Tools DB file.")) + return + + self.app.inform.emit('[success] %s: %s' % (_("Loaded FlatCAM Tools DB from"), filename)) + self.build_db_ui() + self.update_storage() + + def on_save_tools_db(self, silent=False): + self.app.log.debug("ToolsDB.on_save_button() --> Saving Tools Database to file.") + + filename = self.app.data_path + "/geo_tools_db.FlatDB" + + # Preferences save, update the color of the Tools DB Tab text + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): + self.app.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) + + # Save Tools DB in a file + try: + f = open(filename, "w") + json.dump(self.db_tool_dict, f, default=to_dict, indent=2) + f.close() + except Exception as e: + self.app.log.debug("ToolsDB.on_save_tools_db() --> %s" % str(e)) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to write Tools DB to file.")) + return + + if not silent: + self.app.inform.emit('[success] %s' % _("Saved Tools DB.")) + + def ui_connect(self): + # make sure that we don't make multiple connections to the widgets + self.ui_disconnect() + + self.name_entry.editingFinished.connect(self.update_tree_name) + + for key in self.form_fields: + wdg = self.form_fields[key] + + # FCEntry + if isinstance(wdg, FCEntry): + wdg.textChanged.connect(self.update_storage) + + # ComboBox + if isinstance(wdg, FCComboBox): + wdg.currentIndexChanged.connect(self.update_storage) + + # CheckBox + if isinstance(wdg, FCCheckBox): + wdg.toggled.connect(self.update_storage) + + # FCRadio + if isinstance(wdg, RadioSet): + wdg.activated_custom.connect(self.update_storage) + + # SpinBox, DoubleSpinBox + if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner): + wdg.valueChanged.connect(self.update_storage) + + def ui_disconnect(self): + try: + self.name_entry.editingFinished.disconnect(self.update_tree_name) + except (TypeError, AttributeError): + pass + + for key in self.form_fields: + wdg = self.form_fields[key] + + # FCEntry + if isinstance(wdg, FCEntry): + try: + wdg.textChanged.disconnect(self.update_storage) + except (TypeError, AttributeError): + pass + + # ComboBox + if isinstance(wdg, FCComboBox): + try: + wdg.currentIndexChanged.disconnect(self.update_storage) + except (TypeError, AttributeError): + pass + + # CheckBox + if isinstance(wdg, FCCheckBox): + try: + wdg.toggled.disconnect(self.update_storage) + except (TypeError, AttributeError): + pass + + # FCRadio + if isinstance(wdg, RadioSet): + try: + wdg.activated_custom.disconnect(self.update_storage) + except (TypeError, AttributeError): + pass + + # SpinBox, DoubleSpinBox + if isinstance(wdg, FCSpinner) or isinstance(wdg, FCDoubleSpinner): + try: + wdg.valueChanged.disconnect(self.update_storage) + except (TypeError, AttributeError): + pass + + def update_tree_name(self): + val = self.name_entry.get_value() + + item = self.tree_widget.currentItem() + # I'm setting the value for the second column (designated by 1) because first column holds the ID + # and second column holds the Name (this behavior is set in the build_ui method) + item.setData(1, QtCore.Qt.DisplayRole, val) + + def update_storage(self): + """ + Update the dictionary that is the storage of the tools 'database' + :return: + """ + tool_id = str(self.current_toolid) + + try: + wdg = self.sender() + + assert isinstance(wdg, QtWidgets.QWidget), "Expected a QWidget got %s" % type(wdg) + + if wdg is None: + return + + wdg_name = wdg.objectName() + val = wdg.get_value() + except AttributeError: + return + + if wdg_name == "gdb_name": + self.db_tool_dict[tool_id]['name'] = val + elif wdg_name == "gdb_dia": + self.db_tool_dict[tool_id]['tooldia'] = val + elif wdg_name == "gdb_tool_offset": + self.db_tool_dict[tool_id]['offset'] = val + elif wdg_name == "gdb_custom_offset": + self.db_tool_dict[tool_id]['offset_value'] = val + elif wdg_name == "gdb_type": + self.db_tool_dict[tool_id]['type'] = val + elif wdg_name == "gdb_shape": + self.db_tool_dict[tool_id]['tool_type'] = val + else: + if wdg_name == "gdb_cutz": + self.db_tool_dict[tool_id]['data']['cutz'] = val + elif wdg_name == "gdb_multidepth": + self.db_tool_dict[tool_id]['data']['multidepth'] = val + elif wdg_name == "gdb_multidepth_entry": + self.db_tool_dict[tool_id]['data']['depthperpass'] = val + + elif wdg_name == "gdb_travel": + self.db_tool_dict[tool_id]['data']['travelz'] = val + elif wdg_name == "gdb_frxy": + self.db_tool_dict[tool_id]['data']['feedrate'] = val + elif wdg_name == "gdb_frz": + self.db_tool_dict[tool_id]['data']['feedrate_z'] = val + elif wdg_name == "gdb_spindle": + self.db_tool_dict[tool_id]['data']['spindlespeed'] = val + elif wdg_name == "gdb_dwell": + self.db_tool_dict[tool_id]['data']['dwell'] = val + elif wdg_name == "gdb_dwelltime": + self.db_tool_dict[tool_id]['data']['dwelltime'] = val + + elif wdg_name == "gdb_vdia": + self.db_tool_dict[tool_id]['data']['vtipdia'] = val + elif wdg_name == "gdb_vangle": + self.db_tool_dict[tool_id]['data']['vtipangle'] = val + elif wdg_name == "gdb_frapids": + self.db_tool_dict[tool_id]['data']['feedrate_rapid'] = val + elif wdg_name == "gdb_ecut": + self.db_tool_dict[tool_id]['data']['extracut'] = val + elif wdg_name == "gdb_ecut_length": + self.db_tool_dict[tool_id]['data']['extracut_length'] = val + + # NCC Tool + elif wdg_name == "gdb_n_operation": + self.db_tool_dict[tool_id]['data']['tools_nccoperation'] = val + elif wdg_name == "gdb_n_overlap": + self.db_tool_dict[tool_id]['data']['tools_nccoverlap'] = val + elif wdg_name == "gdb_n_margin": + self.db_tool_dict[tool_id]['data']['tools_nccmargin'] = val + elif wdg_name == "gdb_n_method": + self.db_tool_dict[tool_id]['data']['tools_nccmethod'] = val + elif wdg_name == "gdb_n_connect": + self.db_tool_dict[tool_id]['data']['tools_nccconnect'] = val + elif wdg_name == "gdb_n_contour": + self.db_tool_dict[tool_id]['data']['tools_ncccontour'] = val + elif wdg_name == "gdb_n_offset": + self.db_tool_dict[tool_id]['data']['tools_ncc_offset_choice'] = val + elif wdg_name == "gdb_n_offset_value": + self.db_tool_dict[tool_id]['data']['tools_ncc_offset_value'] = val + elif wdg_name == "gdb_n_milling_type": + self.db_tool_dict[tool_id]['data']['tools_nccmilling_type'] = val + + # Paint Tool + elif wdg_name == "gdb_p_overlap": + self.db_tool_dict[tool_id]['data']['tools_paintoverlap'] = val + elif wdg_name == "gdb_p_margin": + self.db_tool_dict[tool_id]['data']['tools_paintmargin'] = val + elif wdg_name == "gdb_p_method": + self.db_tool_dict[tool_id]['data']['tools_paintmethod'] = val + elif wdg_name == "gdb_p_connect": + self.db_tool_dict[tool_id]['data']['tools_pathconnect'] = val + elif wdg_name == "gdb_p_contour": + self.db_tool_dict[tool_id]['data']['tools_paintcontour'] = val + + self.callback_app() + + def on_tool_requested_from_app(self): + if not self.tree_widget.selectedItems(): + self.app.inform.emit('[WARNING_NOTCL] %s...' % _("No Tool/row selected in the Tools Database table")) + return + + for item in self.tree_widget.selectedItems(): + tool_uid = item.data(0, QtCore.Qt.DisplayRole) + + for key in self.db_tool_dict.keys(): + if str(key) == str(tool_uid): + selected_tool = self.db_tool_dict[key] + self.on_tool_request(tool=selected_tool) + + def on_cancel_tool(self): + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): + wdg = self.app.ui.plot_tab_area.widget(idx) + wdg.deleteLater() + self.app.ui.plot_tab_area.removeTab(idx) + self.app.inform.emit('%s' % _("Cancelled adding tool from DB.")) + + # def resize_new_tool_table_widget(self, min_size, max_size): + # """ + # Resize the table widget responsible for adding new tool in the Tool Database + # + # :param min_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() + # :param max_size: passed by rangeChanged signal or the self.new_tool_table_widget.horizontalScrollBar() + # :return: + # """ + # t_height = self.t_height + # if max_size > min_size: + # t_height = self.t_height + self.new_tool_table_widget.verticalScrollBar().height() + # + # self.new_tool_table_widget.setMaximumHeight(t_height) + + def closeEvent(self, QCloseEvent): + super().closeEvent(QCloseEvent) diff --git a/FlatCAMObj.py b/FlatCAMObj.py deleted file mode 100644 index d23ea62b..00000000 --- a/FlatCAMObj.py +++ /dev/null @@ -1,8244 +0,0 @@ -# ########################################################## -# FlatCAM: 2D Post-processing for Manufacturing # -# http://flatcam.org # -# Author: Juan Pablo Caram (c) # -# Date: 2/5/2014 # -# MIT Licence # -# ########################################################## - -# ########################################################## -# File modified by: Marius Stanciu # -# ########################################################## - - -from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing -from shapely.ops import cascaded_union -import shapely.affinity as affinity - -from copy import deepcopy -# from copy import copy - -from io import StringIO -import traceback -import inspect # TODO: For debugging only. -from datetime import datetime - -from flatcamEditors.FlatCAMTextEditor import TextEditor -from flatcamGUI.ObjectUI import * -from flatcamGUI.GUIElements import FCFileSaveDialog -from FlatCAMCommon import LoudDict -from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy -from flatcamParsers.ParseExcellon import Excellon -from flatcamParsers.ParseGerber import Gerber -from camlib import Geometry, CNCjob -import FlatCAMApp - -# from flatcamGUI.VisPyVisuals import ShapeCollection - -import tkinter as tk -import os -import sys -import itertools -import ezdxf - -import math -import numpy as np - -import gettext -import FlatCAMTranslation as fcTranslate -import builtins - -fcTranslate.apply_language('strings') -if '_' not in builtins.__dict__: - _ = gettext.gettext - - -# Interrupts plotting process if FlatCAMObj has been deleted -class ObjectDeleted(Exception): - pass - - -class ValidationError(Exception): - def __init__(self, message, errors): - super().__init__(message) - - self.errors = errors - - -# ####################################### -# # FlatCAMObj ## -# ####################################### - - -class FlatCAMObj(QtCore.QObject): - """ - Base type of objects handled in FlatCAM. These become interactive - in the GUI, can be plotted, and their options can be modified - by the user in their respective forms. - """ - - # Instance of the application to which these are related. - # The app should set this value. - app = None - - # signal to plot a single object - plot_single_object = QtCore.pyqtSignal() - - def __init__(self, name): - """ - Constructor. - - :param name: Name of the object given by the user. - :return: FlatCAMObj - """ - - QtCore.QObject.__init__(self) - - # View - self.ui = None - - self.options = LoudDict(name=name) - self.options.set_change_callback(self.on_options_change) - - self.form_fields = {} - - # store here the default data for Geometry Data - self.default_data = {} - - # 2D mode - # Axes must exist and be attached to canvas. - self.axes = None - self.kind = None # Override with proper name - - if self.app.is_legacy is False: - self.shapes = self.app.plotcanvas.new_shape_group() - # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, pool=self.app.pool, layers=2) - else: - self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name) - - self.mark_shapes = {} - - self.item = None # Link with project view item - - self.muted_ui = False - self.deleted = False - - try: - self._drawing_tolerance = float(self.app.defaults["global_tolerance"]) if \ - self.app.defaults["global_tolerance"] else 0.01 - except ValueError: - self._drawing_tolerance = 0.01 - - self.isHovering = False - self.notHovering = True - - # Flag to show if a selection shape is drawn - self.selection_shape_drawn = False - - # self.units = 'IN' - self.units = self.app.defaults['units'] - - self.plot_single_object.connect(self.single_object_plot) - - def __del__(self): - pass - - def __str__(self): - return "".format(self.kind, self.options["name"]) - - def from_dict(self, d): - """ - This supersedes ``from_dict`` in derived classes. Derived classes - must inherit from FlatCAMObj first, then from derivatives of Geometry. - - ``self.options`` is only updated, not overwritten. This ensures that - options set by the app do not vanish when reading the objects - from a project file. - - :param d: Dictionary with attributes to set. - :return: None - """ - - for attr in self.ser_attrs: - - if attr == 'options': - self.options.update(d[attr]) - else: - try: - setattr(self, attr, d[attr]) - except KeyError: - log.debug("FlatCAMObj.from_dict() --> KeyError: %s. " - "Means that we are loading an old project that don't" - "have all attributes in the latest FlatCAM." % str(attr)) - pass - - def on_options_change(self, key): - # Update form on programmatically options change - self.set_form_item(key) - - # Set object visibility - if key == 'plot': - self.visible = self.options['plot'] - - self.optionChanged.emit(key) - - def set_ui(self, ui): - self.ui = ui - - self.form_fields = {"name": self.ui.name_entry} - - assert isinstance(self.ui, ObjectUI) - self.ui.name_entry.returnPressed.connect(self.on_name_activate) - - try: - # it will raise an exception for those FlatCAM objects that do not build UI with the common elements - self.ui.offset_button.clicked.connect(self.on_offset_button_click) - except (TypeError, AttributeError): - pass - - try: - self.ui.scale_button.clicked.connect(self.on_scale_button_click) - except (TypeError, AttributeError): - pass - - try: - self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click) - except (TypeError, AttributeError): - pass - - # Creates problems on focusOut - try: - self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) - except (TypeError, AttributeError): - pass - - # self.ui.skew_button.clicked.connect(self.on_skew_button_click) - - def build_ui(self): - """ - Sets up the UI/form for this object. Show the UI - in the App. - - :return: None - :rtype: None - """ - - self.muted_ui = True - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()") - - try: - # HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale() - # it seems that the takewidget() does generate a focus out event for the QDoubleSpinbox ... - # and reconnect after the takeWidget() is done - # self.ui.scale_entry.returnPressed.disconnect(self.on_scale_button_click) - self.app.ui.selected_scroll_area.takeWidget() - # self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) - except Exception as e: - self.app.log.debug("FlatCAMObj.build_ui() --> Nothing to remove: %s" % str(e)) - - self.app.ui.selected_scroll_area.setWidget(self.ui) - # self.ui.setMinimumWidth(100) - # self.ui.setMaximumWidth(self.app.ui.selected_tab.sizeHint().width()) - - self.muted_ui = False - - def on_name_activate(self, silent=None): - old_name = copy(self.options["name"]) - new_name = self.ui.name_entry.get_value() - - 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) - except Exception: - log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list") - - self.options["name"] = self.ui.name_entry.get_value() - self.default_data["name"] = self.ui.name_entry.get_value() - self.app.collection.update_view() - if silent: - self.app.inform.emit('[success] %s: %s %s: %s' % ( - _("Name changed from"), str(old_name), _("to"), str(new_name) - ) - ) - - def on_offset_button_click(self): - self.app.report_usage("obj_on_offset_button") - - self.read_form() - vector_val = self.ui.offsetvector_entry.get_value() - - def worker_task(): - with self.app.proc_container.new(_("Offsetting...")): - self.offset(vector_val) - self.app.proc_container.update_view_text('') - with self.app.proc_container.new('%s...' % _("Plotting")): - self.plot() - self.app.object_changed.emit(self) - - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def on_scale_button_click(self): - self.read_form() - try: - factor = float(eval(self.ui.scale_entry.get_value())) - except Exception as e: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed.")) - log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e)) - return - - if type(factor) != float: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed.")) - - # if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0 - if factor == 1.0: - self.app.inform.emit('[success] %s' % _("Scale done.")) - return - - log.debug("FlatCAMObj.on_scale_button_click()") - - def worker_task(): - with self.app.proc_container.new(_("Scaling...")): - self.scale(factor) - self.app.inform.emit('[success] %s' % _("Scale done.")) - - self.app.proc_container.update_view_text('') - with self.app.proc_container.new('%s...' % _("Plotting")): - self.plot() - self.app.object_changed.emit(self) - - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def on_skew_button_click(self): - self.app.report_usage("obj_on_skew_button") - self.read_form() - x_angle = self.ui.xangle_entry.get_value() - y_angle = self.ui.yangle_entry.get_value() - - def worker_task(): - with self.app.proc_container.new(_("Skewing...")): - self.skew(x_angle, y_angle) - self.app.proc_container.update_view_text('') - with self.app.proc_container.new('%s...' % _("Plotting")): - self.plot() - self.app.object_changed.emit(self) - - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def to_form(self): - """ - Copies options to the UI form. - - :return: None - """ - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()") - for option in self.options: - try: - self.set_form_item(option) - except Exception: - self.app.log.warning("Unexpected error:", sys.exc_info()) - - def read_form(self): - """ - Reads form into ``self.options``. - - :return: None - :rtype: None - """ - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()") - for option in self.options: - try: - self.read_form_item(option) - except Exception: - self.app.log.warning("Unexpected error:", sys.exc_info()) - - def set_form_item(self, option): - """ - Copies the specified option to the UI form. - - :param option: Name of the option (Key in ``self.options``). - :type option: str - :return: None - """ - - try: - self.form_fields[option].set_value(self.options[option]) - except KeyError: - # self.app.log.warn("Tried to set an option or field that does not exist: %s" % option) - pass - - def read_form_item(self, option): - """ - Reads the specified option from the UI form into ``self.options``. - - :param option: Name of the option. - :type option: str - :return: None - """ - try: - self.options[option] = self.form_fields[option].get_value() - except KeyError: - pass - # self.app.log.warning("Failed to read option from field: %s" % option) - - def plot(self, kind=None): - """ - Plot this object (Extend this method to implement the actual plotting). - Call this in descendants before doing the plotting. - - :param kind: Used by only some of the FlatCAM objects - :return: Whether to continue plotting or not depending on the "plot" option. Boolean - """ - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()") - - if self.deleted: - return False - - self.clear() - return True - - def single_object_plot(self): - def plot_task(): - with self.app.proc_container.new('%s...' % _("Plotting")): - self.plot() - self.app.object_changed.emit(self) - - self.app.worker_task.emit({'fcn': plot_task, 'params': []}) - - def serialize(self): - """ - Returns a representation of the object as a dictionary so - it can be later exported as JSON. Override this method. - - :return: Dictionary representing the object - :rtype: dict - """ - return - - def deserialize(self, obj_dict): - """ - Re-builds an object from its serialized version. - - :param obj_dict: Dictionary representing a FlatCAMObj - :type obj_dict: dict - :return: None - """ - return - - def add_shape(self, **kwargs): - if self.deleted: - raise ObjectDeleted() - else: - key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs) - return key - - def add_mark_shape(self, apid, **kwargs): - if self.deleted: - raise ObjectDeleted() - else: - key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, layer=0, **kwargs) - return key - - def update_filters(self, last_ext, filter_string): - """ - Will modify the filter string that is used when saving a file (a list of file extensions) to have the last - used file extension as the first one in the special string - - :param last_ext: The file extension that was last used to save a file - :param filter_string: A key in self.app.defaults that holds a string with the filter from QFileDialog - used when saving a file - :return: None - """ - - filters = copy(self.app.defaults[filter_string]) - filter_list = filters.split(';;') - filter_list_enum_1 = enumerate(filter_list) - - # search for the last element in the filters which should always be "All Files (*.*)" - last_elem = '' - for elem in list(filter_list_enum_1): - if '(*.*)' in elem[1]: - last_elem = filter_list.pop(elem[0]) - - filter_list_enum = enumerate(filter_list) - for elem in list(filter_list_enum): - if '.' + last_ext in elem[1]: - used_ext = filter_list.pop(elem[0]) - - # sort the extensions back - filter_list.sort(key=lambda x: x.rpartition('.')[2]) - - # add as a first element the last used extension - filter_list.insert(0, used_ext) - # add back the element that should always be the last (All Files) - filter_list.append(last_elem) - - self.app.defaults[filter_string] = ';;'.join(filter_list) - return - - @staticmethod - def poly2rings(poly): - return [poly.exterior] + [interior for interior in poly.interiors] - - @property - def visible(self): - return self.shapes.visible - - @visible.setter - def visible(self, value, threaded=True): - log.debug("FlatCAMObj.visible()") - - def worker_task(app_obj): - self.shapes.visible = value - - if self.app.is_legacy is False: - # Not all object types has annotations - try: - self.annotation.visible = value - except Exception: - pass - - if threaded is False: - worker_task(app_obj=self.app) - else: - self.app.worker_task.emit({'fcn': worker_task, 'params': [self]}) - - @property - def drawing_tolerance(self): - self.units = self.app.defaults['units'].upper() - tol = self._drawing_tolerance if self.units == 'MM' or not self.units else self._drawing_tolerance / 25.4 - return tol - - @drawing_tolerance.setter - def drawing_tolerance(self, value): - self.units = self.app.defaults['units'].upper() - self._drawing_tolerance = value if self.units == 'MM' or not self.units else value / 25.4 - - def clear(self, update=False): - self.shapes.clear(update) - - # Not all object types has annotations - try: - self.annotation.clear(update) - except AttributeError: - pass - - def delete(self): - # Free resources - del self.ui - del self.options - - # Set flag - self.deleted = True - - -class FlatCAMGerber(FlatCAMObj, Gerber): - """ - Represents Gerber code. - """ - optionChanged = QtCore.pyqtSignal(str) - replotApertures = QtCore.pyqtSignal() - - ui_type = GerberObjectUI - - def merge(self, grb_list, grb_final): - """ - Merges the geometry of objects in geo_list into - the geometry of geo_final. - - :param grb_list: List of FlatCAMGerber Objects to join. - :param grb_final: Destination FlatCAMGeometry object. - :return: None - """ - - if grb_final.solid_geometry is None: - grb_final.solid_geometry = [] - grb_final.follow_geometry = [] - - if not grb_final.apertures: - grb_final.apertures = {} - - if type(grb_final.solid_geometry) is not list: - grb_final.solid_geometry = [grb_final.solid_geometry] - grb_final.follow_geometry = [grb_final.follow_geometry] - - for grb in grb_list: - - # Expand lists - if type(grb) is list: - FlatCAMGerber.merge(grb, grb_final) - else: # If not list, just append - for option in grb.options: - if option != 'name': - try: - grb_final.options[option] = grb.options[option] - except KeyError: - log.warning("Failed to copy option.", option) - - try: - for geos in grb.solid_geometry: - grb_final.solid_geometry.append(geos) - grb_final.follow_geometry.append(geos) - except TypeError: - grb_final.solid_geometry.append(grb.solid_geometry) - grb_final.follow_geometry.append(grb.solid_geometry) - - for ap in grb.apertures: - if ap not in grb_final.apertures: - grb_final.apertures[ap] = grb.apertures[ap] - else: - # create a list of integers out of the grb.apertures keys and find the max of that value - # then, the aperture duplicate is assigned an id value incremented with 1, - # and finally made string because the apertures dict keys are strings - max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1) - grb_final.apertures[max_ap] = {} - grb_final.apertures[max_ap]['geometry'] = [] - - for k, v in grb.apertures[ap].items(): - grb_final.apertures[max_ap][k] = deepcopy(v) - - grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry) - grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry) - - def __init__(self, name): - self.decimals = self.app.decimals - - self.circle_steps = int(self.app.defaults["gerber_circle_steps"]) - - Gerber.__init__(self, steps_per_circle=self.circle_steps) - FlatCAMObj.__init__(self, name) - - self.kind = "gerber" - - # The 'name' is already in self.options from FlatCAMObj - # Automatically updates the UI - self.options.update({ - "plot": True, - "multicolored": False, - "solid": False, - "tool_type": 'circular', - "vtipdia": 0.1, - "vtipangle": 30, - "vcutz": -0.05, - "isotooldia": 0.016, - "isopasses": 1, - "isooverlap": 15, - "milling_type": "cl", - "combine_passes": True, - "noncoppermargin": 0.0, - "noncopperrounded": False, - "bboxmargin": 0.0, - "bboxrounded": False, - "aperture_display": False, - "follow": False, - "iso_scope": 'all', - "iso_type": 'full' - }) - - # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors) - self.iso_type = 2 - - self.multigeo = False - - self.follow = False - - self.apertures_row = 0 - - # store the source file here - self.source_file = "" - - # list of rows with apertures plotted - self.marked_rows = [] - - # Mouse events - self.mr = None - self.mm = None - self.mp = None - - # dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly - self.poly_dict = {} - - # store the status of grid snapping - self.grid_status_memory = None - - self.units_found = self.app.defaults['units'] - - self.fill_color = self.app.defaults['gerber_plot_fill'] - self.outline_color = self.app.defaults['gerber_plot_line'] - self.alpha_level = 'bf' - - # keep track if the UI is built so we don't have to build it every time - self.ui_build = False - - # build only once the aperture storage (takes time) - self.build_aperture_storage = False - - # Attributes to be included in serialization - # Always append to it because it carries contents - # from predecessors. - self.ser_attrs += ['options', 'kind', 'fill_color', 'outline_color', 'alpha_level'] - - def set_ui(self, ui): - """ - Maps options with GUI inputs. - Connects GUI events to methods. - - :param ui: GUI object. - :type ui: GerberObjectUI - :return: None - """ - FlatCAMObj.set_ui(self, ui) - FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()") - - self.units = self.app.defaults['units'].upper() - - self.replotApertures.connect(self.on_mark_cb_click_table) - - self.form_fields.update({ - "plot": self.ui.plot_cb, - "multicolored": self.ui.multicolored_cb, - "solid": self.ui.solid_cb, - "tool_type": self.ui.tool_type_radio, - "vtipdia": self.ui.tipdia_spinner, - "vtipangle": self.ui.tipangle_spinner, - "vcutz": self.ui.cutz_spinner, - "isotooldia": self.ui.iso_tool_dia_entry, - "isopasses": self.ui.iso_width_entry, - "isooverlap": self.ui.iso_overlap_entry, - "milling_type": self.ui.milling_type_radio, - "combine_passes": self.ui.combine_passes_cb, - "noncoppermargin": self.ui.noncopper_margin_entry, - "noncopperrounded": self.ui.noncopper_rounded_cb, - "bboxmargin": self.ui.bbmargin_entry, - "bboxrounded": self.ui.bbrounded_cb, - "aperture_display": self.ui.aperture_table_visibility_cb, - "follow": self.ui.follow_cb, - "iso_scope": self.ui.iso_scope_radio, - "iso_type": self.ui.iso_type_radio - }) - - # Fill form fields only on object create - self.to_form() - - assert isinstance(self.ui, GerberObjectUI) - self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click) - self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click) - self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click) - self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run) - self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run) - self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click) - self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click) - self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change) - self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click) - - # set the model for the Area Exception comboboxes - self.ui.obj_combo.setModel(self.app.collection) - self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) - self.ui.obj_combo.is_last = True - self.ui.obj_combo.obj_type = { - _("Gerber"): "Gerber", _("Geometry"): "Geometry" - }[self.ui.type_obj_combo.get_value()] - self.on_type_obj_index_changed() - - self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed) - - self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change) - # establish visibility for the GUI elements found in the slot function - self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type']) - - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText('%s' % _('Basic')) - self.options['tool_type'] = 'circular' - - self.ui.tool_type_label.hide() - self.ui.tool_type_radio.hide() - - # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1') - self.ui.tool_type_radio.set_value('circular') - - self.ui.tipdialabel.hide() - self.ui.tipdia_spinner.hide() - self.ui.tipanglelabel.hide() - self.ui.tipangle_spinner.hide() - self.ui.cutzlabel.hide() - self.ui.cutz_spinner.hide() - - self.ui.apertures_table_label.hide() - self.ui.aperture_table_visibility_cb.hide() - self.ui.milling_type_label.hide() - self.ui.milling_type_radio.hide() - self.ui.iso_type_label.hide() - self.ui.iso_type_radio.hide() - - self.ui.follow_cb.hide() - self.ui.except_cb.setChecked(False) - self.ui.except_cb.hide() - else: - self.ui.level.setText('%s' % _('Advanced')) - self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia) - self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia) - self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia) - - if self.app.defaults["gerber_buffering"] == 'no': - self.ui.create_buffer_button.show() - try: - self.ui.create_buffer_button.clicked.disconnect(self.on_generate_buffer) - except TypeError: - pass - self.ui.create_buffer_button.clicked.connect(self.on_generate_buffer) - else: - self.ui.create_buffer_button.hide() - - # set initial state of the aperture table and associated widgets - self.on_aperture_table_visibility_change() - - self.build_ui() - self.units_found = self.app.defaults['units'] - - def on_calculate_tooldia(self): - try: - tdia = float(self.ui.tipdia_spinner.get_value()) - except Exception: - return - try: - dang = float(self.ui.tipangle_spinner.get_value()) - except Exception: - return - try: - cutz = float(self.ui.cutz_spinner.get_value()) - except Exception: - return - - cutz *= -1 - if cutz < 0: - cutz *= -1 - - half_tip_angle = dang / 2 - - tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle))) - self.ui.iso_tool_dia_entry.set_value(tool_diameter) - - def on_type_obj_index_changed(self): - val = self.ui.type_obj_combo.get_value() - obj_type = {"Gerber": 0, "Geometry": 2}[val] - self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex())) - self.ui.obj_combo.setCurrentIndex(0) - self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val] - - def on_tool_type_change(self, state): - if state == 'circular': - self.ui.tipdialabel.hide() - self.ui.tipdia_spinner.hide() - self.ui.tipanglelabel.hide() - self.ui.tipangle_spinner.hide() - self.ui.cutzlabel.hide() - self.ui.cutz_spinner.hide() - self.ui.iso_tool_dia_entry.setDisabled(False) - # update the value in the self.iso_tool_dia_entry once this is selected - self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia']) - else: - self.ui.tipdialabel.show() - self.ui.tipdia_spinner.show() - self.ui.tipanglelabel.show() - self.ui.tipangle_spinner.show() - self.ui.cutzlabel.show() - self.ui.cutz_spinner.show() - self.ui.iso_tool_dia_entry.setDisabled(True) - # update the value in the self.iso_tool_dia_entry once this is selected - self.on_calculate_tooldia() - - def build_ui(self): - FlatCAMObj.build_ui(self) - - if self.ui.aperture_table_visibility_cb.get_value() and self.ui_build is False: - self.ui_build = True - - try: - # if connected, disconnect the signal from the slot on item_changed as it creates issues - self.ui.apertures_table.itemChanged.disconnect() - except (TypeError, AttributeError): - pass - - self.apertures_row = 0 - aper_no = self.apertures_row + 1 - sort = [] - for k, v in list(self.apertures.items()): - sort.append(int(k)) - sorted_apertures = sorted(sort) - - n = len(sorted_apertures) - self.ui.apertures_table.setRowCount(n) - - for ap_code in sorted_apertures: - ap_code = str(ap_code) - - ap_id_item = QtWidgets.QTableWidgetItem('%d' % int(self.apertures_row + 1)) - ap_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.apertures_table.setItem(self.apertures_row, 0, ap_id_item) # Tool name/id - - ap_code_item = QtWidgets.QTableWidgetItem(ap_code) - ap_code_item.setFlags(QtCore.Qt.ItemIsEnabled) - - ap_type_item = QtWidgets.QTableWidgetItem(str(self.apertures[ap_code]['type'])) - ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled) - - if str(self.apertures[ap_code]['type']) == 'R' or str(self.apertures[ap_code]['type']) == 'O': - ap_dim_item = QtWidgets.QTableWidgetItem( - '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['width'], - self.decimals, self.apertures[ap_code]['height'] - ) - ) - ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) - elif str(self.apertures[ap_code]['type']) == 'P': - ap_dim_item = QtWidgets.QTableWidgetItem( - '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['diam'], - self.decimals, self.apertures[ap_code]['nVertices']) - ) - ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) - else: - ap_dim_item = QtWidgets.QTableWidgetItem('') - ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) - - try: - if self.apertures[ap_code]['size'] is not None: - ap_size_item = QtWidgets.QTableWidgetItem( - '%.*f' % (self.decimals, float(self.apertures[ap_code]['size']))) - else: - ap_size_item = QtWidgets.QTableWidgetItem('') - except KeyError: - ap_size_item = QtWidgets.QTableWidgetItem('') - ap_size_item.setFlags(QtCore.Qt.ItemIsEnabled) - - mark_item = FCCheckBox() - mark_item.setLayoutDirection(QtCore.Qt.RightToLeft) - # if self.ui.aperture_table_visibility_cb.isChecked(): - # mark_item.setChecked(True) - - self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item) # Aperture Code - self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item) # Aperture Type - self.ui.apertures_table.setItem(self.apertures_row, 3, ap_size_item) # Aperture Dimensions - self.ui.apertures_table.setItem(self.apertures_row, 4, ap_dim_item) # Aperture Dimensions - - empty_plot_item = QtWidgets.QTableWidgetItem('') - empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.apertures_table.setItem(self.apertures_row, 5, empty_plot_item) - self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item) - - self.apertures_row += 1 - - self.ui.apertures_table.selectColumn(0) - self.ui.apertures_table.resizeColumnsToContents() - self.ui.apertures_table.resizeRowsToContents() - - vertical_header = self.ui.apertures_table.verticalHeader() - # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) - vertical_header.hide() - self.ui.apertures_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - horizontal_header = self.ui.apertures_table.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(0, 27) - horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Stretch) - horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(5, 17) - self.ui.apertures_table.setColumnWidth(5, 17) - - self.ui.apertures_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.ui.apertures_table.setSortingEnabled(False) - self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight()) - self.ui.apertures_table.setMaximumHeight(self.ui.apertures_table.getHeight()) - - # update the 'mark' checkboxes state according with what is stored in the self.marked_rows list - if self.marked_rows: - for row in range(self.ui.apertures_table.rowCount()): - try: - self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row]) - except IndexError: - pass - - self.ui_connect() - - def ui_connect(self): - for row in range(self.ui.apertures_table.rowCount()): - try: - self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect(self.on_mark_cb_click_table) - except (TypeError, AttributeError): - pass - self.ui.apertures_table.cellWidget(row, 5).clicked.connect(self.on_mark_cb_click_table) - - try: - self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click) - except (TypeError, AttributeError): - pass - self.ui.mark_all_cb.clicked.connect(self.on_mark_all_click) - - def ui_disconnect(self): - for row in range(self.ui.apertures_table.rowCount()): - try: - self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click) - except (TypeError, AttributeError): - pass - - def on_generate_buffer(self): - self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry")) - - def buffer_task(): - with self.app.proc_container.new('%s...' % _("Buffering")): - if isinstance(self.solid_geometry, list): - self.solid_geometry = MultiPolygon(self.solid_geometry) - - self.solid_geometry = self.solid_geometry.buffer(0.0000001) - self.solid_geometry = self.solid_geometry.buffer(-0.0000001) - self.app.inform.emit('[success] %s.' % _("Done")) - self.plot_single_object.emit() - - self.app.worker_task.emit({'fcn': buffer_task, 'params': []}) - - def on_generatenoncopper_button_click(self, *args): - self.app.report_usage("gerber_on_generatenoncopper_button") - - self.read_form() - name = self.options["name"] + "_noncopper" - - def geo_init(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry) - if isinstance(self.solid_geometry, list): - try: - self.solid_geometry = MultiPolygon(self.solid_geometry) - except Exception: - self.solid_geometry = cascaded_union(self.solid_geometry) - - bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"])) - if not self.options["noncopperrounded"]: - bounding_box = bounding_box.envelope - non_copper = bounding_box.difference(self.solid_geometry) - - if non_copper is None or non_copper.is_empty: - self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done.")) - return "fail" - geo_obj.solid_geometry = non_copper - - self.app.new_object("geometry", name, geo_init) - - def on_generatebb_button_click(self, *args): - self.app.report_usage("gerber_on_generatebb_button") - self.read_form() - name = self.options["name"] + "_bbox" - - def geo_init(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry) - - if isinstance(self.solid_geometry, list): - try: - self.solid_geometry = MultiPolygon(self.solid_geometry) - except Exception: - self.solid_geometry = cascaded_union(self.solid_geometry) - - # Bounding box with rounded corners - bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"])) - if not self.options["bboxrounded"]: # Remove rounded corners - bounding_box = bounding_box.envelope - - if bounding_box is None or bounding_box.is_empty: - self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done.")) - return "fail" - geo_obj.solid_geometry = bounding_box - - self.app.new_object("geometry", name, geo_init) - - def on_iso_button_click(self, *args): - - obj = self.app.collection.get_active() - - self.iso_type = 2 - if self.ui.iso_type_radio.get_value() == 'ext': - self.iso_type = 0 - if self.ui.iso_type_radio.get_value() == 'int': - self.iso_type = 1 - - def worker_task(iso_obj, app_obj): - with self.app.proc_container.new(_("Isolating...")): - if self.ui.follow_cb.get_value() is True: - iso_obj.follow_geo() - # in the end toggle the visibility of the origin object so we can see the generated Geometry - iso_obj.ui.plot_cb.toggle() - else: - app_obj.report_usage("gerber_on_iso_button") - self.read_form() - - iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single' - self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope) - - self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]}) - - def follow_geo(self, outname=None): - """ - Creates a geometry object "following" the gerber paths. - - :return: None - """ - - # default_name = self.options["name"] + "_follow" - # follow_name = outname or default_name - - if outname is None: - follow_name = self.options["name"] + "_follow" - else: - follow_name = outname - - def follow_init(follow_obj, app): - # Propagate options - follow_obj.options["cnctooldia"] = str(self.options["isotooldia"]) - follow_obj.solid_geometry = self.follow_geometry - - # TODO: Do something if this is None. Offer changing name? - try: - self.app.new_object("geometry", follow_name, follow_init) - except Exception as e: - return "Operation failed: %s" % str(e) - - def isolate_handler(self, iso_type, iso_scope): - - if iso_scope == 'all': - self.isolate(iso_type=iso_type) - else: - # disengage the grid snapping since it may be hard to click on polygons with grid snapping on - if self.app.ui.grid_snap_btn.isChecked(): - self.grid_status_memory = True - self.app.ui.grid_snap_btn.trigger() - else: - self.grid_status_memory = False - - self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) - - if self.app.is_legacy is False: - 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.mr) - - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it.")) - - def on_mouse_click_release(self, event): - if self.app.is_legacy is False: - event_pos = event.pos - right_button = 2 - self.app.event_is_dragging = self.app.event_is_dragging - else: - event_pos = (event.xdata, event.ydata) - right_button = 3 - self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning - - try: - x = float(event_pos[0]) - y = float(event_pos[1]) - except TypeError: - return - - event_pos = (x, y) - curr_pos = self.app.plotcanvas.translate_coords(event_pos) - if self.app.grid_status(): - curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1]) - else: - curr_pos = (curr_pos[0], curr_pos[1]) - - if event.button == 1: - clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1])) - - if self.app.selection_type is not None: - self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type) - self.app.selection_type = None - elif clicked_poly: - if clicked_poly not in self.poly_dict.values(): - shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly, - color=self.app.defaults['global_sel_draw_color'] + 'AF', - face_color=self.app.defaults['global_sel_draw_color'] + 'AF', - visible=True) - self.poly_dict[shape_id] = clicked_poly - self.app.inform.emit( - '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)), - _("Click to add next polygon or right click to start isolation.")) - ) - else: - try: - for k, v in list(self.poly_dict.items()): - if v == clicked_poly: - self.app.tool_shapes.remove(k) - self.poly_dict.pop(k) - break - except TypeError: - return - self.app.inform.emit( - '%s. %s' % (_("Removed polygon"), - _("Click to add/remove next polygon or right click to start isolation.")) - ) - - self.app.tool_shapes.redraw() - else: - self.app.inform.emit(_("No polygon detected under click position.")) - elif event.button == right_button and self.app.event_is_dragging is False: - # restore the Grid snapping if it was active before - if self.grid_status_memory is True: - self.app.ui.grid_snap_btn.trigger() - - if self.app.is_legacy is False: - self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) - else: - self.app.plotcanvas.graph_event_disconnect(self.mr) - - self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', - self.app.on_mouse_click_release_over_plot) - - self.app.tool_shapes.clear(update=True) - - if self.poly_dict: - poly_list = deepcopy(list(self.poly_dict.values())) - self.isolate(iso_type=self.iso_type, geometry=poly_list) - self.poly_dict.clear() - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting.")) - - def 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 - :return: - """ - poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) - - # delete previous selection shape - self.app.delete_selection_shape() - - added_poly_count = 0 - try: - for geo in self.solid_geometry: - if geo not in self.poly_dict.values(): - if sel_type is True: - if geo.within(poly_selection): - shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, - shape=geo, - color=self.app.defaults['global_sel_draw_color'] + 'AF', - face_color=self.app.defaults[ - 'global_sel_draw_color'] + 'AF', - visible=True) - self.poly_dict[shape_id] = geo - added_poly_count += 1 - else: - if poly_selection.intersects(geo): - shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, - shape=geo, - color=self.app.defaults['global_sel_draw_color'] + 'AF', - face_color=self.app.defaults[ - 'global_sel_draw_color'] + 'AF', - visible=True) - self.poly_dict[shape_id] = geo - added_poly_count += 1 - except TypeError: - if self.solid_geometry not in self.poly_dict.values(): - if sel_type is True: - if self.solid_geometry.within(poly_selection): - shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, - shape=self.solid_geometry, - color=self.app.defaults['global_sel_draw_color'] + 'AF', - face_color=self.app.defaults[ - 'global_sel_draw_color'] + 'AF', - visible=True) - self.poly_dict[shape_id] = self.solid_geometry - added_poly_count += 1 - else: - if poly_selection.intersects(self.solid_geometry): - shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, - shape=self.solid_geometry, - color=self.app.defaults['global_sel_draw_color'] + 'AF', - face_color=self.app.defaults[ - 'global_sel_draw_color'] + 'AF', - visible=True) - self.poly_dict[shape_id] = self.solid_geometry - added_poly_count += 1 - - if added_poly_count > 0: - self.app.tool_shapes.redraw() - self.app.inform.emit( - '%s: %d. %s' % (_("Added polygon"), - int(added_poly_count), - _("Click to add next polygon or right click to start isolation.")) - ) - else: - self.app.inform.emit(_("No polygon in selection.")) - - def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None, - milling_type=None, follow=None, plot=True): - """ - Creates an isolation routing geometry object in the project. - - :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both - :param geometry: specific geometry to isolate - :param dia: Tool diameter - :param passes: Number of tool widths to cut - :param overlap: Overlap between passes in fraction of tool diameter - :param outname: Base name of the output object - :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes - :param milling_type: type of milling: conventional or climbing - :param follow: Boolean: if to generate a 'follow' geometry - :param plot: Boolean: if to plot the resulting geometry object - :return: None - """ - - if geometry is None: - work_geo = self.follow_geometry if follow is True else self.solid_geometry - else: - work_geo = geometry - - if dia is None: - dia = float(self.options["isotooldia"]) - - if passes is None: - passes = int(self.options["isopasses"]) - - if overlap is None: - overlap = float(self.options["isooverlap"]) - - overlap /= 100.0 - - combine = self.options["combine_passes"] if combine is None else bool(combine) - - if milling_type is None: - milling_type = self.options["milling_type"] - - if iso_type is None: - iso_t = 2 - else: - iso_t = iso_type - - base_name = self.options["name"] - - if combine: - if outname is None: - if self.iso_type == 0: - iso_name = base_name + "_ext_iso" - elif self.iso_type == 1: - iso_name = base_name + "_int_iso" - else: - iso_name = base_name + "_iso" - else: - iso_name = outname - - def iso_init(geo_obj, app_obj): - # Propagate options - geo_obj.options["cnctooldia"] = str(self.options["isotooldia"]) - geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper() - - geo_obj.solid_geometry = [] - - # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI - if self.ui.tool_type_radio.get_value() == 'v': - new_cutz = self.ui.cutz_spinner.get_value() - new_vtipdia = self.ui.tipdia_spinner.get_value() - new_vtipangle = self.ui.tipangle_spinner.get_value() - tool_type = 'V' - else: - new_cutz = self.app.defaults['geometry_cutz'] - new_vtipdia = self.app.defaults['geometry_vtipdia'] - new_vtipangle = self.app.defaults['geometry_vtipangle'] - tool_type = 'C1' - - # store here the default data for Geometry Data - default_data = {} - default_data.update({ - "name": iso_name, - "plot": self.app.defaults['geometry_plot'], - "cutz": new_cutz, - "vtipdia": new_vtipdia, - "vtipangle": new_vtipangle, - "travelz": self.app.defaults['geometry_travelz'], - "feedrate": self.app.defaults['geometry_feedrate'], - "feedrate_z": self.app.defaults['geometry_feedrate_z'], - "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'], - "dwell": self.app.defaults['geometry_dwell'], - "dwelltime": self.app.defaults['geometry_dwelltime'], - "multidepth": self.app.defaults['geometry_multidepth'], - "ppname_g": self.app.defaults['geometry_ppname_g'], - "depthperpass": self.app.defaults['geometry_depthperpass'], - "extracut": self.app.defaults['geometry_extracut'], - "extracut_length": self.app.defaults['geometry_extracut_length'], - "toolchange": self.app.defaults['geometry_toolchange'], - "toolchangez": self.app.defaults['geometry_toolchangez'], - "endz": self.app.defaults['geometry_endz'], - "spindlespeed": self.app.defaults['geometry_spindlespeed'], - "toolchangexy": self.app.defaults['geometry_toolchangexy'], - "startz": self.app.defaults['geometry_startz'] - }) - - geo_obj.tools = {} - geo_obj.tools['1'] = {} - geo_obj.tools.update({ - '1': { - 'tooldia': float(self.options["isotooldia"]), - 'offset': 'Path', - 'offset_value': 0.0, - 'type': _('Rough'), - 'tool_type': tool_type, - 'data': default_data, - 'solid_geometry': geo_obj.solid_geometry - } - }) - - for nr_pass in range(passes): - iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia) - - # if milling type is climb then the move is counter-clockwise around features - mill_dir = 1 if milling_type == 'cl' else 0 - geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t, - follow=follow, nr_passes=nr_pass) - - if geom == 'fail': - app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) - return 'fail' - geo_obj.solid_geometry.append(geom) - - # update the geometry in the tools - geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry - - # detect if solid_geometry is empty and this require list flattening which is "heavy" - # or just looking in the lists (they are one level depth) and if any is not empty - # proceed with object creation, if there are empty and the number of them is the length - # of the list then we have an empty solid_geometry which should raise a Custom Exception - empty_cnt = 0 - if not isinstance(geo_obj.solid_geometry, list) and \ - not isinstance(geo_obj.solid_geometry, MultiPolygon): - geo_obj.solid_geometry = [geo_obj.solid_geometry] - - for g in geo_obj.solid_geometry: - if g: - break - else: - empty_cnt += 1 - - if empty_cnt == len(geo_obj.solid_geometry): - raise ValidationError("Empty Geometry", None) - else: - app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"])) - - # even if combine is checked, one pass is still single-geo - geo_obj.multigeo = True if passes > 1 else False - - # ############################################################ - # ########## AREA SUBTRACTION ################################ - # ############################################################ - if self.ui.except_cb.get_value(): - self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo")) - geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry) - - # TODO: Do something if this is None. Offer changing name? - self.app.new_object("geometry", iso_name, iso_init, plot=plot) - else: - for i in range(passes): - - offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia) - if passes > 1: - if outname is None: - if self.iso_type == 0: - iso_name = base_name + "_ext_iso" + str(i + 1) - elif self.iso_type == 1: - iso_name = base_name + "_int_iso" + str(i + 1) - else: - iso_name = base_name + "_iso" + str(i + 1) - else: - iso_name = outname - else: - if outname is None: - if self.iso_type == 0: - iso_name = base_name + "_ext_iso" - elif self.iso_type == 1: - iso_name = base_name + "_int_iso" - else: - iso_name = base_name + "_iso" - else: - iso_name = outname - - def iso_init(geo_obj, app_obj): - # Propagate options - geo_obj.options["cnctooldia"] = str(self.options["isotooldia"]) - if self.ui.tool_type_radio.get_value() == 'v': - geo_obj.tool_type = 'V' - else: - geo_obj.tool_type = 'C1' - - # if milling type is climb then the move is counter-clockwise around features - mill_dir = 1 if milling_type == 'cl' else 0 - geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t, - follow=follow, - nr_passes=i) - - if geom == 'fail': - app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) - return 'fail' - - geo_obj.solid_geometry = geom - - # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI - # even if the resulting geometry is not multigeo we add the tools dict which will hold the data - # required to be transfered to the Geometry object - if self.ui.tool_type_radio.get_value() == 'v': - new_cutz = self.ui.cutz_spinner.get_value() - new_vtipdia = self.ui.tipdia_spinner.get_value() - new_vtipangle = self.ui.tipangle_spinner.get_value() - tool_type = 'V' - else: - new_cutz = self.app.defaults['geometry_cutz'] - new_vtipdia = self.app.defaults['geometry_vtipdia'] - new_vtipangle = self.app.defaults['geometry_vtipangle'] - tool_type = 'C1' - - # store here the default data for Geometry Data - default_data = {} - default_data.update({ - "name": iso_name, - "plot": self.app.defaults['geometry_plot'], - "cutz": new_cutz, - "vtipdia": new_vtipdia, - "vtipangle": new_vtipangle, - "travelz": self.app.defaults['geometry_travelz'], - "feedrate": self.app.defaults['geometry_feedrate'], - "feedrate_z": self.app.defaults['geometry_feedrate_z'], - "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'], - "dwell": self.app.defaults['geometry_dwell'], - "dwelltime": self.app.defaults['geometry_dwelltime'], - "multidepth": self.app.defaults['geometry_multidepth'], - "ppname_g": self.app.defaults['geometry_ppname_g'], - "depthperpass": self.app.defaults['geometry_depthperpass'], - "extracut": self.app.defaults['geometry_extracut'], - "extracut_length": self.app.defaults['geometry_extracut_length'], - "toolchange": self.app.defaults['geometry_toolchange'], - "toolchangez": self.app.defaults['geometry_toolchangez'], - "endz": self.app.defaults['geometry_endz'], - "spindlespeed": self.app.defaults['geometry_spindlespeed'], - "toolchangexy": self.app.defaults['geometry_toolchangexy'], - "startz": self.app.defaults['geometry_startz'] - }) - - geo_obj.tools = {} - geo_obj.tools['1'] = {} - geo_obj.tools.update({ - '1': { - 'tooldia': float(self.options["isotooldia"]), - 'offset': 'Path', - 'offset_value': 0.0, - 'type': _('Rough'), - 'tool_type': tool_type, - 'data': default_data, - 'solid_geometry': geo_obj.solid_geometry - } - }) - - # detect if solid_geometry is empty and this require list flattening which is "heavy" - # or just looking in the lists (they are one level depth) and if any is not empty - # proceed with object creation, if there are empty and the number of them is the length - # of the list then we have an empty solid_geometry which should raise a Custom Exception - empty_cnt = 0 - if not isinstance(geo_obj.solid_geometry, list): - geo_obj.solid_geometry = [geo_obj.solid_geometry] - - for g in geo_obj.solid_geometry: - if g: - break - else: - empty_cnt += 1 - - if empty_cnt == len(geo_obj.solid_geometry): - raise ValidationError("Empty Geometry", None) - else: - app_obj.inform.emit('[success] %s: %s' % - (_("Isolation geometry created"), geo_obj.options["name"])) - geo_obj.multigeo = False - - # ############################################################ - # ########## AREA SUBTRACTION ################################ - # ############################################################ - if self.ui.except_cb.get_value(): - self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo")) - geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry) - - # TODO: Do something if this is None. Offer changing name? - self.app.new_object("geometry", iso_name, iso_init, plot=plot) - - def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0): - # isolation_geometry produces an envelope that is going on the left of the geometry - # (the copper features). To leave the least amount of burrs on the features - # the tool needs to travel on the right side of the features (this is called conventional milling) - # the first pass is the one cutting all of the features, so it needs to be reversed - # the other passes overlap preceding ones and cut the left over copper. It is better for them - # to cut on the right side of the left over copper i.e on the left side of the features. - - if follow: - geom = self.isolation_geometry(offset, geometry=geometry, follow=follow) - else: - try: - geom = self.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, passes=nr_passes) - except Exception as e: - log.debug('FlatCAMGerber.isolate().generate_envelope() --> %s' % str(e)) - return 'fail' - - if invert: - try: - pl = [] - for p in geom: - if p is not None: - if isinstance(p, Polygon): - pl.append(Polygon(p.exterior.coords[::-1], p.interiors)) - elif isinstance(p, LinearRing): - pl.append(Polygon(p.coords[::-1])) - geom = MultiPolygon(pl) - except TypeError: - if isinstance(geom, Polygon) and geom is not None: - geom = Polygon(geom.exterior.coords[::-1], geom.interiors) - elif isinstance(geom, LinearRing) and geom is not None: - geom = Polygon(geom.coords[::-1]) - else: - log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> Unexpected Geometry %s" % - type(geom)) - except Exception as e: - log.debug("FlatCAMGerber.isolate().generate_envelope() Error --> %s" % str(e)) - return 'fail' - return geom - - def area_subtraction(self, geo, subtractor_geo=None): - """ - Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo - - :param geo: target geometry from which to subtract - :param subtractor_geo: geometry that acts as subtractor - :return: - """ - new_geometry = [] - target_geo = geo - - if subtractor_geo: - sub_union = cascaded_union(subtractor_geo) - else: - name = self.ui.obj_combo.currentText() - subtractor_obj = self.app.collection.get_by_name(name) - sub_union = cascaded_union(subtractor_obj.solid_geometry) - - try: - for geo_elem in target_geo: - if isinstance(geo_elem, Polygon): - for ring in self.poly2rings(geo_elem): - new_geo = ring.difference(sub_union) - 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 ring in self.poly2rings(poly): - new_geo = ring.difference(sub_union) - if new_geo and not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(geo_elem, LineString): - new_geo = geo_elem.difference(sub_union) - if new_geo: - if not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(geo_elem, MultiLineString): - for line_elem in geo_elem: - new_geo = line_elem.difference(sub_union) - if new_geo and not new_geo.is_empty: - new_geometry.append(new_geo) - except TypeError: - if isinstance(target_geo, Polygon): - for ring in self.poly2rings(target_geo): - new_geo = ring.difference(sub_union) - if new_geo: - if not new_geo.is_empty: - new_geometry.append(new_geo) - elif isinstance(target_geo, LineString): - new_geo = target_geo.difference(sub_union) - 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: - new_geo = line_elem.difference(sub_union) - if new_geo and not new_geo.is_empty: - new_geometry.append(new_geo) - return new_geometry - - def on_plot_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('plot') - self.plot() - - def on_solid_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('solid') - self.plot() - - def on_multicolored_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('multicolored') - self.plot() - - def on_follow_cb_click(self): - if self.muted_ui: - return - self.plot() - - def on_aperture_table_visibility_change(self): - if self.ui.aperture_table_visibility_cb.isChecked(): - # add the shapes storage for marking apertures - if self.build_aperture_storage is False: - self.build_aperture_storage = True - - if self.app.is_legacy is False: - for ap_code in self.apertures: - self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=1) - else: - for ap_code in self.apertures: - self.mark_shapes[ap_code] = ShapeCollectionLegacy(obj=self, app=self.app, - name=self.options['name'] + str(ap_code)) - - self.ui.apertures_table.setVisible(True) - for ap in self.mark_shapes: - self.mark_shapes[ap].enabled = True - - self.ui.mark_all_cb.setVisible(True) - self.ui.mark_all_cb.setChecked(False) - self.build_ui() - else: - self.ui.apertures_table.setVisible(False) - - self.ui.mark_all_cb.setVisible(False) - - # on hide disable all mark plots - try: - for row in range(self.ui.apertures_table.rowCount()): - self.ui.apertures_table.cellWidget(row, 5).set_value(False) - self.clear_plot_apertures() - - # for ap in list(self.mark_shapes.keys()): - # # self.mark_shapes[ap].enabled = False - # del self.mark_shapes[ap] - except Exception as e: - log.debug(" FlatCAMGerber.on_aperture_visibility_changed() --> %s" % str(e)) - - def convert_units(self, units): - """ - Converts the units of the object by scaling dimensions in all geometry - and options. - - :param units: Units to which to convert the object: "IN" or "MM". - :type units: str - :return: None - :rtype: None - """ - - # units conversion to get a conversion should be done only once even if we found multiple - # units declaration inside a Gerber file (it can happen to find also the obsolete declaration) - if self.conversion_done is True: - log.debug("Gerber units conversion cancelled. Already done.") - return - - log.debug("FlatCAMObj.FlatCAMGerber.convert_units()") - - factor = Gerber.convert_units(self, units) - - # self.options['isotooldia'] = float(self.options['isotooldia']) * factor - # self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor - - def plot(self, kind=None, **kwargs): - """ - - :param kind: Not used, for compatibility with the plot method for other objects - :param kwargs: Color and face_color, visible - :return: - """ - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()") - - # Does all the required setup and returns False - # if the 'ptint' option is set to False. - if not FlatCAMObj.plot(self): - return - - if 'color' in kwargs: - color = kwargs['color'] - else: - color = self.outline_color - - if 'face_color' in kwargs: - face_color = kwargs['face_color'] - else: - face_color = self.fill_color - - if 'visible' not in kwargs: - visible = self.options['plot'] - else: - visible = kwargs['visible'] - - # if the Follow Geometry checkbox is checked then plot only the follow geometry - if self.ui.follow_cb.get_value(): - geometry = self.follow_geometry - else: - geometry = self.solid_geometry - - # Make sure geometry is iterable. - try: - __ = iter(geometry) - except TypeError: - geometry = [geometry] - - if self.app.is_legacy is False: - def random_color(): - r_color = np.random.rand(4) - r_color[3] = 1 - return r_color - else: - def random_color(): - while True: - r_color = np.random.rand(4) - r_color[3] = 1 - - new_color = '#' - for idx in range(len(r_color)): - new_color += '%x' % int(r_color[idx] * 255) - # do it until a valid color is generated - # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha - # for a total of 9 chars - if len(new_color) == 9: - break - return new_color - - try: - if self.options["solid"]: - for g in geometry: - if type(g) == Polygon or type(g) == LineString: - self.add_shape(shape=g, color=color, - face_color=random_color() if self.options['multicolored'] - else face_color, visible=visible) - elif type(g) == Point: - pass - else: - try: - for el in g: - self.add_shape(shape=el, color=color, - face_color=random_color() if self.options['multicolored'] - else face_color, visible=visible) - except TypeError: - self.add_shape(shape=g, color=color, - face_color=random_color() if self.options['multicolored'] - else face_color, visible=visible) - else: - for g in geometry: - if type(g) == Polygon or type(g) == LineString: - self.add_shape(shape=g, color=random_color() if self.options['multicolored'] else 'black', - visible=visible) - elif type(g) == Point: - pass - else: - for el in g: - self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black', - visible=visible) - self.shapes.redraw( - # update_colors=(self.fill_color, self.outline_color), - # indexes=self.app.plotcanvas.shape_collection.data.keys() - ) - except (ObjectDeleted, AttributeError): - self.shapes.clear(update=True) - except Exception as e: - log.debug("FlatCAMGerber.plot() --> %s" % str(e)) - - # experimental plot() when the solid_geometry is stored in the self.apertures - def plot_aperture(self, run_thread=True, **kwargs): - """ - - :param run_thread: if True run the aperture plot as a thread in a worker - :param kwargs: color and face_color - :return: - """ - - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot_aperture()") - - # Does all the required setup and returns False - # if the 'ptint' option is set to False. - # if not FlatCAMObj.plot(self): - # return - - # for marking apertures, line color and fill color are the same - if 'color' in kwargs: - color = kwargs['color'] - else: - color = self.app.defaults['gerber_plot_fill'] - - if 'marked_aperture' not in kwargs: - return - else: - aperture_to_plot_mark = kwargs['marked_aperture'] - if aperture_to_plot_mark is None: - return - - if 'visible' not in kwargs: - visibility = True - else: - visibility = kwargs['visible'] - - with self.app.proc_container.new(_("Plotting Apertures")): - - def job_thread(app_obj): - try: - if aperture_to_plot_mark in self.apertures: - for elem in self.apertures[aperture_to_plot_mark]['geometry']: - if 'solid' in elem: - geo = elem['solid'] - if type(geo) == Polygon or type(geo) == LineString: - self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color, - face_color=color, visible=visibility) - else: - for el in geo: - self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color, - face_color=color, visible=visibility) - - self.mark_shapes[aperture_to_plot_mark].redraw() - - except (ObjectDeleted, AttributeError): - self.clear_plot_apertures() - except Exception as e: - log.debug("FlatCAMGerber.plot_aperture() --> %s" % str(e)) - - if run_thread: - self.app.worker_task.emit({'fcn': job_thread, 'params': [self]}) - else: - job_thread(self) - - def clear_plot_apertures(self, aperture='all'): - """ - - :param aperture: string; aperture for which to clear the mark shapes - :return: - """ - - if self.mark_shapes: - if aperture == 'all': - for apid in list(self.apertures.keys()): - try: - if self.app.is_legacy is True: - self.mark_shapes[apid].clear(update=False) - else: - self.mark_shapes[apid].clear(update=True) - except Exception as e: - log.debug("FlatCAMGerber.clear_plot_apertures() 'all' --> %s" % str(e)) - else: - try: - if self.app.is_legacy is True: - self.mark_shapes[aperture].clear(update=False) - else: - self.mark_shapes[aperture].clear(update=True) - except Exception as e: - log.debug("FlatCAMGerber.clear_plot_apertures() 'aperture' --> %s" % str(e)) - - def clear_mark_all(self): - self.ui.mark_all_cb.set_value(False) - self.marked_rows[:] = [] - - def on_mark_cb_click_table(self): - """ - Will mark aperture geometries on canvas or delete the markings depending on the checkbox state - :return: - """ - - self.ui_disconnect() - cw = self.sender() - try: - cw_index = self.ui.apertures_table.indexAt(cw.pos()) - cw_row = cw_index.row() - except AttributeError: - cw_row = 0 - except TypeError: - return - - self.marked_rows[:] = [] - - try: - aperture = self.ui.apertures_table.item(cw_row, 1).text() - except AttributeError: - return - - if self.ui.apertures_table.cellWidget(cw_row, 5).isChecked(): - self.marked_rows.append(True) - # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True) - self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF', - marked_aperture=aperture, visible=True, run_thread=True) - # self.mark_shapes[aperture].redraw() - else: - self.marked_rows.append(False) - self.clear_plot_apertures(aperture=aperture) - - # make sure that the Mark All is disabled if one of the row mark's are disabled and - # if all the row mark's are enabled also enable the Mark All checkbox - cb_cnt = 0 - total_row = self.ui.apertures_table.rowCount() - for row in range(total_row): - if self.ui.apertures_table.cellWidget(row, 5).isChecked(): - cb_cnt += 1 - else: - cb_cnt -= 1 - if cb_cnt < total_row: - self.ui.mark_all_cb.setChecked(False) - else: - self.ui.mark_all_cb.setChecked(True) - self.ui_connect() - - def on_mark_all_click(self): - self.ui_disconnect() - mark_all = self.ui.mark_all_cb.isChecked() - for row in range(self.ui.apertures_table.rowCount()): - # update the mark_rows list - if mark_all: - self.marked_rows.append(True) - else: - self.marked_rows[:] = [] - - mark_cb = self.ui.apertures_table.cellWidget(row, 5) - mark_cb.setChecked(mark_all) - - if mark_all: - for aperture in self.apertures: - # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True) - self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF', - marked_aperture=aperture, visible=True) - # HACK: enable/disable the grid for a better look - self.app.ui.grid_snap_btn.trigger() - self.app.ui.grid_snap_btn.trigger() - else: - self.clear_plot_apertures() - self.marked_rows[:] = [] - - self.ui_connect() - - def export_gerber(self, whole, fract, g_zeros='L', factor=1): - """ - Creates a Gerber file content to be exported to a file. - - :param whole: how many digits in the whole part of coordinates - :param fract: how many decimals in coordinates - :param g_zeros: type of the zero suppression used: LZ or TZ; string - :param factor: factor to be applied onto the Gerber coordinates - :return: Gerber_code - """ - log.debug("FlatCAMGerber.export_gerber() --> Generating the Gerber code from the selected Gerber file") - - def tz_format(x, y, fac): - x_c = x * fac - y_c = y * fac - - x_form = "{:.{dec}f}".format(x_c, dec=fract) - y_form = "{:.{dec}f}".format(y_c, dec=fract) - - # extract whole part and decimal part - x_form = x_form.partition('.') - y_form = y_form.partition('.') - - # left padd the 'whole' part with zeros - x_whole = x_form[0].rjust(whole, '0') - y_whole = y_form[0].rjust(whole, '0') - - # restore the coordinate padded in the left with 0 and added the decimal part - # without the decinal dot - x_form = x_whole + x_form[2] - y_form = y_whole + y_form[2] - return x_form, y_form - - def lz_format(x, y, fac): - x_c = x * fac - y_c = y * fac - - x_form = "{:.{dec}f}".format(x_c, dec=fract).replace('.', '') - y_form = "{:.{dec}f}".format(y_c, dec=fract).replace('.', '') - - # pad with rear zeros - x_form.ljust(length, '0') - y_form.ljust(length, '0') - - return x_form, y_form - - # Gerber code is stored here - gerber_code = '' - - # apertures processing - try: - length = whole + fract - if '0' in self.apertures: - if 'geometry' in self.apertures['0']: - for geo_elem in self.apertures['0']['geometry']: - if 'solid' in geo_elem: - geo = geo_elem['solid'] - if not geo.is_empty: - gerber_code += 'G36*\n' - geo_coords = list(geo.exterior.coords) - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - for coord in geo_coords[1:]: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - gerber_code += 'D02*\n' - gerber_code += 'G37*\n' - - clear_list = list(geo.interiors) - if clear_list: - gerber_code += '%LPC*%\n' - for clear_geo in clear_list: - gerber_code += 'G36*\n' - geo_coords = list(clear_geo.coords) - - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = geo_coords[0] - for coord in geo_coords[1:]: - if coord != prev_coord: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - prev_coord = coord - - gerber_code += 'D02*\n' - gerber_code += 'G37*\n' - gerber_code += '%LPD*%\n' - if 'clear' in geo_elem: - geo = geo_elem['clear'] - if not geo.is_empty: - gerber_code += '%LPC*%\n' - gerber_code += 'G36*\n' - geo_coords = list(geo.exterior.coords) - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = geo_coords[0] - for coord in geo_coords[1:]: - if coord != prev_coord: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - prev_coord = coord - - gerber_code += 'D02*\n' - gerber_code += 'G37*\n' - gerber_code += '%LPD*%\n' - except Exception as e: - log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() '0' aperture --> %s" % str(e)) - - for apid in self.apertures: - if apid == '0': - continue - else: - gerber_code += 'D%s*\n' % str(apid) - if 'geometry' in self.apertures[apid]: - for geo_elem in self.apertures[apid]['geometry']: - try: - if 'follow' in geo_elem: - geo = geo_elem['follow'] - if not geo.is_empty: - if isinstance(geo, Point): - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(geo.x, geo.y, factor) - gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(geo.x, geo.y, factor) - gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, - yform=y_formatted) - else: - geo_coords = list(geo.coords) - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = geo_coords[0] - for coord in geo_coords[1:]: - if coord != prev_coord: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - prev_coord = coord - - # gerber_code += "D02*\n" - except Exception as e: - log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() 'follow' --> %s" % str(e)) - - try: - if 'clear' in geo_elem: - gerber_code += '%LPC*%\n' - - geo = geo_elem['clear'] - if not geo.is_empty: - if isinstance(geo, Point): - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(geo.x, geo.y, factor) - gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(geo.x, geo.y, factor) - gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, - yform=y_formatted) - elif isinstance(geo, Polygon): - geo_coords = list(geo.exterior.coords) - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = geo_coords[0] - for coord in geo_coords[1:]: - if coord != prev_coord: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = coord - - for geo_int in geo.interiors: - geo_coords = list(geo_int.coords) - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = geo_coords[0] - for coord in geo_coords[1:]: - if coord != prev_coord: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format( - xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format( - xform=x_formatted, - yform=y_formatted) - - prev_coord = coord - else: - geo_coords = list(geo.coords) - # first command is a move with pen-up D02 at the beginning of the geo - if g_zeros == 'T': - x_formatted, y_formatted = tz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format( - geo_coords[0][0], geo_coords[0][1], factor) - gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = geo_coords[0] - for coord in geo_coords[1:]: - if coord != prev_coord: - if g_zeros == 'T': - x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - else: - x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) - gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, - yform=y_formatted) - - prev_coord = coord - # gerber_code += "D02*\n" - gerber_code += '%LPD*%\n' - except Exception as e: - log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() 'clear' --> %s" % str(e)) - - if not self.apertures: - log.debug("FlatCAMObj.FlatCAMGerber.export_gerber() --> Gerber Object is empty: no apertures.") - return 'fail' - - return gerber_code - - def mirror(self, axis, point): - Gerber.mirror(self, axis=axis, point=point) - self.replotApertures.emit() - - def offset(self, vect): - Gerber.offset(self, vect=vect) - self.replotApertures.emit() - - def rotate(self, angle, point): - Gerber.rotate(self, angle=angle, point=point) - self.replotApertures.emit() - - def scale(self, xfactor, yfactor=None, point=None): - Gerber.scale(self, xfactor=xfactor, yfactor=yfactor, point=point) - self.replotApertures.emit() - - def skew(self, angle_x, angle_y, point): - Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point) - self.replotApertures.emit() - - def buffer(self, distance, join, factor=None): - Gerber.buffer(self, distance=distance, join=join, factor=factor) - self.replotApertures.emit() - - def serialize(self): - return { - "options": self.options, - "kind": self.kind - } - - -class FlatCAMExcellon(FlatCAMObj, Excellon): - """ - Represents Excellon/Drill code. - """ - - ui_type = ExcellonObjectUI - optionChanged = QtCore.pyqtSignal(str) - - def __init__(self, name): - self.decimals = self.app.decimals - - self.circle_steps = int(self.app.defaults["geometry_circle_steps"]) - - Excellon.__init__(self, geo_steps_per_circle=self.circle_steps) - FlatCAMObj.__init__(self, name) - - self.kind = "excellon" - - self.options.update({ - "plot": True, - "solid": False, - - "operation": "drill", - "milling_type": "drills", - - "milling_dia": 0.04, - - "cutz": -0.1, - "multidepth": False, - "depthperpass": 0.7, - "travelz": 0.1, - "feedrate": self.app.defaults["geometry_feedrate"], - "feedrate_z": 5.0, - "feedrate_rapid": 5.0, - "tooldia": 0.1, - "slot_tooldia": 0.1, - "toolchange": False, - "toolchangez": 1.0, - "toolchangexy": "0.0, 0.0", - "extracut": self.app.defaults["geometry_extracut"], - "extracut_length": self.app.defaults["geometry_extracut_length"], - "endz": 2.0, - "endxy": '', - - "startz": None, - "offset": 0.0, - "spindlespeed": 0, - "dwell": True, - "dwelltime": 1000, - "ppname_e": 'default', - "ppname_g": self.app.defaults["geometry_ppname_g"], - "z_pdepth": -0.02, - "feedrate_probe": 3.0, - "optimization_type": "B", - }) - - # TODO: Document this. - self.tool_cbs = {} - - # dict that holds the object names and the option name - # the key is the object name (defines in ObjectUI) for each UI element that is a parameter - # particular for a tool and the value is the actual name of the option that the UI element is changing - self.name2option = {} - - # default set of data to be added to each tool in self.tools as self.tools[tool]['data'] = self.default_data - self.default_data = {} - - # fill in self.default_data values from self.options - for opt_key, opt_val in self.app.options.items(): - if opt_key.find('excellon_') == 0: - self.default_data[opt_key] = deepcopy(opt_val) - for opt_key, opt_val in self.app.options.items(): - if opt_key.find('geometry_') == 0: - self.default_data[opt_key] = deepcopy(opt_val) - - # variable to store the total amount of drills per job - self.tot_drill_cnt = 0 - self.tool_row = 0 - - # variable to store the total amount of slots per job - self.tot_slot_cnt = 0 - self.tool_row_slots = 0 - - # variable to store the distance travelled - self.travel_distance = 0.0 - - # store the source file here - self.source_file = "" - - self.multigeo = False - self.units_found = self.app.defaults['units'] - - self.fill_color = self.app.defaults['excellon_plot_fill'] - self.outline_color = self.app.defaults['excellon_plot_line'] - self.alpha_level = 'bf' - - # Attributes to be included in serialization - # Always append to it because it carries contents - # from predecessors. - self.ser_attrs += ['options', 'kind'] - - def merge(self, exc_list, exc_final): - """ - Merge Excellon objects found in exc_list parameter into exc_final object. - Options are always copied from source . - - Tools are disregarded, what is taken in consideration is the unique drill diameters found as values in the - exc_list tools dict's. In the reconstruction section for each unique tool diameter it will be created a - tool_name to be used in the final Excellon object, exc_final. - - If only one object is in exc_list parameter then this function will copy that object in the exc_final - - :param exc_list: List or one object of FlatCAMExcellon Objects to join. - :param exc_final: Destination FlatCAMExcellon object. - :return: None - """ - - try: - decimals_exc = self.decimals - except AttributeError: - decimals_exc = 4 - - # flag to signal that we need to reorder the tools dictionary and drills and slots lists - flag_order = False - - try: - flattened_list = list(itertools.chain(*exc_list)) - except TypeError: - flattened_list = exc_list - - # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict - # values will be list of Shapely Points; for drills - custom_dict_drills = {} - - # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict - # values will be list of Shapely Points; for slots - custom_dict_slots = {} - - for exc in flattened_list: - # copy options of the current excellon obj to the final excellon obj - for option in exc.options: - if option != 'name': - try: - exc_final.options[option] = exc.options[option] - except Exception: - exc.app.log.warning("Failed to copy option.", option) - - for drill in exc.drills: - exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[drill['tool']]['C'])) - - if exc_tool_dia not in custom_dict_drills: - custom_dict_drills[exc_tool_dia] = [drill['point']] - else: - custom_dict_drills[exc_tool_dia].append(drill['point']) - - for slot in exc.slots: - exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[slot['tool']]['C'])) - - if exc_tool_dia not in custom_dict_slots: - custom_dict_slots[exc_tool_dia] = [[slot['start'], slot['stop']]] - else: - custom_dict_slots[exc_tool_dia].append([slot['start'], slot['stop']]) - - # add the zeros and units to the exc_final object - exc_final.zeros = exc.zeros - exc_final.units = exc.units - - # ########################################## - # Here we add data to the exc_final object # - # ########################################## - - # variable to make tool_name for the tools - current_tool = 0 - # The tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of - # drills - for tool_dia in custom_dict_drills: - # we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter) - current_tool += 1 - - tool_name = str(current_tool) - spec = {"C": float(tool_dia)} - exc_final.tools[tool_name] = spec - - # rebuild the drills list of dict's that belong to the exc_final object - for point in custom_dict_drills[tool_dia]: - exc_final.drills.append( - { - "point": point, - "tool": str(current_tool) - } - ) - - # The tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop]) - # of two Shapely Points in case of slots - for tool_dia in custom_dict_slots: - # we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter) - # but only if there are no drills - if not exc_final.tools: - current_tool += 1 - tool_name = str(current_tool) - spec = {"C": float(tool_dia)} - exc_final.tools[tool_name] = spec - else: - dia_list = [] - for v in exc_final.tools.values(): - dia_list.append(float(v["C"])) - - if tool_dia not in dia_list: - flag_order = True - - current_tool = len(dia_list) + 1 - tool_name = str(current_tool) - spec = {"C": float(tool_dia)} - exc_final.tools[tool_name] = spec - - else: - for k, v in exc_final.tools.items(): - if v["C"] == tool_dia: - current_tool = int(k) - break - - # rebuild the slots list of dict's that belong to the exc_final object - for point in custom_dict_slots[tool_dia]: - exc_final.slots.append( - { - "start": point[0], - "stop": point[1], - "tool": str(current_tool) - } - ) - - # flag_order == True means that there was an slot diameter not in the tools and we also have drills - # and the new tool was added to self.tools therefore we need to reorder the tools and drills and slots - current_tool = 0 - if flag_order is True: - dia_list = [] - temp_drills = [] - temp_slots = [] - temp_tools = {} - for v in exc_final.tools.values(): - dia_list.append(float(v["C"])) - dia_list.sort() - for ordered_dia in dia_list: - current_tool += 1 - tool_name_temp = str(current_tool) - spec_temp = {"C": float(ordered_dia)} - temp_tools[tool_name_temp] = spec_temp - - for drill in exc_final.drills: - exc_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[drill['tool']]['C'])) - if exc_tool_dia == ordered_dia: - temp_drills.append( - { - "point": drill["point"], - "tool": str(current_tool) - } - ) - - for slot in exc_final.slots: - slot_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[slot['tool']]['C'])) - if slot_tool_dia == ordered_dia: - temp_slots.append( - { - "start": slot["start"], - "stop": slot["stop"], - "tool": str(current_tool) - } - ) - - # delete the exc_final tools, drills and slots - exc_final.tools = {} - exc_final.drills[:] = [] - exc_final.slots[:] = [] - - # update the exc_final tools, drills and slots with the ordered values - exc_final.tools = temp_tools - exc_final.drills[:] = temp_drills - exc_final.slots[:] = temp_slots - - # create the geometry for the exc_final object - exc_final.create_geometry() - - def build_ui(self): - FlatCAMObj.build_ui(self) - - self.units = self.app.defaults['units'].upper() - - for row in range(self.ui.tools_table.rowCount()): - try: - # if connected, disconnect the signal from the slot on item_changed as it creates issues - offset_spin_widget = self.ui.tools_table.cellWidget(row, 4) - offset_spin_widget.valueChanged.disconnect() - except (TypeError, AttributeError): - pass - - n = len(self.tools) - # we have (n+2) rows because there are 'n' tools, each a row, plus the last 2 rows for totals. - self.ui.tools_table.setRowCount(n + 2) - - self.tot_drill_cnt = 0 - self.tot_slot_cnt = 0 - - self.tool_row = 0 - - sort = [] - for k, v in list(self.tools.items()): - sort.append((k, v.get('C'))) - sorted_tools = sorted(sort, key=lambda t1: t1[1]) - tools = [i[0] for i in sorted_tools] - - new_options = {} - for opt in self.options: - new_options[opt] = self.options[opt] - - for tool_no in tools: - - # add the data dictionary for each tool with the default values - self.tools[tool_no]['data'] = deepcopy(new_options) - # self.tools[tool_no]['data']["tooldia"] = self.tools[tool_no]["C"] - # self.tools[tool_no]['data']["slot_tooldia"] = self.tools[tool_no]["C"] - - drill_cnt = 0 # variable to store the nr of drills per tool - slot_cnt = 0 # variable to store the nr of slots per tool - - # Find no of drills for the current tool - for drill in self.drills: - if drill['tool'] == tool_no: - drill_cnt += 1 - - self.tot_drill_cnt += drill_cnt - - # Find no of slots for the current tool - for slot in self.slots: - if slot['tool'] == tool_no: - slot_cnt += 1 - - self.tot_slot_cnt += slot_cnt - - exc_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_no)) - exc_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - - dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['C'])) - dia_item.setFlags(QtCore.Qt.ItemIsEnabled) - - drill_count_item = QtWidgets.QTableWidgetItem('%d' % drill_cnt) - drill_count_item.setFlags(QtCore.Qt.ItemIsEnabled) - - # if the slot number is zero is better to not clutter the GUI with zero's so we print a space - slot_count_str = '%d' % slot_cnt if slot_cnt > 0 else '' - slot_count_item = QtWidgets.QTableWidgetItem(slot_count_str) - slot_count_item.setFlags(QtCore.Qt.ItemIsEnabled) - - plot_item = FCCheckBox() - plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) - if self.ui.plot_cb.isChecked(): - plot_item.setChecked(True) - - self.ui.tools_table.setItem(self.tool_row, 0, exc_id_item) # Tool name/id - self.ui.tools_table.setItem(self.tool_row, 1, dia_item) # Diameter - self.ui.tools_table.setItem(self.tool_row, 2, drill_count_item) # Number of drills per tool - self.ui.tools_table.setItem(self.tool_row, 3, slot_count_item) # Number of drills per tool - empty_plot_item = QtWidgets.QTableWidgetItem('') - empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.tools_table.setItem(self.tool_row, 5, empty_plot_item) - self.ui.tools_table.setCellWidget(self.tool_row, 5, plot_item) - - self.tool_row += 1 - - # add a last row with the Total number of drills - empty_1 = QtWidgets.QTableWidgetItem('') - empty_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - empty_1_1 = QtWidgets.QTableWidgetItem('') - empty_1_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - empty_1_2 = QtWidgets.QTableWidgetItem('') - empty_1_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - empty_1_3 = QtWidgets.QTableWidgetItem('') - empty_1_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - - label_tot_drill_count = QtWidgets.QTableWidgetItem(_('Total Drills')) - tot_drill_count = QtWidgets.QTableWidgetItem('%d' % self.tot_drill_cnt) - label_tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled) - tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled) - - self.ui.tools_table.setItem(self.tool_row, 0, empty_1) - self.ui.tools_table.setItem(self.tool_row, 1, label_tot_drill_count) - self.ui.tools_table.setItem(self.tool_row, 2, tot_drill_count) # Total number of drills - self.ui.tools_table.setItem(self.tool_row, 3, empty_1_1) - self.ui.tools_table.setItem(self.tool_row, 5, empty_1_3) - - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - - for k in [1, 2]: - self.ui.tools_table.item(self.tool_row, k).setForeground(QtGui.QColor(127, 0, 255)) - self.ui.tools_table.item(self.tool_row, k).setFont(font) - - self.tool_row += 1 - - # add a last row with the Total number of slots - empty_2 = QtWidgets.QTableWidgetItem('') - empty_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - empty_2_1 = QtWidgets.QTableWidgetItem('') - empty_2_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - empty_2_2 = QtWidgets.QTableWidgetItem('') - empty_2_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - empty_2_3 = QtWidgets.QTableWidgetItem('') - empty_2_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - - label_tot_slot_count = QtWidgets.QTableWidgetItem(_('Total Slots')) - tot_slot_count = QtWidgets.QTableWidgetItem('%d' % self.tot_slot_cnt) - label_tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled) - tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled) - - self.ui.tools_table.setItem(self.tool_row, 0, empty_2) - self.ui.tools_table.setItem(self.tool_row, 1, label_tot_slot_count) - self.ui.tools_table.setItem(self.tool_row, 2, empty_2_1) - self.ui.tools_table.setItem(self.tool_row, 3, tot_slot_count) # Total number of slots - self.ui.tools_table.setItem(self.tool_row, 5, empty_2_3) - - for kl in [1, 2, 3]: - self.ui.tools_table.item(self.tool_row, kl).setFont(font) - self.ui.tools_table.item(self.tool_row, kl).setForeground(QtGui.QColor(0, 70, 255)) - - # sort the tool diameter column - # self.ui.tools_table.sortItems(1) - - # all the tools are selected by default - self.ui.tools_table.selectColumn(0) - - self.ui.tools_table.resizeColumnsToContents() - self.ui.tools_table.resizeRowsToContents() - - vertical_header = self.ui.tools_table.verticalHeader() - # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) - vertical_header.hide() - self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - horizontal_header = self.ui.tools_table.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(0, 20) - - horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - - horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(5, 17) - self.ui.tools_table.setColumnWidth(5, 17) - - # horizontal_header.setStretchLastSection(True) - # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents) - - # horizontal_header.setStretchLastSection(True) - self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self.ui.tools_table.setSortingEnabled(False) - - self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight()) - self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight()) - - if not self.drills: - self.ui.tooldia_entry.hide() - self.ui.generate_milling_button.hide() - else: - self.ui.tooldia_entry.show() - self.ui.generate_milling_button.show() - - if not self.slots: - self.ui.slot_tooldia_entry.hide() - self.ui.generate_milling_slots_button.hide() - else: - self.ui.slot_tooldia_entry.show() - self.ui.generate_milling_slots_button.show() - - # set the text on tool_data_label after loading the object - sel_items = self.ui.tools_table.selectedItems() - sel_rows = [it.row() for it in sel_items] - if len(sel_rows) > 1: - self.ui.tool_data_label.setText( - "%s: %s" % (_('Parameters for'), _("Multiple Tools")) - ) - - self.ui_connect() - - def set_ui(self, ui): - """ - Configures the user interface for this object. - Connects options to form fields. - - :param ui: User interface object. - :type ui: ExcellonObjectUI - :return: None - """ - FlatCAMObj.set_ui(self, ui) - - FlatCAMApp.App.log.debug("FlatCAMExcellon.set_ui()") - - self.units = self.app.defaults['units'].upper() - - self.form_fields.update({ - "plot": self.ui.plot_cb, - "solid": self.ui.solid_cb, - - "operation": self.ui.operation_radio, - "milling_type": self.ui.milling_type_radio, - - "milling_dia": self.ui.mill_dia_entry, - "cutz": self.ui.cutz_entry, - "multidepth": self.ui.mpass_cb, - "depthperpass": self.ui.maxdepth_entry, - "travelz": self.ui.travelz_entry, - "feedrate_z": self.ui.feedrate_z_entry, - "feedrate": self.ui.xyfeedrate_entry, - "feedrate_rapid": self.ui.feedrate_rapid_entry, - "tooldia": self.ui.tooldia_entry, - "slot_tooldia": self.ui.slot_tooldia_entry, - "toolchange": self.ui.toolchange_cb, - "toolchangez": self.ui.toolchangez_entry, - "extracut": self.ui.extracut_cb, - "extracut_length": self.ui.e_cut_entry, - - "spindlespeed": self.ui.spindlespeed_entry, - "dwell": self.ui.dwell_cb, - "dwelltime": self.ui.dwelltime_entry, - - "startz": self.ui.estartz_entry, - "endz": self.ui.endz_entry, - "endxy": self.ui.endxy_entry, - - "offset": self.ui.offset_entry, - - "ppname_e": self.ui.pp_excellon_name_cb, - "ppname_g": self.ui.pp_geo_name_cb, - "z_pdepth": self.ui.pdepth_entry, - "feedrate_probe": self.ui.feedrate_probe_entry, - # "gcode_type": self.ui.excellon_gcode_type_radio - }) - - self.name2option = { - "e_operation": "operation", - "e_milling_type": "milling_type", - "e_milling_dia": "milling_dia", - "e_cutz": "cutz", - "e_multidepth": "multidepth", - "e_depthperpass": "depthperpass", - - "e_travelz": "travelz", - "e_feedratexy": "feedrate", - "e_feedratez": "feedrate_z", - "e_fr_rapid": "feedrate_rapid", - "e_extracut": "extracut", - "e_extracut_length": "extracut_length", - "e_spindlespeed": "spindlespeed", - "e_dwell": "dwell", - "e_dwelltime": "dwelltime", - "e_offset": "offset", - } - - # populate Excellon preprocessor combobox list - for name in list(self.app.preprocessors.keys()): - # the HPGL preprocessor is only for Geometry not for Excellon job therefore don't add it - if name == 'hpgl': - continue - self.ui.pp_excellon_name_cb.addItem(name) - - # populate Geometry (milling) preprocessor combobox list - for name in list(self.app.preprocessors.keys()): - self.ui.pp_geo_name_cb.addItem(name) - - # Fill form fields - self.to_form() - - # update the changes in UI depending on the selected preprocessor in Preferences - # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the - # self.ui.pp_excellon_name_cb combobox - self.on_pp_changed() - - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText('%s' % _('Basic')) - - self.ui.tools_table.setColumnHidden(4, True) - self.ui.tools_table.setColumnHidden(5, True) - self.ui.estartz_label.hide() - self.ui.estartz_entry.hide() - self.ui.feedrate_rapid_label.hide() - self.ui.feedrate_rapid_entry.hide() - self.ui.pdepth_label.hide() - self.ui.pdepth_entry.hide() - self.ui.feedrate_probe_label.hide() - self.ui.feedrate_probe_entry.hide() - else: - self.ui.level.setText('%s' % _('Advanced')) - - assert isinstance(self.ui, ExcellonObjectUI), \ - "Expected a ExcellonObjectUI, got %s" % type(self.ui) - self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click) - self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click) - self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click) - self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click) - - self.on_operation_type(val='drill') - self.ui.operation_radio.activated_custom.connect(self.on_operation_type) - - self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed) - - self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked) - - self.units_found = self.app.defaults['units'] - - # ######################################## - # #######3 TEMP SETTINGS ################# - # ######################################## - self.ui.operation_radio.set_value("drill") - self.ui.operation_radio.setEnabled(False) - - def ui_connect(self): - - # selective plotting - for row in range(self.ui.tools_table.rowCount() - 2): - self.ui.tools_table.cellWidget(row, 5).clicked.connect(self.on_plot_cb_click_table) - self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - - # rows selected - self.ui.tools_table.clicked.connect(self.on_row_selection_change) - self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change) - - # value changed in the particular parameters of a tool - for key, option in self.name2option.items(): - current_widget = self.form_fields[option] - - if isinstance(current_widget, FCCheckBox): - current_widget.stateChanged.connect(self.form_to_storage) - if isinstance(current_widget, RadioSet): - current_widget.activated_custom.connect(self.form_to_storage) - elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner): - current_widget.returnPressed.connect(self.form_to_storage) - - def ui_disconnect(self): - # selective plotting - for row in range(self.ui.tools_table.rowCount()): - try: - self.ui.tools_table.cellWidget(row, 5).clicked.disconnect() - except (TypeError, AttributeError): - pass - try: - self.ui.plot_cb.stateChanged.disconnect() - except (TypeError, AttributeError): - pass - - # rows selected - try: - self.ui.tools_table.clicked.disconnect() - except (TypeError, AttributeError): - pass - try: - self.ui.tools_table.horizontalHeader().sectionClicked.disconnect() - except (TypeError, AttributeError): - pass - - # value changed in the particular parameters of a tool - for key, option in self.name2option.items(): - current_widget = self.form_fields[option] - - if isinstance(current_widget, FCCheckBox): - try: - current_widget.stateChanged.disconnect(self.form_to_storage) - except (TypeError, ValueError): - pass - if isinstance(current_widget, RadioSet): - try: - current_widget.activated_custom.disconnect(self.form_to_storage) - except (TypeError, ValueError): - pass - elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner): - try: - current_widget.returnPressed.disconnect(self.form_to_storage) - except (TypeError, ValueError): - pass - - def on_row_selection_change(self): - self.ui_disconnect() - - sel_rows = [] - sel_items = self.ui.tools_table.selectedItems() - for it in sel_items: - sel_rows.append(it.row()) - - if not sel_rows: - self.ui.tool_data_label.setText( - "%s: %s" % (_('Parameters for'), _("No Tool Selected")) - ) - self.ui.generate_cnc_button.setDisabled(True) - self.ui.generate_milling_button.setDisabled(True) - self.ui.generate_milling_slots_button.setDisabled(True) - self.ui_connect() - return - else: - self.ui.generate_cnc_button.setDisabled(False) - self.ui.generate_milling_button.setDisabled(False) - self.ui.generate_milling_slots_button.setDisabled(False) - - if len(sel_rows) == 1: - # update the QLabel that shows for which Tool we have the parameters in the UI form - tooluid = int(self.ui.tools_table.item(sel_rows[0], 0).text()) - self.ui.tool_data_label.setText( - "%s: %s %d" % (_('Parameters for'), _("Tool"), tooluid) - ) - else: - self.ui.tool_data_label.setText( - "%s: %s" % (_('Parameters for'), _("Multiple Tools")) - ) - - for c_row in sel_rows: - # populate the form with the data from the tool associated with the row parameter - try: - item = self.ui.tools_table.item(c_row, 0) - if type(item) is not None: - tooluid = item.text() - self.storage_to_form(self.tools[str(tooluid)]['data']) - else: - self.ui_connect() - return - except Exception as e: - log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e)) - self.ui_connect() - return - - self.ui_connect() - - def storage_to_form(self, dict_storage): - for form_key in self.form_fields: - for storage_key in dict_storage: - if form_key == storage_key and form_key not in \ - ["toolchange", "toolchangez", "startz", "endz", "ppname_e", "ppname_g"]: - try: - self.form_fields[form_key].set_value(dict_storage[form_key]) - except Exception as e: - log.debug("FlatCAMExcellon.storage_to_form() --> %s" % str(e)) - pass - - 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 - return - - self.ui_disconnect() - - widget_changed = self.sender() - wdg_objname = widget_changed.objectName() - option_changed = self.name2option[wdg_objname] - - # row = self.ui.tools_table.currentRow() - rows = sorted(set(index.row() for index in self.ui.tools_table.selectedIndexes())) - for row in rows: - if row < 0: - row = 0 - tooluid_item = int(self.ui.tools_table.item(row, 0).text()) - - for tooluid_key, tooluid_val in self.tools.items(): - if int(tooluid_key) == tooluid_item: - new_option_value = self.form_fields[option_changed].get_value() - if option_changed in tooluid_val: - tooluid_val[option_changed] = new_option_value - if option_changed in tooluid_val['data']: - tooluid_val['data'][option_changed] = new_option_value - - self.ui_connect() - - def on_operation_type(self, val): - if val == 'mill': - self.ui.mill_type_label.show() - self.ui.milling_type_radio.show() - self.ui.mill_dia_label.show() - self.ui.mill_dia_entry.show() - self.ui.frxylabel.show() - self.ui.xyfeedrate_entry.show() - self.ui.extracut_cb.show() - self.ui.e_cut_entry.show() - - # if 'laser' not in self.ui.pp_excellon_name_cb.get_value().lower(): - # self.ui.mpass_cb.show() - # self.ui.maxdepth_entry.show() - else: - self.ui.mill_type_label.hide() - self.ui.milling_type_radio.hide() - self.ui.mill_dia_label.hide() - self.ui.mill_dia_entry.hide() - # self.ui.mpass_cb.hide() - # self.ui.maxdepth_entry.hide() - self.ui.frxylabel.hide() - self.ui.xyfeedrate_entry.hide() - self.ui.extracut_cb.hide() - self.ui.e_cut_entry.hide() - - def get_selected_tools_list(self): - """ - Returns the keys to the self.tools dictionary corresponding - to the selections on the tool list in the GUI. - - :return: List of tools. - :rtype: list - """ - - return [str(x.text()) for x in self.ui.tools_table.selectedItems()] - - def get_selected_tools_table_items(self): - """ - Returns a list of lists, each list in the list is made out of row elements - - :return: List of table_tools items. - :rtype: list - """ - table_tools_items = [] - for x in self.ui.tools_table.selectedItems(): - # from the columnCount we subtract a value of 1 which represent the last column (plot column) - # which does not have text - txt = '' - elem = [] - - for column in range(0, self.ui.tools_table.columnCount() - 1): - try: - txt = self.ui.tools_table.item(x.row(), column).text() - except AttributeError: - try: - txt = self.ui.tools_table.cellWidget(x.row(), column).currentText() - except AttributeError: - pass - elem.append(txt) - table_tools_items.append(deepcopy(elem)) - # table_tools_items.append([self.ui.tools_table.item(x.row(), column).text() - # for column in range(0, self.ui.tools_table.columnCount() - 1)]) - for item in table_tools_items: - item[0] = str(item[0]) - return table_tools_items - - def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'): - """ - Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code - :return: has_slots and Excellon_code - """ - - excellon_code = '' - - # store here if the file has slots, return 1 if any slots, 0 if only drills - has_slots = 0 - - # drills processing - try: - if self.drills: - length = whole + fract - for tool in self.tools: - excellon_code += 'T0%s\n' % str(tool) if int(tool) < 10 else 'T%s\n' % str(tool) - - for drill in self.drills: - if form == 'dec' and tool == drill['tool']: - drill_x = drill['point'].x * factor - drill_y = drill['point'].y * factor - excellon_code += "X{:.{dec}f}Y{:.{dec}f}\n".format(drill_x, drill_y, dec=fract) - elif e_zeros == 'LZ' and tool == drill['tool']: - drill_x = drill['point'].x * factor - drill_y = drill['point'].y * factor - - exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract) - exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract) - - # extract whole part and decimal part - exc_x_formatted = exc_x_formatted.partition('.') - exc_y_formatted = exc_y_formatted.partition('.') - - # left padd the 'whole' part with zeros - x_whole = exc_x_formatted[0].rjust(whole, '0') - y_whole = exc_y_formatted[0].rjust(whole, '0') - - # restore the coordinate padded in the left with 0 and added the decimal part - # without the decinal dot - exc_x_formatted = x_whole + exc_x_formatted[2] - exc_y_formatted = y_whole + exc_y_formatted[2] - - excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted, - yform=exc_y_formatted) - elif tool == drill['tool']: - drill_x = drill['point'].x * factor - drill_y = drill['point'].y * factor - - exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract).replace('.', '') - exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract).replace('.', '') - - # pad with rear zeros - exc_x_formatted.ljust(length, '0') - exc_y_formatted.ljust(length, '0') - - excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted, - yform=exc_y_formatted) - except Exception as e: - log.debug(str(e)) - - # slots processing - try: - if self.slots: - has_slots = 1 - for tool in self.tools: - excellon_code += 'G05\n' - - if int(tool) < 10: - excellon_code += 'T0' + str(tool) + '\n' - else: - excellon_code += 'T' + str(tool) + '\n' - - for slot in self.slots: - if form == 'dec' and tool == slot['tool']: - start_slot_x = slot['start'].x * factor - start_slot_y = slot['start'].y * factor - stop_slot_x = slot['stop'].x * factor - stop_slot_y = slot['stop'].y * factor - if slot_type == 'routing': - excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x, - start_slot_y, - dec=fract) - excellon_code += "G01X{:.{dec}f}Y{:.{dec}f}\nM16\n".format(stop_slot_x, - stop_slot_y, - dec=fract) - elif slot_type == 'drilling': - excellon_code += "X{:.{dec}f}Y{:.{dec}f}G85X{:.{dec}f}Y{:.{dec}f}\nG05\n".format( - start_slot_x, start_slot_y, stop_slot_x, stop_slot_y, dec=fract - ) - - elif e_zeros == 'LZ' and tool == slot['tool']: - start_slot_x = slot['start'].x * factor - start_slot_y = slot['start'].y * factor - stop_slot_x = slot['stop'].x * factor - stop_slot_y = slot['stop'].y * factor - - start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '') - start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '') - stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '') - stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '') - - # extract whole part and decimal part - start_slot_x_formatted = start_slot_x_formatted.partition('.') - start_slot_y_formatted = start_slot_y_formatted.partition('.') - stop_slot_x_formatted = stop_slot_x_formatted.partition('.') - stop_slot_y_formatted = stop_slot_y_formatted.partition('.') - - # left padd the 'whole' part with zeros - start_x_whole = start_slot_x_formatted[0].rjust(whole, '0') - start_y_whole = start_slot_y_formatted[0].rjust(whole, '0') - stop_x_whole = stop_slot_x_formatted[0].rjust(whole, '0') - stop_y_whole = stop_slot_y_formatted[0].rjust(whole, '0') - - # restore the coordinate padded in the left with 0 and added the decimal part - # without the decinal dot - start_slot_x_formatted = start_x_whole + start_slot_x_formatted[2] - start_slot_y_formatted = start_y_whole + start_slot_y_formatted[2] - stop_slot_x_formatted = stop_x_whole + stop_slot_x_formatted[2] - stop_slot_y_formatted = stop_y_whole + stop_slot_y_formatted[2] - - if slot_type == 'routing': - excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted, - ystart=start_slot_y_formatted) - excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted, - ystop=stop_slot_y_formatted) - elif slot_type == 'drilling': - excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format( - xstart=start_slot_x_formatted, ystart=start_slot_y_formatted, - xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted - ) - elif tool == slot['tool']: - start_slot_x = slot['start'].x * factor - start_slot_y = slot['start'].y * factor - stop_slot_x = slot['stop'].x * factor - stop_slot_y = slot['stop'].y * factor - length = whole + fract - - start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '') - start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '') - stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '') - stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '') - - # pad with rear zeros - start_slot_x_formatted.ljust(length, '0') - start_slot_y_formatted.ljust(length, '0') - stop_slot_x_formatted.ljust(length, '0') - stop_slot_y_formatted.ljust(length, '0') - - if slot_type == 'routing': - excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted, - ystart=start_slot_y_formatted) - excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted, - ystop=stop_slot_y_formatted) - elif slot_type == 'drilling': - excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format( - xstart=start_slot_x_formatted, ystart=start_slot_y_formatted, - xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted - ) - except Exception as e: - log.debug(str(e)) - - if not self.drills and not self.slots: - log.debug("FlatCAMObj.FlatCAMExcellon.export_excellon() --> Excellon Object is empty: no drills, no slots.") - return 'fail' - - return has_slots, excellon_code - - def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False): - """ - Note: This method is a good template for generic operations as - it takes it's options from parameters or otherwise from the - object's options and returns a (success, msg) tuple as feedback - for shell operations. - - :return: Success/failure condition tuple (bool, str). - :rtype: tuple - """ - - # Get the tools from the list. These are keys - # to self.tools - if tools is None: - tools = self.get_selected_tools_list() - - if outname is None: - outname = self.options["name"] + "_mill" - - if tooldia is None: - tooldia = float(self.options["tooldia"]) - - # Sort tools by diameter. items() -> [('name', diameter), ...] - # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 - - sort = [] - for k, v in self.tools.items(): - sort.append((k, v.get('C'))) - sorted_tools = sorted(sort, key=lambda t1: t1[1]) - - if tools == "all": - tools = [i[0] for i in sorted_tools] # List if ordered tool names. - log.debug("Tools 'all' and sorted are: %s" % str(tools)) - - if len(tools) == 0: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Please select one or more tools from the list and try again.")) - return False, "Error: No tools." - - for tool in tools: - if tooldia > self.tools[tool]["C"]: - self.app.inform.emit( - '[ERROR_NOTCL] %s %s: %s' % ( - _("Milling tool for DRILLS is larger than hole size. Cancelled."), - _("Tool"), - str(tool) - ) - ) - return False, "Error: Milling tool is larger than hole." - - def geo_init(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ - "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - - # ## Add properties to the object - - # get the tool_table items in a list of row items - tool_table_items = self.get_selected_tools_table_items() - # insert an information only element in the front - tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")]) - - geo_obj.options['Tools_in_use'] = tool_table_items - geo_obj.options['type'] = 'Excellon Geometry' - geo_obj.options["cnctooldia"] = str(tooldia) - - geo_obj.solid_geometry = [] - - # in case that the tool used has the same diameter with the hole, and since the maximum resolution - # for FlatCAM is 6 decimals, - # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" - for hole in self.drills: - if hole['tool'] in tools: - buffer_value = self.tools[hole['tool']]["C"] / 2 - tooldia / 2 - if buffer_value == 0: - geo_obj.solid_geometry.append( - Point(hole['point']).buffer(0.0000001).exterior) - else: - geo_obj.solid_geometry.append( - Point(hole['point']).buffer(buffer_value).exterior) - if use_thread: - def geo_thread(app_obj): - app_obj.new_object("geometry", outname, geo_init, plot=plot) - - # Create a promise with the new name - self.app.collection.promise(outname) - - # Send to worker - self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]}) - else: - self.app.new_object("geometry", outname, geo_init, plot=plot) - - return True, "" - - def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False): - """ - Note: This method is a good template for generic operations as - it takes it's options from parameters or otherwise from the - object's options and returns a (success, msg) tuple as feedback - for shell operations. - - :return: Success/failure condition tuple (bool, str). - :rtype: tuple - """ - - # Get the tools from the list. These are keys - # to self.tools - if tools is None: - tools = self.get_selected_tools_list() - - if outname is None: - outname = self.options["name"] + "_mill" - - if tooldia is None: - tooldia = float(self.options["slot_tooldia"]) - - # Sort tools by diameter. items() -> [('name', diameter), ...] - # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 - - sort = [] - for k, v in self.tools.items(): - sort.append((k, v.get('C'))) - sorted_tools = sorted(sort, key=lambda t1: t1[1]) - - if tools == "all": - tools = [i[0] for i in sorted_tools] # List if ordered tool names. - log.debug("Tools 'all' and sorted are: %s" % str(tools)) - - if len(tools) == 0: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Please select one or more tools from the list and try again.")) - return False, "Error: No tools." - - for tool in tools: - # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse - adj_toolstable_tooldia = float('%.*f' % (self.decimals, float(tooldia))) - adj_file_tooldia = float('%.*f' % (self.decimals, float(self.tools[tool]["C"]))) - if adj_toolstable_tooldia > adj_file_tooldia + 0.0001: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Milling tool for SLOTS is larger than hole size. Cancelled.")) - return False, "Error: Milling tool is larger than hole." - - def geo_init(geo_obj, app_obj): - assert isinstance(geo_obj, FlatCAMGeometry), \ - "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) - - # ## Add properties to the object - - # get the tool_table items in a list of row items - tool_table_items = self.get_selected_tools_table_items() - # insert an information only element in the front - tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")]) - - geo_obj.options['Tools_in_use'] = tool_table_items - geo_obj.options['type'] = 'Excellon Geometry' - geo_obj.options["cnctooldia"] = str(tooldia) - - geo_obj.solid_geometry = [] - - # in case that the tool used has the same diameter with the hole, and since the maximum resolution - # for FlatCAM is 6 decimals, - # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" - for slot in self.slots: - if slot['tool'] in tools: - toolstable_tool = float('%.*f' % (self.decimals, float(tooldia))) - file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["C"]))) - - # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse - # for the file_tool (tooldia actually) - buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001 - if buffer_value == 0: - start = slot['start'] - stop = slot['stop'] - - lines_string = LineString([start, stop]) - poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior - geo_obj.solid_geometry.append(poly) - else: - start = slot['start'] - stop = slot['stop'] - - lines_string = LineString([start, stop]) - poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior - geo_obj.solid_geometry.append(poly) - - if use_thread: - def geo_thread(app_obj): - app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot) - - # Create a promise with the new name - self.app.collection.promise(outname) - - # Send to worker - self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]}) - else: - self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot) - - return True, "" - - def on_generate_milling_button_click(self, *args): - self.app.report_usage("excellon_on_create_milling_drills button") - self.read_form() - - self.generate_milling_drills(use_thread=False) - - def on_generate_milling_slots_button_click(self, *args): - self.app.report_usage("excellon_on_create_milling_slots_button") - self.read_form() - - self.generate_milling_slots(use_thread=False) - - def on_pp_changed(self): - current_pp = self.ui.pp_excellon_name_cb.get_value() - - if "toolchange_probe" in current_pp.lower(): - self.ui.pdepth_entry.setVisible(True) - self.ui.pdepth_label.show() - - self.ui.feedrate_probe_entry.setVisible(True) - self.ui.feedrate_probe_label.show() - else: - self.ui.pdepth_entry.setVisible(False) - self.ui.pdepth_label.hide() - - self.ui.feedrate_probe_entry.setVisible(False) - self.ui.feedrate_probe_label.hide() - - if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower(): - self.ui.feedrate_rapid_label.show() - self.ui.feedrate_rapid_entry.show() - else: - self.ui.feedrate_rapid_label.hide() - self.ui.feedrate_rapid_entry.hide() - - if 'laser' in current_pp.lower(): - self.ui.cutzlabel.hide() - self.ui.cutz_entry.hide() - try: - self.ui.mpass_cb.hide() - self.ui.maxdepth_entry.hide() - except AttributeError: - pass - - if 'marlin' in current_pp.lower(): - self.ui.travelzlabel.setText('%s:' % _("Focus Z")) - self.ui.endz_label.show() - self.ui.endz_entry.show() - else: - self.ui.travelzlabel.hide() - self.ui.travelz_entry.hide() - - self.ui.endz_label.hide() - self.ui.endz_entry.hide() - - try: - self.ui.frzlabel.hide() - self.ui.feedrate_z_entry.hide() - except AttributeError: - pass - - self.ui.dwell_cb.hide() - self.ui.dwelltime_entry.hide() - - self.ui.spindle_label.setText('%s:' % _("Laser Power")) - - try: - self.ui.tool_offset_label.hide() - self.ui.offset_entry.hide() - except AttributeError: - pass - else: - self.ui.cutzlabel.show() - self.ui.cutz_entry.show() - try: - self.ui.mpass_cb.show() - self.ui.maxdepth_entry.show() - except AttributeError: - pass - - self.ui.travelzlabel.setText('%s:' % _('Travel Z')) - - self.ui.travelzlabel.show() - self.ui.travelz_entry.show() - - self.ui.endz_label.show() - self.ui.endz_entry.show() - - try: - self.ui.frzlabel.show() - self.ui.feedrate_z_entry.show() - except AttributeError: - pass - self.ui.dwell_cb.show() - self.ui.dwelltime_entry.show() - - self.ui.spindle_label.setText('%s:' % _('Spindle speed')) - - try: - self.ui.tool_offset_lbl.show() - self.ui.offset_entry.show() - except AttributeError: - pass - - def on_create_cncjob_button_click(self, *args): - self.app.report_usage("excellon_on_create_cncjob_button") - self.read_form() - - # Get the tools from the list - tools = self.get_selected_tools_list() - - if len(tools) == 0: - # if there is a single tool in the table (remember that the last 2 rows are for totals and do not count in - # tool number) it means that there are 3 rows (1 tool and 2 totals). - # in this case regardless of the selection status of that tool, use it. - if self.ui.tools_table.rowCount() == 3: - tools.append(self.ui.tools_table.item(0, 0).text()) - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Please select one or more tools from the list and try again.")) - return - - xmin = self.options['xmin'] - ymin = self.options['ymin'] - xmax = self.options['xmax'] - ymax = self.options['ymax'] - - job_name = self.options["name"] + "_cnc" - pp_excellon_name = self.options["ppname_e"] - - # Object initialization function for app.new_object() - def job_init(job_obj, app_obj): - assert isinstance(job_obj, FlatCAMCNCjob), \ - "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) - - # get the tool_table items in a list of row items - tool_table_items = self.get_selected_tools_table_items() - # insert an information only element in the front - tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")]) - - # ## Add properties to the object - - job_obj.origin_kind = 'excellon' - - job_obj.options['Tools_in_use'] = tool_table_items - job_obj.options['type'] = 'Excellon' - job_obj.options['ppname_e'] = pp_excellon_name - - job_obj.multidepth = self.options["multidepth"] - job_obj.z_depthpercut = self.options["depthperpass"] - - job_obj.z_move = float(self.options["travelz"]) - job_obj.feedrate = float(self.options["feedrate_z"]) - job_obj.z_feedrate = float(self.options["feedrate_z"]) - job_obj.feedrate_rapid = float(self.options["feedrate_rapid"]) - - job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] != 0 else None - job_obj.spindledir = self.app.defaults['excellon_spindledir'] - job_obj.dwell = self.options["dwell"] - job_obj.dwelltime = float(self.options["dwelltime"]) - - job_obj.pp_excellon_name = pp_excellon_name - - job_obj.toolchange_xy_type = "excellon" - job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"]) - job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"]) - - job_obj.options['xmin'] = xmin - job_obj.options['ymin'] = ymin - job_obj.options['xmax'] = xmax - job_obj.options['ymax'] = ymax - - job_obj.z_pdepth = float(self.options["z_pdepth"]) - job_obj.feedrate_probe = float(self.options["feedrate_probe"]) - - job_obj.z_cut = float(self.options['cutz']) - job_obj.toolchange = self.options["toolchange"] - job_obj.xy_toolchange = self.app.defaults["excellon_toolchangexy"] - job_obj.z_toolchange = float(self.options["toolchangez"]) - job_obj.startz = float(self.options["startz"]) if self.options["startz"] else None - job_obj.endz = float(self.options["endz"]) - job_obj.xy_end = self.options["endxy"] - job_obj.excellon_optimization_type = self.app.defaults["excellon_optimization_type"] - - tools_csv = ','.join(tools) - ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, use_ui=True) - - if ret_val == 'fail': - return 'fail' - - job_obj.gcode_parse() - job_obj.create_geometry() - - # To be run in separate thread - def job_thread(app_obj): - with self.app.proc_container.new(_("Generating CNC Code")): - app_obj.new_object("cncjob", job_name, job_init) - - # Create promise for the new name. - self.app.collection.promise(job_name) - - # Send to worker - # self.app.worker.add_task(job_thread, [self.app]) - self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - - def convert_units(self, units): - log.debug("FlatCAMObj.FlatCAMExcellon.convert_units()") - - Excellon.convert_units(self, units) - - # factor = Excellon.convert_units(self, units) - # self.options['drillz'] = float(self.options['drillz']) * factor - # self.options['travelz'] = float(self.options['travelz']) * factor - # self.options['feedrate'] = float(self.options['feedrate']) * factor - # self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor - # self.options['toolchangez'] = float(self.options['toolchangez']) * factor - # - # if self.app.defaults["excellon_toolchangexy"] == '': - # self.options['toolchangexy'] = "0.0, 0.0" - # else: - # coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")] - # if len(coords_xy) < 2: - # self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be " - # "in the format (x, y) \n" - # "but now there is only one value, not two. ")) - # return 'fail' - # coords_xy[0] *= factor - # coords_xy[1] *= factor - # self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) - # - # if self.options['startz'] is not None: - # self.options['startz'] = float(self.options['startz']) * factor - # self.options['endz'] = float(self.options['endz']) * factor - - def on_solid_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('solid') - self.plot() - - def on_plot_cb_click(self, *args): - if self.muted_ui: - return - self.plot() - self.read_form_item('plot') - - self.ui_disconnect() - cb_flag = self.ui.plot_cb.isChecked() - for row in range(self.ui.tools_table.rowCount() - 2): - table_cb = self.ui.tools_table.cellWidget(row, 5) - if cb_flag: - table_cb.setChecked(True) - else: - table_cb.setChecked(False) - - self.ui_connect() - - def on_plot_cb_click_table(self): - # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) - self.ui_disconnect() - # cw = self.sender() - # cw_index = self.ui.tools_table.indexAt(cw.pos()) - # cw_row = cw_index.row() - check_row = 0 - - self.shapes.clear(update=True) - for tool_key in self.tools: - solid_geometry = self.tools[tool_key]['solid_geometry'] - - # find the geo_tool_table row associated with the tool_key - for row in range(self.ui.tools_table.rowCount()): - tool_item = int(self.ui.tools_table.item(row, 0).text()) - if tool_item == int(tool_key): - check_row = row - break - if self.ui.tools_table.cellWidget(check_row, 5).isChecked(): - self.options['plot'] = True - # self.plot_element(element=solid_geometry, visible=True) - # Plot excellon (All polygons?) - if self.options["solid"]: - for geo in solid_geometry: - self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', - visible=self.options['plot'], - layer=2) - else: - for geo in solid_geometry: - self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot']) - for ints in geo.interiors: - self.add_shape(shape=ints, color='green', visible=self.options['plot']) - self.shapes.redraw() - - # make sure that the general plot is disabled if one of the row plot's are disabled and - # if all the row plot's are enabled also enable the general plot checkbox - cb_cnt = 0 - total_row = self.ui.tools_table.rowCount() - for row in range(total_row - 2): - if self.ui.tools_table.cellWidget(row, 5).isChecked(): - cb_cnt += 1 - else: - cb_cnt -= 1 - if cb_cnt < total_row - 2: - self.ui.plot_cb.setChecked(False) - else: - self.ui.plot_cb.setChecked(True) - self.ui_connect() - - def plot(self, visible=None, kind=None): - - # Does all the required setup and returns False - # if the 'ptint' option is set to False. - if not FlatCAMObj.plot(self): - return - - # try: - # # Plot Excellon (All polygons?) - # if self.options["solid"]: - # for tool in self.tools: - # for geo in self.tools[tool]['solid_geometry']: - # self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', - # visible=self.options['plot'], - # layer=2) - # else: - # for tool in self.tools: - # for geo in self.tools[tool]['solid_geometry']: - # self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot']) - # for ints in geo.interiors: - # self.add_shape(shape=ints, color='orange', visible=self.options['plot']) - # - # self.shapes.redraw() - # return - # except (ObjectDeleted, AttributeError, KeyError): - # self.shapes.clear(update=True) - - # this stays for compatibility reasons, in case we try to open old projects - try: - __ = iter(self.solid_geometry) - except TypeError: - self.solid_geometry = [self.solid_geometry] - - visible = visible if visible else self.options['plot'] - - try: - # Plot Excellon (All polygons?) - if self.options["solid"]: - for geo in self.solid_geometry: - self.add_shape(shape=geo, - color=self.outline_color, - face_color=self.fill_color, - visible=visible, - layer=2) - else: - for geo in self.solid_geometry: - self.add_shape(shape=geo.exterior, color='red', visible=visible) - for ints in geo.interiors: - self.add_shape(shape=ints, color='orange', visible=visible) - - self.shapes.redraw() - except (ObjectDeleted, AttributeError): - self.shapes.clear(update=True) - - 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 - log.debug("FlatCAMExcellon.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.") - return - - self.ui_disconnect() - - row = self.ui.tools_table.currentRow() - if row < 0: - row = 0 - - tooluid_item = int(self.ui.tools_table.item(row, 0).text()) - temp_tool_data = {} - - for tooluid_key, tooluid_val in self.tools.items(): - if int(tooluid_key) == tooluid_item: - # this will hold the 'data' key of the self.tools[tool] dictionary that corresponds to - # the current row in the tool table - temp_tool_data = tooluid_val['data'] - break - - for tooluid_key, tooluid_val in self.tools.items(): - tooluid_val['data'] = deepcopy(temp_tool_data) - - self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools.")) - - self.ui_connect() - - -class FlatCAMGeometry(FlatCAMObj, Geometry): - """ - Geometric object not associated with a specific - format. - """ - optionChanged = QtCore.pyqtSignal(str) - ui_type = GeometryObjectUI - - def __init__(self, name): - self.decimals = self.app.decimals - - self.circle_steps = int(self.app.defaults["geometry_circle_steps"]) - - FlatCAMObj.__init__(self, name) - Geometry.__init__(self, geo_steps_per_circle=self.circle_steps) - - self.kind = "geometry" - - self.options.update({ - "plot": True, - "cutz": -0.002, - "vtipdia": 0.1, - "vtipangle": 30, - "travelz": 0.1, - "feedrate": 5.0, - "feedrate_z": 5.0, - "feedrate_rapid": 5.0, - "spindlespeed": 0, - "dwell": True, - "dwelltime": 1000, - "multidepth": False, - "depthperpass": 0.002, - "extracut": False, - "extracut_length": 0.1, - "endz": 2.0, - "endxy": '', - - "startz": None, - "toolchange": False, - "toolchangez": 1.0, - "toolchangexy": "0.0, 0.0", - "ppname_g": 'default', - "z_pdepth": -0.02, - "feedrate_probe": 3.0, - }) - - if "cnctooldia" not in self.options: - if type(self.app.defaults["geometry_cnctooldia"]) == float: - self.options["cnctooldia"] = self.app.defaults["geometry_cnctooldia"] - else: - try: - tools_string = self.app.defaults["geometry_cnctooldia"].split(",") - tools_diameters = [eval(a) for a in tools_string if a != ''] - self.options["cnctooldia"] = tools_diameters[0] if tools_diameters else 0.0 - except Exception as e: - log.debug("FlatCAMObj.FlatCAMGeometry.init() --> %s" % str(e)) - - self.options["startz"] = self.app.defaults["geometry_startz"] - - # this will hold the tool unique ID that is useful when having multiple tools with same diameter - self.tooluid = 0 - - ''' - self.tools = {} - This is a dictionary. Each dict key is associated with a tool used in geo_tools_table. The key is the - tool_id of the tools and the value is another dict that will hold the data under the following form: - {tooluid: { - 'tooldia': 1, - 'offset': 'Path', - 'offset_value': 0.0 - 'type': 'Rough', - 'tool_type': 'C1', - 'data': self.default_tool_data - 'solid_geometry': [] - } - } - ''' - self.tools = {} - - # this dict is to store those elements (tools) of self.tools that are selected in the self.geo_tools_table - # those elements are the ones used for generating GCode - self.sel_tools = {} - - self.offset_item_options = ["Path", "In", "Out", "Custom"] - self.type_item_options = [_("Iso"), _("Rough"), _("Finish")] - self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] - - # flag to store if the V-Shape tool is selected in self.ui.geo_tools_table - self.v_tool_type = None - - # flag to store if the Geometry is type 'multi-geometry' meaning that each tool has it's own geometry - # the default value is False - self.multigeo = False - - # flag to store if the geometry is part of a special group of geometries that can't be processed by the default - # engine of FlatCAM. Most likely are generated by some of tools and are special cases of geometries. - self.special_group = None - - self.old_pp_state = self.app.defaults["geometry_multidepth"] - self.old_toolchangeg_state = self.app.defaults["geometry_toolchange"] - self.units_found = self.app.defaults['units'] - - # this variable can be updated by the Object that generates the geometry - self.tool_type = 'C1' - - # save here the old value for the Cut Z before it is changed by selecting a V-shape type tool in the tool table - self.old_cutz = self.app.defaults["geometry_cutz"] - - self.fill_color = self.app.defaults['geometry_plot_line'] - self.outline_color = self.app.defaults['geometry_plot_line'] - self.alpha_level = 'FF' - - self.param_fields = {} - - # Attributes to be included in serialization - # Always append to it because it carries contents - # from predecessors. - self.ser_attrs += ['options', 'kind', 'tools', 'multigeo'] - - def build_ui(self): - self.ui_disconnect() - FlatCAMObj.build_ui(self) - - self.units = self.app.defaults['units'] - - tool_idx = 0 - - n = len(self.tools) - self.ui.geo_tools_table.setRowCount(n) - - for tooluid_key, tooluid_value in self.tools.items(): - tool_idx += 1 - row_no = tool_idx - 1 - - tool_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) - tool_id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.geo_tools_table.setItem(row_no, 0, tool_id) # Tool name/id - - # Make sure that the tool diameter when in MM is with no more than 2 decimals. - # There are no tool bits in MM with more than 3 decimals diameter. - # For INCH the decimals should be no more than 3. There are no tools under 10mils. - - dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia']))) - - dia_item.setFlags(QtCore.Qt.ItemIsEnabled) - - offset_item = FCComboBox() - for item in self.offset_item_options: - offset_item.addItem(item) - # offset_item.setStyleSheet('background-color: rgb(255,255,255)') - idx = offset_item.findText(tooluid_value['offset']) - offset_item.setCurrentIndex(idx) - - type_item = FCComboBox() - for item in self.type_item_options: - type_item.addItem(item) - # type_item.setStyleSheet('background-color: rgb(255,255,255)') - idx = type_item.findText(tooluid_value['type']) - type_item.setCurrentIndex(idx) - - tool_type_item = FCComboBox() - for item in self.tool_type_item_options: - tool_type_item.addItem(item) - # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)') - idx = tool_type_item.findText(tooluid_value['tool_type']) - tool_type_item.setCurrentIndex(idx) - - tool_uid_item = QtWidgets.QTableWidgetItem(str(tooluid_key)) - - plot_item = FCCheckBox() - plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) - if self.ui.plot_cb.isChecked(): - plot_item.setChecked(True) - - self.ui.geo_tools_table.setItem(row_no, 1, dia_item) # Diameter - self.ui.geo_tools_table.setCellWidget(row_no, 2, offset_item) - self.ui.geo_tools_table.setCellWidget(row_no, 3, type_item) - self.ui.geo_tools_table.setCellWidget(row_no, 4, tool_type_item) - - # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY ### - self.ui.geo_tools_table.setItem(row_no, 5, tool_uid_item) # Tool unique ID - self.ui.geo_tools_table.setCellWidget(row_no, 6, plot_item) - - try: - self.ui.tool_offset_entry.set_value(tooluid_value['offset_value']) - except Exception as e: - log.debug("build_ui() --> Could not set the 'offset_value' key in self.tools. Error: %s" % str(e)) - - # make the diameter column editable - for row in range(tool_idx): - self.ui.geo_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable | - QtCore.Qt.ItemIsEditable | - QtCore.Qt.ItemIsEnabled) - - # sort the tool diameter column - # self.ui.geo_tools_table.sortItems(1) - # all the tools are selected by default - # self.ui.geo_tools_table.selectColumn(0) - - self.ui.geo_tools_table.resizeColumnsToContents() - self.ui.geo_tools_table.resizeRowsToContents() - - vertical_header = self.ui.geo_tools_table.verticalHeader() - # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) - vertical_header.hide() - self.ui.geo_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - horizontal_header = self.ui.geo_tools_table.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(0, 20) - horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(4, 40) - horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(4, 17) - # horizontal_header.setStretchLastSection(True) - self.ui.geo_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self.ui.geo_tools_table.setColumnWidth(0, 20) - self.ui.geo_tools_table.setColumnWidth(4, 40) - self.ui.geo_tools_table.setColumnWidth(6, 17) - - # self.ui.geo_tools_table.setSortingEnabled(True) - - self.ui.geo_tools_table.setMinimumHeight(self.ui.geo_tools_table.getHeight()) - self.ui.geo_tools_table.setMaximumHeight(self.ui.geo_tools_table.getHeight()) - - # update UI for all rows - useful after units conversion but only if there is at least one row - row_cnt = self.ui.geo_tools_table.rowCount() - if row_cnt > 0: - for r in range(row_cnt): - self.update_ui(r) - - # select only the first tool / row - selected_row = 0 - try: - self.select_tools_table_row(selected_row, clearsel=True) - # update the Geometry UI - self.update_ui() - except Exception as e: - # when the tools table is empty there will be this error but once the table is populated it will go away - log.debug(str(e)) - - # disable the Plot column in Tool Table if the geometry is SingleGeo as it is not needed - # and can create some problems - if self.multigeo is False: - self.ui.geo_tools_table.setColumnHidden(6, True) - else: - self.ui.geo_tools_table.setColumnHidden(6, False) - - self.set_tool_offset_visibility(selected_row) - - # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename - # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed. - self.ui.name_entry.set_value(self.options['name']) - self.ui_connect() - - self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \ - self.ui.e_cut_entry.setDisabled(True) - - # set the text on tool_data_label after loading the object - sel_rows = [] - sel_items = self.ui.geo_tools_table.selectedItems() - for it in sel_items: - sel_rows.append(it.row()) - if len(sel_rows) > 1: - self.ui.tool_data_label.setText( - "%s: %s" % (_('Parameters for'), _("Multiple Tools")) - ) - - def set_ui(self, ui): - FlatCAMObj.set_ui(self, ui) - - log.debug("FlatCAMGeometry.set_ui()") - - assert isinstance(self.ui, GeometryObjectUI), \ - "Expected a GeometryObjectUI, got %s" % type(self.ui) - - self.units = self.app.defaults['units'].upper() - self.units_found = self.app.defaults['units'] - - # populate preprocessor names in the combobox - for name in list(self.app.preprocessors.keys()): - self.ui.pp_geometry_name_cb.addItem(name) - - self.form_fields.update({ - "plot": self.ui.plot_cb, - "cutz": self.ui.cutz_entry, - "vtipdia": self.ui.tipdia_entry, - "vtipangle": self.ui.tipangle_entry, - "travelz": self.ui.travelz_entry, - "feedrate": self.ui.cncfeedrate_entry, - "feedrate_z": self.ui.feedrate_z_entry, - "feedrate_rapid": self.ui.feedrate_rapid_entry, - "spindlespeed": self.ui.cncspindlespeed_entry, - "dwell": self.ui.dwell_cb, - "dwelltime": self.ui.dwelltime_entry, - "multidepth": self.ui.mpass_cb, - "ppname_g": self.ui.pp_geometry_name_cb, - "z_pdepth": self.ui.pdepth_entry, - "feedrate_probe": self.ui.feedrate_probe_entry, - "depthperpass": self.ui.maxdepth_entry, - "extracut": self.ui.extracut_cb, - "extracut_length": self.ui.e_cut_entry, - "toolchange": self.ui.toolchangeg_cb, - "toolchangez": self.ui.toolchangez_entry, - "endz": self.ui.endz_entry, - "endxy": self.ui.endxy_entry, - "cnctooldia": self.ui.addtool_entry - }) - - self.param_fields.update({ - "vtipdia": self.ui.tipdia_entry, - "vtipangle": self.ui.tipangle_entry, - "cutz": self.ui.cutz_entry, - "depthperpass": self.ui.maxdepth_entry, - "multidepth": self.ui.mpass_cb, - "travelz": self.ui.travelz_entry, - "feedrate": self.ui.cncfeedrate_entry, - "feedrate_z": self.ui.feedrate_z_entry, - "feedrate_rapid": self.ui.feedrate_rapid_entry, - "extracut": self.ui.extracut_cb, - "extracut_length": self.ui.e_cut_entry, - "spindlespeed": self.ui.cncspindlespeed_entry, - "dwelltime": self.ui.dwelltime_entry, - "dwell": self.ui.dwell_cb, - "pdepth": self.ui.pdepth_entry, - "pfeedrate": self.ui.feedrate_probe_entry, - }) - # Fill form fields only on object create - self.to_form() - - # update the changes in UI depending on the selected preprocessor in Preferences - # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the - # self.ui.pp_geometry_name_cb combobox - self.on_pp_changed() - - self.ui.tipdialabel.hide() - self.ui.tipdia_entry.hide() - self.ui.tipanglelabel.hide() - self.ui.tipangle_entry.hide() - self.ui.cutz_entry.setDisabled(False) - - # store here the default data for Geometry Data - self.default_data = {} - self.default_data.update({ - "name": None, - "plot": None, - "cutz": None, - "vtipdia": None, - "vtipangle": None, - "travelz": None, - "feedrate": None, - "feedrate_z": None, - "feedrate_rapid": None, - "dwell": None, - "dwelltime": None, - "multidepth": None, - "ppname_g": None, - "depthperpass": None, - "extracut": None, - "extracut_length": None, - "toolchange": None, - "toolchangez": None, - "endz": None, - "endxy": '', - "spindlespeed": 0, - "toolchangexy": None, - "startz": None - }) - - # fill in self.default_data values from self.options - for def_key in self.default_data: - for opt_key, opt_val in self.options.items(): - if def_key == opt_key: - self.default_data[def_key] = deepcopy(opt_val) - - if type(self.options["cnctooldia"]) == float: - tools_list = [self.options["cnctooldia"]] - else: - try: - temp_tools = self.options["cnctooldia"].split(",") - tools_list = [ - float(eval(dia)) for dia in temp_tools if dia != '' - ] - except Exception as e: - log.error("FlatCAMGeometry.set_ui() -> At least one tool diameter needed. " - "Verify in Edit -> Preferences -> Geometry General -> Tool dia. %s" % str(e)) - return - - self.tooluid += 1 - - if not self.tools: - for toold in tools_list: - new_data = deepcopy(self.default_data) - self.tools.update({ - self.tooluid: { - 'tooldia': float('%.*f' % (self.decimals, float(toold))), - 'offset': 'Path', - 'offset_value': 0.0, - 'type': _('Rough'), - 'tool_type': self.tool_type, - 'data': new_data, - 'solid_geometry': self.solid_geometry - } - }) - self.tooluid += 1 - else: - # if self.tools is not empty then it can safely be assumed that it comes from an opened project. - # Because of the serialization the self.tools list on project save, the dict keys (members of self.tools - # are each a dict) are turned into strings so we rebuild the self.tools elements so the keys are - # again float type; dict's don't like having keys changed when iterated through therefore the need for the - # following convoluted way of changing the keys from string to float type - temp_tools = {} - for tooluid_key in self.tools: - val = deepcopy(self.tools[tooluid_key]) - new_key = deepcopy(int(tooluid_key)) - temp_tools[new_key] = val - - self.tools.clear() - self.tools = deepcopy(temp_tools) - - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - - # used to store the state of the mpass_cb if the selected preprocessor for geometry is hpgl - self.old_pp_state = self.default_data['multidepth'] - self.old_toolchangeg_state = self.default_data['toolchange'] - - if not isinstance(self.ui, GeometryObjectUI): - log.debug("Expected a GeometryObjectUI, got %s" % type(self.ui)) - return - - self.ui.geo_tools_table.setupContextMenu() - self.ui.geo_tools_table.addContextMenu( - _("Add from Tool DB"), self.on_tool_add_from_db_clicked, - icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")) - self.ui.geo_tools_table.addContextMenu( - _("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(all_tools=None), - icon=QtGui.QIcon(self.app.resource_location + "/delete32.png")) - - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText('%s' % _('Basic')) - - self.ui.geo_tools_table.setColumnHidden(2, True) - self.ui.geo_tools_table.setColumnHidden(3, True) - # self.ui.geo_tools_table.setColumnHidden(4, True) - self.ui.addtool_entry_lbl.hide() - self.ui.addtool_entry.hide() - self.ui.addtool_btn.hide() - self.ui.copytool_btn.hide() - self.ui.deltool_btn.hide() - # self.ui.endz_label.hide() - # self.ui.endz_entry.hide() - self.ui.fr_rapidlabel.hide() - self.ui.feedrate_rapid_entry.hide() - self.ui.extracut_cb.hide() - self.ui.e_cut_entry.hide() - self.ui.pdepth_label.hide() - self.ui.pdepth_entry.hide() - self.ui.feedrate_probe_label.hide() - self.ui.feedrate_probe_entry.hide() - else: - self.ui.level.setText('%s' % _('Advanced')) - - self.ui.e_cut_entry.setDisabled(False) if self.app.defaults['geometry_extracut'] else \ - self.ui.e_cut_entry.setDisabled(True) - self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state)) - - self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click) - self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False)) - self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False)) - self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed) - - self.ui.tipdia_entry.valueChanged.connect(self.update_cutz) - self.ui.tipangle_entry.valueChanged.connect(self.update_cutz) - - self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked) - self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked) - self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed) - - def on_cut_z_changed(self): - self.old_cutz = self.ui.cutz_entry.get_value() - - def set_tool_offset_visibility(self, current_row): - if current_row is None: - return - try: - tool_offset = self.ui.geo_tools_table.cellWidget(current_row, 2) - if tool_offset is not None: - tool_offset_txt = tool_offset.currentText() - if tool_offset_txt == 'Custom': - self.ui.tool_offset_entry.show() - self.ui.tool_offset_lbl.show() - else: - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - except Exception as e: - log.debug("set_tool_offset_visibility() --> " + str(e)) - return - - def on_offset_value_edited(self): - """ - This will save the offset_value into self.tools storage whenever the offset value is edited - :return: - """ - - for current_row in self.ui.geo_tools_table.selectedItems(): - # sometime the header get selected and it has row number -1 - # we don't want to do anything with the header :) - if current_row.row() < 0: - continue - tool_uid = int(self.ui.geo_tools_table.item(current_row.row(), 5).text()) - self.set_tool_offset_visibility(current_row.row()) - - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == tool_uid: - try: - tooluid_value['offset_value'] = float(self.ui.tool_offset_entry.get_value()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - tooluid_value['offset_value'] = float( - self.ui.tool_offset_entry.get_value().replace(',', '.') - ) - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Wrong value format entered, use a number.")) - return - - def ui_connect(self): - # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the - # changes in geometry UI - for i in self.param_fields: - current_widget = self.param_fields[i] - if isinstance(current_widget, FCCheckBox): - current_widget.stateChanged.connect(self.gui_form_to_storage) - elif isinstance(current_widget, FCComboBox): - current_widget.currentIndexChanged.connect(self.gui_form_to_storage) - elif isinstance(current_widget, FloatEntry) or isinstance(current_widget, LengthEntry) or \ - isinstance(current_widget, FCEntry) or isinstance(current_widget, IntEntry): - current_widget.editingFinished.connect(self.gui_form_to_storage) - elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner): - current_widget.returnPressed.connect(self.gui_form_to_storage) - - for row in range(self.ui.geo_tools_table.rowCount()): - for col in [2, 3, 4]: - self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.connect( - self.on_tooltable_cellwidget_change) - - # I use lambda's because the connected functions have parameters that could be used in certain scenarios - self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add()) - - self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy()) - self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete()) - - # self.ui.geo_tools_table.currentItemChanged.connect(self.on_row_selection_change) - self.ui.geo_tools_table.clicked.connect(self.on_row_selection_change) - self.ui.geo_tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change) - - self.ui.geo_tools_table.itemChanged.connect(self.on_tool_edit) - self.ui.tool_offset_entry.returnPressed.connect(self.on_offset_value_edited) - - for row in range(self.ui.geo_tools_table.rowCount()): - self.ui.geo_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table) - self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - - # common parameters update - self.ui.pp_geometry_name_cb.currentIndexChanged.connect(self.update_common_param_in_storage) - - def ui_disconnect(self): - - # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the - # changes in geometry UI - for i in self.param_fields: - # current_widget = self.ui.grid3.itemAt(i).widget() - current_widget = self.param_fields[i] - if isinstance(current_widget, FCCheckBox): - try: - current_widget.stateChanged.disconnect(self.gui_form_to_storage) - except (TypeError, AttributeError): - pass - elif isinstance(current_widget, FCComboBox): - try: - current_widget.currentIndexChanged.disconnect(self.gui_form_to_storage) - except (TypeError, AttributeError): - pass - elif isinstance(current_widget, LengthEntry) or isinstance(current_widget, IntEntry) or \ - isinstance(current_widget, FCEntry) or isinstance(current_widget, FloatEntry): - try: - current_widget.editingFinished.disconnect(self.gui_form_to_storage) - except (TypeError, AttributeError): - pass - elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner): - try: - current_widget.returnPressed.disconnect(self.gui_form_to_storage) - except TypeError: - pass - - for row in range(self.ui.geo_tools_table.rowCount()): - for col in [2, 3, 4]: - try: - self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.addtool_btn.clicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.copytool_btn.clicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.deltool_btn.clicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.geo_tools_table.clicked.disconnect() - except (TypeError, AttributeError): - pass - try: - self.ui.geo_tools_table.horizontalHeader().sectionClicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.geo_tools_table.itemChanged.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.tool_offset_entry.returnPressed.disconnect() - except (TypeError, AttributeError): - pass - - for row in range(self.ui.geo_tools_table.rowCount()): - try: - self.ui.geo_tools_table.cellWidget(row, 6).clicked.disconnect() - except (TypeError, AttributeError): - pass - - try: - self.ui.plot_cb.stateChanged.disconnect() - except (TypeError, AttributeError): - pass - - def on_row_selection_change(self): - self.update_ui() - - def update_ui(self, row=None): - self.ui_disconnect() - - if row is None: - sel_rows = [] - sel_items = self.ui.geo_tools_table.selectedItems() - for it in sel_items: - sel_rows.append(it.row()) - else: - sel_rows = row if type(row) == list else [row] - - if not sel_rows: - sel_rows = [0] - - for current_row in sel_rows: - self.set_tool_offset_visibility(current_row) - - # populate the form with the data from the tool associated with the row parameter - try: - item = self.ui.geo_tools_table.item(current_row, 5) - if type(item) is not None: - tooluid = int(item.text()) - else: - self.ui_connect() - return - except Exception as e: - log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e)) - self.ui_connect() - return - - # update the QLabel that shows for which Tool we have the parameters in the UI form - if len(sel_rows) == 1: - self.ui.tool_data_label.setText( - "%s: %s %d" % (_('Parameters for'), _("Tool"), tooluid) - ) - - # update the form with the V-Shape fields if V-Shape selected in the geo_tool_table - # also modify the Cut Z form entry to reflect the calculated Cut Z from values got from V-Shape Fields - try: - item = self.ui.geo_tools_table.cellWidget(current_row, 4) - if item is not None: - tool_type_txt = item.currentText() - self.ui_update_v_shape(tool_type_txt=tool_type_txt) - else: - self.ui_connect() - return - except Exception as e: - log.debug("Tool missing in ui_update_v_shape(). Add a tool in Geo Tool Table. %s" % str(e)) - return - - try: - # set the form with data from the newly selected tool - for tooluid_key, tooluid_value in list(self.tools.items()): - if int(tooluid_key) == tooluid: - for key, value in list(tooluid_value.items()): - if key == 'data': - form_value_storage = tooluid_value['data'] - self.update_form(form_value_storage) - if key == 'offset_value': - # update the offset value in the entry even if the entry is hidden - self.ui.tool_offset_entry.set_value(tooluid_value['offset_value']) - - if key == 'tool_type' and value == 'V': - self.update_cutz() - except Exception as e: - log.debug("FlatCAMGeometry.update_ui() -> %s " % str(e)) - - else: - self.ui.tool_data_label.setText( - "%s: %s" % (_('Parameters for'), _("Multiple Tools")) - ) - - self.ui_connect() - - def on_tool_add(self, dia=None): - self.ui_disconnect() - - self.units = self.app.defaults['units'].upper() - - if dia is not None: - tooldia = dia - else: - tooldia = float(self.ui.addtool_entry.get_value()) - - # construct a list of all 'tooluid' in the self.tools - # tool_uid_list = [] - # for tooluid_key in self.tools: - # tool_uid_list.append(int(tooluid_key)) - tool_uid_list = [int(tooluid_key) for tooluid_key in self.tools] - - # find maximum from the temp_uid, add 1 and this is the new 'tooluid' - max_uid = max(tool_uid_list) if tool_uid_list else 0 - self.tooluid = max_uid + 1 - - tooldia = float('%.*f' % (self.decimals, tooldia)) - - # here we actually add the new tool; if there is no tool in the tool table we add a tool with default data - # otherwise we add a tool with data copied from last tool - if self.tools: - last_data = self.tools[max_uid]['data'] - last_offset = self.tools[max_uid]['offset'] - last_offset_value = self.tools[max_uid]['offset_value'] - last_type = self.tools[max_uid]['type'] - last_tool_type = self.tools[max_uid]['tool_type'] - last_solid_geometry = self.tools[max_uid]['solid_geometry'] - - # if previous geometry was empty (it may happen for the first tool added) - # then copy the object.solid_geometry - if not last_solid_geometry: - last_solid_geometry = self.solid_geometry - - self.tools.update({ - self.tooluid: { - 'tooldia': tooldia, - 'offset': last_offset, - 'offset_value': last_offset_value, - 'type': last_type, - 'tool_type': last_tool_type, - 'data': deepcopy(last_data), - 'solid_geometry': deepcopy(last_solid_geometry) - } - }) - else: - self.tools.update({ - self.tooluid: { - 'tooldia': tooldia, - 'offset': 'Path', - 'offset_value': 0.0, - 'type': _('Rough'), - 'tool_type': 'C1', - 'data': deepcopy(self.default_data), - 'solid_geometry': self.solid_geometry - } - }) - - self.tools[self.tooluid]['data']['name'] = self.options['name'] - - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - - # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list - try: - self.ser_attrs.remove('tools') - except TypeError: - pass - self.ser_attrs.append('tools') - - self.app.inform.emit('[success] %s' % _("Tool added in Tool Table.")) - self.ui_connect() - self.build_ui() - - # if there is no tool left in the Tools Table, enable the parameters GUI - if self.ui.geo_tools_table.rowCount() != 0: - self.ui.geo_param_frame.setDisabled(False) - - def on_tool_add_from_db_clicked(self): - """ - Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object - and display the Tools Database tab in the form needed for the Tool adding - :return: None - """ - - # if the Tools Database is already opened focus on it - for idx in range(self.app.ui.plot_tab_area.count()): - if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): - self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab) - break - self.app.on_tools_database() - self.app.tools_db_tab.ok_to_add = True - self.app.tools_db_tab.buttons_frame.hide() - self.app.tools_db_tab.add_tool_from_db.show() - self.app.tools_db_tab.cancel_tool_from_db.show() - - def on_tool_from_db_inserted(self, tool): - """ - Called from the Tools DB object through a App method when adding a tool from Tools Database - :param tool: a dict with the tool data - :return: None - """ - - self.ui_disconnect() - self.units = self.app.defaults['units'].upper() - - tooldia = float(tool['tooldia']) - - # construct a list of all 'tooluid' in the self.tools - tool_uid_list = [] - for tooluid_key in self.tools: - tool_uid_item = int(tooluid_key) - tool_uid_list.append(tool_uid_item) - - # find maximum from the temp_uid, add 1 and this is the new 'tooluid' - if not tool_uid_list: - max_uid = 0 - else: - max_uid = max(tool_uid_list) - self.tooluid = max_uid + 1 - - tooldia = float('%.*f' % (self.decimals, tooldia)) - - self.tools.update({ - self.tooluid: { - 'tooldia': tooldia, - 'offset': tool['offset'], - 'offset_value': float(tool['offset_value']), - 'type': tool['type'], - 'tool_type': tool['tool_type'], - 'data': deepcopy(tool['data']), - 'solid_geometry': self.solid_geometry - } - }) - - self.tools[self.tooluid]['data']['name'] = self.options['name'] - - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - - # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list - try: - self.ser_attrs.remove('tools') - except TypeError: - pass - self.ser_attrs.append('tools') - - self.ui_connect() - self.build_ui() - - # if there is no tool left in the Tools Table, enable the parameters GUI - if self.ui.geo_tools_table.rowCount() != 0: - self.ui.geo_param_frame.setDisabled(False) - - def on_tool_copy(self, all_tools=None): - self.ui_disconnect() - - # find the tool_uid maximum value in the self.tools - uid_list = [] - for key in self.tools: - uid_list.append(int(key)) - try: - max_uid = max(uid_list, key=int) - except ValueError: - max_uid = 0 - - if all_tools is None: - if self.ui.geo_tools_table.selectedItems(): - for current_row in self.ui.geo_tools_table.selectedItems(): - # sometime the header get selected and it has row number -1 - # we don't want to do anything with the header :) - if current_row.row() < 0: - continue - try: - tooluid_copy = int(self.ui.geo_tools_table.item(current_row.row(), 5).text()) - self.set_tool_offset_visibility(current_row.row()) - max_uid += 1 - self.tools[int(max_uid)] = deepcopy(self.tools[tooluid_copy]) - except AttributeError: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy.")) - self.ui_connect() - self.build_ui() - return - except Exception as e: - log.debug("on_tool_copy() --> " + str(e)) - # deselect the table - # self.ui.geo_tools_table.clearSelection() - else: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy.")) - self.ui_connect() - self.build_ui() - return - else: - # we copy all tools in geo_tools_table - try: - temp_tools = deepcopy(self.tools) - max_uid += 1 - for tooluid in temp_tools: - self.tools[int(max_uid)] = deepcopy(temp_tools[tooluid]) - temp_tools.clear() - except Exception as e: - log.debug("on_tool_copy() --> " + str(e)) - - # if there are no more tools in geo tools table then hide the tool offset - if not self.tools: - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - - # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list - try: - self.ser_attrs.remove('tools') - except ValueError: - pass - self.ser_attrs.append('tools') - - self.ui_connect() - self.build_ui() - self.app.inform.emit('[success] %s' % _("Tool was copied in Tool Table.")) - - def on_tool_edit(self, current_item): - self.ui_disconnect() - - current_row = current_item.row() - try: - d = float(self.ui.geo_tools_table.item(current_row, 1).text()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - d = float(self.ui.geo_tools_table.item(current_row, 1).text().replace(',', '.')) - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) - return - - tool_dia = float('%.*f' % (self.decimals, d)) - tooluid = int(self.ui.geo_tools_table.item(current_row, 5).text()) - - self.tools[tooluid]['tooldia'] = tool_dia - - try: - self.ser_attrs.remove('tools') - self.ser_attrs.append('tools') - except (TypeError, ValueError): - pass - - self.app.inform.emit('[success] %s' % _("Tool was edited in Tool Table.")) - self.ui_connect() - self.build_ui() - - def on_tool_delete(self, all_tools=None): - self.ui_disconnect() - - if all_tools is None: - if self.ui.geo_tools_table.selectedItems(): - for current_row in self.ui.geo_tools_table.selectedItems(): - # sometime the header get selected and it has row number -1 - # we don't want to do anything with the header :) - if current_row.row() < 0: - continue - try: - tooluid_del = int(self.ui.geo_tools_table.item(current_row.row(), 5).text()) - self.set_tool_offset_visibility(current_row.row()) - - temp_tools = deepcopy(self.tools) - for tooluid_key in self.tools: - if int(tooluid_key) == tooluid_del: - # if the self.tools has only one tool and we delete it then we move the solid_geometry - # as a property of the object otherwise there will be nothing to hold it - if len(self.tools) == 1: - self.solid_geometry = deepcopy(self.tools[tooluid_key]['solid_geometry']) - temp_tools.pop(tooluid_del, None) - self.tools = deepcopy(temp_tools) - temp_tools.clear() - except AttributeError: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete.")) - self.ui_connect() - self.build_ui() - return - except Exception as e: - log.debug("on_tool_delete() --> " + str(e)) - # deselect the table - # self.ui.geo_tools_table.clearSelection() - else: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete.")) - self.ui_connect() - self.build_ui() - return - else: - # we delete all tools in geo_tools_table - self.tools.clear() - - self.app.plot_all() - - # if there are no more tools in geo tools table then hide the tool offset - if not self.tools: - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - - # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list - try: - self.ser_attrs.remove('tools') - except TypeError: - pass - self.ser_attrs.append('tools') - - self.ui_connect() - self.build_ui() - self.app.inform.emit('[success] %s' % _("Tool was deleted in Tool Table.")) - - obj_active = self.app.collection.get_active() - # if the object was MultiGeo and now it has no tool at all (therefore no geometry) - # we make it back SingleGeo - if self.ui.geo_tools_table.rowCount() <= 0: - obj_active.multigeo = False - obj_active.options['xmin'] = 0 - obj_active.options['ymin'] = 0 - obj_active.options['xmax'] = 0 - obj_active.options['ymax'] = 0 - - if obj_active.multigeo is True: - try: - xmin, ymin, xmax, ymax = obj_active.bounds() - obj_active.options['xmin'] = xmin - obj_active.options['ymin'] = ymin - obj_active.options['xmax'] = xmax - obj_active.options['ymax'] = ymax - except Exception: - obj_active.options['xmin'] = 0 - obj_active.options['ymin'] = 0 - obj_active.options['xmax'] = 0 - obj_active.options['ymax'] = 0 - - # if there is no tool left in the Tools Table, disable the parameters GUI - if self.ui.geo_tools_table.rowCount() == 0: - self.ui.geo_param_frame.setDisabled(True) - - def ui_update_v_shape(self, tool_type_txt): - if tool_type_txt == 'V': - self.ui.tipdialabel.show() - self.ui.tipdia_entry.show() - self.ui.tipanglelabel.show() - self.ui.tipangle_entry.show() - self.ui.cutz_entry.setDisabled(True) - - self.update_cutz() - else: - self.ui.tipdialabel.hide() - self.ui.tipdia_entry.hide() - self.ui.tipanglelabel.hide() - self.ui.tipangle_entry.hide() - self.ui.cutz_entry.setDisabled(False) - - def update_cutz(self): - vdia = float(self.ui.tipdia_entry.get_value()) - half_vangle = float(self.ui.tipangle_entry.get_value()) / 2 - - row = self.ui.geo_tools_table.currentRow() - tool_uid_item = self.ui.geo_tools_table.item(row, 5) - if tool_uid_item is None: - return - tool_uid = int(tool_uid_item.text()) - - tool_dia_item = self.ui.geo_tools_table.item(row, 1) - if tool_dia_item is None: - return - tooldia = float(tool_dia_item.text()) - - try: - new_cutz = (tooldia - vdia) / (2 * math.tan(math.radians(half_vangle))) - except ZeroDivisionError: - new_cutz = self.old_cutz - - new_cutz = float('%.*f' % (self.decimals, new_cutz)) * -1.0 # this value has to be negative - - self.ui.cutz_entry.set_value(new_cutz) - - # store the new CutZ value into storage (self.tools) - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == tool_uid: - tooluid_value['data']['cutz'] = new_cutz - - def on_tooltable_cellwidget_change(self): - cw = self.sender() - cw_index = self.ui.geo_tools_table.indexAt(cw.pos()) - cw_row = cw_index.row() - cw_col = cw_index.column() - current_uid = int(self.ui.geo_tools_table.item(cw_row, 5).text()) - - # store the text of the cellWidget that changed it's index in the self.tools - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == current_uid: - cb_txt = cw.currentText() - if cw_col == 2: - tooluid_value['offset'] = cb_txt - if cb_txt == 'Custom': - self.ui.tool_offset_entry.show() - self.ui.tool_offset_lbl.show() - else: - self.ui.tool_offset_entry.hide() - self.ui.tool_offset_lbl.hide() - # reset the offset_value in storage self.tools - tooluid_value['offset_value'] = 0.0 - elif cw_col == 3: - # force toolpath type as 'Iso' if the tool type is V-Shape - if self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText() == 'V': - tooluid_value['type'] = _('Iso') - idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso')) - self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx) - else: - tooluid_value['type'] = cb_txt - elif cw_col == 4: - tooluid_value['tool_type'] = cb_txt - - # if the tool_type selected is V-Shape then autoselect the toolpath type as Iso - if cb_txt == 'V': - idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso')) - self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx) - else: - self.ui.cutz_entry.set_value(self.old_cutz) - - self.ui_update_v_shape(tool_type_txt=self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText()) - - def update_form(self, dict_storage): - for form_key in self.form_fields: - for storage_key in dict_storage: - if form_key == storage_key: - try: - self.form_fields[form_key].set_value(dict_storage[form_key]) - except Exception as e: - log.debug(str(e)) - - # this is done here because those buttons control through OptionalInputSelection if some entry's are Enabled - # or not. But due of using the ui_disconnect() status is no longer updated and I had to do it here - self.ui.ois_dwell_geo.on_cb_change() - self.ui.ois_mpass_geo.on_cb_change() - self.ui.ois_tcz_geo.on_cb_change() - - def on_apply_param_to_all_clicked(self): - if self.ui.geo_tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage - log.debug("FlatCAMGeometry.gui_form_to_storage() --> no tool in Tools Table, aborting.") - return - - self.ui_disconnect() - - row = self.ui.geo_tools_table.currentRow() - if row < 0: - row = 0 - - # store all the data associated with the row parameter to the self.tools storage - tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text()) - offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText() - type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText() - tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText() - - offset_value_item = float(self.ui.tool_offset_entry.get_value()) - - # this new dict will hold the actual useful data, another dict that is the value of key 'data' - temp_tools = {} - temp_dia = {} - temp_data = {} - - for tooluid_key, tooluid_value in self.tools.items(): - for key, value in tooluid_value.items(): - if key == 'tooldia': - temp_dia[key] = tooldia_item - # update the 'offset', 'type' and 'tool_type' sections - if key == 'offset': - temp_dia[key] = offset_item - if key == 'type': - temp_dia[key] = type_item - if key == 'tool_type': - temp_dia[key] = tool_type_item - if key == 'offset_value': - temp_dia[key] = offset_value_item - - if key == 'data': - # update the 'data' section - for data_key in tooluid_value[key].keys(): - for form_key, form_value in self.form_fields.items(): - if form_key == data_key: - temp_data[data_key] = form_value.get_value() - # make sure we make a copy of the keys not in the form (we may use 'data' keys that are - # updated from self.app.defaults - if data_key not in self.form_fields: - temp_data[data_key] = value[data_key] - temp_dia[key] = deepcopy(temp_data) - temp_data.clear() - - if key == 'solid_geometry': - temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry']) - - temp_tools[tooluid_key] = deepcopy(temp_dia) - - self.tools.clear() - self.tools = deepcopy(temp_tools) - temp_tools.clear() - - self.ui_connect() - - def gui_form_to_storage(self): - if self.ui.geo_tools_table.rowCount() == 0: - # there is no tool in tool table so we can't save the GUI elements values to storage - log.debug("FlatCAMGeometry.gui_form_to_storage() --> no tool in Tools Table, aborting.") - return - - self.ui_disconnect() - widget_changed = self.sender() - try: - widget_idx = self.ui.grid3.indexOf(widget_changed) - except Exception: - return - - # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z - if widget_idx == 1 or widget_idx == 3: - self.update_cutz() - - # the original connect() function of the OptionalInputSelection is no longer working because of the - # ui_diconnect() so I use this 'hack' - if isinstance(widget_changed, FCCheckBox): - if widget_changed.text() == 'Multi-Depth:': - self.ui.ois_mpass_geo.on_cb_change() - - if widget_changed.text() == 'Tool change': - self.ui.ois_tcz_geo.on_cb_change() - - if widget_changed.text() == 'Dwell:': - self.ui.ois_dwell_geo.on_cb_change() - - row = self.ui.geo_tools_table.currentRow() - if row < 0: - row = 0 - - # store all the data associated with the row parameter to the self.tools storage - tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text()) - offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText() - type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText() - tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText() - tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text()) - - offset_value_item = float(self.ui.tool_offset_entry.get_value()) - - # this new dict will hold the actual useful data, another dict that is the value of key 'data' - temp_tools = {} - temp_dia = {} - temp_data = {} - - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == tooluid_item: - for key, value in tooluid_value.items(): - if key == 'tooldia': - temp_dia[key] = tooldia_item - # update the 'offset', 'type' and 'tool_type' sections - if key == 'offset': - temp_dia[key] = offset_item - if key == 'type': - temp_dia[key] = type_item - if key == 'tool_type': - temp_dia[key] = tool_type_item - if key == 'offset_value': - temp_dia[key] = offset_value_item - - if key == 'data': - # update the 'data' section - for data_key in tooluid_value[key].keys(): - for form_key, form_value in self.form_fields.items(): - if form_key == data_key: - temp_data[data_key] = form_value.get_value() - # make sure we make a copy of the keys not in the form (we may use 'data' keys that are - # updated from self.app.defaults - if data_key not in self.form_fields: - temp_data[data_key] = value[data_key] - temp_dia[key] = deepcopy(temp_data) - temp_data.clear() - - if key == 'solid_geometry': - temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry']) - - temp_tools[tooluid_key] = deepcopy(temp_dia) - else: - temp_tools[tooluid_key] = deepcopy(tooluid_value) - - self.tools.clear() - self.tools = deepcopy(temp_tools) - temp_tools.clear() - self.ui_connect() - - def update_common_param_in_storage(self): - for tooluid_value in self.tools.values(): - tooluid_value['data']['ppname_g'] = self.ui.pp_geometry_name_cb.get_value() - - def select_tools_table_row(self, row, clearsel=None): - if clearsel: - self.ui.geo_tools_table.clearSelection() - - if self.ui.geo_tools_table.rowCount() > 0: - # self.ui.geo_tools_table.item(row, 0).setSelected(True) - self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0)) - - def export_dxf(self): - dwg = None - try: - dwg = ezdxf.new('R2010') - msp = dwg.modelspace() - - def g2dxf(dxf_space, geo_obj): - if isinstance(geo_obj, MultiPolygon): - for poly in geo_obj: - ext_points = list(poly.exterior.coords) - dxf_space.add_lwpolyline(ext_points) - for interior in poly.interiors: - dxf_space.add_lwpolyline(list(interior.coords)) - if isinstance(geo_obj, Polygon): - ext_points = list(geo_obj.exterior.coords) - dxf_space.add_lwpolyline(ext_points) - for interior in geo_obj.interiors: - dxf_space.add_lwpolyline(list(interior.coords)) - if isinstance(geo_obj, MultiLineString): - for line in geo_obj: - dxf_space.add_lwpolyline(list(line.coords)) - if isinstance(geo_obj, LineString) or isinstance(geo_obj, LinearRing): - dxf_space.add_lwpolyline(list(geo_obj.coords)) - - multigeo_solid_geometry = [] - if self.multigeo: - for tool in self.tools: - multigeo_solid_geometry += self.tools[tool]['solid_geometry'] - else: - multigeo_solid_geometry = self.solid_geometry - - for geo in multigeo_solid_geometry: - if type(geo) == list: - for g in geo: - g2dxf(msp, g) - else: - g2dxf(msp, geo) - - # points = FlatCAMGeometry.get_pts(geo) - # msp.add_lwpolyline(points) - except Exception as e: - log.debug(str(e)) - - return dwg - - def get_selected_tools_table_items(self): - """ - Returns a list of lists, each list in the list is made out of row elements - - :return: List of table_tools items. - :rtype: list - """ - table_tools_items = [] - if self.multigeo: - for x in self.ui.geo_tools_table.selectedItems(): - elem = [] - txt = '' - - for column in range(0, self.ui.geo_tools_table.columnCount()): - try: - txt = self.ui.geo_tools_table.item(x.row(), column).text() - except AttributeError: - try: - txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText() - except AttributeError: - pass - elem.append(txt) - table_tools_items.append(deepcopy(elem)) - # table_tools_items.append([self.ui.geo_tools_table.item(x.row(), column).text() - # for column in range(0, self.ui.geo_tools_table.columnCount())]) - else: - for x in self.ui.geo_tools_table.selectedItems(): - r = [] - txt = '' - - # the last 2 columns for single-geo geometry are irrelevant and create problems reading - # so we don't read them - for column in range(0, self.ui.geo_tools_table.columnCount() - 2): - # the columns have items that have text but also have items that are widgets - # for which the text they hold has to be read differently - try: - txt = self.ui.geo_tools_table.item(x.row(), column).text() - except AttributeError: - try: - txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText() - except AttributeError: - pass - r.append(txt) - table_tools_items.append(r) - - for item in table_tools_items: - item[0] = str(item[0]) - return table_tools_items - - def on_pp_changed(self): - current_pp = self.ui.pp_geometry_name_cb.get_value() - if current_pp == 'hpgl': - self.old_pp_state = self.ui.mpass_cb.get_value() - self.old_toolchangeg_state = self.ui.toolchangeg_cb.get_value() - - self.ui.mpass_cb.set_value(False) - self.ui.mpass_cb.setDisabled(True) - - self.ui.toolchangeg_cb.set_value(True) - self.ui.toolchangeg_cb.setDisabled(True) - else: - self.ui.mpass_cb.set_value(self.old_pp_state) - self.ui.mpass_cb.setDisabled(False) - - self.ui.toolchangeg_cb.set_value(self.old_toolchangeg_state) - self.ui.toolchangeg_cb.setDisabled(False) - - if "toolchange_probe" in current_pp.lower(): - self.ui.pdepth_entry.setVisible(True) - self.ui.pdepth_label.show() - - self.ui.feedrate_probe_entry.setVisible(True) - self.ui.feedrate_probe_label.show() - else: - self.ui.pdepth_entry.setVisible(False) - self.ui.pdepth_label.hide() - - self.ui.feedrate_probe_entry.setVisible(False) - self.ui.feedrate_probe_label.hide() - - if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower(): - self.ui.fr_rapidlabel.show() - self.ui.feedrate_rapid_entry.show() - else: - self.ui.fr_rapidlabel.hide() - self.ui.feedrate_rapid_entry.hide() - - if 'laser' in current_pp.lower(): - self.ui.cutzlabel.hide() - self.ui.cutz_entry.hide() - try: - self.ui.mpass_cb.hide() - self.ui.maxdepth_entry.hide() - except AttributeError: - pass - - if 'marlin' in current_pp.lower(): - self.ui.travelzlabel.setText('%s:' % _("Focus Z")) - self.ui.endz_label.show() - self.ui.endz_entry.show() - else: - self.ui.travelzlabel.hide() - self.ui.travelz_entry.hide() - - self.ui.endz_label.hide() - self.ui.endz_entry.hide() - - try: - self.ui.frzlabel.hide() - self.ui.feedrate_z_entry.hide() - except AttributeError: - pass - - self.ui.dwell_cb.hide() - self.ui.dwelltime_entry.hide() - - self.ui.spindle_label.setText('%s:' % _("Laser Power")) - - try: - self.ui.tool_offset_label.hide() - self.ui.offset_entry.hide() - except AttributeError: - pass - else: - self.ui.cutzlabel.show() - self.ui.cutz_entry.show() - try: - self.ui.mpass_cb.show() - self.ui.maxdepth_entry.show() - except AttributeError: - pass - - self.ui.travelzlabel.setText('%s:' % _('Travel Z')) - - self.ui.travelzlabel.show() - self.ui.travelz_entry.show() - - self.ui.endz_label.show() - self.ui.endz_entry.show() - - try: - self.ui.frzlabel.show() - self.ui.feedrate_z_entry.show() - except AttributeError: - pass - self.ui.dwell_cb.show() - self.ui.dwelltime_entry.show() - - self.ui.spindle_label.setText('%s:' % _('Spindle speed')) - - try: - self.ui.tool_offset_lbl.show() - self.ui.offset_entry.show() - except AttributeError: - pass - - def on_generatecnc_button_click(self, *args): - log.debug("Generating CNCJob from Geometry ...") - self.app.report_usage("geometry_on_generatecnc_button") - - # this reads the values in the UI form to the self.options dictionary - self.read_form() - - self.sel_tools = {} - - try: - if self.special_group: - self.app.inform.emit( - '[WARNING_NOTCL] %s %s %s.' % - (_("This Geometry can't be processed because it is"), str(self.special_group), _("geometry")) - ) - return - except AttributeError: - pass - - # test to see if we have tools available in the tool table - if self.ui.geo_tools_table.selectedItems(): - for x in self.ui.geo_tools_table.selectedItems(): - # try: - # tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text()) - # except ValueError: - # # try to convert comma to decimal point. if it's still not working error message and return - # try: - # tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text().replace(',', '.')) - # except ValueError: - # self.app.inform.emit('[ERROR_NOTCL] %s' % - # _("Wrong value format entered, use a number.")) - # return - tooluid = int(self.ui.geo_tools_table.item(x.row(), 5).text()) - - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == tooluid: - self.sel_tools.update({ - tooluid: deepcopy(tooluid_value) - }) - self.mtool_gen_cncjob() - self.ui.geo_tools_table.clearSelection() - - elif self.ui.geo_tools_table.rowCount() == 1: - tooluid = int(self.ui.geo_tools_table.item(0, 5).text()) - - for tooluid_key, tooluid_value in self.tools.items(): - if int(tooluid_key) == tooluid: - self.sel_tools.update({ - tooluid: deepcopy(tooluid_value) - }) - self.mtool_gen_cncjob() - self.ui.geo_tools_table.clearSelection() - - else: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No tool selected in the tool table ...")) - - def mtool_gen_cncjob(self, outname=None, tools_dict=None, tools_in_use=None, segx=None, segy=None, - plot=True, use_thread=True): - """ - Creates a multi-tool CNCJob out of this Geometry object. - The actual work is done by the target FlatCAMCNCjob object's - `generate_from_geometry_2()` method. - - :param tools_dict: a dictionary that holds the whole data needed to create the Gcode - (including the solid_geometry) - - :param tools_in_use: the tools that are used, needed by some preprocessors - :type list of lists, each list in the list is made out of row elements of tools table from GUI - - :param outname: - :param tools_dict: - :param tools_in_use: - :param segx: number of segments on the X axis, for auto-levelling - :param segy: number of segments on the Y axis, for auto-levelling - :param plot: if True the generated object will be plotted; if False will not be plotted - :param use_thread: if True use threading - :return: None - """ - - # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia - outname = "%s_%s" % (self.options["name"], 'cnc') if outname is None else outname - - tools_dict = self.sel_tools if tools_dict is None else tools_dict - tools_in_use = tools_in_use if tools_in_use is not None else self.get_selected_tools_table_items() - segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) - segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) - - try: - xmin = self.options['xmin'] - ymin = self.options['ymin'] - xmax = self.options['xmax'] - ymax = self.options['ymax'] - except Exception as e: - log.debug("FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() --> %s\n" % str(e)) - - msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") - msg += '%s %s' % ('FlatCAMObj.FlatCAMGeometry.mtool_gen_cncjob() -->', str(e)) - msg += traceback.format_exc() - self.app.inform.emit(msg) - return - - # Object initialization function for app.new_object() - # RUNNING ON SEPARATE THREAD! - def job_init_single_geometry(job_obj, app_obj): - log.debug("Creating a CNCJob out of a single-geometry") - assert isinstance(job_obj, FlatCAMCNCjob), \ - "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) - - job_obj.options['xmin'] = xmin - job_obj.options['ymin'] = ymin - job_obj.options['xmax'] = xmax - job_obj.options['ymax'] = ymax - - # count the tools - tool_cnt = 0 - - dia_cnc_dict = {} - - # this turn on the FlatCAMCNCJob plot for multiple tools - job_obj.multitool = True - job_obj.multigeo = False - job_obj.cnc_tools.clear() - - job_obj.options['Tools_in_use'] = tools_in_use - job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"]) - job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"]) - - job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"]) - job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"]) - - for tooluid_key in list(tools_dict.keys()): - tool_cnt += 1 - - dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) - tooldia_val = float('%.*f' % (self.decimals, float(tools_dict[tooluid_key]['tooldia']))) - dia_cnc_dict.update({ - 'tooldia': tooldia_val - }) - - if dia_cnc_dict['offset'] == 'in': - tool_offset = -dia_cnc_dict['tooldia'] / 2 - elif dia_cnc_dict['offset'].lower() == 'out': - tool_offset = dia_cnc_dict['tooldia'] / 2 - elif dia_cnc_dict['offset'].lower() == 'custom': - try: - offset_value = float(self.ui.tool_offset_entry.get_value()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - offset_value = float(self.ui.tool_offset_entry.get_value().replace(',', '.')) - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) - return - if offset_value: - tool_offset = float(offset_value) - else: - self.app.inform.emit( - '[WARNING] %s' % _("Tool Offset is selected in Tool Table but no value is provided.\n" - "Add a Tool Offset or change the Offset Type.") - ) - return - else: - tool_offset = 0.0 - - dia_cnc_dict.update({ - 'offset_value': tool_offset - }) - - z_cut = tools_dict[tooluid_key]['data']["cutz"] - z_move = tools_dict[tooluid_key]['data']["travelz"] - feedrate = tools_dict[tooluid_key]['data']["feedrate"] - feedrate_z = tools_dict[tooluid_key]['data']["feedrate_z"] - feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"] - multidepth = tools_dict[tooluid_key]['data']["multidepth"] - extracut = tools_dict[tooluid_key]['data']["extracut"] - extracut_length = tools_dict[tooluid_key]['data']["extracut_length"] - depthpercut = tools_dict[tooluid_key]['data']["depthperpass"] - toolchange = tools_dict[tooluid_key]['data']["toolchange"] - toolchangez = tools_dict[tooluid_key]['data']["toolchangez"] - toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"] - startz = tools_dict[tooluid_key]['data']["startz"] - endz = tools_dict[tooluid_key]['data']["endz"] - endxy = self.options["endxy"] - spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"] - dwell = tools_dict[tooluid_key]['data']["dwell"] - dwelltime = tools_dict[tooluid_key]['data']["dwelltime"] - pp_geometry_name = tools_dict[tooluid_key]['data']["ppname_g"] - - spindledir = self.app.defaults['geometry_spindledir'] - tool_solid_geometry = self.solid_geometry - - job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] - job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] - - # Propagate options - job_obj.options["tooldia"] = tooldia_val - job_obj.options['type'] = 'Geometry' - job_obj.options['tool_dia'] = tooldia_val - - # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially - # to a value of 0.0005 which is 20 times less than 0.01 - tol = float(self.app.defaults['global_tolerance']) / 20 - res = job_obj.generate_from_geometry_2( - self, tooldia=tooldia_val, offset=tool_offset, tolerance=tol, - z_cut=z_cut, z_move=z_move, - feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, - spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, - multidepth=multidepth, depthpercut=depthpercut, - extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, - toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, - pp_geometry_name=pp_geometry_name, - tool_no=tool_cnt) - - if res == 'fail': - log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed") - return 'fail' - else: - dia_cnc_dict['gcode'] = res - - # tell gcode_parse from which point to start drawing the lines depending on what kind of - # object is the source of gcode - job_obj.toolchange_xy_type = "geometry" - - self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) - dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() - self.app.inform.emit('[success] %s' % _("G-Code parsing finished...")) - - # TODO this serve for bounding box creation only; should be optimized - # commented this; there is no need for the actual GCode geometry - the original one will serve as well - # for bounding box values - # dia_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']]) - try: - dia_cnc_dict['solid_geometry'] = tool_solid_geometry - self.app.inform.emit('[success] %s...' % _("Finished G-Code processing")) - except Exception as e: - self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(e))) - - job_obj.cnc_tools.update({ - tooluid_key: deepcopy(dia_cnc_dict) - }) - dia_cnc_dict.clear() - - # Object initialization function for app.new_object() - # RUNNING ON SEPARATE THREAD! - def job_init_multi_geometry(job_obj, app_obj): - log.debug("Creating a CNCJob out of a multi-geometry") - assert isinstance(job_obj, FlatCAMCNCjob), \ - "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) - - current_uid = int(1) - - job_obj.options['xmin'] = xmin - job_obj.options['ymin'] = ymin - job_obj.options['xmax'] = xmax - job_obj.options['ymax'] = ymax - - # count the tools - tool_cnt = 0 - - dia_cnc_dict = {} - - # this turn on the FlatCAMCNCJob plot for multiple tools - job_obj.multitool = True - job_obj.multigeo = True - job_obj.cnc_tools.clear() - - job_obj.options['Tools_in_use'] = tools_in_use - job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"]) - job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"]) - - job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"]) - job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"]) - - # make sure that trying to make a CNCJob from an empty file is not creating an app crash - if not self.solid_geometry: - a = 0 - for tooluid_key in self.tools: - if self.tools[tooluid_key]['solid_geometry'] is None: - a += 1 - if a == len(self.tools): - self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry')) - return 'fail' - - for tooluid_key in list(tools_dict.keys()): - tool_cnt += 1 - dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) - tooldia_val = float('%.*f' % (self.decimals, float(tools_dict[tooluid_key]['tooldia']))) - - dia_cnc_dict.update({ - 'tooldia': tooldia_val - }) - - # find the tool_dia associated with the tooluid_key - # search in the self.tools for the sel_tool_dia and when found see what tooluid has - # on the found tooluid in self.tools we also have the solid_geometry that interest us - # for k, v in self.tools.items(): - # if float('%.*f' % (self.decimals, float(v['tooldia']))) == tooldia_val: - # current_uid = int(k) - # break - - if dia_cnc_dict['offset'] == 'in': - tool_offset = -tooldia_val / 2 - elif dia_cnc_dict['offset'].lower() == 'out': - tool_offset = tooldia_val / 2 - elif dia_cnc_dict['offset'].lower() == 'custom': - offset_value = float(self.ui.tool_offset_entry.get_value()) - if offset_value: - tool_offset = float(offset_value) - else: - self.app.inform.emit('[WARNING] %s' % - _("Tool Offset is selected in Tool Table but " - "no value is provided.\n" - "Add a Tool Offset or change the Offset Type.")) - return - else: - tool_offset = 0.0 - - dia_cnc_dict.update({ - 'offset_value': tool_offset - }) - - z_cut = tools_dict[tooluid_key]['data']["cutz"] - z_move = tools_dict[tooluid_key]['data']["travelz"] - feedrate = tools_dict[tooluid_key]['data']["feedrate"] - feedrate_z = tools_dict[tooluid_key]['data']["feedrate_z"] - feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"] - multidepth = tools_dict[tooluid_key]['data']["multidepth"] - extracut = tools_dict[tooluid_key]['data']["extracut"] - extracut_length = tools_dict[tooluid_key]['data']["extracut_length"] - depthpercut = tools_dict[tooluid_key]['data']["depthperpass"] - toolchange = tools_dict[tooluid_key]['data']["toolchange"] - toolchangez = tools_dict[tooluid_key]['data']["toolchangez"] - toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"] - startz = tools_dict[tooluid_key]['data']["startz"] - endz = tools_dict[tooluid_key]['data']["endz"] - endxy = self.options["endxy"] - spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"] - dwell = tools_dict[tooluid_key]['data']["dwell"] - dwelltime = tools_dict[tooluid_key]['data']["dwelltime"] - pp_geometry_name = tools_dict[tooluid_key]['data']["ppname_g"] - - spindledir = self.app.defaults['geometry_spindledir'] - tool_solid_geometry = self.tools[tooluid_key]['solid_geometry'] - - job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] - job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] - - # Propagate options - job_obj.options["tooldia"] = tooldia_val - job_obj.options['type'] = 'Geometry' - job_obj.options['tool_dia'] = tooldia_val - - # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially - # to a value of 0.0005 which is 20 times less than 0.01 - tol = float(self.app.defaults['global_tolerance']) / 20 - res = job_obj.generate_from_multitool_geometry( - tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset, - tolerance=tol, z_cut=z_cut, z_move=z_move, - feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, - spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, - multidepth=multidepth, depthpercut=depthpercut, - extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, - toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, - pp_geometry_name=pp_geometry_name, - tool_no=tool_cnt) - - if res == 'fail': - log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed") - return 'fail' - else: - dia_cnc_dict['gcode'] = res - - self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) - dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() - self.app.inform.emit('[success] %s' % _("G-Code parsing finished...")) - - # TODO this serve for bounding box creation only; should be optimized - # commented this; there is no need for the actual GCode geometry - the original one will serve as well - # for bounding box values - # geo_for_bound_values = cascaded_union([ - # geo['geom'] for geo in dia_cnc_dict['gcode_parsed'] if geo['geom'].is_valid is True - # ]) - try: - dia_cnc_dict['solid_geometry'] = deepcopy(tool_solid_geometry) - self.app.inform.emit('[success] %s' % _("Finished G-Code processing...")) - except Exception as ee: - self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(ee))) - - # tell gcode_parse from which point to start drawing the lines depending on what kind of - # object is the source of gcode - job_obj.toolchange_xy_type = "geometry" - - job_obj.cnc_tools.update({ - tooluid_key: deepcopy(dia_cnc_dict) - }) - dia_cnc_dict.clear() - - if use_thread: - # To be run in separate thread - def job_thread(app_obj): - if self.multigeo is False: - with self.app.proc_container.new(_("Generating CNC Code")): - if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail': - app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname)) - else: - with self.app.proc_container.new(_("Generating CNC Code")): - if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail': - app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname)) - - # Create a promise with the name - self.app.collection.promise(outname) - # Send to worker - self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - else: - if self.solid_geometry: - self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot) - else: - self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot) - - def generatecncjob( - self, outname=None, - dia=None, offset=None, - z_cut=None, z_move=None, - feedrate=None, feedrate_z=None, feedrate_rapid=None, - spindlespeed=None, dwell=None, dwelltime=None, - multidepth=None, depthperpass=None, - toolchange=None, toolchangez=None, toolchangexy=None, - extracut=None, extracut_length=None, startz=None, endz=None, - pp=None, - segx=None, segy=None, - use_thread=True, - plot=True): - """ - Only used for TCL Command. - Creates a CNCJob out of this Geometry object. The actual - work is done by the target camlib.CNCjob - `generate_from_geometry_2()` method. - - :param outname: Name of the new object - :param dia: Tool diameter - :param offset: - :param z_cut: Cut depth (negative value) - :param z_move: Height of the tool when travelling (not cutting) - :param feedrate: Feed rate while cutting on X - Y plane - :param feedrate_z: Feed rate while cutting on Z plane - :param feedrate_rapid: Feed rate while moving with rapids - :param spindlespeed: Spindle speed (RPM) - :param dwell: - :param dwelltime: - :param multidepth: - :param depthperpass: - :param toolchange: - :param toolchangez: - :param toolchangexy: - :param extracut: - :param extracut_length: - :param startz: - :param endz: - :param pp: Name of the preprocessor - :param segx: - :param segy: - :param use_thread: - :param plot: - :return: None - """ - - tooldia = dia if dia else float(self.options["cnctooldia"]) - outname = outname if outname is not None else self.options["name"] - - z_cut = z_cut if z_cut is not None else float(self.options["cutz"]) - z_move = z_move if z_move is not None else float(self.options["travelz"]) - - feedrate = feedrate if feedrate is not None else float(self.options["feedrate"]) - feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"]) - feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"]) - - multidepth = multidepth if multidepth is not None else self.options["multidepth"] - depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"]) - - segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) - segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) - - extracut = extracut if extracut is not None else float(self.options["extracut"]) - extracut_length = extracut_length if extracut_length is not None else float(self.options["extracut_length"]) - - startz = startz if startz is not None else self.options["startz"] - endz = endz if endz is not None else float(self.options["endz"]) - endxy = self.options["endxy"] - - toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"]) - toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"] - toolchange = toolchange if toolchange else self.options["toolchange"] - - offset = offset if offset else 0.0 - - # int or None. - spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed'] - dwell = dwell if dwell else self.options["dwell"] - dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"]) - - ppname_g = pp if pp else self.options["ppname_g"] - - # Object initialization function for app.new_object() - # RUNNING ON SEPARATE THREAD! - def job_init(job_obj, app_obj): - assert isinstance(job_obj, FlatCAMCNCjob), "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) - - # Propagate options - job_obj.options["tooldia"] = tooldia - - job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] - job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] - - job_obj.options['type'] = 'Geometry' - job_obj.options['tool_dia'] = tooldia - - job_obj.segx = segx - job_obj.segy = segy - - job_obj.z_pdepth = float(self.options["z_pdepth"]) - job_obj.feedrate_probe = float(self.options["feedrate_probe"]) - - job_obj.options['xmin'] = self.options['xmin'] - job_obj.options['ymin'] = self.options['ymin'] - job_obj.options['xmax'] = self.options['xmax'] - job_obj.options['ymax'] = self.options['ymax'] - - # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially - # to a value of 0.0005 which is 20 times less than 0.01 - tol = float(self.app.defaults['global_tolerance']) / 20 - job_obj.generate_from_geometry_2( - self, tooldia=tooldia, offset=offset, tolerance=tol, - z_cut=z_cut, z_move=z_move, - feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, - spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime, - multidepth=multidepth, depthpercut=depthperpass, - toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, - extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, - pp_geometry_name=ppname_g - ) - - # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the - # source of gcode - job_obj.toolchange_xy_type = "geometry" - job_obj.gcode_parse() - self.app.inform.emit('[success] %s' % _("Finished G-Code processing...")) - - if use_thread: - # To be run in separate thread - def job_thread(app_obj): - with self.app.proc_container.new(_("Generating CNC Code")): - app_obj.new_object("cncjob", outname, job_init, plot=plot) - app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname) - - # Create a promise with the name - self.app.collection.promise(outname) - # Send to worker - self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) - else: - self.app.new_object("cncjob", outname, job_init, plot=plot) - - # def on_plot_cb_click(self, *args): - # if self.muted_ui: - # return - # self.read_form_item('plot') - - def scale(self, xfactor, yfactor=None, point=None): - """ - Scales all geometry by a given factor. - - :param xfactor: Factor by which to scale the object's geometry/ - :type xfactor: float - :param yfactor: Factor by which to scale the object's geometry/ - :type yfactor: float - :return: None - :rtype: None - """ - log.debug("FlatCAMObj.FlatCAMGeometry.scale()") - - try: - xfactor = float(xfactor) - except Exception: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float.")) - return - - if yfactor is None: - yfactor = xfactor - else: - try: - yfactor = float(yfactor) - except Exception: - self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float.")) - return - - if xfactor == 1 and yfactor == 1: - return - - if point is None: - px = 0 - py = 0 - else: - px, py = point - - self.geo_len = 0 - self.old_disp_number = 0 - self.el_count = 0 - - def scale_recursion(geom): - if type(geom) is list: - geoms = [] - for local_geom in geom: - geoms.append(scale_recursion(local_geom)) - return geoms - else: - try: - self.el_count += 1 - disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100])) - if self.old_disp_number < disp_number <= 100: - 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)) - except AttributeError: - return geom - - if self.multigeo is True: - for tool in self.tools: - # variables to display the percentage of work done - self.geo_len = 0 - try: - self.geo_len = len(self.tools[tool]['solid_geometry']) - except TypeError: - self.geo_len = 1 - self.old_disp_number = 0 - self.el_count = 0 - - self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry']) - - try: - # variables to display the percentage of work done - self.geo_len = 0 - try: - self.geo_len = len(self.solid_geometry) - except TypeError: - self.geo_len = 1 - self.old_disp_number = 0 - self.el_count = 0 - - self.solid_geometry = scale_recursion(self.solid_geometry) - except AttributeError: - self.solid_geometry = [] - return - - self.app.proc_container.new_text = '' - self.app.inform.emit('[success] %s' % _("Geometry Scale done.")) - - def offset(self, vect): - """ - Offsets all geometry by a given vector/ - - :param vect: (x, y) vector by which to offset the object's geometry. - :type vect: tuple - :return: None - :rtype: None - """ - log.debug("FlatCAMObj.FlatCAMGeometry.offset()") - - try: - dx, dy = vect - except TypeError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("An (x,y) pair of values are needed. " - "Probable you entered only one value in the Offset field.") - ) - return - - if dx == 0 and dy == 0: - return - - self.geo_len = 0 - self.old_disp_number = 0 - self.el_count = 0 - - def translate_recursion(geom): - if type(geom) is list: - geoms = [] - for local_geom in geom: - geoms.append(translate_recursion(local_geom)) - return geoms - else: - try: - self.el_count += 1 - disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100])) - if self.old_disp_number < disp_number <= 100: - self.app.proc_container.update_view_text(' %d%%' % disp_number) - self.old_disp_number = disp_number - - return affinity.translate(geom, xoff=dx, yoff=dy) - except AttributeError: - return geom - - if self.multigeo is True: - for tool in self.tools: - # variables to display the percentage of work done - self.geo_len = 0 - try: - self.geo_len = len(self.tools[tool]['solid_geometry']) - except TypeError: - self.geo_len = 1 - self.old_disp_number = 0 - self.el_count = 0 - - self.tools[tool]['solid_geometry'] = translate_recursion(self.tools[tool]['solid_geometry']) - - # variables to display the percentage of work done - self.geo_len = 0 - try: - self.geo_len = len(self.solid_geometry) - except TypeError: - self.geo_len = 1 - - self.old_disp_number = 0 - self.el_count = 0 - - self.solid_geometry = translate_recursion(self.solid_geometry) - - self.app.proc_container.new_text = '' - self.app.inform.emit('[success] %s' % _("Geometry Offset done.")) - - def convert_units(self, units): - log.debug("FlatCAMObj.FlatCAMGeometry.convert_units()") - - self.ui_disconnect() - - factor = Geometry.convert_units(self, units) - - self.options['cutz'] = float(self.options['cutz']) * factor - self.options['depthperpass'] = float(self.options['depthperpass']) * factor - self.options['travelz'] = float(self.options['travelz']) * factor - self.options['feedrate'] = float(self.options['feedrate']) * factor - self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor - self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor - self.options['endz'] = float(self.options['endz']) * factor - # self.options['cnctooldia'] *= factor - # self.options['painttooldia'] *= factor - # self.options['paintmargin'] *= factor - # self.options['paintoverlap'] *= factor - - self.options["toolchangez"] = float(self.options["toolchangez"]) * factor - - if self.app.defaults["geometry_toolchangexy"] == '': - self.options['toolchangexy'] = "0.0, 0.0" - else: - coords_xy = [float(eval(coord)) for coord in self.app.defaults["geometry_toolchangexy"].split(",")] - if len(coords_xy) < 2: - self.app.inform.emit('[ERROR] %s' % - _("The Toolchange X,Y field in Edit -> Preferences " - "has to be in the format (x, y)\n" - "but now there is only one value, not two.") - ) - return 'fail' - coords_xy[0] *= factor - coords_xy[1] *= factor - self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) - - if self.options['startz'] is not None: - self.options['startz'] = float(self.options['startz']) * factor - - param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', - 'endz', 'toolchangez'] - - if isinstance(self, FlatCAMGeometry): - temp_tools_dict = {} - tool_dia_copy = {} - data_copy = {} - for tooluid_key, tooluid_value in self.tools.items(): - for dia_key, dia_value in tooluid_value.items(): - if dia_key == 'tooldia': - dia_value *= factor - dia_value = float('%.*f' % (self.decimals, dia_value)) - tool_dia_copy[dia_key] = dia_value - if dia_key == 'offset': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'offset_value': - dia_value *= factor - tool_dia_copy[dia_key] = dia_value - - # convert the value in the Custom Tool Offset entry in UI - custom_offset = None - try: - custom_offset = float(self.ui.tool_offset_entry.get_value()) - except ValueError: - # try to convert comma to decimal point. if it's still not working error message and return - try: - custom_offset = float(self.ui.tool_offset_entry.get_value().replace(',', '.')) - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Wrong value format entered, use a number.")) - return - except TypeError: - pass - - if custom_offset: - custom_offset *= factor - self.ui.tool_offset_entry.set_value(custom_offset) - - if dia_key == 'type': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'tool_type': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'data': - for data_key, data_value in dia_value.items(): - # convert the form fields that are convertible - for param in param_list: - if data_key == param and data_value is not None: - data_copy[data_key] = data_value * factor - # copy the other dict entries that are not convertible - if data_key not in param_list: - data_copy[data_key] = data_value - tool_dia_copy[dia_key] = deepcopy(data_copy) - data_copy.clear() - - temp_tools_dict.update({ - tooluid_key: deepcopy(tool_dia_copy) - }) - tool_dia_copy.clear() - - self.tools.clear() - self.tools = deepcopy(temp_tools_dict) - - # if there is a value in the new tool field then convert that one too - try: - self.ui.addtool_entry.returnPressed.disconnect() - except TypeError: - pass - tooldia = self.ui.addtool_entry.get_value() - if tooldia: - tooldia *= factor - tooldia = float('%.*f' % (self.decimals, tooldia)) - - self.ui.addtool_entry.set_value(tooldia) - self.ui.addtool_entry.returnPressed.connect(self.on_tool_add) - - return factor - - def plot_element(self, element, color=None, visible=None): - - if color is None: - color = '#FF0000FF' - - visible = visible if visible else self.options['plot'] - try: - for sub_el in element: - self.plot_element(sub_el, color=color) - - except TypeError: # Element is not iterable... - # if self.app.is_legacy is False: - self.add_shape(shape=element, color=color, visible=visible, layer=0) - - def plot(self, visible=None, kind=None): - """ - Plot the object. - - :param visible: Controls if the added shape is visible of not - :param kind: added so there is no error when a project is loaded and it has both geometry and CNCJob, because - CNCJob require the 'kind' parameter. Perhaps the FlatCAMObj.plot() has to be rewrited - :return: - """ - - # Does all the required setup and returns False - # if the 'ptint' option is set to False. - if not FlatCAMObj.plot(self): - return - - try: - # plot solid geometries found as members of self.tools attribute dict - # for MultiGeo - if self.multigeo is True: # geo multi tool usage - for tooluid_key in self.tools: - solid_geometry = self.tools[tooluid_key]['solid_geometry'] - self.plot_element(solid_geometry, visible=visible, - color=self.app.defaults["geometry_plot_line"]) - else: - # plot solid geometry that may be an direct attribute of the geometry object - # for SingleGeo - if self.solid_geometry: - self.plot_element(self.solid_geometry, visible=visible, - color=self.app.defaults["geometry_plot_line"]) - - # self.plot_element(self.solid_geometry, visible=self.options['plot']) - - self.shapes.redraw() - - except (ObjectDeleted, AttributeError): - self.shapes.clear(update=True) - - def on_plot_cb_click(self, *args): - if self.muted_ui: - return - self.read_form_item('plot') - self.plot() - - self.ui_disconnect() - cb_flag = self.ui.plot_cb.isChecked() - for row in range(self.ui.geo_tools_table.rowCount()): - table_cb = self.ui.geo_tools_table.cellWidget(row, 6) - if cb_flag: - table_cb.setChecked(True) - else: - table_cb.setChecked(False) - self.ui_connect() - - def on_plot_cb_click_table(self): - # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) - self.ui_disconnect() - # cw = self.sender() - # cw_index = self.ui.geo_tools_table.indexAt(cw.pos()) - # cw_row = cw_index.row() - check_row = 0 - - self.shapes.clear(update=True) - for tooluid_key in self.tools: - solid_geometry = self.tools[tooluid_key]['solid_geometry'] - - # find the geo_tool_table row associated with the tooluid_key - for row in range(self.ui.geo_tools_table.rowCount()): - tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text()) - if tooluid_item == int(tooluid_key): - check_row = row - break - if self.ui.geo_tools_table.cellWidget(check_row, 6).isChecked(): - self.plot_element(element=solid_geometry, visible=True) - self.shapes.redraw() - - # make sure that the general plot is disabled if one of the row plot's are disabled and - # if all the row plot's are enabled also enable the general plot checkbox - cb_cnt = 0 - total_row = self.ui.geo_tools_table.rowCount() - for row in range(total_row): - if self.ui.geo_tools_table.cellWidget(row, 6).isChecked(): - cb_cnt += 1 - else: - cb_cnt -= 1 - if cb_cnt < total_row: - self.ui.plot_cb.setChecked(False) - else: - self.ui.plot_cb.setChecked(True) - self.ui_connect() - - def merge(self, geo_list, geo_final, multigeo=None): - """ - Merges the geometry of objects in grb_list into - the geometry of geo_final. - - :param geo_list: List of FlatCAMGerber Objects to join. - :param geo_final: Destination FlatCAMGerber object. - :param multigeo: if the merged geometry objects are of type MultiGeo - :return: None - """ - - if geo_final.solid_geometry is None: - geo_final.solid_geometry = [] - - try: - __ = iter(geo_final.solid_geometry) - except TypeError: - geo_final.solid_geometry = [geo_final.solid_geometry] - - new_solid_geometry = [] - new_options = {} - new_tools = {} - - for geo_obj in geo_list: - for option in geo_obj.options: - if option != 'name': - try: - new_options[option] = deepcopy(geo_obj.options[option]) - except Exception as e: - log.warning("Failed to copy option %s. Error: %s" % (str(option), str(e))) - - # Expand lists - if type(geo_obj) is list: - FlatCAMGeometry.merge(self, geo_list=geo_obj, geo_final=geo_final) - # If not list, just append - else: - if multigeo is None or multigeo is False: - geo_final.multigeo = False - else: - geo_final.multigeo = True - - try: - new_solid_geometry += deepcopy(geo_obj.solid_geometry) - except Exception as e: - log.debug("FlatCAMGeometry.merge() --> %s" % str(e)) - - # find the tool_uid maximum value in the geo_final - try: - max_uid = max([int(i) for i in new_tools.keys()]) - except ValueError: - max_uid = 0 - - # add and merge tools. If what we try to merge as Geometry is Excellon's and/or Gerber's then don't try - # to merge the obj.tools as it is likely there is none to merge. - if not isinstance(geo_obj, FlatCAMGerber) and not isinstance(geo_obj, FlatCAMExcellon): - for tool_uid in geo_obj.tools: - max_uid += 1 - new_tools[max_uid] = deepcopy(geo_obj.tools[tool_uid]) - - geo_final.options.update(new_options) - geo_final.solid_geometry = new_solid_geometry - geo_final.tools = new_tools - - @staticmethod - def get_pts(o): - """ - Returns a list of all points in the object, where - the object can be a MultiPolygon, Polygon, Not a polygon, or a list - of such. Search is done recursively. - - :param: geometric object - :return: List of points - :rtype: list - """ - pts = [] - - # Iterable: descend into each item. - try: - for subo in o: - pts += FlatCAMGeometry.get_pts(subo) - - # Non-iterable - except TypeError: - if o is not None: - if type(o) == MultiPolygon: - for poly in o: - pts += FlatCAMGeometry.get_pts(poly) - # ## Descend into .exerior and .interiors - elif type(o) == Polygon: - pts += FlatCAMGeometry.get_pts(o.exterior) - for i in o.interiors: - pts += FlatCAMGeometry.get_pts(i) - elif type(o) == MultiLineString: - for line in o: - pts += FlatCAMGeometry.get_pts(line) - # ## Has .coords: list them. - else: - pts += list(o.coords) - else: - return - return pts - - -class FlatCAMCNCjob(FlatCAMObj, CNCjob): - """ - Represents G-Code. - """ - optionChanged = QtCore.pyqtSignal(str) - ui_type = CNCObjectUI - - def __init__(self, name, units="in", kind="generic", z_move=0.1, - feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0, - spindlespeed=None): - - FlatCAMApp.App.log.debug("Creating CNCJob object...") - - self.decimals = self.app.decimals - - CNCjob.__init__(self, units=units, kind=kind, z_move=z_move, - feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia, - spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"])) - - FlatCAMObj.__init__(self, name) - - self.kind = "cncjob" - - self.options.update({ - "plot": True, - "tooldia": 0.03937, # 0.4mm in inches - "append": "", - "prepend": "", - "dwell": False, - "dwelltime": 1, - "type": 'Geometry', - "toolchange_macro": '', - "toolchange_macro_enable": False - }) - - ''' - This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the - diameter of the tools and the value is another dict that will hold the data under the following form: - {tooldia: { - 'tooluid': 1, - 'offset': 'Path', - 'type_item': 'Rough', - 'tool_type': 'C1', - 'data': {} # a dict to hold the parameters - 'gcode': "" # a string with the actual GCODE - 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry - (cut or move) - 'solid_geometry': [] - }, - ... - } - It is populated in the FlatCAMGeometry.mtool_gen_cncjob() - BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ... - ''' - self.cnc_tools = {} - - ''' - This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the - diameter of the tools and the value is another dict that will hold the data under the following form: - {tooldia: { - 'tool': int, - 'nr_drills': int, - 'nr_slots': int, - 'offset': float, - 'data': {} # a dict to hold the parameters - 'gcode': "" # a string with the actual GCODE - 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry (cut or move) - 'solid_geometry': [] - }, - ... - } - It is populated in the FlatCAMExcellon.on_create_cncjob_click() but actually - it's done in camlib.CNCJob.generate_from_excellon_by_tool() - BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ... - ''' - self.exc_cnc_tools = {} - - # flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the - # default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects. - self.special_group = None - - # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool - # (like the one in the TCL Command), False - self.multitool = False - - # determine if the GCode was generated out of a Excellon object or a Geometry object - self.origin_kind = None - - # used for parsing the GCode lines to adjust the GCode when the GCode is offseted or scaled - gcodex_re_string = r'(?=.*(X[-\+]?\d*\.\d*))' - self.g_x_re = re.compile(gcodex_re_string) - gcodey_re_string = r'(?=.*(Y[-\+]?\d*\.\d*))' - self.g_y_re = re.compile(gcodey_re_string) - gcodez_re_string = r'(?=.*(Z[-\+]?\d*\.\d*))' - self.g_z_re = re.compile(gcodez_re_string) - - gcodef_re_string = r'(?=.*(F[-\+]?\d*\.\d*))' - self.g_f_re = re.compile(gcodef_re_string) - gcodet_re_string = r'(?=.*(\=\s*[-\+]?\d*\.\d*))' - self.g_t_re = re.compile(gcodet_re_string) - - gcodenr_re_string = r'([+-]?\d*\.\d+)' - self.g_nr_re = re.compile(gcodenr_re_string) - - # Attributes to be included in serialization - # Always append to it because it carries contents - # from predecessors. - self.ser_attrs += ['options', 'kind', 'origin_kind', 'cnc_tools', 'exc_cnc_tools', 'multitool'] - - if self.app.is_legacy is False: - self.text_col = self.app.plotcanvas.new_text_collection() - self.text_col.enabled = True - self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col) - - self.gcode_editor_tab = None - - self.units_found = self.app.defaults['units'] - - def build_ui(self): - self.ui_disconnect() - - FlatCAMObj.build_ui(self) - self.units = self.app.defaults['units'].upper() - - # if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it - self.ui.cnc_tools_table.hide() - if self.cnc_tools: - self.ui.cnc_tools_table.show() - self.build_cnc_tools_table() - - self.ui.exc_cnc_tools_table.hide() - if self.exc_cnc_tools: - self.ui.exc_cnc_tools_table.show() - self.build_excellon_cnc_tools() - # - self.ui_connect() - - def build_cnc_tools_table(self): - tool_idx = 0 - - n = len(self.cnc_tools) - self.ui.cnc_tools_table.setRowCount(n) - - for dia_key, dia_value in self.cnc_tools.items(): - - tool_idx += 1 - row_no = tool_idx - 1 - - t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) - # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.cnc_tools_table.setItem(row_no, 0, t_id) # Tool name/id - - # Make sure that the tool diameter when in MM is with no more than 2 decimals. - # There are no tool bits in MM with more than 2 decimals diameter. - # For INCH the decimals should be no more than 4. There are no tools under 10mils. - - dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['tooldia']))) - - offset_txt = list(str(dia_value['offset'])) - offset_txt[0] = offset_txt[0].upper() - offset_item = QtWidgets.QTableWidgetItem(''.join(offset_txt)) - type_item = QtWidgets.QTableWidgetItem(str(dia_value['type'])) - tool_type_item = QtWidgets.QTableWidgetItem(str(dia_value['tool_type'])) - - t_id.setFlags(QtCore.Qt.ItemIsEnabled) - dia_item.setFlags(QtCore.Qt.ItemIsEnabled) - offset_item.setFlags(QtCore.Qt.ItemIsEnabled) - type_item.setFlags(QtCore.Qt.ItemIsEnabled) - tool_type_item.setFlags(QtCore.Qt.ItemIsEnabled) - - # hack so the checkbox stay centered in the table cell - # used this: - # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row - # plot_item = QtWidgets.QWidget() - # checkbox = FCCheckBox() - # checkbox.setCheckState(QtCore.Qt.Checked) - # qhboxlayout = QtWidgets.QHBoxLayout(plot_item) - # qhboxlayout.addWidget(checkbox) - # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter) - # qhboxlayout.setContentsMargins(0, 0, 0, 0) - plot_item = FCCheckBox() - plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) - tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_key)) - if self.ui.plot_cb.isChecked(): - plot_item.setChecked(True) - - self.ui.cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter - self.ui.cnc_tools_table.setItem(row_no, 2, offset_item) # Offset - self.ui.cnc_tools_table.setItem(row_no, 3, type_item) # Toolpath Type - self.ui.cnc_tools_table.setItem(row_no, 4, tool_type_item) # Tool Type - - # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ## - self.ui.cnc_tools_table.setItem(row_no, 5, tool_uid_item) # Tool unique ID) - self.ui.cnc_tools_table.setCellWidget(row_no, 6, plot_item) - - # make the diameter column editable - # for row in range(tool_idx): - # self.ui.cnc_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable | - # QtCore.Qt.ItemIsEnabled) - - for row in range(tool_idx): - self.ui.cnc_tools_table.item(row, 0).setFlags( - self.ui.cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable) - - self.ui.cnc_tools_table.resizeColumnsToContents() - self.ui.cnc_tools_table.resizeRowsToContents() - - vertical_header = self.ui.cnc_tools_table.verticalHeader() - # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) - vertical_header.hide() - self.ui.cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - horizontal_header = self.ui.cnc_tools_table.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(0, 20) - horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(4, 40) - horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(4, 17) - # horizontal_header.setStretchLastSection(True) - self.ui.cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self.ui.cnc_tools_table.setColumnWidth(0, 20) - self.ui.cnc_tools_table.setColumnWidth(4, 40) - self.ui.cnc_tools_table.setColumnWidth(6, 17) - - # self.ui.geo_tools_table.setSortingEnabled(True) - - self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight()) - self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight()) - - def build_excellon_cnc_tools(self): - tool_idx = 0 - - n = len(self.exc_cnc_tools) - self.ui.exc_cnc_tools_table.setRowCount(n) - - for tooldia_key, dia_value in self.exc_cnc_tools.items(): - - tool_idx += 1 - row_no = tool_idx - 1 - - t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) - dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooldia_key))) - nr_drills_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_drills'])) - nr_slots_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_slots'])) - cutz_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['offset_z']) + self.z_cut)) - - t_id.setFlags(QtCore.Qt.ItemIsEnabled) - dia_item.setFlags(QtCore.Qt.ItemIsEnabled) - nr_drills_item.setFlags(QtCore.Qt.ItemIsEnabled) - nr_slots_item.setFlags(QtCore.Qt.ItemIsEnabled) - cutz_item.setFlags(QtCore.Qt.ItemIsEnabled) - - # hack so the checkbox stay centered in the table cell - # used this: - # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row - # plot_item = QtWidgets.QWidget() - # checkbox = FCCheckBox() - # checkbox.setCheckState(QtCore.Qt.Checked) - # qhboxlayout = QtWidgets.QHBoxLayout(plot_item) - # qhboxlayout.addWidget(checkbox) - # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter) - # qhboxlayout.setContentsMargins(0, 0, 0, 0) - - plot_item = FCCheckBox() - plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) - tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_value['tool'])) - if self.ui.plot_cb.isChecked(): - plot_item.setChecked(True) - - # TODO until the feature of individual plot for an Excellon tool is implemented - plot_item.setDisabled(True) - - self.ui.exc_cnc_tools_table.setItem(row_no, 0, t_id) # Tool name/id - self.ui.exc_cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter - self.ui.exc_cnc_tools_table.setItem(row_no, 2, nr_drills_item) # Nr of drills - self.ui.exc_cnc_tools_table.setItem(row_no, 3, nr_slots_item) # Nr of slots - - # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ## - self.ui.exc_cnc_tools_table.setItem(row_no, 4, tool_uid_item) # Tool unique ID) - self.ui.exc_cnc_tools_table.setItem(row_no, 5, cutz_item) - self.ui.exc_cnc_tools_table.setCellWidget(row_no, 6, plot_item) - - for row in range(tool_idx): - self.ui.exc_cnc_tools_table.item(row, 0).setFlags( - self.ui.exc_cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable) - - self.ui.exc_cnc_tools_table.resizeColumnsToContents() - self.ui.exc_cnc_tools_table.resizeRowsToContents() - - vertical_header = self.ui.exc_cnc_tools_table.verticalHeader() - vertical_header.hide() - self.ui.exc_cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - horizontal_header = self.ui.exc_cnc_tools_table.horizontalHeader() - horizontal_header.setMinimumSectionSize(10) - horizontal_header.setDefaultSectionSize(70) - horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) - horizontal_header.resizeSection(0, 20) - horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents) - - horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed) - - # horizontal_header.setStretchLastSection(True) - self.ui.exc_cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self.ui.exc_cnc_tools_table.setColumnWidth(0, 20) - self.ui.exc_cnc_tools_table.setColumnWidth(6, 17) - - self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight()) - self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight()) - - def set_ui(self, ui): - FlatCAMObj.set_ui(self, ui) - - FlatCAMApp.App.log.debug("FlatCAMCNCJob.set_ui()") - - assert isinstance(self.ui, CNCObjectUI), \ - "Expected a CNCObjectUI, got %s" % type(self.ui) - - self.units = self.app.defaults['units'].upper() - self.units_found = self.app.defaults['units'] - - # this signal has to be connected to it's slot before the defaults are populated - # the decision done in the slot has to override the default value set bellow - self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked) - - self.form_fields.update({ - "plot": self.ui.plot_cb, - "tooldia": self.ui.tooldia_entry, - "append": self.ui.append_text, - "prepend": self.ui.prepend_text, - "toolchange_macro": self.ui.toolchange_text, - "toolchange_macro_enable": self.ui.toolchange_cb - }) - - # Fill form fields only on object create - self.to_form() - - # this means that the object that created this CNCJob was an Excellon or Geometry - try: - if self.travel_distance: - self.ui.t_distance_label.show() - self.ui.t_distance_entry.setVisible(True) - self.ui.t_distance_entry.setDisabled(True) - self.ui.t_distance_entry.set_value('%.*f' % (self.decimals, float(self.travel_distance))) - self.ui.units_label.setText(str(self.units).lower()) - self.ui.units_label.setDisabled(True) - - self.ui.t_time_label.show() - self.ui.t_time_entry.setVisible(True) - self.ui.t_time_entry.setDisabled(True) - # if time is more than 1 then we have minutes, else we have seconds - if self.routing_time > 1: - self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(self.routing_time)))) - self.ui.units_time_label.setText('min') - else: - time_r = self.routing_time * 60 - self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(time_r)))) - self.ui.units_time_label.setText('sec') - self.ui.units_time_label.setDisabled(True) - except AttributeError: - pass - - if self.multitool is False: - self.ui.tooldia_entry.show() - self.ui.updateplot_button.show() - else: - self.ui.tooldia_entry.hide() - self.ui.updateplot_button.hide() - - # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob - self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"]) - - try: - self.ui.annotation_cb.stateChanged.disconnect(self.on_annotation_change) - except (TypeError, AttributeError): - pass - self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change) - - # set if to display text annotations - self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"]) - - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText(_( - 'Basic' - )) - - self.ui.cnc_frame.hide() - else: - self.ui.level.setText(_( - 'Advanced' - )) - self.ui.cnc_frame.show() - - self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click) - self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click) - self.ui.modify_gcode_button.clicked.connect(self.on_edit_code_click) - - self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters) - - self.ui.cncplot_method_combo.activated_custom.connect(self.on_plot_kind_change) - - def on_cnc_custom_parameters(self, signal_text): - if signal_text == 'Parameters': - return - else: - self.ui.toolchange_text.insertPlainText('%%%s%%' % signal_text) - - def ui_connect(self): - for row in range(self.ui.cnc_tools_table.rowCount()): - self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table) - self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) - - def ui_disconnect(self): - for row in range(self.ui.cnc_tools_table.rowCount()): - self.ui.cnc_tools_table.cellWidget(row, 6).clicked.disconnect(self.on_plot_cb_click_table) - try: - self.ui.plot_cb.stateChanged.disconnect(self.on_plot_cb_click) - except (TypeError, AttributeError): - pass - - def on_updateplot_button_click(self, *args): - """ - Callback for the "Updata Plot" button. Reads the form for updates - and plots the object. - """ - self.read_form() - self.on_plot_kind_change() - - def on_plot_kind_change(self): - kind = self.ui.cncplot_method_combo.get_value() - - def worker_task(): - with self.app.proc_container.new(_("Plotting...")): - self.plot(kind=kind) - - self.app.worker_task.emit({'fcn': worker_task, 'params': []}) - - def on_exportgcode_button_click(self, *args): - """ - Handler activated by a button clicked when exporting GCode. - - :param args: - :return: - """ - self.app.report_usage("cncjob_on_exportgcode_button") - - self.read_form() - name = self.app.collection.get_active().options['name'] - save_gcode = False - - if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name: - _filter_ = "RML1 Files .rol (*.rol);;All Files (*.*)" - elif 'hpgl' in self.pp_geometry_name: - _filter_ = "HPGL Files .plt (*.plt);;All Files (*.*)" - else: - save_gcode = True - _filter_ = self.app.defaults['cncjob_save_filters'] - - try: - dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) - filename, _f = FCFileSaveDialog.get_saved_filename( - caption=_("Export Machine Code ..."), - directory=dir_file_to_save, - filter=_filter_ - ) - except TypeError: - filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_) - - filename = str(filename) - - if filename == '': - self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ...")) - return - else: - if save_gcode is True: - used_extension = filename.rpartition('.')[2] - self.update_filters(last_ext=used_extension, filter_string='cncjob_save_filters') - - new_name = os.path.split(str(filename))[1].rpartition('.')[0] - self.ui.name_entry.set_value(new_name) - self.on_name_activate(silent=True) - - preamble = str(self.ui.prepend_text.get_value()) - postamble = str(self.ui.append_text.get_value()) - - gc = self.export_gcode(filename, preamble=preamble, postamble=postamble) - if gc == 'fail': - return - - if self.app.defaults["global_open_style"] is False: - self.app.file_opened.emit("gcode", filename) - self.app.file_saved.emit("gcode", filename) - self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename)) - - def on_edit_code_click(self, *args): - """ - Handler activated by a button clicked when editing GCode. - - :param args: - :return: - """ - - self.app.proc_container.view.set_busy(_("Loading...")) - - preamble = str(self.ui.prepend_text.get_value()) - postamble = str(self.ui.append_text.get_value()) - - gco = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True) - if gco == 'fail': - return - else: - self.app.gcode_edited = gco - - self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True) - - # add the tab if it was closed - self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Code Editor")) - self.gcode_editor_tab.setObjectName('code_editor_tab') - - # delete the absolute and relative position and messages in the infobar - self.app.ui.position_label.setText("") - self.app.ui.rel_position_label.setText("") - - # first clear previous text in text editor (if any) - self.gcode_editor_tab.code_editor.clear() - self.gcode_editor_tab.code_editor.setReadOnly(False) - - self.gcode_editor_tab.code_editor.completer_enable = False - self.gcode_editor_tab.buttonRun.hide() - - # Switch plot_area to CNCJob tab - self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab) - - self.gcode_editor_tab.t_frame.hide() - # then append the text from GCode to the text editor - try: - self.gcode_editor_tab.code_editor.setPlainText(self.app.gcode_edited.getvalue()) - # for line in self.app.gcode_edited: - # QtWidgets.QApplication.processEvents() - # - # proc_line = str(line).strip('\n') - # self.gcode_editor_tab.code_editor.append(proc_line) - except Exception as e: - log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e)) - self.app.inform.emit('[ERROR] %s %s' % ('FlatCAMCNNJob.on_edit_code_click() -->', str(e))) - return - - self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start) - - self.gcode_editor_tab.handleTextChanged() - self.gcode_editor_tab.t_frame.show() - self.app.proc_container.view.set_idle() - - self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor')) - - def gcode_header(self, comment_start_symbol=None, comment_stop_symbol=None): - """ - Will create a header to be added to all GCode files generated by FlatCAM - - :param comment_start_symbol: A symbol to be used as the first symbol in a comment - :param comment_stop_symbol: A symbol to be used as the last symbol in a comment - :return: A string with a GCode header - """ - - log.debug("FlatCAMCNCJob.gcode_header()") - time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) - marlin = False - hpgl = False - probe_pp = False - - start_comment = comment_start_symbol if comment_start_symbol is not None else '(' - stop_comment = comment_stop_symbol if comment_stop_symbol is not None else ')' - - try: - for key in self.cnc_tools: - ppg = self.cnc_tools[key]['data']['ppname_g'] - if 'marlin' in ppg.lower() or 'repetier' in ppg.lower(): - marlin = True - break - if ppg == 'hpgl': - hpgl = True - break - if "toolchange_probe" in ppg.lower(): - probe_pp = True - break - except KeyError: - # log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e)) - pass - - try: - if 'marlin' in self.options['ppname_e'].lower() or 'repetier' in self.options['ppname_e'].lower(): - marlin = True - except KeyError: - # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e)) - pass - - try: - if "toolchange_probe" in self.options['ppname_e'].lower(): - probe_pp = True - except KeyError: - # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e)) - pass - - if marlin is True: - gcode = ';Marlin(Repetier) G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ - (str(self.app.version), str(self.app.version_date)) + '\n' - - gcode += ';Name: ' + str(self.options['name']) + '\n' - gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n' - - # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': - # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' - - gcode += ';Units: ' + self.units.upper() + '\n' + "\n" - gcode += ';Created on ' + time_str + '\n' + '\n' - elif hpgl is True: - gcode = 'CO "HPGL CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s' % \ - (str(self.app.version), str(self.app.version_date)) + '";\n' - - gcode += 'CO "Name: ' + str(self.options['name']) + '";\n' - gcode += 'CO "Type: ' + "HPGL code from " + str(self.options['type']) + '";\n' - - # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': - # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' - - gcode += 'CO "Units: ' + self.units.upper() + '";\n' - gcode += 'CO "Created on ' + time_str + '";\n' - elif probe_pp is True: - gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \ - (str(self.app.version), str(self.app.version_date)) + '\n' - - gcode += '(This GCode tool change is done by using a Probe.)\n' \ - '(Make sure that before you start the job you first do a rough zero for Z axis.)\n' \ - '(This means that you need to zero the CNC axis and then jog to the toolchange X, Y location,)\n' \ - '(mount the probe and adjust the Z so more or less the probe tip touch the plate. ' \ - 'Then zero the Z axis.)\n' + '\n' - - gcode += '(Name: ' + str(self.options['name']) + ')\n' - gcode += '(Type: ' + "G-code from " + str(self.options['type']) + ')\n' - - # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': - # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' - - gcode += '(Units: ' + self.units.upper() + ')\n' + "\n" - gcode += '(Created on ' + time_str + ')\n' + '\n' - else: - gcode = '%sG-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s%s\n' % \ - (start_comment, str(self.app.version), str(self.app.version_date), stop_comment) + '\n' - - gcode += '%sName: ' % start_comment + str(self.options['name']) + '%s\n' % stop_comment - gcode += '%sType: ' % start_comment + "G-code from " + str(self.options['type']) + '%s\n' % stop_comment - - # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': - # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' - - gcode += '%sUnits: ' % start_comment + self.units.upper() + '%s\n' % stop_comment + "\n" - gcode += '%sCreated on ' % start_comment + time_str + '%s\n' % stop_comment + '\n' - - return gcode - - def gcode_footer(self, end_command=None): - """ - Will add the M02 to the end of GCode, if requested. - - :param end_command: 'M02' or 'M30' - String - :return: - """ - if end_command: - return end_command - else: - return 'M02' - - def export_gcode(self, filename=None, preamble='', postamble='', to_file=False): - """ - This will save the GCode from the Gcode object to a file on the OS filesystem - - :param filename: filename for the GCode file - :param preamble: a custom Gcode block to be added at the beginning of the Gcode file - :param postamble: a custom Gcode block to be added at the end of the Gcode file - :param to_file: if False then no actual file is saved but the app will know that a file was created - :return: None - """ - # gcode = '' - # roland = False - # hpgl = False - # isel_icp = False - - include_header = True - - try: - if self.special_group: - self.app.inform.emit('[WARNING_NOTCL] %s %s %s.' % - (_("This CNCJob object can't be processed because it is a"), - str(self.special_group), - _("CNCJob object"))) - return 'fail' - except AttributeError: - pass - - # if this dict is not empty then the object is a Geometry object - if self.cnc_tools: - first_key = next(iter(self.cnc_tools)) - include_header = self.app.preprocessors[self.cnc_tools[first_key]['data']['ppname_g']].include_header - - # if this dict is not empty then the object is an Excellon object - if self.exc_cnc_tools: - first_key = next(iter(self.exc_cnc_tools)) - include_header = self.app.preprocessors[self.exc_cnc_tools[first_key]['data']['ppname_e']].include_header - - # # detect if using Roland preprocessor - # try: - # for key in self.cnc_tools: - # if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20': - # roland = True - # break - # except Exception: - # try: - # for key in self.cnc_tools: - # if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20': - # roland = True - # break - # except Exception: - # pass - # - # # detect if using HPGL preprocessor - # try: - # for key in self.cnc_tools: - # if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl': - # hpgl = True - # break - # except Exception: - # try: - # for key in self.cnc_tools: - # if self.cnc_tools[key]['data']['ppname_e'] == 'hpgl': - # hpgl = True - # break - # except Exception: - # pass - # - # # detect if using ISEL_ICP_CNC preprocessor - # try: - # for key in self.cnc_tools: - # if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_g'].upper(): - # isel_icp = True - # break - # except Exception: - # try: - # for key in self.cnc_tools: - # if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_e'].upper(): - # isel_icp = True - # break - # except Exception: - # pass - - # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor - # if roland is False and hpgl is False and isel_icp is False: - # gcode = self.gcode_header() - - # do not add gcode_header when using the Roland, HPGL or ISEP_ICP_CNC preprocessor (or any other preprocessor - # that has the include_header attribute set as False, add it for every other preprocessor - # if include_header: - # gcode = self.gcode_header() - # else: - # gcode = '' - - # # detect if using multi-tool and make the Gcode summation correctly for each case - # if self.multitool is True: - # for tooluid_key in self.cnc_tools: - # for key, value in self.cnc_tools[tooluid_key].items(): - # if key == 'gcode': - # gcode += value - # break - # else: - # gcode += self.gcode - - # if roland is True: - # g = preamble + gcode + postamble - # elif hpgl is True: - # g = self.gcode_header() + preamble + gcode + postamble - # else: - # # fix so the preamble gets inserted in between the comments header and the actual start of GCODE - # g_idx = gcode.rfind('G20') - # - # # if it did not find 'G20' then search for 'G21' - # if g_idx == -1: - # g_idx = gcode.rfind('G21') - # - # # if it did not find 'G20' and it did not find 'G21' then there is an error and return - # # but only when the preprocessor is not ISEL_ICP who is allowed not to have the G20/G21 command - # if g_idx == -1 and isel_icp is False: - # self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21")) - # return - # - # footer = self.app.defaults['cncjob_footer'] - # end_gcode = self.gcode_footer() if footer is True else '' - # g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode - - gcode = '' - if include_header is False: - g = preamble - # detect if using multi-tool and make the Gcode summation correctly for each case - if self.multitool is True: - for tooluid_key in self.cnc_tools: - for key, value in self.cnc_tools[tooluid_key].items(): - if key == 'gcode': - gcode += value - break - else: - gcode += self.gcode - - g = g + gcode + postamble - else: - # search for the GCode beginning which is usually a G20 or G21 - # fix so the preamble gets inserted in between the comments header and the actual start of GCODE - # g_idx = gcode.rfind('G20') - # - # # if it did not find 'G20' then search for 'G21' - # if g_idx == -1: - # g_idx = gcode.rfind('G21') - # - # # if it did not find 'G20' and it did not find 'G21' then there is an error and return - # if g_idx == -1: - # self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21")) - # return - - # detect if using multi-tool and make the Gcode summation correctly for each case - if self.multitool is True: - for tooluid_key in self.cnc_tools: - for key, value in self.cnc_tools[tooluid_key].items(): - if key == 'gcode': - gcode += value - break - else: - gcode += self.gcode - - end_gcode = self.gcode_footer() if self.app.defaults['cncjob_footer'] is True else '' - - # detect if using a HPGL preprocessor - hpgl = False - if self.cnc_tools: - for key in self.cnc_tools: - if 'ppname_g' in self.cnc_tools[key]['data']: - if 'hpgl' in self.cnc_tools[key]['data']['ppname_g']: - hpgl = True - break - elif self.exc_cnc_tools: - for key in self.cnc_tools: - if 'ppname_e' in self.cnc_tools[key]['data']: - if 'hpgl' in self.cnc_tools[key]['data']['ppname_e']: - hpgl = True - break - - if hpgl: - processed_gcode = '' - pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$") - for gline in gcode.splitlines(): - match = pa_re.search(gline) - if match: - x_int = int(float(match.group(1))) - y_int = int(float(match.group(2))) - new_line = 'PA%d,%d;\n' % (x_int, y_int) - processed_gcode += new_line - else: - processed_gcode += gline + '\n' - - gcode = processed_gcode - g = self.gcode_header() + '\n' + preamble + '\n' + gcode + postamble + end_gcode - else: - try: - g_idx = gcode.index('G94') - g = self.gcode_header() + gcode[:g_idx + 3] + '\n\n' + preamble + '\n' + \ - gcode[(g_idx + 3):] + postamble + end_gcode - except ValueError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("G-code does not have a G94 code and we will not include the code in the " - "'Prepend to GCode' text box")) - g = self.gcode_header() + '\n' + gcode + postamble + end_gcode - - # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box - if self.ui.toolchange_cb.get_value() is True: - # match = self.re_toolchange.search(g) - if 'M6' in g: - m6_code = self.parse_custom_toolchange_code(self.ui.toolchange_text.get_value()) - if m6_code is None or m6_code == '': - self.app.inform.emit( - '[ERROR_NOTCL] %s' % _("Cancelled. The Toolchange Custom code is enabled but it's empty.") - ) - return 'fail' - - g = g.replace('M6', m6_code) - self.app.inform.emit('[success] %s' % _("Toolchange G-code was replaced by a custom code.")) - - lines = StringIO(g) - - # Write - if filename is not None: - try: - force_windows_line_endings = self.app.defaults['cncjob_line_ending'] - if force_windows_line_endings and sys.platform != 'win32': - with open(filename, 'w', newline='\r\n') as f: - for line in lines: - f.write(line) - else: - with open(filename, 'w') as f: - for line in lines: - f.write(line) - except FileNotFoundError: - self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory")) - return - except PermissionError: - self.app.inform.emit( - '[WARNING] %s' % _("Permission denied, saving not possible.\n" - "Most likely another app is holding the file open and not accessible.") - ) - return 'fail' - elif to_file is False: - # Just for adding it to the recent files list. - if self.app.defaults["global_open_style"] is False: - self.app.file_opened.emit("cncjob", filename) - self.app.file_saved.emit("cncjob", filename) - - self.app.inform.emit('[success] %s: %s' % (_("Saved to"), filename)) - else: - return lines - - def on_toolchange_custom_clicked(self, signal): - """ - Handler for clicking toolchange custom. - - :param signal: - :return: - """ - - try: - if 'toolchange_custom' not in str(self.options['ppname_e']).lower(): - if self.ui.toolchange_cb.get_value(): - self.ui.toolchange_cb.set_value(False) - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("The used preprocessor file has to have in it's name: 'toolchange_custom'")) - except KeyError: - try: - for key in self.cnc_tools: - ppg = self.cnc_tools[key]['data']['ppname_g'] - if 'toolchange_custom' not in str(ppg).lower(): - print(ppg) - if self.ui.toolchange_cb.get_value(): - self.ui.toolchange_cb.set_value(False) - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("The used preprocessor file has to have in it's name: " - "'toolchange_custom'")) - except KeyError: - self.app.inform.emit('[ERROR] %s' % _("There is no preprocessor file.")) - - def get_gcode(self, preamble='', postamble=''): - """ - We need this to be able to get_gcode separately for shell command export_gcode - - :param preamble: Extra GCode added to the beginning of the GCode - :param postamble: Extra GCode added at the end of the GCode - :return: The modified GCode - """ - return preamble + '\n' + self.gcode + "\n" + postamble - - def get_svg(self): - # we need this to be able get_svg separately for shell command export_svg - pass - - def on_plot_cb_click(self, *args): - """ - Handler for clicking on the Plot checkbox. - - :param args: - :return: - """ - if self.muted_ui: - return - kind = self.ui.cncplot_method_combo.get_value() - self.plot(kind=kind) - self.read_form_item('plot') - - self.ui_disconnect() - cb_flag = self.ui.plot_cb.isChecked() - for row in range(self.ui.cnc_tools_table.rowCount()): - table_cb = self.ui.cnc_tools_table.cellWidget(row, 6) - if cb_flag: - table_cb.setChecked(True) - else: - table_cb.setChecked(False) - self.ui_connect() - - def on_plot_cb_click_table(self): - """ - Handler for clicking the plot checkboxes added into a Table on each row. Purpose: toggle visibility for the - tool/aperture found on that row. - :return: - """ - - # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) - self.ui_disconnect() - # cw = self.sender() - # cw_index = self.ui.cnc_tools_table.indexAt(cw.pos()) - # cw_row = cw_index.row() - - kind = self.ui.cncplot_method_combo.get_value() - - self.shapes.clear(update=True) - - for tooluid_key in self.cnc_tools: - tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia']))) - gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed'] - # tool_uid = int(self.ui.cnc_tools_table.item(cw_row, 3).text()) - - for r in range(self.ui.cnc_tools_table.rowCount()): - if int(self.ui.cnc_tools_table.item(r, 5).text()) == int(tooluid_key): - if self.ui.cnc_tools_table.cellWidget(r, 6).isChecked(): - self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed, kind=kind) - - self.shapes.redraw() - - # make sure that the general plot is disabled if one of the row plot's are disabled and - # if all the row plot's are enabled also enable the general plot checkbox - cb_cnt = 0 - total_row = self.ui.cnc_tools_table.rowCount() - for row in range(total_row): - if self.ui.cnc_tools_table.cellWidget(row, 6).isChecked(): - cb_cnt += 1 - else: - cb_cnt -= 1 - if cb_cnt < total_row: - self.ui.plot_cb.setChecked(False) - else: - self.ui.plot_cb.setChecked(True) - self.ui_connect() - - def plot(self, visible=None, kind='all'): - """ - # Does all the required setup and returns False - # if the 'ptint' option is set to False. - - :param visible: Boolean to decide if the object will be plotted as visible or disabled on canvas - :param kind: String. Can be "all" or "travel" or "cut". For CNCJob plotting - :return: None - """ - if not FlatCAMObj.plot(self): - return - - visible = visible if visible else self.options['plot'] - - if self.app.is_legacy is False: - if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value(): - self.text_col.enabled = True - else: - self.text_col.enabled = False - self.annotation.redraw() - - try: - if self.multitool is False: # single tool usage - try: - dia_plot = float(self.options["tooldia"]) - except ValueError: - # we may have a tuple with only one element and a comma - dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0] - self.plot2(dia_plot, obj=self, visible=visible, kind=kind) - else: - # multiple tools usage - if self.cnc_tools: - for tooluid_key in self.cnc_tools: - tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia']))) - gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed'] - self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind) - - # TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed - if self.exc_cnc_tools: - for tooldia_key in self.exc_cnc_tools: - tooldia = float('%.*f' % (self.decimals, float(tooldia_key))) - # gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed'] - gcode_parsed = self.gcode_parsed - self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind) - - self.shapes.redraw() - except (ObjectDeleted, AttributeError): - self.shapes.clear(update=True) - if self.app.is_legacy is False: - self.annotation.clear(update=True) - - def on_annotation_change(self): - """ - Handler for toggling the annotation display by clicking a checkbox. - :return: - """ - - if self.app.is_legacy is False: - if self.ui.annotation_cb.get_value(): - self.text_col.enabled = True - else: - self.text_col.enabled = False - # kind = self.ui.cncplot_method_combo.get_value() - # self.plot(kind=kind) - self.annotation.redraw() - else: - kind = self.ui.cncplot_method_combo.get_value() - self.plot(kind=kind) - - def convert_units(self, units): - """ - Units conversion used by the CNCJob objects. - - :param units: Can be "MM" or "IN" - :return: - """ - - log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()") - - factor = CNCjob.convert_units(self, units) - self.options["tooldia"] = float(self.options["tooldia"]) * factor - - param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', - 'endz', 'toolchangez'] - - temp_tools_dict = {} - tool_dia_copy = {} - data_copy = {} - - for tooluid_key, tooluid_value in self.cnc_tools.items(): - for dia_key, dia_value in tooluid_value.items(): - if dia_key == 'tooldia': - dia_value *= factor - dia_value = float('%.*f' % (self.decimals, dia_value)) - tool_dia_copy[dia_key] = dia_value - if dia_key == 'offset': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'offset_value': - dia_value *= factor - tool_dia_copy[dia_key] = dia_value - - if dia_key == 'type': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'tool_type': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'data': - for data_key, data_value in dia_value.items(): - # convert the form fields that are convertible - for param in param_list: - if data_key == param and data_value is not None: - data_copy[data_key] = data_value * factor - # copy the other dict entries that are not convertible - if data_key not in param_list: - data_copy[data_key] = data_value - tool_dia_copy[dia_key] = deepcopy(data_copy) - data_copy.clear() - - if dia_key == 'gcode': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'gcode_parsed': - tool_dia_copy[dia_key] = dia_value - if dia_key == 'solid_geometry': - tool_dia_copy[dia_key] = dia_value - - # if dia_key == 'solid_geometry': - # tool_dia_copy[dia_key] = affinity.scale(dia_value, xfact=factor, origin=(0, 0)) - # if dia_key == 'gcode_parsed': - # for g in dia_value: - # g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0)) - # - # tool_dia_copy['gcode_parsed'] = deepcopy(dia_value) - # tool_dia_copy['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_value]) - - temp_tools_dict.update({ - tooluid_key: deepcopy(tool_dia_copy) - }) - tool_dia_copy.clear() - - self.cnc_tools.clear() - self.cnc_tools = deepcopy(temp_tools_dict) - - -class FlatCAMScript(FlatCAMObj): - """ - Represents a TCL script object. - """ - optionChanged = QtCore.pyqtSignal(str) - ui_type = ScriptObjectUI - - def __init__(self, name): - self.decimals = self.app.decimals - - FlatCAMApp.App.log.debug("Creating a FlatCAMScript object...") - FlatCAMObj.__init__(self, name) - - self.kind = "script" - - self.options.update({ - "plot": True, - "type": 'Script', - "source_file": '', - }) - - self.units = '' - - self.ser_attrs = ['options', 'kind', 'source_file'] - self.source_file = '' - self.script_code = '' - - self.units_found = self.app.defaults['units'] - - # self.script_editor_tab = TextEditor(app=self.app, plain_text=True) - self.script_editor_tab = TextEditor(app=self.app, plain_text=True) - - def set_ui(self, ui): - """ - Sets the Object UI in Selected Tab for the FlatCAM Script type of object. - :param ui: - :return: - """ - FlatCAMObj.set_ui(self, ui) - FlatCAMApp.App.log.debug("FlatCAMScript.set_ui()") - - assert isinstance(self.ui, ScriptObjectUI), \ - "Expected a ScriptObjectUI, got %s" % type(self.ui) - - self.units = self.app.defaults['units'].upper() - self.units_found = self.app.defaults['units'] - - # Fill form fields only on object create - self.to_form() - - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText(_( - 'Basic' - )) - else: - self.ui.level.setText(_( - 'Advanced' - )) - - # tab_here = False - # # try to not add too many times a tab that it is already installed - # for idx in range(self.app.ui.plot_tab_area.count()): - # if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']: - # tab_here = True - # break - # - # # add the tab if it is not already added - # if tab_here is False: - # self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor")) - # self.script_editor_tab.setObjectName(self.options['name']) - - self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor")) - self.script_editor_tab.setObjectName(self.options['name']) - - # first clear previous text in text editor (if any) - # self.script_editor_tab.code_editor.clear() - # self.script_editor_tab.code_editor.setReadOnly(False) - - self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter']) - self.on_autocomplete_changed(state=self.app.defaults['script_autocompleter']) - - self.script_editor_tab.buttonRun.show() - - # Switch plot_area to CNCJob tab - self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab) - - flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" - self.script_editor_tab.buttonOpen.clicked.disconnect() - self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt)) - self.script_editor_tab.buttonSave.clicked.disconnect() - self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt)) - - self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code) - self.script_editor_tab.handleTextChanged() - - self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed) - - self.ser_attrs = ['options', 'kind', 'source_file'] - - # ---------------------------------------------------- # - # ----------- LOAD THE TEXT SOURCE FILE -------------- # - # ---------------------------------------------------- # - self.app.proc_container.view.set_busy(_("Loading...")) - self.script_editor_tab.t_frame.hide() - - try: - self.script_editor_tab.code_editor.setPlainText(self.source_file) - # for line in self.source_file.splitlines(): - # QtWidgets.QApplication.processEvents() - # self.script_editor_tab.code_editor.append(line) - except Exception as e: - log.debug("FlatCAMScript.set_ui() --> %s" % str(e)) - - self.script_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.End) - self.script_editor_tab.t_frame.show() - - self.app.proc_container.view.set_idle() - self.build_ui() - - def build_ui(self): - FlatCAMObj.build_ui(self) - - def handle_run_code(self): - # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell - # tries to print on a hidden widget, therefore show the dock if hidden - if self.app.ui.shell_dock.isHidden(): - self.app.ui.shell_dock.show() - - self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText()) - - old_line = '' - for tcl_command_line in self.script_code.splitlines(): - # do not process lines starting with '#' = comment and empty lines - if not tcl_command_line.startswith('#') and tcl_command_line != '': - # id FlatCAM is run in Windows then replace all the slashes with - # the UNIX style slash that TCL understands - if sys.platform == 'win32': - if "open" in tcl_command_line: - tcl_command_line = tcl_command_line.replace('\\', '/') - - if old_line != '': - new_command = old_line + tcl_command_line + '\n' - else: - new_command = tcl_command_line - - # execute the actual Tcl command - try: - self.app.shell.open_processing() # Disables input box. - - result = self.app.tcl.eval(str(new_command)) - if result != 'None': - self.app.shell.append_output(result + '\n') - - old_line = '' - except tk.TclError: - old_line = old_line + tcl_command_line + '\n' - except Exception as e: - log.debug("FlatCAMScript.handleRunCode() --> %s" % str(e)) - - if old_line != '': - # it means that the script finished with an error - result = self.app.tcl.eval("set errorInfo") - log.error("Exec command Exception: %s" % (result + '\n')) - self.app.shell.append_error('ERROR: ' + result + '\n') - - self.app.shell.close_processing() - - def on_autocomplete_changed(self, state): - if state: - self.script_editor_tab.code_editor.completer_enable = True - else: - self.script_editor_tab.code_editor.completer_enable = False - - def to_dict(self): - """ - Returns a representation of the object as a dictionary. - Attributes to include are listed in ``self.ser_attrs``. - - :return: A dictionary-encoded copy of the object. - :rtype: dict - """ - d = {} - for attr in self.ser_attrs: - d[attr] = getattr(self, attr) - return d - - def from_dict(self, d): - """ - Sets object's attributes from a dictionary. - Attributes to include are listed in ``self.ser_attrs``. - This method will look only for only and all the - attributes in ``self.ser_attrs``. They must all - be present. Use only for deserializing saved - objects. - - :param d: Dictionary of attributes to set in the object. - :type d: dict - :return: None - """ - for attr in self.ser_attrs: - setattr(self, attr, d[attr]) - - -class FlatCAMDocument(FlatCAMObj): - """ - Represents a Document object. - """ - optionChanged = QtCore.pyqtSignal(str) - ui_type = DocumentObjectUI - - def __init__(self, name): - self.decimals = self.app.decimals - - FlatCAMApp.App.log.debug("Creating a Document object...") - FlatCAMObj.__init__(self, name) - - self.kind = "document" - self.units = '' - - self.ser_attrs = ['options', 'kind', 'source_file'] - self.source_file = '' - self.doc_code = '' - - self.font_name = None - self.font_italic = None - self.font_bold = None - self.font_underline = None - - self.document_editor_tab = None - - self._read_only = False - self.units_found = self.app.defaults['units'] - - def set_ui(self, ui): - FlatCAMObj.set_ui(self, ui) - FlatCAMApp.App.log.debug("FlatCAMDocument.set_ui()") - - assert isinstance(self.ui, DocumentObjectUI), \ - "Expected a DocumentObjectUI, got %s" % type(self.ui) - - self.units = self.app.defaults['units'].upper() - self.units_found = self.app.defaults['units'] - - # Fill form fields only on object create - self.to_form() - - # Show/Hide Advanced Options - if self.app.defaults["global_app_level"] == 'b': - self.ui.level.setText(_( - 'Basic' - )) - else: - self.ui.level.setText(_( - 'Advanced' - )) - - self.document_editor_tab = TextEditor(app=self.app) - stylesheet = """ - QTextEdit {selection-background-color:%s; - selection-color:white; - } - """ % self.app.defaults["document_sel_color"] - - self.document_editor_tab.code_editor.setStyleSheet(stylesheet) - - # first clear previous text in text editor (if any) - self.document_editor_tab.code_editor.clear() - self.document_editor_tab.code_editor.setReadOnly(self._read_only) - - self.document_editor_tab.buttonRun.hide() - - self.ui.autocomplete_cb.set_value(self.app.defaults['document_autocompleter']) - self.on_autocomplete_changed(state=self.app.defaults['document_autocompleter']) - self.on_tab_size_change(val=self.app.defaults['document_tab_size']) - - flt = "FlatCAM Docs (*.FlatDoc);;All Files (*.*)" - - # ###################################################################### - # ######################## SIGNALS ##################################### - # ###################################################################### - self.document_editor_tab.buttonOpen.clicked.disconnect() - self.document_editor_tab.buttonOpen.clicked.connect(lambda: self.document_editor_tab.handleOpen(filt=flt)) - self.document_editor_tab.buttonSave.clicked.disconnect() - self.document_editor_tab.buttonSave.clicked.connect(lambda: self.document_editor_tab.handleSaveGCode(filt=flt)) - - self.document_editor_tab.code_editor.textChanged.connect(self.on_text_changed) - - self.ui.font_type_cb.currentFontChanged.connect(self.font_family) - self.ui.font_size_cb.activated.connect(self.font_size) - self.ui.font_bold_tb.clicked.connect(self.on_bold_button) - self.ui.font_italic_tb.clicked.connect(self.on_italic_button) - self.ui.font_under_tb.clicked.connect(self.on_underline_button) - - self.ui.font_color_entry.editingFinished.connect(self.on_font_color_entry) - self.ui.font_color_button.clicked.connect(self.on_font_color_button) - self.ui.sel_color_entry.editingFinished.connect(self.on_selection_color_entry) - self.ui.sel_color_button.clicked.connect(self.on_selection_color_button) - - self.ui.al_left_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignLeft)) - self.ui.al_center_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignCenter)) - self.ui.al_right_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignRight)) - self.ui.al_justify_tb.clicked.connect( - lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignJustify) - ) - - self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed) - self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change) - # ####################################################################### - - self.ui.font_color_entry.set_value(self.app.defaults['document_font_color']) - self.ui.font_color_button.setStyleSheet( - "background-color:%s" % str(self.app.defaults['document_font_color'])) - - self.ui.sel_color_entry.set_value(self.app.defaults['document_sel_color']) - self.ui.sel_color_button.setStyleSheet( - "background-color:%s" % self.app.defaults['document_sel_color']) - - self.ui.font_size_cb.setCurrentIndex(int(self.app.defaults['document_font_size'])) - - self.document_editor_tab.handleTextChanged() - self.ser_attrs = ['options', 'kind', 'source_file'] - - if Qt.mightBeRichText(self.source_file): - self.document_editor_tab.code_editor.setHtml(self.source_file) - else: - for line in self.source_file.splitlines(): - self.document_editor_tab.code_editor.append(line) - - self.build_ui() - - @property - def read_only(self): - return self._read_only - - @read_only.setter - def read_only(self, val): - if val: - self._read_only = True - else: - self._read_only = False - - def build_ui(self): - FlatCAMObj.build_ui(self) - tab_here = False - - # try to not add too many times a tab that it is already installed - for idx in range(self.app.ui.plot_tab_area.count()): - if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']: - tab_here = True - break - - # add the tab if it is not already added - if tab_here is False: - self.app.ui.plot_tab_area.addTab(self.document_editor_tab, '%s' % _("Document Editor")) - self.document_editor_tab.setObjectName(self.options['name']) - - # Switch plot_area to CNCJob tab - self.app.ui.plot_tab_area.setCurrentWidget(self.document_editor_tab) - - def on_autocomplete_changed(self, state): - if state: - self.document_editor_tab.code_editor.completer_enable = True - else: - self.document_editor_tab.code_editor.completer_enable = False - - def on_tab_size_change(self, val=None): - try: - self.ui.tab_size_spinner.returnPressed.disconnect(self.on_tab_size_change) - except TypeError: - pass - - if val: - self.ui.tab_size_spinner.set_value(val) - - tab_balue = int(self.ui.tab_size_spinner.get_value()) - self.document_editor_tab.code_editor.setTabStopWidth(tab_balue) - self.app.defaults['document_tab_size'] = tab_balue - - self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change) - - def on_text_changed(self): - self.source_file = self.document_editor_tab.code_editor.toHtml() - # print(self.source_file) - - def font_family(self, font): - # self.document_editor_tab.code_editor.selectAll() - font.setPointSize(float(self.ui.font_size_cb.get_value())) - self.document_editor_tab.code_editor.setCurrentFont(font) - self.font_name = self.ui.font_type_cb.currentFont().family() - - def font_size(self): - # self.document_editor_tab.code_editor.selectAll() - self.document_editor_tab.code_editor.setFontPointSize(float(self.ui.font_size_cb.get_value())) - - def on_bold_button(self): - if self.ui.font_bold_tb.isChecked(): - self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Bold) - self.font_bold = True - else: - self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Normal) - self.font_bold = False - - def on_italic_button(self): - if self.ui.font_italic_tb.isChecked(): - self.document_editor_tab.code_editor.setFontItalic(True) - self.font_italic = True - else: - self.document_editor_tab.code_editor.setFontItalic(False) - self.font_italic = False - - def on_underline_button(self): - if self.ui.font_under_tb.isChecked(): - self.document_editor_tab.code_editor.setFontUnderline(True) - self.font_underline = True - else: - self.document_editor_tab.code_editor.setFontUnderline(False) - self.font_underline = False - - # Setting font colors handlers - def on_font_color_entry(self): - self.app.defaults['document_font_color'] = self.ui.font_color_entry.get_value() - self.ui.font_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_font_color'])) - - def on_font_color_button(self): - current_color = QtGui.QColor(self.app.defaults['document_font_color']) - - c_dialog = QtWidgets.QColorDialog() - font_color = c_dialog.getColor(initial=current_color) - - if font_color.isValid() is False: - return - - self.document_editor_tab.code_editor.setTextColor(font_color) - self.ui.font_color_button.setStyleSheet("background-color:%s" % str(font_color.name())) - - new_val = str(font_color.name()) - self.ui.font_color_entry.set_value(new_val) - self.app.defaults['document_font_color'] = new_val - - # Setting selection colors handlers - def on_selection_color_entry(self): - self.app.defaults['document_sel_color'] = self.ui.sel_color_entry.get_value() - self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_sel_color'])) - - def on_selection_color_button(self): - current_color = QtGui.QColor(self.app.defaults['document_sel_color']) - - c_dialog = QtWidgets.QColorDialog() - sel_color = c_dialog.getColor(initial=current_color) - - if sel_color.isValid() is False: - return - - p = QtGui.QPalette() - p.setColor(QtGui.QPalette.Highlight, sel_color) - p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('white')) - - self.document_editor_tab.code_editor.setPalette(p) - - self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(sel_color.name())) - - new_val = str(sel_color.name()) - self.ui.sel_color_entry.set_value(new_val) - self.app.defaults['document_sel_color'] = new_val - - def to_dict(self): - """ - Returns a representation of the object as a dictionary. - Attributes to include are listed in ``self.ser_attrs``. - - :return: A dictionary-encoded copy of the object. - :rtype: dict - """ - d = {} - for attr in self.ser_attrs: - d[attr] = getattr(self, attr) - return d - - def from_dict(self, d): - """ - Sets object's attributes from a dictionary. - Attributes to include are listed in ``self.ser_attrs``. - This method will look only for only and all the - attributes in ``self.ser_attrs``. They must all - be present. Use only for deserializing saved - objects. - - :param d: Dictionary of attributes to set in the object. - :type d: dict - :return: None - """ - for attr in self.ser_attrs: - setattr(self, attr, d[attr]) - -# end of file diff --git a/FlatCAMPostProc.py b/FlatCAMPostProc.py index a822c01d..15991d80 100644 --- a/FlatCAMPostProc.py +++ b/FlatCAMPostProc.py @@ -12,8 +12,10 @@ from abc import ABCMeta, abstractmethod import math # module-root dictionary of preprocessors -import FlatCAMApp +import logging + +log = logging.getLogger('base') preprocessors = {} @@ -23,7 +25,7 @@ class ABCPostProcRegister(ABCMeta): newclass = super(ABCPostProcRegister, cls).__new__(cls, clsname, bases, attrs) if object not in bases: if newclass.__name__ in preprocessors: - FlatCAMApp.App.log.warning('Preprocessor %s has been overriden' % newclass.__name__) + log.warning('Preprocessor %s has been overriden' % newclass.__name__) preprocessors[newclass.__name__] = newclass() # here is your register function return newclass diff --git a/FlatCAMTranslation.py b/FlatCAMTranslation.py index f7e75d96..a4ab8f51 100644 --- a/FlatCAMTranslation.py +++ b/FlatCAMTranslation.py @@ -29,6 +29,7 @@ languages_dict = { 'en': 'English', 'es': 'Spanish', 'fr': 'French', + 'hu': 'Hungarian', 'it': 'Italian', 'ro': 'Romanian', 'ru': 'Russian', @@ -87,9 +88,9 @@ def on_language_apply_click(app, restart=False): theme = 'white' if theme == 'white': - resource_loc = 'share' + resource_loc = 'assets/resources' else: - resource_loc = 'share' + resource_loc = 'assets/resources' # do nothing if trying to apply the language that is the current language (already applied). settings = QSettings("Open Source", "FlatCAM") @@ -179,15 +180,22 @@ def restart_program(app, ask=None): theme = 'white' if theme == 'white': - resource_loc = 'share' + resource_loc = 'assets/resources' else: - resource_loc = 'share' + resource_loc = 'assets/resources' - # close the Socket in ArgsThread class - app.new_launch.listener.close() + # try to quit the Socket opened by ArgsThread class + try: + app.new_launch.thread_exit = True + app.new_launch.listener.close() + except Exception as err: + log.debug("FlatCAMTranslation.restart_program() --> %s" % str(err)) - # close the QThread that runs ArgsThread class - app.th.quit() + # try to quit the QThread that run ArgsThread class + try: + app.th.quit() + except Exception as err: + log.debug("FlatCAMTranslation.restart_program() --> %s" % str(err)) if app.should_we_save and app.collection.get_list() or ask is True: msgbox = QtWidgets.QMessageBox() @@ -206,6 +214,6 @@ def restart_program(app, ask=None): if response == bt_yes: app.on_file_saveprojectas(use_thread=True, quit_action=True) - app.save_defaults() + app.preferencesUiManager.save_defaults() python = sys.executable os.execl(python, python, *sys.argv) diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7bda1647 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ + +# Install on Ubuntu(-like) systems + +# Install dependencies system-wide (including python modules) +install_dependencies: + sudo -H ./setup_ubuntu.sh + +USER_ID = $(shell id -u) + +LOCAL_PATH = $(shell pwd) +LOCAL_APPS_PATH = ~/.local/share/applications +ASSEST_PATH = assets/linux + +INSTALL_PATH = /usr/share/flatcam-beta +APPS_PATH = /usr/share/applications + +MIN_PY3_MINOR_VERSION := 5 +PY3_MINOR_VERSION := $(shell python3 --version | cut -d'.' -f2) + +ifneq ($(MIN_PY3_MINOR_VERSION), $(firstword $(sort $(PY3_MINOR_VERSION) $(MIN_PY3_MINOR_VERSION)))) + $(info Current python version is 3.$(PY3_MINOR_VERSION)) + $(error You must have at least 3.$(MIN_PY3_MINOR_VERSION) installed) +endif + +install: +ifeq ($(USER_ID), 0) + @ echo "Installing it system-wide" + cp -rf $(LOCAL_PATH) $(INSTALL_PATH) + @ sed -i "s|python_script_path=.*|python_script_path=$(INSTALL_PATH)|g" $(INSTALL_PATH)/assets/linux/flatcam-beta + ln -sf $(INSTALL_PATH)/assets/linux/flatcam-beta /usr/local/bin + cp -f $(ASSEST_PATH)/flatcam-beta.desktop $(APPS_PATH) + @ sed -i "s|Exec=.*|Exec=$(INSTALL_PATH)/$(ASSEST_PATH)/flatcam-beta|g" $(APPS_PATH)/flatcam-beta.desktop + @ sed -i "s|Icon=.*|Icon=$(INSTALL_PATH)/$(ASSEST_PATH)/icon.png|g" $(APPS_PATH)/flatcam-beta.desktop +else + @ echo "Installing locally for $(USER) only" + cp -f $(ASSEST_PATH)/flatcam-beta.desktop $(LOCAL_APPS_PATH) + @ sed -i "s|Exec=.*|Exec=$(LOCAL_PATH)/$(ASSEST_PATH)/flatcam-beta|g" $(LOCAL_APPS_PATH)/flatcam-beta.desktop + @ sed -i "s|Icon=.*|Icon=$(LOCAL_PATH)/$(ASSEST_PATH)/icon.png|g" $(LOCAL_APPS_PATH)/flatcam-beta.desktop +endif + +remove: +ifeq ($(USER_ID), 0) + @ echo "Uninstalling it system-wide" + rm -rf $(INSTALL_PATH) + rm -f /usr/local/bin/flatcam-beta + rm -r $(APPS_PATH)/flatcam-beta.desktop +else + @ echo "Uninstalling only for $(USER) user" + rm -f $(LOCAL_APPS_PATH)/flatcam-beta.desktop +endif diff --git a/README.md b/README.md index c7756920..094eb7b5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ FlatCAM BETA (c) 2019 - by Marius Stanciu Based on FlatCAM: 2D Computer-Aided PCB Manufacturing by (c) 2014-2016 Juan Pablo Caram -================================================= +===================================================================== FlatCAM is a program for preparing CNC jobs for making PCBs on a CNC router. Among other things, it can take a Gerber file generated by your favorite PCB CAD program, and create G-Code for Isolation routing. -================================================= +===================================================================== ------------- Installation instructions ---------- +-------------------------- Installation instructions ---------------- Works with Python version 3.5 or greater and PyQt5. More on the YouTube channel: https://www.youtube.com/playlist?list=PLVvP2SYRpx-AQgNlfoxw93tXUXon7G94_ + You can contact me on my email address found in the app in: Menu -> Help -> About FlatCAM -> Programmers -> Marius Stanciu @@ -20,7 +21,8 @@ Menu -> Help -> About FlatCAM -> Programmers -> Marius Stanciu - Download sources from: https://bitbucket.org/jpcgt/flatcam/downloads/ - Unzip them on a HDD location that your user has permissions for. -1. Windows +1.Windows + - download the provided installer (for your OS flavor 64bit or 32bit) from: https://bitbucket.org/jpcgt/flatcam/downloads/ - execute the installer and install the program. It is recommended to install as a Local User. @@ -33,7 +35,10 @@ Use one of the versions (64bit or 32it) that are compatible with your OS. To save space use one of the versions that have the smaller size (they offer 2 versions: one with size of few hundred MB and one smaller with size of few tens of MB) - add Python folder and Python\Scripts folder to your Windows Path (https://docs.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee537574(v%3Doffice.14)) -- verify that the pip package can be run by opening Command Prompt(Admin) and running the command: pip -V +- verify that the pip package can be run by opening Command Prompt(Admin) and running the command: +``` +pip -V +``` - look in the requirements.txt file (found in the sources folder) and install all the dependencies using the pip package. The required wheels can be downloaded either from: @@ -42,26 +47,57 @@ or https://pypi.org/ You can download all the required wheels files into a folder (e.g D:\my_folder) and install them from Command Prompt like this: + +``` cd D:\my_folder +``` + and for each wheel file (*.whl) run: +``` D:\my_folder\> pip install --upgrade package_from_requirements.whl +``` Run FlatCAM beta from the installation folder (e.g D:\FlatCAM_beta) in the Command Prompt with the following command: cd D:\FlatCAM_beta python FlatCAM.py -2. Linux +2.Linux + - make sure that Python 3.8 is installed on your OS and that the command: python3 -V confirm it -- verify that the pip package is installed for your Python installation (e.g 3.8) by running the command pip3 -V. +- verify that the pip package is installed for your Python installation (e.g 3.8) by running the command: +``` +pip3 -V +``` + If it is not installed, install it. In Ubuntu-like OS's it is done like this: +``` sudo apt-get install python3-pip +``` or: +``` sudo apt-get install python3.8-pip +``` - verify that the file setup_ubuntu.sh has Linux line-endings (LF) and that it is executable (chmod +x setup_ubuntu.sh) -- run the file setup_ubuntu.sh and install all the dependencies with the command: ./setup_ubuntu.sh +- run the file setup_ubuntu.sh and install all the dependencies with the command: +``` +./setup_ubuntu.sh +``` - if the previous command is successful and has no errors, run FlatCAM with the command: python3 FlatCAM.py -3. MacOS +- Alternatively you can install it on Ubuntu with: +``` +# Optional if depencencies are missing +make install_dependencies + +# Install for the current user only (using the folder in its place) +make install + +# System-wide instalation +sudo make install +``` + +3.MacOS + Instructions from here: https://gist.github.com/natevw/3e6fc929aff358b38c0a#gistcomment-3111878 - create a folder to hold the sources somewhere on your HDD: diff --git a/assets/linux/flatcam-beta b/assets/linux/flatcam-beta new file mode 100755 index 00000000..92f18f39 --- /dev/null +++ b/assets/linux/flatcam-beta @@ -0,0 +1,5 @@ +#!/bin/bash + +script_path=$(readlink -f $0) +python_script_path=$(dirname $script_path)/../../ +python3 $python_script_path/FlatCAM.py $* diff --git a/assets/linux/flatcam-beta.desktop b/assets/linux/flatcam-beta.desktop new file mode 100644 index 00000000..0b729b4f --- /dev/null +++ b/assets/linux/flatcam-beta.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=FlatCAM Beta +Exec=./bin/flatcam-beta +Type=Application +StartupNotify=true +Icon=./assets/icon.png +Comment=G-Code from GERBERS diff --git a/assets/linux/icon.png b/assets/linux/icon.png new file mode 100644 index 00000000..5f6d1726 Binary files /dev/null and b/assets/linux/icon.png differ diff --git a/share/about32.png b/assets/resources/about32.png similarity index 100% rename from share/about32.png rename to assets/resources/about32.png diff --git a/share/active.gif b/assets/resources/active.gif similarity index 100% rename from share/active.gif rename to assets/resources/active.gif diff --git a/share/active_2.gif b/assets/resources/active_2.gif similarity index 100% rename from share/active_2.gif rename to assets/resources/active_2.gif diff --git a/share/active_2_static.png b/assets/resources/active_2_static.png similarity index 100% rename from share/active_2_static.png rename to assets/resources/active_2_static.png diff --git a/share/active_3.gif b/assets/resources/active_3.gif similarity index 100% rename from share/active_3.gif rename to assets/resources/active_3.gif diff --git a/share/active_3_static.png b/assets/resources/active_3_static.png similarity index 100% rename from share/active_3_static.png rename to assets/resources/active_3_static.png diff --git a/share/active_4.gif b/assets/resources/active_4.gif similarity index 100% rename from share/active_4.gif rename to assets/resources/active_4.gif diff --git a/share/active_4_static.png b/assets/resources/active_4_static.png similarity index 100% rename from share/active_4_static.png rename to assets/resources/active_4_static.png diff --git a/share/active_static.png b/assets/resources/active_static.png similarity index 100% rename from share/active_static.png rename to assets/resources/active_static.png diff --git a/share/addarray16.png b/assets/resources/addarray16.png similarity index 100% rename from share/addarray16.png rename to assets/resources/addarray16.png diff --git a/share/addarray20.png b/assets/resources/addarray20.png similarity index 100% rename from share/addarray20.png rename to assets/resources/addarray20.png diff --git a/share/addarray32.png b/assets/resources/addarray32.png similarity index 100% rename from share/addarray32.png rename to assets/resources/addarray32.png diff --git a/share/aero.png b/assets/resources/aero.png similarity index 100% rename from share/aero.png rename to assets/resources/aero.png diff --git a/share/aero_arc.png b/assets/resources/aero_arc.png similarity index 100% rename from share/aero_arc.png rename to assets/resources/aero_arc.png diff --git a/share/aero_array.png b/assets/resources/aero_array.png similarity index 100% rename from share/aero_array.png rename to assets/resources/aero_array.png diff --git a/share/aero_buffer.png b/assets/resources/aero_buffer.png similarity index 100% rename from share/aero_buffer.png rename to assets/resources/aero_buffer.png diff --git a/share/aero_circle.png b/assets/resources/aero_circle.png similarity index 100% rename from share/aero_circle.png rename to assets/resources/aero_circle.png diff --git a/share/aero_circle_geo.png b/assets/resources/aero_circle_geo.png similarity index 100% rename from share/aero_circle_geo.png rename to assets/resources/aero_circle_geo.png diff --git a/share/aero_disc.png b/assets/resources/aero_disc.png similarity index 100% rename from share/aero_disc.png rename to assets/resources/aero_disc.png diff --git a/share/aero_drill.png b/assets/resources/aero_drill.png similarity index 100% rename from share/aero_drill.png rename to assets/resources/aero_drill.png diff --git a/share/aero_drill_array.png b/assets/resources/aero_drill_array.png similarity index 100% rename from share/aero_drill_array.png rename to assets/resources/aero_drill_array.png diff --git a/share/aero_path1.png b/assets/resources/aero_path1.png similarity index 100% rename from share/aero_path1.png rename to assets/resources/aero_path1.png diff --git a/share/aero_path2.png b/assets/resources/aero_path2.png similarity index 100% rename from share/aero_path2.png rename to assets/resources/aero_path2.png diff --git a/share/aero_path3.png b/assets/resources/aero_path3.png similarity index 100% rename from share/aero_path3.png rename to assets/resources/aero_path3.png diff --git a/share/aero_path4.png b/assets/resources/aero_path4.png similarity index 100% rename from share/aero_path4.png rename to assets/resources/aero_path4.png diff --git a/share/aero_path5.png b/assets/resources/aero_path5.png similarity index 100% rename from share/aero_path5.png rename to assets/resources/aero_path5.png diff --git a/share/aero_semidisc.png b/assets/resources/aero_semidisc.png similarity index 100% rename from share/aero_semidisc.png rename to assets/resources/aero_semidisc.png diff --git a/share/aero_slot.png b/assets/resources/aero_slot.png similarity index 100% rename from share/aero_slot.png rename to assets/resources/aero_slot.png diff --git a/share/aero_text.png b/assets/resources/aero_text.png similarity index 100% rename from share/aero_text.png rename to assets/resources/aero_text.png diff --git a/share/align16.png b/assets/resources/align16.png similarity index 100% rename from share/align16.png rename to assets/resources/align16.png diff --git a/share/align32.png b/assets/resources/align32.png similarity index 100% rename from share/align32.png rename to assets/resources/align32.png diff --git a/share/align_center32.png b/assets/resources/align_center32.png similarity index 100% rename from share/align_center32.png rename to assets/resources/align_center32.png diff --git a/share/align_justify32.png b/assets/resources/align_justify32.png similarity index 100% rename from share/align_justify32.png rename to assets/resources/align_justify32.png diff --git a/share/align_left32.png b/assets/resources/align_left32.png similarity index 100% rename from share/align_left32.png rename to assets/resources/align_left32.png diff --git a/share/align_right32.png b/assets/resources/align_right32.png similarity index 100% rename from share/align_right32.png rename to assets/resources/align_right32.png diff --git a/share/aperture16.png b/assets/resources/aperture16.png similarity index 100% rename from share/aperture16.png rename to assets/resources/aperture16.png diff --git a/share/aperture32.png b/assets/resources/aperture32.png similarity index 100% rename from share/aperture32.png rename to assets/resources/aperture32.png diff --git a/share/arc16.png b/assets/resources/arc16.png similarity index 100% rename from share/arc16.png rename to assets/resources/arc16.png diff --git a/share/arc24.png b/assets/resources/arc24.png similarity index 100% rename from share/arc24.png rename to assets/resources/arc24.png diff --git a/share/arc32.png b/assets/resources/arc32.png similarity index 100% rename from share/arc32.png rename to assets/resources/arc32.png diff --git a/share/axis32.png b/assets/resources/axis32.png similarity index 100% rename from share/axis32.png rename to assets/resources/axis32.png diff --git a/share/backup24.png b/assets/resources/backup24.png similarity index 100% rename from share/backup24.png rename to assets/resources/backup24.png diff --git a/share/backup_export24.png b/assets/resources/backup_export24.png similarity index 100% rename from share/backup_export24.png rename to assets/resources/backup_export24.png diff --git a/share/backup_import24.png b/assets/resources/backup_import24.png similarity index 100% rename from share/backup_import24.png rename to assets/resources/backup_import24.png diff --git a/share/black32.png b/assets/resources/black32.png similarity index 100% rename from share/black32.png rename to assets/resources/black32.png diff --git a/share/blocked16.png b/assets/resources/blocked16.png similarity index 100% rename from share/blocked16.png rename to assets/resources/blocked16.png diff --git a/share/blue32.png b/assets/resources/blue32.png similarity index 100% rename from share/blue32.png rename to assets/resources/blue32.png diff --git a/share/bluelight12.png b/assets/resources/bluelight12.png similarity index 100% rename from share/bluelight12.png rename to assets/resources/bluelight12.png diff --git a/share/bold32.png b/assets/resources/bold32.png similarity index 100% rename from share/bold32.png rename to assets/resources/bold32.png diff --git a/share/bookmarks16.png b/assets/resources/bookmarks16.png similarity index 100% rename from share/bookmarks16.png rename to assets/resources/bookmarks16.png diff --git a/share/bookmarks32.png b/assets/resources/bookmarks32.png similarity index 100% rename from share/bookmarks32.png rename to assets/resources/bookmarks32.png diff --git a/share/brown32.png b/assets/resources/brown32.png similarity index 100% rename from share/brown32.png rename to assets/resources/brown32.png diff --git a/share/buffer16-2.png b/assets/resources/buffer16-2.png similarity index 100% rename from share/buffer16-2.png rename to assets/resources/buffer16-2.png diff --git a/share/buffer16.png b/assets/resources/buffer16.png similarity index 100% rename from share/buffer16.png rename to assets/resources/buffer16.png diff --git a/share/buffer20.png b/assets/resources/buffer20.png similarity index 100% rename from share/buffer20.png rename to assets/resources/buffer20.png diff --git a/share/buffer24.png b/assets/resources/buffer24.png similarity index 100% rename from share/buffer24.png rename to assets/resources/buffer24.png diff --git a/share/bug16.png b/assets/resources/bug16.png similarity index 100% rename from share/bug16.png rename to assets/resources/bug16.png diff --git a/share/bug32.png b/assets/resources/bug32.png similarity index 100% rename from share/bug32.png rename to assets/resources/bug32.png diff --git a/share/calculator16.png b/assets/resources/calculator16.png similarity index 100% rename from share/calculator16.png rename to assets/resources/calculator16.png diff --git a/share/calculator24.png b/assets/resources/calculator24.png similarity index 100% rename from share/calculator24.png rename to assets/resources/calculator24.png diff --git a/share/calibrate_16.png b/assets/resources/calibrate_16.png similarity index 100% rename from share/calibrate_16.png rename to assets/resources/calibrate_16.png diff --git a/share/calibrate_32.png b/assets/resources/calibrate_32.png similarity index 100% rename from share/calibrate_32.png rename to assets/resources/calibrate_32.png diff --git a/share/cancel_edit16.png b/assets/resources/cancel_edit16.png similarity index 100% rename from share/cancel_edit16.png rename to assets/resources/cancel_edit16.png diff --git a/share/cancel_edit32.png b/assets/resources/cancel_edit32.png similarity index 100% rename from share/cancel_edit32.png rename to assets/resources/cancel_edit32.png diff --git a/share/circle32.png b/assets/resources/circle32.png similarity index 100% rename from share/circle32.png rename to assets/resources/circle32.png diff --git a/share/clear_plot16.png b/assets/resources/clear_plot16.png similarity index 100% rename from share/clear_plot16.png rename to assets/resources/clear_plot16.png diff --git a/share/clear_plot32.png b/assets/resources/clear_plot32.png similarity index 100% rename from share/clear_plot32.png rename to assets/resources/clear_plot32.png diff --git a/share/close_edit_file16.png b/assets/resources/close_edit_file16.png similarity index 100% rename from share/close_edit_file16.png rename to assets/resources/close_edit_file16.png diff --git a/share/close_edit_file32.png b/assets/resources/close_edit_file32.png similarity index 100% rename from share/close_edit_file32.png rename to assets/resources/close_edit_file32.png diff --git a/share/cnc16.png b/assets/resources/cnc16.png similarity index 100% rename from share/cnc16.png rename to assets/resources/cnc16.png diff --git a/share/cnc32.png b/assets/resources/cnc32.png similarity index 100% rename from share/cnc32.png rename to assets/resources/cnc32.png diff --git a/share/code.png b/assets/resources/code.png similarity index 100% rename from share/code.png rename to assets/resources/code.png diff --git a/share/code_editor32.png b/assets/resources/code_editor32.png similarity index 100% rename from share/code_editor32.png rename to assets/resources/code_editor32.png diff --git a/share/convert24.png b/assets/resources/convert24.png similarity index 100% rename from share/convert24.png rename to assets/resources/convert24.png diff --git a/share/copperfill16.png b/assets/resources/copperfill16.png similarity index 100% rename from share/copperfill16.png rename to assets/resources/copperfill16.png diff --git a/share/copperfill32.png b/assets/resources/copperfill32.png similarity index 100% rename from share/copperfill32.png rename to assets/resources/copperfill32.png diff --git a/share/copy.png b/assets/resources/copy.png similarity index 100% rename from share/copy.png rename to assets/resources/copy.png diff --git a/share/copy16.png b/assets/resources/copy16.png similarity index 100% rename from share/copy16.png rename to assets/resources/copy16.png diff --git a/share/copy32.png b/assets/resources/copy32.png similarity index 100% rename from share/copy32.png rename to assets/resources/copy32.png diff --git a/share/copy_file16.png b/assets/resources/copy_file16.png similarity index 100% rename from share/copy_file16.png rename to assets/resources/copy_file16.png diff --git a/share/copy_file32.png b/assets/resources/copy_file32.png similarity index 100% rename from share/copy_file32.png rename to assets/resources/copy_file32.png diff --git a/share/copy_geo.png b/assets/resources/copy_geo.png similarity index 100% rename from share/copy_geo.png rename to assets/resources/copy_geo.png diff --git a/share/corner32.png b/assets/resources/corner32.png similarity index 100% rename from share/corner32.png rename to assets/resources/corner32.png diff --git a/share/cut16.png b/assets/resources/cut16.png similarity index 100% rename from share/cut16.png rename to assets/resources/cut16.png diff --git a/share/cut16_bis.png b/assets/resources/cut16_bis.png similarity index 100% rename from share/cut16_bis.png rename to assets/resources/cut16_bis.png diff --git a/share/cut32.png b/assets/resources/cut32.png similarity index 100% rename from share/cut32.png rename to assets/resources/cut32.png diff --git a/share/cut32_bis.png b/assets/resources/cut32_bis.png similarity index 100% rename from share/cut32_bis.png rename to assets/resources/cut32_bis.png diff --git a/share/cutpath16.png b/assets/resources/cutpath16.png similarity index 100% rename from share/cutpath16.png rename to assets/resources/cutpath16.png diff --git a/share/cutpath24.png b/assets/resources/cutpath24.png similarity index 100% rename from share/cutpath24.png rename to assets/resources/cutpath24.png diff --git a/share/cutpath32.png b/assets/resources/cutpath32.png similarity index 100% rename from share/cutpath32.png rename to assets/resources/cutpath32.png diff --git a/share/dark_resources/Makefile b/assets/resources/dark_resources/Makefile similarity index 100% rename from share/dark_resources/Makefile rename to assets/resources/dark_resources/Makefile diff --git a/share/dark_resources/about32.png b/assets/resources/dark_resources/about32.png similarity index 100% rename from share/dark_resources/about32.png rename to assets/resources/dark_resources/about32.png diff --git a/share/dark_resources/active.gif b/assets/resources/dark_resources/active.gif similarity index 100% rename from share/dark_resources/active.gif rename to assets/resources/dark_resources/active.gif diff --git a/share/dark_resources/active_2.gif b/assets/resources/dark_resources/active_2.gif similarity index 100% rename from share/dark_resources/active_2.gif rename to assets/resources/dark_resources/active_2.gif diff --git a/share/dark_resources/active_2_static.png b/assets/resources/dark_resources/active_2_static.png similarity index 100% rename from share/dark_resources/active_2_static.png rename to assets/resources/dark_resources/active_2_static.png diff --git a/share/dark_resources/active_3.gif b/assets/resources/dark_resources/active_3.gif similarity index 100% rename from share/dark_resources/active_3.gif rename to assets/resources/dark_resources/active_3.gif diff --git a/share/dark_resources/active_3_static.png b/assets/resources/dark_resources/active_3_static.png similarity index 100% rename from share/dark_resources/active_3_static.png rename to assets/resources/dark_resources/active_3_static.png diff --git a/share/dark_resources/active_4.gif b/assets/resources/dark_resources/active_4.gif similarity index 100% rename from share/dark_resources/active_4.gif rename to assets/resources/dark_resources/active_4.gif diff --git a/share/dark_resources/active_4_static.png b/assets/resources/dark_resources/active_4_static.png similarity index 100% rename from share/dark_resources/active_4_static.png rename to assets/resources/dark_resources/active_4_static.png diff --git a/share/dark_resources/active_static.png b/assets/resources/dark_resources/active_static.png similarity index 100% rename from share/dark_resources/active_static.png rename to assets/resources/dark_resources/active_static.png diff --git a/share/dark_resources/addarray16.png b/assets/resources/dark_resources/addarray16.png similarity index 100% rename from share/dark_resources/addarray16.png rename to assets/resources/dark_resources/addarray16.png diff --git a/share/dark_resources/addarray20.png b/assets/resources/dark_resources/addarray20.png similarity index 100% rename from share/dark_resources/addarray20.png rename to assets/resources/dark_resources/addarray20.png diff --git a/share/dark_resources/addarray32.png b/assets/resources/dark_resources/addarray32.png similarity index 100% rename from share/dark_resources/addarray32.png rename to assets/resources/dark_resources/addarray32.png diff --git a/share/dark_resources/aero.png b/assets/resources/dark_resources/aero.png similarity index 100% rename from share/dark_resources/aero.png rename to assets/resources/dark_resources/aero.png diff --git a/share/dark_resources/aero_arc.png b/assets/resources/dark_resources/aero_arc.png similarity index 100% rename from share/dark_resources/aero_arc.png rename to assets/resources/dark_resources/aero_arc.png diff --git a/share/dark_resources/aero_array.png b/assets/resources/dark_resources/aero_array.png similarity index 100% rename from share/dark_resources/aero_array.png rename to assets/resources/dark_resources/aero_array.png diff --git a/share/dark_resources/aero_buffer.png b/assets/resources/dark_resources/aero_buffer.png similarity index 100% rename from share/dark_resources/aero_buffer.png rename to assets/resources/dark_resources/aero_buffer.png diff --git a/share/dark_resources/aero_circle.png b/assets/resources/dark_resources/aero_circle.png similarity index 100% rename from share/dark_resources/aero_circle.png rename to assets/resources/dark_resources/aero_circle.png diff --git a/share/dark_resources/aero_circle_geo.png b/assets/resources/dark_resources/aero_circle_geo.png similarity index 100% rename from share/dark_resources/aero_circle_geo.png rename to assets/resources/dark_resources/aero_circle_geo.png diff --git a/share/dark_resources/aero_disc.png b/assets/resources/dark_resources/aero_disc.png similarity index 100% rename from share/dark_resources/aero_disc.png rename to assets/resources/dark_resources/aero_disc.png diff --git a/share/dark_resources/aero_drill.png b/assets/resources/dark_resources/aero_drill.png similarity index 100% rename from share/dark_resources/aero_drill.png rename to assets/resources/dark_resources/aero_drill.png diff --git a/share/dark_resources/aero_drill_array.png b/assets/resources/dark_resources/aero_drill_array.png similarity index 100% rename from share/dark_resources/aero_drill_array.png rename to assets/resources/dark_resources/aero_drill_array.png diff --git a/share/dark_resources/aero_path1.png b/assets/resources/dark_resources/aero_path1.png similarity index 100% rename from share/dark_resources/aero_path1.png rename to assets/resources/dark_resources/aero_path1.png diff --git a/share/dark_resources/aero_path2.png b/assets/resources/dark_resources/aero_path2.png similarity index 100% rename from share/dark_resources/aero_path2.png rename to assets/resources/dark_resources/aero_path2.png diff --git a/share/dark_resources/aero_path3.png b/assets/resources/dark_resources/aero_path3.png similarity index 100% rename from share/dark_resources/aero_path3.png rename to assets/resources/dark_resources/aero_path3.png diff --git a/share/dark_resources/aero_path4.png b/assets/resources/dark_resources/aero_path4.png similarity index 100% rename from share/dark_resources/aero_path4.png rename to assets/resources/dark_resources/aero_path4.png diff --git a/share/dark_resources/aero_path5.png b/assets/resources/dark_resources/aero_path5.png similarity index 100% rename from share/dark_resources/aero_path5.png rename to assets/resources/dark_resources/aero_path5.png diff --git a/share/dark_resources/aero_semidisc.png b/assets/resources/dark_resources/aero_semidisc.png similarity index 100% rename from share/dark_resources/aero_semidisc.png rename to assets/resources/dark_resources/aero_semidisc.png diff --git a/share/dark_resources/aero_slot.png b/assets/resources/dark_resources/aero_slot.png similarity index 100% rename from share/dark_resources/aero_slot.png rename to assets/resources/dark_resources/aero_slot.png diff --git a/share/dark_resources/aero_text.png b/assets/resources/dark_resources/aero_text.png similarity index 100% rename from share/dark_resources/aero_text.png rename to assets/resources/dark_resources/aero_text.png diff --git a/share/dark_resources/align16.png b/assets/resources/dark_resources/align16.png similarity index 100% rename from share/dark_resources/align16.png rename to assets/resources/dark_resources/align16.png diff --git a/share/dark_resources/align32.png b/assets/resources/dark_resources/align32.png similarity index 100% rename from share/dark_resources/align32.png rename to assets/resources/dark_resources/align32.png diff --git a/share/dark_resources/align_center32.png b/assets/resources/dark_resources/align_center32.png similarity index 100% rename from share/dark_resources/align_center32.png rename to assets/resources/dark_resources/align_center32.png diff --git a/share/dark_resources/align_justify32.png b/assets/resources/dark_resources/align_justify32.png similarity index 100% rename from share/dark_resources/align_justify32.png rename to assets/resources/dark_resources/align_justify32.png diff --git a/share/dark_resources/align_left32.png b/assets/resources/dark_resources/align_left32.png similarity index 100% rename from share/dark_resources/align_left32.png rename to assets/resources/dark_resources/align_left32.png diff --git a/share/dark_resources/align_right32.png b/assets/resources/dark_resources/align_right32.png similarity index 100% rename from share/dark_resources/align_right32.png rename to assets/resources/dark_resources/align_right32.png diff --git a/share/dark_resources/aperture16.png b/assets/resources/dark_resources/aperture16.png similarity index 100% rename from share/dark_resources/aperture16.png rename to assets/resources/dark_resources/aperture16.png diff --git a/share/dark_resources/aperture32.png b/assets/resources/dark_resources/aperture32.png similarity index 100% rename from share/dark_resources/aperture32.png rename to assets/resources/dark_resources/aperture32.png diff --git a/share/dark_resources/arc16.png b/assets/resources/dark_resources/arc16.png similarity index 100% rename from share/dark_resources/arc16.png rename to assets/resources/dark_resources/arc16.png diff --git a/share/dark_resources/arc24.png b/assets/resources/dark_resources/arc24.png similarity index 100% rename from share/dark_resources/arc24.png rename to assets/resources/dark_resources/arc24.png diff --git a/share/dark_resources/arc32.png b/assets/resources/dark_resources/arc32.png similarity index 100% rename from share/dark_resources/arc32.png rename to assets/resources/dark_resources/arc32.png diff --git a/share/dark_resources/axis32.png b/assets/resources/dark_resources/axis32.png similarity index 100% rename from share/dark_resources/axis32.png rename to assets/resources/dark_resources/axis32.png diff --git a/share/dark_resources/backup24.png b/assets/resources/dark_resources/backup24.png similarity index 100% rename from share/dark_resources/backup24.png rename to assets/resources/dark_resources/backup24.png diff --git a/share/dark_resources/backup_export24.png b/assets/resources/dark_resources/backup_export24.png similarity index 100% rename from share/dark_resources/backup_export24.png rename to assets/resources/dark_resources/backup_export24.png diff --git a/share/dark_resources/backup_import24.png b/assets/resources/dark_resources/backup_import24.png similarity index 100% rename from share/dark_resources/backup_import24.png rename to assets/resources/dark_resources/backup_import24.png diff --git a/share/dark_resources/black32.png b/assets/resources/dark_resources/black32.png similarity index 100% rename from share/dark_resources/black32.png rename to assets/resources/dark_resources/black32.png diff --git a/share/dark_resources/blocked16.png b/assets/resources/dark_resources/blocked16.png similarity index 100% rename from share/dark_resources/blocked16.png rename to assets/resources/dark_resources/blocked16.png diff --git a/share/dark_resources/blue32.png b/assets/resources/dark_resources/blue32.png similarity index 100% rename from share/dark_resources/blue32.png rename to assets/resources/dark_resources/blue32.png diff --git a/share/dark_resources/bluelight12.png b/assets/resources/dark_resources/bluelight12.png similarity index 100% rename from share/dark_resources/bluelight12.png rename to assets/resources/dark_resources/bluelight12.png diff --git a/share/dark_resources/bold32.png b/assets/resources/dark_resources/bold32.png similarity index 100% rename from share/dark_resources/bold32.png rename to assets/resources/dark_resources/bold32.png diff --git a/share/dark_resources/bookmarks16.png b/assets/resources/dark_resources/bookmarks16.png similarity index 100% rename from share/dark_resources/bookmarks16.png rename to assets/resources/dark_resources/bookmarks16.png diff --git a/share/dark_resources/bookmarks32.png b/assets/resources/dark_resources/bookmarks32.png similarity index 100% rename from share/dark_resources/bookmarks32.png rename to assets/resources/dark_resources/bookmarks32.png diff --git a/share/dark_resources/brown32.png b/assets/resources/dark_resources/brown32.png similarity index 100% rename from share/dark_resources/brown32.png rename to assets/resources/dark_resources/brown32.png diff --git a/share/dark_resources/buffer16-2.png b/assets/resources/dark_resources/buffer16-2.png similarity index 100% rename from share/dark_resources/buffer16-2.png rename to assets/resources/dark_resources/buffer16-2.png diff --git a/share/dark_resources/buffer16.png b/assets/resources/dark_resources/buffer16.png similarity index 100% rename from share/dark_resources/buffer16.png rename to assets/resources/dark_resources/buffer16.png diff --git a/share/dark_resources/buffer20.png b/assets/resources/dark_resources/buffer20.png similarity index 100% rename from share/dark_resources/buffer20.png rename to assets/resources/dark_resources/buffer20.png diff --git a/share/dark_resources/buffer24.png b/assets/resources/dark_resources/buffer24.png similarity index 100% rename from share/dark_resources/buffer24.png rename to assets/resources/dark_resources/buffer24.png diff --git a/share/dark_resources/bug16.png b/assets/resources/dark_resources/bug16.png similarity index 100% rename from share/dark_resources/bug16.png rename to assets/resources/dark_resources/bug16.png diff --git a/share/dark_resources/bug32.png b/assets/resources/dark_resources/bug32.png similarity index 100% rename from share/dark_resources/bug32.png rename to assets/resources/dark_resources/bug32.png diff --git a/share/dark_resources/calculator16.png b/assets/resources/dark_resources/calculator16.png similarity index 100% rename from share/dark_resources/calculator16.png rename to assets/resources/dark_resources/calculator16.png diff --git a/share/dark_resources/calculator24.png b/assets/resources/dark_resources/calculator24.png similarity index 100% rename from share/dark_resources/calculator24.png rename to assets/resources/dark_resources/calculator24.png diff --git a/share/dark_resources/calibrate_16.png b/assets/resources/dark_resources/calibrate_16.png similarity index 100% rename from share/dark_resources/calibrate_16.png rename to assets/resources/dark_resources/calibrate_16.png diff --git a/share/dark_resources/calibrate_32.png b/assets/resources/dark_resources/calibrate_32.png similarity index 100% rename from share/dark_resources/calibrate_32.png rename to assets/resources/dark_resources/calibrate_32.png diff --git a/share/dark_resources/cancel_edit16.png b/assets/resources/dark_resources/cancel_edit16.png similarity index 100% rename from share/dark_resources/cancel_edit16.png rename to assets/resources/dark_resources/cancel_edit16.png diff --git a/share/dark_resources/cancel_edit32.png b/assets/resources/dark_resources/cancel_edit32.png similarity index 100% rename from share/dark_resources/cancel_edit32.png rename to assets/resources/dark_resources/cancel_edit32.png diff --git a/share/dark_resources/circle32.png b/assets/resources/dark_resources/circle32.png similarity index 100% rename from share/dark_resources/circle32.png rename to assets/resources/dark_resources/circle32.png diff --git a/share/dark_resources/clear_plot16.png b/assets/resources/dark_resources/clear_plot16.png similarity index 100% rename from share/dark_resources/clear_plot16.png rename to assets/resources/dark_resources/clear_plot16.png diff --git a/share/dark_resources/clear_plot32.png b/assets/resources/dark_resources/clear_plot32.png similarity index 100% rename from share/dark_resources/clear_plot32.png rename to assets/resources/dark_resources/clear_plot32.png diff --git a/share/dark_resources/close_edit_file16.png b/assets/resources/dark_resources/close_edit_file16.png similarity index 100% rename from share/dark_resources/close_edit_file16.png rename to assets/resources/dark_resources/close_edit_file16.png diff --git a/share/dark_resources/close_edit_file32.png b/assets/resources/dark_resources/close_edit_file32.png similarity index 100% rename from share/dark_resources/close_edit_file32.png rename to assets/resources/dark_resources/close_edit_file32.png diff --git a/share/dark_resources/cnc16.png b/assets/resources/dark_resources/cnc16.png similarity index 100% rename from share/dark_resources/cnc16.png rename to assets/resources/dark_resources/cnc16.png diff --git a/share/dark_resources/cnc32.png b/assets/resources/dark_resources/cnc32.png similarity index 100% rename from share/dark_resources/cnc32.png rename to assets/resources/dark_resources/cnc32.png diff --git a/share/dark_resources/code.png b/assets/resources/dark_resources/code.png similarity index 100% rename from share/dark_resources/code.png rename to assets/resources/dark_resources/code.png diff --git a/share/dark_resources/code_editor32.png b/assets/resources/dark_resources/code_editor32.png similarity index 100% rename from share/dark_resources/code_editor32.png rename to assets/resources/dark_resources/code_editor32.png diff --git a/share/dark_resources/convert24.png b/assets/resources/dark_resources/convert24.png similarity index 100% rename from share/dark_resources/convert24.png rename to assets/resources/dark_resources/convert24.png diff --git a/share/dark_resources/copperfill16.png b/assets/resources/dark_resources/copperfill16.png similarity index 100% rename from share/dark_resources/copperfill16.png rename to assets/resources/dark_resources/copperfill16.png diff --git a/share/dark_resources/copperfill32.png b/assets/resources/dark_resources/copperfill32.png similarity index 100% rename from share/dark_resources/copperfill32.png rename to assets/resources/dark_resources/copperfill32.png diff --git a/share/dark_resources/copy.png b/assets/resources/dark_resources/copy.png similarity index 100% rename from share/dark_resources/copy.png rename to assets/resources/dark_resources/copy.png diff --git a/share/dark_resources/copy16.png b/assets/resources/dark_resources/copy16.png similarity index 100% rename from share/dark_resources/copy16.png rename to assets/resources/dark_resources/copy16.png diff --git a/share/dark_resources/copy32.png b/assets/resources/dark_resources/copy32.png similarity index 100% rename from share/dark_resources/copy32.png rename to assets/resources/dark_resources/copy32.png diff --git a/share/dark_resources/copy_16.png b/assets/resources/dark_resources/copy_16.png similarity index 100% rename from share/dark_resources/copy_16.png rename to assets/resources/dark_resources/copy_16.png diff --git a/share/dark_resources/copy_file16.png b/assets/resources/dark_resources/copy_file16.png similarity index 100% rename from share/dark_resources/copy_file16.png rename to assets/resources/dark_resources/copy_file16.png diff --git a/share/dark_resources/copy_file32.png b/assets/resources/dark_resources/copy_file32.png similarity index 100% rename from share/dark_resources/copy_file32.png rename to assets/resources/dark_resources/copy_file32.png diff --git a/share/dark_resources/copy_geo.png b/assets/resources/dark_resources/copy_geo.png similarity index 100% rename from share/dark_resources/copy_geo.png rename to assets/resources/dark_resources/copy_geo.png diff --git a/share/dark_resources/corner32.png b/assets/resources/dark_resources/corner32.png similarity index 100% rename from share/dark_resources/corner32.png rename to assets/resources/dark_resources/corner32.png diff --git a/share/dark_resources/cut16.png b/assets/resources/dark_resources/cut16.png similarity index 100% rename from share/dark_resources/cut16.png rename to assets/resources/dark_resources/cut16.png diff --git a/share/dark_resources/cut16_bis.png b/assets/resources/dark_resources/cut16_bis.png similarity index 100% rename from share/dark_resources/cut16_bis.png rename to assets/resources/dark_resources/cut16_bis.png diff --git a/share/dark_resources/cut32.png b/assets/resources/dark_resources/cut32.png similarity index 100% rename from share/dark_resources/cut32.png rename to assets/resources/dark_resources/cut32.png diff --git a/share/dark_resources/cut32_bis.png b/assets/resources/dark_resources/cut32_bis.png similarity index 100% rename from share/dark_resources/cut32_bis.png rename to assets/resources/dark_resources/cut32_bis.png diff --git a/share/dark_resources/cutpath16.png b/assets/resources/dark_resources/cutpath16.png similarity index 100% rename from share/dark_resources/cutpath16.png rename to assets/resources/dark_resources/cutpath16.png diff --git a/share/dark_resources/cutpath24.png b/assets/resources/dark_resources/cutpath24.png similarity index 100% rename from share/dark_resources/cutpath24.png rename to assets/resources/dark_resources/cutpath24.png diff --git a/share/dark_resources/cutpath32.png b/assets/resources/dark_resources/cutpath32.png similarity index 100% rename from share/dark_resources/cutpath32.png rename to assets/resources/dark_resources/cutpath32.png diff --git a/share/dark_resources/database32.png b/assets/resources/dark_resources/database32.png similarity index 100% rename from share/dark_resources/database32.png rename to assets/resources/dark_resources/database32.png diff --git a/share/dark_resources/defaults.png b/assets/resources/dark_resources/defaults.png similarity index 100% rename from share/dark_resources/defaults.png rename to assets/resources/dark_resources/defaults.png diff --git a/share/dark_resources/delete32.png b/assets/resources/dark_resources/delete32.png similarity index 100% rename from share/dark_resources/delete32.png rename to assets/resources/dark_resources/delete32.png diff --git a/share/dark_resources/delete_file16.png b/assets/resources/dark_resources/delete_file16.png similarity index 100% rename from share/dark_resources/delete_file16.png rename to assets/resources/dark_resources/delete_file16.png diff --git a/share/dark_resources/delete_file32.png b/assets/resources/dark_resources/delete_file32.png similarity index 100% rename from share/dark_resources/delete_file32.png rename to assets/resources/dark_resources/delete_file32.png diff --git a/share/dark_resources/deleteshape16.png b/assets/resources/dark_resources/deleteshape16.png similarity index 100% rename from share/dark_resources/deleteshape16.png rename to assets/resources/dark_resources/deleteshape16.png diff --git a/share/dark_resources/deleteshape24.png b/assets/resources/dark_resources/deleteshape24.png similarity index 100% rename from share/dark_resources/deleteshape24.png rename to assets/resources/dark_resources/deleteshape24.png diff --git a/share/dark_resources/deleteshape32.png b/assets/resources/dark_resources/deleteshape32.png similarity index 100% rename from share/dark_resources/deleteshape32.png rename to assets/resources/dark_resources/deleteshape32.png diff --git a/share/dark_resources/deselect_all32.png b/assets/resources/dark_resources/deselect_all32.png similarity index 100% rename from share/dark_resources/deselect_all32.png rename to assets/resources/dark_resources/deselect_all32.png diff --git a/share/dark_resources/disable16.png b/assets/resources/dark_resources/disable16.png similarity index 100% rename from share/dark_resources/disable16.png rename to assets/resources/dark_resources/disable16.png diff --git a/share/dark_resources/disable32.png b/assets/resources/dark_resources/disable32.png similarity index 100% rename from share/dark_resources/disable32.png rename to assets/resources/dark_resources/disable32.png diff --git a/share/dark_resources/disc32.png b/assets/resources/dark_resources/disc32.png similarity index 100% rename from share/dark_resources/disc32.png rename to assets/resources/dark_resources/disc32.png diff --git a/share/dark_resources/distance16.png b/assets/resources/dark_resources/distance16.png similarity index 100% rename from share/dark_resources/distance16.png rename to assets/resources/dark_resources/distance16.png diff --git a/share/dark_resources/distance32.png b/assets/resources/dark_resources/distance32.png similarity index 100% rename from share/dark_resources/distance32.png rename to assets/resources/dark_resources/distance32.png diff --git a/share/dark_resources/distance_min16.png b/assets/resources/dark_resources/distance_min16.png similarity index 100% rename from share/dark_resources/distance_min16.png rename to assets/resources/dark_resources/distance_min16.png diff --git a/share/dark_resources/distance_min32.png b/assets/resources/dark_resources/distance_min32.png similarity index 100% rename from share/dark_resources/distance_min32.png rename to assets/resources/dark_resources/distance_min32.png diff --git a/share/dark_resources/doubleside16.png b/assets/resources/dark_resources/doubleside16.png similarity index 100% rename from share/dark_resources/doubleside16.png rename to assets/resources/dark_resources/doubleside16.png diff --git a/share/dark_resources/doubleside32.png b/assets/resources/dark_resources/doubleside32.png similarity index 100% rename from share/dark_resources/doubleside32.png rename to assets/resources/dark_resources/doubleside32.png diff --git a/share/dark_resources/draw32.png b/assets/resources/dark_resources/draw32.png similarity index 100% rename from share/dark_resources/draw32.png rename to assets/resources/dark_resources/draw32.png diff --git a/share/dark_resources/drill16.png b/assets/resources/dark_resources/drill16.png similarity index 100% rename from share/dark_resources/drill16.png rename to assets/resources/dark_resources/drill16.png diff --git a/share/dark_resources/drill32.png b/assets/resources/dark_resources/drill32.png similarity index 100% rename from share/dark_resources/drill32.png rename to assets/resources/dark_resources/drill32.png diff --git a/share/dark_resources/dxf16.png b/assets/resources/dark_resources/dxf16.png similarity index 100% rename from share/dark_resources/dxf16.png rename to assets/resources/dark_resources/dxf16.png diff --git a/share/dark_resources/edit16.png b/assets/resources/dark_resources/edit16.png similarity index 100% rename from share/dark_resources/edit16.png rename to assets/resources/dark_resources/edit16.png diff --git a/share/dark_resources/edit32.png b/assets/resources/dark_resources/edit32.png similarity index 100% rename from share/dark_resources/edit32.png rename to assets/resources/dark_resources/edit32.png diff --git a/share/dark_resources/edit_file16.png b/assets/resources/dark_resources/edit_file16.png similarity index 100% rename from share/dark_resources/edit_file16.png rename to assets/resources/dark_resources/edit_file16.png diff --git a/share/dark_resources/edit_file32.png b/assets/resources/dark_resources/edit_file32.png similarity index 100% rename from share/dark_resources/edit_file32.png rename to assets/resources/dark_resources/edit_file32.png diff --git a/share/dark_resources/edit_ok16.png b/assets/resources/dark_resources/edit_ok16.png similarity index 100% rename from share/dark_resources/edit_ok16.png rename to assets/resources/dark_resources/edit_ok16.png diff --git a/share/dark_resources/edit_ok32.png b/assets/resources/dark_resources/edit_ok32.png similarity index 100% rename from share/dark_resources/edit_ok32.png rename to assets/resources/dark_resources/edit_ok32.png diff --git a/share/dark_resources/edit_ok32_bis.png b/assets/resources/dark_resources/edit_ok32_bis.png similarity index 100% rename from share/dark_resources/edit_ok32_bis.png rename to assets/resources/dark_resources/edit_ok32_bis.png diff --git a/share/dark_resources/eraser26.png b/assets/resources/dark_resources/eraser26.png similarity index 100% rename from share/dark_resources/eraser26.png rename to assets/resources/dark_resources/eraser26.png diff --git a/share/dark_resources/explode32.png b/assets/resources/dark_resources/explode32.png similarity index 100% rename from share/dark_resources/explode32.png rename to assets/resources/dark_resources/explode32.png diff --git a/share/dark_resources/export.png b/assets/resources/dark_resources/export.png similarity index 100% rename from share/dark_resources/export.png rename to assets/resources/dark_resources/export.png diff --git a/share/dark_resources/export_png32.png b/assets/resources/dark_resources/export_png32.png similarity index 100% rename from share/dark_resources/export_png32.png rename to assets/resources/dark_resources/export_png32.png diff --git a/share/dark_resources/extract_drill16.png b/assets/resources/dark_resources/extract_drill16.png similarity index 100% rename from share/dark_resources/extract_drill16.png rename to assets/resources/dark_resources/extract_drill16.png diff --git a/share/dark_resources/extract_drill32.png b/assets/resources/dark_resources/extract_drill32.png similarity index 100% rename from share/dark_resources/extract_drill32.png rename to assets/resources/dark_resources/extract_drill32.png diff --git a/share/dark_resources/fiducials_32.png b/assets/resources/dark_resources/fiducials_32.png similarity index 100% rename from share/dark_resources/fiducials_32.png rename to assets/resources/dark_resources/fiducials_32.png diff --git a/share/dark_resources/file16.png b/assets/resources/dark_resources/file16.png similarity index 100% rename from share/dark_resources/file16.png rename to assets/resources/dark_resources/file16.png diff --git a/share/dark_resources/file32.png b/assets/resources/dark_resources/file32.png similarity index 100% rename from share/dark_resources/file32.png rename to assets/resources/dark_resources/file32.png diff --git a/share/dark_resources/film16.png b/assets/resources/dark_resources/film16.png similarity index 100% rename from share/dark_resources/film16.png rename to assets/resources/dark_resources/film16.png diff --git a/share/dark_resources/film32.png b/assets/resources/dark_resources/film32.png similarity index 100% rename from share/dark_resources/film32.png rename to assets/resources/dark_resources/film32.png diff --git a/share/dark_resources/flatcam_icon128.png b/assets/resources/dark_resources/flatcam_icon128.png similarity index 100% rename from share/dark_resources/flatcam_icon128.png rename to assets/resources/dark_resources/flatcam_icon128.png diff --git a/share/dark_resources/flatcam_icon16.ico b/assets/resources/dark_resources/flatcam_icon16.ico similarity index 100% rename from share/dark_resources/flatcam_icon16.ico rename to assets/resources/dark_resources/flatcam_icon16.ico diff --git a/share/dark_resources/flatcam_icon16.png b/assets/resources/dark_resources/flatcam_icon16.png similarity index 100% rename from share/dark_resources/flatcam_icon16.png rename to assets/resources/dark_resources/flatcam_icon16.png diff --git a/share/dark_resources/flatcam_icon24.png b/assets/resources/dark_resources/flatcam_icon24.png similarity index 100% rename from share/dark_resources/flatcam_icon24.png rename to assets/resources/dark_resources/flatcam_icon24.png diff --git a/share/dark_resources/flatcam_icon256.ico b/assets/resources/dark_resources/flatcam_icon256.ico similarity index 100% rename from share/dark_resources/flatcam_icon256.ico rename to assets/resources/dark_resources/flatcam_icon256.ico diff --git a/share/dark_resources/flatcam_icon256.png b/assets/resources/dark_resources/flatcam_icon256.png similarity index 100% rename from share/dark_resources/flatcam_icon256.png rename to assets/resources/dark_resources/flatcam_icon256.png diff --git a/share/dark_resources/flatcam_icon32.ico b/assets/resources/dark_resources/flatcam_icon32.ico similarity index 100% rename from share/dark_resources/flatcam_icon32.ico rename to assets/resources/dark_resources/flatcam_icon32.ico diff --git a/share/dark_resources/flatcam_icon32.png b/assets/resources/dark_resources/flatcam_icon32.png similarity index 100% rename from share/dark_resources/flatcam_icon32.png rename to assets/resources/dark_resources/flatcam_icon32.png diff --git a/share/dark_resources/flatcam_icon32_green.png b/assets/resources/dark_resources/flatcam_icon32_green.png similarity index 100% rename from share/dark_resources/flatcam_icon32_green.png rename to assets/resources/dark_resources/flatcam_icon32_green.png diff --git a/share/dark_resources/flatcam_icon48.ico b/assets/resources/dark_resources/flatcam_icon48.ico similarity index 100% rename from share/dark_resources/flatcam_icon48.ico rename to assets/resources/dark_resources/flatcam_icon48.ico diff --git a/share/dark_resources/flatcam_icon48.png b/assets/resources/dark_resources/flatcam_icon48.png similarity index 100% rename from share/dark_resources/flatcam_icon48.png rename to assets/resources/dark_resources/flatcam_icon48.png diff --git a/share/dark_resources/flipx.png b/assets/resources/dark_resources/flipx.png similarity index 100% rename from share/dark_resources/flipx.png rename to assets/resources/dark_resources/flipx.png diff --git a/share/dark_resources/flipy.png b/assets/resources/dark_resources/flipy.png similarity index 100% rename from share/dark_resources/flipy.png rename to assets/resources/dark_resources/flipy.png diff --git a/share/dark_resources/floppy16.png b/assets/resources/dark_resources/floppy16.png similarity index 100% rename from share/dark_resources/floppy16.png rename to assets/resources/dark_resources/floppy16.png diff --git a/share/dark_resources/floppy32.png b/assets/resources/dark_resources/floppy32.png similarity index 100% rename from share/dark_resources/floppy32.png rename to assets/resources/dark_resources/floppy32.png diff --git a/share/dark_resources/folder16.png b/assets/resources/dark_resources/folder16.png similarity index 100% rename from share/dark_resources/folder16.png rename to assets/resources/dark_resources/folder16.png diff --git a/share/dark_resources/folder32.png b/assets/resources/dark_resources/folder32.png similarity index 100% rename from share/dark_resources/folder32.png rename to assets/resources/dark_resources/folder32.png diff --git a/share/dark_resources/folder32_Excellon.png b/assets/resources/dark_resources/folder32_Excellon.png similarity index 100% rename from share/dark_resources/folder32_Excellon.png rename to assets/resources/dark_resources/folder32_Excellon.png diff --git a/share/dark_resources/folder32_bis.png b/assets/resources/dark_resources/folder32_bis.png similarity index 100% rename from share/dark_resources/folder32_bis.png rename to assets/resources/dark_resources/folder32_bis.png diff --git a/share/dark_resources/folder32_gerber.png b/assets/resources/dark_resources/folder32_gerber.png similarity index 100% rename from share/dark_resources/folder32_gerber.png rename to assets/resources/dark_resources/folder32_gerber.png diff --git a/share/dark_resources/fscreen32.png b/assets/resources/dark_resources/fscreen32.png similarity index 100% rename from share/dark_resources/fscreen32.png rename to assets/resources/dark_resources/fscreen32.png diff --git a/share/dark_resources/gear32.png b/assets/resources/dark_resources/gear32.png similarity index 100% rename from share/dark_resources/gear32.png rename to assets/resources/dark_resources/gear32.png diff --git a/share/dark_resources/gear48.png b/assets/resources/dark_resources/gear48.png similarity index 100% rename from share/dark_resources/gear48.png rename to assets/resources/dark_resources/gear48.png diff --git a/share/dark_resources/geometry16.png b/assets/resources/dark_resources/geometry16.png similarity index 100% rename from share/dark_resources/geometry16.png rename to assets/resources/dark_resources/geometry16.png diff --git a/share/dark_resources/geometry32.png b/assets/resources/dark_resources/geometry32.png similarity index 100% rename from share/dark_resources/geometry32.png rename to assets/resources/dark_resources/geometry32.png diff --git a/share/dark_resources/globe16.png b/assets/resources/dark_resources/globe16.png similarity index 100% rename from share/dark_resources/globe16.png rename to assets/resources/dark_resources/globe16.png diff --git a/share/dark_resources/goemetry32.png b/assets/resources/dark_resources/goemetry32.png similarity index 100% rename from share/dark_resources/goemetry32.png rename to assets/resources/dark_resources/goemetry32.png diff --git a/share/dark_resources/graylight12.png b/assets/resources/dark_resources/graylight12.png similarity index 100% rename from share/dark_resources/graylight12.png rename to assets/resources/dark_resources/graylight12.png diff --git a/share/dark_resources/green32.png b/assets/resources/dark_resources/green32.png similarity index 100% rename from share/dark_resources/green32.png rename to assets/resources/dark_resources/green32.png diff --git a/share/dark_resources/greenlight12.png b/assets/resources/dark_resources/greenlight12.png similarity index 100% rename from share/dark_resources/greenlight12.png rename to assets/resources/dark_resources/greenlight12.png diff --git a/share/dark_resources/grid16.png b/assets/resources/dark_resources/grid16.png similarity index 100% rename from share/dark_resources/grid16.png rename to assets/resources/dark_resources/grid16.png diff --git a/share/dark_resources/grid32.png b/assets/resources/dark_resources/grid32.png similarity index 100% rename from share/dark_resources/grid32.png rename to assets/resources/dark_resources/grid32.png diff --git a/share/dark_resources/grid32_menu.png b/assets/resources/dark_resources/grid32_menu.png similarity index 100% rename from share/dark_resources/grid32_menu.png rename to assets/resources/dark_resources/grid32_menu.png diff --git a/share/dark_resources/help.png b/assets/resources/dark_resources/help.png similarity index 100% rename from share/dark_resources/help.png rename to assets/resources/dark_resources/help.png diff --git a/share/dark_resources/home16.png b/assets/resources/dark_resources/home16.png similarity index 100% rename from share/dark_resources/home16.png rename to assets/resources/dark_resources/home16.png diff --git a/share/dark_resources/image16.png b/assets/resources/dark_resources/image16.png similarity index 100% rename from share/dark_resources/image16.png rename to assets/resources/dark_resources/image16.png diff --git a/share/dark_resources/image32.png b/assets/resources/dark_resources/image32.png similarity index 100% rename from share/dark_resources/image32.png rename to assets/resources/dark_resources/image32.png diff --git a/share/dark_resources/import.png b/assets/resources/dark_resources/import.png similarity index 100% rename from share/dark_resources/import.png rename to assets/resources/dark_resources/import.png diff --git a/share/dark_resources/info16.png b/assets/resources/dark_resources/info16.png similarity index 100% rename from share/dark_resources/info16.png rename to assets/resources/dark_resources/info16.png diff --git a/share/dark_resources/intersection16.png b/assets/resources/dark_resources/intersection16.png similarity index 100% rename from share/dark_resources/intersection16.png rename to assets/resources/dark_resources/intersection16.png diff --git a/share/dark_resources/intersection24.png b/assets/resources/dark_resources/intersection24.png similarity index 100% rename from share/dark_resources/intersection24.png rename to assets/resources/dark_resources/intersection24.png diff --git a/share/dark_resources/intersection32.png b/assets/resources/dark_resources/intersection32.png similarity index 100% rename from share/dark_resources/intersection32.png rename to assets/resources/dark_resources/intersection32.png diff --git a/share/dark_resources/invert16.png b/assets/resources/dark_resources/invert16.png similarity index 100% rename from share/dark_resources/invert16.png rename to assets/resources/dark_resources/invert16.png diff --git a/share/dark_resources/invert32.png b/assets/resources/dark_resources/invert32.png similarity index 100% rename from share/dark_resources/invert32.png rename to assets/resources/dark_resources/invert32.png diff --git a/share/dark_resources/italic32.png b/assets/resources/dark_resources/italic32.png similarity index 100% rename from share/dark_resources/italic32.png rename to assets/resources/dark_resources/italic32.png diff --git a/share/dark_resources/join16.png b/assets/resources/dark_resources/join16.png similarity index 100% rename from share/dark_resources/join16.png rename to assets/resources/dark_resources/join16.png diff --git a/share/dark_resources/join32.png b/assets/resources/dark_resources/join32.png similarity index 100% rename from share/dark_resources/join32.png rename to assets/resources/dark_resources/join32.png diff --git a/share/dark_resources/jump_to16.png b/assets/resources/dark_resources/jump_to16.png similarity index 100% rename from share/dark_resources/jump_to16.png rename to assets/resources/dark_resources/jump_to16.png diff --git a/share/dark_resources/jump_to32.png b/assets/resources/dark_resources/jump_to32.png similarity index 100% rename from share/dark_resources/jump_to32.png rename to assets/resources/dark_resources/jump_to32.png diff --git a/share/dark_resources/language32.png b/assets/resources/dark_resources/language32.png similarity index 100% rename from share/dark_resources/language32.png rename to assets/resources/dark_resources/language32.png diff --git a/share/dark_resources/letter_t_32.png b/assets/resources/dark_resources/letter_t_32.png similarity index 100% rename from share/dark_resources/letter_t_32.png rename to assets/resources/dark_resources/letter_t_32.png diff --git a/share/dark_resources/link16.png b/assets/resources/dark_resources/link16.png similarity index 100% rename from share/dark_resources/link16.png rename to assets/resources/dark_resources/link16.png diff --git a/share/dark_resources/locate16.png b/assets/resources/dark_resources/locate16.png similarity index 100% rename from share/dark_resources/locate16.png rename to assets/resources/dark_resources/locate16.png diff --git a/share/dark_resources/locate32.png b/assets/resources/dark_resources/locate32.png similarity index 100% rename from share/dark_resources/locate32.png rename to assets/resources/dark_resources/locate32.png diff --git a/share/dark_resources/machine16.png b/assets/resources/dark_resources/machine16.png similarity index 100% rename from share/dark_resources/machine16.png rename to assets/resources/dark_resources/machine16.png diff --git a/share/dark_resources/markarea32.png b/assets/resources/dark_resources/markarea32.png similarity index 100% rename from share/dark_resources/markarea32.png rename to assets/resources/dark_resources/markarea32.png diff --git a/share/dark_resources/move16.png b/assets/resources/dark_resources/move16.png similarity index 100% rename from share/dark_resources/move16.png rename to assets/resources/dark_resources/move16.png diff --git a/share/dark_resources/move32.png b/assets/resources/dark_resources/move32.png similarity index 100% rename from share/dark_resources/move32.png rename to assets/resources/dark_resources/move32.png diff --git a/share/dark_resources/move32_bis.png b/assets/resources/dark_resources/move32_bis.png similarity index 100% rename from share/dark_resources/move32_bis.png rename to assets/resources/dark_resources/move32_bis.png diff --git a/share/dark_resources/ncc16.png b/assets/resources/dark_resources/ncc16.png similarity index 100% rename from share/dark_resources/ncc16.png rename to assets/resources/dark_resources/ncc16.png diff --git a/share/dark_resources/new_exc32.png b/assets/resources/dark_resources/new_exc32.png similarity index 100% rename from share/dark_resources/new_exc32.png rename to assets/resources/dark_resources/new_exc32.png diff --git a/share/dark_resources/new_file16.png b/assets/resources/dark_resources/new_file16.png similarity index 100% rename from share/dark_resources/new_file16.png rename to assets/resources/dark_resources/new_file16.png diff --git a/share/dark_resources/new_file32.png b/assets/resources/dark_resources/new_file32.png similarity index 100% rename from share/dark_resources/new_file32.png rename to assets/resources/dark_resources/new_file32.png diff --git a/share/dark_resources/new_file_exc16.png b/assets/resources/dark_resources/new_file_exc16.png similarity index 100% rename from share/dark_resources/new_file_exc16.png rename to assets/resources/dark_resources/new_file_exc16.png diff --git a/share/dark_resources/new_file_exc32.png b/assets/resources/dark_resources/new_file_exc32.png similarity index 100% rename from share/dark_resources/new_file_exc32.png rename to assets/resources/dark_resources/new_file_exc32.png diff --git a/share/dark_resources/new_file_geo16.png b/assets/resources/dark_resources/new_file_geo16.png similarity index 100% rename from share/dark_resources/new_file_geo16.png rename to assets/resources/dark_resources/new_file_geo16.png diff --git a/share/dark_resources/new_file_geo32.png b/assets/resources/dark_resources/new_file_geo32.png similarity index 100% rename from share/dark_resources/new_file_geo32.png rename to assets/resources/dark_resources/new_file_geo32.png diff --git a/share/dark_resources/new_file_grb16.png b/assets/resources/dark_resources/new_file_grb16.png similarity index 100% rename from share/dark_resources/new_file_grb16.png rename to assets/resources/dark_resources/new_file_grb16.png diff --git a/share/dark_resources/new_file_grb32.png b/assets/resources/dark_resources/new_file_grb32.png similarity index 100% rename from share/dark_resources/new_file_grb32.png rename to assets/resources/dark_resources/new_file_grb32.png diff --git a/share/dark_resources/new_geo16.png b/assets/resources/dark_resources/new_geo16.png similarity index 100% rename from share/dark_resources/new_geo16.png rename to assets/resources/dark_resources/new_geo16.png diff --git a/share/dark_resources/new_geo32.png b/assets/resources/dark_resources/new_geo32.png similarity index 100% rename from share/dark_resources/new_geo32.png rename to assets/resources/dark_resources/new_geo32.png diff --git a/share/dark_resources/new_geo32_bis.png b/assets/resources/dark_resources/new_geo32_bis.png similarity index 100% rename from share/dark_resources/new_geo32_bis.png rename to assets/resources/dark_resources/new_geo32_bis.png diff --git a/share/dark_resources/notebook16.png b/assets/resources/dark_resources/notebook16.png similarity index 100% rename from share/dark_resources/notebook16.png rename to assets/resources/dark_resources/notebook16.png diff --git a/share/dark_resources/notebook32.png b/assets/resources/dark_resources/notebook32.png similarity index 100% rename from share/dark_resources/notebook32.png rename to assets/resources/dark_resources/notebook32.png diff --git a/share/dark_resources/notes16.png b/assets/resources/dark_resources/notes16.png similarity index 100% rename from share/dark_resources/notes16.png rename to assets/resources/dark_resources/notes16.png diff --git a/share/dark_resources/notes16_1.png b/assets/resources/dark_resources/notes16_1.png similarity index 100% rename from share/dark_resources/notes16_1.png rename to assets/resources/dark_resources/notes16_1.png diff --git a/share/dark_resources/offset32.png b/assets/resources/dark_resources/offset32.png similarity index 100% rename from share/dark_resources/offset32.png rename to assets/resources/dark_resources/offset32.png diff --git a/share/dark_resources/offsetx32.png b/assets/resources/dark_resources/offsetx32.png similarity index 100% rename from share/dark_resources/offsetx32.png rename to assets/resources/dark_resources/offsetx32.png diff --git a/share/dark_resources/offsety32.png b/assets/resources/dark_resources/offsety32.png similarity index 100% rename from share/dark_resources/offsety32.png rename to assets/resources/dark_resources/offsety32.png diff --git a/share/dark_resources/open_excellon32.png b/assets/resources/dark_resources/open_excellon32.png similarity index 100% rename from share/dark_resources/open_excellon32.png rename to assets/resources/dark_resources/open_excellon32.png diff --git a/share/dark_resources/open_script32.png b/assets/resources/dark_resources/open_script32.png similarity index 100% rename from share/dark_resources/open_script32.png rename to assets/resources/dark_resources/open_script32.png diff --git a/share/dark_resources/origin.png b/assets/resources/dark_resources/origin.png similarity index 100% rename from share/dark_resources/origin.png rename to assets/resources/dark_resources/origin.png diff --git a/share/dark_resources/origin16.png b/assets/resources/dark_resources/origin16.png similarity index 100% rename from share/dark_resources/origin16.png rename to assets/resources/dark_resources/origin16.png diff --git a/share/dark_resources/origin2_16.png b/assets/resources/dark_resources/origin2_16.png similarity index 100% rename from share/dark_resources/origin2_16.png rename to assets/resources/dark_resources/origin2_16.png diff --git a/share/dark_resources/origin2_32.png b/assets/resources/dark_resources/origin2_32.png similarity index 100% rename from share/dark_resources/origin2_32.png rename to assets/resources/dark_resources/origin2_32.png diff --git a/share/dark_resources/origin32.png b/assets/resources/dark_resources/origin32.png similarity index 100% rename from share/dark_resources/origin32.png rename to assets/resources/dark_resources/origin32.png diff --git a/share/dark_resources/padarray32.png b/assets/resources/dark_resources/padarray32.png similarity index 100% rename from share/dark_resources/padarray32.png rename to assets/resources/dark_resources/padarray32.png diff --git a/share/dark_resources/paint16.png b/assets/resources/dark_resources/paint16.png similarity index 100% rename from share/dark_resources/paint16.png rename to assets/resources/dark_resources/paint16.png diff --git a/share/dark_resources/paint20.png b/assets/resources/dark_resources/paint20.png similarity index 100% rename from share/dark_resources/paint20.png rename to assets/resources/dark_resources/paint20.png diff --git a/share/dark_resources/paint20_1.png b/assets/resources/dark_resources/paint20_1.png similarity index 100% rename from share/dark_resources/paint20_1.png rename to assets/resources/dark_resources/paint20_1.png diff --git a/share/dark_resources/panel16.png b/assets/resources/dark_resources/panel16.png similarity index 100% rename from share/dark_resources/panel16.png rename to assets/resources/dark_resources/panel16.png diff --git a/share/dark_resources/panel32.png b/assets/resources/dark_resources/panel32.png similarity index 100% rename from share/dark_resources/panel32.png rename to assets/resources/dark_resources/panel32.png diff --git a/share/dark_resources/panelize16.png b/assets/resources/dark_resources/panelize16.png similarity index 100% rename from share/dark_resources/panelize16.png rename to assets/resources/dark_resources/panelize16.png diff --git a/share/dark_resources/panelize32.png b/assets/resources/dark_resources/panelize32.png similarity index 100% rename from share/dark_resources/panelize32.png rename to assets/resources/dark_resources/panelize32.png diff --git a/share/dark_resources/path32.png b/assets/resources/dark_resources/path32.png similarity index 100% rename from share/dark_resources/path32.png rename to assets/resources/dark_resources/path32.png diff --git a/share/dark_resources/pdf32.png b/assets/resources/dark_resources/pdf32.png similarity index 100% rename from share/dark_resources/pdf32.png rename to assets/resources/dark_resources/pdf32.png diff --git a/share/dark_resources/pdf_link16.png b/assets/resources/dark_resources/pdf_link16.png similarity index 100% rename from share/dark_resources/pdf_link16.png rename to assets/resources/dark_resources/pdf_link16.png diff --git a/share/dark_resources/plot32.png b/assets/resources/dark_resources/plot32.png similarity index 100% rename from share/dark_resources/plot32.png rename to assets/resources/dark_resources/plot32.png diff --git a/share/dark_resources/plus16.png b/assets/resources/dark_resources/plus16.png similarity index 100% rename from share/dark_resources/plus16.png rename to assets/resources/dark_resources/plus16.png diff --git a/share/dark_resources/plus32.png b/assets/resources/dark_resources/plus32.png similarity index 100% rename from share/dark_resources/plus32.png rename to assets/resources/dark_resources/plus32.png diff --git a/share/dark_resources/pointer.png b/assets/resources/dark_resources/pointer.png similarity index 100% rename from share/dark_resources/pointer.png rename to assets/resources/dark_resources/pointer.png diff --git a/share/dark_resources/pointer32.png b/assets/resources/dark_resources/pointer32.png similarity index 100% rename from share/dark_resources/pointer32.png rename to assets/resources/dark_resources/pointer32.png diff --git a/share/dark_resources/poligonize32.png b/assets/resources/dark_resources/poligonize32.png similarity index 100% rename from share/dark_resources/poligonize32.png rename to assets/resources/dark_resources/poligonize32.png diff --git a/share/dark_resources/polygon32.png b/assets/resources/dark_resources/polygon32.png similarity index 100% rename from share/dark_resources/polygon32.png rename to assets/resources/dark_resources/polygon32.png diff --git a/share/dark_resources/power16.png b/assets/resources/dark_resources/power16.png similarity index 100% rename from share/dark_resources/power16.png rename to assets/resources/dark_resources/power16.png diff --git a/share/dark_resources/pref.png b/assets/resources/dark_resources/pref.png similarity index 100% rename from share/dark_resources/pref.png rename to assets/resources/dark_resources/pref.png diff --git a/share/dark_resources/printer16.png b/assets/resources/dark_resources/printer16.png similarity index 100% rename from share/dark_resources/printer16.png rename to assets/resources/dark_resources/printer16.png diff --git a/share/dark_resources/printer32.png b/assets/resources/dark_resources/printer32.png similarity index 100% rename from share/dark_resources/printer32.png rename to assets/resources/dark_resources/printer32.png diff --git a/share/dark_resources/project16.png b/assets/resources/dark_resources/project16.png similarity index 100% rename from share/dark_resources/project16.png rename to assets/resources/dark_resources/project16.png diff --git a/share/dark_resources/project_save16.png b/assets/resources/dark_resources/project_save16.png similarity index 100% rename from share/dark_resources/project_save16.png rename to assets/resources/dark_resources/project_save16.png diff --git a/share/dark_resources/project_save32.png b/assets/resources/dark_resources/project_save32.png similarity index 100% rename from share/dark_resources/project_save32.png rename to assets/resources/dark_resources/project_save32.png diff --git a/share/dark_resources/properties32.png b/assets/resources/dark_resources/properties32.png similarity index 100% rename from share/dark_resources/properties32.png rename to assets/resources/dark_resources/properties32.png diff --git a/share/dark_resources/punch16.png b/assets/resources/dark_resources/punch16.png similarity index 100% rename from share/dark_resources/punch16.png rename to assets/resources/dark_resources/punch16.png diff --git a/share/dark_resources/punch32.png b/assets/resources/dark_resources/punch32.png similarity index 100% rename from share/dark_resources/punch32.png rename to assets/resources/dark_resources/punch32.png diff --git a/share/dark_resources/qrcode32.png b/assets/resources/dark_resources/qrcode32.png similarity index 100% rename from share/dark_resources/qrcode32.png rename to assets/resources/dark_resources/qrcode32.png diff --git a/share/dark_resources/recent_files.png b/assets/resources/dark_resources/recent_files.png similarity index 100% rename from share/dark_resources/recent_files.png rename to assets/resources/dark_resources/recent_files.png diff --git a/share/dark_resources/rectangle32.png b/assets/resources/dark_resources/rectangle32.png similarity index 100% rename from share/dark_resources/rectangle32.png rename to assets/resources/dark_resources/rectangle32.png diff --git a/share/dark_resources/recycle16.png b/assets/resources/dark_resources/recycle16.png similarity index 100% rename from share/dark_resources/recycle16.png rename to assets/resources/dark_resources/recycle16.png diff --git a/share/dark_resources/red32.png b/assets/resources/dark_resources/red32.png similarity index 100% rename from share/dark_resources/red32.png rename to assets/resources/dark_resources/red32.png diff --git a/share/dark_resources/redlight12.png b/assets/resources/dark_resources/redlight12.png similarity index 100% rename from share/dark_resources/redlight12.png rename to assets/resources/dark_resources/redlight12.png diff --git a/share/dark_resources/replot16.png b/assets/resources/dark_resources/replot16.png similarity index 100% rename from share/dark_resources/replot16.png rename to assets/resources/dark_resources/replot16.png diff --git a/share/dark_resources/replot32.png b/assets/resources/dark_resources/replot32.png similarity index 100% rename from share/dark_resources/replot32.png rename to assets/resources/dark_resources/replot32.png diff --git a/share/dark_resources/resize16.png b/assets/resources/dark_resources/resize16.png similarity index 100% rename from share/dark_resources/resize16.png rename to assets/resources/dark_resources/resize16.png diff --git a/share/dark_resources/rotate.png b/assets/resources/dark_resources/rotate.png similarity index 100% rename from share/dark_resources/rotate.png rename to assets/resources/dark_resources/rotate.png diff --git a/share/dark_resources/rules32.png b/assets/resources/dark_resources/rules32.png similarity index 100% rename from share/dark_resources/rules32.png rename to assets/resources/dark_resources/rules32.png diff --git a/share/dark_resources/save_as.png b/assets/resources/dark_resources/save_as.png similarity index 100% rename from share/dark_resources/save_as.png rename to assets/resources/dark_resources/save_as.png diff --git a/share/dark_resources/scale32.png b/assets/resources/dark_resources/scale32.png similarity index 100% rename from share/dark_resources/scale32.png rename to assets/resources/dark_resources/scale32.png diff --git a/share/dark_resources/script14.png b/assets/resources/dark_resources/script14.png similarity index 100% rename from share/dark_resources/script14.png rename to assets/resources/dark_resources/script14.png diff --git a/share/dark_resources/script16.png b/assets/resources/dark_resources/script16.png similarity index 100% rename from share/dark_resources/script16.png rename to assets/resources/dark_resources/script16.png diff --git a/share/dark_resources/script_new16.png b/assets/resources/dark_resources/script_new16.png similarity index 100% rename from share/dark_resources/script_new16.png rename to assets/resources/dark_resources/script_new16.png diff --git a/share/dark_resources/script_new24.png b/assets/resources/dark_resources/script_new24.png similarity index 100% rename from share/dark_resources/script_new24.png rename to assets/resources/dark_resources/script_new24.png diff --git a/share/dark_resources/script_open16.png b/assets/resources/dark_resources/script_open16.png similarity index 100% rename from share/dark_resources/script_open16.png rename to assets/resources/dark_resources/script_open16.png diff --git a/share/dark_resources/script_open18.png b/assets/resources/dark_resources/script_open18.png similarity index 100% rename from share/dark_resources/script_open18.png rename to assets/resources/dark_resources/script_open18.png diff --git a/share/dark_resources/script_open24.png b/assets/resources/dark_resources/script_open24.png similarity index 100% rename from share/dark_resources/script_open24.png rename to assets/resources/dark_resources/script_open24.png diff --git a/share/dark_resources/select_all.png b/assets/resources/dark_resources/select_all.png similarity index 100% rename from share/dark_resources/select_all.png rename to assets/resources/dark_resources/select_all.png diff --git a/share/dark_resources/semidisc32.png b/assets/resources/dark_resources/semidisc32.png similarity index 100% rename from share/dark_resources/semidisc32.png rename to assets/resources/dark_resources/semidisc32.png diff --git a/share/dark_resources/set_color16.png b/assets/resources/dark_resources/set_color16.png similarity index 100% rename from share/dark_resources/set_color16.png rename to assets/resources/dark_resources/set_color16.png diff --git a/share/dark_resources/set_color32.png b/assets/resources/dark_resources/set_color32.png similarity index 100% rename from share/dark_resources/set_color32.png rename to assets/resources/dark_resources/set_color32.png diff --git a/share/dark_resources/shell16.png b/assets/resources/dark_resources/shell16.png similarity index 100% rename from share/dark_resources/shell16.png rename to assets/resources/dark_resources/shell16.png diff --git a/share/dark_resources/shell32.png b/assets/resources/dark_resources/shell32.png similarity index 100% rename from share/dark_resources/shell32.png rename to assets/resources/dark_resources/shell32.png diff --git a/share/dark_resources/shortcuts24.png b/assets/resources/dark_resources/shortcuts24.png similarity index 100% rename from share/dark_resources/shortcuts24.png rename to assets/resources/dark_resources/shortcuts24.png diff --git a/share/dark_resources/skewX.png b/assets/resources/dark_resources/skewX.png similarity index 100% rename from share/dark_resources/skewX.png rename to assets/resources/dark_resources/skewX.png diff --git a/share/dark_resources/skewY.png b/assets/resources/dark_resources/skewY.png similarity index 100% rename from share/dark_resources/skewY.png rename to assets/resources/dark_resources/skewY.png diff --git a/share/dark_resources/slot26.png b/assets/resources/dark_resources/slot26.png similarity index 100% rename from share/dark_resources/slot26.png rename to assets/resources/dark_resources/slot26.png diff --git a/share/dark_resources/slot_array26.png b/assets/resources/dark_resources/slot_array26.png similarity index 100% rename from share/dark_resources/slot_array26.png rename to assets/resources/dark_resources/slot_array26.png diff --git a/share/dark_resources/snap_16.png b/assets/resources/dark_resources/snap_16.png similarity index 100% rename from share/dark_resources/snap_16.png rename to assets/resources/dark_resources/snap_16.png diff --git a/share/dark_resources/snap_filled_16.png b/assets/resources/dark_resources/snap_filled_16.png similarity index 100% rename from share/dark_resources/snap_filled_16.png rename to assets/resources/dark_resources/snap_filled_16.png diff --git a/share/dark_resources/solderpaste32.png b/assets/resources/dark_resources/solderpaste32.png similarity index 100% rename from share/dark_resources/solderpaste32.png rename to assets/resources/dark_resources/solderpaste32.png diff --git a/share/dark_resources/solderpastebis32.png b/assets/resources/dark_resources/solderpastebis32.png similarity index 100% rename from share/dark_resources/solderpastebis32.png rename to assets/resources/dark_resources/solderpastebis32.png diff --git a/share/dark_resources/source32.png b/assets/resources/dark_resources/source32.png similarity index 100% rename from share/dark_resources/source32.png rename to assets/resources/dark_resources/source32.png diff --git a/share/dark_resources/splash.png b/assets/resources/dark_resources/splash.png similarity index 100% rename from share/dark_resources/splash.png rename to assets/resources/dark_resources/splash.png diff --git a/share/dark_resources/sub32.png b/assets/resources/dark_resources/sub32.png similarity index 100% rename from share/dark_resources/sub32.png rename to assets/resources/dark_resources/sub32.png diff --git a/share/dark_resources/subtract16.png b/assets/resources/dark_resources/subtract16.png similarity index 100% rename from share/dark_resources/subtract16.png rename to assets/resources/dark_resources/subtract16.png diff --git a/share/dark_resources/subtract24.png b/assets/resources/dark_resources/subtract24.png similarity index 100% rename from share/dark_resources/subtract24.png rename to assets/resources/dark_resources/subtract24.png diff --git a/share/dark_resources/subtract32.png b/assets/resources/dark_resources/subtract32.png similarity index 100% rename from share/dark_resources/subtract32.png rename to assets/resources/dark_resources/subtract32.png diff --git a/share/dark_resources/svg16.png b/assets/resources/dark_resources/svg16.png similarity index 100% rename from share/dark_resources/svg16.png rename to assets/resources/dark_resources/svg16.png diff --git a/share/dark_resources/svg32.png b/assets/resources/dark_resources/svg32.png similarity index 100% rename from share/dark_resources/svg32.png rename to assets/resources/dark_resources/svg32.png diff --git a/share/dark_resources/text32.png b/assets/resources/dark_resources/text32.png similarity index 100% rename from share/dark_resources/text32.png rename to assets/resources/dark_resources/text32.png diff --git a/share/dark_resources/toggle_units16.png b/assets/resources/dark_resources/toggle_units16.png similarity index 100% rename from share/dark_resources/toggle_units16.png rename to assets/resources/dark_resources/toggle_units16.png diff --git a/share/dark_resources/toggle_units32.png b/assets/resources/dark_resources/toggle_units32.png similarity index 100% rename from share/dark_resources/toggle_units32.png rename to assets/resources/dark_resources/toggle_units32.png diff --git a/share/dark_resources/track32.png b/assets/resources/dark_resources/track32.png similarity index 100% rename from share/dark_resources/track32.png rename to assets/resources/dark_resources/track32.png diff --git a/share/dark_resources/transform.png b/assets/resources/dark_resources/transform.png similarity index 100% rename from share/dark_resources/transform.png rename to assets/resources/dark_resources/transform.png diff --git a/share/dark_resources/trash16.png b/assets/resources/dark_resources/trash16.png similarity index 100% rename from share/dark_resources/trash16.png rename to assets/resources/dark_resources/trash16.png diff --git a/share/dark_resources/trash32.png b/assets/resources/dark_resources/trash32.png similarity index 100% rename from share/dark_resources/trash32.png rename to assets/resources/dark_resources/trash32.png diff --git a/share/dark_resources/tv16.png b/assets/resources/dark_resources/tv16.png similarity index 100% rename from share/dark_resources/tv16.png rename to assets/resources/dark_resources/tv16.png diff --git a/share/dark_resources/underline32.png b/assets/resources/dark_resources/underline32.png similarity index 100% rename from share/dark_resources/underline32.png rename to assets/resources/dark_resources/underline32.png diff --git a/share/dark_resources/union16.png b/assets/resources/dark_resources/union16.png similarity index 100% rename from share/dark_resources/union16.png rename to assets/resources/dark_resources/union16.png diff --git a/share/dark_resources/union32.png b/assets/resources/dark_resources/union32.png similarity index 100% rename from share/dark_resources/union32.png rename to assets/resources/dark_resources/union32.png diff --git a/share/dark_resources/videohelp24.png b/assets/resources/dark_resources/videohelp24.png similarity index 100% rename from share/dark_resources/videohelp24.png rename to assets/resources/dark_resources/videohelp24.png diff --git a/share/dark_resources/view64.png b/assets/resources/dark_resources/view64.png similarity index 100% rename from share/dark_resources/view64.png rename to assets/resources/dark_resources/view64.png diff --git a/share/dark_resources/violet32.png b/assets/resources/dark_resources/violet32.png similarity index 100% rename from share/dark_resources/violet32.png rename to assets/resources/dark_resources/violet32.png diff --git a/share/dark_resources/warning.png b/assets/resources/dark_resources/warning.png similarity index 100% rename from share/dark_resources/warning.png rename to assets/resources/dark_resources/warning.png diff --git a/share/dark_resources/white32.png b/assets/resources/dark_resources/white32.png similarity index 100% rename from share/dark_resources/white32.png rename to assets/resources/dark_resources/white32.png diff --git a/share/dark_resources/workspace24.png b/assets/resources/dark_resources/workspace24.png similarity index 100% rename from share/dark_resources/workspace24.png rename to assets/resources/dark_resources/workspace24.png diff --git a/share/dark_resources/yellow32.png b/assets/resources/dark_resources/yellow32.png similarity index 100% rename from share/dark_resources/yellow32.png rename to assets/resources/dark_resources/yellow32.png diff --git a/share/dark_resources/yellowlight12.png b/assets/resources/dark_resources/yellowlight12.png similarity index 100% rename from share/dark_resources/yellowlight12.png rename to assets/resources/dark_resources/yellowlight12.png diff --git a/share/dark_resources/youtube32.png b/assets/resources/dark_resources/youtube32.png similarity index 100% rename from share/dark_resources/youtube32.png rename to assets/resources/dark_resources/youtube32.png diff --git a/share/dark_resources/zoom_fit32.png b/assets/resources/dark_resources/zoom_fit32.png similarity index 100% rename from share/dark_resources/zoom_fit32.png rename to assets/resources/dark_resources/zoom_fit32.png diff --git a/share/dark_resources/zoom_in32.png b/assets/resources/dark_resources/zoom_in32.png similarity index 100% rename from share/dark_resources/zoom_in32.png rename to assets/resources/dark_resources/zoom_in32.png diff --git a/share/dark_resources/zoom_out32.png b/assets/resources/dark_resources/zoom_out32.png similarity index 100% rename from share/dark_resources/zoom_out32.png rename to assets/resources/dark_resources/zoom_out32.png diff --git a/share/database32.png b/assets/resources/database32.png similarity index 100% rename from share/database32.png rename to assets/resources/database32.png diff --git a/share/defaults.png b/assets/resources/defaults.png similarity index 100% rename from share/defaults.png rename to assets/resources/defaults.png diff --git a/share/delete32.png b/assets/resources/delete32.png similarity index 100% rename from share/delete32.png rename to assets/resources/delete32.png diff --git a/share/delete_file16.png b/assets/resources/delete_file16.png similarity index 100% rename from share/delete_file16.png rename to assets/resources/delete_file16.png diff --git a/share/delete_file32.png b/assets/resources/delete_file32.png similarity index 100% rename from share/delete_file32.png rename to assets/resources/delete_file32.png diff --git a/share/deleteshape16.png b/assets/resources/deleteshape16.png similarity index 100% rename from share/deleteshape16.png rename to assets/resources/deleteshape16.png diff --git a/share/deleteshape24.png b/assets/resources/deleteshape24.png similarity index 100% rename from share/deleteshape24.png rename to assets/resources/deleteshape24.png diff --git a/share/deleteshape32.png b/assets/resources/deleteshape32.png similarity index 100% rename from share/deleteshape32.png rename to assets/resources/deleteshape32.png diff --git a/share/deselect_all32.png b/assets/resources/deselect_all32.png similarity index 100% rename from share/deselect_all32.png rename to assets/resources/deselect_all32.png diff --git a/share/disable16.png b/assets/resources/disable16.png similarity index 100% rename from share/disable16.png rename to assets/resources/disable16.png diff --git a/share/disable32.png b/assets/resources/disable32.png similarity index 100% rename from share/disable32.png rename to assets/resources/disable32.png diff --git a/share/disc32.png b/assets/resources/disc32.png similarity index 100% rename from share/disc32.png rename to assets/resources/disc32.png diff --git a/share/distance16.png b/assets/resources/distance16.png similarity index 100% rename from share/distance16.png rename to assets/resources/distance16.png diff --git a/share/distance32.png b/assets/resources/distance32.png similarity index 100% rename from share/distance32.png rename to assets/resources/distance32.png diff --git a/share/distance_min16.png b/assets/resources/distance_min16.png similarity index 100% rename from share/distance_min16.png rename to assets/resources/distance_min16.png diff --git a/share/distance_min32.png b/assets/resources/distance_min32.png similarity index 100% rename from share/distance_min32.png rename to assets/resources/distance_min32.png diff --git a/share/doubleside16.png b/assets/resources/doubleside16.png similarity index 100% rename from share/doubleside16.png rename to assets/resources/doubleside16.png diff --git a/share/doubleside32.png b/assets/resources/doubleside32.png similarity index 100% rename from share/doubleside32.png rename to assets/resources/doubleside32.png diff --git a/share/draw32.png b/assets/resources/draw32.png similarity index 100% rename from share/draw32.png rename to assets/resources/draw32.png diff --git a/share/drill16.png b/assets/resources/drill16.png similarity index 100% rename from share/drill16.png rename to assets/resources/drill16.png diff --git a/share/drill32.png b/assets/resources/drill32.png similarity index 100% rename from share/drill32.png rename to assets/resources/drill32.png diff --git a/share/dxf16.png b/assets/resources/dxf16.png similarity index 100% rename from share/dxf16.png rename to assets/resources/dxf16.png diff --git a/share/edit16.png b/assets/resources/edit16.png similarity index 100% rename from share/edit16.png rename to assets/resources/edit16.png diff --git a/share/edit32.png b/assets/resources/edit32.png similarity index 100% rename from share/edit32.png rename to assets/resources/edit32.png diff --git a/share/edit_file16.png b/assets/resources/edit_file16.png similarity index 100% rename from share/edit_file16.png rename to assets/resources/edit_file16.png diff --git a/share/edit_file32.png b/assets/resources/edit_file32.png similarity index 100% rename from share/edit_file32.png rename to assets/resources/edit_file32.png diff --git a/share/edit_ok16.png b/assets/resources/edit_ok16.png similarity index 100% rename from share/edit_ok16.png rename to assets/resources/edit_ok16.png diff --git a/share/edit_ok32.png b/assets/resources/edit_ok32.png similarity index 100% rename from share/edit_ok32.png rename to assets/resources/edit_ok32.png diff --git a/share/edit_ok32_bis.png b/assets/resources/edit_ok32_bis.png similarity index 100% rename from share/edit_ok32_bis.png rename to assets/resources/edit_ok32_bis.png diff --git a/share/eraser26.png b/assets/resources/eraser26.png similarity index 100% rename from share/eraser26.png rename to assets/resources/eraser26.png diff --git a/share/explode32.png b/assets/resources/explode32.png similarity index 100% rename from share/explode32.png rename to assets/resources/explode32.png diff --git a/share/export.png b/assets/resources/export.png similarity index 100% rename from share/export.png rename to assets/resources/export.png diff --git a/share/export_png32.png b/assets/resources/export_png32.png similarity index 100% rename from share/export_png32.png rename to assets/resources/export_png32.png diff --git a/share/extract_drill16.png b/assets/resources/extract_drill16.png similarity index 100% rename from share/extract_drill16.png rename to assets/resources/extract_drill16.png diff --git a/share/extract_drill32.png b/assets/resources/extract_drill32.png similarity index 100% rename from share/extract_drill32.png rename to assets/resources/extract_drill32.png diff --git a/share/fiducials_32.png b/assets/resources/fiducials_32.png similarity index 100% rename from share/fiducials_32.png rename to assets/resources/fiducials_32.png diff --git a/share/file16.png b/assets/resources/file16.png similarity index 100% rename from share/file16.png rename to assets/resources/file16.png diff --git a/share/file32.png b/assets/resources/file32.png similarity index 100% rename from share/file32.png rename to assets/resources/file32.png diff --git a/share/film16.png b/assets/resources/film16.png similarity index 100% rename from share/film16.png rename to assets/resources/film16.png diff --git a/share/film32.png b/assets/resources/film32.png similarity index 100% rename from share/film32.png rename to assets/resources/film32.png diff --git a/share/flatcam_icon128.png b/assets/resources/flatcam_icon128.png similarity index 100% rename from share/flatcam_icon128.png rename to assets/resources/flatcam_icon128.png diff --git a/share/flatcam_icon128_inv.png b/assets/resources/flatcam_icon128_inv.png similarity index 100% rename from share/flatcam_icon128_inv.png rename to assets/resources/flatcam_icon128_inv.png diff --git a/share/flatcam_icon16.ico b/assets/resources/flatcam_icon16.ico similarity index 100% rename from share/flatcam_icon16.ico rename to assets/resources/flatcam_icon16.ico diff --git a/share/flatcam_icon16.png b/assets/resources/flatcam_icon16.png similarity index 100% rename from share/flatcam_icon16.png rename to assets/resources/flatcam_icon16.png diff --git a/share/flatcam_icon24.png b/assets/resources/flatcam_icon24.png similarity index 100% rename from share/flatcam_icon24.png rename to assets/resources/flatcam_icon24.png diff --git a/share/flatcam_icon256.ico b/assets/resources/flatcam_icon256.ico similarity index 100% rename from share/flatcam_icon256.ico rename to assets/resources/flatcam_icon256.ico diff --git a/share/flatcam_icon256.png b/assets/resources/flatcam_icon256.png similarity index 100% rename from share/flatcam_icon256.png rename to assets/resources/flatcam_icon256.png diff --git a/share/flatcam_icon32.ico b/assets/resources/flatcam_icon32.ico similarity index 100% rename from share/flatcam_icon32.ico rename to assets/resources/flatcam_icon32.ico diff --git a/share/flatcam_icon32.png b/assets/resources/flatcam_icon32.png similarity index 100% rename from share/flatcam_icon32.png rename to assets/resources/flatcam_icon32.png diff --git a/share/flatcam_icon32_green.png b/assets/resources/flatcam_icon32_green.png similarity index 100% rename from share/flatcam_icon32_green.png rename to assets/resources/flatcam_icon32_green.png diff --git a/share/flatcam_icon48.ico b/assets/resources/flatcam_icon48.ico similarity index 100% rename from share/flatcam_icon48.ico rename to assets/resources/flatcam_icon48.ico diff --git a/share/flatcam_icon48.png b/assets/resources/flatcam_icon48.png similarity index 100% rename from share/flatcam_icon48.png rename to assets/resources/flatcam_icon48.png diff --git a/share/flipx.png b/assets/resources/flipx.png similarity index 100% rename from share/flipx.png rename to assets/resources/flipx.png diff --git a/share/flipy.png b/assets/resources/flipy.png similarity index 100% rename from share/flipy.png rename to assets/resources/flipy.png diff --git a/share/floppy16.png b/assets/resources/floppy16.png similarity index 100% rename from share/floppy16.png rename to assets/resources/floppy16.png diff --git a/share/floppy32.png b/assets/resources/floppy32.png similarity index 100% rename from share/floppy32.png rename to assets/resources/floppy32.png diff --git a/share/folder16.png b/assets/resources/folder16.png similarity index 100% rename from share/folder16.png rename to assets/resources/folder16.png diff --git a/share/folder32.png b/assets/resources/folder32.png similarity index 100% rename from share/folder32.png rename to assets/resources/folder32.png diff --git a/share/folder32_Excellon.png b/assets/resources/folder32_Excellon.png similarity index 100% rename from share/folder32_Excellon.png rename to assets/resources/folder32_Excellon.png diff --git a/share/folder32_bis.png b/assets/resources/folder32_bis.png similarity index 100% rename from share/folder32_bis.png rename to assets/resources/folder32_bis.png diff --git a/share/folder32_gerber.png b/assets/resources/folder32_gerber.png similarity index 100% rename from share/folder32_gerber.png rename to assets/resources/folder32_gerber.png diff --git a/share/fscreen32.png b/assets/resources/fscreen32.png similarity index 100% rename from share/fscreen32.png rename to assets/resources/fscreen32.png diff --git a/share/gear32.png b/assets/resources/gear32.png similarity index 100% rename from share/gear32.png rename to assets/resources/gear32.png diff --git a/share/gear48.png b/assets/resources/gear48.png similarity index 100% rename from share/gear48.png rename to assets/resources/gear48.png diff --git a/share/geometry16.png b/assets/resources/geometry16.png similarity index 100% rename from share/geometry16.png rename to assets/resources/geometry16.png diff --git a/share/geometry32.png b/assets/resources/geometry32.png similarity index 100% rename from share/geometry32.png rename to assets/resources/geometry32.png diff --git a/share/globe16.png b/assets/resources/globe16.png similarity index 100% rename from share/globe16.png rename to assets/resources/globe16.png diff --git a/share/graylight12.png b/assets/resources/graylight12.png similarity index 100% rename from share/graylight12.png rename to assets/resources/graylight12.png diff --git a/share/green32.png b/assets/resources/green32.png similarity index 100% rename from share/green32.png rename to assets/resources/green32.png diff --git a/share/greenlight12.png b/assets/resources/greenlight12.png similarity index 100% rename from share/greenlight12.png rename to assets/resources/greenlight12.png diff --git a/share/grid16.png b/assets/resources/grid16.png similarity index 100% rename from share/grid16.png rename to assets/resources/grid16.png diff --git a/share/grid32.png b/assets/resources/grid32.png similarity index 100% rename from share/grid32.png rename to assets/resources/grid32.png diff --git a/share/grid32_menu.png b/assets/resources/grid32_menu.png similarity index 100% rename from share/grid32_menu.png rename to assets/resources/grid32_menu.png diff --git a/share/help.png b/assets/resources/help.png similarity index 100% rename from share/help.png rename to assets/resources/help.png diff --git a/share/home16.png b/assets/resources/home16.png similarity index 100% rename from share/home16.png rename to assets/resources/home16.png diff --git a/share/image16.png b/assets/resources/image16.png similarity index 100% rename from share/image16.png rename to assets/resources/image16.png diff --git a/share/image32.png b/assets/resources/image32.png similarity index 100% rename from share/image32.png rename to assets/resources/image32.png diff --git a/share/import.png b/assets/resources/import.png similarity index 100% rename from share/import.png rename to assets/resources/import.png diff --git a/share/info16.png b/assets/resources/info16.png similarity index 100% rename from share/info16.png rename to assets/resources/info16.png diff --git a/share/intersection16.png b/assets/resources/intersection16.png similarity index 100% rename from share/intersection16.png rename to assets/resources/intersection16.png diff --git a/share/intersection24.png b/assets/resources/intersection24.png similarity index 100% rename from share/intersection24.png rename to assets/resources/intersection24.png diff --git a/share/intersection32.png b/assets/resources/intersection32.png similarity index 100% rename from share/intersection32.png rename to assets/resources/intersection32.png diff --git a/share/invert16.png b/assets/resources/invert16.png similarity index 100% rename from share/invert16.png rename to assets/resources/invert16.png diff --git a/share/invert32.png b/assets/resources/invert32.png similarity index 100% rename from share/invert32.png rename to assets/resources/invert32.png diff --git a/share/italic32.png b/assets/resources/italic32.png similarity index 100% rename from share/italic32.png rename to assets/resources/italic32.png diff --git a/share/join16.png b/assets/resources/join16.png similarity index 100% rename from share/join16.png rename to assets/resources/join16.png diff --git a/share/join32.png b/assets/resources/join32.png similarity index 100% rename from share/join32.png rename to assets/resources/join32.png diff --git a/share/jump_to16.png b/assets/resources/jump_to16.png similarity index 100% rename from share/jump_to16.png rename to assets/resources/jump_to16.png diff --git a/share/jump_to32.png b/assets/resources/jump_to32.png similarity index 100% rename from share/jump_to32.png rename to assets/resources/jump_to32.png diff --git a/share/language32.png b/assets/resources/language32.png similarity index 100% rename from share/language32.png rename to assets/resources/language32.png diff --git a/share/letter_t_32.png b/assets/resources/letter_t_32.png similarity index 100% rename from share/letter_t_32.png rename to assets/resources/letter_t_32.png diff --git a/share/link16.png b/assets/resources/link16.png similarity index 100% rename from share/link16.png rename to assets/resources/link16.png diff --git a/share/locate16.png b/assets/resources/locate16.png similarity index 100% rename from share/locate16.png rename to assets/resources/locate16.png diff --git a/share/locate32.png b/assets/resources/locate32.png similarity index 100% rename from share/locate32.png rename to assets/resources/locate32.png diff --git a/share/machine16.png b/assets/resources/machine16.png similarity index 100% rename from share/machine16.png rename to assets/resources/machine16.png diff --git a/share/markarea32.png b/assets/resources/markarea32.png similarity index 100% rename from share/markarea32.png rename to assets/resources/markarea32.png diff --git a/share/move16.png b/assets/resources/move16.png similarity index 100% rename from share/move16.png rename to assets/resources/move16.png diff --git a/share/move32.png b/assets/resources/move32.png similarity index 100% rename from share/move32.png rename to assets/resources/move32.png diff --git a/share/move32_bis.png b/assets/resources/move32_bis.png similarity index 100% rename from share/move32_bis.png rename to assets/resources/move32_bis.png diff --git a/share/ncc16.png b/assets/resources/ncc16.png similarity index 100% rename from share/ncc16.png rename to assets/resources/ncc16.png diff --git a/share/new_exc32.png b/assets/resources/new_exc32.png similarity index 100% rename from share/new_exc32.png rename to assets/resources/new_exc32.png diff --git a/share/new_file16.png b/assets/resources/new_file16.png similarity index 100% rename from share/new_file16.png rename to assets/resources/new_file16.png diff --git a/share/new_file32.png b/assets/resources/new_file32.png similarity index 100% rename from share/new_file32.png rename to assets/resources/new_file32.png diff --git a/share/new_file_exc16.png b/assets/resources/new_file_exc16.png similarity index 100% rename from share/new_file_exc16.png rename to assets/resources/new_file_exc16.png diff --git a/share/new_file_exc32.png b/assets/resources/new_file_exc32.png similarity index 100% rename from share/new_file_exc32.png rename to assets/resources/new_file_exc32.png diff --git a/share/new_file_geo16.png b/assets/resources/new_file_geo16.png similarity index 100% rename from share/new_file_geo16.png rename to assets/resources/new_file_geo16.png diff --git a/share/new_file_geo32.png b/assets/resources/new_file_geo32.png similarity index 100% rename from share/new_file_geo32.png rename to assets/resources/new_file_geo32.png diff --git a/share/new_file_grb16.png b/assets/resources/new_file_grb16.png similarity index 100% rename from share/new_file_grb16.png rename to assets/resources/new_file_grb16.png diff --git a/share/new_file_grb32.png b/assets/resources/new_file_grb32.png similarity index 100% rename from share/new_file_grb32.png rename to assets/resources/new_file_grb32.png diff --git a/share/new_geo16.png b/assets/resources/new_geo16.png similarity index 100% rename from share/new_geo16.png rename to assets/resources/new_geo16.png diff --git a/share/new_geo32.png b/assets/resources/new_geo32.png similarity index 100% rename from share/new_geo32.png rename to assets/resources/new_geo32.png diff --git a/share/new_geo32_bis.png b/assets/resources/new_geo32_bis.png similarity index 100% rename from share/new_geo32_bis.png rename to assets/resources/new_geo32_bis.png diff --git a/share/notebook16.png b/assets/resources/notebook16.png similarity index 100% rename from share/notebook16.png rename to assets/resources/notebook16.png diff --git a/share/notebook32.png b/assets/resources/notebook32.png similarity index 100% rename from share/notebook32.png rename to assets/resources/notebook32.png diff --git a/share/notes16.png b/assets/resources/notes16.png similarity index 100% rename from share/notes16.png rename to assets/resources/notes16.png diff --git a/share/notes16_1.png b/assets/resources/notes16_1.png similarity index 100% rename from share/notes16_1.png rename to assets/resources/notes16_1.png diff --git a/share/offsetx32.png b/assets/resources/offsetx32.png similarity index 100% rename from share/offsetx32.png rename to assets/resources/offsetx32.png diff --git a/share/offsety32.png b/assets/resources/offsety32.png similarity index 100% rename from share/offsety32.png rename to assets/resources/offsety32.png diff --git a/share/open_excellon32.png b/assets/resources/open_excellon32.png similarity index 100% rename from share/open_excellon32.png rename to assets/resources/open_excellon32.png diff --git a/share/open_script32.png b/assets/resources/open_script32.png similarity index 100% rename from share/open_script32.png rename to assets/resources/open_script32.png diff --git a/share/origin.png b/assets/resources/origin.png similarity index 100% rename from share/origin.png rename to assets/resources/origin.png diff --git a/share/origin16.png b/assets/resources/origin16.png similarity index 100% rename from share/origin16.png rename to assets/resources/origin16.png diff --git a/share/origin2_16.png b/assets/resources/origin2_16.png similarity index 100% rename from share/origin2_16.png rename to assets/resources/origin2_16.png diff --git a/share/origin2_32.png b/assets/resources/origin2_32.png similarity index 100% rename from share/origin2_32.png rename to assets/resources/origin2_32.png diff --git a/share/origin32.png b/assets/resources/origin32.png similarity index 100% rename from share/origin32.png rename to assets/resources/origin32.png diff --git a/share/padarray32.png b/assets/resources/padarray32.png similarity index 100% rename from share/padarray32.png rename to assets/resources/padarray32.png diff --git a/share/paint16.png b/assets/resources/paint16.png similarity index 100% rename from share/paint16.png rename to assets/resources/paint16.png diff --git a/share/paint20.png b/assets/resources/paint20.png similarity index 100% rename from share/paint20.png rename to assets/resources/paint20.png diff --git a/share/paint20_1.png b/assets/resources/paint20_1.png similarity index 100% rename from share/paint20_1.png rename to assets/resources/paint20_1.png diff --git a/share/panel16.png b/assets/resources/panel16.png similarity index 100% rename from share/panel16.png rename to assets/resources/panel16.png diff --git a/share/panel32.png b/assets/resources/panel32.png similarity index 100% rename from share/panel32.png rename to assets/resources/panel32.png diff --git a/share/panelize16.png b/assets/resources/panelize16.png similarity index 100% rename from share/panelize16.png rename to assets/resources/panelize16.png diff --git a/share/panelize32.png b/assets/resources/panelize32.png similarity index 100% rename from share/panelize32.png rename to assets/resources/panelize32.png diff --git a/share/path32.png b/assets/resources/path32.png similarity index 100% rename from share/path32.png rename to assets/resources/path32.png diff --git a/share/pdf32.png b/assets/resources/pdf32.png similarity index 100% rename from share/pdf32.png rename to assets/resources/pdf32.png diff --git a/share/pdf_link16.png b/assets/resources/pdf_link16.png similarity index 100% rename from share/pdf_link16.png rename to assets/resources/pdf_link16.png diff --git a/share/plot32.png b/assets/resources/plot32.png similarity index 100% rename from share/plot32.png rename to assets/resources/plot32.png diff --git a/share/plus16.png b/assets/resources/plus16.png similarity index 100% rename from share/plus16.png rename to assets/resources/plus16.png diff --git a/share/plus32.png b/assets/resources/plus32.png similarity index 100% rename from share/plus32.png rename to assets/resources/plus32.png diff --git a/share/pointer.svg b/assets/resources/pointer.svg similarity index 100% rename from share/pointer.svg rename to assets/resources/pointer.svg diff --git a/share/pointer32.png b/assets/resources/pointer32.png similarity index 100% rename from share/pointer32.png rename to assets/resources/pointer32.png diff --git a/share/poligonize32.png b/assets/resources/poligonize32.png similarity index 100% rename from share/poligonize32.png rename to assets/resources/poligonize32.png diff --git a/share/polygon32.png b/assets/resources/polygon32.png similarity index 100% rename from share/polygon32.png rename to assets/resources/polygon32.png diff --git a/share/power16.png b/assets/resources/power16.png similarity index 100% rename from share/power16.png rename to assets/resources/power16.png diff --git a/share/pref.png b/assets/resources/pref.png similarity index 100% rename from share/pref.png rename to assets/resources/pref.png diff --git a/share/printer16.png b/assets/resources/printer16.png similarity index 100% rename from share/printer16.png rename to assets/resources/printer16.png diff --git a/share/printer32.png b/assets/resources/printer32.png similarity index 100% rename from share/printer32.png rename to assets/resources/printer32.png diff --git a/share/project16.png b/assets/resources/project16.png similarity index 100% rename from share/project16.png rename to assets/resources/project16.png diff --git a/share/project_save16.png b/assets/resources/project_save16.png similarity index 100% rename from share/project_save16.png rename to assets/resources/project_save16.png diff --git a/share/project_save32.png b/assets/resources/project_save32.png similarity index 100% rename from share/project_save32.png rename to assets/resources/project_save32.png diff --git a/share/properties32.png b/assets/resources/properties32.png similarity index 100% rename from share/properties32.png rename to assets/resources/properties32.png diff --git a/share/punch16.png b/assets/resources/punch16.png similarity index 100% rename from share/punch16.png rename to assets/resources/punch16.png diff --git a/share/punch32.png b/assets/resources/punch32.png similarity index 100% rename from share/punch32.png rename to assets/resources/punch32.png diff --git a/share/qrcode32.png b/assets/resources/qrcode32.png similarity index 100% rename from share/qrcode32.png rename to assets/resources/qrcode32.png diff --git a/share/recent_files.png b/assets/resources/recent_files.png similarity index 100% rename from share/recent_files.png rename to assets/resources/recent_files.png diff --git a/share/rectangle32.png b/assets/resources/rectangle32.png similarity index 100% rename from share/rectangle32.png rename to assets/resources/rectangle32.png diff --git a/share/recycle16.png b/assets/resources/recycle16.png similarity index 100% rename from share/recycle16.png rename to assets/resources/recycle16.png diff --git a/share/red32.png b/assets/resources/red32.png similarity index 100% rename from share/red32.png rename to assets/resources/red32.png diff --git a/share/redlight12.png b/assets/resources/redlight12.png similarity index 100% rename from share/redlight12.png rename to assets/resources/redlight12.png diff --git a/share/replot16.png b/assets/resources/replot16.png similarity index 100% rename from share/replot16.png rename to assets/resources/replot16.png diff --git a/share/replot32.png b/assets/resources/replot32.png similarity index 100% rename from share/replot32.png rename to assets/resources/replot32.png diff --git a/share/resize16.png b/assets/resources/resize16.png similarity index 100% rename from share/resize16.png rename to assets/resources/resize16.png diff --git a/share/rotate.png b/assets/resources/rotate.png similarity index 100% rename from share/rotate.png rename to assets/resources/rotate.png diff --git a/share/rules32.png b/assets/resources/rules32.png similarity index 100% rename from share/rules32.png rename to assets/resources/rules32.png diff --git a/share/save_as.png b/assets/resources/save_as.png similarity index 100% rename from share/save_as.png rename to assets/resources/save_as.png diff --git a/share/scale32.png b/assets/resources/scale32.png similarity index 100% rename from share/scale32.png rename to assets/resources/scale32.png diff --git a/share/script14.png b/assets/resources/script14.png similarity index 100% rename from share/script14.png rename to assets/resources/script14.png diff --git a/share/script16.png b/assets/resources/script16.png similarity index 100% rename from share/script16.png rename to assets/resources/script16.png diff --git a/share/script_new16.png b/assets/resources/script_new16.png similarity index 100% rename from share/script_new16.png rename to assets/resources/script_new16.png diff --git a/share/script_new24.png b/assets/resources/script_new24.png similarity index 100% rename from share/script_new24.png rename to assets/resources/script_new24.png diff --git a/share/script_open16.png b/assets/resources/script_open16.png similarity index 100% rename from share/script_open16.png rename to assets/resources/script_open16.png diff --git a/share/script_open18.png b/assets/resources/script_open18.png similarity index 100% rename from share/script_open18.png rename to assets/resources/script_open18.png diff --git a/share/script_open24.png b/assets/resources/script_open24.png similarity index 100% rename from share/script_open24.png rename to assets/resources/script_open24.png diff --git a/share/select_all.png b/assets/resources/select_all.png similarity index 100% rename from share/select_all.png rename to assets/resources/select_all.png diff --git a/share/semidisc32.png b/assets/resources/semidisc32.png similarity index 100% rename from share/semidisc32.png rename to assets/resources/semidisc32.png diff --git a/share/set_color16.png b/assets/resources/set_color16.png similarity index 100% rename from share/set_color16.png rename to assets/resources/set_color16.png diff --git a/share/set_color32.png b/assets/resources/set_color32.png similarity index 100% rename from share/set_color32.png rename to assets/resources/set_color32.png diff --git a/share/shell16.png b/assets/resources/shell16.png similarity index 100% rename from share/shell16.png rename to assets/resources/shell16.png diff --git a/share/shell32.png b/assets/resources/shell32.png similarity index 100% rename from share/shell32.png rename to assets/resources/shell32.png diff --git a/share/shortcuts24.png b/assets/resources/shortcuts24.png similarity index 100% rename from share/shortcuts24.png rename to assets/resources/shortcuts24.png diff --git a/share/skewX.png b/assets/resources/skewX.png similarity index 100% rename from share/skewX.png rename to assets/resources/skewX.png diff --git a/share/skewY.png b/assets/resources/skewY.png similarity index 100% rename from share/skewY.png rename to assets/resources/skewY.png diff --git a/share/slot26.png b/assets/resources/slot26.png similarity index 100% rename from share/slot26.png rename to assets/resources/slot26.png diff --git a/share/slot_array26.png b/assets/resources/slot_array26.png similarity index 100% rename from share/slot_array26.png rename to assets/resources/slot_array26.png diff --git a/share/snap_16.png b/assets/resources/snap_16.png similarity index 100% rename from share/snap_16.png rename to assets/resources/snap_16.png diff --git a/share/snap_filled_16.png b/assets/resources/snap_filled_16.png similarity index 100% rename from share/snap_filled_16.png rename to assets/resources/snap_filled_16.png diff --git a/share/solderpaste32.png b/assets/resources/solderpaste32.png similarity index 100% rename from share/solderpaste32.png rename to assets/resources/solderpaste32.png diff --git a/share/solderpastebis32.png b/assets/resources/solderpastebis32.png similarity index 100% rename from share/solderpastebis32.png rename to assets/resources/solderpastebis32.png diff --git a/share/source32.png b/assets/resources/source32.png similarity index 100% rename from share/source32.png rename to assets/resources/source32.png diff --git a/share/splash.png b/assets/resources/splash.png similarity index 100% rename from share/splash.png rename to assets/resources/splash.png diff --git a/share/sub32.png b/assets/resources/sub32.png similarity index 100% rename from share/sub32.png rename to assets/resources/sub32.png diff --git a/share/subtract16.png b/assets/resources/subtract16.png similarity index 100% rename from share/subtract16.png rename to assets/resources/subtract16.png diff --git a/share/subtract24.png b/assets/resources/subtract24.png similarity index 100% rename from share/subtract24.png rename to assets/resources/subtract24.png diff --git a/share/subtract32.png b/assets/resources/subtract32.png similarity index 100% rename from share/subtract32.png rename to assets/resources/subtract32.png diff --git a/share/svg16.png b/assets/resources/svg16.png similarity index 100% rename from share/svg16.png rename to assets/resources/svg16.png diff --git a/share/svg32.png b/assets/resources/svg32.png similarity index 100% rename from share/svg32.png rename to assets/resources/svg32.png diff --git a/share/text32.png b/assets/resources/text32.png similarity index 100% rename from share/text32.png rename to assets/resources/text32.png diff --git a/share/toggle_units16.png b/assets/resources/toggle_units16.png similarity index 100% rename from share/toggle_units16.png rename to assets/resources/toggle_units16.png diff --git a/share/toggle_units32.png b/assets/resources/toggle_units32.png similarity index 100% rename from share/toggle_units32.png rename to assets/resources/toggle_units32.png diff --git a/share/track32.png b/assets/resources/track32.png similarity index 100% rename from share/track32.png rename to assets/resources/track32.png diff --git a/share/transform.png b/assets/resources/transform.png similarity index 100% rename from share/transform.png rename to assets/resources/transform.png diff --git a/share/trash16.png b/assets/resources/trash16.png similarity index 100% rename from share/trash16.png rename to assets/resources/trash16.png diff --git a/share/trash32.png b/assets/resources/trash32.png similarity index 100% rename from share/trash32.png rename to assets/resources/trash32.png diff --git a/share/tv16.png b/assets/resources/tv16.png similarity index 100% rename from share/tv16.png rename to assets/resources/tv16.png diff --git a/share/underline32.png b/assets/resources/underline32.png similarity index 100% rename from share/underline32.png rename to assets/resources/underline32.png diff --git a/share/union16.png b/assets/resources/union16.png similarity index 100% rename from share/union16.png rename to assets/resources/union16.png diff --git a/share/union32.png b/assets/resources/union32.png similarity index 100% rename from share/union32.png rename to assets/resources/union32.png diff --git a/share/videohelp24.png b/assets/resources/videohelp24.png similarity index 100% rename from share/videohelp24.png rename to assets/resources/videohelp24.png diff --git a/share/view64.png b/assets/resources/view64.png similarity index 100% rename from share/view64.png rename to assets/resources/view64.png diff --git a/share/violet32.png b/assets/resources/violet32.png similarity index 100% rename from share/violet32.png rename to assets/resources/violet32.png diff --git a/share/warning.png b/assets/resources/warning.png similarity index 100% rename from share/warning.png rename to assets/resources/warning.png diff --git a/share/white32.png b/assets/resources/white32.png similarity index 100% rename from share/white32.png rename to assets/resources/white32.png diff --git a/share/workspace24.png b/assets/resources/workspace24.png similarity index 100% rename from share/workspace24.png rename to assets/resources/workspace24.png diff --git a/share/yellow32.png b/assets/resources/yellow32.png similarity index 100% rename from share/yellow32.png rename to assets/resources/yellow32.png diff --git a/share/yellowlight12.png b/assets/resources/yellowlight12.png similarity index 100% rename from share/yellowlight12.png rename to assets/resources/yellowlight12.png diff --git a/share/youtube32.png b/assets/resources/youtube32.png similarity index 100% rename from share/youtube32.png rename to assets/resources/youtube32.png diff --git a/share/zoom_fit32.png b/assets/resources/zoom_fit32.png similarity index 100% rename from share/zoom_fit32.png rename to assets/resources/zoom_fit32.png diff --git a/share/zoom_in32.png b/assets/resources/zoom_in32.png similarity index 100% rename from share/zoom_in32.png rename to assets/resources/zoom_in32.png diff --git a/share/zoom_out32.png b/assets/resources/zoom_out32.png similarity index 100% rename from share/zoom_out32.png rename to assets/resources/zoom_out32.png diff --git a/camlib.py b/camlib.py index 44ba0106..da0854f9 100644 --- a/camlib.py +++ b/camlib.py @@ -33,9 +33,11 @@ from shapely.wkt import dumps as sdumps from shapely.geometry.base import BaseGeometry from shapely.geometry import shape -# needed for legacy mode +# --------------------------------------- +# NEEDED for Legacy mode # Used for solid polygons in Matplotlib from descartes.patch import PolygonPatch +# --------------------------------------- import collections from collections import Iterable @@ -44,6 +46,8 @@ import rasterio from rasterio.features import shapes import ezdxf +from FlatCAMCommon import GracefulException as grace + # TODO: Commented for FlatCAM packaging with cx_freeze # from scipy.spatial import KDTree, Delaunay # from scipy.spatial import Delaunay @@ -56,7 +60,7 @@ if platform.architecture()[0] == '64bit': from ortools.constraint_solver import routing_enums_pb2 import logging -import FlatCAMApp + import gettext import FlatCAMTranslation as fcTranslate import builtins @@ -635,26 +639,26 @@ class Geometry(object): def bounds_rec(obj): if type(obj) is list: - minx = np.Inf - miny = np.Inf - maxx = -np.Inf - maxy = -np.Inf + gminx = np.Inf + gminy = np.Inf + gmaxx = -np.Inf + gmaxy = -np.Inf for k in obj: if type(k) is dict: for key in k: minx_, miny_, maxx_, maxy_ = bounds_rec(k[key]) - minx = min(minx, minx_) - miny = min(miny, miny_) - maxx = max(maxx, maxx_) - maxy = max(maxy, maxy_) + gminx = min(gminx, minx_) + gminy = min(gminy, miny_) + gmaxx = max(gmaxx, maxx_) + gmaxy = max(gmaxy, maxy_) else: minx_, miny_, maxx_, maxy_ = bounds_rec(k) - minx = min(minx, minx_) - miny = min(miny, miny_) - maxx = max(maxx, maxx_) - maxy = max(maxy, maxy_) - return minx, miny, maxx, maxy + gminx = min(gminx, minx_) + gminy = min(gminy, miny_) + gmaxx = max(gmaxx, maxx_) + gmaxy = max(gmaxy, maxy_) + return gminx, gminy, gmaxx, gmaxy else: # it's a Shapely object, return it's bounds return obj.bounds @@ -678,7 +682,7 @@ class Geometry(object): maxx_list.append(maxx) maxy_list.append(maxy) - return(min(minx_list), min(miny_list), max(maxx_list), max(maxy_list)) + return min(minx_list), min(miny_list), max(maxx_list), max(maxy_list) else: if flatten: self.flatten(reset=True) @@ -710,7 +714,6 @@ class Geometry(object): # return 0, 0, 0, 0 # # if type(self.solid_geometry) is list: - # # TODO: This can be done faster. See comment from Shapely mailing lists. # if len(self.solid_geometry) == 0: # log.debug('solid_geometry is empty []') # return 0, 0, 0, 0 @@ -726,7 +729,6 @@ class Geometry(object): # return 0, 0, 0, 0 # # if type(self.solid_geometry) is list: - # # TODO: This can be done faster. See comment from Shapely mailing lists. # if len(self.solid_geometry) == 0: # log.debug('solid_geometry is empty []') # return 0, 0, 0, 0 @@ -930,7 +932,7 @@ class Geometry(object): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_iso = [] @@ -954,7 +956,7 @@ class Geometry(object): for pol in working_geo: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if offset == 0: geo_iso.append(pol) else: @@ -1005,13 +1007,13 @@ class Geometry(object): """ Imports shapes from an SVG file into the object's geometry. - :param filename: Path to the SVG file. - :type filename: str + :param filename: Path to the SVG file. + :type filename: str :param object_type: parameter passed further along - :param flip: Flip the vertically. - :type flip: bool - :param units: FlatCAM units - :return: None + :param flip: Flip the vertically. + :type flip: bool + :param units: FlatCAM units + :return: None """ log.debug("camlib.Geometry.import_svg()") @@ -1060,10 +1062,10 @@ class Geometry(object): """ Imports shapes from an DXF file into the object's geometry. - :param filename: Path to the DXF file. - :type filename: str - :param units: Application units - :type flip: str + :param filename: Path to the DXF file. + :type filename: str + :param object_type: + :param units: Application units :return: None """ @@ -1212,15 +1214,15 @@ class Geometry(object): This algorithm shrinks the edges of the polygon and takes the resulting edges as toolpaths. - :param polygon: Polygon to clear. - :param tooldia: Diameter of the tool. - :param steps_per_circle: number of linear segments to be used to approximate a circle - :param overlap: Overlap of toolpasses. - :param connect: Draw lines between disjoint segments to - minimize tool lifts. - :param contour: Paint around the edges. Inconsequential in - this painting method. - :param prog_plot: boolean; if Ture use the progressive plotting + :param polygon: Polygon to clear. + :param tooldia: Diameter of the tool. + :param steps_per_circle: number of linear segments to be used to approximate a circle + :param overlap: Overlap of toolpasses. + :param connect: Draw lines between disjoint segments to + minimize tool lifts. + :param contour: Paint around the edges. Inconsequential in + this painting method. + :param prog_plot: boolean; if Ture use the progressive plotting :return: """ @@ -1261,7 +1263,7 @@ class Geometry(object): while True: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -1353,7 +1355,7 @@ class Geometry(object): while True: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -1467,7 +1469,7 @@ class Geometry(object): while y > bot + tooldia / 1.999999999: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -1504,7 +1506,7 @@ class Geometry(object): while x < right - tooldia / 1.999999999: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -1594,15 +1596,15 @@ class Geometry(object): This algorithm draws parallel lines inside the polygon. - :param line: The target line that create painted polygon. - :param aperture_size: the size of the aperture that is used to draw the 'line' as a polygon - :type line: shapely.geometry.LineString or shapely.geometry.MultiLineString - :param tooldia: Tool diameter. - :param steps_per_circle: how many linear segments to use to approximate a circle - :param overlap: Tool path overlap percentage. - :param connect: Connect lines to avoid tool lifts. - :param contour: Paint around the edges. - :param prog_plot: boolean; if to use the progressive plotting + :param line: The target line that create painted polygon. + :param aperture_size: the size of the aperture that is used to draw the 'line' as a polygon + :type line: shapely.geometry.LineString or shapely.geometry.MultiLineString + :param tooldia: Tool diameter. + :param steps_per_circle: how many linear segments to use to approximate a circle + :param overlap: Tool path overlap percentage. + :param connect: Connect lines to avoid tool lifts. + :param contour: Paint around the edges. + :param prog_plot: boolean; if to use the progressive plotting :return: """ @@ -1635,7 +1637,7 @@ class Geometry(object): while delta < aperture_size / 2: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -1800,7 +1802,7 @@ class Geometry(object): # #storage.insert(shape) # ## Iterate over geometry paths getting the nearest each time. - #optimized_paths = [] + # optimized_paths = [] optimized_paths = FlatCAMRTreeStorage() optimized_paths.get_points = get_pts path_count = 0 @@ -1870,12 +1872,13 @@ class Geometry(object): def path_connect(storage, origin=(0, 0)): """ Simplifies paths in the FlatCAMRTreeStorage storage by - connecting paths that touch on their enpoints. + connecting paths that touch on their endpoints. - :param storage: Storage containing the initial paths. - :rtype storage: FlatCAMRTreeStorage - :return: Simplified storage. - :rtype: FlatCAMRTreeStorage + :param storage: Storage containing the initial paths. + :rtype storage: FlatCAMRTreeStorage + :param origin: tuple; point from which to calculate the nearest point + :return: Simplified storage. + :rtype: FlatCAMRTreeStorage """ log.debug("path_connect()") @@ -1888,7 +1891,7 @@ class Geometry(object): # storage.get_points = get_pts # # for shape in pathlist: - # if shape is not None: # TODO: This shouldn't have happened. + # if shape is not None: # storage.insert(shape) path_count = 0 @@ -2113,11 +2116,11 @@ class Geometry(object): Mirrors the object around a specified axis passign through the given point. - :param axis: "X" or "Y" indicates around which axis to mirror. - :type axis: str - :param point: [x, y] point belonging to the mirror axis. - :type point: list - :return: None + :param axis: "X" or "Y" indicates around which axis to mirror. + :type axis: str + :param point: [x, y] point belonging to the mirror axis. + :type point: list + :return: None """ log.debug("camlib.Geometry.mirror()") @@ -2148,8 +2151,7 @@ class Geometry(object): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.tools[tool]['solid_geometry']: - self.geo_len += 1 + self.geo_len = len(self.tools[tool]['solid_geometry']) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -2160,19 +2162,16 @@ class Geometry(object): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.solid_geometry: - self.geo_len += 1 + self.geo_len = len(self.solid_geometry) except TypeError: self.geo_len = 1 self.old_disp_number = 0 self.el_count = 0 self.solid_geometry = mirror_geom(self.solid_geometry) - self.app.inform.emit('[success] %s...' % - _('Object was mirrored')) + self.app.inform.emit('[success] %s...' % _('Object was mirrored')) except AttributeError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to mirror. No object selected")) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to mirror. No object selected")) self.app.proc_container.new_text = '' @@ -2180,29 +2179,28 @@ class Geometry(object): """ Rotate an object by an angle (in degrees) around the provided coordinates. - Parameters - ---------- + :param angle: The angle of rotation are specified in degrees (default). Positive angles are counter-clockwise and negative are clockwise rotations. + :param point: The point of origin can be a keyword 'center' for the bounding box center (default), 'centroid' for the geometry's centroid, a Point object or a coordinate tuple (x0, y0). - See shapely manual for more information: - http://toblerity.org/shapely/manual.html#affine-transformations + See shapely manual for more information: http://toblerity.org/shapely/manual.html#affine-transformations """ log.debug("camlib.Geometry.rotate()") px, py = point def rotate_geom(obj): - if type(obj) is list: + try: new_obj = [] for g in obj: new_obj.append(rotate_geom(g)) return new_obj - else: + except TypeError: try: self.el_count += 1 disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100])) @@ -2220,8 +2218,7 @@ class Geometry(object): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.tools[tool]['solid_geometry']: - self.geo_len += 1 + self.geo_len = len(self.tools[tool]['solid_geometry']) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -2232,19 +2229,16 @@ class Geometry(object): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.solid_geometry: - self.geo_len += 1 + self.geo_len = len(self.solid_geometry) except TypeError: self.geo_len = 1 self.old_disp_number = 0 self.el_count = 0 self.solid_geometry = rotate_geom(self.solid_geometry) - self.app.inform.emit('[success] %s...' % - _('Object was rotated')) + self.app.inform.emit('[success] %s...' % _('Object was rotated')) except AttributeError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to rotate. No object selected")) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to rotate. No object selected")) self.app.proc_container.new_text = '' @@ -2252,28 +2246,29 @@ class Geometry(object): """ Shear/Skew the geometries of an object by angles along x and y dimensions. - Parameters - ---------- + :param angle_x: + :param angle_y: angle_x, angle_y : float, float The shear angle(s) for the x and y axes respectively. These can be specified in either degrees (default) or radians by setting use_radians=True. + + :param point: Origin point for Skew point: tuple of coordinates (x,y) - See shapely manual for more information: - http://toblerity.org/shapely/manual.html#affine-transformations + See shapely manual for more information: http://toblerity.org/shapely/manual.html#affine-transformations """ log.debug("camlib.Geometry.skew()") px, py = point def skew_geom(obj): - if type(obj) is list: + try: new_obj = [] for g in obj: new_obj.append(skew_geom(g)) return new_obj - else: + except TypeError: try: self.el_count += 1 disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100])) @@ -2291,8 +2286,7 @@ class Geometry(object): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.tools[tool]['solid_geometry']: - self.geo_len += 1 + self.geo_len = len(self.tools[tool]['solid_geometry']) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -2310,11 +2304,9 @@ class Geometry(object): self.el_count = 0 self.solid_geometry = skew_geom(self.solid_geometry) - self.app.inform.emit('[success] %s...' % - _('Object was skewed')) + self.app.inform.emit('[success] %s...' % _('Object was skewed')) except AttributeError: - self.app.inform.emit('[ERROR_NOTCL] %s' % - _("Failed to skew. No object selected")) + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed to skew. No object selected")) self.app.proc_container.new_text = '' @@ -2328,8 +2320,9 @@ class Geometry(object): def buffer(self, distance, join, factor): """ - :param distance: if 'factor' is True then distance is the factor - :param factor: True or False (None) + :param distance: if 'factor' is True then distance is the factor + :param join: The kind of join used by the shapely buffer method: round, square or bevel + :param factor: True or False (None) :return: """ @@ -2477,6 +2470,8 @@ class CNCjob(Geometry): self.multidepth = False self.z_depthpercut = depthpercut + self.extracut_length = None + self.excellon_optimization_type = 'B' # if set True then the GCode generation will use UI; used in Excellon GVode for now @@ -2779,7 +2774,7 @@ class CNCjob(Geometry): for drill in exobj.drills: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if drill['tool'] in tools: try: @@ -2813,11 +2808,11 @@ class CNCjob(Geometry): def __init__(self, tool): """Initialize distance array.""" - locations = create_data_array(tool) + locs = create_data_array(tool) self.matrix = {} - if locations: - size = len(locations) + if locs: + size = len(locs) for from_node in range(size): self.matrix[from_node] = {} @@ -2825,10 +2820,10 @@ class CNCjob(Geometry): if from_node == to_node: self.matrix[from_node][to_node] = 0 else: - x1 = locations[from_node][0] - y1 = locations[from_node][1] - x2 = locations[to_node][0] - y2 = locations[to_node][1] + x1 = locs[from_node][0] + y1 = locs[from_node][1] + x2 = locs[to_node][0] + y2 = locs[to_node][1] self.matrix[from_node][to_node] = distance_euclidian(x1, y1, x2, y2) # def Distance(self, from_node, to_node): @@ -2846,8 +2841,8 @@ class CNCjob(Geometry): if tool not in points: return None - for point in points[tool]: - loc_list.append((point.coords.xy[0][0], point.coords.xy[1][0])) + for pt in points[tool]: + loc_list.append((pt.coords.xy[0][0], pt.coords.xy[1][0])) return loc_list if self.xy_toolchange is not None: @@ -2873,7 +2868,7 @@ class CNCjob(Geometry): for tool in tools: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace self.tool = tool self.postdata['toolC'] = exobj.tools[tool]["C"] @@ -2973,7 +2968,7 @@ class CNCjob(Geometry): while not routing.IsEnd(node): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace node_list.append(node) node = assignment.Value(routing.NextVar(node)) @@ -2987,7 +2982,7 @@ class CNCjob(Geometry): if tool in points: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # Tool change sequence (optional) if self.toolchange: @@ -3031,7 +3026,7 @@ class CNCjob(Geometry): for k in node_list: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace locx = locations[k][0] locy = locations[k][1] @@ -3103,7 +3098,7 @@ class CNCjob(Geometry): for tool in tools: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace self.tool = tool self.postdata['toolC']=exobj.tools[tool]["C"] @@ -3206,7 +3201,7 @@ class CNCjob(Geometry): if tool in points: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # Tool change sequence (optional) if self.toolchange: @@ -3249,7 +3244,7 @@ class CNCjob(Geometry): for k in node_list: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace locx = locations[k][0] locy = locations[k][1] @@ -3323,7 +3318,7 @@ class CNCjob(Geometry): for tool in tools: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if exobj.drills: self.tool = tool @@ -3370,7 +3365,7 @@ class CNCjob(Geometry): if tool in points: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # Tool change sequence (optional) if self.toolchange: @@ -3418,7 +3413,7 @@ class CNCjob(Geometry): for point in node_list: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace locx = point[0] locy = point[1] @@ -3675,13 +3670,13 @@ class CNCjob(Geometry): log.debug("Indexing geometry before generating G-Code...") self.app.inform.emit(_("Indexing geometry before generating G-Code...")) - for shape in flat_geometry: + for geo_shape in flat_geometry: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace - if shape is not None: # TODO: This shouldn't have happened. - storage.insert(shape) + if geo_shape is not None: + storage.insert(geo_shape) # self.input_geometry_bounds = geometry.bounds() @@ -3757,7 +3752,7 @@ class CNCjob(Geometry): while True: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace path_count += 1 @@ -3839,14 +3834,31 @@ class CNCjob(Geometry): :param geometry: :param append: :param tooldia: + :param offset: :param tolerance: - :param multidepth: If True, use multiple passes to reach - the desired depth. - :param depthpercut: Maximum depth in each pass. - :param extracut: Adds (or not) an extra cut at the end of each path - overlapping the first point in path to ensure complete copper removal - :param extracut_length: The extra cut length - :return: None + :param z_cut: + :param z_move: + :param feedrate: + :param feedrate_z: + :param feedrate_rapid: + :param spindlespeed: + :param spindledir: + :param dwell: + :param dwelltime: + :param multidepth: If True, use multiple passes to reach the desired depth. + :param depthpercut: Maximum depth in each pass. + :param toolchange: + :param toolchangez: + :param toolchangexy: + :param extracut: Adds (or not) an extra cut at the end of each path overlapping the first point in + path to ensure complete copper removal + :param extracut_length: The extra cut length + :param startz: + :param endz: + :param endxy: + :param pp_geometry_name: + :param tool_no: + :return: None """ if not isinstance(geometry, Geometry): @@ -4049,13 +4061,13 @@ class CNCjob(Geometry): log.debug("Indexing geometry before generating G-Code...") self.app.inform.emit(_("Indexing geometry before generating G-Code...")) - for shape in flat_geometry: + for geo_shape in flat_geometry: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace - if shape is not None: # TODO: This shouldn't have happened. - storage.insert(shape) + if geo_shape is not None: + storage.insert(geo_shape) if not append: self.gcode = "" @@ -4080,7 +4092,7 @@ class CNCjob(Geometry): if toolchange is False: self.gcode += self.doformat(p.lift_code, x=self.oldx, y=self.oldy) # Move (up) to travel height - self.gcode += self.doformat(p.startz_code, x=self.oldx , y=self.oldy) + self.gcode += self.doformat(p.startz_code, x=self.oldx, y=self.oldy) if toolchange: # if "line_xyz" in self.pp_geometry_name: @@ -4130,7 +4142,7 @@ class CNCjob(Geometry): while True: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace path_count += 1 @@ -4219,7 +4231,6 @@ class CNCjob(Geometry): self.app.inform.emit('[ERROR_NOTCL] %s' % _("There is no tool data in the SolderPaste geometry.")) - # this is the tool diameter, it is used as such to accommodate the preprocessor who need the tool diameter # given under the name 'toolC' @@ -4254,9 +4265,13 @@ class CNCjob(Geometry): # Store the geometry log.debug("Indexing geometry before generating G-Code...") - for shape in flat_geometry: - if shape is not None: - storage.insert(shape) + for geo_shape in flat_geometry: + if self.app.abort_flag: + # graceful abort requested by the user + raise grace + + if geo_shape is not None: + storage.insert(geo_shape) # Initial G-Code self.gcode = self.doformat(p.start_code) @@ -4278,7 +4293,7 @@ class CNCjob(Geometry): while True: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace path_count += 1 @@ -4303,7 +4318,7 @@ class CNCjob(Geometry): log.debug("Finishing SolderPste G-Code... %s paths traced." % path_count) self.app.inform.emit( - '%s... %s %s' % (_("Finished SolderPste G-Code generation"), str(path_count), _("paths traced.")) + '%s... %s %s' % (_("Finished SolderPaste G-Code generation"), str(path_count), _("paths traced.")) ) # Finish @@ -5014,18 +5029,25 @@ class CNCjob(Geometry): z_cut=None, z_move=None, zdownrate=None, feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)): """ + Generates G-code to cut along the linear feature. - :param linear: The path to cut along. - :type: Shapely.LinearRing or Shapely.Linear String - :param tolerance: All points in the simplified object will be within the - tolerance distance of the original geometry. - :type tolerance: float - :param feedrate: speed for cut on X - Y plane - :param feedrate_z: speed for cut on Z plane - :param feedrate_rapid: speed to move between cuts; usually is G0 but some CNC require to specify it - :return: G-code to cut along the linear feature. - :rtype: str + :param linear: The path to cut along. + :type: Shapely.LinearRing or Shapely.Linear String + :param tolerance: All points in the simplified object will be within the + tolerance distance of the original geometry. + :type tolerance: float + :param down: + :param up: + :param z_cut: + :param z_move: + :param zdownrate: + :param feedrate: speed for cut on X - Y plane + :param feedrate_z: speed for cut on Z plane + :param feedrate_rapid: speed to move between cuts; usually is G0 but some CNC require to specify it + :param cont: + :param old_point: + :return: G-code to cut along the linear feature. """ if z_cut is None: @@ -5087,7 +5109,7 @@ class CNCjob(Geometry): for pt in path[1:]: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if self.coordinates_type == "G90": # For Absolute coordinates type G90 @@ -5112,22 +5134,30 @@ class CNCjob(Geometry): return gcode def linear2gcode_extra(self, linear, extracut_length, tolerance=0, down=True, up=True, - z_cut=None, z_move=None, zdownrate=None, - feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)): + z_cut=None, z_move=None, zdownrate=None, + feedrate=None, feedrate_z=None, feedrate_rapid=None, cont=False, old_point=(0, 0)): """ + Generates G-code to cut along the linear feature. - :param linear: The path to cut along. - :param extracut_length: how much to cut extra over the first point at the end of the path - :type: Shapely.LinearRing or Shapely.Linear String - :param tolerance: All points in the simplified object will be within the - tolerance distance of the original geometry. - :type tolerance: float - :param feedrate: speed for cut on X - Y plane - :param feedrate_z: speed for cut on Z plane - :param feedrate_rapid: speed to move between cuts; usually is G0 but some CNC require to specify it - :return: G-code to cut along the linear feature. - :rtype: str + :param linear: The path to cut along. + :type: Shapely.LinearRing or Shapely.Linear String + :param extracut_length: how much to cut extra over the first point at the end of the path + :param tolerance: All points in the simplified object will be within the + tolerance distance of the original geometry. + :type tolerance: float + :param down: + :param up: + :param z_cut: + :param z_move: + :param zdownrate: + :param feedrate: speed for cut on X - Y plane + :param feedrate_z: speed for cut on Z plane + :param feedrate_rapid: speed to move between cuts; usually is G0 but some CNC require to specify it + :param cont: + :param old_point: + :return: G-code to cut along the linear feature. + :rtype: str """ if z_cut is None: @@ -5190,7 +5220,7 @@ class CNCjob(Geometry): for pt in path[1:]: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if self.coordinates_type == "G90": # For Absolute coordinates type G90 @@ -5342,7 +5372,7 @@ class CNCjob(Geometry): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace path = list(point.coords) p = self.pp_geometry @@ -5401,7 +5431,7 @@ class CNCjob(Geometry): for g in self.gcode_parsed: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if g['kind'][0] == 'C': cuts.append(g) @@ -5417,7 +5447,7 @@ class CNCjob(Geometry): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if cuts: cutsgeom = cascaded_union([geo['geom'] for geo in cuts]) @@ -5433,38 +5463,38 @@ class CNCjob(Geometry): return svg_elem - def bounds(self): + def bounds(self, flatten=None): """ Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax). + + :param flatten: Not used, it is here for compatibility with base class method """ - # fixed issue of getting bounds only for one level lists of objects - # now it can get bounds for nested lists of objects log.debug("camlib.CNCJob.bounds()") def bounds_rec(obj): if type(obj) is list: - minx = np.Inf - miny = np.Inf - maxx = -np.Inf - maxy = -np.Inf + cminx = np.Inf + cminy = np.Inf + cmaxx = -np.Inf + cmaxy = -np.Inf for k in obj: if type(k) is dict: for key in k: minx_, miny_, maxx_, maxy_ = bounds_rec(k[key]) - minx = min(minx, minx_) - miny = min(miny, miny_) - maxx = max(maxx, maxx_) - maxy = max(maxy, maxy_) + cminx = min(cminx, minx_) + cminy = min(cminy, miny_) + cmaxx = max(cmaxx, maxx_) + cmaxy = max(cmaxy, maxy_) else: minx_, miny_, maxx_, maxy_ = bounds_rec(k) - minx = min(minx, minx_) - miny = min(miny, miny_) - maxx = max(maxx, maxx_) - maxy = max(maxy, maxy_) - return minx, miny, maxx, maxy + cminx = min(cminx, minx_) + cminy = min(cminy, miny_) + cmaxx = max(cmaxx, maxx_) + cmaxy = max(cmaxy, maxy_) + return cminx, cminy, cmaxx, cmaxy else: # it's a Shapely object, return it's bounds return obj.bounds @@ -5510,12 +5540,12 @@ class CNCjob(Geometry): given factor. Tool sizes, feedrates, or Z-axis dimensions are not altered. - :param factor: Number by which to scale the object. - :type factor: float - :param point: the (x,y) coords for the point of origin of scale - :type tuple of floats - :return: None - :rtype: None + :param factor: Number by which to scale the object. + :type factor: float + :param point: the (x,y) coords for the point of origin of scale + :type tuple of floats + :return: None + :rtype: None """ log.debug("camlib.CNCJob.scale()") @@ -5638,8 +5668,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.gcode_parsed: - self.geo_len += 1 + self.geo_len = len(self.gcode_parsed) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5667,8 +5696,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in v['gcode_parsed']: - self.geo_len += 1 + self.geo_len = len(v['gcode_parsed']) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5700,9 +5728,9 @@ class CNCjob(Geometry): g_offsetx_re, g_offsety_re, multitool, cnnc_tools are attributes of FlatCAMCNCJob class in camlib - :param vect: (x, y) offset vector. - :type vect: tuple - :return: None + :param vect: (x, y) offset vector. + :type vect: tuple + :return: None """ log.debug("camlib.CNCJob.offset()") @@ -5748,8 +5776,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.gcode_parsed: - self.geo_len += 1 + self.geo_len = len(self.gcode_parsed) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5777,8 +5804,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in v['gcode_parsed']: - self.geo_len += 1 + self.geo_len = len(v['gcode_parsed']) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5804,9 +5830,10 @@ class CNCjob(Geometry): def mirror(self, axis, point): """ - Mirror the geometrys of an object by an given axis around the coordinates of the 'point' - :param angle: - :param point: tupple of coordinates (x,y) + Mirror the geometry of an object by an given axis around the coordinates of the 'point' + + :param axis: Axis for Mirror + :param point: tuple of coordinates (x,y). Point of origin for Mirror :return: """ log.debug("camlib.CNCJob.mirror()") @@ -5817,8 +5844,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.gcode_parsed: - self.geo_len += 1 + self.geo_len = len(self.gcode_parsed) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5843,16 +5869,16 @@ class CNCjob(Geometry): """ Shear/Skew the geometries of an object by angles along x and y dimensions. - Parameters - ---------- + :param angle_x: + :param angle_y: angle_x, angle_y : float, float The shear angle(s) for the x and y axes respectively. These can be specified in either degrees (default) or radians by setting use_radians=True. - point: tupple of coordinates (x,y) - See shapely manual for more information: - http://toblerity.org/shapely/manual.html#affine-transformations + :param point: tupple of coordinates (x,y) + + See shapely manual for more information: http://toblerity.org/shapely/manual.html#affine-transformations """ log.debug("camlib.CNCJob.skew()") @@ -5861,8 +5887,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.gcode_parsed: - self.geo_len += 1 + self.geo_len = len(self.gcode_parsed) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5885,9 +5910,10 @@ class CNCjob(Geometry): def rotate(self, angle, point): """ - Rotate the geometrys of an object by an given angle around the coordinates of the 'point' - :param angle: - :param point: tupple of coordinates (x,y) + Rotate the geometry of an object by an given angle around the coordinates of the 'point' + + :param angle: Angle of Rotation + :param point: tuple of coordinates (x,y). Origin point for Rotation :return: """ log.debug("camlib.CNCJob.rotate()") @@ -5897,8 +5923,7 @@ class CNCjob(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.gcode_parsed: - self.geo_len += 1 + self.geo_len = len(self.gcode_parsed) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -5921,6 +5946,12 @@ class CNCjob(Geometry): def get_bounds(geometry_list): + """ + Will return limit values for a list of geometries + + :param geometry_list: List of geometries for which to calculate the bounds limits + :return: + """ xmin = np.Inf ymin = np.Inf xmax = -np.Inf @@ -5943,21 +5974,21 @@ def arc(center, radius, start, stop, direction, steps_per_circ): """ Creates a list of point along the specified arc. - :param center: Coordinates of the center [x, y] - :type center: list - :param radius: Radius of the arc. - :type radius: float - :param start: Starting angle in radians - :type start: float - :param stop: End angle in radians - :type stop: float - :param direction: Orientation of the arc, "CW" or "CCW" - :type direction: string - :param steps_per_circ: Number of straight line segments to - represent a circle. - :type steps_per_circ: int - :return: The desired arc, as list of tuples - :rtype: list + :param center: Coordinates of the center [x, y] + :type center: list + :param radius: Radius of the arc. + :type radius: float + :param start: Starting angle in radians + :type start: float + :param stop: End angle in radians + :type stop: float + :param direction: Orientation of the arc, "CW" or "CCW" + :type direction: string + :param steps_per_circ: Number of straight line segments to + represent a circle. + :type steps_per_circ: int + :return: The desired arc, as list of tuples + :rtype: list """ # TODO: Resolution should be established by maximum error from the exact arc. @@ -6031,10 +6062,10 @@ def to_dict(obj): * ApertureMacro * BaseGeometry - :param obj: Shapely geometry. - :type obj: BaseGeometry - :return: Dictionary with serializable form if ``obj`` was - BaseGeometry or ApertureMacro, otherwise returns ``obj``. + :param obj: Shapely geometry. + :type obj: BaseGeometry + :return: Dictionary with serializable form if ``obj`` was + BaseGeometry or ApertureMacro, otherwise returns ``obj``. """ if isinstance(obj, ApertureMacro): return { @@ -6053,9 +6084,9 @@ def dict2obj(d): """ Default deserializer. - :param d: Serializable dictionary representation of an object - to be reconstructed. - :return: Reconstructed object. + :param d: Serializable dictionary representation of an object + to be reconstructed. + :return: Reconstructed object. """ if '__class__' in d and '__inst__' in d: if d['__class__'] == "Shply": @@ -6390,10 +6421,10 @@ def three_point_circle(p1, p2, p3): Computes the center and radius of a circle from 3 points on its circumference. - :param p1: Point 1 - :param p2: Point 2 - :param p3: Point 3 - :return: center, radius + :param p1: Point 1 + :param p2: Point 2 + :param p3: Point 3 + :return: center, radius """ # Midpoints a1 = (p1 + p2) / 2.0 diff --git a/defaults.py b/defaults.py new file mode 100644 index 00000000..3d880db0 --- /dev/null +++ b/defaults.py @@ -0,0 +1,844 @@ +import os +import stat +import sys +from copy import deepcopy +from FlatCAMCommon import LoudDict +from camlib import to_dict, CNCjob, Geometry +import simplejson +import logging +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +from flatcamParsers.ParseExcellon import Excellon +from flatcamParsers.ParseGerber import Gerber + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext +log = logging.getLogger('FlatCAMDefaults') + + +class FlatCAMDefaults: + + factory_defaults = { + # Global APP Preferences + "decimals_inch": 4, + "decimals_metric": 4, + "version": 8.992, # defaults format version, not necessarily equal to app version + "first_run": True, + "units": "MM", + "global_serial": 0, + "global_stats": dict(), + "global_tabs_detachable": True, + "global_jump_ref": 'abs', + "global_locate_pt": 'bl', + "global_tpdf_tmargin": 15.0, + "global_tpdf_bmargin": 10.0, + "global_tpdf_lmargin": 20.0, + "global_tpdf_rmargin": 20.0, + "global_autosave": False, + "global_autosave_timeout": 300000, + + # General + "global_graphic_engine": '3D', + "global_app_level": 'b', + "global_portable": False, + "global_language": 'English', + "global_version_check": True, + "global_send_stats": True, + "global_pan_button": '2', + "global_mselect_key": 'Control', + "global_project_at_startup": False, + "global_systray_icon": True, + "global_project_autohide": True, + "global_toggle_tooltips": True, + "global_worker_number": 2, + "global_tolerance": 0.005, + "global_open_style": True, + "global_delete_confirmation": True, + "global_compression_level": 3, + "global_save_compressed": True, + + "global_machinist_setting": False, + + # Global GUI Preferences + "global_gridx": 1.0, + "global_gridy": 1.0, + "global_snap_max": 0.05, + "global_workspace": False, + "global_workspaceT": "A4", + "global_workspace_orientation": 'p', + + "global_grid_context_menu": { + 'in': [0.01, 0.02, 0.025, 0.05, 0.1], + 'mm': [0.1, 0.2, 0.5, 1, 2.54] + }, + + "global_sel_fill": '#a5a5ffbf', + "global_sel_line": '#0000ffbf', + "global_alt_sel_fill": '#BBF268BF', + "global_alt_sel_line": '#006E20BF', + "global_draw_color": '#FF0000', + "global_sel_draw_color": '#0000FF', + "global_proj_item_color": '#000000', + "global_proj_item_dis_color": '#b7b7cb', + "global_activity_icon": 'Ball green', + + "global_toolbar_view": 511, + + "global_background_timeout": 300000, # Default value is 5 minutes + "global_verbose_error_level": 0, # Shell verbosity 0 = default + # (python trace only for unknown errors), + # 1 = show trace(show trace always), + # 2 = (For the future). + + # Persistence + "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. + "global_shell_at_startup": False, # Show the shell at startup. + "global_recent_limit": 10, # Max. items in recent list. + + "global_bookmarks": dict(), + "global_bookmarks_limit": 10, + + "fit_key": 'V', + "zoom_out_key": '-', + "zoom_in_key": '=', + "grid_toggle_key": 'G', + "global_zoom_ratio": 1.5, + "global_point_clipboard_format": "(%.*f, %.*f)", + "global_zdownrate": None, + + "global_tcl_path": '', + + # General GUI Settings + "global_theme": 'white', + "global_gray_icons": False, + "global_hover": False, + "global_selection_shape": True, + "global_layout": "compact", + "global_cursor_type": "small", + "global_cursor_size": 20, + "global_cursor_width": 2, + "global_cursor_color": '#FF0000', + "global_cursor_color_enabled": True, + + # Gerber General + "gerber_plot": True, + "gerber_solid": True, + "gerber_multicolored": False, + "gerber_circle_steps": 64, + "gerber_use_buffer_for_union": True, + "gerber_clean_apertures": True, + "gerber_extra_buffering": True, + + "gerber_plot_fill": '#BBF268BF', + "gerber_plot_line": '#006E20BF', + + "gerber_def_units": 'IN', + "gerber_def_zeros": 'L', + "gerber_save_filters": "Gerber File .gbr (*.gbr);;Gerber File .bot (*.bot);;Gerber File .bsm (*.bsm);;" + "Gerber File .cmp (*.cmp);;Gerber File .crc (*.crc);;Gerber File .crs (*.crs);;" + "Gerber File .gb0 (*.gb0);;Gerber File .gb1 (*.gb1);;Gerber File .gb2 (*.gb2);;" + "Gerber File .gb3 (*.gb3);;Gerber File .gb4 (*.gb4);;Gerber File .gb5 (*.gb5);;" + "Gerber File .gb6 (*.gb6);;Gerber File .gb7 (*.gb7);;Gerber File .gb8 (*.gb8);;" + "Gerber File .gb9 (*.gb9);;Gerber File .gbd (*.gbd);;Gerber File .gbl (*.gbl);;" + "Gerber File .gbo (*.gbo);;Gerber File .gbp (*.gbp);;Gerber File .gbs (*.gbs);;" + "Gerber File .gdo (*.gdo);;Gerber File .ger (*.ger);;Gerber File .gko (*.gko);;" + "Gerber File .gm1 (*.gm1);;Gerber File .gm2 (*.gm2);;Gerber File .gm3 (*.gm3);;" + "Gerber File .grb (*.grb);;Gerber File .gtl (*.gtl);;Gerber File .gto (*.gto);;" + "Gerber File .gtp (*.gtp);;Gerber File .gts (*.gts);;Gerber File .ly15 (*.ly15);;" + "Gerber File .ly2 (*.ly2);;Gerber File .mil (*.mil);;" + "Gerber File .outline (*.outline);;Gerber File .pho (*.pho);;" + "Gerber File .plc (*.plc);;Gerber File .pls (*.pls);;Gerber File .smb (*.smb);;" + "Gerber File .smt (*.smt);;Gerber File .sol (*.sol);;Gerber File .spb (*.spb);;" + "Gerber File .spt (*.spt);;Gerber File .ssb (*.ssb);;Gerber File .sst (*.sst);;" + "Gerber File .stc (*.stc);;Gerber File .sts (*.sts);;Gerber File .top (*.top);;" + "Gerber File .tsm (*.tsm);;Gerber File .art (*.art)" + "All Files (*.*)", + + # Gerber Options + "gerber_isotooldia": 0.1, + "gerber_isopasses": 1, + "gerber_isooverlap": 10, + "gerber_milling_type": "cl", + "gerber_combine_passes": False, + "gerber_iso_scope": 'all', + "gerber_noncoppermargin": 0.1, + "gerber_noncopperrounded": False, + "gerber_bboxmargin": 0.1, + "gerber_bboxrounded": False, + + # Gerber Advanced Options + "gerber_aperture_display": False, + "gerber_aperture_scale_factor": 1.0, + "gerber_aperture_buffer_factor": 0.0, + "gerber_follow": False, + "gerber_tool_type": 'circular', + "gerber_vtipdia": 0.1, + "gerber_vtipangle": 30, + "gerber_vcutz": -0.05, + "gerber_iso_type": "full", + "gerber_buffering": "full", + "gerber_simplification": False, + "gerber_simp_tolerance": 0.0005, + + # Gerber Export + "gerber_exp_units": 'IN', + "gerber_exp_integer": 2, + "gerber_exp_decimals": 4, + "gerber_exp_zeros": 'L', + + # Gerber Editor + "gerber_editor_sel_limit": 30, + "gerber_editor_newcode": 10, + "gerber_editor_newsize": 0.8, + "gerber_editor_newtype": 'C', + "gerber_editor_newdim": "0.5, 0.5", + "gerber_editor_array_size": 5, + "gerber_editor_lin_axis": 'X', + "gerber_editor_lin_pitch": 0.1, + "gerber_editor_lin_angle": 0.0, + "gerber_editor_circ_dir": 'CW', + "gerber_editor_circ_angle": 0.0, + "gerber_editor_scale_f": 1.0, + "gerber_editor_buff_f": 0.1, + "gerber_editor_ma_low": 0.0, + "gerber_editor_ma_high": 1.0, + + # Excellon General + "excellon_plot": True, + "excellon_solid": True, + "excellon_format_upper_in": 2, + "excellon_format_lower_in": 4, + "excellon_format_upper_mm": 3, + "excellon_format_lower_mm": 3, + "excellon_zeros": "L", + "excellon_units": "INCH", + "excellon_update": True, + + "excellon_optimization_type": 'B', + + "excellon_search_time": 3, + "excellon_save_filters": "Excellon File .txt (*.txt);;Excellon File .drd (*.drd);;" + "Excellon File .drill (*.drill);;" + "Excellon File .drl (*.drl);;Excellon File .exc (*.exc);;" + "Excellon File .ncd (*.ncd);;Excellon File .tap (*.tap);;" + "Excellon File .xln (*.xln);;All Files (*.*)", + "excellon_plot_fill": '#C40000BF', + "excellon_plot_line": '#750000BF', + + # Excellon Options + "excellon_operation": "drill", + "excellon_milling_type": "drills", + + "excellon_milling_dia": 0.8, + + "excellon_cutz": -1.7, + "excellon_multidepth": False, + "excellon_depthperpass": 0.7, + "excellon_travelz": 2, + "excellon_endz": 0.5, + "excellon_endxy": None, + "excellon_feedrate_z": 300, + "excellon_spindlespeed": 0, + "excellon_dwell": False, + "excellon_dwelltime": 1, + "excellon_toolchange": False, + "excellon_toolchangez": 15, + "excellon_ppname_e": 'default', + "excellon_tooldia": 0.8, + "excellon_slot_tooldia": 1.8, + "excellon_gcode_type": "drills", + + # Excellon Advanced Options + "excellon_offset": 0.0, + "excellon_toolchangexy": "0.0, 0.0", + "excellon_startz": None, + "excellon_feedrate_rapid": 1500, + "excellon_z_pdepth": -0.02, + "excellon_feedrate_probe": 75, + "excellon_spindledir": 'CW', + "excellon_f_plunge": False, + "excellon_f_retract": False, + + # Excellon Export + "excellon_exp_units": 'INCH', + "excellon_exp_format": 'ndec', + "excellon_exp_integer": 2, + "excellon_exp_decimals": 4, + "excellon_exp_zeros": 'LZ', + "excellon_exp_slot_type": 'routing', + + # Excellon Editor + "excellon_editor_sel_limit": 30, + "excellon_editor_newdia": 1.0, + "excellon_editor_array_size": 5, + "excellon_editor_lin_dir": 'X', + "excellon_editor_lin_pitch": 2.54, + "excellon_editor_lin_angle": 0.0, + "excellon_editor_circ_dir": 'CW', + "excellon_editor_circ_angle": 12, + # Excellon Slots + "excellon_editor_slot_direction": 'X', + "excellon_editor_slot_angle": 0.0, + "excellon_editor_slot_length": 5.0, + # Excellon Slot Array + "excellon_editor_slot_array_size": 5, + "excellon_editor_slot_lin_dir": 'X', + "excellon_editor_slot_lin_pitch": 2.54, + "excellon_editor_slot_lin_angle": 0.0, + "excellon_editor_slot_circ_dir": 'CW', + "excellon_editor_slot_circ_angle": 0.0, + + # Geometry General + "geometry_plot": True, + "geometry_circle_steps": 64, + "geometry_cnctooldia": "2.4", + "geometry_plot_line": "#FF0000", + + # Geometry Options + "geometry_cutz": -2.4, + "geometry_vtipdia": 0.1, + "geometry_vtipangle": 30, + "geometry_multidepth": False, + "geometry_depthperpass": 0.8, + "geometry_travelz": 2, + "geometry_toolchange": False, + "geometry_toolchangez": 15.0, + "geometry_endz": 15.0, + "geometry_endxy": None, + + "geometry_feedrate": 120, + "geometry_feedrate_z": 60, + "geometry_spindlespeed": 0, + "geometry_dwell": False, + "geometry_dwelltime": 1, + "geometry_ppname_g": 'default', + + # Geometry Advanced Options + "geometry_toolchangexy": "0.0, 0.0", + "geometry_startz": None, + "geometry_feedrate_rapid": 1500, + "geometry_extracut": False, + "geometry_extracut_length": 0.1, + "geometry_z_pdepth": -0.02, + "geometry_f_plunge": False, + "geometry_spindledir": 'CW', + "geometry_feedrate_probe": 75, + "geometry_segx": 0.0, + "geometry_segy": 0.0, + + # Geometry Editor + "geometry_editor_sel_limit": 30, + "geometry_editor_milling_type": "cl", + + # CNC Job General + "cncjob_plot": True, + "cncjob_plot_kind": 'all', + "cncjob_annotation": True, + "cncjob_tooldia": 1.0, + "cncjob_coords_type": "G90", + "cncjob_coords_decimals": 4, + "cncjob_fr_decimals": 2, + "cncjob_steps_per_circle": 64, + "cncjob_footer": False, + "cncjob_line_ending": False, + "cncjob_save_filters": "G-Code Files .nc (*.nc);;G-Code Files .din (*.din);;G-Code Files .dnc (*.dnc);;" + "G-Code Files .ecs (*.ecs);;G-Code Files .eia (*.eia);;G-Code Files .fan (*.fan);;" + "G-Code Files .fgc (*.fgc);;G-Code Files .fnc (*.fnc);;G-Code Files . gc (*.gc);;" + "G-Code Files .gcd (*.gcd);;G-Code Files .gcode (*.gcode);;G-Code Files .h (*.h);;" + "G-Code Files .hnc (*.hnc);;G-Code Files .i (*.i);;G-Code Files .min (*.min);;" + "G-Code Files .mpf (*.mpf);;G-Code Files .mpr (*.mpr);;G-Code Files .cnc (*.cnc);;" + "G-Code Files .ncc (*.ncc);;G-Code Files .ncg (*.ncg);;G-Code Files .ncp (*.ncp);;" + "G-Code Files .ngc (*.ngc);;G-Code Files .out (*.out);;G-Code Files .ply (*.ply);;" + "G-Code Files .sbp (*.sbp);;G-Code Files .tap (*.tap);;G-Code Files .xpi (*.xpi);;" + "All Files (*.*)", + "cncjob_plot_line": '#4650BDFF', + "cncjob_plot_fill": '#5E6CFFFF', + "cncjob_travel_line": '#B5AB3A4C', + "cncjob_travel_fill": '#F0E24D4C', + + # CNC Job Options + "cncjob_prepend": "", + "cncjob_append": "", + + # CNC Job Advanced Options + "cncjob_toolchange_macro": "", + "cncjob_toolchange_macro_enable": False, + "cncjob_annotation_fontsize": 9, + "cncjob_annotation_fontcolor": '#990000', + + # NCC Tool + "tools_ncctools": "1.0, 0.5", + "tools_nccorder": 'rev', + "tools_nccoperation": 'clear', + "tools_nccoverlap": 40, + "tools_nccmargin": 1.0, + "tools_nccmethod": _("Seed"), + "tools_nccconnect": True, + "tools_ncccontour": True, + "tools_nccrest": False, + "tools_ncc_offset_choice": False, + "tools_ncc_offset_value": 0.0000, + "tools_nccref": _('Itself'), + "tools_ncc_area_shape": "square", + "tools_ncc_plotting": 'normal', + "tools_nccmilling_type": 'cl', + "tools_ncctool_type": 'C1', + "tools_ncccutz": -0.05, + "tools_ncctipdia": 0.1, + "tools_ncctipangle": 30, + "tools_nccnewdia": 0.1, + + # Cutout Tool + "tools_cutouttooldia": 2.4, + "tools_cutoutkind": "single", + "tools_cutoutmargin": 0.1, + "tools_cutout_z": -1.8, + "tools_cutout_depthperpass": 0.6, + "tools_cutout_mdepth": True, + "tools_cutoutgapsize": 4, + "tools_gaps_ff": "4", + "tools_cutout_convexshape": False, + + # Paint Tool + "tools_painttooldia": 0.3, + "tools_paintorder": 'rev', + "tools_paintoverlap": 20, + "tools_paintmargin": 0.0, + "tools_paintmethod": _("Seed"), + "tools_selectmethod": _("All Polygons"), + "tools_paint_area_shape": "square", + "tools_pathconnect": True, + "tools_paintcontour": True, + "tools_paint_plotting": 'normal', + "tools_paintrest": False, + "tools_painttool_type": 'C1', + "tools_paintcutz": -0.05, + "tools_painttipdia": 0.1, + "tools_painttipangle": 30, + "tools_paintnewdia": 0.1, + + # 2-Sided Tool + "tools_2sided_mirror_axis": "X", + "tools_2sided_axis_loc": "point", + "tools_2sided_drilldia": 3.125, + "tools_2sided_allign_axis": "X", + + # Film Tool + "tools_film_type": 'neg', + "tools_film_boundary": 1.0, + "tools_film_scale_stroke": 0, + "tools_film_color": '#000000', + "tools_film_scale_cb": False, + "tools_film_scale_x_entry": 1.0, + "tools_film_scale_y_entry": 1.0, + "tools_film_skew_cb": False, + "tools_film_skew_x_entry": 0.0, + "tools_film_skew_y_entry": 0.0, + "tools_film_skew_ref_radio": 'bottomleft', + "tools_film_mirror_cb": False, + "tools_film_mirror_axis_radio": 'none', + "tools_film_file_type_radio": 'svg', + "tools_film_orientation": 'p', + "tools_film_pagesize": 'A4', + + # Panel Tool + "tools_panelize_spacing_columns": 0, + "tools_panelize_spacing_rows": 0, + "tools_panelize_columns": 1, + "tools_panelize_rows": 1, + "tools_panelize_constrain": False, + "tools_panelize_constrainx": 200.0, + "tools_panelize_constrainy": 290.0, + "tools_panelize_panel_type": 'gerber', + + # Calculators Tool + "tools_calc_vshape_tip_dia": 0.2, + "tools_calc_vshape_tip_angle": 30, + "tools_calc_vshape_cut_z": 0.05, + "tools_calc_electro_length": 10.0, + "tools_calc_electro_width": 10.0, + "tools_calc_electro_cdensity": 13.0, + "tools_calc_electro_growth": 10.0, + + # Transform Tool + "tools_transform_rotate": 90, + "tools_transform_skew_x": 0.0, + "tools_transform_skew_y": 0.0, + "tools_transform_scale_x": 1.0, + "tools_transform_scale_y": 1.0, + "tools_transform_scale_link": True, + "tools_transform_scale_reference": True, + "tools_transform_offset_x": 0.0, + "tools_transform_offset_y": 0.0, + "tools_transform_mirror_reference": False, + "tools_transform_mirror_point": (0, 0), + "tools_transform_buffer_dis": 0.0, + "tools_transform_buffer_factor": 100.0, + "tools_transform_buffer_corner": True, + + # SolderPaste Tool + "tools_solderpaste_tools": "1.0, 0.3", + "tools_solderpaste_new": 0.3, + "tools_solderpaste_z_start": 0.05, + "tools_solderpaste_z_dispense": 0.1, + "tools_solderpaste_z_stop": 0.05, + "tools_solderpaste_z_travel": 0.1, + "tools_solderpaste_z_toolchange": 1.0, + "tools_solderpaste_xy_toolchange": "0.0, 0.0", + "tools_solderpaste_frxy": 150, + "tools_solderpaste_frz": 150, + "tools_solderpaste_frz_dispense": 1.0, + "tools_solderpaste_speedfwd": 300, + "tools_solderpaste_dwellfwd": 1, + "tools_solderpaste_speedrev": 200, + "tools_solderpaste_dwellrev": 1, + "tools_solderpaste_pp": 'Paste_1', + + # Subtract Tool + "tools_sub_close_paths": True, + + # Distance Tool + "tools_dist_snap_center": False, + + # ######################################################################################################## + # ################################ TOOLS 2 ############################################################### + # ######################################################################################################## + + # Optimal Tool + "tools_opt_precision": 4, + + # Check Rules Tool + "tools_cr_trace_size": True, + "tools_cr_trace_size_val": 0.25, + "tools_cr_c2c": True, + "tools_cr_c2c_val": 0.25, + "tools_cr_c2o": True, + "tools_cr_c2o_val": 1.0, + "tools_cr_s2s": True, + "tools_cr_s2s_val": 0.25, + "tools_cr_s2sm": True, + "tools_cr_s2sm_val": 0.25, + "tools_cr_s2o": True, + "tools_cr_s2o_val": 1.0, + "tools_cr_sm2sm": True, + "tools_cr_sm2sm_val": 0.25, + "tools_cr_ri": True, + "tools_cr_ri_val": 0.3, + "tools_cr_h2h": True, + "tools_cr_h2h_val": 0.3, + "tools_cr_dh": True, + "tools_cr_dh_val": 0.3, + + # QRCode Tool + "tools_qrcode_version": 1, + "tools_qrcode_error": 'L', + "tools_qrcode_box_size": 3, + "tools_qrcode_border_size": 4, + "tools_qrcode_qrdata": '', + "tools_qrcode_polarity": 'pos', + "tools_qrcode_rounded": 's', + "tools_qrcode_fill_color": '#000000', + "tools_qrcode_back_color": '#FFFFFF', + "tools_qrcode_sel_limit": 330, + + # Copper Thieving Tool + "tools_copper_thieving_clearance": 0.25, + "tools_copper_thieving_margin": 1.0, + "tools_copper_thieving_reference": 'itself', + "tools_copper_thieving_box_type": 'rect', + "tools_copper_thieving_circle_steps": 64, + "tools_copper_thieving_fill_type": 'solid', + "tools_copper_thieving_dots_dia": 1.0, + "tools_copper_thieving_dots_spacing": 2.0, + "tools_copper_thieving_squares_size": 1.0, + "tools_copper_thieving_squares_spacing": 2.0, + "tools_copper_thieving_lines_size": 0.25, + "tools_copper_thieving_lines_spacing": 2.0, + "tools_copper_thieving_rb_margin": 1.0, + "tools_copper_thieving_rb_thickness": 1.0, + "tools_copper_thieving_mask_clearance": 0.0, + + # Fiducials Tool + "tools_fiducials_dia": 1.0, + "tools_fiducials_margin": 1.0, + "tools_fiducials_mode": 'auto', + "tools_fiducials_second_pos": 'up', + "tools_fiducials_type": 'circular', + "tools_fiducials_line_thickness": 0.25, + + # Calibration Tool + "tools_cal_calsource": 'object', + "tools_cal_travelz": 2.0, + "tools_cal_verz": 0.1, + "tools_cal_zeroz": False, + "tools_cal_toolchangez": 15, + "tools_cal_toolchange_xy": '', + "tools_cal_sec_point": 'tl', + + # Drills Extraction Tool + "tools_edrills_hole_type": 'fixed', + "tools_edrills_hole_fixed_dia": 0.5, + "tools_edrills_hole_prop_factor": 80.0, + "tools_edrills_circular_ring": 0.2, + "tools_edrills_oblong_ring": 0.2, + "tools_edrills_square_ring": 0.2, + "tools_edrills_rectangular_ring": 0.2, + "tools_edrills_others_ring": 0.2, + "tools_edrills_circular": True, + "tools_edrills_oblong": False, + "tools_edrills_square": False, + "tools_edrills_rectangular": False, + "tools_edrills_others": False, + + # Punch Gerber Tool + "tools_punch_hole_type": 'exc', + "tools_punch_hole_fixed_dia": 0.5, + "tools_punch_hole_prop_factor": 80.0, + "tools_punch_circular_ring": 0.2, + "tools_punch_oblong_ring": 0.2, + "tools_punch_square_ring": 0.2, + "tools_punch_rectangular_ring": 0.2, + "tools_punch_others_ring": 0.2, + "tools_punch_circular": True, + "tools_punch_oblong": False, + "tools_punch_square": True, + "tools_punch_rectangular": False, + "tools_punch_others": False, + + # Align Objects Tool + "tools_align_objects_align_type": 'sp', + + # Invert Gerber Tool + "tools_invert_margin": 0.1, + "tools_invert_join_style": 's', + + # Utilities + # file associations + "fa_excellon": 'drd, drill, drl, exc, ncd, tap, xln', + "fa_gcode": 'cnc, din, dnc, ecs, eia, fan, fgc, fnc, gc, gcd, gcode, h, hnc, i, min, mpf, mpr, nc, ncc, ' + 'ncg, ncp, ngc, out, ply, rol, sbp, tap, xpi', + "fa_gerber": 'art, bot, bsm, cmp, crc, crs, dim, gb0, gb1, gb2, gb3, gb4, gb5, gb6, gb7, gb8, gb9, gbd, ' + 'gbl, gbo, gbp, gbr, gbs, gdo, ger, gko, 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', + # Keyword list + "util_autocomplete_keywords": 'Desktop, Documents, FlatConfig, FlatPrj, False, ' + 'Marius, My Documents, Paste_1, ' + 'Repetier, Roland_MDX_20, True, Users, Toolchange_Custom, ' + 'Toolchange_Probe_MACH3, ' + 'Toolchange_manual, Users, all, axis, auto, axisoffset, ' + 'box, center_x, center_y, columns, combine, connect, contour, default, ' + 'depthperpass, dia, diatol, dist, drilled_dias, drillz, dpp, dwelltime, ' + 'endxy, endz, extracut_length, f, feedrate, ' + 'feedrate_z, grbl_11, GRBL_laser, gridoffsety, gridx, gridy, has_offset, ' + 'holes, hpgl, iso_type, line_xyz, margin, marlin, method, milled_dias, ' + 'minoffset, name, offset, opt_type, order, outname, overlap, ' + '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, ' + 'tooldia, travelz, use_threads, value, x, x0, x1, y, y0, y1, z_cut, ' + 'z_move', + "script_autocompleter": True, + "script_text": "", + "script_plot": True, + "script_source_file": "", + + "document_autocompleter": False, + "document_text": "", + "document_plot": True, + "document_source_file": "", + "document_font_color": '#000000', + "document_sel_color": '#0055ff', + "document_font_size": 6, + "document_tab_size": 80, + } + + @classmethod + def save_factory_defaults(cls, file_path: str): + """Writes the factory defaults to a file at the given path, overwriting any existing file.""" + # Delete any existing factory defaults file + if os.path.isfile(file_path): + os.chmod(file_path, stat.S_IRWXO | stat.S_IWRITE | stat.S_IWGRP) + os.remove(file_path) + + try: + # recreate a new factory defaults file and save the factory defaults data into it + f_f_def_s = open(file_path, "w") + simplejson.dump(cls.factory_defaults, f_f_def_s, default=to_dict, indent=2, sort_keys=True) + f_f_def_s.close() + + # and then make the factory_defaults.FlatConfig file read_only + # so it can't be modified after creation. + os.chmod(file_path, stat.S_IREAD | stat.S_IRGRP | stat.S_IROTH) + log.debug("FlatCAM factory defaults written to: %s" % file_path) + except Exception as e: + log.error("save_factory_defaults() -> %s" % str(e)) + + def __init__(self): + self.defaults = LoudDict() + self.defaults.update(self.factory_defaults) + self.current_defaults = {} # copy used for restoring after cancelled prefs changes + self.current_defaults.update(self.factory_defaults) + self.old_defaults_found = False + + # #### Pass-through to the defaults LoudDict ##### + def __len__(self): + return self.defaults.__len__() + + def __getitem__(self, item): + return self.defaults.__getitem__(item) + + def __setitem__(self, key, value): + return self.defaults.__setitem__(key, value) + + def __delitem__(self, key): + return self.defaults.__delitem__(key) + + def __iter__(self): + return self.defaults.__iter__() + + def __getattr__(self, item): + # Unfortunately this method alone is not enough to pass through the other magic methods above. + return self.defaults.__getattribute__(item) + + # #### Additional Methods ##### + def write(self, filename: str): + """Saves the defaults to a file on disk""" + with open(filename, "w") as file: + simplejson.dump(self.defaults, file, default=to_dict, indent=2, sort_keys=True) + + def load(self, filename: str): + """Loads the defaults from a file on disk, performing migration if required.""" + + # Read in the file + try: + f = open(filename) + options = f.read() + f.close() + except IOError: + log.error("Could not load defaults file.") + self.inform.emit('[ERROR] %s' % _("Could not load defaults 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)) + self.inform.emit('[ERROR] %s' % _("Failed to parse defaults file.")) + return + if defaults is None: + return + + # Perform migration if necessary + if self.__is_old_defaults(defaults): + self.old_defaults_found = True + defaults = self.__migrate_old_defaults(defaults=defaults) + else: + self.old_defaults_found = False + + # Save the resulting defaults + self.defaults.update(defaults) + self.current_defaults.update(self.defaults) + + log.debug("FlatCAM defaults loaded from: %s" % filename) + + def __is_old_defaults(self, defaults: dict) -> bool: + """Takes a defaults dict and determines whether or not migration is necessary.""" + return 'version' not in defaults or defaults['version'] != self.factory_defaults['version'] + + def __migrate_old_defaults(self, defaults: dict) -> dict: + """Performs migration on the passed-in defaults dictionary, and returns the migrated dict""" + migrated = {} + for k, v in defaults.items(): + if k in self.factory_defaults and k != 'version': + # check if the types are the same. Because some types (tuple, float, int etc) + # may be stored as strings we check their types. + try: + target = eval(self.defaults[k]) + except (NameError, TypeError, SyntaxError): + # it's an unknown string leave it as it is + target = deepcopy(self.factory_defaults[k]) + + try: + source = eval(v) + except (NameError, TypeError, SyntaxError): + # it's an unknown string leave it as it is + source = deepcopy(v) + + if type(target) == type(source): + migrated[k] = v + return migrated + + def reset_to_factory_defaults(self): + self.defaults.update(self.factory_defaults) + self.current_defaults.update(self.factory_defaults) + self.old_defaults_found = False + + def propagate_defaults(self): + """ + This method is used to set default values in classes. It's + an alternative to project options but allows the use + of values invisible to the user. + """ + log.debug("propagate_defaults()") + + # Which objects to update the given parameters. + routes = { + "global_zdownrate": CNCjob, + "excellon_zeros": Excellon, + "excellon_format_upper_in": Excellon, + "excellon_format_lower_in": Excellon, + "excellon_format_upper_mm": Excellon, + "excellon_format_lower_mm": Excellon, + "excellon_units": Excellon, + "gerber_use_buffer_for_union": Gerber, + "geometry_multidepth": Geometry + } + + for param in routes: + if param in routes[param].defaults: + try: + routes[param].defaults[param] = self.defaults[param] + except KeyError: + log.error("FlatCAMApp.propagate_defaults() --> ERROR: " + param + " not in defaults.") + else: + # Try extracting the name: + # classname_param here is param in the object + if param.find(routes[param].__name__.lower() + "_") == 0: + p = param[len(routes[param].__name__) + 1:] + if p in routes[param].defaults: + routes[param].defaults[p] = self.defaults[param] + + def report_usage(self, resource): + """ + Increments usage counter for the given resource + in self.defaults['global_stats']. + + :param resource: Name of the resource. + :return: None + """ + + if resource in self.defaults['global_stats']: + self.defaults['global_stats'][resource] += 1 + else: + self.defaults['global_stats'][resource] = 1 diff --git a/flatcamEditors/FlatCAMExcEditor.py b/flatcamEditors/FlatCAMExcEditor.py index 2baaac5d..9220aba5 100644 --- a/flatcamEditors/FlatCAMExcEditor.py +++ b/flatcamEditors/FlatCAMExcEditor.py @@ -12,7 +12,6 @@ from camlib import distance, arc, FlatCAMRTreeStorage from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, RadioSet, FCSpinner from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor from flatcamParsers.ParseExcellon import Excellon -import FlatCAMApp from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon, Point import shapely.affinity as affinity @@ -179,7 +178,7 @@ class FCDrillArray(FCShapeTool): try: QtGui.QGuiApplication.restoreOverrideCursor() - except Exception as e: + except Exception: pass self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_drill_array.png')) @@ -1516,7 +1515,7 @@ class FlatCAMExcEditor(QtCore.QObject): draw_shape_idx = -1 def __init__(self, app): - assert isinstance(app, FlatCAMApp.App), "Expected the app to be a FlatCAMApp.App, got %s" % type(app) + # assert isinstance(app, FlatCAMApp.App), "Expected the app to be a FlatCAMApp.App, got %s" % type(app) super(FlatCAMExcEditor, self).__init__() @@ -2230,8 +2229,8 @@ class FlatCAMExcEditor(QtCore.QObject): # store the status of the editor so the Delete at object level will not work until the edit is finished self.editor_active = False - def entry2option(option, entry): - self.options[option] = float(entry.text()) + # def entry2option(option, entry): + # self.options[option] = float(entry.text()) # Event signals disconnect id holders self.mp = None @@ -2388,7 +2387,7 @@ class FlatCAMExcEditor(QtCore.QObject): try: # Find no of slots for the current tool - for slot in self.slots: + for slot in self.slot_points_edit: if slot['tool'] == tool_no: slot_cnt += 1 @@ -2661,15 +2660,13 @@ class FlatCAMExcEditor(QtCore.QObject): # self.tools_table_exc.selectionModel().currentChanged.disconnect() self.is_modified = True - new_dia = None + # new_dia = None - if self.tools_table_exc.currentItem() is not None: - try: - new_dia = float(self.tools_table_exc.currentItem().text()) - except ValueError as e: - log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e)) - self.tools_table_exc.setCurrentItem(None) - return + try: + new_dia = float(self.tools_table_exc.currentItem().text()) + except ValueError as e: + log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e)) + return row_of_item_changed = self.tools_table_exc.currentRow() # rows start with 0, tools start with 1 so we adjust the value by 1 @@ -3042,7 +3039,7 @@ class FlatCAMExcEditor(QtCore.QObject): Imports the geometry from the given FlatCAM Excellon object into the editor. - :param exc_obj: FlatCAMExcellon object + :param exc_obj: ExcellonObject object :return: None """ @@ -3118,7 +3115,7 @@ class FlatCAMExcEditor(QtCore.QObject): """ Create a new Excellon object that contain the edited content of the source Excellon object - :param exc_obj: FlatCAMExcellon + :param exc_obj: ExcellonObject :return: None """ @@ -3297,7 +3294,8 @@ class FlatCAMExcEditor(QtCore.QObject): return self.edited_obj_name - def update_options(self, obj): + @staticmethod + def update_options(obj): try: if not obj.options: obj.options = {} @@ -3316,10 +3314,14 @@ class FlatCAMExcEditor(QtCore.QObject): """ Creates a new Excellon object for the edited Excellon. Thread-safe. - :param outname: Name of the resulting object. None causes the - name to be that of the file. - :type outname: str - :return: None + :param outname: Name of the resulting object. None causes the + name to be that of the file. + :type outname: str + + :param n_drills: The new Drills storage + :param n_slots: The new Slots storage + :param n_tools: The new Tools storage + :return: None """ self.app.log.debug("Update the Excellon object with edited content. Source is %s" % @@ -3429,12 +3431,12 @@ class FlatCAMExcEditor(QtCore.QObject): self.replot() - def toolbar_tool_toggle(self, key): - self.options[key] = self.sender().isChecked() - if self.options[key] is True: - return 1 - else: - return 0 + # def toolbar_tool_toggle(self, key): + # self.options[key] = self.sender().isChecked() + # if self.options[key] is True: + # return 1 + # else: + # return 0 def on_canvas_click(self, event): """ @@ -3446,12 +3448,12 @@ class FlatCAMExcEditor(QtCore.QObject): """ if self.app.is_legacy is False: event_pos = event.pos - event_is_dragging = event.is_dragging - right_button = 2 + # event_is_dragging = event.is_dragging + # right_button = 2 else: event_pos = (event.xdata, event.ydata) - event_is_dragging = self.app.plotcanvas.is_dragging - right_button = 3 + # event_is_dragging = self.app.plotcanvas.is_dragging + # right_button = 3 self.pos = self.canvas.translate_coords(event_pos) @@ -3575,8 +3577,8 @@ class FlatCAMExcEditor(QtCore.QObject): if isinstance(shape, DrawToolUtilityShape): self.utility.append(shape) - else: - self.storage.insert(shape) # TODO: Check performance + # else: + # self.storage.insert(shape) def on_exc_click_release(self, event): """ @@ -3591,11 +3593,11 @@ class FlatCAMExcEditor(QtCore.QObject): if self.app.is_legacy is False: event_pos = event.pos - event_is_dragging = event.is_dragging + # event_is_dragging = event.is_dragging right_button = 2 else: event_pos = (event.xdata, event.ydata) - event_is_dragging = self.app.plotcanvas.is_dragging + # event_is_dragging = self.app.plotcanvas.is_dragging right_button = 3 pos_canvas = self.canvas.translate_coords(event_pos) @@ -4027,7 +4029,7 @@ class FlatCAMExcEditor(QtCore.QObject): del self.slot_points_edit[storage][0] if del_shape in self.selected: - self.selected.remove(del_shape) # TODO: Check performance + self.selected.remove(del_shape) def delete_utility_geometry(self): for_deletion = [util_shape for util_shape in self.utility] diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py index a3761201..4716f831 100644 --- a/flatcamEditors/FlatCAMGeoEditor.py +++ b/flatcamEditors/FlatCAMGeoEditor.py @@ -20,7 +20,6 @@ from flatcamGUI.ObjectUI import RadioSet from flatcamGUI.GUIElements import OptionalInputSection, FCCheckBox, FCEntry, FCComboBox, FCTextAreaRich, \ FCTable, FCDoubleSpinner, FCButton, EvalEntry2, FCInputDialog, FCTree from flatcamParsers.ParseFont import * -import FlatCAMApp from shapely.geometry import LineString, LinearRing, MultiLineString, Polygon, MultiPolygon from shapely.ops import cascaded_union, unary_union, linemerge @@ -88,8 +87,8 @@ class BufferSelectionTool(FlatCAMTool): 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") + " - '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")) @@ -123,7 +122,7 @@ class BufferSelectionTool(FlatCAMTool): self.buffer_distance_entry.set_value(0.01) def run(self): - self.app.report_usage("Geo Editor ToolBuffer()") + self.app.defaults.report_usage("Geo Editor ToolBuffer()") FlatCAMTool.run(self) # if the splitter us hidden, display it @@ -340,7 +339,7 @@ class TextInputTool(FlatCAMTool): self.font_italic_tb.clicked.connect(self.on_italic_button) def run(self): - self.app.report_usage("Geo Editor TextInputTool()") + self.app.defaults.report_usage("Geo Editor TextInputTool()") FlatCAMTool.run(self) # if the splitter us hidden, display it @@ -538,7 +537,7 @@ class PaintOptionsTool(FlatCAMTool): self.set_tool_ui() def run(self): - self.app.report_usage("Geo Editor ToolPaint()") + self.app.defaults.report_usage("Geo Editor ToolPaint()") FlatCAMTool.run(self) # if the splitter us hidden, display it @@ -981,7 +980,7 @@ class TransformEditorTool(FlatCAMTool): self.set_tool_ui() def run(self): - self.app.report_usage("Geo Editor Transform Tool()") + self.app.defaults.report_usage("Geo Editor Transform Tool()") FlatCAMTool.run(self) self.set_tool_ui() @@ -3299,8 +3298,8 @@ class FlatCAMGeoEditor(QtCore.QObject): draw_shape_idx = -1 def __init__(self, app, disabled=False): - assert isinstance(app, FlatCAMApp.App), \ - "Expected the app to be a FlatCAMApp.App, got %s" % type(app) + # assert isinstance(app, FlatCAMApp.App), \ + # "Expected the app to be a FlatCAMApp.App, got %s" % type(app) super(FlatCAMGeoEditor, self).__init__() @@ -4011,6 +4010,7 @@ class FlatCAMGeoEditor(QtCore.QObject): :return: Boolean. Status of the checkbox that toggled the Editor Tool """ cb_widget = self.sender() + assert isinstance(cb_widget, QtWidgets.QAction), "Expected a QAction got %s" % type(cb_widget) self.options[key] = cb_widget.isChecked() return 1 if self.options[key] is True else 0 @@ -4035,7 +4035,7 @@ class FlatCAMGeoEditor(QtCore.QObject): Imports the geometry from the given FlatCAM Geometry object into the editor. - :param fcgeometry: FlatCAMGeometry + :param fcgeometry: GeometryObject :param multigeo_tool: A tool for the case of the edited geometry being of type 'multigeo' :return: None """ @@ -4750,7 +4750,7 @@ class FlatCAMGeoEditor(QtCore.QObject): Transfers the geometry tool shape buffer to the selected geometry object. The geometry already in the object are removed. - :param fcgeometry: FlatCAMGeometry + :param fcgeometry: GeometryObject :return: None """ if self.multigeo_tool: diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py index a1052495..e95f9bd6 100644 --- a/flatcamEditors/FlatCAMGrbEditor.py +++ b/flatcamEditors/FlatCAMGrbEditor.py @@ -21,7 +21,6 @@ from camlib import distance, arc, three_point_circle from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, FCSpinner, RadioSet, \ EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox from FlatCAMTool import FlatCAMTool -import FlatCAMApp import numpy as np from numpy.linalg import norm as numpy_norm @@ -182,6 +181,7 @@ class FCShapeTool(DrawTool): def __init__(self, draw_app): DrawTool.__init__(self, draw_app) + self.name = None def make(self): pass @@ -199,7 +199,7 @@ class FCPad(FCShapeTool): try: QtGui.QGuiApplication.restoreOverrideCursor() - except Exception as e: + except Exception: pass self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) @@ -1415,7 +1415,7 @@ class FCDisc(FCShapeTool): try: QtGui.QGuiApplication.restoreOverrideCursor() - except Exception as e: + except Exception: pass self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_disc.png')) QtGui.QGuiApplication.setOverrideCursor(self.cursor) @@ -2422,8 +2422,8 @@ class FlatCAMGrbEditor(QtCore.QObject): mp_finished = QtCore.pyqtSignal(list) def __init__(self, app): - assert isinstance(app, FlatCAMApp.App), \ - "Expected the app to be a FlatCAMApp.App, got %s" % type(app) + # assert isinstance(app, FlatCAMApp.App), \ + # "Expected the app to be a FlatCAMApp.App, got %s" % type(app) super(FlatCAMGrbEditor, self).__init__() @@ -2621,8 +2621,8 @@ class FlatCAMGrbEditor(QtCore.QObject): 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") + " - '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")) @@ -3479,7 +3479,7 @@ class FlatCAMGrbEditor(QtCore.QObject): current_table_dia_edited = float(self.apertures_table.currentItem().text()) except ValueError as e: log.debug("FlatCAMExcEditor.on_tool_edit() --> %s" % str(e)) - self.apertures_table.setCurrentItem(None) + # self.apertures_table.setCurrentItem(None) return row_of_item_changed = self.apertures_table.currentRow() @@ -3833,7 +3833,7 @@ class FlatCAMGrbEditor(QtCore.QObject): Imports the geometry found in self.apertures from the given FlatCAM Gerber object into the editor. - :param orig_grb_obj: FlatCAMExcellon + :param orig_grb_obj: ExcellonObject :return: None """ @@ -3956,10 +3956,10 @@ class FlatCAMGrbEditor(QtCore.QObject): global_clear_geo = [] # create one big geometry made out of all 'negative' (clear) polygons - for apid in app_obj.gerber_obj.apertures: + for aper_id in app_obj.gerber_obj.apertures: # first check if we have any clear_geometry (LPC) and if yes added it to the global_clear_geo - if 'geometry' in app_obj.gerber_obj.apertures[apid]: - for elem in app_obj.gerber_obj.apertures[apid]['geometry']: + if 'geometry' in app_obj.gerber_obj.apertures[aper_id]: + for elem in app_obj.gerber_obj.apertures[aper_id]['geometry']: if 'clear' in elem: global_clear_geo.append(elem['clear']) log.warning("Found %d clear polygons." % len(global_clear_geo)) @@ -3967,7 +3967,7 @@ class FlatCAMGrbEditor(QtCore.QObject): if global_clear_geo: global_clear_geo = MultiPolygon(global_clear_geo) if isinstance(global_clear_geo, Polygon): - global_clear_geo = list(global_clear_geo) + global_clear_geo = [global_clear_geo] # we subtract the big "negative" (clear) geometry from each solid polygon but only the part of # clear geometry that fits inside the solid. otherwise we may loose the solid @@ -3979,8 +3979,8 @@ class FlatCAMGrbEditor(QtCore.QObject): # solid_geo = elem['solid'] # for clear_geo in global_clear_geo: # # Make sure that the clear_geo is within the solid_geo otherwise we loose - # # the solid_geometry. We want for clear_geometry just to cut into solid_geometry not to - # # delete it + # # the solid_geometry. We want for clear_geometry just to cut + # # into solid_geometry not to delete it # if clear_geo.within(solid_geo): # solid_geo = solid_geo.difference(clear_geo) # try: @@ -4307,14 +4307,14 @@ class FlatCAMGrbEditor(QtCore.QObject): self.plot_all() - def toolbar_tool_toggle(self, key): - """ - - :param key: key to update in self.options dictionary - :return: - """ - self.options[key] = self.sender().isChecked() - return self.options[key] + # def toolbar_tool_toggle(self, key): + # """ + # + # :param key: key to update in self.options dictionary + # :return: + # """ + # self.options[key] = self.sender().isChecked() + # return self.options[key] def on_grb_shape_complete(self, storage=None, specific_shape=None, no_plot=False): """ @@ -4389,12 +4389,12 @@ class FlatCAMGrbEditor(QtCore.QObject): """ if self.app.is_legacy is False: event_pos = event.pos - event_is_dragging = event.is_dragging - right_button = 2 + # event_is_dragging = event.is_dragging + # right_button = 2 else: event_pos = (event.xdata, event.ydata) - event_is_dragging = self.app.plotcanvas.is_dragging - right_button = 3 + # event_is_dragging = self.app.plotcanvas.is_dragging + # right_button = 3 self.pos = self.canvas.translate_coords(event_pos) @@ -4457,11 +4457,11 @@ class FlatCAMGrbEditor(QtCore.QObject): self.modifiers = QtWidgets.QApplication.keyboardModifiers() if self.app.is_legacy is False: event_pos = event.pos - event_is_dragging = event.is_dragging + # event_is_dragging = event.is_dragging right_button = 2 else: event_pos = (event.xdata, event.ydata) - event_is_dragging = self.app.plotcanvas.is_dragging + # event_is_dragging = self.app.plotcanvas.is_dragging right_button = 3 pos_canvas = self.canvas.translate_coords(event_pos) @@ -4747,10 +4747,10 @@ class FlatCAMGrbEditor(QtCore.QObject): Plots a geometric object or list of objects without rendering. Plotted objects are returned as a list. This allows for efficient/animated rendering. - :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such) - :param color: Shape color - :param linewidth: Width of lines in # of pixels. - :return: List of plotted elements. + :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such) + :param color: Shape color + :param linewidth: Width of lines in # of pixels. + :return: List of plotted elements. """ if geometry is None: @@ -5516,7 +5516,7 @@ class TransformEditorTool(FlatCAMTool): self.set_tool_ui() def run(self, toggle=True): - self.app.report_usage("Geo Editor Transform Tool()") + self.app.defaults.report_usage("Geo Editor Transform Tool()") # if the splitter is hidden, display it, else hide it but only if the current widget is the same if self.app.ui.splitter.sizes()[0] == 0: @@ -5597,7 +5597,7 @@ class TransformEditorTool(FlatCAMTool): self.flip_ref_entry.set_value((0, 0)) def template(self): - if not self.fcdraw.selected: + if not self.draw_app.selected: self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. No shape selected.")) return diff --git a/flatcamEditors/FlatCAMTextEditor.py b/flatcamEditors/FlatCAMTextEditor.py index d65dfd7c..327e2406 100644 --- a/flatcamEditors/FlatCAMTextEditor.py +++ b/flatcamEditors/FlatCAMTextEditor.py @@ -152,14 +152,14 @@ class TextEditor(QtWidgets.QWidget): self.code_edited = '' def handlePrint(self): - self.app.report_usage("handlePrint()") + self.app.defaults.report_usage("handlePrint()") dialog = QtPrintSupport.QPrintDialog() if dialog.exec_() == QtWidgets.QDialog.Accepted: self.code_editor.document().print_(dialog.printer()) def handlePreview(self): - self.app.report_usage("handlePreview()") + self.app.defaults.report_usage("handlePreview()") dialog = QtPrintSupport.QPrintPreviewDialog() dialog.paintRequested.connect(self.code_editor.print_) @@ -172,7 +172,7 @@ class TextEditor(QtWidgets.QWidget): pass def handleOpen(self, filt=None): - self.app.report_usage("handleOpen()") + self.app.defaults.report_usage("handleOpen()") if filt: _filter_ = filt @@ -192,7 +192,7 @@ class TextEditor(QtWidgets.QWidget): file.close() def handleSaveGCode(self, name=None, filt=None, callback=None): - self.app.report_usage("handleSaveGCode()") + self.app.defaults.report_usage("handleSaveGCode()") if filt: _filter_ = filt @@ -287,7 +287,7 @@ class TextEditor(QtWidgets.QWidget): callback() def handleFindGCode(self): - self.app.report_usage("handleFindGCode()") + self.app.defaults.report_usage("handleFindGCode()") flags = QtGui.QTextDocument.FindCaseSensitively text_to_be_found = self.entryFind.get_value() @@ -298,7 +298,7 @@ class TextEditor(QtWidgets.QWidget): r = self.code_editor.find(str(text_to_be_found), flags) def handleReplaceGCode(self): - self.app.report_usage("handleReplaceGCode()") + self.app.defaults.report_usage("handleReplaceGCode()") old = self.entryFind.get_value() new = self.entryReplace.get_value() diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py index 905ad9fe..bb40b298 100644 --- a/flatcamGUI/FlatCAMGUI.py +++ b/flatcamGUI/FlatCAMGUI.py @@ -16,7 +16,7 @@ from flatcamEditors.FlatCAMGeoEditor import FCShapeTool from matplotlib.backend_bases import KeyEvent as mpl_key_event import webbrowser -from ObjectCollection import KeySensitiveListView +from flatcamObjects.ObjectCollection import KeySensitiveListView import subprocess import os @@ -183,6 +183,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/script_new16.png'), _('New Script ...'), self) self.menufileopenscript = QtWidgets.QAction( QtGui.QIcon(self.app.resource_location + '/open_script32.png'), _('Open Script ...'), self) + self.menufileopenscriptexample = QtWidgets.QAction( + QtGui.QIcon(self.app.resource_location + '/open_script32.png'), _('Open Example ...'), self) self.menufilerunscript = QtWidgets.QAction( QtGui.QIcon(self.app.resource_location + '/script16.png'), '%s\tShift+S' % _('Run Script ...'), self) self.menufilerunscript.setToolTip( @@ -192,6 +194,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): ) self.menufile_scripting.addAction(self.menufilenewscript) self.menufile_scripting.addAction(self.menufileopenscript) + self.menufile_scripting.addAction(self.menufileopenscriptexample) self.menufile_scripting.addSeparator() self.menufile_scripting.addAction(self.menufilerunscript) @@ -1741,7 +1744,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # F keys section _("Open Online Manual"), _("Open Online Tutorials"), _("Refresh Plots"), _("Delete Object"), _("Alternate: Delete Tool"), - _("(left to Key_1)Toogle Notebook Area (Left Side)"), _("En(Dis)able Obj Plot"), + _("(left to Key_1)Toggle Notebook Area (Left Side)"), _("En(Dis)able Obj Plot"), _("Deselects all objects") ) ) @@ -2293,13 +2296,13 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.rel_position_label = QtWidgets.QLabel( "Dx: 0.0000   Dy: 0.0000    ") self.rel_position_label.setMinimumWidth(110) - self.rel_position_label.setToolTip(_("Relative neasurement.\nReference is last click position")) + self.rel_position_label.setToolTip(_("Relative measurement.\nReference is last click position")) self.infobar.addWidget(self.rel_position_label) self.position_label = QtWidgets.QLabel( "    X: 0.0000   Y: 0.0000") self.position_label.setMinimumWidth(110) - self.position_label.setToolTip(_("Absolute neasurement.\nReference is (X=0, Y= 0) position")) + self.position_label.setToolTip(_("Absolute measurement.\nReference is (X=0, Y= 0) position")) self.infobar.addWidget(self.position_label) self.units_label = QtWidgets.QLabel("[in]") @@ -2570,9 +2573,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow): self.run_script_btn = self.toolbarshell.addAction( QtGui.QIcon(self.app.resource_location + '/script16.png'), _('Run Script ...')) - # ######################################################################## - # ## Tools Toolbar # ## - # ######################################################################## + # ######################################################################### + # ######################### Tools Toolbar ################################# + # ######################################################################### self.dblsided_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/doubleside32.png'), _("2Sided Tool")) self.align_btn = self.toolbartools.addAction( @@ -2616,6 +2619,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow): QtGui.QIcon(self.app.resource_location + '/fiducials_32.png'), _("Fiducials Tool")) self.cal_btn = self.toolbartools.addAction( QtGui.QIcon(self.app.resource_location + '/calibrate_32.png'), _("Calibration Tool")) + self.punch_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/punch32.png'), _("Punch Gerber Tool")) + self.invert_btn = self.toolbartools.addAction( + QtGui.QIcon(self.app.resource_location + '/invert32.png'), _("Invert Gerber Tool")) # ######################################################################## # ## Excellon Editor Toolbar # ## @@ -2876,7 +2883,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): # Open Excellon file if key == QtCore.Qt.Key_E: - self.app.on_fileopenexcellon() + self.app.on_fileopenexcellon(signal=None) # Open Gerber file if key == QtCore.Qt.Key_G: @@ -2884,7 +2891,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if 'editor' in widget_name.lower(): self.app.goto_text_line() else: - self.app.on_fileopengerber() + self.app.on_fileopengerber(signal=None) # Distance Tool if key == QtCore.Qt.Key_M: @@ -2910,7 +2917,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow): if key == QtCore.Qt.Key_S: widget_name = self.plot_tab_area.currentWidget().objectName() if widget_name == 'preferences_tab': - self.app.on_save_button(save_to_file=False) + self.app.preferencesUiManager.on_save_button(save_to_file=False) return if widget_name == 'database_tab': @@ -4284,20 +4291,24 @@ class FlatCAMInfoBar(QtWidgets.QWidget): def set_status(self, text, level="info"): level = str(level) + self.pmap.fill() if level == "ERROR" or level == "ERROR_NOTCL": self.pmap = QtGui.QPixmap(self.app.resource_location + '/redlight12.png') - elif level == "success" or level == "SUCCESS": + elif level.lower() == "success": self.pmap = QtGui.QPixmap(self.app.resource_location + '/greenlight12.png') elif level == "WARNING" or level == "WARNING_NOTCL": self.pmap = QtGui.QPixmap(self.app.resource_location + '/yellowlight12.png') - elif level == "selected" or level == "SELECTED": + elif level.lower() == "selected": self.pmap = QtGui.QPixmap(self.app.resource_location + '/bluelight12.png') else: self.pmap = QtGui.QPixmap(self.app.resource_location + '/graylight12.png') - self.set_text_(text) - self.icon.setPixmap(self.pmap) + try: + self.set_text_(text) + self.icon.setPixmap(self.pmap) + except Exception as e: + log.debug("FlatCAMInfoBar.set_status() --> %s" % str(e)) class FlatCAMSystemTray(QtWidgets.QSystemTrayIcon): diff --git a/flatcamGUI/GUIElements.py b/flatcamGUI/GUIElements.py index 6a5d06a2..e550e98f 100644 --- a/flatcamGUI/GUIElements.py +++ b/flatcamGUI/GUIElements.py @@ -2088,9 +2088,9 @@ class FCDetachableTab2(FCDetachableTab): :param currentIndex: :return: """ - idx = self.currentIndex() - - self.tab_closed_signal.emit(self.tabText(idx)) + # idx = self.currentIndex() + self.tab_name = self.widget(currentIndex).objectName() + self.tab_closed_signal.emit(self.tab_name) self.removeTab(currentIndex) @@ -2694,8 +2694,7 @@ class _ExpandableTextEdit(QTextEdit): if line_count <= 1: self.historyPrev.emit() return - elif event.matches(QKeySequence.MoveToNextPage) or \ - event.matches(QKeySequence.MoveToPreviousPage): + elif event.matches(QKeySequence.MoveToNextPage) or event.matches(QKeySequence.MoveToPreviousPage): return self._termWidget.browser().keyPressEvent(event) tc = self.textCursor() diff --git a/flatcamGUI/ObjectUI.py b/flatcamGUI/ObjectUI.py index 3b91514c..4004048d 100644 --- a/flatcamGUI/ObjectUI.py +++ b/flatcamGUI/ObjectUI.py @@ -35,7 +35,8 @@ class ObjectUI(QtWidgets.QWidget): put UI elements in ObjectUI.custom_box (QtWidgets.QLayout). """ - def __init__(self, app, icon_file='share/flatcam_icon32.png', title=_('FlatCAM Object'), parent=None, common=True): + def __init__(self, app, icon_file='assets/resources/flatcam_icon32.png', title=_('FlatCAM Object'), + parent=None, common=True): QtWidgets.QWidget.__init__(self, parent=parent) self.app = app @@ -48,9 +49,9 @@ class ObjectUI(QtWidgets.QWidget): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' layout = QtWidgets.QVBoxLayout() self.setLayout(layout) @@ -60,7 +61,7 @@ class ObjectUI(QtWidgets.QWidget): layout.addLayout(self.title_box) # ## Page Title icon - pixmap = QtGui.QPixmap(icon_file.replace('share', self.resource_loc)) + pixmap = QtGui.QPixmap(icon_file.replace('assets/resources', self.resource_loc)) self.icon = QtWidgets.QLabel() self.icon.setPixmap(pixmap) self.title_box.addWidget(self.icon, stretch=0) @@ -307,7 +308,7 @@ class GerberObjectUI(ObjectUI): # Tool Type self.tool_type_label = QtWidgets.QLabel('%s:' % _('Tool Type')) self.tool_type_label.setToolTip( - _("Choose what tool to use for Gerber isolation:\n" + _("Choose which tool to use for Gerber isolation:\n" "'Circular' or 'V-shape'.\n" "When the 'V-shape' is selected then the tool\n" "diameter will depend on the chosen cut depth.") @@ -434,7 +435,7 @@ class GerberObjectUI(ObjectUI): grid1.addWidget(self.follow_cb, 8, 1) self.except_cb.setToolTip(_("When the isolation geometry is generated,\n" - "by checking this, the area of the object bellow\n" + "by checking this, the area of the object below\n" "will be subtracted from the isolation geometry.")) grid1.addWidget(self.except_cb, 8, 2) @@ -713,9 +714,9 @@ class ExcellonObjectUI(ObjectUI): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' ObjectUI.__init__(self, title=_('Excellon Object'), icon_file=self.resource_loc + '/drill32.png', @@ -1241,6 +1242,7 @@ class ExcellonObjectUI(ObjectUI): self.pdepth_entry.set_precision(self.decimals) self.pdepth_entry.set_range(-9999.9999, 9999.9999) self.pdepth_entry.setSingleStep(0.1) + self.pdepth_entry.setObjectName("e_depth_probe") self.grid5.addWidget(self.pdepth_label, 13, 0) self.grid5.addWidget(self.pdepth_entry, 13, 1) @@ -1258,7 +1260,7 @@ class ExcellonObjectUI(ObjectUI): self.feedrate_probe_entry.set_precision(self.decimals) self.feedrate_probe_entry.set_range(0.0, 9999.9999) self.feedrate_probe_entry.setSingleStep(0.1) - self.feedrate_probe_entry.setObjectName(_("e_fr_probe")) + self.feedrate_probe_entry.setObjectName("e_fr_probe") self.grid5.addWidget(self.feedrate_probe_label, 14, 0) self.grid5.addWidget(self.feedrate_probe_entry, 14, 1) @@ -1411,9 +1413,9 @@ class GeometryObjectUI(ObjectUI): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' super(GeometryObjectUI, self).__init__( title=_('Geometry Object'), @@ -1530,7 +1532,7 @@ class GeometryObjectUI(ObjectUI): "- Circular with 1 ... 4 teeth -> it is informative only. Being circular the cut width in material\n" "is exactly the tool diameter.\n" "- Ball -> informative only and make reference to the Ball type endmill.\n" - "- V-Shape -> it will disable de Z-Cut parameter in the UI form and enable two additional UI form\n" + "- V-Shape -> it will disable Z-Cut parameter in the UI form and enable two additional UI form\n" "fields: V-Tip Dia and V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n" "as the cut width into material will be equal with the value in the Tool " "Diameter column of this table.\n" @@ -2107,9 +2109,9 @@ class CNCObjectUI(ObjectUI): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' ObjectUI.__init__( self, title=_('CNC Job Object'), @@ -2440,9 +2442,9 @@ class ScriptObjectUI(ObjectUI): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' ObjectUI.__init__(self, title=_('Script Object'), icon_file=self.resource_loc + '/script_new24.png', @@ -2507,9 +2509,9 @@ class DocumentObjectUI(ObjectUI): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' ObjectUI.__init__(self, title=_('Document Object'), icon_file=self.resource_loc + '/notes16_1.png', diff --git a/flatcamGUI/PlotCanvas.py b/flatcamGUI/PlotCanvas.py index cfb24039..de7a7028 100644 --- a/flatcamGUI/PlotCanvas.py +++ b/flatcamGUI/PlotCanvas.py @@ -8,7 +8,7 @@ from PyQt5 import QtCore import logging -from flatcamGUI.VisPyCanvas import VisPyCanvas, time, Color +from flatcamGUI.VisPyCanvas import VisPyCanvas, Color from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor from vispy.scene.visuals import InfiniteLine, Line @@ -244,7 +244,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas): """ if big is True: self.big_cursor = True - self.c = CursorBig() + self.c = CursorBig(app=self.fcapp) # in case there are multiple new_cursor calls, best to disconnect first the signals try: @@ -410,9 +410,9 @@ class CursorBig(QtCore.QObject): mouse_state_updated = QtCore.pyqtSignal(bool) mouse_position_updated = QtCore.pyqtSignal(list) - def __init__(self): + def __init__(self, app): super().__init__() - + self.app = app self._enabled = None @property diff --git a/flatcamGUI/PlotCanvasLegacy.py b/flatcamGUI/PlotCanvasLegacy.py index d2136784..df9c8231 100644 --- a/flatcamGUI/PlotCanvasLegacy.py +++ b/flatcamGUI/PlotCanvasLegacy.py @@ -16,8 +16,6 @@ from descartes.patch import PolygonPatch from shapely.geometry import Polygon, LineString, LinearRing -import FlatCAMApp - from copy import deepcopy import logging @@ -496,7 +494,7 @@ class PlotCanvasLegacy(QtCore.QObject): :param event: :return: """ - FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key)) + log.debug('on_key_down(): ' + str(event.key)) self.key = event.key def on_key_up(self, event): @@ -531,7 +529,7 @@ class PlotCanvasLegacy(QtCore.QObject): try: self.figure.clf() except KeyError: - FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()") + log.warning("KeyError in MPL figure.clf()") # Re-build self.figure.add_axes(self.axes) @@ -582,7 +580,7 @@ class PlotCanvasLegacy(QtCore.QObject): try: r = width / height except ZeroDivisionError: - FlatCAMApp.App.log.error("Height is %f" % height) + log.error("Height is %f" % height) return canvas_w, canvas_h = self.canvas.get_width_height() canvas_r = float(canvas_w) / canvas_h @@ -1190,10 +1188,10 @@ class ShapeCollectionLegacy: linewidth=local_shapes[element]['linewidth']) self.axes.add_patch(patch) except AssertionError: - FlatCAMApp.App.log.warning("A geometry component was not a polygon:") - FlatCAMApp.App.log.warning(str(element)) + log.warning("A geometry component was not a polygon:") + log.warning(str(element)) except Exception as e: - FlatCAMApp.App.log.debug( + log.debug( "PlotCanvasLegacy.ShepeCollectionLegacy.redraw() gerber 'solid' --> %s" % str(e)) else: try: diff --git a/flatcamGUI/PreferencesUI.py b/flatcamGUI/PreferencesUI.py index 8ad23154..e7c32c7b 100644 --- a/flatcamGUI/PreferencesUI.py +++ b/flatcamGUI/PreferencesUI.py @@ -3,28 +3,1142 @@ # File Author: Marius Adrian Stanciu (c) # # Date: 10/10/2019 # # MIT Licence # +# # +# Modified by David Robertson 29.04.2020 # # ########################################################## +import os -from PyQt5.QtCore import QSettings +from defaults import FlatCAMDefaults from flatcamGUI.GUIElements import * import platform import sys - +import logging import gettext import FlatCAMTranslation as fcTranslate import builtins +log = logging.getLogger('PreferencesUI') fcTranslate.apply_language('strings') if '_' not in builtins.__dict__: _ = gettext.gettext -settings = QtCore.QSettings("Open Source", "FlatCAM") +settings = QSettings("Open Source", "FlatCAM") if settings.contains("machinist"): machinist_setting = settings.value('machinist', type=int) else: machinist_setting = 0 +class PreferencesUIManager: + + def __init__(self, defaults: FlatCAMDefaults, data_path: str, ui, inform): + """ + Class that control the Preferences Tab + + :param defaults: a dictionary storage where all the application settings are stored + :param data_path: a path to the file where all the preferences are stored for persistence + :param ui: reference to the FlatCAMGUI class which constructs the UI + :param inform: a pyqtSignal used to display information's in the StatusBar of the GUI + """ + + self.defaults = defaults + self.data_path = data_path + self.ui = ui + self.inform = inform + self.ignore_tab_close_event = False + + # if Preferences are changed in the Edit -> Preferences tab the value will be set to True + self.preferences_changed_flag = False + + # when adding entries here read the comments in the method found bellow named: + # def new_object(self, kind, name, initialize, active=True, fit=True, plot=True) + self.defaults_form_fields = { + # General App + "decimals_inch": self.ui.general_defaults_form.general_app_group.precision_inch_entry, + "decimals_metric": self.ui.general_defaults_form.general_app_group.precision_metric_entry, + "units": self.ui.general_defaults_form.general_app_group.units_radio, + "global_graphic_engine": self.ui.general_defaults_form.general_app_group.ge_radio, + "global_app_level": self.ui.general_defaults_form.general_app_group.app_level_radio, + "global_portable": self.ui.general_defaults_form.general_app_group.portability_cb, + "global_language": self.ui.general_defaults_form.general_app_group.language_cb, + + "global_systray_icon": self.ui.general_defaults_form.general_app_group.systray_cb, + "global_shell_at_startup": self.ui.general_defaults_form.general_app_group.shell_startup_cb, + "global_project_at_startup": self.ui.general_defaults_form.general_app_group.project_startup_cb, + "global_version_check": self.ui.general_defaults_form.general_app_group.version_check_cb, + "global_send_stats": self.ui.general_defaults_form.general_app_group.send_stats_cb, + + "global_worker_number": self.ui.general_defaults_form.general_app_group.worker_number_sb, + "global_tolerance": self.ui.general_defaults_form.general_app_group.tol_entry, + + "global_compression_level": self.ui.general_defaults_form.general_app_group.compress_spinner, + "global_save_compressed": self.ui.general_defaults_form.general_app_group.save_type_cb, + "global_autosave": self.ui.general_defaults_form.general_app_group.autosave_cb, + "global_autosave_timeout": self.ui.general_defaults_form.general_app_group.autosave_entry, + + "global_tpdf_tmargin": self.ui.general_defaults_form.general_app_group.tmargin_entry, + "global_tpdf_bmargin": self.ui.general_defaults_form.general_app_group.bmargin_entry, + "global_tpdf_lmargin": self.ui.general_defaults_form.general_app_group.lmargin_entry, + "global_tpdf_rmargin": self.ui.general_defaults_form.general_app_group.rmargin_entry, + + # General GUI Preferences + "global_theme": self.ui.general_defaults_form.general_gui_group.theme_radio, + "global_gray_icons": self.ui.general_defaults_form.general_gui_group.gray_icons_cb, + "global_layout": self.ui.general_defaults_form.general_gui_group.layout_combo, + "global_hover": self.ui.general_defaults_form.general_gui_group.hover_cb, + "global_selection_shape": self.ui.general_defaults_form.general_gui_group.selection_cb, + + "global_sel_fill": self.ui.general_defaults_form.general_gui_group.sf_color_entry, + "global_sel_line": self.ui.general_defaults_form.general_gui_group.sl_color_entry, + "global_alt_sel_fill": self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry, + "global_alt_sel_line": self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry, + "global_draw_color": self.ui.general_defaults_form.general_gui_group.draw_color_entry, + "global_sel_draw_color": self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry, + + "global_proj_item_color": self.ui.general_defaults_form.general_gui_group.proj_color_entry, + "global_proj_item_dis_color": self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry, + "global_project_autohide": self.ui.general_defaults_form.general_gui_group.project_autohide_cb, + + # General GUI Settings + "global_gridx": self.ui.general_defaults_form.general_app_set_group.gridx_entry, + "global_gridy": self.ui.general_defaults_form.general_app_set_group.gridy_entry, + "global_snap_max": self.ui.general_defaults_form.general_app_set_group.snap_max_dist_entry, + "global_workspace": self.ui.general_defaults_form.general_app_set_group.workspace_cb, + "global_workspaceT": self.ui.general_defaults_form.general_app_set_group.wk_cb, + "global_workspace_orientation": self.ui.general_defaults_form.general_app_set_group.wk_orientation_radio, + + "global_cursor_type": self.ui.general_defaults_form.general_app_set_group.cursor_radio, + "global_cursor_size": self.ui.general_defaults_form.general_app_set_group.cursor_size_entry, + "global_cursor_width": self.ui.general_defaults_form.general_app_set_group.cursor_width_entry, + "global_cursor_color_enabled": self.ui.general_defaults_form.general_app_set_group.mouse_cursor_color_cb, + "global_cursor_color": self.ui.general_defaults_form.general_app_set_group.mouse_cursor_entry, + "global_pan_button": self.ui.general_defaults_form.general_app_set_group.pan_button_radio, + "global_mselect_key": self.ui.general_defaults_form.general_app_set_group.mselect_radio, + "global_delete_confirmation": self.ui.general_defaults_form.general_app_set_group.delete_conf_cb, + "global_open_style": self.ui.general_defaults_form.general_app_set_group.open_style_cb, + "global_toggle_tooltips": self.ui.general_defaults_form.general_app_set_group.toggle_tooltips_cb, + "global_machinist_setting": self.ui.general_defaults_form.general_app_set_group.machinist_cb, + + "global_bookmarks_limit": self.ui.general_defaults_form.general_app_set_group.bm_limit_spinner, + "global_activity_icon": self.ui.general_defaults_form.general_app_set_group.activity_combo, + + # Gerber General + "gerber_plot": self.ui.gerber_defaults_form.gerber_gen_group.plot_cb, + "gerber_solid": self.ui.gerber_defaults_form.gerber_gen_group.solid_cb, + "gerber_multicolored": self.ui.gerber_defaults_form.gerber_gen_group.multicolored_cb, + "gerber_circle_steps": self.ui.gerber_defaults_form.gerber_gen_group.circle_steps_entry, + "gerber_def_units": self.ui.gerber_defaults_form.gerber_gen_group.gerber_units_radio, + "gerber_def_zeros": self.ui.gerber_defaults_form.gerber_gen_group.gerber_zeros_radio, + "gerber_clean_apertures": self.ui.gerber_defaults_form.gerber_gen_group.gerber_clean_cb, + "gerber_extra_buffering": self.ui.gerber_defaults_form.gerber_gen_group.gerber_extra_buffering, + "gerber_plot_fill": self.ui.gerber_defaults_form.gerber_gen_group.pf_color_entry, + "gerber_plot_line": self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry, + + # Gerber Options + "gerber_isotooldia": self.ui.gerber_defaults_form.gerber_opt_group.iso_tool_dia_entry, + "gerber_isopasses": self.ui.gerber_defaults_form.gerber_opt_group.iso_width_entry, + "gerber_isooverlap": self.ui.gerber_defaults_form.gerber_opt_group.iso_overlap_entry, + "gerber_combine_passes": self.ui.gerber_defaults_form.gerber_opt_group.combine_passes_cb, + "gerber_iso_scope": self.ui.gerber_defaults_form.gerber_opt_group.iso_scope_radio, + "gerber_milling_type": self.ui.gerber_defaults_form.gerber_opt_group.milling_type_radio, + "gerber_noncoppermargin": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_margin_entry, + "gerber_noncopperrounded": self.ui.gerber_defaults_form.gerber_opt_group.noncopper_rounded_cb, + "gerber_bboxmargin": self.ui.gerber_defaults_form.gerber_opt_group.bbmargin_entry, + "gerber_bboxrounded": self.ui.gerber_defaults_form.gerber_opt_group.bbrounded_cb, + + # Gerber Advanced Options + "gerber_aperture_display": self.ui.gerber_defaults_form.gerber_adv_opt_group.aperture_table_visibility_cb, + # "gerber_aperture_scale_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.scale_aperture_entry, + # "gerber_aperture_buffer_factor": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffer_aperture_entry, + "gerber_follow": self.ui.gerber_defaults_form.gerber_adv_opt_group.follow_cb, + "gerber_tool_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.tool_type_radio, + "gerber_vtipdia": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipdia_spinner, + "gerber_vtipangle": self.ui.gerber_defaults_form.gerber_adv_opt_group.tipangle_spinner, + "gerber_vcutz": self.ui.gerber_defaults_form.gerber_adv_opt_group.cutz_spinner, + "gerber_iso_type": self.ui.gerber_defaults_form.gerber_adv_opt_group.iso_type_radio, + + "gerber_buffering": self.ui.gerber_defaults_form.gerber_adv_opt_group.buffering_radio, + "gerber_simplification": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplify_cb, + "gerber_simp_tolerance": self.ui.gerber_defaults_form.gerber_adv_opt_group.simplification_tol_spinner, + + # Gerber Export + "gerber_exp_units": self.ui.gerber_defaults_form.gerber_exp_group.gerber_units_radio, + "gerber_exp_integer": self.ui.gerber_defaults_form.gerber_exp_group.format_whole_entry, + "gerber_exp_decimals": self.ui.gerber_defaults_form.gerber_exp_group.format_dec_entry, + "gerber_exp_zeros": self.ui.gerber_defaults_form.gerber_exp_group.zeros_radio, + + # Gerber Editor + "gerber_editor_sel_limit": self.ui.gerber_defaults_form.gerber_editor_group.sel_limit_entry, + "gerber_editor_newcode": self.ui.gerber_defaults_form.gerber_editor_group.addcode_entry, + "gerber_editor_newsize": self.ui.gerber_defaults_form.gerber_editor_group.addsize_entry, + "gerber_editor_newtype": self.ui.gerber_defaults_form.gerber_editor_group.addtype_combo, + "gerber_editor_newdim": self.ui.gerber_defaults_form.gerber_editor_group.adddim_entry, + "gerber_editor_array_size": self.ui.gerber_defaults_form.gerber_editor_group.grb_array_size_entry, + "gerber_editor_lin_axis": self.ui.gerber_defaults_form.gerber_editor_group.grb_axis_radio, + "gerber_editor_lin_pitch": self.ui.gerber_defaults_form.gerber_editor_group.grb_pitch_entry, + "gerber_editor_lin_angle": self.ui.gerber_defaults_form.gerber_editor_group.grb_angle_entry, + "gerber_editor_circ_dir": self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_dir_radio, + "gerber_editor_circ_angle": + self.ui.gerber_defaults_form.gerber_editor_group.grb_circular_angle_entry, + "gerber_editor_scale_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_scale_entry, + "gerber_editor_buff_f": self.ui.gerber_defaults_form.gerber_editor_group.grb_buff_entry, + "gerber_editor_ma_low": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_low_entry, + "gerber_editor_ma_high": self.ui.gerber_defaults_form.gerber_editor_group.grb_ma_high_entry, + + # Excellon General + "excellon_plot": self.ui.excellon_defaults_form.excellon_gen_group.plot_cb, + "excellon_solid": self.ui.excellon_defaults_form.excellon_gen_group.solid_cb, + "excellon_format_upper_in": + self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_in_entry, + "excellon_format_lower_in": + self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_in_entry, + "excellon_format_upper_mm": + self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_upper_mm_entry, + "excellon_format_lower_mm": + self.ui.excellon_defaults_form.excellon_gen_group.excellon_format_lower_mm_entry, + "excellon_zeros": self.ui.excellon_defaults_form.excellon_gen_group.excellon_zeros_radio, + "excellon_units": self.ui.excellon_defaults_form.excellon_gen_group.excellon_units_radio, + "excellon_update": self.ui.excellon_defaults_form.excellon_gen_group.update_excellon_cb, + "excellon_optimization_type": self.ui.excellon_defaults_form.excellon_gen_group.excellon_optimization_radio, + "excellon_search_time": self.ui.excellon_defaults_form.excellon_gen_group.optimization_time_entry, + "excellon_plot_fill": self.ui.excellon_defaults_form.excellon_gen_group.fill_color_entry, + "excellon_plot_line": self.ui.excellon_defaults_form.excellon_gen_group.line_color_entry, + + # Excellon Options + "excellon_operation": self.ui.excellon_defaults_form.excellon_opt_group.operation_radio, + "excellon_milling_type": self.ui.excellon_defaults_form.excellon_opt_group.milling_type_radio, + + "excellon_milling_dia": self.ui.excellon_defaults_form.excellon_opt_group.mill_dia_entry, + + "excellon_cutz": self.ui.excellon_defaults_form.excellon_opt_group.cutz_entry, + "excellon_multidepth": self.ui.excellon_defaults_form.excellon_opt_group.mpass_cb, + "excellon_depthperpass": self.ui.excellon_defaults_form.excellon_opt_group.maxdepth_entry, + "excellon_travelz": self.ui.excellon_defaults_form.excellon_opt_group.travelz_entry, + "excellon_endz": self.ui.excellon_defaults_form.excellon_opt_group.endz_entry, + "excellon_endxy": self.ui.excellon_defaults_form.excellon_opt_group.endxy_entry, + + "excellon_feedrate_z": self.ui.excellon_defaults_form.excellon_opt_group.feedrate_z_entry, + "excellon_spindlespeed": self.ui.excellon_defaults_form.excellon_opt_group.spindlespeed_entry, + "excellon_dwell": self.ui.excellon_defaults_form.excellon_opt_group.dwell_cb, + "excellon_dwelltime": self.ui.excellon_defaults_form.excellon_opt_group.dwelltime_entry, + "excellon_toolchange": self.ui.excellon_defaults_form.excellon_opt_group.toolchange_cb, + "excellon_toolchangez": self.ui.excellon_defaults_form.excellon_opt_group.toolchangez_entry, + "excellon_ppname_e": self.ui.excellon_defaults_form.excellon_opt_group.pp_excellon_name_cb, + "excellon_tooldia": self.ui.excellon_defaults_form.excellon_opt_group.tooldia_entry, + "excellon_slot_tooldia": self.ui.excellon_defaults_form.excellon_opt_group.slot_tooldia_entry, + "excellon_gcode_type": self.ui.excellon_defaults_form.excellon_opt_group.excellon_gcode_type_radio, + + # Excellon Advanced Options + "excellon_offset": self.ui.excellon_defaults_form.excellon_adv_opt_group.offset_entry, + "excellon_toolchangexy": self.ui.excellon_defaults_form.excellon_adv_opt_group.toolchangexy_entry, + "excellon_startz": self.ui.excellon_defaults_form.excellon_adv_opt_group.estartz_entry, + "excellon_feedrate_rapid": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_rapid_entry, + "excellon_z_pdepth": self.ui.excellon_defaults_form.excellon_adv_opt_group.pdepth_entry, + "excellon_feedrate_probe": self.ui.excellon_defaults_form.excellon_adv_opt_group.feedrate_probe_entry, + "excellon_spindledir": self.ui.excellon_defaults_form.excellon_adv_opt_group.spindledir_radio, + "excellon_f_plunge": self.ui.excellon_defaults_form.excellon_adv_opt_group.fplunge_cb, + "excellon_f_retract": self.ui.excellon_defaults_form.excellon_adv_opt_group.fretract_cb, + + # Excellon Export + "excellon_exp_units": self.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio, + "excellon_exp_format": self.ui.excellon_defaults_form.excellon_exp_group.format_radio, + "excellon_exp_integer": self.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry, + "excellon_exp_decimals": self.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry, + "excellon_exp_zeros": self.ui.excellon_defaults_form.excellon_exp_group.zeros_radio, + "excellon_exp_slot_type": self.ui.excellon_defaults_form.excellon_exp_group.slot_type_radio, + + # Excellon Editor + "excellon_editor_sel_limit": self.ui.excellon_defaults_form.excellon_editor_group.sel_limit_entry, + "excellon_editor_newdia": self.ui.excellon_defaults_form.excellon_editor_group.addtool_entry, + "excellon_editor_array_size": self.ui.excellon_defaults_form.excellon_editor_group.drill_array_size_entry, + "excellon_editor_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_axis_radio, + "excellon_editor_lin_pitch": self.ui.excellon_defaults_form.excellon_editor_group.drill_pitch_entry, + "excellon_editor_lin_angle": self.ui.excellon_defaults_form.excellon_editor_group.drill_angle_entry, + "excellon_editor_circ_dir": self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_dir_radio, + "excellon_editor_circ_angle": + self.ui.excellon_defaults_form.excellon_editor_group.drill_circular_angle_entry, + # Excellon Slots + "excellon_editor_slot_direction": + self.ui.excellon_defaults_form.excellon_editor_group.slot_axis_radio, + "excellon_editor_slot_angle": + self.ui.excellon_defaults_form.excellon_editor_group.slot_angle_spinner, + "excellon_editor_slot_length": + self.ui.excellon_defaults_form.excellon_editor_group.slot_length_entry, + # Excellon Slots + "excellon_editor_slot_array_size": + self.ui.excellon_defaults_form.excellon_editor_group.slot_array_size_entry, + "excellon_editor_slot_lin_dir": self.ui.excellon_defaults_form.excellon_editor_group.slot_array_axis_radio, + "excellon_editor_slot_lin_pitch": + self.ui.excellon_defaults_form.excellon_editor_group.slot_array_pitch_entry, + "excellon_editor_slot_lin_angle": + self.ui.excellon_defaults_form.excellon_editor_group.slot_array_angle_entry, + "excellon_editor_slot_circ_dir": + self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_dir_radio, + "excellon_editor_slot_circ_angle": + self.ui.excellon_defaults_form.excellon_editor_group.slot_array_circular_angle_entry, + + # Geometry General + "geometry_plot": self.ui.geometry_defaults_form.geometry_gen_group.plot_cb, + "geometry_circle_steps": self.ui.geometry_defaults_form.geometry_gen_group.circle_steps_entry, + "geometry_cnctooldia": self.ui.geometry_defaults_form.geometry_gen_group.cnctooldia_entry, + "geometry_plot_line": self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry, + + # Geometry Options + "geometry_cutz": self.ui.geometry_defaults_form.geometry_opt_group.cutz_entry, + "geometry_travelz": self.ui.geometry_defaults_form.geometry_opt_group.travelz_entry, + "geometry_feedrate": self.ui.geometry_defaults_form.geometry_opt_group.cncfeedrate_entry, + "geometry_feedrate_z": self.ui.geometry_defaults_form.geometry_opt_group.feedrate_z_entry, + "geometry_spindlespeed": self.ui.geometry_defaults_form.geometry_opt_group.cncspindlespeed_entry, + "geometry_dwell": self.ui.geometry_defaults_form.geometry_opt_group.dwell_cb, + "geometry_dwelltime": self.ui.geometry_defaults_form.geometry_opt_group.dwelltime_entry, + "geometry_ppname_g": self.ui.geometry_defaults_form.geometry_opt_group.pp_geometry_name_cb, + "geometry_toolchange": self.ui.geometry_defaults_form.geometry_opt_group.toolchange_cb, + "geometry_toolchangez": self.ui.geometry_defaults_form.geometry_opt_group.toolchangez_entry, + "geometry_endz": self.ui.geometry_defaults_form.geometry_opt_group.endz_entry, + "geometry_endxy": self.ui.geometry_defaults_form.geometry_opt_group.endxy_entry, + "geometry_depthperpass": self.ui.geometry_defaults_form.geometry_opt_group.depthperpass_entry, + "geometry_multidepth": self.ui.geometry_defaults_form.geometry_opt_group.multidepth_cb, + + # Geometry Advanced Options + "geometry_toolchangexy": self.ui.geometry_defaults_form.geometry_adv_opt_group.toolchangexy_entry, + "geometry_startz": self.ui.geometry_defaults_form.geometry_adv_opt_group.gstartz_entry, + "geometry_feedrate_rapid": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_rapid_entry, + "geometry_extracut": self.ui.geometry_defaults_form.geometry_adv_opt_group.extracut_cb, + "geometry_extracut_length": self.ui.geometry_defaults_form.geometry_adv_opt_group.e_cut_entry, + "geometry_z_pdepth": self.ui.geometry_defaults_form.geometry_adv_opt_group.pdepth_entry, + "geometry_feedrate_probe": self.ui.geometry_defaults_form.geometry_adv_opt_group.feedrate_probe_entry, + "geometry_spindledir": self.ui.geometry_defaults_form.geometry_adv_opt_group.spindledir_radio, + "geometry_f_plunge": self.ui.geometry_defaults_form.geometry_adv_opt_group.fplunge_cb, + "geometry_segx": self.ui.geometry_defaults_form.geometry_adv_opt_group.segx_entry, + "geometry_segy": self.ui.geometry_defaults_form.geometry_adv_opt_group.segy_entry, + + # Geometry Editor + "geometry_editor_sel_limit": self.ui.geometry_defaults_form.geometry_editor_group.sel_limit_entry, + "geometry_editor_milling_type": self.ui.geometry_defaults_form.geometry_editor_group.milling_type_radio, + + # CNCJob General + "cncjob_plot": self.ui.cncjob_defaults_form.cncjob_gen_group.plot_cb, + "cncjob_plot_kind": self.ui.cncjob_defaults_form.cncjob_gen_group.cncplot_method_radio, + "cncjob_annotation": self.ui.cncjob_defaults_form.cncjob_gen_group.annotation_cb, + + "cncjob_tooldia": self.ui.cncjob_defaults_form.cncjob_gen_group.tooldia_entry, + "cncjob_coords_type": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_type_radio, + "cncjob_coords_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.coords_dec_entry, + "cncjob_fr_decimals": self.ui.cncjob_defaults_form.cncjob_gen_group.fr_dec_entry, + "cncjob_steps_per_circle": self.ui.cncjob_defaults_form.cncjob_gen_group.steps_per_circle_entry, + "cncjob_line_ending": self.ui.cncjob_defaults_form.cncjob_gen_group.line_ending_cb, + "cncjob_plot_line": self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry, + "cncjob_plot_fill": self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry, + "cncjob_travel_line": self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry, + "cncjob_travel_fill": self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry, + + # CNC Job Options + "cncjob_prepend": self.ui.cncjob_defaults_form.cncjob_opt_group.prepend_text, + "cncjob_append": self.ui.cncjob_defaults_form.cncjob_opt_group.append_text, + + # CNC Job Advanced Options + "cncjob_toolchange_macro": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_text, + "cncjob_toolchange_macro_enable": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.toolchange_cb, + "cncjob_annotation_fontsize": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontsize_sp, + "cncjob_annotation_fontcolor": self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry, + + # NCC Tool + "tools_ncctools": self.ui.tools_defaults_form.tools_ncc_group.ncc_tool_dia_entry, + "tools_nccorder": self.ui.tools_defaults_form.tools_ncc_group.ncc_order_radio, + "tools_nccoverlap": self.ui.tools_defaults_form.tools_ncc_group.ncc_overlap_entry, + "tools_nccmargin": self.ui.tools_defaults_form.tools_ncc_group.ncc_margin_entry, + "tools_nccmethod": self.ui.tools_defaults_form.tools_ncc_group.ncc_method_combo, + "tools_nccconnect": self.ui.tools_defaults_form.tools_ncc_group.ncc_connect_cb, + "tools_ncccontour": self.ui.tools_defaults_form.tools_ncc_group.ncc_contour_cb, + "tools_nccrest": self.ui.tools_defaults_form.tools_ncc_group.ncc_rest_cb, + "tools_ncc_offset_choice": self.ui.tools_defaults_form.tools_ncc_group.ncc_choice_offset_cb, + "tools_ncc_offset_value": self.ui.tools_defaults_form.tools_ncc_group.ncc_offset_spinner, + "tools_nccref": self.ui.tools_defaults_form.tools_ncc_group.select_combo, + "tools_ncc_area_shape": self.ui.tools_defaults_form.tools_ncc_group.area_shape_radio, + "tools_ncc_plotting": self.ui.tools_defaults_form.tools_ncc_group.ncc_plotting_radio, + "tools_nccmilling_type": self.ui.tools_defaults_form.tools_ncc_group.milling_type_radio, + "tools_ncctool_type": self.ui.tools_defaults_form.tools_ncc_group.tool_type_radio, + "tools_ncccutz": self.ui.tools_defaults_form.tools_ncc_group.cutz_entry, + "tools_ncctipdia": self.ui.tools_defaults_form.tools_ncc_group.tipdia_entry, + "tools_ncctipangle": self.ui.tools_defaults_form.tools_ncc_group.tipangle_entry, + "tools_nccnewdia": self.ui.tools_defaults_form.tools_ncc_group.newdia_entry, + + # CutOut Tool + "tools_cutouttooldia": self.ui.tools_defaults_form.tools_cutout_group.cutout_tooldia_entry, + "tools_cutoutkind": self.ui.tools_defaults_form.tools_cutout_group.obj_kind_combo, + "tools_cutoutmargin": self.ui.tools_defaults_form.tools_cutout_group.cutout_margin_entry, + "tools_cutout_z": self.ui.tools_defaults_form.tools_cutout_group.cutz_entry, + "tools_cutout_depthperpass": self.ui.tools_defaults_form.tools_cutout_group.maxdepth_entry, + "tools_cutout_mdepth": self.ui.tools_defaults_form.tools_cutout_group.mpass_cb, + "tools_cutoutgapsize": self.ui.tools_defaults_form.tools_cutout_group.cutout_gap_entry, + "tools_gaps_ff": self.ui.tools_defaults_form.tools_cutout_group.gaps_combo, + "tools_cutout_convexshape": self.ui.tools_defaults_form.tools_cutout_group.convex_box, + + # Paint Area Tool + "tools_painttooldia": self.ui.tools_defaults_form.tools_paint_group.painttooldia_entry, + "tools_paintorder": self.ui.tools_defaults_form.tools_paint_group.paint_order_radio, + "tools_paintoverlap": self.ui.tools_defaults_form.tools_paint_group.paintoverlap_entry, + "tools_paintmargin": self.ui.tools_defaults_form.tools_paint_group.paintmargin_entry, + "tools_paintmethod": self.ui.tools_defaults_form.tools_paint_group.paintmethod_combo, + "tools_selectmethod": self.ui.tools_defaults_form.tools_paint_group.selectmethod_combo, + "tools_paint_area_shape": self.ui.tools_defaults_form.tools_paint_group.area_shape_radio, + "tools_pathconnect": self.ui.tools_defaults_form.tools_paint_group.pathconnect_cb, + "tools_paintcontour": self.ui.tools_defaults_form.tools_paint_group.contour_cb, + "tools_paint_plotting": self.ui.tools_defaults_form.tools_paint_group.paint_plotting_radio, + + "tools_paintrest": self.ui.tools_defaults_form.tools_paint_group.rest_cb, + "tools_painttool_type": self.ui.tools_defaults_form.tools_paint_group.tool_type_radio, + "tools_paintcutz": self.ui.tools_defaults_form.tools_paint_group.cutz_entry, + "tools_painttipdia": self.ui.tools_defaults_form.tools_paint_group.tipdia_entry, + "tools_painttipangle": self.ui.tools_defaults_form.tools_paint_group.tipangle_entry, + "tools_paintnewdia": self.ui.tools_defaults_form.tools_paint_group.newdia_entry, + + # 2-sided Tool + "tools_2sided_mirror_axis": self.ui.tools_defaults_form.tools_2sided_group.mirror_axis_radio, + "tools_2sided_axis_loc": self.ui.tools_defaults_form.tools_2sided_group.axis_location_radio, + "tools_2sided_drilldia": self.ui.tools_defaults_form.tools_2sided_group.drill_dia_entry, + "tools_2sided_allign_axis": self.ui.tools_defaults_form.tools_2sided_group.align_axis_radio, + + # Film Tool + "tools_film_type": self.ui.tools_defaults_form.tools_film_group.film_type_radio, + "tools_film_boundary": self.ui.tools_defaults_form.tools_film_group.film_boundary_entry, + "tools_film_scale_stroke": self.ui.tools_defaults_form.tools_film_group.film_scale_stroke_entry, + "tools_film_color": self.ui.tools_defaults_form.tools_film_group.film_color_entry, + "tools_film_scale_cb": self.ui.tools_defaults_form.tools_film_group.film_scale_cb, + "tools_film_scale_x_entry": self.ui.tools_defaults_form.tools_film_group.film_scalex_entry, + "tools_film_scale_y_entry": self.ui.tools_defaults_form.tools_film_group.film_scaley_entry, + "tools_film_skew_cb": self.ui.tools_defaults_form.tools_film_group.film_skew_cb, + "tools_film_skew_x_entry": self.ui.tools_defaults_form.tools_film_group.film_skewx_entry, + "tools_film_skew_y_entry": self.ui.tools_defaults_form.tools_film_group.film_skewy_entry, + "tools_film_skew_ref_radio": self.ui.tools_defaults_form.tools_film_group.film_skew_reference, + "tools_film_mirror_cb": self.ui.tools_defaults_form.tools_film_group.film_mirror_cb, + "tools_film_mirror_axis_radio": self.ui.tools_defaults_form.tools_film_group.film_mirror_axis, + "tools_film_file_type_radio": self.ui.tools_defaults_form.tools_film_group.file_type_radio, + "tools_film_orientation": self.ui.tools_defaults_form.tools_film_group.orientation_radio, + "tools_film_pagesize": self.ui.tools_defaults_form.tools_film_group.pagesize_combo, + + # Panelize Tool + "tools_panelize_spacing_columns": self.ui.tools_defaults_form.tools_panelize_group.pspacing_columns, + "tools_panelize_spacing_rows": self.ui.tools_defaults_form.tools_panelize_group.pspacing_rows, + "tools_panelize_columns": self.ui.tools_defaults_form.tools_panelize_group.pcolumns, + "tools_panelize_rows": self.ui.tools_defaults_form.tools_panelize_group.prows, + "tools_panelize_constrain": self.ui.tools_defaults_form.tools_panelize_group.pconstrain_cb, + "tools_panelize_constrainx": self.ui.tools_defaults_form.tools_panelize_group.px_width_entry, + "tools_panelize_constrainy": self.ui.tools_defaults_form.tools_panelize_group.py_height_entry, + "tools_panelize_panel_type": self.ui.tools_defaults_form.tools_panelize_group.panel_type_radio, + + # Calculators Tool + "tools_calc_vshape_tip_dia": self.ui.tools_defaults_form.tools_calculators_group.tip_dia_entry, + "tools_calc_vshape_tip_angle": self.ui.tools_defaults_form.tools_calculators_group.tip_angle_entry, + "tools_calc_vshape_cut_z": self.ui.tools_defaults_form.tools_calculators_group.cut_z_entry, + "tools_calc_electro_length": self.ui.tools_defaults_form.tools_calculators_group.pcblength_entry, + "tools_calc_electro_width": self.ui.tools_defaults_form.tools_calculators_group.pcbwidth_entry, + "tools_calc_electro_cdensity": self.ui.tools_defaults_form.tools_calculators_group.cdensity_entry, + "tools_calc_electro_growth": self.ui.tools_defaults_form.tools_calculators_group.growth_entry, + + # Transformations Tool + "tools_transform_rotate": self.ui.tools_defaults_form.tools_transform_group.rotate_entry, + "tools_transform_skew_x": self.ui.tools_defaults_form.tools_transform_group.skewx_entry, + "tools_transform_skew_y": self.ui.tools_defaults_form.tools_transform_group.skewy_entry, + "tools_transform_scale_x": self.ui.tools_defaults_form.tools_transform_group.scalex_entry, + "tools_transform_scale_y": self.ui.tools_defaults_form.tools_transform_group.scaley_entry, + "tools_transform_scale_link": self.ui.tools_defaults_form.tools_transform_group.link_cb, + "tools_transform_scale_reference": self.ui.tools_defaults_form.tools_transform_group.reference_cb, + "tools_transform_offset_x": self.ui.tools_defaults_form.tools_transform_group.offx_entry, + "tools_transform_offset_y": self.ui.tools_defaults_form.tools_transform_group.offy_entry, + "tools_transform_mirror_reference": self.ui.tools_defaults_form.tools_transform_group.mirror_reference_cb, + "tools_transform_mirror_point": self.ui.tools_defaults_form.tools_transform_group.flip_ref_entry, + "tools_transform_buffer_dis": self.ui.tools_defaults_form.tools_transform_group.buffer_entry, + "tools_transform_buffer_factor": self.ui.tools_defaults_form.tools_transform_group.buffer_factor_entry, + "tools_transform_buffer_corner": self.ui.tools_defaults_form.tools_transform_group.buffer_rounded_cb, + + # SolderPaste Dispensing Tool + "tools_solderpaste_tools": self.ui.tools_defaults_form.tools_solderpaste_group.nozzle_tool_dia_entry, + "tools_solderpaste_new": self.ui.tools_defaults_form.tools_solderpaste_group.addtool_entry, + "tools_solderpaste_z_start": self.ui.tools_defaults_form.tools_solderpaste_group.z_start_entry, + "tools_solderpaste_z_dispense": self.ui.tools_defaults_form.tools_solderpaste_group.z_dispense_entry, + "tools_solderpaste_z_stop": self.ui.tools_defaults_form.tools_solderpaste_group.z_stop_entry, + "tools_solderpaste_z_travel": self.ui.tools_defaults_form.tools_solderpaste_group.z_travel_entry, + "tools_solderpaste_z_toolchange": self.ui.tools_defaults_form.tools_solderpaste_group.z_toolchange_entry, + "tools_solderpaste_xy_toolchange": self.ui.tools_defaults_form.tools_solderpaste_group.xy_toolchange_entry, + "tools_solderpaste_frxy": self.ui.tools_defaults_form.tools_solderpaste_group.frxy_entry, + "tools_solderpaste_frz": self.ui.tools_defaults_form.tools_solderpaste_group.frz_entry, + "tools_solderpaste_frz_dispense": self.ui.tools_defaults_form.tools_solderpaste_group.frz_dispense_entry, + "tools_solderpaste_speedfwd": self.ui.tools_defaults_form.tools_solderpaste_group.speedfwd_entry, + "tools_solderpaste_dwellfwd": self.ui.tools_defaults_form.tools_solderpaste_group.dwellfwd_entry, + "tools_solderpaste_speedrev": self.ui.tools_defaults_form.tools_solderpaste_group.speedrev_entry, + "tools_solderpaste_dwellrev": self.ui.tools_defaults_form.tools_solderpaste_group.dwellrev_entry, + "tools_solderpaste_pp": self.ui.tools_defaults_form.tools_solderpaste_group.pp_combo, + "tools_sub_close_paths": self.ui.tools_defaults_form.tools_sub_group.close_paths_cb, + + # ####################################################################################################### + # ########################################## TOOLS 2 #################################################### + # ####################################################################################################### + + # Optimal Tool + "tools_opt_precision": self.ui.tools2_defaults_form.tools2_optimal_group.precision_sp, + + # Check Rules Tool + "tools_cr_trace_size": self.ui.tools2_defaults_form.tools2_checkrules_group.trace_size_cb, + "tools_cr_trace_size_val": self.ui.tools2_defaults_form.tools2_checkrules_group.trace_size_entry, + "tools_cr_c2c": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2copper_cb, + "tools_cr_c2c_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2copper_entry, + "tools_cr_c2o": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2ol_cb, + "tools_cr_c2o_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_copper2ol_entry, + "tools_cr_s2s": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2silk_cb, + "tools_cr_s2s_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2silk_entry, + "tools_cr_s2sm": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2sm_cb, + "tools_cr_s2sm_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2sm_entry, + "tools_cr_s2o": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2ol_cb, + "tools_cr_s2o_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_silk2ol_entry, + "tools_cr_sm2sm": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_sm2sm_cb, + "tools_cr_sm2sm_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_sm2sm_entry, + "tools_cr_ri": self.ui.tools2_defaults_form.tools2_checkrules_group.ring_integrity_cb, + "tools_cr_ri_val": self.ui.tools2_defaults_form.tools2_checkrules_group.ring_integrity_entry, + "tools_cr_h2h": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_d2d_cb, + "tools_cr_h2h_val": self.ui.tools2_defaults_form.tools2_checkrules_group.clearance_d2d_entry, + "tools_cr_dh": self.ui.tools2_defaults_form.tools2_checkrules_group.drill_size_cb, + "tools_cr_dh_val": self.ui.tools2_defaults_form.tools2_checkrules_group.drill_size_entry, + + # QRCode Tool + "tools_qrcode_version": self.ui.tools2_defaults_form.tools2_qrcode_group.version_entry, + "tools_qrcode_error": self.ui.tools2_defaults_form.tools2_qrcode_group.error_radio, + "tools_qrcode_box_size": self.ui.tools2_defaults_form.tools2_qrcode_group.bsize_entry, + "tools_qrcode_border_size": self.ui.tools2_defaults_form.tools2_qrcode_group.border_size_entry, + "tools_qrcode_qrdata": self.ui.tools2_defaults_form.tools2_qrcode_group.text_data, + "tools_qrcode_polarity": self.ui.tools2_defaults_form.tools2_qrcode_group.pol_radio, + "tools_qrcode_rounded": self.ui.tools2_defaults_form.tools2_qrcode_group.bb_radio, + "tools_qrcode_fill_color": self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry, + "tools_qrcode_back_color": self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry, + "tools_qrcode_sel_limit": self.ui.tools2_defaults_form.tools2_qrcode_group.sel_limit_entry, + + # Copper Thieving Tool + "tools_copper_thieving_clearance": self.ui.tools2_defaults_form.tools2_cfill_group.clearance_entry, + "tools_copper_thieving_margin": self.ui.tools2_defaults_form.tools2_cfill_group.margin_entry, + "tools_copper_thieving_reference": self.ui.tools2_defaults_form.tools2_cfill_group.reference_radio, + "tools_copper_thieving_box_type": self.ui.tools2_defaults_form.tools2_cfill_group.bbox_type_radio, + "tools_copper_thieving_circle_steps": self.ui.tools2_defaults_form.tools2_cfill_group.circlesteps_entry, + "tools_copper_thieving_fill_type": self.ui.tools2_defaults_form.tools2_cfill_group.fill_type_radio, + "tools_copper_thieving_dots_dia": self.ui.tools2_defaults_form.tools2_cfill_group.dot_dia_entry, + "tools_copper_thieving_dots_spacing": self.ui.tools2_defaults_form.tools2_cfill_group.dot_spacing_entry, + "tools_copper_thieving_squares_size": self.ui.tools2_defaults_form.tools2_cfill_group.square_size_entry, + "tools_copper_thieving_squares_spacing": + self.ui.tools2_defaults_form.tools2_cfill_group.squares_spacing_entry, + "tools_copper_thieving_lines_size": self.ui.tools2_defaults_form.tools2_cfill_group.line_size_entry, + "tools_copper_thieving_lines_spacing": self.ui.tools2_defaults_form.tools2_cfill_group.lines_spacing_entry, + "tools_copper_thieving_rb_margin": self.ui.tools2_defaults_form.tools2_cfill_group.rb_margin_entry, + "tools_copper_thieving_rb_thickness": self.ui.tools2_defaults_form.tools2_cfill_group.rb_thickness_entry, + "tools_copper_thieving_mask_clearance": self.ui.tools2_defaults_form.tools2_cfill_group.clearance_ppm_entry, + + # Fiducials Tool + "tools_fiducials_dia": self.ui.tools2_defaults_form.tools2_fiducials_group.dia_entry, + "tools_fiducials_margin": self.ui.tools2_defaults_form.tools2_fiducials_group.margin_entry, + "tools_fiducials_mode": self.ui.tools2_defaults_form.tools2_fiducials_group.mode_radio, + "tools_fiducials_second_pos": self.ui.tools2_defaults_form.tools2_fiducials_group.pos_radio, + "tools_fiducials_type": self.ui.tools2_defaults_form.tools2_fiducials_group.fid_type_radio, + "tools_fiducials_line_thickness": self.ui.tools2_defaults_form.tools2_fiducials_group.line_thickness_entry, + + # Calibration Tool + "tools_cal_calsource": self.ui.tools2_defaults_form.tools2_cal_group.cal_source_radio, + "tools_cal_travelz": self.ui.tools2_defaults_form.tools2_cal_group.travelz_entry, + "tools_cal_verz": self.ui.tools2_defaults_form.tools2_cal_group.verz_entry, + "tools_cal_zeroz": self.ui.tools2_defaults_form.tools2_cal_group.zeroz_cb, + "tools_cal_toolchangez": self.ui.tools2_defaults_form.tools2_cal_group.toolchangez_entry, + "tools_cal_toolchange_xy": self.ui.tools2_defaults_form.tools2_cal_group.toolchange_xy_entry, + "tools_cal_sec_point": self.ui.tools2_defaults_form.tools2_cal_group.second_point_radio, + + # Extract Drills Tool + "tools_edrills_hole_type": self.ui.tools2_defaults_form.tools2_edrills_group.hole_size_radio, + "tools_edrills_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_edrills_group.dia_entry, + "tools_edrills_hole_prop_factor": self.ui.tools2_defaults_form.tools2_edrills_group.factor_entry, + "tools_edrills_circular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.circular_ring_entry, + "tools_edrills_oblong_ring": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_ring_entry, + "tools_edrills_square_ring": self.ui.tools2_defaults_form.tools2_edrills_group.square_ring_entry, + "tools_edrills_rectangular_ring": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_ring_entry, + "tools_edrills_others_ring": self.ui.tools2_defaults_form.tools2_edrills_group.other_ring_entry, + "tools_edrills_circular": self.ui.tools2_defaults_form.tools2_edrills_group.circular_cb, + "tools_edrills_oblong": self.ui.tools2_defaults_form.tools2_edrills_group.oblong_cb, + "tools_edrills_square": self.ui.tools2_defaults_form.tools2_edrills_group.square_cb, + "tools_edrills_rectangular": self.ui.tools2_defaults_form.tools2_edrills_group.rectangular_cb, + "tools_edrills_others": self.ui.tools2_defaults_form.tools2_edrills_group.other_cb, + + # Punch Gerber Tool + "tools_punch_hole_type": self.ui.tools2_defaults_form.tools2_punch_group.hole_size_radio, + "tools_punch_hole_fixed_dia": self.ui.tools2_defaults_form.tools2_punch_group.dia_entry, + "tools_punch_hole_prop_factor": self.ui.tools2_defaults_form.tools2_punch_group.factor_entry, + "tools_punch_circular_ring": self.ui.tools2_defaults_form.tools2_punch_group.circular_ring_entry, + "tools_punch_oblong_ring": self.ui.tools2_defaults_form.tools2_punch_group.oblong_ring_entry, + "tools_punch_square_ring": self.ui.tools2_defaults_form.tools2_punch_group.square_ring_entry, + "tools_punch_rectangular_ring": self.ui.tools2_defaults_form.tools2_punch_group.rectangular_ring_entry, + "tools_punch_others_ring": self.ui.tools2_defaults_form.tools2_punch_group.other_ring_entry, + "tools_punch_circular": self.ui.tools2_defaults_form.tools2_punch_group.circular_cb, + "tools_punch_oblong": self.ui.tools2_defaults_form.tools2_punch_group.oblong_cb, + "tools_punch_square": self.ui.tools2_defaults_form.tools2_punch_group.square_cb, + "tools_punch_rectangular": self.ui.tools2_defaults_form.tools2_punch_group.rectangular_cb, + "tools_punch_others": self.ui.tools2_defaults_form.tools2_punch_group.other_cb, + + # Invert Gerber Tool + "tools_invert_margin": self.ui.tools2_defaults_form.tools2_invert_group.margin_entry, + "tools_invert_join_style": self.ui.tools2_defaults_form.tools2_invert_group.join_radio, + + # Utilities + # File associations + "fa_excellon": self.ui.util_defaults_form.fa_excellon_group.exc_list_text, + "fa_gcode": self.ui.util_defaults_form.fa_gcode_group.gco_list_text, + # "fa_geometry": self.ui.util_defaults_form.fa_geometry_group.close_paths_cb, + "fa_gerber": self.ui.util_defaults_form.fa_gerber_group.grb_list_text, + "util_autocomplete_keywords": self.ui.util_defaults_form.kw_group.kw_list_text, + + } + + def defaults_read_form(self): + """ + Will read all the values in the Preferences GUI and update the defaults dictionary. + + :return: None + """ + for option in self.defaults_form_fields: + try: + self.defaults[option] = self.defaults_form_fields[option].get_value() + except Exception as e: + log.debug("App.defaults_read_form() --> %s" % str(e)) + + def defaults_write_form(self, factor=None, fl_units=None, source_dict=None): + """ + Will set the values for all the GUI elements in Preferences GUI based on the values found in the + self.defaults dictionary. + + :param factor: will apply a factor to the values that written in the GUI elements + :param fl_units: current measuring units in FlatCAM: Metric or Inch + :param source_dict: the repository of options, usually is the self.defaults + :return: None + """ + + options_storage = self.defaults if source_dict is None else source_dict + + for option in options_storage: + if source_dict: + self.defaults_write_form_field(option, factor=factor, units=fl_units, defaults_dict=source_dict) + else: + self.defaults_write_form_field(option, factor=factor, units=fl_units) + + def defaults_write_form_field(self, field, factor=None, units=None, defaults_dict=None): + """ + Basically it is the worker in the self.defaults_write_form() + + :param field: the GUI element in Preferences GUI to be updated + :param factor: factor to be applied to the field parameter + :param units: current FLatCAM measuring units + :param defaults_dict: the defaults storage + :return: None, it updates GUI elements + """ + + def_dict = self.defaults if defaults_dict is None else defaults_dict + + try: + value = def_dict[field] + log.debug("value is " + str(value) + " and factor is "+str(factor)) + if factor is not None: + value *= factor + + form_field = self.defaults_form_fields[field] + if units is None: + form_field.set_value(value) + elif (units == 'IN' or units == 'MM') and (field == 'global_gridx' or field == 'global_gridy'): + form_field.set_value(value) + + except KeyError: + pass + except AttributeError: + log.debug(field) + + def show_preferences_gui(self): + """ + Called to initialize and show the Preferences GUI + + :return: None + """ + + gen_form = self.ui.general_defaults_form + try: + self.ui.general_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.general_scroll_area.setWidget(gen_form) + gen_form.show() + + ger_form = self.ui.gerber_defaults_form + try: + self.ui.gerber_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.gerber_scroll_area.setWidget(ger_form) + ger_form.show() + + exc_form = self.ui.excellon_defaults_form + try: + self.ui.excellon_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.excellon_scroll_area.setWidget(exc_form) + exc_form.show() + + geo_form = self.ui.geometry_defaults_form + try: + self.ui.geometry_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.geometry_scroll_area.setWidget(geo_form) + geo_form.show() + + cnc_form = self.ui.cncjob_defaults_form + try: + self.ui.cncjob_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.cncjob_scroll_area.setWidget(cnc_form) + cnc_form.show() + + tools_form = self.ui.tools_defaults_form + try: + self.ui.tools_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.tools_scroll_area.setWidget(tools_form) + tools_form.show() + + tools2_form = self.ui.tools2_defaults_form + try: + self.ui.tools2_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.tools2_scroll_area.setWidget(tools2_form) + tools2_form.show() + + fa_form = self.ui.util_defaults_form + try: + self.ui.fa_scroll_area.takeWidget() + except Exception: + log.debug("Nothing to remove") + self.ui.fa_scroll_area.setWidget(fa_form) + fa_form.show() + + # Initialize the color box's color in Preferences -> Global -> Colo + self.__init_color_pickers() + + # Button handlers + self.ui.pref_save_button.clicked.connect(lambda: self.on_save_button(save_to_file=True)) + self.ui.pref_apply_button.clicked.connect(lambda: self.on_save_button(save_to_file=False)) + self.ui.pref_close_button.clicked.connect(self.on_pref_close_button) + self.ui.pref_defaults_button.clicked.connect(self.on_restore_defaults_preferences) + + log.debug("Finished Preferences GUI form initialization.") + + def __init_color_pickers(self): + # Init Gerber Plot Colors + self.ui.gerber_defaults_form.gerber_gen_group.pf_color_entry.set_value(self.defaults['gerber_plot_fill']) + self.ui.gerber_defaults_form.gerber_gen_group.pf_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['gerber_plot_fill'])[:7]) + self.ui.gerber_defaults_form.gerber_gen_group.pf_color_alpha_spinner.set_value( + int(self.defaults['gerber_plot_fill'][7:9], 16)) + self.ui.gerber_defaults_form.gerber_gen_group.pf_color_alpha_slider.setValue( + int(self.defaults['gerber_plot_fill'][7:9], 16)) + + self.ui.gerber_defaults_form.gerber_gen_group.pl_color_entry.set_value(self.defaults['gerber_plot_line']) + self.ui.gerber_defaults_form.gerber_gen_group.pl_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['gerber_plot_line'])[:7]) + + # Init Excellon Plot Colors + self.ui.excellon_defaults_form.excellon_gen_group.fill_color_entry.set_value( + self.defaults['excellon_plot_fill']) + self.ui.excellon_defaults_form.excellon_gen_group.fill_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['excellon_plot_fill'])[:7]) + self.ui.excellon_defaults_form.excellon_gen_group.color_alpha_spinner.set_value( + int(self.defaults['excellon_plot_fill'][7:9], 16)) + self.ui.excellon_defaults_form.excellon_gen_group.color_alpha_slider.setValue( + int(self.defaults['excellon_plot_fill'][7:9], 16)) + + self.ui.excellon_defaults_form.excellon_gen_group.line_color_entry.set_value( + self.defaults['excellon_plot_line']) + self.ui.excellon_defaults_form.excellon_gen_group.line_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['excellon_plot_line'])[:7]) + + # Init Geometry Plot Colors + self.ui.geometry_defaults_form.geometry_gen_group.line_color_entry.set_value( + self.defaults['geometry_plot_line']) + self.ui.geometry_defaults_form.geometry_gen_group.line_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['geometry_plot_line'])[:7]) + + # Init CNCJob Travel Line Colors + self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_entry.set_value( + self.defaults['cncjob_travel_fill']) + self.ui.cncjob_defaults_form.cncjob_gen_group.tfill_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['cncjob_travel_fill'])[:7]) + self.ui.cncjob_defaults_form.cncjob_gen_group.tcolor_alpha_spinner.set_value( + int(self.defaults['cncjob_travel_fill'][7:9], 16)) + self.ui.cncjob_defaults_form.cncjob_gen_group.tcolor_alpha_slider.setValue( + int(self.defaults['cncjob_travel_fill'][7:9], 16)) + + self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_entry.set_value( + self.defaults['cncjob_travel_line']) + self.ui.cncjob_defaults_form.cncjob_gen_group.tline_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['cncjob_travel_line'])[:7]) + + # Init CNCJob Plot Colors + self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_entry.set_value( + self.defaults['cncjob_plot_fill']) + self.ui.cncjob_defaults_form.cncjob_gen_group.fill_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['cncjob_plot_fill'])[:7]) + + self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_entry.set_value( + self.defaults['cncjob_plot_line']) + self.ui.cncjob_defaults_form.cncjob_gen_group.line_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['cncjob_plot_line'])[:7]) + + # Init Left-Right Selection colors + self.ui.general_defaults_form.general_gui_group.sf_color_entry.set_value(self.defaults['global_sel_fill']) + self.ui.general_defaults_form.general_gui_group.sf_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_sel_fill'])[:7]) + self.ui.general_defaults_form.general_gui_group.sf_color_alpha_spinner.set_value( + int(self.defaults['global_sel_fill'][7:9], 16)) + self.ui.general_defaults_form.general_gui_group.sf_color_alpha_slider.setValue( + int(self.defaults['global_sel_fill'][7:9], 16)) + + self.ui.general_defaults_form.general_gui_group.sl_color_entry.set_value(self.defaults['global_sel_line']) + self.ui.general_defaults_form.general_gui_group.sl_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_sel_line'])[:7]) + + # Init Right-Left Selection colors + self.ui.general_defaults_form.general_gui_group.alt_sf_color_entry.set_value( + self.defaults['global_alt_sel_fill']) + self.ui.general_defaults_form.general_gui_group.alt_sf_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_alt_sel_fill'])[:7]) + self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_spinner.set_value( + int(self.defaults['global_sel_fill'][7:9], 16)) + self.ui.general_defaults_form.general_gui_group.alt_sf_color_alpha_slider.setValue( + int(self.defaults['global_sel_fill'][7:9], 16)) + + self.ui.general_defaults_form.general_gui_group.alt_sl_color_entry.set_value( + self.defaults['global_alt_sel_line']) + self.ui.general_defaults_form.general_gui_group.alt_sl_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_alt_sel_line'])[:7]) + + # Init Draw color and Selection Draw Color + self.ui.general_defaults_form.general_gui_group.draw_color_entry.set_value( + self.defaults['global_draw_color']) + self.ui.general_defaults_form.general_gui_group.draw_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_draw_color'])[:7]) + + self.ui.general_defaults_form.general_gui_group.sel_draw_color_entry.set_value( + self.defaults['global_sel_draw_color']) + self.ui.general_defaults_form.general_gui_group.sel_draw_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_sel_draw_color'])[:7]) + + # Init Project Items color + self.ui.general_defaults_form.general_gui_group.proj_color_entry.set_value( + self.defaults['global_proj_item_color']) + self.ui.general_defaults_form.general_gui_group.proj_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_proj_item_color'])[:7]) + + # Init Project Disabled Items color + self.ui.general_defaults_form.general_gui_group.proj_color_dis_entry.set_value( + self.defaults['global_proj_item_dis_color']) + self.ui.general_defaults_form.general_gui_group.proj_color_dis_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_proj_item_dis_color'])[:7]) + + # Init Project Disabled Items color + self.ui.general_defaults_form.general_app_set_group.mouse_cursor_entry.set_value( + self.defaults['global_cursor_color']) + self.ui.general_defaults_form.general_app_set_group.mouse_cursor_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['global_cursor_color'])[:7]) + + # Init the Annotation CNC Job color + self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_entry.set_value( + self.defaults['cncjob_annotation_fontcolor']) + self.ui.cncjob_defaults_form.cncjob_adv_opt_group.annotation_fontcolor_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['cncjob_annotation_fontcolor'])[:7]) + + # Init the Tool Film color + self.ui.tools_defaults_form.tools_film_group.film_color_entry.set_value( + self.defaults['tools_film_color']) + self.ui.tools_defaults_form.tools_film_group.film_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['tools_film_color'])[:7] + ) + + # Init the Tool QRCode colors + self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_entry.set_value( + self.defaults['tools_qrcode_fill_color']) + self.ui.tools2_defaults_form.tools2_qrcode_group.fill_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['tools_qrcode_fill_color'])[:7]) + + self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_entry.set_value( + self.defaults['tools_qrcode_back_color']) + self.ui.tools2_defaults_form.tools2_qrcode_group.back_color_button.setStyleSheet( + "background-color:%s;" + "border-color: dimgray" % str(self.defaults['tools_qrcode_back_color'])[:7]) + + def on_save_button(self, save_to_file=True): + log.debug("on_save_button() --> Applying preferences to file.") + + # Preferences saved, update flag + self.preferences_changed_flag = False + + # Preferences save, update the color of the Preferences Tab text + for idx in range(self.ui.plot_tab_area.count()): + if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): + self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) + + # restore the default stylesheet by setting a blank one + self.ui.pref_apply_button.setStyleSheet("") + + self.inform.emit('%s' % _("Preferences applied.")) + + # make sure we update the self.current_defaults dict used to undo changes to self.defaults + self.defaults.current_defaults.update(self.defaults) + + if save_to_file: + self.save_defaults(silent=False) + # load the defaults so they are updated into the app + self.defaults.load(filename=os.path.join(self.data_path, 'current_defaults.FlatConfig')) + + # Re-fresh project options + self.ui.app.on_options_app2project() + + settgs = QSettings("Open Source", "FlatCAM") + + # save the notebook font size + fsize = self.ui.general_defaults_form.general_app_set_group.notebook_font_size_spinner.get_value() + settgs.setValue('notebook_font_size', fsize) + + # save the axis font size + g_fsize = self.ui.general_defaults_form.general_app_set_group.axis_font_size_spinner.get_value() + settgs.setValue('axis_font_size', g_fsize) + + # save the textbox font size + tb_fsize = self.ui.general_defaults_form.general_app_set_group.textbox_font_size_spinner.get_value() + settgs.setValue('textbox_font_size', tb_fsize) + + settgs.setValue( + 'machinist', + 1 if self.ui.general_defaults_form.general_app_set_group.machinist_cb.get_value() else 0 + ) + + # This will write the setting to the platform specific storage. + del settgs + + if save_to_file: + # close the tab and delete it + for idx in range(self.ui.plot_tab_area.count()): + if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): + self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) + self.ui.plot_tab_area.closeTab(idx) + break + + def on_pref_close_button(self): + # Preferences saved, update flag + self.preferences_changed_flag = False + self.ignore_tab_close_event = True + + try: + self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.disconnect() + except (TypeError, AttributeError): + pass + self.defaults_write_form(source_dict=self.defaults.current_defaults) + self.ui.general_defaults_form.general_app_group.units_radio.activated_custom.connect( + lambda: self.ui.app.on_toggle_units(no_pref=False)) + self.defaults.update(self.defaults.current_defaults) + + # Preferences save, update the color of the Preferences Tab text + for idx in range(self.ui.plot_tab_area.count()): + if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): + self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('black')) + self.ui.plot_tab_area.closeTab(idx) + break + + self.inform.emit('%s' % _("Preferences closed without saving.")) + self.ignore_tab_close_event = False + + def on_restore_defaults_preferences(self): + """ + Loads the application's factory default settings into ``self.defaults``. + + :return: None + """ + log.debug("on_restore_defaults_preferences()") + self.defaults.reset_to_factory_defaults() + self.on_preferences_edited() + self.inform.emit('[success] %s' % _("Preferences default values are restored.")) + + def save_defaults(self, silent=False, data_path=None, first_time=False): + """ + Saves application default options + ``self.defaults`` to current_defaults.FlatConfig file. + Save the toolbars visibility status to the preferences file (current_defaults.FlatConfig) to be + used at the next launch of the application. + + :param silent: Whether to display a message in status bar or not; boolean + :param data_path: The path where to save the preferences file (current_defaults.FlatConfig) + When the application is portable it should be a mobile location. + :param first_time: Boolean. If True will execute some code when the app is run first time + :return: None + """ + self.defaults.report_usage("save_defaults") + + if data_path is None: + data_path = self.data_path + + 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.FlatConfig") + try: + self.defaults.write(filename=filename) + except Exception as e: + log.error("save_defaults() --> Failed to write defaults to file %s" % str(e)) + self.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed to write defaults to file."), str(filename))) + return + + if not silent: + self.inform.emit('[success] %s' % _("Preferences saved.")) + + # 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.toolbargeo.isVisible(): + tb_status += 2 + + if self.ui.toolbarview.isVisible(): + tb_status += 4 + + if self.ui.toolbartools.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.snap_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. + Will color the Preferences tab text to Red color. + :return: + """ + if self.preferences_changed_flag is False: + self.inform.emit('[WARNING_NOTCL] %s' % _("Preferences edited but not saved.")) + + for idx in range(self.ui.plot_tab_area.count()): + if self.ui.plot_tab_area.tabText(idx) == _("Preferences"): + self.ui.plot_tab_area.tabBar.setTabTextColor(idx, QtGui.QColor('red')) + + self.ui.pref_apply_button.setStyleSheet("QPushButton {color: red;}") + + self.preferences_changed_flag = True + + def on_close_preferences_tab(self): + if self.ignore_tab_close_event: + return + + # disconnect + for idx in range(self.ui.pref_tab_area.count()): + for tb in self.ui.pref_tab_area.widget(idx).findChildren(QtCore.QObject): + try: + tb.textEdited.disconnect(self.on_preferences_edited) + except (TypeError, AttributeError): + pass + + try: + tb.modificationChanged.disconnect(self.on_preferences_edited) + except (TypeError, AttributeError): + pass + + try: + tb.toggled.disconnect(self.on_preferences_edited) + except (TypeError, AttributeError): + pass + + try: + tb.valueChanged.disconnect(self.on_preferences_edited) + except (TypeError, AttributeError): + pass + + try: + tb.currentIndexChanged.disconnect(self.on_preferences_edited) + except (TypeError, AttributeError): + pass + + # Prompt user to save + if self.preferences_changed_flag is True: + msgbox = QtWidgets.QMessageBox() + msgbox.setText(_("One or more values are changed.\n" + "Do you want to save the Preferences?")) + msgbox.setWindowTitle(_("Save Preferences")) + msgbox.setWindowIcon(QtGui.QIcon(self.ui.app.resource_location + '/save_as.png')) + + bt_yes = msgbox.addButton(_('Yes'), QtWidgets.QMessageBox.YesRole) + msgbox.addButton(_('No'), QtWidgets.QMessageBox.NoRole) + + msgbox.setDefaultButton(bt_yes) + msgbox.exec_() + response = msgbox.clickedButton() + + if response == bt_yes: + self.on_save_button(save_to_file=True) + self.inform.emit('[success] %s' % _("Preferences saved.")) + else: + self.preferences_changed_flag = False + self.inform.emit('') + return + + class OptionsGroupUI(QtWidgets.QGroupBox): app = None @@ -402,9 +1516,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.layout_combo, 4, 1) # Set the current index for layout_combo - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("layout"): - layout = settings.value('layout', type=str) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("layout"): + layout = qsettings.value('layout', type=str) idx = self.layout_combo.findText(layout.capitalize()) self.layout_combo.setCurrentIndex(idx) @@ -431,9 +1545,9 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): "It will be applied at the next app start.") ) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("hdpi"): - self.hdpi_cb.set_value(settings.value('hdpi', type=int)) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("hdpi"): + self.hdpi_cb.set_value(qsettings.value('hdpi', type=int)) else: self.hdpi_cb.set_value(False) self.hdpi_cb.stateChanged.connect(self.handle_hdpi) @@ -722,7 +1836,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): # Setting Editor Draw colors signals self.draw_color_entry.editingFinished.connect(self.on_draw_color_entry) - self.draw_color_button.clicked.connect( self.on_draw_color_button) + self.draw_color_button.clicked.connect(self.on_draw_color_button) self.sel_draw_color_entry.editingFinished.connect(self.on_sel_draw_color_entry) self.sel_draw_color_button.clicked.connect(self.on_sel_draw_color_button) @@ -735,32 +1849,33 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): self.layout_combo.activated.connect(self.on_layout) - def on_theme_change(self): val = self.theme_radio.get_value() - t_settings = QSettings("Open Source", "FlatCAM") - t_settings.setValue('theme', val) + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('theme', val) # This will write the setting to the platform specific storage. - del t_settings + del qsettings self.app.on_app_restart() - def handle_style(self, style): + @staticmethod + def handle_style(style): # set current style - settings = QSettings("Open Source", "FlatCAM") - settings.setValue('style', style) + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('style', style) # This will write the setting to the platform specific storage. - del settings + del qsettings - def handle_hdpi(self, state): + @staticmethod + def handle_hdpi(state): # set current HDPI - settings = QSettings("Open Source", "FlatCAM") - settings.setValue('hdpi', state) + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('hdpi', state) # This will write the setting to the platform specific storage. - del settings + del qsettings # Setting selection colors (left - right) handlers def on_sf_color_entry(self): @@ -959,7 +2074,7 @@ class GeneralGUIPrefGroupUI(OptionsGroupUI): :param lay: Type of layout to be set on the toolbard :return: None """ - self.app.report_usage("on_layout()") + self.app.defaults.report_usage("on_layout()") if lay: current_layout = lay else: @@ -1116,9 +2231,9 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): theme = 'white' if theme == 'white': - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' else: - self.resource_loc = 'share' + self.resource_loc = 'assets/resources' # Create a grid layout for the Application general settings grid0 = QtWidgets.QGridLayout() @@ -1290,9 +2405,9 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.notebook_font_size_spinner.set_range(8, 40) self.notebook_font_size_spinner.setWrapping(True) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("notebook_font_size"): - self.notebook_font_size_spinner.set_value(settings.value('notebook_font_size', type=int)) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("notebook_font_size"): + self.notebook_font_size_spinner.set_value(qsettings.value('notebook_font_size', type=int)) else: self.notebook_font_size_spinner.set_value(12) @@ -1309,9 +2424,9 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.axis_font_size_spinner.set_range(0, 40) self.axis_font_size_spinner.setWrapping(True) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("axis_font_size"): - self.axis_font_size_spinner.set_value(settings.value('axis_font_size', type=int)) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("axis_font_size"): + self.axis_font_size_spinner.set_value(qsettings.value('axis_font_size', type=int)) else: self.axis_font_size_spinner.set_value(8) @@ -1329,8 +2444,8 @@ class GeneralAPPSetGroupUI(OptionsGroupUI): self.textbox_font_size_spinner.set_range(8, 40) self.textbox_font_size_spinner.setWrapping(True) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): self.textbox_font_size_spinner.set_value(settings.value('textbox_font_size', type=int)) else: self.textbox_font_size_spinner.set_value(10) @@ -1695,8 +2810,8 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): _("Enable display of the splash screen at application startup.") ) - settings = QSettings("Open Source", "FlatCAM") - if settings.value("splash_screen"): + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.value("splash_screen"): self.splash_cb.set_value(True) else: self.splash_cb.set_value(False) @@ -1920,12 +3035,13 @@ class GeneralAppPrefGroupUI(OptionsGroupUI): self.language_apply_btn.clicked.connect(lambda: fcTranslate.on_language_apply_click(app=self.app, restart=True)) - def on_splash_changed(self, state): - settings = QSettings("Open Source", "FlatCAM") - settings.setValue('splash_screen', 1) if state else settings.setValue('splash_screen', 0) + @staticmethod + def on_splash_changed(state): + qsettings = QSettings("Open Source", "FlatCAM") + qsettings.setValue('splash_screen', 1) if state else qsettings.setValue('splash_screen', 0) # This will write the setting to the platform specific storage. - del settings + del qsettings class GerberGenPrefGroupUI(OptionsGroupUI): @@ -1933,6 +3049,7 @@ class GerberGenPrefGroupUI(OptionsGroupUI): # OptionsGroupUI.__init__(self, "Gerber General Preferences", parent=parent) super(GerberGenPrefGroupUI, self).__init__(self) + self.parent = parent self.setTitle(str(_("Gerber General"))) self.decimals = decimals @@ -2129,7 +3246,7 @@ class GerberGenPrefGroupUI(OptionsGroupUI): def on_pf_color_entry(self): self.app.defaults['gerber_plot_fill'] = self.pf_color_entry.get_value()[:7] + \ self.app.defaults['gerber_plot_fill'][7:9] - self.pf_color_button.setStyleSheet("background-color:%s" % str(self.defaults['gerber_plot_fill'])[:7]) + self.pf_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['gerber_plot_fill'])[:7]) def on_pf_color_button(self): current_color = QtGui.QColor(self.app.defaults['gerber_plot_fill'][:7]) @@ -2163,7 +3280,7 @@ class GerberGenPrefGroupUI(OptionsGroupUI): def on_pl_color_entry(self): self.app.defaults['gerber_plot_line'] = self.pl_color_entry.get_value()[:7] + \ self.app.defaults['gerber_plot_line'][7:9] - self.pl_color_button.setStyleSheet("background-color:%s" % str(self.defaults['gerber_plot_line'])[:7]) + self.pl_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['gerber_plot_line'])[:7]) def on_pl_color_button(self): current_color = QtGui.QColor(self.app.defaults['gerber_plot_line'][:7]) @@ -2396,7 +3513,7 @@ class GerberAdvOptPrefGroupUI(OptionsGroupUI): # Tool Type self.tool_type_label = QtWidgets.QLabel('%s' % _('Tool Type')) self.tool_type_label.setToolTip( - _("Choose what tool to use for Gerber isolation:\n" + _("Choose which tool to use for Gerber isolation:\n" "'Circular' or 'V-shape'.\n" "When the 'V-shape' is selected then the tool\n" "diameter will depend on the chosen cut depth.") @@ -3230,12 +4347,12 @@ class ExcellonGenPrefGroupUI(OptionsGroupUI): self.app.defaults['excellon_plot_line'] = new_val_line def on_excellon_defaults_button(self): - self.app.defaults_form_fields["excellon_format_lower_in"].set_value('4') - self.app.defaults_form_fields["excellon_format_upper_in"].set_value('2') - self.app.defaults_form_fields["excellon_format_lower_mm"].set_value('3') - self.app.defaults_form_fields["excellon_format_upper_mm"].set_value('3') - self.app.defaults_form_fields["excellon_zeros"].set_value('L') - self.app.defaults_form_fields["excellon_units"].set_value('INCH') + self.app.preferencesUiManager.defaults_form_fields["excellon_format_lower_in"].set_value('4') + self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_in"].set_value('2') + self.app.preferencesUiManager.defaults_form_fields["excellon_format_lower_mm"].set_value('3') + self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_mm"].set_value('3') + self.app.preferencesUiManager.defaults_form_fields["excellon_zeros"].set_value('L') + self.app.preferencesUiManager.defaults_form_fields["excellon_units"].set_value('INCH') class ExcellonOptPrefGroupUI(OptionsGroupUI): @@ -5063,9 +6180,9 @@ class CNCJobOptPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.export_gcode_label) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): - tb_fsize = settings.value('textbox_font_size', type=int) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): + tb_fsize = qsettings.value('textbox_font_size', type=int) else: tb_fsize = 10 font = QtGui.QFont() @@ -5140,9 +6257,9 @@ class CNCJobAdvOptPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(toolchangelabel) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): - tb_fsize = settings.value('textbox_font_size', type=int) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): + tb_fsize = qsettings.value('textbox_font_size', type=int) else: tb_fsize = 10 font = QtGui.QFont() @@ -6926,7 +8043,6 @@ class ToolsTransformPrefGroupUI(OptionsGroupUI): grid0.addWidget(self.buffer_rounded_cb, 19, 0, 1, 2) - self.layout.addStretch() @@ -8420,7 +9536,7 @@ class Tools2PunchGerberPrefGroupUI(OptionsGroupUI): "- Excellon Object-> the Excellon object drills center will serve as reference.\n" "- Fixed Diameter -> will try to use the pads center as reference adding fixed diameter holes.\n" "- Fixed Annular Ring -> will try to keep a set annular ring.\n" - "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.\n") + "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.") ) grid_lay.addWidget(self.hole_size_label, 9, 0) grid_lay.addWidget(self.hole_size_radio, 9, 1) @@ -8636,9 +9752,9 @@ class FAExcPrefGroupUI(OptionsGroupUI): ) self.vertical_lay.addWidget(list_label) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): - tb_fsize = settings.value('textbox_font_size', type=int) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): + tb_fsize = qsettings.value('textbox_font_size', type=int) else: tb_fsize = 10 @@ -8709,9 +9825,9 @@ class FAGcoPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.gco_list_label) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): - tb_fsize = settings.value('textbox_font_size', type=int) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): + tb_fsize = qsettings.value('textbox_font_size', type=int) else: tb_fsize = 10 @@ -8779,9 +9895,9 @@ class FAGrbPrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.grb_list_label) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): - tb_fsize = settings.value('textbox_font_size', type=int) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): + tb_fsize = qsettings.value('textbox_font_size', type=int) else: tb_fsize = 10 @@ -8851,9 +9967,9 @@ class AutoCompletePrefGroupUI(OptionsGroupUI): ) self.layout.addWidget(self.grb_list_label) - settings = QSettings("Open Source", "FlatCAM") - if settings.contains("textbox_font_size"): - tb_fsize = settings.value('textbox_font_size', type=int) + qsettings = QSettings("Open Source", "FlatCAM") + if qsettings.contains("textbox_font_size"): + tb_fsize = qsettings.value('textbox_font_size', type=int) else: tb_fsize = 10 diff --git a/flatcamObjects/FlatCAMCNCJob.py b/flatcamObjects/FlatCAMCNCJob.py new file mode 100644 index 00000000..fa73b56c --- /dev/null +++ b/flatcamObjects/FlatCAMCNCJob.py @@ -0,0 +1,1221 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + +from copy import deepcopy +from io import StringIO +from datetime import datetime + +from flatcamEditors.FlatCAMTextEditor import TextEditor +from flatcamObjects.FlatCAMObj import * + +from camlib import CNCjob + +import os +import sys +import math + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class CNCJobObject(FlatCAMObj, CNCjob): + """ + Represents G-Code. + """ + optionChanged = QtCore.pyqtSignal(str) + ui_type = CNCObjectUI + + def __init__(self, name, units="in", kind="generic", z_move=0.1, + feedrate=3.0, feedrate_rapid=3.0, z_cut=-0.002, tooldia=0.0, + spindlespeed=None): + + log.debug("Creating CNCJob object...") + + self.decimals = self.app.decimals + + CNCjob.__init__(self, units=units, kind=kind, z_move=z_move, + feedrate=feedrate, feedrate_rapid=feedrate_rapid, z_cut=z_cut, tooldia=tooldia, + spindlespeed=spindlespeed, steps_per_circle=int(self.app.defaults["cncjob_steps_per_circle"])) + + FlatCAMObj.__init__(self, name) + + self.kind = "cncjob" + + self.options.update({ + "plot": True, + "tooldia": 0.03937, # 0.4mm in inches + "append": "", + "prepend": "", + "dwell": False, + "dwelltime": 1, + "type": 'Geometry', + "toolchange_macro": '', + "toolchange_macro_enable": False + }) + + ''' + This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the + diameter of the tools and the value is another dict that will hold the data under the following form: + {tooldia: { + 'tooluid': 1, + 'offset': 'Path', + 'type_item': 'Rough', + 'tool_type': 'C1', + 'data': {} # a dict to hold the parameters + 'gcode': "" # a string with the actual GCODE + 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry + (cut or move) + 'solid_geometry': [] + }, + ... + } + It is populated in the GeometryObject.mtool_gen_cncjob() + BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ... + ''' + self.cnc_tools = {} + + ''' + This is a dict of dictionaries. Each dict is associated with a tool present in the file. The key is the + diameter of the tools and the value is another dict that will hold the data under the following form: + {tooldia: { + 'tool': int, + 'nr_drills': int, + 'nr_slots': int, + 'offset': float, + 'data': {} # a dict to hold the parameters + 'gcode': "" # a string with the actual GCODE + 'gcode_parsed': {} # dictionary holding the CNCJob geometry and type of geometry (cut or move) + 'solid_geometry': [] + }, + ... + } + It is populated in the ExcellonObject.on_create_cncjob_click() but actually + it's done in camlib.CNCJob.generate_from_excellon_by_tool() + BEWARE: I rely on the ordered nature of the Python 3.7 dictionary. Things might change ... + ''' + self.exc_cnc_tools = {} + + # flag to store if the CNCJob is part of a special group of CNCJob objects that can't be processed by the + # default engine of FlatCAM. They generated by some of tools and are special cases of CNCJob objects. + self.special_group = None + + # for now it show if the plot will be done for multi-tool CNCJob (True) or for single tool + # (like the one in the TCL Command), False + self.multitool = False + + # determine if the GCode was generated out of a Excellon object or a Geometry object + self.origin_kind = None + + # used for parsing the GCode lines to adjust the GCode when the GCode is offseted or scaled + gcodex_re_string = r'(?=.*(X[-\+]?\d*\.\d*))' + self.g_x_re = re.compile(gcodex_re_string) + gcodey_re_string = r'(?=.*(Y[-\+]?\d*\.\d*))' + self.g_y_re = re.compile(gcodey_re_string) + gcodez_re_string = r'(?=.*(Z[-\+]?\d*\.\d*))' + self.g_z_re = re.compile(gcodez_re_string) + + gcodef_re_string = r'(?=.*(F[-\+]?\d*\.\d*))' + self.g_f_re = re.compile(gcodef_re_string) + gcodet_re_string = r'(?=.*(\=\s*[-\+]?\d*\.\d*))' + self.g_t_re = re.compile(gcodet_re_string) + + gcodenr_re_string = r'([+-]?\d*\.\d+)' + self.g_nr_re = re.compile(gcodenr_re_string) + + # Attributes to be included in serialization + # Always append to it because it carries contents + # from predecessors. + self.ser_attrs += ['options', 'kind', 'origin_kind', 'cnc_tools', 'exc_cnc_tools', 'multitool'] + + if self.app.is_legacy is False: + self.text_col = self.app.plotcanvas.new_text_collection() + self.text_col.enabled = True + self.annotation = self.app.plotcanvas.new_text_group(collection=self.text_col) + + self.gcode_editor_tab = None + + self.units_found = self.app.defaults['units'] + + def build_ui(self): + self.ui_disconnect() + + FlatCAMObj.build_ui(self) + self.units = self.app.defaults['units'].upper() + + # if the FlatCAM object is Excellon don't build the CNC Tools Table but hide it + self.ui.cnc_tools_table.hide() + if self.cnc_tools: + self.ui.cnc_tools_table.show() + self.build_cnc_tools_table() + + self.ui.exc_cnc_tools_table.hide() + if self.exc_cnc_tools: + self.ui.exc_cnc_tools_table.show() + self.build_excellon_cnc_tools() + # + self.ui_connect() + + def build_cnc_tools_table(self): + tool_idx = 0 + + n = len(self.cnc_tools) + self.ui.cnc_tools_table.setRowCount(n) + + for dia_key, dia_value in self.cnc_tools.items(): + + tool_idx += 1 + row_no = tool_idx - 1 + + t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) + # id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.cnc_tools_table.setItem(row_no, 0, t_id) # Tool name/id + + # Make sure that the tool diameter when in MM is with no more than 2 decimals. + # There are no tool bits in MM with more than 2 decimals diameter. + # For INCH the decimals should be no more than 4. There are no tools under 10mils. + + dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['tooldia']))) + + offset_txt = list(str(dia_value['offset'])) + offset_txt[0] = offset_txt[0].upper() + offset_item = QtWidgets.QTableWidgetItem(''.join(offset_txt)) + type_item = QtWidgets.QTableWidgetItem(str(dia_value['type'])) + tool_type_item = QtWidgets.QTableWidgetItem(str(dia_value['tool_type'])) + + t_id.setFlags(QtCore.Qt.ItemIsEnabled) + dia_item.setFlags(QtCore.Qt.ItemIsEnabled) + offset_item.setFlags(QtCore.Qt.ItemIsEnabled) + type_item.setFlags(QtCore.Qt.ItemIsEnabled) + tool_type_item.setFlags(QtCore.Qt.ItemIsEnabled) + + # hack so the checkbox stay centered in the table cell + # used this: + # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row + # plot_item = QtWidgets.QWidget() + # checkbox = FCCheckBox() + # checkbox.setCheckState(QtCore.Qt.Checked) + # qhboxlayout = QtWidgets.QHBoxLayout(plot_item) + # qhboxlayout.addWidget(checkbox) + # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter) + # qhboxlayout.setContentsMargins(0, 0, 0, 0) + plot_item = FCCheckBox() + plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) + tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_key)) + if self.ui.plot_cb.isChecked(): + plot_item.setChecked(True) + + self.ui.cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter + self.ui.cnc_tools_table.setItem(row_no, 2, offset_item) # Offset + self.ui.cnc_tools_table.setItem(row_no, 3, type_item) # Toolpath Type + self.ui.cnc_tools_table.setItem(row_no, 4, tool_type_item) # Tool Type + + # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ## + self.ui.cnc_tools_table.setItem(row_no, 5, tool_uid_item) # Tool unique ID) + self.ui.cnc_tools_table.setCellWidget(row_no, 6, plot_item) + + # make the diameter column editable + # for row in range(tool_idx): + # self.ui.cnc_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable | + # QtCore.Qt.ItemIsEnabled) + + for row in range(tool_idx): + self.ui.cnc_tools_table.item(row, 0).setFlags( + self.ui.cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable) + + self.ui.cnc_tools_table.resizeColumnsToContents() + self.ui.cnc_tools_table.resizeRowsToContents() + + vertical_header = self.ui.cnc_tools_table.verticalHeader() + # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + vertical_header.hide() + self.ui.cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + horizontal_header = self.ui.cnc_tools_table.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 20) + horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(4, 40) + horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(4, 17) + # horizontal_header.setStretchLastSection(True) + self.ui.cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.ui.cnc_tools_table.setColumnWidth(0, 20) + self.ui.cnc_tools_table.setColumnWidth(4, 40) + self.ui.cnc_tools_table.setColumnWidth(6, 17) + + # self.ui.geo_tools_table.setSortingEnabled(True) + + self.ui.cnc_tools_table.setMinimumHeight(self.ui.cnc_tools_table.getHeight()) + self.ui.cnc_tools_table.setMaximumHeight(self.ui.cnc_tools_table.getHeight()) + + def build_excellon_cnc_tools(self): + tool_idx = 0 + + n = len(self.exc_cnc_tools) + self.ui.exc_cnc_tools_table.setRowCount(n) + + for tooldia_key, dia_value in self.exc_cnc_tools.items(): + + tool_idx += 1 + row_no = tool_idx - 1 + + t_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) + dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooldia_key))) + nr_drills_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_drills'])) + nr_slots_item = QtWidgets.QTableWidgetItem('%d' % int(dia_value['nr_slots'])) + cutz_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(dia_value['offset_z']) + self.z_cut)) + + t_id.setFlags(QtCore.Qt.ItemIsEnabled) + dia_item.setFlags(QtCore.Qt.ItemIsEnabled) + nr_drills_item.setFlags(QtCore.Qt.ItemIsEnabled) + nr_slots_item.setFlags(QtCore.Qt.ItemIsEnabled) + cutz_item.setFlags(QtCore.Qt.ItemIsEnabled) + + # hack so the checkbox stay centered in the table cell + # used this: + # https://stackoverflow.com/questions/32458111/pyqt-allign-checkbox-and-put-it-in-every-row + # plot_item = QtWidgets.QWidget() + # checkbox = FCCheckBox() + # checkbox.setCheckState(QtCore.Qt.Checked) + # qhboxlayout = QtWidgets.QHBoxLayout(plot_item) + # qhboxlayout.addWidget(checkbox) + # qhboxlayout.setAlignment(QtCore.Qt.AlignCenter) + # qhboxlayout.setContentsMargins(0, 0, 0, 0) + + plot_item = FCCheckBox() + plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) + tool_uid_item = QtWidgets.QTableWidgetItem(str(dia_value['tool'])) + if self.ui.plot_cb.isChecked(): + plot_item.setChecked(True) + + # TODO until the feature of individual plot for an Excellon tool is implemented + plot_item.setDisabled(True) + + self.ui.exc_cnc_tools_table.setItem(row_no, 0, t_id) # Tool name/id + self.ui.exc_cnc_tools_table.setItem(row_no, 1, dia_item) # Diameter + self.ui.exc_cnc_tools_table.setItem(row_no, 2, nr_drills_item) # Nr of drills + self.ui.exc_cnc_tools_table.setItem(row_no, 3, nr_slots_item) # Nr of slots + + # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY # ## + self.ui.exc_cnc_tools_table.setItem(row_no, 4, tool_uid_item) # Tool unique ID) + self.ui.exc_cnc_tools_table.setItem(row_no, 5, cutz_item) + self.ui.exc_cnc_tools_table.setCellWidget(row_no, 6, plot_item) + + for row in range(tool_idx): + self.ui.exc_cnc_tools_table.item(row, 0).setFlags( + self.ui.exc_cnc_tools_table.item(row, 0).flags() ^ QtCore.Qt.ItemIsSelectable) + + self.ui.exc_cnc_tools_table.resizeColumnsToContents() + self.ui.exc_cnc_tools_table.resizeRowsToContents() + + vertical_header = self.ui.exc_cnc_tools_table.verticalHeader() + vertical_header.hide() + self.ui.exc_cnc_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + horizontal_header = self.ui.exc_cnc_tools_table.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 20) + horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents) + + horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed) + + # horizontal_header.setStretchLastSection(True) + self.ui.exc_cnc_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.ui.exc_cnc_tools_table.setColumnWidth(0, 20) + self.ui.exc_cnc_tools_table.setColumnWidth(6, 17) + + self.ui.exc_cnc_tools_table.setMinimumHeight(self.ui.exc_cnc_tools_table.getHeight()) + self.ui.exc_cnc_tools_table.setMaximumHeight(self.ui.exc_cnc_tools_table.getHeight()) + + def set_ui(self, ui): + FlatCAMObj.set_ui(self, ui) + + log.debug("FlatCAMCNCJob.set_ui()") + + assert isinstance(self.ui, CNCObjectUI), \ + "Expected a CNCObjectUI, got %s" % type(self.ui) + + self.units = self.app.defaults['units'].upper() + self.units_found = self.app.defaults['units'] + + # this signal has to be connected to it's slot before the defaults are populated + # the decision done in the slot has to override the default value set bellow + self.ui.toolchange_cb.toggled.connect(self.on_toolchange_custom_clicked) + + self.form_fields.update({ + "plot": self.ui.plot_cb, + "tooldia": self.ui.tooldia_entry, + "append": self.ui.append_text, + "prepend": self.ui.prepend_text, + "toolchange_macro": self.ui.toolchange_text, + "toolchange_macro_enable": self.ui.toolchange_cb + }) + + # Fill form fields only on object create + self.to_form() + + # this means that the object that created this CNCJob was an Excellon or Geometry + try: + if self.travel_distance: + self.ui.t_distance_label.show() + self.ui.t_distance_entry.setVisible(True) + self.ui.t_distance_entry.setDisabled(True) + self.ui.t_distance_entry.set_value('%.*f' % (self.decimals, float(self.travel_distance))) + self.ui.units_label.setText(str(self.units).lower()) + self.ui.units_label.setDisabled(True) + + self.ui.t_time_label.show() + self.ui.t_time_entry.setVisible(True) + self.ui.t_time_entry.setDisabled(True) + # if time is more than 1 then we have minutes, else we have seconds + if self.routing_time > 1: + self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(self.routing_time)))) + self.ui.units_time_label.setText('min') + else: + time_r = self.routing_time * 60 + self.ui.t_time_entry.set_value('%.*f' % (self.decimals, math.ceil(float(time_r)))) + self.ui.units_time_label.setText('sec') + self.ui.units_time_label.setDisabled(True) + except AttributeError: + pass + + if self.multitool is False: + self.ui.tooldia_entry.show() + self.ui.updateplot_button.show() + else: + self.ui.tooldia_entry.hide() + self.ui.updateplot_button.hide() + + # set the kind of geometries are plotted by default with plot2() from camlib.CNCJob + self.ui.cncplot_method_combo.set_value(self.app.defaults["cncjob_plot_kind"]) + + try: + self.ui.annotation_cb.stateChanged.disconnect(self.on_annotation_change) + except (TypeError, AttributeError): + pass + self.ui.annotation_cb.stateChanged.connect(self.on_annotation_change) + + # set if to display text annotations + self.ui.annotation_cb.set_value(self.app.defaults["cncjob_annotation"]) + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText(_( + 'Basic' + )) + + self.ui.cnc_frame.hide() + else: + self.ui.level.setText(_( + 'Advanced' + )) + self.ui.cnc_frame.show() + + self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click) + self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click) + self.ui.modify_gcode_button.clicked.connect(self.on_edit_code_click) + + self.ui.tc_variable_combo.currentIndexChanged[str].connect(self.on_cnc_custom_parameters) + + self.ui.cncplot_method_combo.activated_custom.connect(self.on_plot_kind_change) + + def on_cnc_custom_parameters(self, signal_text): + if signal_text == 'Parameters': + return + else: + self.ui.toolchange_text.insertPlainText('%%%s%%' % signal_text) + + def ui_connect(self): + for row in range(self.ui.cnc_tools_table.rowCount()): + self.ui.cnc_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table) + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + + def ui_disconnect(self): + for row in range(self.ui.cnc_tools_table.rowCount()): + self.ui.cnc_tools_table.cellWidget(row, 6).clicked.disconnect(self.on_plot_cb_click_table) + try: + self.ui.plot_cb.stateChanged.disconnect(self.on_plot_cb_click) + except (TypeError, AttributeError): + pass + + def on_updateplot_button_click(self, *args): + """ + Callback for the "Updata Plot" button. Reads the form for updates + and plots the object. + """ + self.read_form() + self.on_plot_kind_change() + + def on_plot_kind_change(self): + kind = self.ui.cncplot_method_combo.get_value() + + def worker_task(): + with self.app.proc_container.new(_("Plotting...")): + self.plot(kind=kind) + + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def on_exportgcode_button_click(self, *args): + """ + Handler activated by a button clicked when exporting GCode. + + :param args: + :return: + """ + self.app.defaults.report_usage("cncjob_on_exportgcode_button") + + self.read_form() + name = self.app.collection.get_active().options['name'] + save_gcode = False + + if 'Roland' in self.pp_excellon_name or 'Roland' in self.pp_geometry_name: + _filter_ = "RML1 Files .rol (*.rol);;All Files (*.*)" + elif 'hpgl' in self.pp_geometry_name: + _filter_ = "HPGL Files .plt (*.plt);;All Files (*.*)" + else: + save_gcode = True + _filter_ = self.app.defaults['cncjob_save_filters'] + + try: + dir_file_to_save = self.app.get_last_save_folder() + '/' + str(name) + filename, _f = FCFileSaveDialog.get_saved_filename( + caption=_("Export Machine Code ..."), + directory=dir_file_to_save, + filter=_filter_ + ) + except TypeError: + filename, _f = FCFileSaveDialog.get_saved_filename(caption=_("Export Machine Code ..."), filter=_filter_) + + filename = str(filename) + + if filename == '': + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ...")) + return + else: + if save_gcode is True: + used_extension = filename.rpartition('.')[2] + self.update_filters(last_ext=used_extension, filter_string='cncjob_save_filters') + + new_name = os.path.split(str(filename))[1].rpartition('.')[0] + self.ui.name_entry.set_value(new_name) + self.on_name_activate(silent=True) + + preamble = str(self.ui.prepend_text.get_value()) + postamble = str(self.ui.append_text.get_value()) + + gc = self.export_gcode(filename, preamble=preamble, postamble=postamble) + if gc == 'fail': + return + + if self.app.defaults["global_open_style"] is False: + self.app.file_opened.emit("gcode", filename) + self.app.file_saved.emit("gcode", filename) + self.app.inform.emit('[success] %s: %s' % (_("Machine Code file saved to"), filename)) + + def on_edit_code_click(self, *args): + """ + Handler activated by a button clicked when editing GCode. + + :param args: + :return: + """ + + self.app.proc_container.view.set_busy(_("Loading...")) + + preamble = str(self.ui.prepend_text.get_value()) + postamble = str(self.ui.append_text.get_value()) + + gco = self.export_gcode(preamble=preamble, postamble=postamble, to_file=True) + if gco == 'fail': + return + else: + self.app.gcode_edited = gco + + self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True) + + # add the tab if it was closed + self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Code Editor")) + self.gcode_editor_tab.setObjectName('code_editor_tab') + + # delete the absolute and relative position and messages in the infobar + self.app.ui.position_label.setText("") + self.app.ui.rel_position_label.setText("") + + # first clear previous text in text editor (if any) + self.gcode_editor_tab.code_editor.clear() + self.gcode_editor_tab.code_editor.setReadOnly(False) + + self.gcode_editor_tab.code_editor.completer_enable = False + self.gcode_editor_tab.buttonRun.hide() + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab) + + self.gcode_editor_tab.t_frame.hide() + # then append the text from GCode to the text editor + try: + self.gcode_editor_tab.code_editor.setPlainText(self.app.gcode_edited.getvalue()) + # for line in self.app.gcode_edited: + # QtWidgets.QApplication.processEvents() + # + # proc_line = str(line).strip('\n') + # self.gcode_editor_tab.code_editor.append(proc_line) + except Exception as e: + log.debug('FlatCAMCNNJob.on_edit_code_click() -->%s' % str(e)) + self.app.inform.emit('[ERROR] %s %s' % ('FlatCAMCNNJob.on_edit_code_click() -->', str(e))) + return + + self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start) + + self.gcode_editor_tab.handleTextChanged() + self.gcode_editor_tab.t_frame.show() + self.app.proc_container.view.set_idle() + + self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor')) + + def gcode_header(self, comment_start_symbol=None, comment_stop_symbol=None): + """ + Will create a header to be added to all GCode files generated by FlatCAM + + :param comment_start_symbol: A symbol to be used as the first symbol in a comment + :param comment_stop_symbol: A symbol to be used as the last symbol in a comment + :return: A string with a GCode header + """ + + log.debug("FlatCAMCNCJob.gcode_header()") + time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now()) + marlin = False + hpgl = False + probe_pp = False + + start_comment = comment_start_symbol if comment_start_symbol is not None else '(' + stop_comment = comment_stop_symbol if comment_stop_symbol is not None else ')' + + try: + for key in self.cnc_tools: + ppg = self.cnc_tools[key]['data']['ppname_g'] + if 'marlin' in ppg.lower() or 'repetier' in ppg.lower(): + marlin = True + break + if ppg == 'hpgl': + hpgl = True + break + if "toolchange_probe" in ppg.lower(): + probe_pp = True + break + except KeyError: + # log.debug("FlatCAMCNCJob.gcode_header() error: --> %s" % str(e)) + pass + + try: + if 'marlin' in self.options['ppname_e'].lower() or 'repetier' in self.options['ppname_e'].lower(): + marlin = True + except KeyError: + # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e)) + pass + + try: + if "toolchange_probe" in self.options['ppname_e'].lower(): + probe_pp = True + except KeyError: + # log.debug("FlatCAMCNCJob.gcode_header(): --> There is no such self.option: %s" % str(e)) + pass + + if marlin is True: + gcode = ';Marlin(Repetier) G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s\n' % \ + (str(self.app.version), str(self.app.version_date)) + '\n' + + gcode += ';Name: ' + str(self.options['name']) + '\n' + gcode += ';Type: ' + "G-code from " + str(self.options['type']) + '\n' + + # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' + + gcode += ';Units: ' + self.units.upper() + '\n' + "\n" + gcode += ';Created on ' + time_str + '\n' + '\n' + elif hpgl is True: + gcode = 'CO "HPGL CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s' % \ + (str(self.app.version), str(self.app.version_date)) + '";\n' + + gcode += 'CO "Name: ' + str(self.options['name']) + '";\n' + gcode += 'CO "Type: ' + "HPGL code from " + str(self.options['type']) + '";\n' + + # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' + + gcode += 'CO "Units: ' + self.units.upper() + '";\n' + gcode += 'CO "Created on ' + time_str + '";\n' + elif probe_pp is True: + gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \ + (str(self.app.version), str(self.app.version_date)) + '\n' + + gcode += '(This GCode tool change is done by using a Probe.)\n' \ + '(Make sure that before you start the job you first do a rough zero for Z axis.)\n' \ + '(This means that you need to zero the CNC axis and then jog to the toolchange X, Y location,)\n' \ + '(mount the probe and adjust the Z so more or less the probe tip touch the plate. ' \ + 'Then zero the Z axis.)\n' + '\n' + + gcode += '(Name: ' + str(self.options['name']) + ')\n' + gcode += '(Type: ' + "G-code from " + str(self.options['type']) + ')\n' + + # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' + + gcode += '(Units: ' + self.units.upper() + ')\n' + "\n" + gcode += '(Created on ' + time_str + ')\n' + '\n' + else: + gcode = '%sG-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s%s\n' % \ + (start_comment, str(self.app.version), str(self.app.version_date), stop_comment) + '\n' + + gcode += '%sName: ' % start_comment + str(self.options['name']) + '%s\n' % stop_comment + gcode += '%sType: ' % start_comment + "G-code from " + str(self.options['type']) + '%s\n' % stop_comment + + # if str(p['options']['type']) == 'Excellon' or str(p['options']['type']) == 'Excellon Geometry': + # gcode += '(Tools in use: ' + str(p['options']['Tools_in_use']) + ')\n' + + gcode += '%sUnits: ' % start_comment + self.units.upper() + '%s\n' % stop_comment + "\n" + gcode += '%sCreated on ' % start_comment + time_str + '%s\n' % stop_comment + '\n' + + return gcode + + @staticmethod + def gcode_footer(end_command=None): + """ + Will add the M02 to the end of GCode, if requested. + + :param end_command: 'M02' or 'M30' - String + :return: + """ + if end_command: + return end_command + else: + return 'M02' + + def export_gcode(self, filename=None, preamble='', postamble='', to_file=False, from_tcl=False): + """ + This will save the GCode from the Gcode object to a file on the OS filesystem + + :param filename: filename for the GCode file + :param preamble: a custom Gcode block to be added at the beginning of the Gcode file + :param postamble: a custom Gcode block to be added at the end of the Gcode file + :param to_file: if False then no actual file is saved but the app will know that a file was created + :param from_tcl: True if run from Tcl Shell + :return: None + """ + # gcode = '' + # roland = False + # hpgl = False + # isel_icp = False + + include_header = True + + try: + if self.special_group: + self.app.inform.emit('[WARNING_NOTCL] %s %s %s.' % + (_("This CNCJob object can't be processed because it is a"), + str(self.special_group), + _("CNCJob object"))) + return 'fail' + except AttributeError: + pass + + # if this dict is not empty then the object is a Geometry object + if self.cnc_tools: + first_key = next(iter(self.cnc_tools)) + include_header = self.app.preprocessors[self.cnc_tools[first_key]['data']['ppname_g']].include_header + + # if this dict is not empty then the object is an Excellon object + if self.exc_cnc_tools: + first_key = next(iter(self.exc_cnc_tools)) + include_header = self.app.preprocessors[self.exc_cnc_tools[first_key]['data']['ppname_e']].include_header + + # # detect if using Roland preprocessor + # try: + # for key in self.cnc_tools: + # if self.cnc_tools[key]['data']['ppname_g'] == 'Roland_MDX_20': + # roland = True + # break + # except Exception: + # try: + # for key in self.cnc_tools: + # if self.cnc_tools[key]['data']['ppname_e'] == 'Roland_MDX_20': + # roland = True + # break + # except Exception: + # pass + # + # # detect if using HPGL preprocessor + # try: + # for key in self.cnc_tools: + # if self.cnc_tools[key]['data']['ppname_g'] == 'hpgl': + # hpgl = True + # break + # except Exception: + # try: + # for key in self.cnc_tools: + # if self.cnc_tools[key]['data']['ppname_e'] == 'hpgl': + # hpgl = True + # break + # except Exception: + # pass + # + # # detect if using ISEL_ICP_CNC preprocessor + # try: + # for key in self.cnc_tools: + # if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_g'].upper(): + # isel_icp = True + # break + # except Exception: + # try: + # for key in self.cnc_tools: + # if 'ISEL_ICP' in self.cnc_tools[key]['data']['ppname_e'].upper(): + # isel_icp = True + # break + # except Exception: + # pass + + # do not add gcode_header when using the Roland preprocessor, add it for every other preprocessor + # if roland is False and hpgl is False and isel_icp is False: + # gcode = self.gcode_header() + + # do not add gcode_header when using the Roland, HPGL or ISEP_ICP_CNC preprocessor (or any other preprocessor + # that has the include_header attribute set as False, add it for every other preprocessor + # if include_header: + # gcode = self.gcode_header() + # else: + # gcode = '' + + # # detect if using multi-tool and make the Gcode summation correctly for each case + # if self.multitool is True: + # for tooluid_key in self.cnc_tools: + # for key, value in self.cnc_tools[tooluid_key].items(): + # if key == 'gcode': + # gcode += value + # break + # else: + # gcode += self.gcode + + # if roland is True: + # g = preamble + gcode + postamble + # elif hpgl is True: + # g = self.gcode_header() + preamble + gcode + postamble + # else: + # # fix so the preamble gets inserted in between the comments header and the actual start of GCODE + # g_idx = gcode.rfind('G20') + # + # # if it did not find 'G20' then search for 'G21' + # if g_idx == -1: + # g_idx = gcode.rfind('G21') + # + # # if it did not find 'G20' and it did not find 'G21' then there is an error and return + # # but only when the preprocessor is not ISEL_ICP who is allowed not to have the G20/G21 command + # if g_idx == -1 and isel_icp is False: + # self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21")) + # return + # + # footer = self.app.defaults['cncjob_footer'] + # end_gcode = self.gcode_footer() if footer is True else '' + # g = gcode[:g_idx] + preamble + '\n' + gcode[g_idx:] + postamble + end_gcode + + gcode = '' + if include_header is False: + g = preamble + # detect if using multi-tool and make the Gcode summation correctly for each case + if self.multitool is True: + for tooluid_key in self.cnc_tools: + for key, value in self.cnc_tools[tooluid_key].items(): + if key == 'gcode': + gcode += value + break + else: + gcode += self.gcode + + g = g + gcode + postamble + else: + # search for the GCode beginning which is usually a G20 or G21 + # fix so the preamble gets inserted in between the comments header and the actual start of GCODE + # g_idx = gcode.rfind('G20') + # + # # if it did not find 'G20' then search for 'G21' + # if g_idx == -1: + # g_idx = gcode.rfind('G21') + # + # # if it did not find 'G20' and it did not find 'G21' then there is an error and return + # if g_idx == -1: + # self.app.inform.emit('[ERROR_NOTCL] %s' % _("G-code does not have a units code: either G20 or G21")) + # return + + # detect if using multi-tool and make the Gcode summation correctly for each case + if self.multitool is True: + for tooluid_key in self.cnc_tools: + for key, value in self.cnc_tools[tooluid_key].items(): + if key == 'gcode': + gcode += value + break + else: + gcode += self.gcode + + end_gcode = self.gcode_footer() if self.app.defaults['cncjob_footer'] is True else '' + + # detect if using a HPGL preprocessor + hpgl = False + if self.cnc_tools: + for key in self.cnc_tools: + if 'ppname_g' in self.cnc_tools[key]['data']: + if 'hpgl' in self.cnc_tools[key]['data']['ppname_g']: + hpgl = True + break + elif self.exc_cnc_tools: + for key in self.cnc_tools: + if 'ppname_e' in self.cnc_tools[key]['data']: + if 'hpgl' in self.cnc_tools[key]['data']['ppname_e']: + hpgl = True + break + + if hpgl: + processed_gcode = '' + pa_re = re.compile(r"^PA\s*(-?\d+\.\d*),?\s*(-?\d+\.\d*)*;?$") + for gline in gcode.splitlines(): + match = pa_re.search(gline) + if match: + x_int = int(float(match.group(1))) + y_int = int(float(match.group(2))) + new_line = 'PA%d,%d;\n' % (x_int, y_int) + processed_gcode += new_line + else: + processed_gcode += gline + '\n' + + gcode = processed_gcode + g = self.gcode_header() + '\n' + preamble + '\n' + gcode + postamble + end_gcode + else: + try: + g_idx = gcode.index('G94') + g = self.gcode_header() + gcode[:g_idx + 3] + '\n\n' + preamble + '\n' + \ + gcode[(g_idx + 3):] + postamble + end_gcode + except ValueError: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("G-code does not have a G94 code and we will not include the code in the " + "'Prepend to GCode' text box")) + g = self.gcode_header() + '\n' + gcode + postamble + end_gcode + + # if toolchange custom is used, replace M6 code with the code from the Toolchange Custom Text box + if self.ui.toolchange_cb.get_value() is True: + # match = self.re_toolchange.search(g) + if 'M6' in g: + m6_code = self.parse_custom_toolchange_code(self.ui.toolchange_text.get_value()) + if m6_code is None or m6_code == '': + self.app.inform.emit( + '[ERROR_NOTCL] %s' % _("Cancelled. The Toolchange Custom code is enabled but it's empty.") + ) + return 'fail' + + g = g.replace('M6', m6_code) + self.app.inform.emit('[success] %s' % _("Toolchange G-code was replaced by a custom code.")) + + lines = StringIO(g) + + # Write + if filename is not None: + try: + force_windows_line_endings = self.app.defaults['cncjob_line_ending'] + if force_windows_line_endings and sys.platform != 'win32': + with open(filename, 'w', newline='\r\n') as f: + for line in lines: + f.write(line) + else: + with open(filename, 'w') as f: + for line in lines: + f.write(line) + except FileNotFoundError: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No such file or directory")) + return + except PermissionError: + self.app.inform.emit( + '[WARNING] %s' % _("Permission denied, saving not possible.\n" + "Most likely another app is holding the file open and not accessible.") + ) + return 'fail' + elif to_file is False: + # Just for adding it to the recent files list. + if self.app.defaults["global_open_style"] is False: + self.app.file_opened.emit("cncjob", filename) + self.app.file_saved.emit("cncjob", filename) + + self.app.inform.emit('[success] %s: %s' % (_("Saved to"), filename)) + else: + return lines + + def on_toolchange_custom_clicked(self, signal): + """ + Handler for clicking toolchange custom. + + :param signal: + :return: + """ + + try: + if 'toolchange_custom' not in str(self.options['ppname_e']).lower(): + if self.ui.toolchange_cb.get_value(): + self.ui.toolchange_cb.set_value(False) + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("The used preprocessor file has to have in it's name: 'toolchange_custom'")) + except KeyError: + try: + for key in self.cnc_tools: + ppg = self.cnc_tools[key]['data']['ppname_g'] + if 'toolchange_custom' not in str(ppg).lower(): + print(ppg) + if self.ui.toolchange_cb.get_value(): + self.ui.toolchange_cb.set_value(False) + self.app.inform.emit('[WARNING_NOTCL] %s' % + _("The used preprocessor file has to have in it's name: " + "'toolchange_custom'")) + except KeyError: + self.app.inform.emit('[ERROR] %s' % _("There is no preprocessor file.")) + + def get_gcode(self, preamble='', postamble=''): + """ + We need this to be able to get_gcode separately for shell command export_gcode + + :param preamble: Extra GCode added to the beginning of the GCode + :param postamble: Extra GCode added at the end of the GCode + :return: The modified GCode + """ + return preamble + '\n' + self.gcode + "\n" + postamble + + def get_svg(self): + # we need this to be able get_svg separately for shell command export_svg + pass + + def on_plot_cb_click(self, *args): + """ + Handler for clicking on the Plot checkbox. + + :param args: + :return: + """ + if self.muted_ui: + return + kind = self.ui.cncplot_method_combo.get_value() + self.plot(kind=kind) + self.read_form_item('plot') + + self.ui_disconnect() + cb_flag = self.ui.plot_cb.isChecked() + for row in range(self.ui.cnc_tools_table.rowCount()): + table_cb = self.ui.cnc_tools_table.cellWidget(row, 6) + if cb_flag: + table_cb.setChecked(True) + else: + table_cb.setChecked(False) + self.ui_connect() + + def on_plot_cb_click_table(self): + """ + Handler for clicking the plot checkboxes added into a Table on each row. Purpose: toggle visibility for the + tool/aperture found on that row. + :return: + """ + + # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) + self.ui_disconnect() + # cw = self.sender() + # cw_index = self.ui.cnc_tools_table.indexAt(cw.pos()) + # cw_row = cw_index.row() + + kind = self.ui.cncplot_method_combo.get_value() + + self.shapes.clear(update=True) + + for tooluid_key in self.cnc_tools: + tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia']))) + gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed'] + # tool_uid = int(self.ui.cnc_tools_table.item(cw_row, 3).text()) + + for r in range(self.ui.cnc_tools_table.rowCount()): + if int(self.ui.cnc_tools_table.item(r, 5).text()) == int(tooluid_key): + if self.ui.cnc_tools_table.cellWidget(r, 6).isChecked(): + self.plot2(tooldia=tooldia, obj=self, visible=True, gcode_parsed=gcode_parsed, kind=kind) + + self.shapes.redraw() + + # make sure that the general plot is disabled if one of the row plot's are disabled and + # if all the row plot's are enabled also enable the general plot checkbox + cb_cnt = 0 + total_row = self.ui.cnc_tools_table.rowCount() + for row in range(total_row): + if self.ui.cnc_tools_table.cellWidget(row, 6).isChecked(): + cb_cnt += 1 + else: + cb_cnt -= 1 + if cb_cnt < total_row: + self.ui.plot_cb.setChecked(False) + else: + self.ui.plot_cb.setChecked(True) + self.ui_connect() + + def plot(self, visible=None, kind='all'): + """ + # Does all the required setup and returns False + # if the 'ptint' option is set to False. + + :param visible: Boolean to decide if the object will be plotted as visible or disabled on canvas + :param kind: String. Can be "all" or "travel" or "cut". For CNCJob plotting + :return: None + """ + if not FlatCAMObj.plot(self): + return + + visible = visible if visible else self.options['plot'] + + if self.app.is_legacy is False: + if self.ui.annotation_cb.get_value() and self.ui.plot_cb.get_value(): + self.text_col.enabled = True + else: + self.text_col.enabled = False + self.annotation.redraw() + + try: + if self.multitool is False: # single tool usage + try: + dia_plot = float(self.options["tooldia"]) + except ValueError: + # we may have a tuple with only one element and a comma + dia_plot = [float(el) for el in self.options["tooldia"].split(',') if el != ''][0] + self.plot2(dia_plot, obj=self, visible=visible, kind=kind) + else: + # multiple tools usage + if self.cnc_tools: + for tooluid_key in self.cnc_tools: + tooldia = float('%.*f' % (self.decimals, float(self.cnc_tools[tooluid_key]['tooldia']))) + gcode_parsed = self.cnc_tools[tooluid_key]['gcode_parsed'] + self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind) + + # TODO: until the gcode parsed will be stored on each Excellon tool this will not get executed + if self.exc_cnc_tools: + for tooldia_key in self.exc_cnc_tools: + tooldia = float('%.*f' % (self.decimals, float(tooldia_key))) + # gcode_parsed = self.cnc_tools[tooldia_key]['gcode_parsed'] + gcode_parsed = self.gcode_parsed + self.plot2(tooldia=tooldia, obj=self, visible=visible, gcode_parsed=gcode_parsed, kind=kind) + + self.shapes.redraw() + except (ObjectDeleted, AttributeError): + self.shapes.clear(update=True) + if self.app.is_legacy is False: + self.annotation.clear(update=True) + + def on_annotation_change(self): + """ + Handler for toggling the annotation display by clicking a checkbox. + :return: + """ + + if self.app.is_legacy is False: + if self.ui.annotation_cb.get_value(): + self.text_col.enabled = True + else: + self.text_col.enabled = False + # kind = self.ui.cncplot_method_combo.get_value() + # self.plot(kind=kind) + self.annotation.redraw() + else: + kind = self.ui.cncplot_method_combo.get_value() + self.plot(kind=kind) + + def convert_units(self, units): + """ + Units conversion used by the CNCJob objects. + + :param units: Can be "MM" or "IN" + :return: + """ + + log.debug("FlatCAMObj.FlatCAMECNCjob.convert_units()") + + factor = CNCjob.convert_units(self, units) + self.options["tooldia"] = float(self.options["tooldia"]) * factor + + param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', + 'endz', 'toolchangez'] + + temp_tools_dict = {} + tool_dia_copy = {} + data_copy = {} + + for tooluid_key, tooluid_value in self.cnc_tools.items(): + for dia_key, dia_value in tooluid_value.items(): + if dia_key == 'tooldia': + dia_value *= factor + dia_value = float('%.*f' % (self.decimals, dia_value)) + tool_dia_copy[dia_key] = dia_value + if dia_key == 'offset': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'offset_value': + dia_value *= factor + tool_dia_copy[dia_key] = dia_value + + if dia_key == 'type': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'tool_type': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'data': + for data_key, data_value in dia_value.items(): + # convert the form fields that are convertible + for param in param_list: + if data_key == param and data_value is not None: + data_copy[data_key] = data_value * factor + # copy the other dict entries that are not convertible + if data_key not in param_list: + data_copy[data_key] = data_value + tool_dia_copy[dia_key] = deepcopy(data_copy) + data_copy.clear() + + if dia_key == 'gcode': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'gcode_parsed': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'solid_geometry': + tool_dia_copy[dia_key] = dia_value + + # if dia_key == 'solid_geometry': + # tool_dia_copy[dia_key] = affinity.scale(dia_value, xfact=factor, origin=(0, 0)) + # if dia_key == 'gcode_parsed': + # for g in dia_value: + # g['geom'] = affinity.scale(g['geom'], factor, factor, origin=(0, 0)) + # + # tool_dia_copy['gcode_parsed'] = deepcopy(dia_value) + # tool_dia_copy['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_value]) + + temp_tools_dict.update({ + tooluid_key: deepcopy(tool_dia_copy) + }) + tool_dia_copy.clear() + + self.cnc_tools.clear() + self.cnc_tools = deepcopy(temp_tools_dict) diff --git a/flatcamObjects/FlatCAMDocument.py b/flatcamObjects/FlatCAMDocument.py new file mode 100644 index 00000000..5f33b73a --- /dev/null +++ b/flatcamObjects/FlatCAMDocument.py @@ -0,0 +1,314 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + +from flatcamEditors.FlatCAMTextEditor import TextEditor +from flatcamObjects.FlatCAMObj import * + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class DocumentObject(FlatCAMObj): + """ + Represents a Document object. + """ + optionChanged = QtCore.pyqtSignal(str) + ui_type = DocumentObjectUI + + def __init__(self, name): + self.decimals = self.app.decimals + + log.debug("Creating a Document object...") + FlatCAMObj.__init__(self, name) + + self.kind = "document" + self.units = '' + + self.ser_attrs = ['options', 'kind', 'source_file'] + self.source_file = '' + self.doc_code = '' + + self.font_name = None + self.font_italic = None + self.font_bold = None + self.font_underline = None + + self.document_editor_tab = None + + self._read_only = False + self.units_found = self.app.defaults['units'] + + def set_ui(self, ui): + FlatCAMObj.set_ui(self, ui) + log.debug("DocumentObject.set_ui()") + + assert isinstance(self.ui, DocumentObjectUI), \ + "Expected a DocumentObjectUI, got %s" % type(self.ui) + + self.units = self.app.defaults['units'].upper() + self.units_found = self.app.defaults['units'] + + # Fill form fields only on object create + self.to_form() + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText(_( + 'Basic' + )) + else: + self.ui.level.setText(_( + 'Advanced' + )) + + self.document_editor_tab = TextEditor(app=self.app) + stylesheet = """ + QTextEdit {selection-background-color:%s; + selection-color:white; + } + """ % self.app.defaults["document_sel_color"] + + self.document_editor_tab.code_editor.setStyleSheet(stylesheet) + + # first clear previous text in text editor (if any) + self.document_editor_tab.code_editor.clear() + self.document_editor_tab.code_editor.setReadOnly(self._read_only) + + self.document_editor_tab.buttonRun.hide() + + self.ui.autocomplete_cb.set_value(self.app.defaults['document_autocompleter']) + self.on_autocomplete_changed(state=self.app.defaults['document_autocompleter']) + self.on_tab_size_change(val=self.app.defaults['document_tab_size']) + + flt = "FlatCAM Docs (*.FlatDoc);;All Files (*.*)" + + # ###################################################################### + # ######################## SIGNALS ##################################### + # ###################################################################### + self.document_editor_tab.buttonOpen.clicked.disconnect() + self.document_editor_tab.buttonOpen.clicked.connect(lambda: self.document_editor_tab.handleOpen(filt=flt)) + self.document_editor_tab.buttonSave.clicked.disconnect() + self.document_editor_tab.buttonSave.clicked.connect(lambda: self.document_editor_tab.handleSaveGCode(filt=flt)) + + self.document_editor_tab.code_editor.textChanged.connect(self.on_text_changed) + + self.ui.font_type_cb.currentFontChanged.connect(self.font_family) + self.ui.font_size_cb.activated.connect(self.font_size) + self.ui.font_bold_tb.clicked.connect(self.on_bold_button) + self.ui.font_italic_tb.clicked.connect(self.on_italic_button) + self.ui.font_under_tb.clicked.connect(self.on_underline_button) + + self.ui.font_color_entry.editingFinished.connect(self.on_font_color_entry) + self.ui.font_color_button.clicked.connect(self.on_font_color_button) + self.ui.sel_color_entry.editingFinished.connect(self.on_selection_color_entry) + self.ui.sel_color_button.clicked.connect(self.on_selection_color_button) + + self.ui.al_left_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignLeft)) + self.ui.al_center_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignCenter)) + self.ui.al_right_tb.clicked.connect(lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignRight)) + self.ui.al_justify_tb.clicked.connect( + lambda: self.document_editor_tab.code_editor.setAlignment(Qt.AlignJustify) + ) + + self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed) + self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change) + # ####################################################################### + + self.ui.font_color_entry.set_value(self.app.defaults['document_font_color']) + self.ui.font_color_button.setStyleSheet( + "background-color:%s" % str(self.app.defaults['document_font_color'])) + + self.ui.sel_color_entry.set_value(self.app.defaults['document_sel_color']) + self.ui.sel_color_button.setStyleSheet( + "background-color:%s" % self.app.defaults['document_sel_color']) + + self.ui.font_size_cb.setCurrentIndex(int(self.app.defaults['document_font_size'])) + + self.document_editor_tab.handleTextChanged() + self.ser_attrs = ['options', 'kind', 'source_file'] + + if Qt.mightBeRichText(self.source_file): + self.document_editor_tab.code_editor.setHtml(self.source_file) + else: + for line in self.source_file.splitlines(): + self.document_editor_tab.code_editor.append(line) + + self.build_ui() + + @property + def read_only(self): + return self._read_only + + @read_only.setter + def read_only(self, val): + if val: + self._read_only = True + else: + self._read_only = False + + def build_ui(self): + FlatCAMObj.build_ui(self) + tab_here = False + + # try to not add too many times a tab that it is already installed + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']: + tab_here = True + break + + # add the tab if it is not already added + if tab_here is False: + self.app.ui.plot_tab_area.addTab(self.document_editor_tab, '%s' % _("Document Editor")) + self.document_editor_tab.setObjectName(self.options['name']) + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.document_editor_tab) + + def on_autocomplete_changed(self, state): + if state: + self.document_editor_tab.code_editor.completer_enable = True + else: + self.document_editor_tab.code_editor.completer_enable = False + + def on_tab_size_change(self, val=None): + try: + self.ui.tab_size_spinner.returnPressed.disconnect(self.on_tab_size_change) + except TypeError: + pass + + if val: + self.ui.tab_size_spinner.set_value(val) + + tab_balue = int(self.ui.tab_size_spinner.get_value()) + self.document_editor_tab.code_editor.setTabStopWidth(tab_balue) + self.app.defaults['document_tab_size'] = tab_balue + + self.ui.tab_size_spinner.returnPressed.connect(self.on_tab_size_change) + + def on_text_changed(self): + self.source_file = self.document_editor_tab.code_editor.toHtml() + # print(self.source_file) + + def font_family(self, font): + # self.document_editor_tab.code_editor.selectAll() + font.setPointSize(float(self.ui.font_size_cb.get_value())) + self.document_editor_tab.code_editor.setCurrentFont(font) + self.font_name = self.ui.font_type_cb.currentFont().family() + + def font_size(self): + # self.document_editor_tab.code_editor.selectAll() + self.document_editor_tab.code_editor.setFontPointSize(float(self.ui.font_size_cb.get_value())) + + def on_bold_button(self): + if self.ui.font_bold_tb.isChecked(): + self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Bold) + self.font_bold = True + else: + self.document_editor_tab.code_editor.setFontWeight(QtGui.QFont.Normal) + self.font_bold = False + + def on_italic_button(self): + if self.ui.font_italic_tb.isChecked(): + self.document_editor_tab.code_editor.setFontItalic(True) + self.font_italic = True + else: + self.document_editor_tab.code_editor.setFontItalic(False) + self.font_italic = False + + def on_underline_button(self): + if self.ui.font_under_tb.isChecked(): + self.document_editor_tab.code_editor.setFontUnderline(True) + self.font_underline = True + else: + self.document_editor_tab.code_editor.setFontUnderline(False) + self.font_underline = False + + # Setting font colors handlers + def on_font_color_entry(self): + self.app.defaults['document_font_color'] = self.ui.font_color_entry.get_value() + self.ui.font_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_font_color'])) + + def on_font_color_button(self): + current_color = QtGui.QColor(self.app.defaults['document_font_color']) + + c_dialog = QtWidgets.QColorDialog() + font_color = c_dialog.getColor(initial=current_color) + + if font_color.isValid() is False: + return + + self.document_editor_tab.code_editor.setTextColor(font_color) + self.ui.font_color_button.setStyleSheet("background-color:%s" % str(font_color.name())) + + new_val = str(font_color.name()) + self.ui.font_color_entry.set_value(new_val) + self.app.defaults['document_font_color'] = new_val + + # Setting selection colors handlers + def on_selection_color_entry(self): + self.app.defaults['document_sel_color'] = self.ui.sel_color_entry.get_value() + self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(self.app.defaults['document_sel_color'])) + + def on_selection_color_button(self): + current_color = QtGui.QColor(self.app.defaults['document_sel_color']) + + c_dialog = QtWidgets.QColorDialog() + sel_color = c_dialog.getColor(initial=current_color) + + if sel_color.isValid() is False: + return + + p = QtGui.QPalette() + p.setColor(QtGui.QPalette.Highlight, sel_color) + p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('white')) + + self.document_editor_tab.code_editor.setPalette(p) + + self.ui.sel_color_button.setStyleSheet("background-color:%s" % str(sel_color.name())) + + new_val = str(sel_color.name()) + self.ui.sel_color_entry.set_value(new_val) + self.app.defaults['document_sel_color'] = new_val + + def to_dict(self): + """ + Returns a representation of the object as a dictionary. + Attributes to include are listed in ``self.ser_attrs``. + + :return: A dictionary-encoded copy of the object. + :rtype: dict + """ + d = {} + for attr in self.ser_attrs: + d[attr] = getattr(self, attr) + return d + + def from_dict(self, d): + """ + Sets object's attributes from a dictionary. + Attributes to include are listed in ``self.ser_attrs``. + This method will look only for only and all the + attributes in ``self.ser_attrs``. They must all + be present. Use only for deserializing saved + objects. + + :param d: Dictionary of attributes to set in the object. + :type d: dict + :return: None + """ + for attr in self.ser_attrs: + setattr(self, attr, d[attr]) diff --git a/flatcamObjects/FlatCAMExcellon.py b/flatcamObjects/FlatCAMExcellon.py new file mode 100644 index 00000000..79020ab1 --- /dev/null +++ b/flatcamObjects/FlatCAMExcellon.py @@ -0,0 +1,1622 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + + +from shapely.geometry import Point, LineString + +from copy import deepcopy + +from flatcamParsers.ParseExcellon import Excellon +from flatcamObjects.FlatCAMObj import * + +import itertools + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class ExcellonObject(FlatCAMObj, Excellon): + """ + Represents Excellon/Drill code. + """ + + ui_type = ExcellonObjectUI + optionChanged = QtCore.pyqtSignal(str) + + def __init__(self, name): + self.decimals = self.app.decimals + + self.circle_steps = int(self.app.defaults["geometry_circle_steps"]) + + Excellon.__init__(self, geo_steps_per_circle=self.circle_steps) + FlatCAMObj.__init__(self, name) + + self.kind = "excellon" + + self.options.update({ + "plot": True, + "solid": False, + + "operation": "drill", + "milling_type": "drills", + + "milling_dia": 0.04, + + "cutz": -0.1, + "multidepth": False, + "depthperpass": 0.7, + "travelz": 0.1, + "feedrate": self.app.defaults["geometry_feedrate"], + "feedrate_z": 5.0, + "feedrate_rapid": 5.0, + "tooldia": 0.1, + "slot_tooldia": 0.1, + "toolchange": False, + "toolchangez": 1.0, + "toolchangexy": "0.0, 0.0", + "extracut": self.app.defaults["geometry_extracut"], + "extracut_length": self.app.defaults["geometry_extracut_length"], + "endz": 2.0, + "endxy": '', + + "startz": None, + "offset": 0.0, + "spindlespeed": 0, + "dwell": True, + "dwelltime": 1000, + "ppname_e": 'default', + "ppname_g": self.app.defaults["geometry_ppname_g"], + "z_pdepth": -0.02, + "feedrate_probe": 3.0, + "optimization_type": "B", + }) + + # TODO: Document this. + self.tool_cbs = {} + + # dict that holds the object names and the option name + # the key is the object name (defines in ObjectUI) for each UI element that is a parameter + # particular for a tool and the value is the actual name of the option that the UI element is changing + self.name2option = {} + + # default set of data to be added to each tool in self.tools as self.tools[tool]['data'] = self.default_data + self.default_data = {} + + # fill in self.default_data values from self.options + for opt_key, opt_val in self.app.options.items(): + if opt_key.find('excellon_') == 0: + self.default_data[opt_key] = deepcopy(opt_val) + for opt_key, opt_val in self.app.options.items(): + if opt_key.find('geometry_') == 0: + self.default_data[opt_key] = deepcopy(opt_val) + + # variable to store the total amount of drills per job + self.tot_drill_cnt = 0 + self.tool_row = 0 + + # variable to store the total amount of slots per job + self.tot_slot_cnt = 0 + self.tool_row_slots = 0 + + # variable to store the distance travelled + self.travel_distance = 0.0 + + # store the source file here + self.source_file = "" + + self.multigeo = False + self.units_found = self.app.defaults['units'] + + self.fill_color = self.app.defaults['excellon_plot_fill'] + self.outline_color = self.app.defaults['excellon_plot_line'] + self.alpha_level = 'bf' + + # Attributes to be included in serialization + # Always append to it because it carries contents + # from predecessors. + self.ser_attrs += ['options', 'kind'] + + @staticmethod + def merge(exc_list, exc_final, decimals=None): + """ + Merge Excellon objects found in exc_list parameter into exc_final object. + Options are always copied from source . + + Tools are disregarded, what is taken in consideration is the unique drill diameters found as values in the + exc_list tools dict's. In the reconstruction section for each unique tool diameter it will be created a + tool_name to be used in the final Excellon object, exc_final. + + If only one object is in exc_list parameter then this function will copy that object in the exc_final + + :param exc_list: List or one object of ExcellonObject Objects to join. + :param exc_final: Destination ExcellonObject object. + :return: None + """ + + if decimals is None: + decimals = 4 + decimals_exc = decimals + + # flag to signal that we need to reorder the tools dictionary and drills and slots lists + flag_order = False + + try: + flattened_list = list(itertools.chain(*exc_list)) + except TypeError: + flattened_list = exc_list + + # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict + # values will be list of Shapely Points; for drills + custom_dict_drills = {} + + # this dict will hold the unique tool diameters found in the exc_list objects as the dict keys and the dict + # values will be list of Shapely Points; for slots + custom_dict_slots = {} + + for exc in flattened_list: + # copy options of the current excellon obj to the final excellon obj + for option in exc.options: + if option != 'name': + try: + exc_final.options[option] = exc.options[option] + except Exception: + exc.app.log.warning("Failed to copy option.", option) + + for drill in exc.drills: + exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[drill['tool']]['C'])) + + if exc_tool_dia not in custom_dict_drills: + custom_dict_drills[exc_tool_dia] = [drill['point']] + else: + custom_dict_drills[exc_tool_dia].append(drill['point']) + + for slot in exc.slots: + exc_tool_dia = float('%.*f' % (decimals_exc, exc.tools[slot['tool']]['C'])) + + if exc_tool_dia not in custom_dict_slots: + custom_dict_slots[exc_tool_dia] = [[slot['start'], slot['stop']]] + else: + custom_dict_slots[exc_tool_dia].append([slot['start'], slot['stop']]) + + # add the zeros and units to the exc_final object + exc_final.zeros = exc.zeros + exc_final.units = exc.units + + # ########################################## + # Here we add data to the exc_final object # + # ########################################## + + # variable to make tool_name for the tools + current_tool = 0 + # The tools diameter are now the keys in the drill_dia dict and the values are the Shapely Points in case of + # drills + for tool_dia in custom_dict_drills: + # we create a tool name for each key in the drill_dia dict (the key is a unique drill diameter) + current_tool += 1 + + tool_name = str(current_tool) + spec = {"C": float(tool_dia)} + exc_final.tools[tool_name] = spec + + # rebuild the drills list of dict's that belong to the exc_final object + for point in custom_dict_drills[tool_dia]: + exc_final.drills.append( + { + "point": point, + "tool": str(current_tool) + } + ) + + # The tools diameter are now the keys in the drill_dia dict and the values are a list ([start, stop]) + # of two Shapely Points in case of slots + for tool_dia in custom_dict_slots: + # we create a tool name for each key in the slot_dia dict (the key is a unique slot diameter) + # but only if there are no drills + if not exc_final.tools: + current_tool += 1 + tool_name = str(current_tool) + spec = {"C": float(tool_dia)} + exc_final.tools[tool_name] = spec + else: + dia_list = [] + for v in exc_final.tools.values(): + dia_list.append(float(v["C"])) + + if tool_dia not in dia_list: + flag_order = True + + current_tool = len(dia_list) + 1 + tool_name = str(current_tool) + spec = {"C": float(tool_dia)} + exc_final.tools[tool_name] = spec + + else: + for k, v in exc_final.tools.items(): + if v["C"] == tool_dia: + current_tool = int(k) + break + + # rebuild the slots list of dict's that belong to the exc_final object + for point in custom_dict_slots[tool_dia]: + exc_final.slots.append( + { + "start": point[0], + "stop": point[1], + "tool": str(current_tool) + } + ) + + # flag_order == True means that there was an slot diameter not in the tools and we also have drills + # and the new tool was added to self.tools therefore we need to reorder the tools and drills and slots + current_tool = 0 + if flag_order is True: + dia_list = [] + temp_drills = [] + temp_slots = [] + temp_tools = {} + for v in exc_final.tools.values(): + dia_list.append(float(v["C"])) + dia_list.sort() + for ordered_dia in dia_list: + current_tool += 1 + tool_name_temp = str(current_tool) + spec_temp = {"C": float(ordered_dia)} + temp_tools[tool_name_temp] = spec_temp + + for drill in exc_final.drills: + exc_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[drill['tool']]['C'])) + if exc_tool_dia == ordered_dia: + temp_drills.append( + { + "point": drill["point"], + "tool": str(current_tool) + } + ) + + for slot in exc_final.slots: + slot_tool_dia = float('%.*f' % (decimals_exc, exc_final.tools[slot['tool']]['C'])) + if slot_tool_dia == ordered_dia: + temp_slots.append( + { + "start": slot["start"], + "stop": slot["stop"], + "tool": str(current_tool) + } + ) + + # delete the exc_final tools, drills and slots + exc_final.tools = {} + exc_final.drills[:] = [] + exc_final.slots[:] = [] + + # update the exc_final tools, drills and slots with the ordered values + exc_final.tools = temp_tools + exc_final.drills[:] = temp_drills + exc_final.slots[:] = temp_slots + + # create the geometry for the exc_final object + exc_final.create_geometry() + + def build_ui(self): + FlatCAMObj.build_ui(self) + + self.units = self.app.defaults['units'].upper() + + for row in range(self.ui.tools_table.rowCount()): + try: + # if connected, disconnect the signal from the slot on item_changed as it creates issues + offset_spin_widget = self.ui.tools_table.cellWidget(row, 4) + offset_spin_widget.valueChanged.disconnect() + except (TypeError, AttributeError): + pass + + n = len(self.tools) + # we have (n+2) rows because there are 'n' tools, each a row, plus the last 2 rows for totals. + self.ui.tools_table.setRowCount(n + 2) + + self.tot_drill_cnt = 0 + self.tot_slot_cnt = 0 + + self.tool_row = 0 + + sort = [] + for k, v in list(self.tools.items()): + sort.append((k, v.get('C'))) + sorted_tools = sorted(sort, key=lambda t1: t1[1]) + tools = [i[0] for i in sorted_tools] + + new_options = {} + for opt in self.options: + new_options[opt] = self.options[opt] + + for tool_no in tools: + + # add the data dictionary for each tool with the default values + self.tools[tool_no]['data'] = deepcopy(new_options) + # self.tools[tool_no]['data']["tooldia"] = self.tools[tool_no]["C"] + # self.tools[tool_no]['data']["slot_tooldia"] = self.tools[tool_no]["C"] + + drill_cnt = 0 # variable to store the nr of drills per tool + slot_cnt = 0 # variable to store the nr of slots per tool + + # Find no of drills for the current tool + for drill in self.drills: + if drill['tool'] == tool_no: + drill_cnt += 1 + + self.tot_drill_cnt += drill_cnt + + # Find no of slots for the current tool + for slot in self.slots: + if slot['tool'] == tool_no: + slot_cnt += 1 + + self.tot_slot_cnt += slot_cnt + + exc_id_item = QtWidgets.QTableWidgetItem('%d' % int(tool_no)) + exc_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + + dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.tools[tool_no]['C'])) + dia_item.setFlags(QtCore.Qt.ItemIsEnabled) + + drill_count_item = QtWidgets.QTableWidgetItem('%d' % drill_cnt) + drill_count_item.setFlags(QtCore.Qt.ItemIsEnabled) + + # if the slot number is zero is better to not clutter the GUI with zero's so we print a space + slot_count_str = '%d' % slot_cnt if slot_cnt > 0 else '' + slot_count_item = QtWidgets.QTableWidgetItem(slot_count_str) + slot_count_item.setFlags(QtCore.Qt.ItemIsEnabled) + + plot_item = FCCheckBox() + plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) + if self.ui.plot_cb.isChecked(): + plot_item.setChecked(True) + + self.ui.tools_table.setItem(self.tool_row, 0, exc_id_item) # Tool name/id + self.ui.tools_table.setItem(self.tool_row, 1, dia_item) # Diameter + self.ui.tools_table.setItem(self.tool_row, 2, drill_count_item) # Number of drills per tool + self.ui.tools_table.setItem(self.tool_row, 3, slot_count_item) # Number of drills per tool + empty_plot_item = QtWidgets.QTableWidgetItem('') + empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.tools_table.setItem(self.tool_row, 5, empty_plot_item) + self.ui.tools_table.setCellWidget(self.tool_row, 5, plot_item) + + self.tool_row += 1 + + # add a last row with the Total number of drills + empty_1 = QtWidgets.QTableWidgetItem('') + empty_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + empty_1_1 = QtWidgets.QTableWidgetItem('') + empty_1_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + empty_1_2 = QtWidgets.QTableWidgetItem('') + empty_1_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + empty_1_3 = QtWidgets.QTableWidgetItem('') + empty_1_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + + label_tot_drill_count = QtWidgets.QTableWidgetItem(_('Total Drills')) + tot_drill_count = QtWidgets.QTableWidgetItem('%d' % self.tot_drill_cnt) + label_tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled) + tot_drill_count.setFlags(QtCore.Qt.ItemIsEnabled) + + self.ui.tools_table.setItem(self.tool_row, 0, empty_1) + self.ui.tools_table.setItem(self.tool_row, 1, label_tot_drill_count) + self.ui.tools_table.setItem(self.tool_row, 2, tot_drill_count) # Total number of drills + self.ui.tools_table.setItem(self.tool_row, 3, empty_1_1) + self.ui.tools_table.setItem(self.tool_row, 5, empty_1_3) + + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + + for k in [1, 2]: + self.ui.tools_table.item(self.tool_row, k).setForeground(QtGui.QColor(127, 0, 255)) + self.ui.tools_table.item(self.tool_row, k).setFont(font) + + self.tool_row += 1 + + # add a last row with the Total number of slots + empty_2 = QtWidgets.QTableWidgetItem('') + empty_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + empty_2_1 = QtWidgets.QTableWidgetItem('') + empty_2_1.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + empty_2_2 = QtWidgets.QTableWidgetItem('') + empty_2_2.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + empty_2_3 = QtWidgets.QTableWidgetItem('') + empty_2_3.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + + label_tot_slot_count = QtWidgets.QTableWidgetItem(_('Total Slots')) + tot_slot_count = QtWidgets.QTableWidgetItem('%d' % self.tot_slot_cnt) + label_tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled) + tot_slot_count.setFlags(QtCore.Qt.ItemIsEnabled) + + self.ui.tools_table.setItem(self.tool_row, 0, empty_2) + self.ui.tools_table.setItem(self.tool_row, 1, label_tot_slot_count) + self.ui.tools_table.setItem(self.tool_row, 2, empty_2_1) + self.ui.tools_table.setItem(self.tool_row, 3, tot_slot_count) # Total number of slots + self.ui.tools_table.setItem(self.tool_row, 5, empty_2_3) + + for kl in [1, 2, 3]: + self.ui.tools_table.item(self.tool_row, kl).setFont(font) + self.ui.tools_table.item(self.tool_row, kl).setForeground(QtGui.QColor(0, 70, 255)) + + # sort the tool diameter column + # self.ui.tools_table.sortItems(1) + + # all the tools are selected by default + self.ui.tools_table.selectColumn(0) + + self.ui.tools_table.resizeColumnsToContents() + self.ui.tools_table.resizeRowsToContents() + + vertical_header = self.ui.tools_table.verticalHeader() + # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + vertical_header.hide() + self.ui.tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + horizontal_header = self.ui.tools_table.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 20) + + horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + + horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(5, 17) + self.ui.tools_table.setColumnWidth(5, 17) + + # horizontal_header.setStretchLastSection(True) + # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents) + + # horizontal_header.setStretchLastSection(True) + self.ui.tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.ui.tools_table.setSortingEnabled(False) + + self.ui.tools_table.setMinimumHeight(self.ui.tools_table.getHeight()) + self.ui.tools_table.setMaximumHeight(self.ui.tools_table.getHeight()) + + if not self.drills: + self.ui.tooldia_entry.hide() + self.ui.generate_milling_button.hide() + else: + self.ui.tooldia_entry.show() + self.ui.generate_milling_button.show() + + if not self.slots: + self.ui.slot_tooldia_entry.hide() + self.ui.generate_milling_slots_button.hide() + else: + self.ui.slot_tooldia_entry.show() + self.ui.generate_milling_slots_button.show() + + # set the text on tool_data_label after loading the object + sel_items = self.ui.tools_table.selectedItems() + sel_rows = [it.row() for it in sel_items] + if len(sel_rows) > 1: + self.ui.tool_data_label.setText( + "%s: %s" % (_('Parameters for'), _("Multiple Tools")) + ) + + self.ui_connect() + + def set_ui(self, ui): + """ + Configures the user interface for this object. + Connects options to form fields. + + :param ui: User interface object. + :type ui: ExcellonObjectUI + :return: None + """ + FlatCAMObj.set_ui(self, ui) + + log.debug("ExcellonObject.set_ui()") + + self.units = self.app.defaults['units'].upper() + + self.form_fields.update({ + "plot": self.ui.plot_cb, + "solid": self.ui.solid_cb, + + "operation": self.ui.operation_radio, + "milling_type": self.ui.milling_type_radio, + + "milling_dia": self.ui.mill_dia_entry, + "cutz": self.ui.cutz_entry, + "multidepth": self.ui.mpass_cb, + "depthperpass": self.ui.maxdepth_entry, + "travelz": self.ui.travelz_entry, + "feedrate_z": self.ui.feedrate_z_entry, + "feedrate": self.ui.xyfeedrate_entry, + "feedrate_rapid": self.ui.feedrate_rapid_entry, + "tooldia": self.ui.tooldia_entry, + "slot_tooldia": self.ui.slot_tooldia_entry, + "toolchange": self.ui.toolchange_cb, + "toolchangez": self.ui.toolchangez_entry, + "extracut": self.ui.extracut_cb, + "extracut_length": self.ui.e_cut_entry, + + "spindlespeed": self.ui.spindlespeed_entry, + "dwell": self.ui.dwell_cb, + "dwelltime": self.ui.dwelltime_entry, + + "startz": self.ui.estartz_entry, + "endz": self.ui.endz_entry, + "endxy": self.ui.endxy_entry, + + "offset": self.ui.offset_entry, + + "ppname_e": self.ui.pp_excellon_name_cb, + "ppname_g": self.ui.pp_geo_name_cb, + "z_pdepth": self.ui.pdepth_entry, + "feedrate_probe": self.ui.feedrate_probe_entry, + # "gcode_type": self.ui.excellon_gcode_type_radio + }) + + self.name2option = { + "e_operation": "operation", + "e_milling_type": "milling_type", + "e_milling_dia": "milling_dia", + "e_cutz": "cutz", + "e_multidepth": "multidepth", + "e_depthperpass": "depthperpass", + + "e_travelz": "travelz", + "e_feedratexy": "feedrate", + "e_feedratez": "feedrate_z", + "e_fr_rapid": "feedrate_rapid", + "e_extracut": "extracut", + "e_extracut_length": "extracut_length", + "e_spindlespeed": "spindlespeed", + "e_dwell": "dwell", + "e_dwelltime": "dwelltime", + "e_offset": "offset", + } + + # populate Excellon preprocessor combobox list + for name in list(self.app.preprocessors.keys()): + # the HPGL preprocessor is only for Geometry not for Excellon job therefore don't add it + if name == 'hpgl': + continue + self.ui.pp_excellon_name_cb.addItem(name) + + # populate Geometry (milling) preprocessor combobox list + for name in list(self.app.preprocessors.keys()): + self.ui.pp_geo_name_cb.addItem(name) + + # Fill form fields + self.to_form() + + # update the changes in UI depending on the selected preprocessor in Preferences + # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the + # self.ui.pp_excellon_name_cb combobox + self.on_pp_changed() + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText('%s' % _('Basic')) + + self.ui.tools_table.setColumnHidden(4, True) + self.ui.tools_table.setColumnHidden(5, True) + self.ui.estartz_label.hide() + self.ui.estartz_entry.hide() + self.ui.feedrate_rapid_label.hide() + self.ui.feedrate_rapid_entry.hide() + self.ui.pdepth_label.hide() + self.ui.pdepth_entry.hide() + self.ui.feedrate_probe_label.hide() + self.ui.feedrate_probe_entry.hide() + else: + self.ui.level.setText('%s' % _('Advanced')) + + assert isinstance(self.ui, ExcellonObjectUI), \ + "Expected a ExcellonObjectUI, got %s" % type(self.ui) + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click) + self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click) + self.ui.generate_milling_button.clicked.connect(self.on_generate_milling_button_click) + self.ui.generate_milling_slots_button.clicked.connect(self.on_generate_milling_slots_button_click) + + self.on_operation_type(val='drill') + self.ui.operation_radio.activated_custom.connect(self.on_operation_type) + + self.ui.pp_excellon_name_cb.activated.connect(self.on_pp_changed) + + self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked) + + self.units_found = self.app.defaults['units'] + + # ######################################## + # #######3 TEMP SETTINGS ################# + # ######################################## + self.ui.operation_radio.set_value("drill") + self.ui.operation_radio.setEnabled(False) + + def ui_connect(self): + + # selective plotting + for row in range(self.ui.tools_table.rowCount() - 2): + self.ui.tools_table.cellWidget(row, 5).clicked.connect(self.on_plot_cb_click_table) + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + + # rows selected + self.ui.tools_table.clicked.connect(self.on_row_selection_change) + self.ui.tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change) + + # value changed in the particular parameters of a tool + for key, option in self.name2option.items(): + current_widget = self.form_fields[option] + + if isinstance(current_widget, FCCheckBox): + current_widget.stateChanged.connect(self.form_to_storage) + if isinstance(current_widget, RadioSet): + current_widget.activated_custom.connect(self.form_to_storage) + elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner): + current_widget.returnPressed.connect(self.form_to_storage) + + def ui_disconnect(self): + # selective plotting + for row in range(self.ui.tools_table.rowCount()): + try: + self.ui.tools_table.cellWidget(row, 5).clicked.disconnect() + except (TypeError, AttributeError): + pass + try: + self.ui.plot_cb.stateChanged.disconnect() + except (TypeError, AttributeError): + pass + + # rows selected + try: + self.ui.tools_table.clicked.disconnect() + except (TypeError, AttributeError): + pass + try: + self.ui.tools_table.horizontalHeader().sectionClicked.disconnect() + except (TypeError, AttributeError): + pass + + # value changed in the particular parameters of a tool + for key, option in self.name2option.items(): + current_widget = self.form_fields[option] + + if isinstance(current_widget, FCCheckBox): + try: + current_widget.stateChanged.disconnect(self.form_to_storage) + except (TypeError, ValueError): + pass + if isinstance(current_widget, RadioSet): + try: + current_widget.activated_custom.disconnect(self.form_to_storage) + except (TypeError, ValueError): + pass + elif isinstance(current_widget, FCDoubleSpinner) or isinstance(current_widget, FCSpinner): + try: + current_widget.returnPressed.disconnect(self.form_to_storage) + except (TypeError, ValueError): + pass + + def on_row_selection_change(self): + self.ui_disconnect() + + sel_rows = [] + sel_items = self.ui.tools_table.selectedItems() + for it in sel_items: + sel_rows.append(it.row()) + + if not sel_rows: + self.ui.tool_data_label.setText( + "%s: %s" % (_('Parameters for'), _("No Tool Selected")) + ) + self.ui.generate_cnc_button.setDisabled(True) + self.ui.generate_milling_button.setDisabled(True) + self.ui.generate_milling_slots_button.setDisabled(True) + self.ui_connect() + return + else: + self.ui.generate_cnc_button.setDisabled(False) + self.ui.generate_milling_button.setDisabled(False) + self.ui.generate_milling_slots_button.setDisabled(False) + + if len(sel_rows) == 1: + # update the QLabel that shows for which Tool we have the parameters in the UI form + tooluid = int(self.ui.tools_table.item(sel_rows[0], 0).text()) + self.ui.tool_data_label.setText( + "%s: %s %d" % (_('Parameters for'), _("Tool"), tooluid) + ) + else: + self.ui.tool_data_label.setText( + "%s: %s" % (_('Parameters for'), _("Multiple Tools")) + ) + + for c_row in sel_rows: + # populate the form with the data from the tool associated with the row parameter + try: + item = self.ui.tools_table.item(c_row, 0) + if type(item) is not None: + tooluid = item.text() + self.storage_to_form(self.tools[str(tooluid)]['data']) + else: + self.ui_connect() + return + except Exception as e: + log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e)) + self.ui_connect() + return + + self.ui_connect() + + def storage_to_form(self, dict_storage): + for form_key in self.form_fields: + for storage_key in dict_storage: + if form_key == storage_key and form_key not in \ + ["toolchange", "toolchangez", "startz", "endz", "ppname_e", "ppname_g"]: + try: + self.form_fields[form_key].set_value(dict_storage[form_key]) + except Exception as e: + log.debug("ExcellonObject.storage_to_form() --> %s" % str(e)) + pass + + 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 + return + + self.ui_disconnect() + + widget_changed = self.sender() + wdg_objname = widget_changed.objectName() + option_changed = self.name2option[wdg_objname] + + # row = self.ui.tools_table.currentRow() + rows = sorted(set(index.row() for index in self.ui.tools_table.selectedIndexes())) + for row in rows: + if row < 0: + row = 0 + tooluid_item = int(self.ui.tools_table.item(row, 0).text()) + + for tooluid_key, tooluid_val in self.tools.items(): + if int(tooluid_key) == tooluid_item: + new_option_value = self.form_fields[option_changed].get_value() + if option_changed in tooluid_val: + tooluid_val[option_changed] = new_option_value + if option_changed in tooluid_val['data']: + tooluid_val['data'][option_changed] = new_option_value + + self.ui_connect() + + def on_operation_type(self, val): + if val == 'mill': + self.ui.mill_type_label.show() + self.ui.milling_type_radio.show() + self.ui.mill_dia_label.show() + self.ui.mill_dia_entry.show() + self.ui.frxylabel.show() + self.ui.xyfeedrate_entry.show() + self.ui.extracut_cb.show() + self.ui.e_cut_entry.show() + + # if 'laser' not in self.ui.pp_excellon_name_cb.get_value().lower(): + # self.ui.mpass_cb.show() + # self.ui.maxdepth_entry.show() + else: + self.ui.mill_type_label.hide() + self.ui.milling_type_radio.hide() + self.ui.mill_dia_label.hide() + self.ui.mill_dia_entry.hide() + # self.ui.mpass_cb.hide() + # self.ui.maxdepth_entry.hide() + self.ui.frxylabel.hide() + self.ui.xyfeedrate_entry.hide() + self.ui.extracut_cb.hide() + self.ui.e_cut_entry.hide() + + def get_selected_tools_list(self): + """ + Returns the keys to the self.tools dictionary corresponding + to the selections on the tool list in the GUI. + + :return: List of tools. + :rtype: list + """ + + return [str(x.text()) for x in self.ui.tools_table.selectedItems()] + + def get_selected_tools_table_items(self): + """ + Returns a list of lists, each list in the list is made out of row elements + + :return: List of table_tools items. + :rtype: list + """ + table_tools_items = [] + for x in self.ui.tools_table.selectedItems(): + # from the columnCount we subtract a value of 1 which represent the last column (plot column) + # which does not have text + txt = '' + elem = [] + + for column in range(0, self.ui.tools_table.columnCount() - 1): + try: + txt = self.ui.tools_table.item(x.row(), column).text() + except AttributeError: + try: + txt = self.ui.tools_table.cellWidget(x.row(), column).currentText() + except AttributeError: + pass + elem.append(txt) + table_tools_items.append(deepcopy(elem)) + # table_tools_items.append([self.ui.tools_table.item(x.row(), column).text() + # for column in range(0, self.ui.tools_table.columnCount() - 1)]) + for item in table_tools_items: + item[0] = str(item[0]) + return table_tools_items + + def export_excellon(self, whole, fract, e_zeros=None, form='dec', factor=1, slot_type='routing'): + """ + Returns two values, first is a boolean , if 1 then the file has slots and second contain the Excellon code + :return: has_slots and Excellon_code + """ + + excellon_code = '' + + # store here if the file has slots, return 1 if any slots, 0 if only drills + has_slots = 0 + + # drills processing + try: + if self.drills: + length = whole + fract + for tool in self.tools: + excellon_code += 'T0%s\n' % str(tool) if int(tool) < 10 else 'T%s\n' % str(tool) + + for drill in self.drills: + if form == 'dec' and tool == drill['tool']: + drill_x = drill['point'].x * factor + drill_y = drill['point'].y * factor + excellon_code += "X{:.{dec}f}Y{:.{dec}f}\n".format(drill_x, drill_y, dec=fract) + elif e_zeros == 'LZ' and tool == drill['tool']: + drill_x = drill['point'].x * factor + drill_y = drill['point'].y * factor + + exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract) + exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract) + + # extract whole part and decimal part + exc_x_formatted = exc_x_formatted.partition('.') + exc_y_formatted = exc_y_formatted.partition('.') + + # left padd the 'whole' part with zeros + x_whole = exc_x_formatted[0].rjust(whole, '0') + y_whole = exc_y_formatted[0].rjust(whole, '0') + + # restore the coordinate padded in the left with 0 and added the decimal part + # without the decinal dot + exc_x_formatted = x_whole + exc_x_formatted[2] + exc_y_formatted = y_whole + exc_y_formatted[2] + + excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted, + yform=exc_y_formatted) + elif tool == drill['tool']: + drill_x = drill['point'].x * factor + drill_y = drill['point'].y * factor + + exc_x_formatted = "{:.{dec}f}".format(drill_x, dec=fract).replace('.', '') + exc_y_formatted = "{:.{dec}f}".format(drill_y, dec=fract).replace('.', '') + + # pad with rear zeros + exc_x_formatted.ljust(length, '0') + exc_y_formatted.ljust(length, '0') + + excellon_code += "X{xform}Y{yform}\n".format(xform=exc_x_formatted, + yform=exc_y_formatted) + except Exception as e: + log.debug(str(e)) + + # slots processing + try: + if self.slots: + has_slots = 1 + for tool in self.tools: + excellon_code += 'G05\n' + + if int(tool) < 10: + excellon_code += 'T0' + str(tool) + '\n' + else: + excellon_code += 'T' + str(tool) + '\n' + + for slot in self.slots: + if form == 'dec' and tool == slot['tool']: + start_slot_x = slot['start'].x * factor + start_slot_y = slot['start'].y * factor + stop_slot_x = slot['stop'].x * factor + stop_slot_y = slot['stop'].y * factor + if slot_type == 'routing': + excellon_code += "G00X{:.{dec}f}Y{:.{dec}f}\nM15\n".format(start_slot_x, + start_slot_y, + dec=fract) + excellon_code += "G01X{:.{dec}f}Y{:.{dec}f}\nM16\n".format(stop_slot_x, + stop_slot_y, + dec=fract) + elif slot_type == 'drilling': + excellon_code += "X{:.{dec}f}Y{:.{dec}f}G85X{:.{dec}f}Y{:.{dec}f}\nG05\n".format( + start_slot_x, start_slot_y, stop_slot_x, stop_slot_y, dec=fract + ) + + elif e_zeros == 'LZ' and tool == slot['tool']: + start_slot_x = slot['start'].x * factor + start_slot_y = slot['start'].y * factor + stop_slot_x = slot['stop'].x * factor + stop_slot_y = slot['stop'].y * factor + + start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '') + start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '') + stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '') + stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '') + + # extract whole part and decimal part + start_slot_x_formatted = start_slot_x_formatted.partition('.') + start_slot_y_formatted = start_slot_y_formatted.partition('.') + stop_slot_x_formatted = stop_slot_x_formatted.partition('.') + stop_slot_y_formatted = stop_slot_y_formatted.partition('.') + + # left padd the 'whole' part with zeros + start_x_whole = start_slot_x_formatted[0].rjust(whole, '0') + start_y_whole = start_slot_y_formatted[0].rjust(whole, '0') + stop_x_whole = stop_slot_x_formatted[0].rjust(whole, '0') + stop_y_whole = stop_slot_y_formatted[0].rjust(whole, '0') + + # restore the coordinate padded in the left with 0 and added the decimal part + # without the decinal dot + start_slot_x_formatted = start_x_whole + start_slot_x_formatted[2] + start_slot_y_formatted = start_y_whole + start_slot_y_formatted[2] + stop_slot_x_formatted = stop_x_whole + stop_slot_x_formatted[2] + stop_slot_y_formatted = stop_y_whole + stop_slot_y_formatted[2] + + if slot_type == 'routing': + excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted, + ystart=start_slot_y_formatted) + excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted, + ystop=stop_slot_y_formatted) + elif slot_type == 'drilling': + excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format( + xstart=start_slot_x_formatted, ystart=start_slot_y_formatted, + xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted + ) + elif tool == slot['tool']: + start_slot_x = slot['start'].x * factor + start_slot_y = slot['start'].y * factor + stop_slot_x = slot['stop'].x * factor + stop_slot_y = slot['stop'].y * factor + length = whole + fract + + start_slot_x_formatted = "{:.{dec}f}".format(start_slot_x, dec=fract).replace('.', '') + start_slot_y_formatted = "{:.{dec}f}".format(start_slot_y, dec=fract).replace('.', '') + stop_slot_x_formatted = "{:.{dec}f}".format(stop_slot_x, dec=fract).replace('.', '') + stop_slot_y_formatted = "{:.{dec}f}".format(stop_slot_y, dec=fract).replace('.', '') + + # pad with rear zeros + start_slot_x_formatted.ljust(length, '0') + start_slot_y_formatted.ljust(length, '0') + stop_slot_x_formatted.ljust(length, '0') + stop_slot_y_formatted.ljust(length, '0') + + if slot_type == 'routing': + excellon_code += "G00X{xstart}Y{ystart}\nM15\n".format(xstart=start_slot_x_formatted, + ystart=start_slot_y_formatted) + excellon_code += "G01X{xstop}Y{ystop}\nM16\n".format(xstop=stop_slot_x_formatted, + ystop=stop_slot_y_formatted) + elif slot_type == 'drilling': + excellon_code += "{xstart}Y{ystart}G85X{xstop}Y{ystop}\nG05\n".format( + xstart=start_slot_x_formatted, ystart=start_slot_y_formatted, + xstop=stop_slot_x_formatted, ystop=stop_slot_y_formatted + ) + except Exception as e: + log.debug(str(e)) + + if not self.drills and not self.slots: + log.debug("FlatCAMObj.ExcellonObject.export_excellon() --> Excellon Object is empty: no drills, no slots.") + return 'fail' + + return has_slots, excellon_code + + def generate_milling_drills(self, tools=None, outname=None, tooldia=None, plot=False, use_thread=False): + """ + Note: This method is a good template for generic operations as + it takes it's options from parameters or otherwise from the + object's options and returns a (success, msg) tuple as feedback + for shell operations. + + :return: Success/failure condition tuple (bool, str). + :rtype: tuple + """ + + # Get the tools from the list. These are keys + # to self.tools + if tools is None: + tools = self.get_selected_tools_list() + + if outname is None: + outname = self.options["name"] + "_mill" + + if tooldia is None: + tooldia = float(self.options["tooldia"]) + + # Sort tools by diameter. items() -> [('name', diameter), ...] + # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 + + sort = [] + for k, v in self.tools.items(): + sort.append((k, v.get('C'))) + sorted_tools = sorted(sort, key=lambda t1: t1[1]) + + if tools == "all": + tools = [i[0] for i in sorted_tools] # List if ordered tool names. + log.debug("Tools 'all' and sorted are: %s" % str(tools)) + + if len(tools) == 0: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Please select one or more tools from the list and try again.")) + return False, "Error: No tools." + + for tool in tools: + if tooldia > self.tools[tool]["C"]: + self.app.inform.emit( + '[ERROR_NOTCL] %s %s: %s' % ( + _("Milling tool for DRILLS is larger than hole size. Cancelled."), + _("Tool"), + str(tool) + ) + ) + return False, "Error: Milling tool is larger than hole." + + def geo_init(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', "Initializer expected a GeometryObject, got %s" % type(geo_obj) + + # ## Add properties to the object + + # get the tool_table items in a list of row items + tool_table_items = self.get_selected_tools_table_items() + # insert an information only element in the front + tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")]) + + geo_obj.options['Tools_in_use'] = tool_table_items + geo_obj.options['type'] = 'Excellon Geometry' + geo_obj.options["cnctooldia"] = str(tooldia) + + geo_obj.solid_geometry = [] + + # in case that the tool used has the same diameter with the hole, and since the maximum resolution + # for FlatCAM is 6 decimals, + # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" + for hole in self.drills: + if hole['tool'] in tools: + buffer_value = self.tools[hole['tool']]["C"] / 2 - tooldia / 2 + if buffer_value == 0: + geo_obj.solid_geometry.append( + Point(hole['point']).buffer(0.0000001).exterior) + else: + geo_obj.solid_geometry.append( + Point(hole['point']).buffer(buffer_value).exterior) + if use_thread: + def geo_thread(app_obj): + app_obj.new_object("geometry", outname, geo_init, plot=plot) + + # Create a promise with the new name + self.app.collection.promise(outname) + + # Send to worker + self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]}) + else: + self.app.new_object("geometry", outname, geo_init, plot=plot) + + return True, "" + + def generate_milling_slots(self, tools=None, outname=None, tooldia=None, plot=True, use_thread=False): + """ + Note: This method is a good template for generic operations as + it takes it's options from parameters or otherwise from the + object's options and returns a (success, msg) tuple as feedback + for shell operations. + + :return: Success/failure condition tuple (bool, str). + :rtype: tuple + """ + + # Get the tools from the list. These are keys + # to self.tools + if tools is None: + tools = self.get_selected_tools_list() + + if outname is None: + outname = self.options["name"] + "_mill" + + if tooldia is None: + tooldia = float(self.options["slot_tooldia"]) + + # Sort tools by diameter. items() -> [('name', diameter), ...] + # sorted_tools = sorted(list(self.tools.items()), key=lambda tl: tl[1]) # no longer works in Python3 + + sort = [] + for k, v in self.tools.items(): + sort.append((k, v.get('C'))) + sorted_tools = sorted(sort, key=lambda t1: t1[1]) + + if tools == "all": + tools = [i[0] for i in sorted_tools] # List if ordered tool names. + log.debug("Tools 'all' and sorted are: %s" % str(tools)) + + if len(tools) == 0: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Please select one or more tools from the list and try again.")) + return False, "Error: No tools." + + for tool in tools: + # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse + adj_toolstable_tooldia = float('%.*f' % (self.decimals, float(tooldia))) + adj_file_tooldia = float('%.*f' % (self.decimals, float(self.tools[tool]["C"]))) + if adj_toolstable_tooldia > adj_file_tooldia + 0.0001: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Milling tool for SLOTS is larger than hole size. Cancelled.")) + return False, "Error: Milling tool is larger than hole." + + def geo_init(geo_obj, app_obj): + assert geo_obj.kind == 'geometry' "Initializer expected a GeometryObject, got %s" % type(geo_obj) + + # ## Add properties to the object + + # get the tool_table items in a list of row items + tool_table_items = self.get_selected_tools_table_items() + # insert an information only element in the front + tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")]) + + geo_obj.options['Tools_in_use'] = tool_table_items + geo_obj.options['type'] = 'Excellon Geometry' + geo_obj.options["cnctooldia"] = str(tooldia) + + geo_obj.solid_geometry = [] + + # in case that the tool used has the same diameter with the hole, and since the maximum resolution + # for FlatCAM is 6 decimals, + # we add a tenth of the minimum value, meaning 0.0000001, which from our point of view is "almost zero" + for slot in self.slots: + if slot['tool'] in tools: + toolstable_tool = float('%.*f' % (self.decimals, float(tooldia))) + file_tool = float('%.*f' % (self.decimals, float(self.tools[tool]["C"]))) + + # I add the 0.0001 value to account for the rounding error in converting from IN to MM and reverse + # for the file_tool (tooldia actually) + buffer_value = float(file_tool / 2) - float(toolstable_tool / 2) + 0.0001 + if buffer_value == 0: + start = slot['start'] + stop = slot['stop'] + + lines_string = LineString([start, stop]) + poly = lines_string.buffer(0.0000001, int(self.geo_steps_per_circle)).exterior + geo_obj.solid_geometry.append(poly) + else: + start = slot['start'] + stop = slot['stop'] + + lines_string = LineString([start, stop]) + poly = lines_string.buffer(buffer_value, int(self.geo_steps_per_circle)).exterior + geo_obj.solid_geometry.append(poly) + + if use_thread: + def geo_thread(app_obj): + app_obj.new_object("geometry", outname + '_slot', geo_init, plot=plot) + + # Create a promise with the new name + self.app.collection.promise(outname) + + # Send to worker + self.app.worker_task.emit({'fcn': geo_thread, 'params': [self.app]}) + else: + self.app.new_object("geometry", outname + '_slot', geo_init, plot=plot) + + return True, "" + + def on_generate_milling_button_click(self, *args): + self.app.defaults.report_usage("excellon_on_create_milling_drills button") + self.read_form() + + self.generate_milling_drills(use_thread=False) + + def on_generate_milling_slots_button_click(self, *args): + self.app.defaults.report_usage("excellon_on_create_milling_slots_button") + self.read_form() + + self.generate_milling_slots(use_thread=False) + + def on_pp_changed(self): + current_pp = self.ui.pp_excellon_name_cb.get_value() + + if "toolchange_probe" in current_pp.lower(): + self.ui.pdepth_entry.setVisible(True) + self.ui.pdepth_label.show() + + self.ui.feedrate_probe_entry.setVisible(True) + self.ui.feedrate_probe_label.show() + else: + self.ui.pdepth_entry.setVisible(False) + self.ui.pdepth_label.hide() + + self.ui.feedrate_probe_entry.setVisible(False) + self.ui.feedrate_probe_label.hide() + + if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower(): + self.ui.feedrate_rapid_label.show() + self.ui.feedrate_rapid_entry.show() + else: + self.ui.feedrate_rapid_label.hide() + self.ui.feedrate_rapid_entry.hide() + + if 'laser' in current_pp.lower(): + self.ui.cutzlabel.hide() + self.ui.cutz_entry.hide() + try: + self.ui.mpass_cb.hide() + self.ui.maxdepth_entry.hide() + except AttributeError: + pass + + if 'marlin' in current_pp.lower(): + self.ui.travelzlabel.setText('%s:' % _("Focus Z")) + self.ui.endz_label.show() + self.ui.endz_entry.show() + else: + self.ui.travelzlabel.hide() + self.ui.travelz_entry.hide() + + self.ui.endz_label.hide() + self.ui.endz_entry.hide() + + try: + self.ui.frzlabel.hide() + self.ui.feedrate_z_entry.hide() + except AttributeError: + pass + + self.ui.dwell_cb.hide() + self.ui.dwelltime_entry.hide() + + self.ui.spindle_label.setText('%s:' % _("Laser Power")) + + try: + self.ui.tool_offset_label.hide() + self.ui.offset_entry.hide() + except AttributeError: + pass + else: + self.ui.cutzlabel.show() + self.ui.cutz_entry.show() + try: + self.ui.mpass_cb.show() + self.ui.maxdepth_entry.show() + except AttributeError: + pass + + self.ui.travelzlabel.setText('%s:' % _('Travel Z')) + + self.ui.travelzlabel.show() + self.ui.travelz_entry.show() + + self.ui.endz_label.show() + self.ui.endz_entry.show() + + try: + self.ui.frzlabel.show() + self.ui.feedrate_z_entry.show() + except AttributeError: + pass + self.ui.dwell_cb.show() + self.ui.dwelltime_entry.show() + + self.ui.spindle_label.setText('%s:' % _('Spindle speed')) + + try: + self.ui.tool_offset_lbl.show() + self.ui.offset_entry.show() + except AttributeError: + pass + + def on_create_cncjob_button_click(self, *args): + self.app.defaults.report_usage("excellon_on_create_cncjob_button") + self.read_form() + + # Get the tools from the list + tools = self.get_selected_tools_list() + + if len(tools) == 0: + # if there is a single tool in the table (remember that the last 2 rows are for totals and do not count in + # tool number) it means that there are 3 rows (1 tool and 2 totals). + # in this case regardless of the selection status of that tool, use it. + if self.ui.tools_table.rowCount() == 3: + tools.append(self.ui.tools_table.item(0, 0).text()) + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Please select one or more tools from the list and try again.")) + return + + xmin = self.options['xmin'] + ymin = self.options['ymin'] + xmax = self.options['xmax'] + ymax = self.options['ymax'] + + job_name = self.options["name"] + "_cnc" + pp_excellon_name = self.options["ppname_e"] + + # Object initialization function for app.new_object() + def job_init(job_obj, app_obj): + assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj) + + # get the tool_table items in a list of row items + tool_table_items = self.get_selected_tools_table_items() + # insert an information only element in the front + tool_table_items.insert(0, [_("Tool_nr"), _("Diameter"), _("Drills_Nr"), _("Slots_Nr")]) + + # ## Add properties to the object + + job_obj.origin_kind = 'excellon' + + job_obj.options['Tools_in_use'] = tool_table_items + job_obj.options['type'] = 'Excellon' + job_obj.options['ppname_e'] = pp_excellon_name + + job_obj.multidepth = self.options["multidepth"] + job_obj.z_depthpercut = self.options["depthperpass"] + + job_obj.z_move = float(self.options["travelz"]) + job_obj.feedrate = float(self.options["feedrate_z"]) + job_obj.z_feedrate = float(self.options["feedrate_z"]) + job_obj.feedrate_rapid = float(self.options["feedrate_rapid"]) + + job_obj.spindlespeed = float(self.options["spindlespeed"]) if self.options["spindlespeed"] != 0 else None + job_obj.spindledir = self.app.defaults['excellon_spindledir'] + job_obj.dwell = self.options["dwell"] + job_obj.dwelltime = float(self.options["dwelltime"]) + + job_obj.pp_excellon_name = pp_excellon_name + + job_obj.toolchange_xy_type = "excellon" + job_obj.coords_decimals = int(self.app.defaults["cncjob_coords_decimals"]) + job_obj.fr_decimals = int(self.app.defaults["cncjob_fr_decimals"]) + + job_obj.options['xmin'] = xmin + job_obj.options['ymin'] = ymin + job_obj.options['xmax'] = xmax + job_obj.options['ymax'] = ymax + + job_obj.z_pdepth = float(self.options["z_pdepth"]) + job_obj.feedrate_probe = float(self.options["feedrate_probe"]) + + job_obj.z_cut = float(self.options['cutz']) + job_obj.toolchange = self.options["toolchange"] + job_obj.xy_toolchange = self.app.defaults["excellon_toolchangexy"] + job_obj.z_toolchange = float(self.options["toolchangez"]) + job_obj.startz = float(self.options["startz"]) if self.options["startz"] else None + job_obj.endz = float(self.options["endz"]) + job_obj.xy_end = self.options["endxy"] + job_obj.excellon_optimization_type = self.app.defaults["excellon_optimization_type"] + + tools_csv = ','.join(tools) + ret_val = job_obj.generate_from_excellon_by_tool(self, tools_csv, use_ui=True) + + if ret_val == 'fail': + return 'fail' + + job_obj.gcode_parse() + job_obj.create_geometry() + + # To be run in separate thread + def job_thread(app_obj): + with self.app.proc_container.new(_("Generating CNC Code")): + app_obj.new_object("cncjob", job_name, job_init) + + # Create promise for the new name. + self.app.collection.promise(job_name) + + # Send to worker + # self.app.worker.add_task(job_thread, [self.app]) + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + + def convert_units(self, units): + log.debug("FlatCAMObj.ExcellonObject.convert_units()") + + Excellon.convert_units(self, units) + + # factor = Excellon.convert_units(self, units) + # self.options['drillz'] = float(self.options['drillz']) * factor + # self.options['travelz'] = float(self.options['travelz']) * factor + # self.options['feedrate'] = float(self.options['feedrate']) * factor + # self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor + # self.options['toolchangez'] = float(self.options['toolchangez']) * factor + # + # if self.app.defaults["excellon_toolchangexy"] == '': + # self.options['toolchangexy'] = "0.0, 0.0" + # else: + # coords_xy = [float(eval(coord)) for coord in self.app.defaults["excellon_toolchangexy"].split(",")] + # if len(coords_xy) < 2: + # self.app.inform.emit('[ERROR] %s' % _("The Toolchange X,Y field in Edit -> Preferences has to be " + # "in the format (x, y) \n" + # "but now there is only one value, not two. ")) + # return 'fail' + # coords_xy[0] *= factor + # coords_xy[1] *= factor + # self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) + # + # if self.options['startz'] is not None: + # self.options['startz'] = float(self.options['startz']) * factor + # self.options['endz'] = float(self.options['endz']) * factor + + def on_solid_cb_click(self, *args): + if self.muted_ui: + return + self.read_form_item('solid') + self.plot() + + def on_plot_cb_click(self, *args): + if self.muted_ui: + return + self.plot() + self.read_form_item('plot') + + self.ui_disconnect() + cb_flag = self.ui.plot_cb.isChecked() + for row in range(self.ui.tools_table.rowCount() - 2): + table_cb = self.ui.tools_table.cellWidget(row, 5) + if cb_flag: + table_cb.setChecked(True) + else: + table_cb.setChecked(False) + + self.ui_connect() + + def on_plot_cb_click_table(self): + # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) + self.ui_disconnect() + # cw = self.sender() + # cw_index = self.ui.tools_table.indexAt(cw.pos()) + # cw_row = cw_index.row() + check_row = 0 + + self.shapes.clear(update=True) + for tool_key in self.tools: + solid_geometry = self.tools[tool_key]['solid_geometry'] + + # find the geo_tool_table row associated with the tool_key + for row in range(self.ui.tools_table.rowCount()): + tool_item = int(self.ui.tools_table.item(row, 0).text()) + if tool_item == int(tool_key): + check_row = row + break + if self.ui.tools_table.cellWidget(check_row, 5).isChecked(): + self.options['plot'] = True + # self.plot_element(element=solid_geometry, visible=True) + # Plot excellon (All polygons?) + if self.options["solid"]: + for geo in solid_geometry: + self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', + visible=self.options['plot'], + layer=2) + else: + for geo in solid_geometry: + self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot']) + for ints in geo.interiors: + self.add_shape(shape=ints, color='green', visible=self.options['plot']) + self.shapes.redraw() + + # make sure that the general plot is disabled if one of the row plot's are disabled and + # if all the row plot's are enabled also enable the general plot checkbox + cb_cnt = 0 + total_row = self.ui.tools_table.rowCount() + for row in range(total_row - 2): + if self.ui.tools_table.cellWidget(row, 5).isChecked(): + cb_cnt += 1 + else: + cb_cnt -= 1 + if cb_cnt < total_row - 2: + self.ui.plot_cb.setChecked(False) + else: + self.ui.plot_cb.setChecked(True) + self.ui_connect() + + def plot(self, visible=None, kind=None): + + # Does all the required setup and returns False + # if the 'ptint' option is set to False. + if not FlatCAMObj.plot(self): + return + + # try: + # # Plot Excellon (All polygons?) + # if self.options["solid"]: + # for tool in self.tools: + # for geo in self.tools[tool]['solid_geometry']: + # self.add_shape(shape=geo, color='#750000BF', face_color='#C40000BF', + # visible=self.options['plot'], + # layer=2) + # else: + # for tool in self.tools: + # for geo in self.tools[tool]['solid_geometry']: + # self.add_shape(shape=geo.exterior, color='red', visible=self.options['plot']) + # for ints in geo.interiors: + # self.add_shape(shape=ints, color='orange', visible=self.options['plot']) + # + # self.shapes.redraw() + # return + # except (ObjectDeleted, AttributeError, KeyError): + # self.shapes.clear(update=True) + + # this stays for compatibility reasons, in case we try to open old projects + try: + __ = iter(self.solid_geometry) + except TypeError: + self.solid_geometry = [self.solid_geometry] + + visible = visible if visible else self.options['plot'] + + try: + # Plot Excellon (All polygons?) + if self.options["solid"]: + for geo in self.solid_geometry: + self.add_shape(shape=geo, + color=self.outline_color, + face_color=self.fill_color, + visible=visible, + layer=2) + else: + for geo in self.solid_geometry: + self.add_shape(shape=geo.exterior, color='red', visible=visible) + for ints in geo.interiors: + self.add_shape(shape=ints, color='orange', visible=visible) + + self.shapes.redraw() + except (ObjectDeleted, AttributeError): + self.shapes.clear(update=True) + + 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 + log.debug("ExcellonObject.on_apply_param_to_all_clicked() --> no tool in Tools Table, aborting.") + return + + self.ui_disconnect() + + row = self.ui.tools_table.currentRow() + if row < 0: + row = 0 + + tooluid_item = int(self.ui.tools_table.item(row, 0).text()) + temp_tool_data = {} + + for tooluid_key, tooluid_val in self.tools.items(): + if int(tooluid_key) == tooluid_item: + # this will hold the 'data' key of the self.tools[tool] dictionary that corresponds to + # the current row in the tool table + temp_tool_data = tooluid_val['data'] + break + + for tooluid_key, tooluid_val in self.tools.items(): + tooluid_val['data'] = deepcopy(temp_tool_data) + + self.app.inform.emit('[success] %s' % _("Current Tool parameters were applied to all tools.")) + + self.ui_connect() diff --git a/flatcamObjects/FlatCAMGeometry.py b/flatcamObjects/FlatCAMGeometry.py new file mode 100644 index 00000000..e18bd96e --- /dev/null +++ b/flatcamObjects/FlatCAMGeometry.py @@ -0,0 +1,2622 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + +from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString, LinearRing +import shapely.affinity as affinity + +from camlib import Geometry + +from flatcamObjects.FlatCAMObj import * + +import ezdxf +import math +import numpy as np +from copy import deepcopy +import traceback + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class GeometryObject(FlatCAMObj, Geometry): + """ + Geometric object not associated with a specific + format. + """ + optionChanged = QtCore.pyqtSignal(str) + ui_type = GeometryObjectUI + + def __init__(self, name): + self.decimals = self.app.decimals + + self.circle_steps = int(self.app.defaults["geometry_circle_steps"]) + + FlatCAMObj.__init__(self, name) + Geometry.__init__(self, geo_steps_per_circle=self.circle_steps) + + self.kind = "geometry" + + self.options.update({ + "plot": True, + "cutz": -0.002, + "vtipdia": 0.1, + "vtipangle": 30, + "travelz": 0.1, + "feedrate": 5.0, + "feedrate_z": 5.0, + "feedrate_rapid": 5.0, + "spindlespeed": 0, + "dwell": True, + "dwelltime": 1000, + "multidepth": False, + "depthperpass": 0.002, + "extracut": False, + "extracut_length": 0.1, + "endz": 2.0, + "endxy": '', + + "startz": None, + "toolchange": False, + "toolchangez": 1.0, + "toolchangexy": "0.0, 0.0", + "ppname_g": 'default', + "z_pdepth": -0.02, + "feedrate_probe": 3.0, + }) + + if "cnctooldia" not in self.options: + if type(self.app.defaults["geometry_cnctooldia"]) == float: + self.options["cnctooldia"] = self.app.defaults["geometry_cnctooldia"] + else: + try: + tools_string = self.app.defaults["geometry_cnctooldia"].split(",") + tools_diameters = [eval(a) for a in tools_string if a != ''] + self.options["cnctooldia"] = tools_diameters[0] if tools_diameters else 0.0 + except Exception as e: + log.debug("FlatCAMObj.GeometryObject.init() --> %s" % str(e)) + + self.options["startz"] = self.app.defaults["geometry_startz"] + + # this will hold the tool unique ID that is useful when having multiple tools with same diameter + self.tooluid = 0 + + ''' + self.tools = {} + This is a dictionary. Each dict key is associated with a tool used in geo_tools_table. The key is the + tool_id of the tools and the value is another dict that will hold the data under the following form: + {tooluid: { + 'tooldia': 1, + 'offset': 'Path', + 'offset_value': 0.0 + 'type': 'Rough', + 'tool_type': 'C1', + 'data': self.default_tool_data + 'solid_geometry': [] + } + } + ''' + self.tools = {} + + # this dict is to store those elements (tools) of self.tools that are selected in the self.geo_tools_table + # those elements are the ones used for generating GCode + self.sel_tools = {} + + self.offset_item_options = ["Path", "In", "Out", "Custom"] + self.type_item_options = [_("Iso"), _("Rough"), _("Finish")] + self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"] + + # flag to store if the V-Shape tool is selected in self.ui.geo_tools_table + self.v_tool_type = None + + # flag to store if the Geometry is type 'multi-geometry' meaning that each tool has it's own geometry + # the default value is False + self.multigeo = False + + # flag to store if the geometry is part of a special group of geometries that can't be processed by the default + # engine of FlatCAM. Most likely are generated by some of tools and are special cases of geometries. + self.special_group = None + + self.old_pp_state = self.app.defaults["geometry_multidepth"] + self.old_toolchangeg_state = self.app.defaults["geometry_toolchange"] + self.units_found = self.app.defaults['units'] + + # this variable can be updated by the Object that generates the geometry + self.tool_type = 'C1' + + # save here the old value for the Cut Z before it is changed by selecting a V-shape type tool in the tool table + self.old_cutz = self.app.defaults["geometry_cutz"] + + self.fill_color = self.app.defaults['geometry_plot_line'] + self.outline_color = self.app.defaults['geometry_plot_line'] + self.alpha_level = 'FF' + + self.param_fields = {} + + # Attributes to be included in serialization + # Always append to it because it carries contents + # from predecessors. + self.ser_attrs += ['options', 'kind', 'tools', 'multigeo'] + + def build_ui(self): + self.ui_disconnect() + FlatCAMObj.build_ui(self) + + self.units = self.app.defaults['units'] + + tool_idx = 0 + + n = len(self.tools) + self.ui.geo_tools_table.setRowCount(n) + + for tooluid_key, tooluid_value in self.tools.items(): + tool_idx += 1 + row_no = tool_idx - 1 + + tool_id = QtWidgets.QTableWidgetItem('%d' % int(tool_idx)) + tool_id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.geo_tools_table.setItem(row_no, 0, tool_id) # Tool name/id + + # Make sure that the tool diameter when in MM is with no more than 2 decimals. + # There are no tool bits in MM with more than 3 decimals diameter. + # For INCH the decimals should be no more than 3. There are no tools under 10mils. + + dia_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, float(tooluid_value['tooldia']))) + + dia_item.setFlags(QtCore.Qt.ItemIsEnabled) + + offset_item = FCComboBox() + for item in self.offset_item_options: + offset_item.addItem(item) + # offset_item.setStyleSheet('background-color: rgb(255,255,255)') + idx = offset_item.findText(tooluid_value['offset']) + offset_item.setCurrentIndex(idx) + + type_item = FCComboBox() + for item in self.type_item_options: + type_item.addItem(item) + # type_item.setStyleSheet('background-color: rgb(255,255,255)') + idx = type_item.findText(tooluid_value['type']) + type_item.setCurrentIndex(idx) + + tool_type_item = FCComboBox() + for item in self.tool_type_item_options: + tool_type_item.addItem(item) + # tool_type_item.setStyleSheet('background-color: rgb(255,255,255)') + idx = tool_type_item.findText(tooluid_value['tool_type']) + tool_type_item.setCurrentIndex(idx) + + tool_uid_item = QtWidgets.QTableWidgetItem(str(tooluid_key)) + + plot_item = FCCheckBox() + plot_item.setLayoutDirection(QtCore.Qt.RightToLeft) + if self.ui.plot_cb.isChecked(): + plot_item.setChecked(True) + + self.ui.geo_tools_table.setItem(row_no, 1, dia_item) # Diameter + self.ui.geo_tools_table.setCellWidget(row_no, 2, offset_item) + self.ui.geo_tools_table.setCellWidget(row_no, 3, type_item) + self.ui.geo_tools_table.setCellWidget(row_no, 4, tool_type_item) + + # ## REMEMBER: THIS COLUMN IS HIDDEN IN OBJECTUI.PY ### + self.ui.geo_tools_table.setItem(row_no, 5, tool_uid_item) # Tool unique ID + self.ui.geo_tools_table.setCellWidget(row_no, 6, plot_item) + + try: + self.ui.tool_offset_entry.set_value(tooluid_value['offset_value']) + except Exception as e: + log.debug("build_ui() --> Could not set the 'offset_value' key in self.tools. Error: %s" % str(e)) + + # make the diameter column editable + for row in range(tool_idx): + self.ui.geo_tools_table.item(row, 1).setFlags(QtCore.Qt.ItemIsSelectable | + QtCore.Qt.ItemIsEditable | + QtCore.Qt.ItemIsEnabled) + + # sort the tool diameter column + # self.ui.geo_tools_table.sortItems(1) + # all the tools are selected by default + # self.ui.geo_tools_table.selectColumn(0) + + self.ui.geo_tools_table.resizeColumnsToContents() + self.ui.geo_tools_table.resizeRowsToContents() + + vertical_header = self.ui.geo_tools_table.verticalHeader() + # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + vertical_header.hide() + self.ui.geo_tools_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + horizontal_header = self.ui.geo_tools_table.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 20) + horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) + # horizontal_header.setColumnWidth(2, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(4, 40) + horizontal_header.setSectionResizeMode(6, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(4, 17) + # horizontal_header.setStretchLastSection(True) + self.ui.geo_tools_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.ui.geo_tools_table.setColumnWidth(0, 20) + self.ui.geo_tools_table.setColumnWidth(4, 40) + self.ui.geo_tools_table.setColumnWidth(6, 17) + + # self.ui.geo_tools_table.setSortingEnabled(True) + + self.ui.geo_tools_table.setMinimumHeight(self.ui.geo_tools_table.getHeight()) + self.ui.geo_tools_table.setMaximumHeight(self.ui.geo_tools_table.getHeight()) + + # update UI for all rows - useful after units conversion but only if there is at least one row + row_cnt = self.ui.geo_tools_table.rowCount() + if row_cnt > 0: + for r in range(row_cnt): + self.update_ui(r) + + # select only the first tool / row + selected_row = 0 + try: + self.select_tools_table_row(selected_row, clearsel=True) + # update the Geometry UI + self.update_ui() + except Exception as e: + # when the tools table is empty there will be this error but once the table is populated it will go away + log.debug(str(e)) + + # disable the Plot column in Tool Table if the geometry is SingleGeo as it is not needed + # and can create some problems + if self.multigeo is False: + self.ui.geo_tools_table.setColumnHidden(6, True) + else: + self.ui.geo_tools_table.setColumnHidden(6, False) + + self.set_tool_offset_visibility(selected_row) + + # HACK: for whatever reasons the name in Selected tab is reverted to the original one after a successful rename + # done in the collection view but only for Geometry objects. Perhaps some references remains. Should be fixed. + self.ui.name_entry.set_value(self.options['name']) + self.ui_connect() + + self.ui.e_cut_entry.setDisabled(False) if self.ui.extracut_cb.get_value() else \ + self.ui.e_cut_entry.setDisabled(True) + + # set the text on tool_data_label after loading the object + sel_rows = [] + sel_items = self.ui.geo_tools_table.selectedItems() + for it in sel_items: + sel_rows.append(it.row()) + if len(sel_rows) > 1: + self.ui.tool_data_label.setText( + "%s: %s" % (_('Parameters for'), _("Multiple Tools")) + ) + + def set_ui(self, ui): + FlatCAMObj.set_ui(self, ui) + + log.debug("GeometryObject.set_ui()") + + assert isinstance(self.ui, GeometryObjectUI), \ + "Expected a GeometryObjectUI, got %s" % type(self.ui) + + self.units = self.app.defaults['units'].upper() + self.units_found = self.app.defaults['units'] + + # populate preprocessor names in the combobox + for name in list(self.app.preprocessors.keys()): + self.ui.pp_geometry_name_cb.addItem(name) + + self.form_fields.update({ + "plot": self.ui.plot_cb, + "cutz": self.ui.cutz_entry, + "vtipdia": self.ui.tipdia_entry, + "vtipangle": self.ui.tipangle_entry, + "travelz": self.ui.travelz_entry, + "feedrate": self.ui.cncfeedrate_entry, + "feedrate_z": self.ui.feedrate_z_entry, + "feedrate_rapid": self.ui.feedrate_rapid_entry, + "spindlespeed": self.ui.cncspindlespeed_entry, + "dwell": self.ui.dwell_cb, + "dwelltime": self.ui.dwelltime_entry, + "multidepth": self.ui.mpass_cb, + "ppname_g": self.ui.pp_geometry_name_cb, + "z_pdepth": self.ui.pdepth_entry, + "feedrate_probe": self.ui.feedrate_probe_entry, + "depthperpass": self.ui.maxdepth_entry, + "extracut": self.ui.extracut_cb, + "extracut_length": self.ui.e_cut_entry, + "toolchange": self.ui.toolchangeg_cb, + "toolchangez": self.ui.toolchangez_entry, + "endz": self.ui.endz_entry, + "endxy": self.ui.endxy_entry, + "cnctooldia": self.ui.addtool_entry + }) + + self.param_fields.update({ + "vtipdia": self.ui.tipdia_entry, + "vtipangle": self.ui.tipangle_entry, + "cutz": self.ui.cutz_entry, + "depthperpass": self.ui.maxdepth_entry, + "multidepth": self.ui.mpass_cb, + "travelz": self.ui.travelz_entry, + "feedrate": self.ui.cncfeedrate_entry, + "feedrate_z": self.ui.feedrate_z_entry, + "feedrate_rapid": self.ui.feedrate_rapid_entry, + "extracut": self.ui.extracut_cb, + "extracut_length": self.ui.e_cut_entry, + "spindlespeed": self.ui.cncspindlespeed_entry, + "dwelltime": self.ui.dwelltime_entry, + "dwell": self.ui.dwell_cb, + "pdepth": self.ui.pdepth_entry, + "pfeedrate": self.ui.feedrate_probe_entry, + }) + # Fill form fields only on object create + self.to_form() + + # update the changes in UI depending on the selected preprocessor in Preferences + # after this moment all the changes in the Posprocessor combo will be handled by the activated signal of the + # self.ui.pp_geometry_name_cb combobox + self.on_pp_changed() + + self.ui.tipdialabel.hide() + self.ui.tipdia_entry.hide() + self.ui.tipanglelabel.hide() + self.ui.tipangle_entry.hide() + self.ui.cutz_entry.setDisabled(False) + + # store here the default data for Geometry Data + self.default_data = {} + self.default_data.update({ + "name": None, + "plot": None, + "cutz": None, + "vtipdia": None, + "vtipangle": None, + "travelz": None, + "feedrate": None, + "feedrate_z": None, + "feedrate_rapid": None, + "dwell": None, + "dwelltime": None, + "multidepth": None, + "ppname_g": None, + "depthperpass": None, + "extracut": None, + "extracut_length": None, + "toolchange": None, + "toolchangez": None, + "endz": None, + "endxy": '', + "spindlespeed": 0, + "toolchangexy": None, + "startz": None + }) + + # fill in self.default_data values from self.options + for def_key in self.default_data: + for opt_key, opt_val in self.options.items(): + if def_key == opt_key: + self.default_data[def_key] = deepcopy(opt_val) + + if type(self.options["cnctooldia"]) == float: + tools_list = [self.options["cnctooldia"]] + else: + try: + temp_tools = self.options["cnctooldia"].split(",") + tools_list = [ + float(eval(dia)) for dia in temp_tools if dia != '' + ] + except Exception as e: + log.error("GeometryObject.set_ui() -> At least one tool diameter needed. " + "Verify in Edit -> Preferences -> Geometry General -> Tool dia. %s" % str(e)) + return + + self.tooluid += 1 + + if not self.tools: + for toold in tools_list: + new_data = deepcopy(self.default_data) + self.tools.update({ + self.tooluid: { + 'tooldia': float('%.*f' % (self.decimals, float(toold))), + 'offset': 'Path', + 'offset_value': 0.0, + 'type': _('Rough'), + 'tool_type': self.tool_type, + 'data': new_data, + 'solid_geometry': self.solid_geometry + } + }) + self.tooluid += 1 + else: + # if self.tools is not empty then it can safely be assumed that it comes from an opened project. + # Because of the serialization the self.tools list on project save, the dict keys (members of self.tools + # are each a dict) are turned into strings so we rebuild the self.tools elements so the keys are + # again float type; dict's don't like having keys changed when iterated through therefore the need for the + # following convoluted way of changing the keys from string to float type + temp_tools = {} + for tooluid_key in self.tools: + val = deepcopy(self.tools[tooluid_key]) + new_key = deepcopy(int(tooluid_key)) + temp_tools[new_key] = val + + self.tools.clear() + self.tools = deepcopy(temp_tools) + + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + + # used to store the state of the mpass_cb if the selected preprocessor for geometry is hpgl + self.old_pp_state = self.default_data['multidepth'] + self.old_toolchangeg_state = self.default_data['toolchange'] + + if not isinstance(self.ui, GeometryObjectUI): + log.debug("Expected a GeometryObjectUI, got %s" % type(self.ui)) + return + + self.ui.geo_tools_table.setupContextMenu() + self.ui.geo_tools_table.addContextMenu( + _("Add from Tool DB"), self.on_tool_add_from_db_clicked, + icon=QtGui.QIcon(self.app.resource_location + "/plus16.png")) + self.ui.geo_tools_table.addContextMenu( + _("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(all_tools=None), + icon=QtGui.QIcon(self.app.resource_location + "/delete32.png")) + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText('%s' % _('Basic')) + + self.ui.geo_tools_table.setColumnHidden(2, True) + self.ui.geo_tools_table.setColumnHidden(3, True) + # self.ui.geo_tools_table.setColumnHidden(4, True) + self.ui.addtool_entry_lbl.hide() + self.ui.addtool_entry.hide() + self.ui.addtool_btn.hide() + self.ui.copytool_btn.hide() + self.ui.deltool_btn.hide() + # self.ui.endz_label.hide() + # self.ui.endz_entry.hide() + self.ui.fr_rapidlabel.hide() + self.ui.feedrate_rapid_entry.hide() + self.ui.extracut_cb.hide() + self.ui.e_cut_entry.hide() + self.ui.pdepth_label.hide() + self.ui.pdepth_entry.hide() + self.ui.feedrate_probe_label.hide() + self.ui.feedrate_probe_entry.hide() + else: + self.ui.level.setText('%s' % _('Advanced')) + + self.ui.e_cut_entry.setDisabled(False) if self.app.defaults['geometry_extracut'] else \ + self.ui.e_cut_entry.setDisabled(True) + self.ui.extracut_cb.toggled.connect(lambda state: self.ui.e_cut_entry.setDisabled(not state)) + + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click) + self.ui.paint_tool_button.clicked.connect(lambda: self.app.paint_tool.run(toggle=False)) + self.ui.generate_ncc_button.clicked.connect(lambda: self.app.ncclear_tool.run(toggle=False)) + self.ui.pp_geometry_name_cb.activated.connect(self.on_pp_changed) + + self.ui.tipdia_entry.valueChanged.connect(self.update_cutz) + self.ui.tipangle_entry.valueChanged.connect(self.update_cutz) + + self.ui.addtool_from_db_btn.clicked.connect(self.on_tool_add_from_db_clicked) + self.ui.apply_param_to_all.clicked.connect(self.on_apply_param_to_all_clicked) + self.ui.cutz_entry.returnPressed.connect(self.on_cut_z_changed) + + def on_cut_z_changed(self): + self.old_cutz = self.ui.cutz_entry.get_value() + + def set_tool_offset_visibility(self, current_row): + if current_row is None: + return + try: + tool_offset = self.ui.geo_tools_table.cellWidget(current_row, 2) + if tool_offset is not None: + tool_offset_txt = tool_offset.currentText() + if tool_offset_txt == 'Custom': + self.ui.tool_offset_entry.show() + self.ui.tool_offset_lbl.show() + else: + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + except Exception as e: + log.debug("set_tool_offset_visibility() --> " + str(e)) + return + + def on_offset_value_edited(self): + """ + This will save the offset_value into self.tools storage whenever the offset value is edited + :return: + """ + + for current_row in self.ui.geo_tools_table.selectedItems(): + # sometime the header get selected and it has row number -1 + # we don't want to do anything with the header :) + if current_row.row() < 0: + continue + tool_uid = int(self.ui.geo_tools_table.item(current_row.row(), 5).text()) + self.set_tool_offset_visibility(current_row.row()) + + for tooluid_key, tooluid_value in self.tools.items(): + if int(tooluid_key) == tool_uid: + try: + tooluid_value['offset_value'] = float(self.ui.tool_offset_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + tooluid_value['offset_value'] = float( + self.ui.tool_offset_entry.get_value().replace(',', '.') + ) + except ValueError: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Wrong value format entered, use a number.")) + return + + def ui_connect(self): + # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the + # changes in geometry UI + for i in self.param_fields: + current_widget = self.param_fields[i] + if isinstance(current_widget, FCCheckBox): + current_widget.stateChanged.connect(self.gui_form_to_storage) + elif isinstance(current_widget, FCComboBox): + current_widget.currentIndexChanged.connect(self.gui_form_to_storage) + elif isinstance(current_widget, FloatEntry) or isinstance(current_widget, LengthEntry) or \ + isinstance(current_widget, FCEntry) or isinstance(current_widget, IntEntry): + current_widget.editingFinished.connect(self.gui_form_to_storage) + elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner): + current_widget.returnPressed.connect(self.gui_form_to_storage) + + for row in range(self.ui.geo_tools_table.rowCount()): + for col in [2, 3, 4]: + self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.connect( + self.on_tooltable_cellwidget_change) + + # I use lambda's because the connected functions have parameters that could be used in certain scenarios + self.ui.addtool_btn.clicked.connect(lambda: self.on_tool_add()) + + self.ui.copytool_btn.clicked.connect(lambda: self.on_tool_copy()) + self.ui.deltool_btn.clicked.connect(lambda: self.on_tool_delete()) + + # self.ui.geo_tools_table.currentItemChanged.connect(self.on_row_selection_change) + self.ui.geo_tools_table.clicked.connect(self.on_row_selection_change) + self.ui.geo_tools_table.horizontalHeader().sectionClicked.connect(self.on_row_selection_change) + + self.ui.geo_tools_table.itemChanged.connect(self.on_tool_edit) + self.ui.tool_offset_entry.returnPressed.connect(self.on_offset_value_edited) + + for row in range(self.ui.geo_tools_table.rowCount()): + self.ui.geo_tools_table.cellWidget(row, 6).clicked.connect(self.on_plot_cb_click_table) + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + + # common parameters update + self.ui.pp_geometry_name_cb.currentIndexChanged.connect(self.update_common_param_in_storage) + + def ui_disconnect(self): + + # on any change to the widgets that matter it will be called self.gui_form_to_storage which will save the + # changes in geometry UI + for i in self.param_fields: + # current_widget = self.ui.grid3.itemAt(i).widget() + current_widget = self.param_fields[i] + if isinstance(current_widget, FCCheckBox): + try: + current_widget.stateChanged.disconnect(self.gui_form_to_storage) + except (TypeError, AttributeError): + pass + elif isinstance(current_widget, FCComboBox): + try: + current_widget.currentIndexChanged.disconnect(self.gui_form_to_storage) + except (TypeError, AttributeError): + pass + elif isinstance(current_widget, LengthEntry) or isinstance(current_widget, IntEntry) or \ + isinstance(current_widget, FCEntry) or isinstance(current_widget, FloatEntry): + try: + current_widget.editingFinished.disconnect(self.gui_form_to_storage) + except (TypeError, AttributeError): + pass + elif isinstance(current_widget, FCSpinner) or isinstance(current_widget, FCDoubleSpinner): + try: + current_widget.returnPressed.disconnect(self.gui_form_to_storage) + except TypeError: + pass + + for row in range(self.ui.geo_tools_table.rowCount()): + for col in [2, 3, 4]: + try: + self.ui.geo_tools_table.cellWidget(row, col).currentIndexChanged.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.addtool_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.copytool_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.deltool_btn.clicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.geo_tools_table.clicked.disconnect() + except (TypeError, AttributeError): + pass + try: + self.ui.geo_tools_table.horizontalHeader().sectionClicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.geo_tools_table.itemChanged.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.tool_offset_entry.returnPressed.disconnect() + except (TypeError, AttributeError): + pass + + for row in range(self.ui.geo_tools_table.rowCount()): + try: + self.ui.geo_tools_table.cellWidget(row, 6).clicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.plot_cb.stateChanged.disconnect() + except (TypeError, AttributeError): + pass + + def on_row_selection_change(self): + self.update_ui() + + def update_ui(self, row=None): + self.ui_disconnect() + + if row is None: + sel_rows = [] + sel_items = self.ui.geo_tools_table.selectedItems() + for it in sel_items: + sel_rows.append(it.row()) + else: + sel_rows = row if type(row) == list else [row] + + if not sel_rows: + sel_rows = [0] + + for current_row in sel_rows: + self.set_tool_offset_visibility(current_row) + + # populate the form with the data from the tool associated with the row parameter + try: + item = self.ui.geo_tools_table.item(current_row, 5) + if type(item) is not None: + tooluid = int(item.text()) + else: + self.ui_connect() + return + except Exception as e: + log.debug("Tool missing. Add a tool in Geo Tool Table. %s" % str(e)) + self.ui_connect() + return + + # update the QLabel that shows for which Tool we have the parameters in the UI form + if len(sel_rows) == 1: + self.ui.tool_data_label.setText( + "%s: %s %d" % (_('Parameters for'), _("Tool"), tooluid) + ) + + # update the form with the V-Shape fields if V-Shape selected in the geo_tool_table + # also modify the Cut Z form entry to reflect the calculated Cut Z from values got from V-Shape Fields + try: + item = self.ui.geo_tools_table.cellWidget(current_row, 4) + if item is not None: + tool_type_txt = item.currentText() + self.ui_update_v_shape(tool_type_txt=tool_type_txt) + else: + self.ui_connect() + return + except Exception as e: + log.debug("Tool missing in ui_update_v_shape(). Add a tool in Geo Tool Table. %s" % str(e)) + return + + try: + # set the form with data from the newly selected tool + for tooluid_key, tooluid_value in list(self.tools.items()): + if int(tooluid_key) == tooluid: + for key, value in list(tooluid_value.items()): + if key == 'data': + form_value_storage = tooluid_value['data'] + self.update_form(form_value_storage) + if key == 'offset_value': + # update the offset value in the entry even if the entry is hidden + self.ui.tool_offset_entry.set_value(tooluid_value['offset_value']) + + if key == 'tool_type' and value == 'V': + self.update_cutz() + except Exception as e: + log.debug("GeometryObject.update_ui() -> %s " % str(e)) + + else: + self.ui.tool_data_label.setText( + "%s: %s" % (_('Parameters for'), _("Multiple Tools")) + ) + + self.ui_connect() + + def on_tool_add(self, dia=None): + self.ui_disconnect() + + self.units = self.app.defaults['units'].upper() + + if dia is not None: + tooldia = dia + else: + tooldia = float(self.ui.addtool_entry.get_value()) + + # construct a list of all 'tooluid' in the self.tools + # tool_uid_list = [] + # for tooluid_key in self.tools: + # tool_uid_list.append(int(tooluid_key)) + tool_uid_list = [int(tooluid_key) for tooluid_key in self.tools] + + # find maximum from the temp_uid, add 1 and this is the new 'tooluid' + max_uid = max(tool_uid_list) if tool_uid_list else 0 + self.tooluid = max_uid + 1 + + tooldia = float('%.*f' % (self.decimals, tooldia)) + + # here we actually add the new tool; if there is no tool in the tool table we add a tool with default data + # otherwise we add a tool with data copied from last tool + if self.tools: + last_data = self.tools[max_uid]['data'] + last_offset = self.tools[max_uid]['offset'] + last_offset_value = self.tools[max_uid]['offset_value'] + last_type = self.tools[max_uid]['type'] + last_tool_type = self.tools[max_uid]['tool_type'] + last_solid_geometry = self.tools[max_uid]['solid_geometry'] + + # if previous geometry was empty (it may happen for the first tool added) + # then copy the object.solid_geometry + if not last_solid_geometry: + last_solid_geometry = self.solid_geometry + + self.tools.update({ + self.tooluid: { + 'tooldia': tooldia, + 'offset': last_offset, + 'offset_value': last_offset_value, + 'type': last_type, + 'tool_type': last_tool_type, + 'data': deepcopy(last_data), + 'solid_geometry': deepcopy(last_solid_geometry) + } + }) + else: + self.tools.update({ + self.tooluid: { + 'tooldia': tooldia, + 'offset': 'Path', + 'offset_value': 0.0, + 'type': _('Rough'), + 'tool_type': 'C1', + 'data': deepcopy(self.default_data), + 'solid_geometry': self.solid_geometry + } + }) + + self.tools[self.tooluid]['data']['name'] = self.options['name'] + + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + + # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list + try: + self.ser_attrs.remove('tools') + except TypeError: + pass + self.ser_attrs.append('tools') + + self.app.inform.emit('[success] %s' % _("Tool added in Tool Table.")) + self.ui_connect() + self.build_ui() + + # if there is no tool left in the Tools Table, enable the parameters GUI + if self.ui.geo_tools_table.rowCount() != 0: + self.ui.geo_param_frame.setDisabled(False) + + def on_tool_add_from_db_clicked(self): + """ + Called when the user wants to add a new tool from Tools Database. It will create the Tools Database object + and display the Tools Database tab in the form needed for the Tool adding + :return: None + """ + + # if the Tools Database is already opened focus on it + for idx in range(self.app.ui.plot_tab_area.count()): + if self.app.ui.plot_tab_area.tabText(idx) == _("Tools Database"): + self.app.ui.plot_tab_area.setCurrentWidget(self.app.tools_db_tab) + break + self.app.on_tools_database() + self.app.tools_db_tab.ok_to_add = True + self.app.tools_db_tab.buttons_frame.hide() + self.app.tools_db_tab.add_tool_from_db.show() + self.app.tools_db_tab.cancel_tool_from_db.show() + + def on_tool_from_db_inserted(self, tool): + """ + Called from the Tools DB object through a App method when adding a tool from Tools Database + :param tool: a dict with the tool data + :return: None + """ + + self.ui_disconnect() + self.units = self.app.defaults['units'].upper() + + tooldia = float(tool['tooldia']) + + # construct a list of all 'tooluid' in the self.tools + tool_uid_list = [] + for tooluid_key in self.tools: + tool_uid_item = int(tooluid_key) + tool_uid_list.append(tool_uid_item) + + # find maximum from the temp_uid, add 1 and this is the new 'tooluid' + if not tool_uid_list: + max_uid = 0 + else: + max_uid = max(tool_uid_list) + self.tooluid = max_uid + 1 + + tooldia = float('%.*f' % (self.decimals, tooldia)) + + self.tools.update({ + self.tooluid: { + 'tooldia': tooldia, + 'offset': tool['offset'], + 'offset_value': float(tool['offset_value']), + 'type': tool['type'], + 'tool_type': tool['tool_type'], + 'data': deepcopy(tool['data']), + 'solid_geometry': self.solid_geometry + } + }) + + self.tools[self.tooluid]['data']['name'] = self.options['name'] + + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + + # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list + try: + self.ser_attrs.remove('tools') + except TypeError: + pass + self.ser_attrs.append('tools') + + self.ui_connect() + self.build_ui() + + # if there is no tool left in the Tools Table, enable the parameters GUI + if self.ui.geo_tools_table.rowCount() != 0: + self.ui.geo_param_frame.setDisabled(False) + + def on_tool_copy(self, all_tools=None): + self.ui_disconnect() + + # find the tool_uid maximum value in the self.tools + uid_list = [] + for key in self.tools: + uid_list.append(int(key)) + try: + max_uid = max(uid_list, key=int) + except ValueError: + max_uid = 0 + + if all_tools is None: + if self.ui.geo_tools_table.selectedItems(): + for current_row in self.ui.geo_tools_table.selectedItems(): + # sometime the header get selected and it has row number -1 + # we don't want to do anything with the header :) + if current_row.row() < 0: + continue + try: + tooluid_copy = int(self.ui.geo_tools_table.item(current_row.row(), 5).text()) + self.set_tool_offset_visibility(current_row.row()) + max_uid += 1 + self.tools[int(max_uid)] = deepcopy(self.tools[tooluid_copy]) + except AttributeError: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy.")) + self.ui_connect() + self.build_ui() + return + except Exception as e: + log.debug("on_tool_copy() --> " + str(e)) + # deselect the table + # self.ui.geo_tools_table.clearSelection() + else: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to copy.")) + self.ui_connect() + self.build_ui() + return + else: + # we copy all tools in geo_tools_table + try: + temp_tools = deepcopy(self.tools) + max_uid += 1 + for tooluid in temp_tools: + self.tools[int(max_uid)] = deepcopy(temp_tools[tooluid]) + temp_tools.clear() + except Exception as e: + log.debug("on_tool_copy() --> " + str(e)) + + # if there are no more tools in geo tools table then hide the tool offset + if not self.tools: + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + + # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list + try: + self.ser_attrs.remove('tools') + except ValueError: + pass + self.ser_attrs.append('tools') + + self.ui_connect() + self.build_ui() + self.app.inform.emit('[success] %s' % _("Tool was copied in Tool Table.")) + + def on_tool_edit(self, current_item): + self.ui_disconnect() + + current_row = current_item.row() + try: + d = float(self.ui.geo_tools_table.item(current_row, 1).text()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + d = float(self.ui.geo_tools_table.item(current_row, 1).text().replace(',', '.')) + except ValueError: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) + return + + tool_dia = float('%.*f' % (self.decimals, d)) + tooluid = int(self.ui.geo_tools_table.item(current_row, 5).text()) + + self.tools[tooluid]['tooldia'] = tool_dia + + try: + self.ser_attrs.remove('tools') + self.ser_attrs.append('tools') + except (TypeError, ValueError): + pass + + self.app.inform.emit('[success] %s' % _("Tool was edited in Tool Table.")) + self.ui_connect() + self.build_ui() + + def on_tool_delete(self, all_tools=None): + self.ui_disconnect() + + if all_tools is None: + if self.ui.geo_tools_table.selectedItems(): + for current_row in self.ui.geo_tools_table.selectedItems(): + # sometime the header get selected and it has row number -1 + # we don't want to do anything with the header :) + if current_row.row() < 0: + continue + try: + tooluid_del = int(self.ui.geo_tools_table.item(current_row.row(), 5).text()) + self.set_tool_offset_visibility(current_row.row()) + + temp_tools = deepcopy(self.tools) + for tooluid_key in self.tools: + if int(tooluid_key) == tooluid_del: + # if the self.tools has only one tool and we delete it then we move the solid_geometry + # as a property of the object otherwise there will be nothing to hold it + if len(self.tools) == 1: + self.solid_geometry = deepcopy(self.tools[tooluid_key]['solid_geometry']) + temp_tools.pop(tooluid_del, None) + self.tools = deepcopy(temp_tools) + temp_tools.clear() + except AttributeError: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete.")) + self.ui_connect() + self.build_ui() + return + except Exception as e: + log.debug("on_tool_delete() --> " + str(e)) + # deselect the table + # self.ui.geo_tools_table.clearSelection() + else: + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed. Select a tool to delete.")) + self.ui_connect() + self.build_ui() + return + else: + # we delete all tools in geo_tools_table + self.tools.clear() + + self.app.plot_all() + + # if there are no more tools in geo tools table then hide the tool offset + if not self.tools: + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + + # we do this HACK to make sure the tools attribute to be serialized is updated in the self.ser_attrs list + try: + self.ser_attrs.remove('tools') + except TypeError: + pass + self.ser_attrs.append('tools') + + self.ui_connect() + self.build_ui() + self.app.inform.emit('[success] %s' % _("Tool was deleted in Tool Table.")) + + obj_active = self.app.collection.get_active() + # if the object was MultiGeo and now it has no tool at all (therefore no geometry) + # we make it back SingleGeo + if self.ui.geo_tools_table.rowCount() <= 0: + obj_active.multigeo = False + obj_active.options['xmin'] = 0 + obj_active.options['ymin'] = 0 + obj_active.options['xmax'] = 0 + obj_active.options['ymax'] = 0 + + if obj_active.multigeo is True: + try: + xmin, ymin, xmax, ymax = obj_active.bounds() + obj_active.options['xmin'] = xmin + obj_active.options['ymin'] = ymin + obj_active.options['xmax'] = xmax + obj_active.options['ymax'] = ymax + except Exception: + obj_active.options['xmin'] = 0 + obj_active.options['ymin'] = 0 + obj_active.options['xmax'] = 0 + obj_active.options['ymax'] = 0 + + # if there is no tool left in the Tools Table, disable the parameters GUI + if self.ui.geo_tools_table.rowCount() == 0: + self.ui.geo_param_frame.setDisabled(True) + + def ui_update_v_shape(self, tool_type_txt): + if tool_type_txt == 'V': + self.ui.tipdialabel.show() + self.ui.tipdia_entry.show() + self.ui.tipanglelabel.show() + self.ui.tipangle_entry.show() + self.ui.cutz_entry.setDisabled(True) + + self.update_cutz() + else: + self.ui.tipdialabel.hide() + self.ui.tipdia_entry.hide() + self.ui.tipanglelabel.hide() + self.ui.tipangle_entry.hide() + self.ui.cutz_entry.setDisabled(False) + + def update_cutz(self): + vdia = float(self.ui.tipdia_entry.get_value()) + half_vangle = float(self.ui.tipangle_entry.get_value()) / 2 + + row = self.ui.geo_tools_table.currentRow() + tool_uid_item = self.ui.geo_tools_table.item(row, 5) + if tool_uid_item is None: + return + tool_uid = int(tool_uid_item.text()) + + tool_dia_item = self.ui.geo_tools_table.item(row, 1) + if tool_dia_item is None: + return + tooldia = float(tool_dia_item.text()) + + try: + new_cutz = (tooldia - vdia) / (2 * math.tan(math.radians(half_vangle))) + except ZeroDivisionError: + new_cutz = self.old_cutz + + new_cutz = float('%.*f' % (self.decimals, new_cutz)) * -1.0 # this value has to be negative + + self.ui.cutz_entry.set_value(new_cutz) + + # store the new CutZ value into storage (self.tools) + for tooluid_key, tooluid_value in self.tools.items(): + if int(tooluid_key) == tool_uid: + tooluid_value['data']['cutz'] = new_cutz + + def on_tooltable_cellwidget_change(self): + cw = self.sender() + # assert isinstance(cw, FCComboBox) or isinstance(cw, FCCheckBox),\ + # "Expected a FCCombobox or a FCCheckbox got %s" % type(cw) + cw_index = self.ui.geo_tools_table.indexAt(cw.pos()) + cw_row = cw_index.row() + cw_col = cw_index.column() + current_uid = int(self.ui.geo_tools_table.item(cw_row, 5).text()) + + # store the text of the cellWidget that changed it's index in the self.tools + for tooluid_key, tooluid_value in self.tools.items(): + if int(tooluid_key) == current_uid: + cb_txt = cw.currentText() + if cw_col == 2: + tooluid_value['offset'] = cb_txt + if cb_txt == 'Custom': + self.ui.tool_offset_entry.show() + self.ui.tool_offset_lbl.show() + else: + self.ui.tool_offset_entry.hide() + self.ui.tool_offset_lbl.hide() + # reset the offset_value in storage self.tools + tooluid_value['offset_value'] = 0.0 + elif cw_col == 3: + # force toolpath type as 'Iso' if the tool type is V-Shape + if self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText() == 'V': + tooluid_value['type'] = _('Iso') + idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso')) + self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx) + else: + tooluid_value['type'] = cb_txt + elif cw_col == 4: + tooluid_value['tool_type'] = cb_txt + + # if the tool_type selected is V-Shape then autoselect the toolpath type as Iso + if cb_txt == 'V': + idx = self.ui.geo_tools_table.cellWidget(cw_row, 3).findText(_('Iso')) + self.ui.geo_tools_table.cellWidget(cw_row, 3).setCurrentIndex(idx) + else: + self.ui.cutz_entry.set_value(self.old_cutz) + + self.ui_update_v_shape(tool_type_txt=self.ui.geo_tools_table.cellWidget(cw_row, 4).currentText()) + + def update_form(self, dict_storage): + for form_key in self.form_fields: + for storage_key in dict_storage: + if form_key == storage_key: + try: + self.form_fields[form_key].set_value(dict_storage[form_key]) + except Exception as e: + log.debug(str(e)) + + # this is done here because those buttons control through OptionalInputSelection if some entry's are Enabled + # or not. But due of using the ui_disconnect() status is no longer updated and I had to do it here + self.ui.ois_dwell_geo.on_cb_change() + self.ui.ois_mpass_geo.on_cb_change() + self.ui.ois_tcz_geo.on_cb_change() + + def on_apply_param_to_all_clicked(self): + if self.ui.geo_tools_table.rowCount() == 0: + # there is no tool in tool table so we can't save the GUI elements values to storage + log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.") + return + + self.ui_disconnect() + + row = self.ui.geo_tools_table.currentRow() + if row < 0: + row = 0 + + # store all the data associated with the row parameter to the self.tools storage + tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text()) + offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText() + type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText() + tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText() + + offset_value_item = float(self.ui.tool_offset_entry.get_value()) + + # this new dict will hold the actual useful data, another dict that is the value of key 'data' + temp_tools = {} + temp_dia = {} + temp_data = {} + + for tooluid_key, tooluid_value in self.tools.items(): + for key, value in tooluid_value.items(): + if key == 'tooldia': + temp_dia[key] = tooldia_item + # update the 'offset', 'type' and 'tool_type' sections + if key == 'offset': + temp_dia[key] = offset_item + if key == 'type': + temp_dia[key] = type_item + if key == 'tool_type': + temp_dia[key] = tool_type_item + if key == 'offset_value': + temp_dia[key] = offset_value_item + + if key == 'data': + # update the 'data' section + for data_key in tooluid_value[key].keys(): + for form_key, form_value in self.form_fields.items(): + if form_key == data_key: + temp_data[data_key] = form_value.get_value() + # make sure we make a copy of the keys not in the form (we may use 'data' keys that are + # updated from self.app.defaults + if data_key not in self.form_fields: + temp_data[data_key] = value[data_key] + temp_dia[key] = deepcopy(temp_data) + temp_data.clear() + + if key == 'solid_geometry': + temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry']) + + temp_tools[tooluid_key] = deepcopy(temp_dia) + + self.tools.clear() + self.tools = deepcopy(temp_tools) + temp_tools.clear() + + self.ui_connect() + + def gui_form_to_storage(self): + if self.ui.geo_tools_table.rowCount() == 0: + # there is no tool in tool table so we can't save the GUI elements values to storage + log.debug("GeometryObject.gui_form_to_storage() --> no tool in Tools Table, aborting.") + return + + self.ui_disconnect() + widget_changed = self.sender() + try: + widget_idx = self.ui.grid3.indexOf(widget_changed) + except Exception: + return + + # those are the indexes for the V-Tip Dia and V-Tip Angle, if edited calculate the new Cut Z + if widget_idx == 1 or widget_idx == 3: + self.update_cutz() + + # the original connect() function of the OptionalInputSelection is no longer working because of the + # ui_diconnect() so I use this 'hack' + if isinstance(widget_changed, FCCheckBox): + if widget_changed.text() == 'Multi-Depth:': + self.ui.ois_mpass_geo.on_cb_change() + + if widget_changed.text() == 'Tool change': + self.ui.ois_tcz_geo.on_cb_change() + + if widget_changed.text() == 'Dwell:': + self.ui.ois_dwell_geo.on_cb_change() + + row = self.ui.geo_tools_table.currentRow() + if row < 0: + row = 0 + + # store all the data associated with the row parameter to the self.tools storage + tooldia_item = float(self.ui.geo_tools_table.item(row, 1).text()) + offset_item = self.ui.geo_tools_table.cellWidget(row, 2).currentText() + type_item = self.ui.geo_tools_table.cellWidget(row, 3).currentText() + tool_type_item = self.ui.geo_tools_table.cellWidget(row, 4).currentText() + tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text()) + + offset_value_item = float(self.ui.tool_offset_entry.get_value()) + + # this new dict will hold the actual useful data, another dict that is the value of key 'data' + temp_tools = {} + temp_dia = {} + temp_data = {} + + for tooluid_key, tooluid_value in self.tools.items(): + if int(tooluid_key) == tooluid_item: + for key, value in tooluid_value.items(): + if key == 'tooldia': + temp_dia[key] = tooldia_item + # update the 'offset', 'type' and 'tool_type' sections + if key == 'offset': + temp_dia[key] = offset_item + if key == 'type': + temp_dia[key] = type_item + if key == 'tool_type': + temp_dia[key] = tool_type_item + if key == 'offset_value': + temp_dia[key] = offset_value_item + + if key == 'data': + # update the 'data' section + for data_key in tooluid_value[key].keys(): + for form_key, form_value in self.form_fields.items(): + if form_key == data_key: + temp_data[data_key] = form_value.get_value() + # make sure we make a copy of the keys not in the form (we may use 'data' keys that are + # updated from self.app.defaults + if data_key not in self.form_fields: + temp_data[data_key] = value[data_key] + temp_dia[key] = deepcopy(temp_data) + temp_data.clear() + + if key == 'solid_geometry': + temp_dia[key] = deepcopy(self.tools[tooluid_key]['solid_geometry']) + + temp_tools[tooluid_key] = deepcopy(temp_dia) + else: + temp_tools[tooluid_key] = deepcopy(tooluid_value) + + self.tools.clear() + self.tools = deepcopy(temp_tools) + temp_tools.clear() + self.ui_connect() + + def update_common_param_in_storage(self): + for tooluid_value in self.tools.values(): + tooluid_value['data']['ppname_g'] = self.ui.pp_geometry_name_cb.get_value() + + def select_tools_table_row(self, row, clearsel=None): + if clearsel: + self.ui.geo_tools_table.clearSelection() + + if self.ui.geo_tools_table.rowCount() > 0: + # self.ui.geo_tools_table.item(row, 0).setSelected(True) + self.ui.geo_tools_table.setCurrentItem(self.ui.geo_tools_table.item(row, 0)) + + def export_dxf(self): + dwg = None + try: + dwg = ezdxf.new('R2010') + msp = dwg.modelspace() + + def g2dxf(dxf_space, geo_obj): + if isinstance(geo_obj, MultiPolygon): + for poly in geo_obj: + ext_points = list(poly.exterior.coords) + dxf_space.add_lwpolyline(ext_points) + for interior in poly.interiors: + dxf_space.add_lwpolyline(list(interior.coords)) + if isinstance(geo_obj, Polygon): + ext_points = list(geo_obj.exterior.coords) + dxf_space.add_lwpolyline(ext_points) + for interior in geo_obj.interiors: + dxf_space.add_lwpolyline(list(interior.coords)) + if isinstance(geo_obj, MultiLineString): + for line in geo_obj: + dxf_space.add_lwpolyline(list(line.coords)) + if isinstance(geo_obj, LineString) or isinstance(geo_obj, LinearRing): + dxf_space.add_lwpolyline(list(geo_obj.coords)) + + multigeo_solid_geometry = [] + if self.multigeo: + for tool in self.tools: + multigeo_solid_geometry += self.tools[tool]['solid_geometry'] + else: + multigeo_solid_geometry = self.solid_geometry + + for geo in multigeo_solid_geometry: + if type(geo) == list: + for g in geo: + g2dxf(msp, g) + else: + g2dxf(msp, geo) + + # points = GeometryObject.get_pts(geo) + # msp.add_lwpolyline(points) + except Exception as e: + log.debug(str(e)) + + return dwg + + def get_selected_tools_table_items(self): + """ + Returns a list of lists, each list in the list is made out of row elements + + :return: List of table_tools items. + :rtype: list + """ + table_tools_items = [] + if self.multigeo: + for x in self.ui.geo_tools_table.selectedItems(): + elem = [] + txt = '' + + for column in range(0, self.ui.geo_tools_table.columnCount()): + try: + txt = self.ui.geo_tools_table.item(x.row(), column).text() + except AttributeError: + try: + txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText() + except AttributeError: + pass + elem.append(txt) + table_tools_items.append(deepcopy(elem)) + # table_tools_items.append([self.ui.geo_tools_table.item(x.row(), column).text() + # for column in range(0, self.ui.geo_tools_table.columnCount())]) + else: + for x in self.ui.geo_tools_table.selectedItems(): + r = [] + txt = '' + + # the last 2 columns for single-geo geometry are irrelevant and create problems reading + # so we don't read them + for column in range(0, self.ui.geo_tools_table.columnCount() - 2): + # the columns have items that have text but also have items that are widgets + # for which the text they hold has to be read differently + try: + txt = self.ui.geo_tools_table.item(x.row(), column).text() + except AttributeError: + try: + txt = self.ui.geo_tools_table.cellWidget(x.row(), column).currentText() + except AttributeError: + pass + r.append(txt) + table_tools_items.append(r) + + for item in table_tools_items: + item[0] = str(item[0]) + return table_tools_items + + def on_pp_changed(self): + current_pp = self.ui.pp_geometry_name_cb.get_value() + if current_pp == 'hpgl': + self.old_pp_state = self.ui.mpass_cb.get_value() + self.old_toolchangeg_state = self.ui.toolchangeg_cb.get_value() + + self.ui.mpass_cb.set_value(False) + self.ui.mpass_cb.setDisabled(True) + + self.ui.toolchangeg_cb.set_value(True) + self.ui.toolchangeg_cb.setDisabled(True) + else: + self.ui.mpass_cb.set_value(self.old_pp_state) + self.ui.mpass_cb.setDisabled(False) + + self.ui.toolchangeg_cb.set_value(self.old_toolchangeg_state) + self.ui.toolchangeg_cb.setDisabled(False) + + if "toolchange_probe" in current_pp.lower(): + self.ui.pdepth_entry.setVisible(True) + self.ui.pdepth_label.show() + + self.ui.feedrate_probe_entry.setVisible(True) + self.ui.feedrate_probe_label.show() + else: + self.ui.pdepth_entry.setVisible(False) + self.ui.pdepth_label.hide() + + self.ui.feedrate_probe_entry.setVisible(False) + self.ui.feedrate_probe_label.hide() + + if 'marlin' in current_pp.lower() or 'custom' in current_pp.lower(): + self.ui.fr_rapidlabel.show() + self.ui.feedrate_rapid_entry.show() + else: + self.ui.fr_rapidlabel.hide() + self.ui.feedrate_rapid_entry.hide() + + if 'laser' in current_pp.lower(): + self.ui.cutzlabel.hide() + self.ui.cutz_entry.hide() + try: + self.ui.mpass_cb.hide() + self.ui.maxdepth_entry.hide() + except AttributeError: + pass + + if 'marlin' in current_pp.lower(): + self.ui.travelzlabel.setText('%s:' % _("Focus Z")) + self.ui.endz_label.show() + self.ui.endz_entry.show() + else: + self.ui.travelzlabel.hide() + self.ui.travelz_entry.hide() + + self.ui.endz_label.hide() + self.ui.endz_entry.hide() + + try: + self.ui.frzlabel.hide() + self.ui.feedrate_z_entry.hide() + except AttributeError: + pass + + self.ui.dwell_cb.hide() + self.ui.dwelltime_entry.hide() + + self.ui.spindle_label.setText('%s:' % _("Laser Power")) + + try: + self.ui.tool_offset_label.hide() + self.ui.offset_entry.hide() + except AttributeError: + pass + else: + self.ui.cutzlabel.show() + self.ui.cutz_entry.show() + try: + self.ui.mpass_cb.show() + self.ui.maxdepth_entry.show() + except AttributeError: + pass + + self.ui.travelzlabel.setText('%s:' % _('Travel Z')) + + self.ui.travelzlabel.show() + self.ui.travelz_entry.show() + + self.ui.endz_label.show() + self.ui.endz_entry.show() + + try: + self.ui.frzlabel.show() + self.ui.feedrate_z_entry.show() + except AttributeError: + pass + self.ui.dwell_cb.show() + self.ui.dwelltime_entry.show() + + self.ui.spindle_label.setText('%s:' % _('Spindle speed')) + + try: + self.ui.tool_offset_lbl.show() + self.ui.offset_entry.show() + except AttributeError: + pass + + def on_generatecnc_button_click(self, *args): + log.debug("Generating CNCJob from Geometry ...") + self.app.defaults.report_usage("geometry_on_generatecnc_button") + + # this reads the values in the UI form to the self.options dictionary + self.read_form() + + self.sel_tools = {} + + try: + if self.special_group: + self.app.inform.emit( + '[WARNING_NOTCL] %s %s %s.' % + (_("This Geometry can't be processed because it is"), str(self.special_group), _("geometry")) + ) + return + except AttributeError: + pass + + # test to see if we have tools available in the tool table + if self.ui.geo_tools_table.selectedItems(): + for x in self.ui.geo_tools_table.selectedItems(): + # try: + # tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text()) + # except ValueError: + # # try to convert comma to decimal point. if it's still not working error message and return + # try: + # tooldia = float(self.ui.geo_tools_table.item(x.row(), 1).text().replace(',', '.')) + # except ValueError: + # self.app.inform.emit('[ERROR_NOTCL] %s' % + # _("Wrong value format entered, use a number.")) + # return + tooluid = int(self.ui.geo_tools_table.item(x.row(), 5).text()) + + for tooluid_key, tooluid_value in self.tools.items(): + if int(tooluid_key) == tooluid: + self.sel_tools.update({ + tooluid: deepcopy(tooluid_value) + }) + self.mtool_gen_cncjob() + self.ui.geo_tools_table.clearSelection() + + elif self.ui.geo_tools_table.rowCount() == 1: + tooluid = int(self.ui.geo_tools_table.item(0, 5).text()) + + for tooluid_key, tooluid_value in self.tools.items(): + if int(tooluid_key) == tooluid: + self.sel_tools.update({ + tooluid: deepcopy(tooluid_value) + }) + self.mtool_gen_cncjob() + self.ui.geo_tools_table.clearSelection() + + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Failed. No tool selected in the tool table ...")) + + def mtool_gen_cncjob(self, outname=None, tools_dict=None, tools_in_use=None, segx=None, segy=None, + plot=True, use_thread=True): + """ + Creates a multi-tool CNCJob out of this Geometry object. + The actual work is done by the target CNCJobObject object's + `generate_from_geometry_2()` method. + + :param tools_dict: a dictionary that holds the whole data needed to create the Gcode + (including the solid_geometry) + + :param tools_in_use: the tools that are used, needed by some preprocessors + :type list of lists, each list in the list is made out of row elements of tools table from GUI + + :param outname: + :param tools_dict: + :param tools_in_use: + :param segx: number of segments on the X axis, for auto-levelling + :param segy: number of segments on the Y axis, for auto-levelling + :param plot: if True the generated object will be plotted; if False will not be plotted + :param use_thread: if True use threading + :return: None + """ + + # use the name of the first tool selected in self.geo_tools_table which has the diameter passed as tool_dia + outname = "%s_%s" % (self.options["name"], 'cnc') if outname is None else outname + + tools_dict = self.sel_tools if tools_dict is None else tools_dict + tools_in_use = tools_in_use if tools_in_use is not None else self.get_selected_tools_table_items() + segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) + segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) + + try: + xmin = self.options['xmin'] + ymin = self.options['ymin'] + xmax = self.options['xmax'] + ymax = self.options['ymax'] + except Exception as e: + log.debug("FlatCAMObj.GeometryObject.mtool_gen_cncjob() --> %s\n" % str(e)) + + msg = '[ERROR] %s' % _("An internal error has occurred. See shell.\n") + msg += '%s %s' % ('FlatCAMObj.GeometryObject.mtool_gen_cncjob() -->', str(e)) + msg += traceback.format_exc() + self.app.inform.emit(msg) + return + + # Object initialization function for app.new_object() + # RUNNING ON SEPARATE THREAD! + def job_init_single_geometry(job_obj, app_obj): + log.debug("Creating a CNCJob out of a single-geometry") + assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj) + + job_obj.options['xmin'] = xmin + job_obj.options['ymin'] = ymin + job_obj.options['xmax'] = xmax + job_obj.options['ymax'] = ymax + + # count the tools + tool_cnt = 0 + + dia_cnc_dict = {} + + # this turn on the FlatCAMCNCJob plot for multiple tools + job_obj.multitool = True + job_obj.multigeo = False + job_obj.cnc_tools.clear() + + job_obj.options['Tools_in_use'] = tools_in_use + job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"]) + job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"]) + + job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"]) + job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"]) + + for tooluid_key in list(tools_dict.keys()): + tool_cnt += 1 + + dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) + tooldia_val = float('%.*f' % (self.decimals, float(tools_dict[tooluid_key]['tooldia']))) + dia_cnc_dict.update({ + 'tooldia': tooldia_val + }) + + if dia_cnc_dict['offset'] == 'in': + tool_offset = -dia_cnc_dict['tooldia'] / 2 + elif dia_cnc_dict['offset'].lower() == 'out': + tool_offset = dia_cnc_dict['tooldia'] / 2 + elif dia_cnc_dict['offset'].lower() == 'custom': + try: + offset_value = float(self.ui.tool_offset_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + offset_value = float(self.ui.tool_offset_entry.get_value().replace(',', '.')) + except ValueError: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number.")) + return + if offset_value: + tool_offset = float(offset_value) + else: + self.app.inform.emit( + '[WARNING] %s' % _("Tool Offset is selected in Tool Table but no value is provided.\n" + "Add a Tool Offset or change the Offset Type.") + ) + return + else: + tool_offset = 0.0 + + dia_cnc_dict.update({ + 'offset_value': tool_offset + }) + + z_cut = tools_dict[tooluid_key]['data']["cutz"] + z_move = tools_dict[tooluid_key]['data']["travelz"] + feedrate = tools_dict[tooluid_key]['data']["feedrate"] + feedrate_z = tools_dict[tooluid_key]['data']["feedrate_z"] + feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"] + multidepth = tools_dict[tooluid_key]['data']["multidepth"] + extracut = tools_dict[tooluid_key]['data']["extracut"] + extracut_length = tools_dict[tooluid_key]['data']["extracut_length"] + depthpercut = tools_dict[tooluid_key]['data']["depthperpass"] + toolchange = tools_dict[tooluid_key]['data']["toolchange"] + toolchangez = tools_dict[tooluid_key]['data']["toolchangez"] + toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"] + startz = tools_dict[tooluid_key]['data']["startz"] + endz = tools_dict[tooluid_key]['data']["endz"] + endxy = self.options["endxy"] + spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"] + dwell = tools_dict[tooluid_key]['data']["dwell"] + dwelltime = tools_dict[tooluid_key]['data']["dwelltime"] + pp_geometry_name = tools_dict[tooluid_key]['data']["ppname_g"] + + spindledir = self.app.defaults['geometry_spindledir'] + tool_solid_geometry = self.solid_geometry + + job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] + job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] + + # Propagate options + job_obj.options["tooldia"] = tooldia_val + job_obj.options['type'] = 'Geometry' + job_obj.options['tool_dia'] = tooldia_val + + # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially + # to a value of 0.0005 which is 20 times less than 0.01 + tol = float(self.app.defaults['global_tolerance']) / 20 + res = job_obj.generate_from_geometry_2( + self, tooldia=tooldia_val, offset=tool_offset, tolerance=tol, + z_cut=z_cut, z_move=z_move, + feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, + spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, + multidepth=multidepth, depthpercut=depthpercut, + extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, + toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, + pp_geometry_name=pp_geometry_name, + tool_no=tool_cnt) + + if res == 'fail': + log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed") + return 'fail' + else: + dia_cnc_dict['gcode'] = res + + # tell gcode_parse from which point to start drawing the lines depending on what kind of + # object is the source of gcode + job_obj.toolchange_xy_type = "geometry" + + self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) + dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() + self.app.inform.emit('[success] %s' % _("G-Code parsing finished...")) + + # TODO this serve for bounding box creation only; should be optimized + # commented this; there is no need for the actual GCode geometry - the original one will serve as well + # for bounding box values + # dia_cnc_dict['solid_geometry'] = cascaded_union([geo['geom'] for geo in dia_cnc_dict['gcode_parsed']]) + try: + dia_cnc_dict['solid_geometry'] = tool_solid_geometry + self.app.inform.emit('[success] %s...' % _("Finished G-Code processing")) + except Exception as er: + self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(er))) + + job_obj.cnc_tools.update({ + tooluid_key: deepcopy(dia_cnc_dict) + }) + dia_cnc_dict.clear() + + # Object initialization function for app.new_object() + # RUNNING ON SEPARATE THREAD! + def job_init_multi_geometry(job_obj, app_obj): + log.debug("Creating a CNCJob out of a multi-geometry") + assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj) + + job_obj.options['xmin'] = xmin + job_obj.options['ymin'] = ymin + job_obj.options['xmax'] = xmax + job_obj.options['ymax'] = ymax + + # count the tools + tool_cnt = 0 + + dia_cnc_dict = {} + + # this turn on the FlatCAMCNCJob plot for multiple tools + job_obj.multitool = True + job_obj.multigeo = True + job_obj.cnc_tools.clear() + + job_obj.options['Tools_in_use'] = tools_in_use + job_obj.segx = segx if segx else float(self.app.defaults["geometry_segx"]) + job_obj.segy = segy if segy else float(self.app.defaults["geometry_segy"]) + + job_obj.z_pdepth = float(self.app.defaults["geometry_z_pdepth"]) + job_obj.feedrate_probe = float(self.app.defaults["geometry_feedrate_probe"]) + + # make sure that trying to make a CNCJob from an empty file is not creating an app crash + if not self.solid_geometry: + a = 0 + for tooluid_key in self.tools: + if self.tools[tooluid_key]['solid_geometry'] is None: + a += 1 + if a == len(self.tools): + self.app.inform.emit('[ERROR_NOTCL] %s...' % _('Cancelled. Empty file, it has no geometry')) + return 'fail' + + for tooluid_key in list(tools_dict.keys()): + tool_cnt += 1 + dia_cnc_dict = deepcopy(tools_dict[tooluid_key]) + tooldia_val = float('%.*f' % (self.decimals, float(tools_dict[tooluid_key]['tooldia']))) + + dia_cnc_dict.update({ + 'tooldia': tooldia_val + }) + + # find the tool_dia associated with the tooluid_key + # search in the self.tools for the sel_tool_dia and when found see what tooluid has + # on the found tooluid in self.tools we also have the solid_geometry that interest us + # for k, v in self.tools.items(): + # if float('%.*f' % (self.decimals, float(v['tooldia']))) == tooldia_val: + # current_uid = int(k) + # break + + if dia_cnc_dict['offset'] == 'in': + tool_offset = -tooldia_val / 2 + elif dia_cnc_dict['offset'].lower() == 'out': + tool_offset = tooldia_val / 2 + elif dia_cnc_dict['offset'].lower() == 'custom': + offset_value = float(self.ui.tool_offset_entry.get_value()) + if offset_value: + tool_offset = float(offset_value) + else: + self.app.inform.emit('[WARNING] %s' % + _("Tool Offset is selected in Tool Table but " + "no value is provided.\n" + "Add a Tool Offset or change the Offset Type.")) + return + else: + tool_offset = 0.0 + + dia_cnc_dict.update({ + 'offset_value': tool_offset + }) + + z_cut = tools_dict[tooluid_key]['data']["cutz"] + z_move = tools_dict[tooluid_key]['data']["travelz"] + feedrate = tools_dict[tooluid_key]['data']["feedrate"] + feedrate_z = tools_dict[tooluid_key]['data']["feedrate_z"] + feedrate_rapid = tools_dict[tooluid_key]['data']["feedrate_rapid"] + multidepth = tools_dict[tooluid_key]['data']["multidepth"] + extracut = tools_dict[tooluid_key]['data']["extracut"] + extracut_length = tools_dict[tooluid_key]['data']["extracut_length"] + depthpercut = tools_dict[tooluid_key]['data']["depthperpass"] + toolchange = tools_dict[tooluid_key]['data']["toolchange"] + toolchangez = tools_dict[tooluid_key]['data']["toolchangez"] + toolchangexy = tools_dict[tooluid_key]['data']["toolchangexy"] + startz = tools_dict[tooluid_key]['data']["startz"] + endz = tools_dict[tooluid_key]['data']["endz"] + endxy = self.options["endxy"] + spindlespeed = tools_dict[tooluid_key]['data']["spindlespeed"] + dwell = tools_dict[tooluid_key]['data']["dwell"] + dwelltime = tools_dict[tooluid_key]['data']["dwelltime"] + pp_geometry_name = tools_dict[tooluid_key]['data']["ppname_g"] + + spindledir = self.app.defaults['geometry_spindledir'] + tool_solid_geometry = self.tools[tooluid_key]['solid_geometry'] + + job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] + job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] + + # Propagate options + job_obj.options["tooldia"] = tooldia_val + job_obj.options['type'] = 'Geometry' + job_obj.options['tool_dia'] = tooldia_val + + # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially + # to a value of 0.0005 which is 20 times less than 0.01 + tol = float(self.app.defaults['global_tolerance']) / 20 + res = job_obj.generate_from_multitool_geometry( + tool_solid_geometry, tooldia=tooldia_val, offset=tool_offset, + tolerance=tol, z_cut=z_cut, z_move=z_move, + feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, + spindlespeed=spindlespeed, spindledir=spindledir, dwell=dwell, dwelltime=dwelltime, + multidepth=multidepth, depthpercut=depthpercut, + extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, + toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, + pp_geometry_name=pp_geometry_name, + tool_no=tool_cnt) + + if res == 'fail': + log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed") + return 'fail' + else: + dia_cnc_dict['gcode'] = res + + self.app.inform.emit('[success] %s' % _("G-Code parsing in progress...")) + dia_cnc_dict['gcode_parsed'] = job_obj.gcode_parse() + self.app.inform.emit('[success] %s' % _("G-Code parsing finished...")) + + # TODO this serve for bounding box creation only; should be optimized + # commented this; there is no need for the actual GCode geometry - the original one will serve as well + # for bounding box values + # geo_for_bound_values = cascaded_union([ + # geo['geom'] for geo in dia_cnc_dict['gcode_parsed'] if geo['geom'].is_valid is True + # ]) + try: + dia_cnc_dict['solid_geometry'] = deepcopy(tool_solid_geometry) + self.app.inform.emit('[success] %s' % _("Finished G-Code processing...")) + except Exception as ee: + self.app.inform.emit('[ERROR] %s: %s' % (_("G-Code processing failed with error"), str(ee))) + + # tell gcode_parse from which point to start drawing the lines depending on what kind of + # object is the source of gcode + job_obj.toolchange_xy_type = "geometry" + + job_obj.cnc_tools.update({ + tooluid_key: deepcopy(dia_cnc_dict) + }) + dia_cnc_dict.clear() + + if use_thread: + # To be run in separate thread + def job_thread(app_obj): + if self.multigeo is False: + with self.app.proc_container.new(_("Generating CNC Code")): + if app_obj.new_object("cncjob", outname, job_init_single_geometry, plot=plot) != 'fail': + app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname)) + else: + with self.app.proc_container.new(_("Generating CNC Code")): + if app_obj.new_object("cncjob", outname, job_init_multi_geometry) != 'fail': + app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname)) + + # Create a promise with the name + self.app.collection.promise(outname) + # Send to worker + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + else: + if self.solid_geometry: + self.app.new_object("cncjob", outname, job_init_single_geometry, plot=plot) + else: + self.app.new_object("cncjob", outname, job_init_multi_geometry, plot=plot) + + def generatecncjob( + self, outname=None, + dia=None, offset=None, + z_cut=None, z_move=None, + feedrate=None, feedrate_z=None, feedrate_rapid=None, + spindlespeed=None, dwell=None, dwelltime=None, + multidepth=None, depthperpass=None, + toolchange=None, toolchangez=None, toolchangexy=None, + extracut=None, extracut_length=None, startz=None, endz=None, + pp=None, + segx=None, segy=None, + use_thread=True, + plot=True): + """ + Only used for TCL Command. + Creates a CNCJob out of this Geometry object. The actual + work is done by the target camlib.CNCjob + `generate_from_geometry_2()` method. + + :param outname: Name of the new object + :param dia: Tool diameter + :param offset: + :param z_cut: Cut depth (negative value) + :param z_move: Height of the tool when travelling (not cutting) + :param feedrate: Feed rate while cutting on X - Y plane + :param feedrate_z: Feed rate while cutting on Z plane + :param feedrate_rapid: Feed rate while moving with rapids + :param spindlespeed: Spindle speed (RPM) + :param dwell: + :param dwelltime: + :param multidepth: + :param depthperpass: + :param toolchange: + :param toolchangez: + :param toolchangexy: + :param extracut: + :param extracut_length: + :param startz: + :param endz: + :param pp: Name of the preprocessor + :param segx: + :param segy: + :param use_thread: + :param plot: + :return: None + """ + + tooldia = dia if dia else float(self.options["cnctooldia"]) + outname = outname if outname is not None else self.options["name"] + + z_cut = z_cut if z_cut is not None else float(self.options["cutz"]) + z_move = z_move if z_move is not None else float(self.options["travelz"]) + + feedrate = feedrate if feedrate is not None else float(self.options["feedrate"]) + feedrate_z = feedrate_z if feedrate_z is not None else float(self.options["feedrate_z"]) + feedrate_rapid = feedrate_rapid if feedrate_rapid is not None else float(self.options["feedrate_rapid"]) + + multidepth = multidepth if multidepth is not None else self.options["multidepth"] + depthperpass = depthperpass if depthperpass is not None else float(self.options["depthperpass"]) + + segx = segx if segx is not None else float(self.app.defaults['geometry_segx']) + segy = segy if segy is not None else float(self.app.defaults['geometry_segy']) + + extracut = extracut if extracut is not None else float(self.options["extracut"]) + extracut_length = extracut_length if extracut_length is not None else float(self.options["extracut_length"]) + + startz = startz if startz is not None else self.options["startz"] + endz = endz if endz is not None else float(self.options["endz"]) + endxy = self.options["endxy"] + + toolchangez = toolchangez if toolchangez else float(self.options["toolchangez"]) + toolchangexy = toolchangexy if toolchangexy else self.options["toolchangexy"] + toolchange = toolchange if toolchange else self.options["toolchange"] + + offset = offset if offset else 0.0 + + # int or None. + spindlespeed = spindlespeed if spindlespeed else self.options['spindlespeed'] + dwell = dwell if dwell else self.options["dwell"] + dwelltime = dwelltime if dwelltime else float(self.options["dwelltime"]) + + ppname_g = pp if pp else self.options["ppname_g"] + + # Object initialization function for app.new_object() + # RUNNING ON SEPARATE THREAD! + def job_init(job_obj, app_obj): + assert job_obj.kind == 'cncjob', "Initializer expected a CNCJobObject, got %s" % type(job_obj) + + # Propagate options + job_obj.options["tooldia"] = tooldia + + job_obj.coords_decimals = self.app.defaults["cncjob_coords_decimals"] + job_obj.fr_decimals = self.app.defaults["cncjob_fr_decimals"] + + job_obj.options['type'] = 'Geometry' + job_obj.options['tool_dia'] = tooldia + + job_obj.segx = segx + job_obj.segy = segy + + job_obj.z_pdepth = float(self.options["z_pdepth"]) + job_obj.feedrate_probe = float(self.options["feedrate_probe"]) + + job_obj.options['xmin'] = self.options['xmin'] + job_obj.options['ymin'] = self.options['ymin'] + job_obj.options['xmax'] = self.options['xmax'] + job_obj.options['ymax'] = self.options['ymax'] + + # it seems that the tolerance needs to be a lot lower value than 0.01 and it was hardcoded initially + # to a value of 0.0005 which is 20 times less than 0.01 + tol = float(self.app.defaults['global_tolerance']) / 20 + job_obj.generate_from_geometry_2( + self, tooldia=tooldia, offset=offset, tolerance=tol, + z_cut=z_cut, z_move=z_move, + feedrate=feedrate, feedrate_z=feedrate_z, feedrate_rapid=feedrate_rapid, + spindlespeed=spindlespeed, dwell=dwell, dwelltime=dwelltime, + multidepth=multidepth, depthpercut=depthperpass, + toolchange=toolchange, toolchangez=toolchangez, toolchangexy=toolchangexy, + extracut=extracut, extracut_length=extracut_length, startz=startz, endz=endz, endxy=endxy, + pp_geometry_name=ppname_g + ) + + # tell gcode_parse from which point to start drawing the lines depending on what kind of object is the + # source of gcode + job_obj.toolchange_xy_type = "geometry" + job_obj.gcode_parse() + self.app.inform.emit('[success] %s' % _("Finished G-Code processing...")) + + if use_thread: + # To be run in separate thread + def job_thread(app_obj): + with self.app.proc_container.new(_("Generating CNC Code")): + app_obj.new_object("cncjob", outname, job_init, plot=plot) + app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname) + + # Create a promise with the name + self.app.collection.promise(outname) + # Send to worker + self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]}) + else: + self.app.new_object("cncjob", outname, job_init, plot=plot) + + # def on_plot_cb_click(self, *args): + # if self.muted_ui: + # return + # self.read_form_item('plot') + + def scale(self, xfactor, yfactor=None, point=None): + """ + Scales all geometry by a given factor. + + :param xfactor: Factor by which to scale the object's geometry/ + :type xfactor: float + :param yfactor: Factor by which to scale the object's geometry/ + :type yfactor: float + :param point: Point around which to scale + :return: None + :rtype: None + """ + log.debug("FlatCAMObj.GeometryObject.scale()") + + try: + xfactor = float(xfactor) + except Exception: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float.")) + return + + if yfactor is None: + yfactor = xfactor + else: + try: + yfactor = float(yfactor) + except Exception: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scale factor has to be a number: integer or float.")) + return + + if xfactor == 1 and yfactor == 1: + return + + if point is None: + px = 0 + py = 0 + else: + px, py = point + + self.geo_len = 0 + self.old_disp_number = 0 + self.el_count = 0 + + def scale_recursion(geom): + if type(geom) is list: + geoms = [] + for local_geom in geom: + geoms.append(scale_recursion(local_geom)) + return geoms + else: + try: + self.el_count += 1 + disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100])) + if self.old_disp_number < disp_number <= 100: + 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)) + except AttributeError: + return geom + + if self.multigeo is True: + for tool in self.tools: + # variables to display the percentage of work done + self.geo_len = 0 + try: + self.geo_len = len(self.tools[tool]['solid_geometry']) + except TypeError: + self.geo_len = 1 + self.old_disp_number = 0 + self.el_count = 0 + + self.tools[tool]['solid_geometry'] = scale_recursion(self.tools[tool]['solid_geometry']) + + try: + # variables to display the percentage of work done + self.geo_len = 0 + try: + self.geo_len = len(self.solid_geometry) + except TypeError: + self.geo_len = 1 + self.old_disp_number = 0 + self.el_count = 0 + + self.solid_geometry = scale_recursion(self.solid_geometry) + except AttributeError: + self.solid_geometry = [] + return + + self.app.proc_container.new_text = '' + self.app.inform.emit('[success] %s' % _("Geometry Scale done.")) + + def offset(self, vect): + """ + Offsets all geometry by a given vector/ + + :param vect: (x, y) vector by which to offset the object's geometry. + :type vect: tuple + :return: None + :rtype: None + """ + log.debug("FlatCAMObj.GeometryObject.offset()") + + try: + dx, dy = vect + except TypeError: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("An (x,y) pair of values are needed. " + "Probable you entered only one value in the Offset field.") + ) + return + + if dx == 0 and dy == 0: + return + + self.geo_len = 0 + self.old_disp_number = 0 + self.el_count = 0 + + def translate_recursion(geom): + if type(geom) is list: + geoms = [] + for local_geom in geom: + geoms.append(translate_recursion(local_geom)) + return geoms + else: + try: + self.el_count += 1 + disp_number = int(np.interp(self.el_count, [0, self.geo_len], [0, 100])) + if self.old_disp_number < disp_number <= 100: + self.app.proc_container.update_view_text(' %d%%' % disp_number) + self.old_disp_number = disp_number + + return affinity.translate(geom, xoff=dx, yoff=dy) + except AttributeError: + return geom + + if self.multigeo is True: + for tool in self.tools: + # variables to display the percentage of work done + self.geo_len = 0 + try: + self.geo_len = len(self.tools[tool]['solid_geometry']) + except TypeError: + self.geo_len = 1 + self.old_disp_number = 0 + self.el_count = 0 + + self.tools[tool]['solid_geometry'] = translate_recursion(self.tools[tool]['solid_geometry']) + + # variables to display the percentage of work done + self.geo_len = 0 + try: + self.geo_len = len(self.solid_geometry) + except TypeError: + self.geo_len = 1 + + self.old_disp_number = 0 + self.el_count = 0 + + self.solid_geometry = translate_recursion(self.solid_geometry) + + self.app.proc_container.new_text = '' + self.app.inform.emit('[success] %s' % _("Geometry Offset done.")) + + def convert_units(self, units): + log.debug("FlatCAMObj.GeometryObject.convert_units()") + + self.ui_disconnect() + + factor = Geometry.convert_units(self, units) + + self.options['cutz'] = float(self.options['cutz']) * factor + self.options['depthperpass'] = float(self.options['depthperpass']) * factor + self.options['travelz'] = float(self.options['travelz']) * factor + self.options['feedrate'] = float(self.options['feedrate']) * factor + self.options['feedrate_z'] = float(self.options['feedrate_z']) * factor + self.options['feedrate_rapid'] = float(self.options['feedrate_rapid']) * factor + self.options['endz'] = float(self.options['endz']) * factor + # self.options['cnctooldia'] *= factor + # self.options['painttooldia'] *= factor + # self.options['paintmargin'] *= factor + # self.options['paintoverlap'] *= factor + + self.options["toolchangez"] = float(self.options["toolchangez"]) * factor + + if self.app.defaults["geometry_toolchangexy"] == '': + self.options['toolchangexy'] = "0.0, 0.0" + else: + coords_xy = [float(eval(coord)) for coord in self.app.defaults["geometry_toolchangexy"].split(",")] + if len(coords_xy) < 2: + self.app.inform.emit('[ERROR] %s' % + _("The Toolchange X,Y field in Edit -> Preferences " + "has to be in the format (x, y)\n" + "but now there is only one value, not two.") + ) + return 'fail' + coords_xy[0] *= factor + coords_xy[1] *= factor + self.options['toolchangexy'] = "%f, %f" % (coords_xy[0], coords_xy[1]) + + if self.options['startz'] is not None: + self.options['startz'] = float(self.options['startz']) * factor + + param_list = ['cutz', 'depthperpass', 'travelz', 'feedrate', 'feedrate_z', 'feedrate_rapid', + 'endz', 'toolchangez'] + + if isinstance(self, GeometryObject): + temp_tools_dict = {} + tool_dia_copy = {} + data_copy = {} + for tooluid_key, tooluid_value in self.tools.items(): + for dia_key, dia_value in tooluid_value.items(): + if dia_key == 'tooldia': + dia_value *= factor + dia_value = float('%.*f' % (self.decimals, dia_value)) + tool_dia_copy[dia_key] = dia_value + if dia_key == 'offset': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'offset_value': + dia_value *= factor + tool_dia_copy[dia_key] = dia_value + + # convert the value in the Custom Tool Offset entry in UI + custom_offset = None + try: + custom_offset = float(self.ui.tool_offset_entry.get_value()) + except ValueError: + # try to convert comma to decimal point. if it's still not working error message and return + try: + custom_offset = float(self.ui.tool_offset_entry.get_value().replace(',', '.')) + except ValueError: + self.app.inform.emit('[ERROR_NOTCL] %s' % + _("Wrong value format entered, use a number.")) + return + except TypeError: + pass + + if custom_offset: + custom_offset *= factor + self.ui.tool_offset_entry.set_value(custom_offset) + + if dia_key == 'type': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'tool_type': + tool_dia_copy[dia_key] = dia_value + if dia_key == 'data': + for data_key, data_value in dia_value.items(): + # convert the form fields that are convertible + for param in param_list: + if data_key == param and data_value is not None: + data_copy[data_key] = data_value * factor + # copy the other dict entries that are not convertible + if data_key not in param_list: + data_copy[data_key] = data_value + tool_dia_copy[dia_key] = deepcopy(data_copy) + data_copy.clear() + + temp_tools_dict.update({ + tooluid_key: deepcopy(tool_dia_copy) + }) + tool_dia_copy.clear() + + self.tools.clear() + self.tools = deepcopy(temp_tools_dict) + + # if there is a value in the new tool field then convert that one too + try: + self.ui.addtool_entry.returnPressed.disconnect() + except TypeError: + pass + tooldia = self.ui.addtool_entry.get_value() + if tooldia: + tooldia *= factor + tooldia = float('%.*f' % (self.decimals, tooldia)) + + self.ui.addtool_entry.set_value(tooldia) + self.ui.addtool_entry.returnPressed.connect(self.on_tool_add) + + return factor + + def plot_element(self, element, color=None, visible=None): + + if color is None: + color = '#FF0000FF' + + visible = visible if visible else self.options['plot'] + try: + for sub_el in element: + self.plot_element(sub_el, color=color) + + except TypeError: # Element is not iterable... + # if self.app.is_legacy is False: + self.add_shape(shape=element, color=color, visible=visible, layer=0) + + def plot(self, visible=None, kind=None): + """ + Plot the object. + + :param visible: Controls if the added shape is visible of not + :param kind: added so there is no error when a project is loaded and it has both geometry and CNCJob, because + CNCJob require the 'kind' parameter. Perhaps the FlatCAMObj.plot() has to be rewrited + :return: + """ + + # Does all the required setup and returns False + # if the 'ptint' option is set to False. + if not FlatCAMObj.plot(self): + return + + try: + # plot solid geometries found as members of self.tools attribute dict + # for MultiGeo + if self.multigeo is True: # geo multi tool usage + for tooluid_key in self.tools: + solid_geometry = self.tools[tooluid_key]['solid_geometry'] + self.plot_element(solid_geometry, visible=visible, + color=self.app.defaults["geometry_plot_line"]) + else: + # plot solid geometry that may be an direct attribute of the geometry object + # for SingleGeo + if self.solid_geometry: + self.plot_element(self.solid_geometry, visible=visible, + color=self.app.defaults["geometry_plot_line"]) + + # self.plot_element(self.solid_geometry, visible=self.options['plot']) + + self.shapes.redraw() + + except (ObjectDeleted, AttributeError): + self.shapes.clear(update=True) + + def on_plot_cb_click(self, *args): + if self.muted_ui: + return + self.read_form_item('plot') + self.plot() + + self.ui_disconnect() + cb_flag = self.ui.plot_cb.isChecked() + for row in range(self.ui.geo_tools_table.rowCount()): + table_cb = self.ui.geo_tools_table.cellWidget(row, 6) + if cb_flag: + table_cb.setChecked(True) + else: + table_cb.setChecked(False) + self.ui_connect() + + def on_plot_cb_click_table(self): + # self.ui.cnc_tools_table.cellWidget(row, 2).widget().setCheckState(QtCore.Qt.Unchecked) + self.ui_disconnect() + # cw = self.sender() + # cw_index = self.ui.geo_tools_table.indexAt(cw.pos()) + # cw_row = cw_index.row() + check_row = 0 + + self.shapes.clear(update=True) + for tooluid_key in self.tools: + solid_geometry = self.tools[tooluid_key]['solid_geometry'] + + # find the geo_tool_table row associated with the tooluid_key + for row in range(self.ui.geo_tools_table.rowCount()): + tooluid_item = int(self.ui.geo_tools_table.item(row, 5).text()) + if tooluid_item == int(tooluid_key): + check_row = row + break + if self.ui.geo_tools_table.cellWidget(check_row, 6).isChecked(): + self.plot_element(element=solid_geometry, visible=True) + self.shapes.redraw() + + # make sure that the general plot is disabled if one of the row plot's are disabled and + # if all the row plot's are enabled also enable the general plot checkbox + cb_cnt = 0 + total_row = self.ui.geo_tools_table.rowCount() + for row in range(total_row): + if self.ui.geo_tools_table.cellWidget(row, 6).isChecked(): + cb_cnt += 1 + else: + cb_cnt -= 1 + if cb_cnt < total_row: + self.ui.plot_cb.setChecked(False) + else: + self.ui.plot_cb.setChecked(True) + self.ui_connect() + + @staticmethod + def merge(geo_list, geo_final, multigeo=None): + """ + Merges the geometry of objects in grb_list into the geometry of geo_final. + + :param geo_list: List of GerberObject Objects to join. + :param geo_final: Destination GerberObject object. + :param multigeo: if the merged geometry objects are of type MultiGeo + :return: None + """ + + if geo_final.solid_geometry is None: + geo_final.solid_geometry = [] + + try: + __ = iter(geo_final.solid_geometry) + except TypeError: + geo_final.solid_geometry = [geo_final.solid_geometry] + + new_solid_geometry = [] + new_options = {} + new_tools = {} + + for geo_obj in geo_list: + for option in geo_obj.options: + if option != 'name': + try: + new_options[option] = deepcopy(geo_obj.options[option]) + except Exception as e: + log.warning("Failed to copy option %s. Error: %s" % (str(option), str(e))) + + # Expand lists + if type(geo_obj) is list: + GeometryObject.merge(geo_list=geo_obj, geo_final=geo_final) + # If not list, just append + else: + if multigeo is None or multigeo is False: + geo_final.multigeo = False + else: + geo_final.multigeo = True + + try: + new_solid_geometry += deepcopy(geo_obj.solid_geometry) + except Exception as e: + log.debug("GeometryObject.merge() --> %s" % str(e)) + + # find the tool_uid maximum value in the geo_final + try: + max_uid = max([int(i) for i in new_tools.keys()]) + except ValueError: + max_uid = 0 + + # add and merge tools. If what we try to merge as Geometry is Excellon's and/or Gerber's then don't try + # to merge the obj.tools as it is likely there is none to merge. + if geo_obj.kind != 'gerber' and geo_obj.kind != 'excellon': + for tool_uid in geo_obj.tools: + max_uid += 1 + new_tools[max_uid] = deepcopy(geo_obj.tools[tool_uid]) + + geo_final.options.update(new_options) + geo_final.solid_geometry = new_solid_geometry + geo_final.tools = new_tools + + @staticmethod + def get_pts(o): + """ + Returns a list of all points in the object, where + the object can be a MultiPolygon, Polygon, Not a polygon, or a list + of such. Search is done recursively. + + :param: geometric object + :return: List of points + :rtype: list + """ + pts = [] + + # Iterable: descend into each item. + try: + for subo in o: + pts += GeometryObject.get_pts(subo) + + # Non-iterable + except TypeError: + if o is not None: + if type(o) == MultiPolygon: + for poly in o: + pts += GeometryObject.get_pts(poly) + # ## Descend into .exerior and .interiors + elif type(o) == Polygon: + pts += GeometryObject.get_pts(o.exterior) + for i in o.interiors: + pts += GeometryObject.get_pts(i) + elif type(o) == MultiLineString: + for line in o: + pts += GeometryObject.get_pts(line) + # ## Has .coords: list them. + else: + pts += list(o.coords) + else: + return + return pts diff --git a/flatcamObjects/FlatCAMGerber.py b/flatcamObjects/FlatCAMGerber.py new file mode 100644 index 00000000..4d7985cd --- /dev/null +++ b/flatcamObjects/FlatCAMGerber.py @@ -0,0 +1,1871 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + + +from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString, LineString, LinearRing +from shapely.ops import cascaded_union + +from flatcamParsers.ParseGerber import Gerber +from flatcamObjects.FlatCAMObj import * + +import math +import numpy as np +from copy import deepcopy + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class GerberObject(FlatCAMObj, Gerber): + """ + Represents Gerber code. + """ + optionChanged = QtCore.pyqtSignal(str) + replotApertures = QtCore.pyqtSignal() + + ui_type = GerberObjectUI + + @staticmethod + def merge(grb_list, grb_final): + """ + Merges the geometry of objects in geo_list into + the geometry of geo_final. + + :param grb_list: List of GerberObject Objects to join. + :param grb_final: Destination GeometryObject object. + :return: None + """ + + if grb_final.solid_geometry is None: + grb_final.solid_geometry = [] + grb_final.follow_geometry = [] + + if not grb_final.apertures: + grb_final.apertures = {} + + if type(grb_final.solid_geometry) is not list: + grb_final.solid_geometry = [grb_final.solid_geometry] + grb_final.follow_geometry = [grb_final.follow_geometry] + + for grb in grb_list: + + # Expand lists + if type(grb) is list: + GerberObject.merge(grb_list=grb, grb_final=grb_final) + else: # If not list, just append + for option in grb.options: + if option != 'name': + try: + grb_final.options[option] = grb.options[option] + except KeyError: + log.warning("Failed to copy option.", option) + + try: + for geos in grb.solid_geometry: + grb_final.solid_geometry.append(geos) + grb_final.follow_geometry.append(geos) + except TypeError: + grb_final.solid_geometry.append(grb.solid_geometry) + grb_final.follow_geometry.append(grb.solid_geometry) + + for ap in grb.apertures: + if ap not in grb_final.apertures: + grb_final.apertures[ap] = grb.apertures[ap] + else: + # create a list of integers out of the grb.apertures keys and find the max of that value + # then, the aperture duplicate is assigned an id value incremented with 1, + # and finally made string because the apertures dict keys are strings + max_ap = str(max([int(k) for k in grb_final.apertures.keys()]) + 1) + grb_final.apertures[max_ap] = {} + grb_final.apertures[max_ap]['geometry'] = [] + + for k, v in grb.apertures[ap].items(): + grb_final.apertures[max_ap][k] = deepcopy(v) + + grb_final.solid_geometry = MultiPolygon(grb_final.solid_geometry) + grb_final.follow_geometry = MultiPolygon(grb_final.follow_geometry) + + def __init__(self, name): + self.decimals = self.app.decimals + + self.circle_steps = int(self.app.defaults["gerber_circle_steps"]) + + Gerber.__init__(self, steps_per_circle=self.circle_steps) + FlatCAMObj.__init__(self, name) + + self.kind = "gerber" + + # The 'name' is already in self.options from FlatCAMObj + # Automatically updates the UI + self.options.update({ + "plot": True, + "multicolored": False, + "solid": False, + "tool_type": 'circular', + "vtipdia": 0.1, + "vtipangle": 30, + "vcutz": -0.05, + "isotooldia": 0.016, + "isopasses": 1, + "isooverlap": 15, + "milling_type": "cl", + "combine_passes": True, + "noncoppermargin": 0.0, + "noncopperrounded": False, + "bboxmargin": 0.0, + "bboxrounded": False, + "aperture_display": False, + "follow": False, + "iso_scope": 'all', + "iso_type": 'full' + }) + + # type of isolation: 0 = exteriors, 1 = interiors, 2 = complete isolation (both interiors and exteriors) + self.iso_type = 2 + + self.multigeo = False + + self.follow = False + + self.apertures_row = 0 + + # store the source file here + self.source_file = "" + + # list of rows with apertures plotted + self.marked_rows = [] + + # Mouse events + self.mr = None + self.mm = None + self.mp = None + + # dict to store the polygons selected for isolation; key is the shape added to be plotted and value is the poly + self.poly_dict = {} + + # store the status of grid snapping + self.grid_status_memory = None + + self.units_found = self.app.defaults['units'] + + self.fill_color = self.app.defaults['gerber_plot_fill'] + self.outline_color = self.app.defaults['gerber_plot_line'] + self.alpha_level = 'bf' + + # keep track if the UI is built so we don't have to build it every time + self.ui_build = False + + # build only once the aperture storage (takes time) + self.build_aperture_storage = False + + # Attributes to be included in serialization + # Always append to it because it carries contents + # from predecessors. + self.ser_attrs += ['options', 'kind', 'fill_color', 'outline_color', 'alpha_level'] + + def set_ui(self, ui): + """ + Maps options with GUI inputs. + Connects GUI events to methods. + + :param ui: GUI object. + :type ui: GerberObjectUI + :return: None + """ + FlatCAMObj.set_ui(self, ui) + log.debug("GerberObject.set_ui()") + + self.units = self.app.defaults['units'].upper() + + self.replotApertures.connect(self.on_mark_cb_click_table) + + self.form_fields.update({ + "plot": self.ui.plot_cb, + "multicolored": self.ui.multicolored_cb, + "solid": self.ui.solid_cb, + "tool_type": self.ui.tool_type_radio, + "vtipdia": self.ui.tipdia_spinner, + "vtipangle": self.ui.tipangle_spinner, + "vcutz": self.ui.cutz_spinner, + "isotooldia": self.ui.iso_tool_dia_entry, + "isopasses": self.ui.iso_width_entry, + "isooverlap": self.ui.iso_overlap_entry, + "milling_type": self.ui.milling_type_radio, + "combine_passes": self.ui.combine_passes_cb, + "noncoppermargin": self.ui.noncopper_margin_entry, + "noncopperrounded": self.ui.noncopper_rounded_cb, + "bboxmargin": self.ui.bbmargin_entry, + "bboxrounded": self.ui.bbrounded_cb, + "aperture_display": self.ui.aperture_table_visibility_cb, + "follow": self.ui.follow_cb, + "iso_scope": self.ui.iso_scope_radio, + "iso_type": self.ui.iso_type_radio + }) + + # Fill form fields only on object create + self.to_form() + + assert isinstance(self.ui, GerberObjectUI) + self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click) + self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click) + self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click) + self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click) + self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run) + self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run) + self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click) + self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click) + self.ui.aperture_table_visibility_cb.stateChanged.connect(self.on_aperture_table_visibility_change) + self.ui.follow_cb.stateChanged.connect(self.on_follow_cb_click) + + # set the model for the Area Exception comboboxes + self.ui.obj_combo.setModel(self.app.collection) + self.ui.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) + self.ui.obj_combo.is_last = True + self.ui.obj_combo.obj_type = { + _("Gerber"): "Gerber", _("Geometry"): "Geometry" + }[self.ui.type_obj_combo.get_value()] + self.on_type_obj_index_changed() + + self.ui.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed) + + self.ui.tool_type_radio.activated_custom.connect(self.on_tool_type_change) + # establish visibility for the GUI elements found in the slot function + self.ui.tool_type_radio.activated_custom.emit(self.options['tool_type']) + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText('%s' % _('Basic')) + self.options['tool_type'] = 'circular' + + self.ui.tool_type_label.hide() + self.ui.tool_type_radio.hide() + + # override the Preferences Value; in Basic mode the Tool Type is always Circular ('C1') + self.ui.tool_type_radio.set_value('circular') + + self.ui.tipdialabel.hide() + self.ui.tipdia_spinner.hide() + self.ui.tipanglelabel.hide() + self.ui.tipangle_spinner.hide() + self.ui.cutzlabel.hide() + self.ui.cutz_spinner.hide() + + self.ui.apertures_table_label.hide() + self.ui.aperture_table_visibility_cb.hide() + self.ui.milling_type_label.hide() + self.ui.milling_type_radio.hide() + self.ui.iso_type_label.hide() + self.ui.iso_type_radio.hide() + + self.ui.follow_cb.hide() + self.ui.except_cb.setChecked(False) + self.ui.except_cb.hide() + else: + self.ui.level.setText('%s' % _('Advanced')) + self.ui.tipdia_spinner.valueChanged.connect(self.on_calculate_tooldia) + self.ui.tipangle_spinner.valueChanged.connect(self.on_calculate_tooldia) + self.ui.cutz_spinner.valueChanged.connect(self.on_calculate_tooldia) + + if self.app.defaults["gerber_buffering"] == 'no': + self.ui.create_buffer_button.show() + try: + self.ui.create_buffer_button.clicked.disconnect(self.on_generate_buffer) + except TypeError: + pass + self.ui.create_buffer_button.clicked.connect(self.on_generate_buffer) + else: + self.ui.create_buffer_button.hide() + + # set initial state of the aperture table and associated widgets + self.on_aperture_table_visibility_change() + + self.build_ui() + self.units_found = self.app.defaults['units'] + + def on_calculate_tooldia(self): + try: + tdia = float(self.ui.tipdia_spinner.get_value()) + except Exception: + return + try: + dang = float(self.ui.tipangle_spinner.get_value()) + except Exception: + return + try: + cutz = float(self.ui.cutz_spinner.get_value()) + except Exception: + return + + cutz *= -1 + if cutz < 0: + cutz *= -1 + + half_tip_angle = dang / 2 + + tool_diameter = tdia + (2 * cutz * math.tan(math.radians(half_tip_angle))) + self.ui.iso_tool_dia_entry.set_value(tool_diameter) + + def on_type_obj_index_changed(self): + val = self.ui.type_obj_combo.get_value() + obj_type = {"Gerber": 0, "Geometry": 2}[val] + self.ui.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex())) + self.ui.obj_combo.setCurrentIndex(0) + self.ui.obj_combo.obj_type = {_("Gerber"): "Gerber", _("Geometry"): "Geometry"}[val] + + def on_tool_type_change(self, state): + if state == 'circular': + self.ui.tipdialabel.hide() + self.ui.tipdia_spinner.hide() + self.ui.tipanglelabel.hide() + self.ui.tipangle_spinner.hide() + self.ui.cutzlabel.hide() + self.ui.cutz_spinner.hide() + self.ui.iso_tool_dia_entry.setDisabled(False) + # update the value in the self.iso_tool_dia_entry once this is selected + self.ui.iso_tool_dia_entry.set_value(self.options['isotooldia']) + else: + self.ui.tipdialabel.show() + self.ui.tipdia_spinner.show() + self.ui.tipanglelabel.show() + self.ui.tipangle_spinner.show() + self.ui.cutzlabel.show() + self.ui.cutz_spinner.show() + self.ui.iso_tool_dia_entry.setDisabled(True) + # update the value in the self.iso_tool_dia_entry once this is selected + self.on_calculate_tooldia() + + def build_ui(self): + FlatCAMObj.build_ui(self) + + if self.ui.aperture_table_visibility_cb.get_value() and self.ui_build is False: + self.ui_build = True + + try: + # if connected, disconnect the signal from the slot on item_changed as it creates issues + self.ui.apertures_table.itemChanged.disconnect() + except (TypeError, AttributeError): + pass + + self.apertures_row = 0 + aper_no = self.apertures_row + 1 + sort = [] + for k, v in list(self.apertures.items()): + sort.append(int(k)) + sorted_apertures = sorted(sort) + + n = len(sorted_apertures) + self.ui.apertures_table.setRowCount(n) + + for ap_code in sorted_apertures: + ap_code = str(ap_code) + + ap_id_item = QtWidgets.QTableWidgetItem('%d' % int(self.apertures_row + 1)) + ap_id_item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.apertures_table.setItem(self.apertures_row, 0, ap_id_item) # Tool name/id + + ap_code_item = QtWidgets.QTableWidgetItem(ap_code) + ap_code_item.setFlags(QtCore.Qt.ItemIsEnabled) + + ap_type_item = QtWidgets.QTableWidgetItem(str(self.apertures[ap_code]['type'])) + ap_type_item.setFlags(QtCore.Qt.ItemIsEnabled) + + if str(self.apertures[ap_code]['type']) == 'R' or str(self.apertures[ap_code]['type']) == 'O': + ap_dim_item = QtWidgets.QTableWidgetItem( + '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['width'], + self.decimals, self.apertures[ap_code]['height'] + ) + ) + ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) + elif str(self.apertures[ap_code]['type']) == 'P': + ap_dim_item = QtWidgets.QTableWidgetItem( + '%.*f, %.*f' % (self.decimals, self.apertures[ap_code]['diam'], + self.decimals, self.apertures[ap_code]['nVertices']) + ) + ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) + else: + ap_dim_item = QtWidgets.QTableWidgetItem('') + ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled) + + try: + if self.apertures[ap_code]['size'] is not None: + ap_size_item = QtWidgets.QTableWidgetItem( + '%.*f' % (self.decimals, float(self.apertures[ap_code]['size']))) + else: + ap_size_item = QtWidgets.QTableWidgetItem('') + except KeyError: + ap_size_item = QtWidgets.QTableWidgetItem('') + ap_size_item.setFlags(QtCore.Qt.ItemIsEnabled) + + mark_item = FCCheckBox() + mark_item.setLayoutDirection(QtCore.Qt.RightToLeft) + # if self.ui.aperture_table_visibility_cb.isChecked(): + # mark_item.setChecked(True) + + self.ui.apertures_table.setItem(self.apertures_row, 1, ap_code_item) # Aperture Code + self.ui.apertures_table.setItem(self.apertures_row, 2, ap_type_item) # Aperture Type + self.ui.apertures_table.setItem(self.apertures_row, 3, ap_size_item) # Aperture Dimensions + self.ui.apertures_table.setItem(self.apertures_row, 4, ap_dim_item) # Aperture Dimensions + + empty_plot_item = QtWidgets.QTableWidgetItem('') + empty_plot_item.setFlags(~QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.ui.apertures_table.setItem(self.apertures_row, 5, empty_plot_item) + self.ui.apertures_table.setCellWidget(self.apertures_row, 5, mark_item) + + self.apertures_row += 1 + + self.ui.apertures_table.selectColumn(0) + self.ui.apertures_table.resizeColumnsToContents() + self.ui.apertures_table.resizeRowsToContents() + + vertical_header = self.ui.apertures_table.verticalHeader() + # vertical_header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + vertical_header.hide() + self.ui.apertures_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + horizontal_header = self.ui.apertures_table.horizontalHeader() + horizontal_header.setMinimumSectionSize(10) + horizontal_header.setDefaultSectionSize(70) + horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(0, 27) + horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) + horizontal_header.setSectionResizeMode(4, QtWidgets.QHeaderView.Stretch) + horizontal_header.setSectionResizeMode(5, QtWidgets.QHeaderView.Fixed) + horizontal_header.resizeSection(5, 17) + self.ui.apertures_table.setColumnWidth(5, 17) + + self.ui.apertures_table.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.ui.apertures_table.setSortingEnabled(False) + self.ui.apertures_table.setMinimumHeight(self.ui.apertures_table.getHeight()) + self.ui.apertures_table.setMaximumHeight(self.ui.apertures_table.getHeight()) + + # update the 'mark' checkboxes state according with what is stored in the self.marked_rows list + if self.marked_rows: + for row in range(self.ui.apertures_table.rowCount()): + try: + self.ui.apertures_table.cellWidget(row, 5).set_value(self.marked_rows[row]) + except IndexError: + pass + + self.ui_connect() + + def ui_connect(self): + for row in range(self.ui.apertures_table.rowCount()): + try: + self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect(self.on_mark_cb_click_table) + except (TypeError, AttributeError): + pass + self.ui.apertures_table.cellWidget(row, 5).clicked.connect(self.on_mark_cb_click_table) + + try: + self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click) + except (TypeError, AttributeError): + pass + self.ui.mark_all_cb.clicked.connect(self.on_mark_all_click) + + def ui_disconnect(self): + for row in range(self.ui.apertures_table.rowCount()): + try: + self.ui.apertures_table.cellWidget(row, 5).clicked.disconnect() + except (TypeError, AttributeError): + pass + + try: + self.ui.mark_all_cb.clicked.disconnect(self.on_mark_all_click) + except (TypeError, AttributeError): + pass + + def on_generate_buffer(self): + self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Buffering solid geometry")) + + def buffer_task(): + with self.app.proc_container.new('%s...' % _("Buffering")): + if isinstance(self.solid_geometry, list): + self.solid_geometry = MultiPolygon(self.solid_geometry) + + self.solid_geometry = self.solid_geometry.buffer(0.0000001) + self.solid_geometry = self.solid_geometry.buffer(-0.0000001) + self.app.inform.emit('[success] %s.' % _("Done")) + self.plot_single_object.emit() + + self.app.worker_task.emit({'fcn': buffer_task, 'params': []}) + + def on_generatenoncopper_button_click(self, *args): + self.app.defaults.report_usage("gerber_on_generatenoncopper_button") + + self.read_form() + name = self.options["name"] + "_noncopper" + + def geo_init(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', "Expected a Geometry object got %s" % type(geo_obj) + + if isinstance(self.solid_geometry, list): + try: + self.solid_geometry = MultiPolygon(self.solid_geometry) + except Exception: + self.solid_geometry = cascaded_union(self.solid_geometry) + + bounding_box = self.solid_geometry.envelope.buffer(float(self.options["noncoppermargin"])) + if not self.options["noncopperrounded"]: + bounding_box = bounding_box.envelope + non_copper = bounding_box.difference(self.solid_geometry) + + if non_copper is None or non_copper.is_empty: + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done.")) + return "fail" + geo_obj.solid_geometry = non_copper + + self.app.new_object("geometry", name, geo_init) + + def on_generatebb_button_click(self, *args): + self.app.defaults.report_usage("gerber_on_generatebb_button") + self.read_form() + name = self.options["name"] + "_bbox" + + def geo_init(geo_obj, app_obj): + assert geo_obj.kind == 'geometry', "Expected a Geometry object got %s" % type(geo_obj) + + if isinstance(self.solid_geometry, list): + try: + self.solid_geometry = MultiPolygon(self.solid_geometry) + except Exception: + self.solid_geometry = cascaded_union(self.solid_geometry) + + # Bounding box with rounded corners + bounding_box = self.solid_geometry.envelope.buffer(float(self.options["bboxmargin"])) + if not self.options["bboxrounded"]: # Remove rounded corners + bounding_box = bounding_box.envelope + + if bounding_box is None or bounding_box.is_empty: + self.app.inform.emit("[ERROR_NOTCL] %s" % _("Operation could not be done.")) + return "fail" + geo_obj.solid_geometry = bounding_box + + self.app.new_object("geometry", name, geo_init) + + def on_iso_button_click(self, *args): + + obj = self.app.collection.get_active() + + self.iso_type = 2 + if self.ui.iso_type_radio.get_value() == 'ext': + self.iso_type = 0 + if self.ui.iso_type_radio.get_value() == 'int': + self.iso_type = 1 + + def worker_task(iso_obj, app_obj): + with self.app.proc_container.new(_("Isolating...")): + if self.ui.follow_cb.get_value() is True: + iso_obj.follow_geo() + # in the end toggle the visibility of the origin object so we can see the generated Geometry + iso_obj.ui.plot_cb.toggle() + else: + app_obj.defaults.report_usage("gerber_on_iso_button") + self.read_form() + + iso_scope = 'all' if self.ui.iso_scope_radio.get_value() == 'all' else 'single' + self.isolate_handler(iso_type=self.iso_type, iso_scope=iso_scope) + + self.app.worker_task.emit({'fcn': worker_task, 'params': [obj, self.app]}) + + def follow_geo(self, outname=None): + """ + Creates a geometry object "following" the gerber paths. + + :return: None + """ + + # default_name = self.options["name"] + "_follow" + # follow_name = outname or default_name + + if outname is None: + follow_name = self.options["name"] + "_follow" + else: + follow_name = outname + + def follow_init(follow_obj, app): + # Propagate options + follow_obj.options["cnctooldia"] = str(self.options["isotooldia"]) + follow_obj.solid_geometry = self.follow_geometry + + # TODO: Do something if this is None. Offer changing name? + try: + self.app.new_object("geometry", follow_name, follow_init) + except Exception as e: + return "Operation failed: %s" % str(e) + + def isolate_handler(self, iso_type, iso_scope): + + if iso_scope == 'all': + self.isolate(iso_type=iso_type) + else: + # disengage the grid snapping since it may be hard to click on polygons with grid snapping on + if self.app.ui.grid_snap_btn.isChecked(): + self.grid_status_memory = True + self.app.ui.grid_snap_btn.trigger() + else: + self.grid_status_memory = False + + self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release) + + if self.app.is_legacy is False: + 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.mr) + + self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click on a polygon to isolate it.")) + + def on_mouse_click_release(self, event): + if self.app.is_legacy is False: + event_pos = event.pos + right_button = 2 + self.app.event_is_dragging = self.app.event_is_dragging + else: + event_pos = (event.xdata, event.ydata) + right_button = 3 + self.app.event_is_dragging = self.app.ui.popMenu.mouse_is_panning + + try: + x = float(event_pos[0]) + y = float(event_pos[1]) + except TypeError: + return + + event_pos = (x, y) + curr_pos = self.app.plotcanvas.translate_coords(event_pos) + if self.app.grid_status(): + curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1]) + else: + curr_pos = (curr_pos[0], curr_pos[1]) + + if event.button == 1: + clicked_poly = self.find_polygon(point=(curr_pos[0], curr_pos[1])) + + if self.app.selection_type is not None: + self.selection_area_handler(self.app.pos, curr_pos, self.app.selection_type) + self.app.selection_type = None + elif clicked_poly: + if clicked_poly not in self.poly_dict.values(): + shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, shape=clicked_poly, + color=self.app.defaults['global_sel_draw_color'] + 'AF', + face_color=self.app.defaults['global_sel_draw_color'] + 'AF', + visible=True) + self.poly_dict[shape_id] = clicked_poly + self.app.inform.emit( + '%s: %d. %s' % (_("Added polygon"), int(len(self.poly_dict)), + _("Click to add next polygon or right click to start isolation.")) + ) + else: + try: + for k, v in list(self.poly_dict.items()): + if v == clicked_poly: + self.app.tool_shapes.remove(k) + self.poly_dict.pop(k) + break + except TypeError: + return + self.app.inform.emit( + '%s. %s' % (_("Removed polygon"), + _("Click to add/remove next polygon or right click to start isolation.")) + ) + + self.app.tool_shapes.redraw() + else: + self.app.inform.emit(_("No polygon detected under click position.")) + elif event.button == right_button and self.app.event_is_dragging is False: + # restore the Grid snapping if it was active before + if self.grid_status_memory is True: + self.app.ui.grid_snap_btn.trigger() + + if self.app.is_legacy is False: + self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release) + else: + self.app.plotcanvas.graph_event_disconnect(self.mr) + + self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release', + self.app.on_mouse_click_release_over_plot) + + self.app.tool_shapes.clear(update=True) + + if self.poly_dict: + poly_list = deepcopy(list(self.poly_dict.values())) + self.isolate(iso_type=self.iso_type, geometry=poly_list) + self.poly_dict.clear() + else: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("List of single polygons is empty. Aborting.")) + + def 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 + :return: + """ + poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])]) + + # delete previous selection shape + self.app.delete_selection_shape() + + added_poly_count = 0 + try: + for geo in self.solid_geometry: + if geo not in self.poly_dict.values(): + if sel_type is True: + if geo.within(poly_selection): + shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, + shape=geo, + color=self.app.defaults['global_sel_draw_color'] + 'AF', + face_color=self.app.defaults[ + 'global_sel_draw_color'] + 'AF', + visible=True) + self.poly_dict[shape_id] = geo + added_poly_count += 1 + else: + if poly_selection.intersects(geo): + shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, + shape=geo, + color=self.app.defaults['global_sel_draw_color'] + 'AF', + face_color=self.app.defaults[ + 'global_sel_draw_color'] + 'AF', + visible=True) + self.poly_dict[shape_id] = geo + added_poly_count += 1 + except TypeError: + if self.solid_geometry not in self.poly_dict.values(): + if sel_type is True: + if self.solid_geometry.within(poly_selection): + shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, + shape=self.solid_geometry, + color=self.app.defaults['global_sel_draw_color'] + 'AF', + face_color=self.app.defaults[ + 'global_sel_draw_color'] + 'AF', + visible=True) + self.poly_dict[shape_id] = self.solid_geometry + added_poly_count += 1 + else: + if poly_selection.intersects(self.solid_geometry): + shape_id = self.app.tool_shapes.add(tolerance=self.drawing_tolerance, layer=0, + shape=self.solid_geometry, + color=self.app.defaults['global_sel_draw_color'] + 'AF', + face_color=self.app.defaults[ + 'global_sel_draw_color'] + 'AF', + visible=True) + self.poly_dict[shape_id] = self.solid_geometry + added_poly_count += 1 + + if added_poly_count > 0: + self.app.tool_shapes.redraw() + self.app.inform.emit( + '%s: %d. %s' % (_("Added polygon"), + int(added_poly_count), + _("Click to add next polygon or right click to start isolation.")) + ) + else: + self.app.inform.emit(_("No polygon in selection.")) + + def isolate(self, iso_type=None, geometry=None, dia=None, passes=None, overlap=None, outname=None, combine=None, + milling_type=None, follow=None, plot=True): + """ + Creates an isolation routing geometry object in the project. + + :param iso_type: type of isolation to be done: 0 = exteriors, 1 = interiors and 2 = both + :param geometry: specific geometry to isolate + :param dia: Tool diameter + :param passes: Number of tool widths to cut + :param overlap: Overlap between passes in fraction of tool diameter + :param outname: Base name of the output object + :param combine: Boolean: if to combine passes in one resulting object in case of multiple passes + :param milling_type: type of milling: conventional or climbing + :param follow: Boolean: if to generate a 'follow' geometry + :param plot: Boolean: if to plot the resulting geometry object + :return: None + """ + + if geometry is None: + work_geo = self.follow_geometry if follow is True else self.solid_geometry + else: + work_geo = geometry + + if dia is None: + dia = float(self.options["isotooldia"]) + + if passes is None: + passes = int(self.options["isopasses"]) + + if overlap is None: + overlap = float(self.options["isooverlap"]) + + overlap /= 100.0 + + combine = self.options["combine_passes"] if combine is None else bool(combine) + + if milling_type is None: + milling_type = self.options["milling_type"] + + if iso_type is None: + iso_t = 2 + else: + iso_t = iso_type + + base_name = self.options["name"] + + if combine: + if outname is None: + if self.iso_type == 0: + iso_name = base_name + "_ext_iso" + elif self.iso_type == 1: + iso_name = base_name + "_int_iso" + else: + iso_name = base_name + "_iso" + else: + iso_name = outname + + def iso_init(geo_obj, app_obj): + # Propagate options + geo_obj.options["cnctooldia"] = str(self.options["isotooldia"]) + geo_obj.tool_type = self.ui.tool_type_radio.get_value().upper() + + geo_obj.solid_geometry = [] + + # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI + if self.ui.tool_type_radio.get_value() == 'v': + new_cutz = self.ui.cutz_spinner.get_value() + new_vtipdia = self.ui.tipdia_spinner.get_value() + new_vtipangle = self.ui.tipangle_spinner.get_value() + tool_type = 'V' + else: + new_cutz = self.app.defaults['geometry_cutz'] + new_vtipdia = self.app.defaults['geometry_vtipdia'] + new_vtipangle = self.app.defaults['geometry_vtipangle'] + tool_type = 'C1' + + # store here the default data for Geometry Data + default_data = {} + default_data.update({ + "name": iso_name, + "plot": self.app.defaults['geometry_plot'], + "cutz": new_cutz, + "vtipdia": new_vtipdia, + "vtipangle": new_vtipangle, + "travelz": self.app.defaults['geometry_travelz'], + "feedrate": self.app.defaults['geometry_feedrate'], + "feedrate_z": self.app.defaults['geometry_feedrate_z'], + "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'], + "dwell": self.app.defaults['geometry_dwell'], + "dwelltime": self.app.defaults['geometry_dwelltime'], + "multidepth": self.app.defaults['geometry_multidepth'], + "ppname_g": self.app.defaults['geometry_ppname_g'], + "depthperpass": self.app.defaults['geometry_depthperpass'], + "extracut": self.app.defaults['geometry_extracut'], + "extracut_length": self.app.defaults['geometry_extracut_length'], + "toolchange": self.app.defaults['geometry_toolchange'], + "toolchangez": self.app.defaults['geometry_toolchangez'], + "endz": self.app.defaults['geometry_endz'], + "spindlespeed": self.app.defaults['geometry_spindlespeed'], + "toolchangexy": self.app.defaults['geometry_toolchangexy'], + "startz": self.app.defaults['geometry_startz'] + }) + + geo_obj.tools = {} + geo_obj.tools['1'] = {} + geo_obj.tools.update({ + '1': { + 'tooldia': float(self.options["isotooldia"]), + 'offset': 'Path', + 'offset_value': 0.0, + 'type': _('Rough'), + 'tool_type': tool_type, + 'data': default_data, + 'solid_geometry': geo_obj.solid_geometry + } + }) + + for nr_pass in range(passes): + iso_offset = dia * ((2 * nr_pass + 1) / 2.0) - (nr_pass * overlap * dia) + + # if milling type is climb then the move is counter-clockwise around features + mill_dir = 1 if milling_type == 'cl' else 0 + geom = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t, + follow=follow, nr_passes=nr_pass) + + if geom == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) + return 'fail' + geo_obj.solid_geometry.append(geom) + + # update the geometry in the tools + geo_obj.tools['1']['solid_geometry'] = geo_obj.solid_geometry + + # detect if solid_geometry is empty and this require list flattening which is "heavy" + # or just looking in the lists (they are one level depth) and if any is not empty + # proceed with object creation, if there are empty and the number of them is the length + # of the list then we have an empty solid_geometry which should raise a Custom Exception + empty_cnt = 0 + if not isinstance(geo_obj.solid_geometry, list) and \ + not isinstance(geo_obj.solid_geometry, MultiPolygon): + geo_obj.solid_geometry = [geo_obj.solid_geometry] + + for g in geo_obj.solid_geometry: + if g: + break + else: + empty_cnt += 1 + + if empty_cnt == len(geo_obj.solid_geometry): + raise ValidationError("Empty Geometry", None) + else: + app_obj.inform.emit('[success] %s" %s' % (_("Isolation geometry created"), geo_obj.options["name"])) + + # even if combine is checked, one pass is still single-geo + geo_obj.multigeo = True if passes > 1 else False + + # ############################################################ + # ########## AREA SUBTRACTION ################################ + # ############################################################ + if self.ui.except_cb.get_value(): + self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo")) + geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry) + + # TODO: Do something if this is None. Offer changing name? + self.app.new_object("geometry", iso_name, iso_init, plot=plot) + else: + for i in range(passes): + + offset = dia * ((2 * i + 1) / 2.0) - (i * overlap * dia) + if passes > 1: + if outname is None: + if self.iso_type == 0: + iso_name = base_name + "_ext_iso" + str(i + 1) + elif self.iso_type == 1: + iso_name = base_name + "_int_iso" + str(i + 1) + else: + iso_name = base_name + "_iso" + str(i + 1) + else: + iso_name = outname + else: + if outname is None: + if self.iso_type == 0: + iso_name = base_name + "_ext_iso" + elif self.iso_type == 1: + iso_name = base_name + "_int_iso" + else: + iso_name = base_name + "_iso" + else: + iso_name = outname + + def iso_init(geo_obj, app_obj): + # Propagate options + geo_obj.options["cnctooldia"] = str(self.options["isotooldia"]) + if self.ui.tool_type_radio.get_value() == 'v': + geo_obj.tool_type = 'V' + else: + geo_obj.tool_type = 'C1' + + # if milling type is climb then the move is counter-clockwise around features + mill_dir = 1 if milling_type == 'cl' else 0 + geom = self.generate_envelope(offset, mill_dir, geometry=work_geo, env_iso_type=iso_t, + follow=follow, + nr_passes=i) + + if geom == 'fail': + app_obj.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated.")) + return 'fail' + + geo_obj.solid_geometry = geom + + # transfer the Cut Z and Vtip and VAngle values in case that we use the V-Shape tool in Gerber UI + # even if the resulting geometry is not multigeo we add the tools dict which will hold the data + # required to be transfered to the Geometry object + if self.ui.tool_type_radio.get_value() == 'v': + new_cutz = self.ui.cutz_spinner.get_value() + new_vtipdia = self.ui.tipdia_spinner.get_value() + new_vtipangle = self.ui.tipangle_spinner.get_value() + tool_type = 'V' + else: + new_cutz = self.app.defaults['geometry_cutz'] + new_vtipdia = self.app.defaults['geometry_vtipdia'] + new_vtipangle = self.app.defaults['geometry_vtipangle'] + tool_type = 'C1' + + # store here the default data for Geometry Data + default_data = {} + default_data.update({ + "name": iso_name, + "plot": self.app.defaults['geometry_plot'], + "cutz": new_cutz, + "vtipdia": new_vtipdia, + "vtipangle": new_vtipangle, + "travelz": self.app.defaults['geometry_travelz'], + "feedrate": self.app.defaults['geometry_feedrate'], + "feedrate_z": self.app.defaults['geometry_feedrate_z'], + "feedrate_rapid": self.app.defaults['geometry_feedrate_rapid'], + "dwell": self.app.defaults['geometry_dwell'], + "dwelltime": self.app.defaults['geometry_dwelltime'], + "multidepth": self.app.defaults['geometry_multidepth'], + "ppname_g": self.app.defaults['geometry_ppname_g'], + "depthperpass": self.app.defaults['geometry_depthperpass'], + "extracut": self.app.defaults['geometry_extracut'], + "extracut_length": self.app.defaults['geometry_extracut_length'], + "toolchange": self.app.defaults['geometry_toolchange'], + "toolchangez": self.app.defaults['geometry_toolchangez'], + "endz": self.app.defaults['geometry_endz'], + "spindlespeed": self.app.defaults['geometry_spindlespeed'], + "toolchangexy": self.app.defaults['geometry_toolchangexy'], + "startz": self.app.defaults['geometry_startz'] + }) + + geo_obj.tools = {} + geo_obj.tools['1'] = {} + geo_obj.tools.update({ + '1': { + 'tooldia': float(self.options["isotooldia"]), + 'offset': 'Path', + 'offset_value': 0.0, + 'type': _('Rough'), + 'tool_type': tool_type, + 'data': default_data, + 'solid_geometry': geo_obj.solid_geometry + } + }) + + # detect if solid_geometry is empty and this require list flattening which is "heavy" + # or just looking in the lists (they are one level depth) and if any is not empty + # proceed with object creation, if there are empty and the number of them is the length + # of the list then we have an empty solid_geometry which should raise a Custom Exception + empty_cnt = 0 + if not isinstance(geo_obj.solid_geometry, list): + geo_obj.solid_geometry = [geo_obj.solid_geometry] + + for g in geo_obj.solid_geometry: + if g: + break + else: + empty_cnt += 1 + + if empty_cnt == len(geo_obj.solid_geometry): + raise ValidationError("Empty Geometry", None) + else: + app_obj.inform.emit('[success] %s: %s' % + (_("Isolation geometry created"), geo_obj.options["name"])) + geo_obj.multigeo = False + + # ############################################################ + # ########## AREA SUBTRACTION ################################ + # ############################################################ + if self.ui.except_cb.get_value(): + self.app.proc_container.update_view_text(' %s' % _("Subtracting Geo")) + geo_obj.solid_geometry = self.area_subtraction(geo_obj.solid_geometry) + + # TODO: Do something if this is None. Offer changing name? + self.app.new_object("geometry", iso_name, iso_init, plot=plot) + + def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0): + # isolation_geometry produces an envelope that is going on the left of the geometry + # (the copper features). To leave the least amount of burrs on the features + # the tool needs to travel on the right side of the features (this is called conventional milling) + # the first pass is the one cutting all of the features, so it needs to be reversed + # the other passes overlap preceding ones and cut the left over copper. It is better for them + # to cut on the right side of the left over copper i.e on the left side of the features. + + if follow: + geom = self.isolation_geometry(offset, geometry=geometry, follow=follow) + else: + try: + geom = self.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type, passes=nr_passes) + except Exception as e: + log.debug('GerberObject.isolate().generate_envelope() --> %s' % str(e)) + return 'fail' + + if invert: + try: + pl = [] + for p in geom: + if p is not None: + if isinstance(p, Polygon): + pl.append(Polygon(p.exterior.coords[::-1], p.interiors)) + elif isinstance(p, LinearRing): + pl.append(Polygon(p.coords[::-1])) + geom = MultiPolygon(pl) + except TypeError: + if isinstance(geom, Polygon) and geom is not None: + geom = Polygon(geom.exterior.coords[::-1], geom.interiors) + elif isinstance(geom, LinearRing) and geom is not None: + geom = Polygon(geom.coords[::-1]) + else: + log.debug("GerberObject.isolate().generate_envelope() Error --> Unexpected Geometry %s" % + type(geom)) + except Exception as e: + log.debug("GerberObject.isolate().generate_envelope() Error --> %s" % str(e)) + return 'fail' + return geom + + def area_subtraction(self, geo, subtractor_geo=None): + """ + Subtracts the subtractor_geo (if present else self.solid_geometry) from the geo + + :param geo: target geometry from which to subtract + :param subtractor_geo: geometry that acts as subtractor + :return: + """ + new_geometry = [] + target_geo = geo + + if subtractor_geo: + sub_union = cascaded_union(subtractor_geo) + else: + name = self.ui.obj_combo.currentText() + subtractor_obj = self.app.collection.get_by_name(name) + sub_union = cascaded_union(subtractor_obj.solid_geometry) + + try: + for geo_elem in target_geo: + if isinstance(geo_elem, Polygon): + for ring in self.poly2rings(geo_elem): + new_geo = ring.difference(sub_union) + 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 ring in self.poly2rings(poly): + new_geo = ring.difference(sub_union) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, LineString): + new_geo = geo_elem.difference(sub_union) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(geo_elem, MultiLineString): + for line_elem in geo_elem: + new_geo = line_elem.difference(sub_union) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + except TypeError: + if isinstance(target_geo, Polygon): + for ring in self.poly2rings(target_geo): + new_geo = ring.difference(sub_union) + if new_geo: + if not new_geo.is_empty: + new_geometry.append(new_geo) + elif isinstance(target_geo, LineString): + new_geo = target_geo.difference(sub_union) + 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: + new_geo = line_elem.difference(sub_union) + if new_geo and not new_geo.is_empty: + new_geometry.append(new_geo) + return new_geometry + + def on_plot_cb_click(self, *args): + if self.muted_ui: + return + self.read_form_item('plot') + self.plot() + + def on_solid_cb_click(self, *args): + if self.muted_ui: + return + self.read_form_item('solid') + self.plot() + + def on_multicolored_cb_click(self, *args): + if self.muted_ui: + return + self.read_form_item('multicolored') + self.plot() + + def on_follow_cb_click(self): + if self.muted_ui: + return + self.plot() + + def on_aperture_table_visibility_change(self): + if self.ui.aperture_table_visibility_cb.isChecked(): + # add the shapes storage for marking apertures + if self.build_aperture_storage is False: + self.build_aperture_storage = True + + if self.app.is_legacy is False: + for ap_code in self.apertures: + self.mark_shapes[ap_code] = self.app.plotcanvas.new_shape_collection(layers=1) + else: + for ap_code in self.apertures: + self.mark_shapes[ap_code] = ShapeCollectionLegacy(obj=self, app=self.app, + name=self.options['name'] + str(ap_code)) + + self.ui.apertures_table.setVisible(True) + for ap in self.mark_shapes: + self.mark_shapes[ap].enabled = True + + self.ui.mark_all_cb.setVisible(True) + self.ui.mark_all_cb.setChecked(False) + self.build_ui() + else: + self.ui.apertures_table.setVisible(False) + + self.ui.mark_all_cb.setVisible(False) + + # on hide disable all mark plots + try: + for row in range(self.ui.apertures_table.rowCount()): + self.ui.apertures_table.cellWidget(row, 5).set_value(False) + self.clear_plot_apertures() + + # for ap in list(self.mark_shapes.keys()): + # # self.mark_shapes[ap].enabled = False + # del self.mark_shapes[ap] + except Exception as e: + log.debug(" GerberObject.on_aperture_visibility_changed() --> %s" % str(e)) + + def convert_units(self, units): + """ + Converts the units of the object by scaling dimensions in all geometry + and options. + + :param units: Units to which to convert the object: "IN" or "MM". + :type units: str + :return: None + :rtype: None + """ + + # units conversion to get a conversion should be done only once even if we found multiple + # units declaration inside a Gerber file (it can happen to find also the obsolete declaration) + if self.conversion_done is True: + log.debug("Gerber units conversion cancelled. Already done.") + return + + log.debug("FlatCAMObj.GerberObject.convert_units()") + + factor = Gerber.convert_units(self, units) + + # self.options['isotooldia'] = float(self.options['isotooldia']) * factor + # self.options['bboxmargin'] = float(self.options['bboxmargin']) * factor + + def plot(self, kind=None, **kwargs): + """ + + :param kind: Not used, for compatibility with the plot method for other objects + :param kwargs: Color and face_color, visible + :return: + """ + log.debug(str(inspect.stack()[1][3]) + " --> GerberObject.plot()") + + # Does all the required setup and returns False + # if the 'ptint' option is set to False. + if not FlatCAMObj.plot(self): + return + + if 'color' in kwargs: + color = kwargs['color'] + else: + color = self.outline_color + + if 'face_color' in kwargs: + face_color = kwargs['face_color'] + else: + face_color = self.fill_color + + if 'visible' not in kwargs: + visible = self.options['plot'] + else: + visible = kwargs['visible'] + + # if the Follow Geometry checkbox is checked then plot only the follow geometry + if self.ui.follow_cb.get_value(): + geometry = self.follow_geometry + else: + geometry = self.solid_geometry + + # Make sure geometry is iterable. + try: + __ = iter(geometry) + except TypeError: + geometry = [geometry] + + if self.app.is_legacy is False: + def random_color(): + r_color = np.random.rand(4) + r_color[3] = 1 + return r_color + else: + def random_color(): + while True: + r_color = np.random.rand(4) + r_color[3] = 1 + + new_color = '#' + for idx in range(len(r_color)): + new_color += '%x' % int(r_color[idx] * 255) + # do it until a valid color is generated + # a valid color has the # symbol, another 6 chars for the color and the last 2 chars for alpha + # for a total of 9 chars + if len(new_color) == 9: + break + return new_color + + try: + if self.options["solid"]: + for g in geometry: + if type(g) == Polygon or type(g) == LineString: + self.add_shape(shape=g, color=color, + face_color=random_color() if self.options['multicolored'] + else face_color, visible=visible) + elif type(g) == Point: + pass + else: + try: + for el in g: + self.add_shape(shape=el, color=color, + face_color=random_color() if self.options['multicolored'] + else face_color, visible=visible) + except TypeError: + self.add_shape(shape=g, color=color, + face_color=random_color() if self.options['multicolored'] + else face_color, visible=visible) + else: + for g in geometry: + if type(g) == Polygon or type(g) == LineString: + self.add_shape(shape=g, color=random_color() if self.options['multicolored'] else 'black', + visible=visible) + elif type(g) == Point: + pass + else: + for el in g: + self.add_shape(shape=el, color=random_color() if self.options['multicolored'] else 'black', + visible=visible) + self.shapes.redraw( + # update_colors=(self.fill_color, self.outline_color), + # indexes=self.app.plotcanvas.shape_collection.data.keys() + ) + except (ObjectDeleted, AttributeError): + self.shapes.clear(update=True) + except Exception as e: + log.debug("GerberObject.plot() --> %s" % str(e)) + + # experimental plot() when the solid_geometry is stored in the self.apertures + def plot_aperture(self, run_thread=True, **kwargs): + """ + + :param run_thread: if True run the aperture plot as a thread in a worker + :param kwargs: color and face_color + :return: + """ + + log.debug(str(inspect.stack()[1][3]) + " --> GerberObject.plot_aperture()") + + # Does all the required setup and returns False + # if the 'ptint' option is set to False. + # if not FlatCAMObj.plot(self): + # return + + # for marking apertures, line color and fill color are the same + if 'color' in kwargs: + color = kwargs['color'] + else: + color = self.app.defaults['gerber_plot_fill'] + + if 'marked_aperture' not in kwargs: + return + else: + aperture_to_plot_mark = kwargs['marked_aperture'] + if aperture_to_plot_mark is None: + return + + if 'visible' not in kwargs: + visibility = True + else: + visibility = kwargs['visible'] + + with self.app.proc_container.new(_("Plotting Apertures")): + + def job_thread(app_obj): + try: + if aperture_to_plot_mark in self.apertures: + for elem in self.apertures[aperture_to_plot_mark]['geometry']: + if 'solid' in elem: + geo = elem['solid'] + if type(geo) == Polygon or type(geo) == LineString: + self.add_mark_shape(apid=aperture_to_plot_mark, shape=geo, color=color, + face_color=color, visible=visibility) + else: + for el in geo: + self.add_mark_shape(apid=aperture_to_plot_mark, shape=el, color=color, + face_color=color, visible=visibility) + + self.mark_shapes[aperture_to_plot_mark].redraw() + + except (ObjectDeleted, AttributeError): + self.clear_plot_apertures() + except Exception as e: + log.debug("GerberObject.plot_aperture() --> %s" % str(e)) + + if run_thread: + self.app.worker_task.emit({'fcn': job_thread, 'params': [self]}) + else: + job_thread(self) + + def clear_plot_apertures(self, aperture='all'): + """ + + :param aperture: string; aperture for which to clear the mark shapes + :return: + """ + + if self.mark_shapes: + if aperture == 'all': + for apid in list(self.apertures.keys()): + try: + if self.app.is_legacy is True: + self.mark_shapes[apid].clear(update=False) + else: + self.mark_shapes[apid].clear(update=True) + except Exception as e: + log.debug("GerberObject.clear_plot_apertures() 'all' --> %s" % str(e)) + else: + try: + if self.app.is_legacy is True: + self.mark_shapes[aperture].clear(update=False) + else: + self.mark_shapes[aperture].clear(update=True) + except Exception as e: + log.debug("GerberObject.clear_plot_apertures() 'aperture' --> %s" % str(e)) + + def clear_mark_all(self): + self.ui.mark_all_cb.set_value(False) + self.marked_rows[:] = [] + + def on_mark_cb_click_table(self): + """ + Will mark aperture geometries on canvas or delete the markings depending on the checkbox state + :return: + """ + + self.ui_disconnect() + try: + cw = self.sender() + assert isinstance(cw, FCCheckBox),\ + "Expected a cellWidget but got %s" % type(cw) + cw_index = self.ui.apertures_table.indexAt(cw.pos()) + cw_row = cw_index.row() + except AttributeError: + cw_row = 0 + except TypeError: + return + + self.marked_rows[:] = [] + + try: + aperture = self.ui.apertures_table.item(cw_row, 1).text() + except AttributeError: + return + + if self.ui.apertures_table.cellWidget(cw_row, 5).isChecked(): + self.marked_rows.append(True) + # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True) + self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF', + marked_aperture=aperture, visible=True, run_thread=True) + # self.mark_shapes[aperture].redraw() + else: + self.marked_rows.append(False) + self.clear_plot_apertures(aperture=aperture) + + # make sure that the Mark All is disabled if one of the row mark's are disabled and + # if all the row mark's are enabled also enable the Mark All checkbox + cb_cnt = 0 + total_row = self.ui.apertures_table.rowCount() + for row in range(total_row): + if self.ui.apertures_table.cellWidget(row, 5).isChecked(): + cb_cnt += 1 + else: + cb_cnt -= 1 + if cb_cnt < total_row: + self.ui.mark_all_cb.setChecked(False) + else: + self.ui.mark_all_cb.setChecked(True) + self.ui_connect() + + def on_mark_all_click(self): + self.ui_disconnect() + mark_all = self.ui.mark_all_cb.isChecked() + for row in range(self.ui.apertures_table.rowCount()): + # update the mark_rows list + if mark_all: + self.marked_rows.append(True) + else: + self.marked_rows[:] = [] + + mark_cb = self.ui.apertures_table.cellWidget(row, 5) + mark_cb.setChecked(mark_all) + + if mark_all: + for aperture in self.apertures: + # self.plot_aperture(color='#2d4606bf', marked_aperture=aperture, visible=True) + self.plot_aperture(color=self.app.defaults['global_sel_draw_color'] + 'AF', + marked_aperture=aperture, visible=True) + # HACK: enable/disable the grid for a better look + self.app.ui.grid_snap_btn.trigger() + self.app.ui.grid_snap_btn.trigger() + else: + self.clear_plot_apertures() + self.marked_rows[:] = [] + + self.ui_connect() + + def export_gerber(self, whole, fract, g_zeros='L', factor=1): + """ + Creates a Gerber file content to be exported to a file. + + :param whole: how many digits in the whole part of coordinates + :param fract: how many decimals in coordinates + :param g_zeros: type of the zero suppression used: LZ or TZ; string + :param factor: factor to be applied onto the Gerber coordinates + :return: Gerber_code + """ + log.debug("GerberObject.export_gerber() --> Generating the Gerber code from the selected Gerber file") + + def tz_format(x, y, fac): + x_c = x * fac + y_c = y * fac + + x_form = "{:.{dec}f}".format(x_c, dec=fract) + y_form = "{:.{dec}f}".format(y_c, dec=fract) + + # extract whole part and decimal part + x_form = x_form.partition('.') + y_form = y_form.partition('.') + + # left padd the 'whole' part with zeros + x_whole = x_form[0].rjust(whole, '0') + y_whole = y_form[0].rjust(whole, '0') + + # restore the coordinate padded in the left with 0 and added the decimal part + # without the decinal dot + x_form = x_whole + x_form[2] + y_form = y_whole + y_form[2] + return x_form, y_form + + def lz_format(x, y, fac): + x_c = x * fac + y_c = y * fac + + x_form = "{:.{dec}f}".format(x_c, dec=fract).replace('.', '') + y_form = "{:.{dec}f}".format(y_c, dec=fract).replace('.', '') + + # pad with rear zeros + x_form.ljust(length, '0') + y_form.ljust(length, '0') + + return x_form, y_form + + # Gerber code is stored here + gerber_code = '' + + # apertures processing + try: + length = whole + fract + if '0' in self.apertures: + if 'geometry' in self.apertures['0']: + for geo_elem in self.apertures['0']['geometry']: + if 'solid' in geo_elem: + geo = geo_elem['solid'] + if not geo.is_empty: + gerber_code += 'G36*\n' + geo_coords = list(geo.exterior.coords) + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + for coord in geo_coords[1:]: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + gerber_code += 'D02*\n' + gerber_code += 'G37*\n' + + clear_list = list(geo.interiors) + if clear_list: + gerber_code += '%LPC*%\n' + for clear_geo in clear_list: + gerber_code += 'G36*\n' + geo_coords = list(clear_geo.coords) + + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = geo_coords[0] + for coord in geo_coords[1:]: + if coord != prev_coord: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + prev_coord = coord + + gerber_code += 'D02*\n' + gerber_code += 'G37*\n' + gerber_code += '%LPD*%\n' + if 'clear' in geo_elem: + geo = geo_elem['clear'] + if not geo.is_empty: + gerber_code += '%LPC*%\n' + gerber_code += 'G36*\n' + geo_coords = list(geo.exterior.coords) + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = geo_coords[0] + for coord in geo_coords[1:]: + if coord != prev_coord: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + prev_coord = coord + + gerber_code += 'D02*\n' + gerber_code += 'G37*\n' + gerber_code += '%LPD*%\n' + except Exception as e: + log.debug("FlatCAMObj.GerberObject.export_gerber() '0' aperture --> %s" % str(e)) + + for apid in self.apertures: + if apid == '0': + continue + else: + gerber_code += 'D%s*\n' % str(apid) + if 'geometry' in self.apertures[apid]: + for geo_elem in self.apertures[apid]['geometry']: + try: + if 'follow' in geo_elem: + geo = geo_elem['follow'] + if not geo.is_empty: + if isinstance(geo, Point): + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(geo.x, geo.y, factor) + gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(geo.x, geo.y, factor) + gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, + yform=y_formatted) + else: + geo_coords = list(geo.coords) + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = geo_coords[0] + for coord in geo_coords[1:]: + if coord != prev_coord: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + prev_coord = coord + + # gerber_code += "D02*\n" + except Exception as e: + log.debug("FlatCAMObj.GerberObject.export_gerber() 'follow' --> %s" % str(e)) + + try: + if 'clear' in geo_elem: + gerber_code += '%LPC*%\n' + + geo = geo_elem['clear'] + if not geo.is_empty: + if isinstance(geo, Point): + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(geo.x, geo.y, factor) + gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(geo.x, geo.y, factor) + gerber_code += "X{xform}Y{yform}D03*\n".format(xform=x_formatted, + yform=y_formatted) + elif isinstance(geo, Polygon): + geo_coords = list(geo.exterior.coords) + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = geo_coords[0] + for coord in geo_coords[1:]: + if coord != prev_coord: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = coord + + for geo_int in geo.interiors: + geo_coords = list(geo_int.coords) + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = geo_coords[0] + for coord in geo_coords[1:]: + if coord != prev_coord: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format( + xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format( + xform=x_formatted, + yform=y_formatted) + + prev_coord = coord + else: + geo_coords = list(geo.coords) + # first command is a move with pen-up D02 at the beginning of the geo + if g_zeros == 'T': + x_formatted, y_formatted = tz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format( + geo_coords[0][0], geo_coords[0][1], factor) + gerber_code += "X{xform}Y{yform}D02*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = geo_coords[0] + for coord in geo_coords[1:]: + if coord != prev_coord: + if g_zeros == 'T': + x_formatted, y_formatted = tz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + else: + x_formatted, y_formatted = lz_format(coord[0], coord[1], factor) + gerber_code += "X{xform}Y{yform}D01*\n".format(xform=x_formatted, + yform=y_formatted) + + prev_coord = coord + # gerber_code += "D02*\n" + gerber_code += '%LPD*%\n' + except Exception as e: + log.debug("FlatCAMObj.GerberObject.export_gerber() 'clear' --> %s" % str(e)) + + if not self.apertures: + log.debug("FlatCAMObj.GerberObject.export_gerber() --> Gerber Object is empty: no apertures.") + return 'fail' + + return gerber_code + + def mirror(self, axis, point): + Gerber.mirror(self, axis=axis, point=point) + self.replotApertures.emit() + + def offset(self, vect): + Gerber.offset(self, vect=vect) + self.replotApertures.emit() + + def rotate(self, angle, point): + Gerber.rotate(self, angle=angle, point=point) + self.replotApertures.emit() + + def scale(self, xfactor, yfactor=None, point=None): + Gerber.scale(self, xfactor=xfactor, yfactor=yfactor, point=point) + self.replotApertures.emit() + + def skew(self, angle_x, angle_y, point): + Gerber.skew(self, angle_x=angle_x, angle_y=angle_y, point=point) + self.replotApertures.emit() + + def buffer(self, distance, join, factor=None): + Gerber.buffer(self, distance=distance, join=join, factor=factor) + self.replotApertures.emit() + + def serialize(self): + return { + "options": self.options, + "kind": self.kind + } diff --git a/flatcamObjects/FlatCAMObj.py b/flatcamObjects/FlatCAMObj.py new file mode 100644 index 00000000..94108cc9 --- /dev/null +++ b/flatcamObjects/FlatCAMObj.py @@ -0,0 +1,505 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + +import inspect # TODO: For debugging only. + +from flatcamGUI.ObjectUI import * + +from FlatCAMCommon import LoudDict +from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy + +import sys + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +# Interrupts plotting process if FlatCAMObj has been deleted +class ObjectDeleted(Exception): + pass + + +class ValidationError(Exception): + def __init__(self, message, errors): + super().__init__(message) + + self.errors = errors + + +class FlatCAMObj(QtCore.QObject): + """ + Base type of objects handled in FlatCAM. These become interactive + in the GUI, can be plotted, and their options can be modified + by the user in their respective forms. + """ + + # Instance of the application to which these are related. + # The app should set this value. + app = None + + # signal to plot a single object + plot_single_object = QtCore.pyqtSignal() + + def __init__(self, name): + """ + Constructor. + + :param name: Name of the object given by the user. + :return: FlatCAMObj + """ + + QtCore.QObject.__init__(self) + + # View + self.ui = None + + self.options = LoudDict(name=name) + self.options.set_change_callback(self.on_options_change) + + self.form_fields = {} + + # store here the default data for Geometry Data + self.default_data = {} + + # 2D mode + # Axes must exist and be attached to canvas. + self.axes = None + self.kind = None # Override with proper name + + if self.app.is_legacy is False: + self.shapes = self.app.plotcanvas.new_shape_group() + # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, pool=self.app.pool, layers=2) + else: + self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name=name) + + self.mark_shapes = {} + + self.item = None # Link with project view item + + self.muted_ui = False + self.deleted = False + + try: + self._drawing_tolerance = float(self.app.defaults["global_tolerance"]) if \ + self.app.defaults["global_tolerance"] else 0.01 + except ValueError: + self._drawing_tolerance = 0.01 + + self.isHovering = False + self.notHovering = True + + # Flag to show if a selection shape is drawn + self.selection_shape_drawn = False + + # self.units = 'IN' + self.units = self.app.defaults['units'] + + self.plot_single_object.connect(self.single_object_plot) + + def __del__(self): + pass + + def __str__(self): + return "".format(self.kind, self.options["name"]) + + def from_dict(self, d): + """ + This supersedes ``from_dict`` in derived classes. Derived classes + must inherit from FlatCAMObj first, then from derivatives of Geometry. + + ``self.options`` is only updated, not overwritten. This ensures that + options set by the app do not vanish when reading the objects + from a project file. + + :param d: Dictionary with attributes to set. + :return: None + """ + + for attr in self.ser_attrs: + + if attr == 'options': + self.options.update(d[attr]) + else: + try: + setattr(self, attr, d[attr]) + except KeyError: + log.debug("FlatCAMObj.from_dict() --> KeyError: %s. " + "Means that we are loading an old project that don't" + "have all attributes in the latest FlatCAM." % str(attr)) + pass + + def on_options_change(self, key): + # Update form on programmatically options change + self.set_form_item(key) + + # Set object visibility + if key == 'plot': + self.visible = self.options['plot'] + + self.optionChanged.emit(key) + + def set_ui(self, ui): + self.ui = ui + + self.form_fields = {"name": self.ui.name_entry} + + assert isinstance(self.ui, ObjectUI) + self.ui.name_entry.returnPressed.connect(self.on_name_activate) + + try: + # it will raise an exception for those FlatCAM objects that do not build UI with the common elements + self.ui.offset_button.clicked.connect(self.on_offset_button_click) + except (TypeError, AttributeError): + pass + + try: + self.ui.scale_button.clicked.connect(self.on_scale_button_click) + except (TypeError, AttributeError): + pass + + try: + self.ui.offsetvector_entry.returnPressed.connect(self.on_offset_button_click) + except (TypeError, AttributeError): + pass + + # Creates problems on focusOut + try: + self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) + except (TypeError, AttributeError): + pass + + # self.ui.skew_button.clicked.connect(self.on_skew_button_click) + + def build_ui(self): + """ + Sets up the UI/form for this object. Show the UI + in the App. + + :return: None + :rtype: None + """ + + self.muted_ui = True + log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()") + + try: + # HACK: disconnect the scale entry signal since on focus out event will trigger an undesired scale() + # it seems that the takewidget() does generate a focus out event for the QDoubleSpinbox ... + # and reconnect after the takeWidget() is done + # self.ui.scale_entry.returnPressed.disconnect(self.on_scale_button_click) + self.app.ui.selected_scroll_area.takeWidget() + # self.ui.scale_entry.returnPressed.connect(self.on_scale_button_click) + except Exception as e: + self.app.log.debug("FlatCAMObj.build_ui() --> Nothing to remove: %s" % str(e)) + + self.app.ui.selected_scroll_area.setWidget(self.ui) + # self.ui.setMinimumWidth(100) + # self.ui.setMaximumWidth(self.app.ui.selected_tab.sizeHint().width()) + + self.muted_ui = False + + def on_name_activate(self, silent=None): + old_name = copy(self.options["name"]) + new_name = self.ui.name_entry.get_value() + + 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) + except Exception: + log.debug("on_name_activate() --> Could not remove the old object name from auto-completer model list") + + self.options["name"] = self.ui.name_entry.get_value() + self.default_data["name"] = self.ui.name_entry.get_value() + self.app.collection.update_view() + if silent: + self.app.inform.emit('[success] %s: %s %s: %s' % ( + _("Name changed from"), str(old_name), _("to"), str(new_name) + ) + ) + + def on_offset_button_click(self): + self.app.defaults.report_usage("obj_on_offset_button") + + self.read_form() + vector_val = self.ui.offsetvector_entry.get_value() + + def worker_task(): + with self.app.proc_container.new(_("Offsetting...")): + self.offset(vector_val) + self.app.proc_container.update_view_text('') + with self.app.proc_container.new('%s...' % _("Plotting")): + self.plot() + self.app.object_changed.emit(self) + + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def on_scale_button_click(self): + self.read_form() + try: + factor = float(eval(self.ui.scale_entry.get_value())) + except Exception as e: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed.")) + log.debug("FlatCAMObj.on_scale_button_click() -- %s" % str(e)) + return + + if type(factor) != float: + self.app.inform.emit('[ERROR_NOTCL] %s' % _("Scaling could not be executed.")) + + # if factor is 1.0 do nothing, there is no point in scaling with a factor of 1.0 + if factor == 1.0: + self.app.inform.emit('[success] %s' % _("Scale done.")) + return + + log.debug("FlatCAMObj.on_scale_button_click()") + + def worker_task(): + with self.app.proc_container.new(_("Scaling...")): + self.scale(factor) + self.app.inform.emit('[success] %s' % _("Scale done.")) + + self.app.proc_container.update_view_text('') + with self.app.proc_container.new('%s...' % _("Plotting")): + self.plot() + self.app.object_changed.emit(self) + + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def on_skew_button_click(self): + self.app.defaults.report_usage("obj_on_skew_button") + self.read_form() + x_angle = self.ui.xangle_entry.get_value() + y_angle = self.ui.yangle_entry.get_value() + + def worker_task(): + with self.app.proc_container.new(_("Skewing...")): + self.skew(x_angle, y_angle) + self.app.proc_container.update_view_text('') + with self.app.proc_container.new('%s...' % _("Plotting")): + self.plot() + self.app.object_changed.emit(self) + + self.app.worker_task.emit({'fcn': worker_task, 'params': []}) + + def to_form(self): + """ + Copies options to the UI form. + + :return: None + """ + log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.to_form()") + for option in self.options: + try: + self.set_form_item(option) + except Exception: + self.app.log.warning("Unexpected error:", sys.exc_info()) + + def read_form(self): + """ + Reads form into ``self.options``. + + :return: None + :rtype: None + """ + log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.read_form()") + for option in self.options: + try: + self.read_form_item(option) + except Exception: + self.app.log.warning("Unexpected error:", sys.exc_info()) + + def set_form_item(self, option): + """ + Copies the specified option to the UI form. + + :param option: Name of the option (Key in ``self.options``). + :type option: str + :return: None + """ + + try: + self.form_fields[option].set_value(self.options[option]) + except KeyError: + # self.app.log.warn("Tried to set an option or field that does not exist: %s" % option) + pass + + def read_form_item(self, option): + """ + Reads the specified option from the UI form into ``self.options``. + + :param option: Name of the option. + :type option: str + :return: None + """ + try: + self.options[option] = self.form_fields[option].get_value() + except KeyError: + pass + # self.app.log.warning("Failed to read option from field: %s" % option) + + def plot(self, kind=None): + """ + Plot this object (Extend this method to implement the actual plotting). + Call this in descendants before doing the plotting. + + :param kind: Used by only some of the FlatCAM objects + :return: Whether to continue plotting or not depending on the "plot" option. Boolean + """ + log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()") + + if self.deleted: + return False + + self.clear() + return True + + def single_object_plot(self): + def plot_task(): + with self.app.proc_container.new('%s...' % _("Plotting")): + self.plot() + self.app.object_changed.emit(self) + + self.app.worker_task.emit({'fcn': plot_task, 'params': []}) + + def serialize(self): + """ + Returns a representation of the object as a dictionary so + it can be later exported as JSON. Override this method. + + :return: Dictionary representing the object + :rtype: dict + """ + return + + def deserialize(self, obj_dict): + """ + Re-builds an object from its serialized version. + + :param obj_dict: Dictionary representing a FlatCAMObj + :type obj_dict: dict + :return: None + """ + return + + def add_shape(self, **kwargs): + if self.deleted: + raise ObjectDeleted() + else: + key = self.shapes.add(tolerance=self.drawing_tolerance, **kwargs) + return key + + def add_mark_shape(self, apid, **kwargs): + if self.deleted: + raise ObjectDeleted() + else: + key = self.mark_shapes[apid].add(tolerance=self.drawing_tolerance, layer=0, **kwargs) + return key + + def update_filters(self, last_ext, filter_string): + """ + Will modify the filter string that is used when saving a file (a list of file extensions) to have the last + used file extension as the first one in the special string + + :param last_ext: The file extension that was last used to save a file + :param filter_string: A key in self.app.defaults that holds a string with the filter from QFileDialog + used when saving a file + :return: None + """ + + filters = copy(self.app.defaults[filter_string]) + filter_list = filters.split(';;') + filter_list_enum_1 = enumerate(filter_list) + + # search for the last element in the filters which should always be "All Files (*.*)" + last_elem = '' + for elem in list(filter_list_enum_1): + if '(*.*)' in elem[1]: + last_elem = filter_list.pop(elem[0]) + + filter_list_enum = enumerate(filter_list) + for elem in list(filter_list_enum): + if '.' + last_ext in elem[1]: + used_ext = filter_list.pop(elem[0]) + + # sort the extensions back + filter_list.sort(key=lambda x: x.rpartition('.')[2]) + + # add as a first element the last used extension + filter_list.insert(0, used_ext) + # add back the element that should always be the last (All Files) + filter_list.append(last_elem) + + self.app.defaults[filter_string] = ';;'.join(filter_list) + return + + @staticmethod + def poly2rings(poly): + return [poly.exterior] + [interior for interior in poly.interiors] + + @property + def visible(self): + return self.shapes.visible + + @visible.setter + def visible(self, value, threaded=True): + log.debug("FlatCAMObj.visible()") + + def worker_task(app_obj): + self.shapes.visible = value + + if self.app.is_legacy is False: + # Not all object types has annotations + try: + self.annotation.visible = value + except Exception: + pass + + if threaded is False: + worker_task(app_obj=self.app) + else: + self.app.worker_task.emit({'fcn': worker_task, 'params': [self]}) + + @property + def drawing_tolerance(self): + self.units = self.app.defaults['units'].upper() + tol = self._drawing_tolerance if self.units == 'MM' or not self.units else self._drawing_tolerance / 25.4 + return tol + + @drawing_tolerance.setter + def drawing_tolerance(self, value): + self.units = self.app.defaults['units'].upper() + self._drawing_tolerance = value if self.units == 'MM' or not self.units else value / 25.4 + + def clear(self, update=False): + self.shapes.clear(update) + + # Not all object types has annotations + try: + self.annotation.clear(update) + except AttributeError: + pass + + def delete(self): + # Free resources + del self.ui + del self.options + + # Set flag + self.deleted = True \ No newline at end of file diff --git a/flatcamObjects/FlatCAMScript.py b/flatcamObjects/FlatCAMScript.py new file mode 100644 index 00000000..69e6e65f --- /dev/null +++ b/flatcamObjects/FlatCAMScript.py @@ -0,0 +1,231 @@ +# ########################################################## +# FlatCAM: 2D Post-processing for Manufacturing # +# http://flatcam.org # +# Author: Juan Pablo Caram (c) # +# Date: 2/5/2014 # +# MIT Licence # +# ########################################################## + +# ########################################################## +# File modified by: Marius Stanciu # +# ########################################################## + +from flatcamEditors.FlatCAMTextEditor import TextEditor +from flatcamObjects.FlatCAMObj import * +from flatcamGUI.ObjectUI import * + +import tkinter as tk +import sys +from copy import deepcopy + +import gettext +import FlatCAMTranslation as fcTranslate +import builtins + +fcTranslate.apply_language('strings') +if '_' not in builtins.__dict__: + _ = gettext.gettext + + +class ScriptObject(FlatCAMObj): + """ + Represents a TCL script object. + """ + optionChanged = QtCore.pyqtSignal(str) + ui_type = ScriptObjectUI + + def __init__(self, name): + self.decimals = self.app.decimals + + log.debug("Creating a ScriptObject object...") + FlatCAMObj.__init__(self, name) + + self.kind = "script" + + self.options.update({ + "plot": True, + "type": 'Script', + "source_file": '', + }) + + self.units = '' + + self.ser_attrs = ['options', 'kind', 'source_file'] + self.source_file = '' + self.script_code = '' + + self.units_found = self.app.defaults['units'] + + # self.script_editor_tab = TextEditor(app=self.app, plain_text=True) + self.script_editor_tab = TextEditor(app=self.app, plain_text=True) + + def set_ui(self, ui): + """ + Sets the Object UI in Selected Tab for the FlatCAM Script type of object. + :param ui: + :return: + """ + FlatCAMObj.set_ui(self, ui) + log.debug("ScriptObject.set_ui()") + + assert isinstance(self.ui, ScriptObjectUI), \ + "Expected a ScriptObjectUI, got %s" % type(self.ui) + + self.units = self.app.defaults['units'].upper() + self.units_found = self.app.defaults['units'] + + # Fill form fields only on object create + self.to_form() + + # Show/Hide Advanced Options + if self.app.defaults["global_app_level"] == 'b': + self.ui.level.setText(_( + 'Basic' + )) + else: + self.ui.level.setText(_( + 'Advanced' + )) + + # tab_here = False + # # try to not add too many times a tab that it is already installed + # for idx in range(self.app.ui.plot_tab_area.count()): + # if self.app.ui.plot_tab_area.widget(idx).objectName() == self.options['name']: + # tab_here = True + # break + # + # # add the tab if it is not already added + # if tab_here is False: + # self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor")) + # self.script_editor_tab.setObjectName(self.options['name']) + + self.app.ui.plot_tab_area.addTab(self.script_editor_tab, '%s' % _("Script Editor")) + self.script_editor_tab.setObjectName(self.options['name']) + + # first clear previous text in text editor (if any) + # self.script_editor_tab.code_editor.clear() + # self.script_editor_tab.code_editor.setReadOnly(False) + + self.ui.autocomplete_cb.set_value(self.app.defaults['script_autocompleter']) + self.on_autocomplete_changed(state=self.app.defaults['script_autocompleter']) + + self.script_editor_tab.buttonRun.show() + + # Switch plot_area to CNCJob tab + self.app.ui.plot_tab_area.setCurrentWidget(self.script_editor_tab) + + flt = "FlatCAM Scripts (*.FlatScript);;All Files (*.*)" + self.script_editor_tab.buttonOpen.clicked.disconnect() + self.script_editor_tab.buttonOpen.clicked.connect(lambda: self.script_editor_tab.handleOpen(filt=flt)) + self.script_editor_tab.buttonSave.clicked.disconnect() + self.script_editor_tab.buttonSave.clicked.connect(lambda: self.script_editor_tab.handleSaveGCode(filt=flt)) + + self.script_editor_tab.buttonRun.clicked.connect(self.handle_run_code) + self.script_editor_tab.handleTextChanged() + + self.ui.autocomplete_cb.stateChanged.connect(self.on_autocomplete_changed) + + self.ser_attrs = ['options', 'kind', 'source_file'] + + # ---------------------------------------------------- # + # ----------- LOAD THE TEXT SOURCE FILE -------------- # + # ---------------------------------------------------- # + self.app.proc_container.view.set_busy(_("Loading...")) + self.script_editor_tab.t_frame.hide() + + try: + self.script_editor_tab.code_editor.setPlainText(self.source_file) + # for line in self.source_file.splitlines(): + # QtWidgets.QApplication.processEvents() + # self.script_editor_tab.code_editor.append(line) + except Exception as e: + log.debug("ScriptObject.set_ui() --> %s" % str(e)) + + self.script_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.End) + self.script_editor_tab.t_frame.show() + + self.app.proc_container.view.set_idle() + self.build_ui() + + def build_ui(self): + FlatCAMObj.build_ui(self) + + def handle_run_code(self): + # trying to run a Tcl command without having the Shell open will create some warnings because the Tcl Shell + # tries to print on a hidden widget, therefore show the dock if hidden + if self.app.ui.shell_dock.isHidden(): + self.app.ui.shell_dock.show() + + self.script_code = deepcopy(self.script_editor_tab.code_editor.toPlainText()) + + old_line = '' + for tcl_command_line in self.script_code.splitlines(): + # do not process lines starting with '#' = comment and empty lines + if not tcl_command_line.startswith('#') and tcl_command_line != '': + # id FlatCAM is run in Windows then replace all the slashes with + # the UNIX style slash that TCL understands + if sys.platform == 'win32': + if "open" in tcl_command_line: + tcl_command_line = tcl_command_line.replace('\\', '/') + + if old_line != '': + new_command = old_line + tcl_command_line + '\n' + else: + new_command = tcl_command_line + + # execute the actual Tcl command + try: + self.app.shell.open_processing() # Disables input box. + + result = self.app.shell.tcl.eval(str(new_command)) + if result != 'None': + self.app.shell.append_output(result + '\n') + + old_line = '' + except tk.TclError: + old_line = old_line + tcl_command_line + '\n' + except Exception as e: + log.debug("ScriptObject.handleRunCode() --> %s" % str(e)) + + if old_line != '': + # it means that the script finished with an error + result = self.app.shell.tcl.eval("set errorInfo") + log.error("Exec command Exception: %s\n" % result) + self.app.shell.append_error('ERROR: %s\n '% result) + + self.app.shell.close_processing() + + def on_autocomplete_changed(self, state): + if state: + self.script_editor_tab.code_editor.completer_enable = True + else: + self.script_editor_tab.code_editor.completer_enable = False + + def to_dict(self): + """ + Returns a representation of the object as a dictionary. + Attributes to include are listed in ``self.ser_attrs``. + + :return: A dictionary-encoded copy of the object. + :rtype: dict + """ + d = {} + for attr in self.ser_attrs: + d[attr] = getattr(self, attr) + return d + + def from_dict(self, d): + """ + Sets object's attributes from a dictionary. + Attributes to include are listed in ``self.ser_attrs``. + This method will look only for only and all the + attributes in ``self.ser_attrs``. They must all + be present. Use only for deserializing saved + objects. + + :param d: Dictionary of attributes to set in the object. + :type d: dict + :return: None + """ + for attr in self.ser_attrs: + setattr(self, attr, d[attr]) diff --git a/ObjectCollection.py b/flatcamObjects/ObjectCollection.py similarity index 92% rename from ObjectCollection.py rename to flatcamObjects/ObjectCollection.py index 1108683a..ee1cf933 100644 --- a/ObjectCollection.py +++ b/flatcamObjects/ObjectCollection.py @@ -16,14 +16,18 @@ from PyQt5.QtCore import Qt, QSettings from PyQt5.QtGui import QColor # from PyQt5.QtCore import QModelIndex -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon, FlatCAMCNCjob, FlatCAMDocument, FlatCAMScript, \ - FlatCAMObj +from flatcamObjects.FlatCAMObj import FlatCAMObj +from flatcamObjects.FlatCAMCNCJob import CNCJobObject +from flatcamObjects.FlatCAMDocument import DocumentObject +from flatcamObjects.FlatCAMExcellon import ExcellonObject +from flatcamObjects.FlatCAMGeometry import GeometryObject +from flatcamObjects.FlatCAMGerber import GerberObject +from flatcamObjects.FlatCAMScript import ScriptObject + import inspect # TODO: Remove -import FlatCAMApp import re import logging -import collections from copy import deepcopy from numpy import Inf @@ -234,21 +238,21 @@ class ObjectCollection(QtCore.QAbstractItemModel): ] classdict = { - "gerber": FlatCAMGerber, - "excellon": FlatCAMExcellon, - "cncjob": FlatCAMCNCjob, - "geometry": FlatCAMGeometry, - "script": FlatCAMScript, - "document": FlatCAMDocument + "gerber": GerberObject, + "excellon": ExcellonObject, + "cncjob": CNCJobObject, + "geometry": GeometryObject, + "script": ScriptObject, + "document": DocumentObject } icon_files = { - "gerber": "share/flatcam_icon16.png", - "excellon": "share/drill16.png", - "cncjob": "share/cnc16.png", - "geometry": "share/geometry16.png", - "script": "share/script_new16.png", - "document": "share/notes16_1.png" + "gerber": "assets/resources/flatcam_icon16.png", + "excellon": "assets/resources/drill16.png", + "cncjob": "assets/resources/cnc16.png", + "geometry": "assets/resources/geometry16.png", + "script": "assets/resources/script_new16.png", + "document": "assets/resources/notes16_1.png" } # will emit the name of the object that was just selected @@ -268,7 +272,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.icons = {} for kind in ObjectCollection.icon_files: self.icons[kind] = QtGui.QPixmap( - ObjectCollection.icon_files[kind].replace('share', self.app.resource_location)) + ObjectCollection.icon_files[kind].replace('assets/resources', self.app.resource_location)) # Create root tree view item self.root_item = TreeItem(["root"]) @@ -332,7 +336,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.update_list_signal.connect(self.on_update_list_signal) def promise(self, obj_name): - FlatCAMApp.App.log.debug("Object %s has been promised." % obj_name) + log.debug("Object %s has been promised." % obj_name) self.promises.add(obj_name) def has_promises(self): @@ -349,7 +353,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): return len(self.plot_promises) > 0 def on_mouse_down(self, event): - FlatCAMApp.App.log.debug("Mouse button pressed on list") + log.debug("Mouse button pressed on list") def on_menu_request(self, pos): @@ -373,17 +377,17 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.app.ui.menuprojectcolor.setEnabled(False) for obj in self.get_selected(): - if type(obj) == FlatCAMGerber or type(obj) == FlatCAMExcellon: + if type(obj) == GerberObject or type(obj) == ExcellonObject: self.app.ui.menuprojectcolor.setEnabled(True) - if type(obj) != FlatCAMGeometry: + if type(obj) != GeometryObject: self.app.ui.menuprojectgeneratecnc.setVisible(False) - if type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMGerber: + if type(obj) != GeometryObject and type(obj) != ExcellonObject and type(obj) != GerberObject: self.app.ui.menuprojectedit.setVisible(False) - if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob: + if type(obj) != GerberObject and type(obj) != ExcellonObject and type(obj) != CNCJobObject: self.app.ui.menuprojectviewsource.setVisible(False) - if type(obj) != FlatCAMGerber and type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and \ - type(obj) != FlatCAMCNCjob: + if type(obj) != GerberObject and type(obj) != GeometryObject and type(obj) != ExcellonObject and \ + type(obj) != CNCJobObject: # meaning for Scripts and for Document type of FlatCAM object self.app.ui.menuprojectenable.setVisible(False) self.app.ui.menuprojectdisable.setVisible(False) @@ -532,21 +536,21 @@ class ObjectCollection(QtCore.QAbstractItemModel): # return QtWidgets.QAbstractItemModel.flags(self, index) def append(self, obj, active=False, to_index=None): - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()") + log.debug(str(inspect.stack()[1][3]) + " --> OC.append()") name = obj.options["name"] # Check promises and clear if exists if name in self.promises: self.promises.remove(name) - # FlatCAMApp.App.log.debug("Promised object %s became available." % name) - # FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises)) + # log.debug("Promised object %s became available." % name) + # log.debug("%d promised objects remaining." % len(self.promises)) # Prevent same name while name in self.get_names(): # ## Create a new name # Ends with number? - FlatCAMApp.App.log.debug("new_object(): Object name (%s) exists, changing." % name) + log.debug("new_object(): Object name (%s) exists, changing." % name) match = re.search(r'(.*[^\d])?(\d+)$', name) if match: # Yes: Increment the number! base = match.group(1) or '' @@ -596,7 +600,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): :rtype: list """ - # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()") + # log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()") return [x.options['name'] for x in self.get_list()] def get_bounds(self): @@ -606,7 +610,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): :return: [xmin, ymin, xmax, ymax] :rtype: list """ - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()") + log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()") # TODO: Move the operation out of here. @@ -624,7 +628,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): xmax = max([xmax, gxmax]) ymax = max([ymax, gymax]) except Exception as e: - FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e)) + log.warning("DEV WARNING: Tried to get bounds of empty geometry. %s" % str(e)) return [xmin, ymin, xmax, ymax] @@ -638,7 +642,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): :return: The requested object or None if no such object. :rtype: FlatCAMObj or None """ - # FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()") + # log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()") if isCaseSensitive is None or isCaseSensitive is True: for obj in self.get_list(): @@ -760,7 +764,7 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.app.all_objects_list = self.get_list() def delete_all(self): - FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()") + log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()") self.app.object_status_changed.emit(None, 'delete_all', '') @@ -897,8 +901,15 @@ class ObjectCollection(QtCore.QAbstractItemModel): self.set_inactive(name) def on_list_selection_change(self, current, previous): - # FlatCAMApp.App.log.debug("on_list_selection_change()") - # FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous))) + """ + + :param current: Current selected item + :param previous: Previously selected item + :return: + """ + + # log.debug("on_list_selection_change()") + # log.debug("Current: %s, Previous %s" % (str(current), str(previous))) try: obj = current.indexes()[0].internalPointer().obj @@ -942,12 +953,12 @@ class ObjectCollection(QtCore.QAbstractItemModel): ) except IndexError: self.item_selected.emit('none') - # FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)") + # log.debug("on_list_selection_change(): Index Error (Nothing selected?)") self.app.inform.emit('') try: self.app.ui.selected_scroll_area.takeWidget() except Exception as e: - FlatCAMApp.App.log.debug("Nothing to remove. %s" % str(e)) + log.debug("Nothing to remove. %s" % str(e)) self.app.setup_component_editor() return diff --git a/flatcamObjects/__init__.py b/flatcamObjects/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flatcamParsers/ParseExcellon.py b/flatcamParsers/ParseExcellon.py index 406bbb16..ab52e44a 100644 --- a/flatcamParsers/ParseExcellon.py +++ b/flatcamParsers/ParseExcellon.py @@ -7,7 +7,6 @@ # ########################################################## ## from camlib import Geometry -import FlatCAMApp import shapely.affinity as affinity from shapely.geometry import Point, LineString @@ -19,6 +18,7 @@ import traceback from copy import deepcopy import FlatCAMTranslation as fcTranslate +from FlatCAMCommon import GracefulException as grace import gettext import builtins @@ -86,6 +86,7 @@ class Excellon(Geometry): :return: Excellon object. :rtype: Excellon """ + self.decimals = self.app.decimals if geo_steps_per_circle is None: @@ -241,12 +242,12 @@ class Excellon(Geometry): def parse_file(self, filename=None, file_obj=None): """ - Reads the specified file as array of lines as - passes it to ``parse_lines()``. + Reads the specified file as array of lines as passes it to ``parse_lines()``. - :param filename: The file to be read and parsed. - :type filename: str - :return: None + :param filename: The file to be read and parsed. + :param file_obj: + :type filename: str + :return: None """ if file_obj: estr = file_obj @@ -298,7 +299,7 @@ class Excellon(Geometry): for eline in elines: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace line_num += 1 # log.debug("%3d %s" % (line_num, str(eline))) @@ -526,7 +527,7 @@ class Excellon(Geometry): slot_dia = 0.05 try: slot_dia = float(self.tools[current_tool]['C']) - except Exception as e: + except Exception: pass log.debug( 'Milling/Drilling slot with tool %s, diam=%f' % ( @@ -596,7 +597,7 @@ class Excellon(Geometry): slot_dia = 0.05 try: slot_dia = float(self.tools[current_tool]['C']) - except Exception as e: + except Exception: pass log.debug( 'Milling/Drilling slot with tool %s, diam=%f' % ( @@ -893,9 +894,8 @@ class Excellon(Geometry): log.info("Zeros: %s, Units %s." % (self.zeros, self.units)) except Exception: log.error("Excellon PARSING FAILED. Line %d: %s" % (line_num, eline)) - msg = '[ERROR_NOTCL] %s' % \ - _("An internal error has ocurred. See shell.\n") - msg += ('{e_code} {tx} {l_nr}: {line}\n').format( + msg = '[ERROR_NOTCL] %s' % _("An internal error has occurred. See shell.\n") + msg += '{e_code} {tx} {l_nr}: {line}\n'.format( e_code='[ERROR]', tx=_("Excellon Parser error.\nParsing Failed. Line"), l_nr=line_num, @@ -1010,13 +1010,13 @@ class Excellon(Geometry): "Excellon geometry creation failed due of ERROR: %s" % str(e)) return "fail" - def bounds(self): + def bounds(self, flatten=None): """ Returns coordinates of rectangular bounds of Excellon geometry: (xmin, ymin, xmax, ymax). + + :param flatten: No used """ - # fixed issue of getting bounds only for one level lists of objects - # now it can get bounds for nested lists of objects log.debug("flatcamParsers.ParseExcellon.Excellon.bounds()") @@ -1056,11 +1056,11 @@ class Excellon(Geometry): maxy_list = [] for tool in self.tools: - minx, miny, maxx, maxy = bounds_rec(self.tools[tool]['solid_geometry']) - minx_list.append(minx) - miny_list.append(miny) - maxx_list.append(maxx) - maxy_list.append(maxy) + eminx, eminy, emaxx, emaxy = bounds_rec(self.tools[tool]['solid_geometry']) + minx_list.append(eminx) + miny_list.append(eminy) + maxx_list.append(emaxx) + maxy_list.append(emaxy) return min(minx_list), min(miny_list), max(maxx_list), max(maxy_list) @@ -1075,8 +1075,9 @@ class Excellon(Geometry): Kind of convolute way to make the conversion and it is based on the assumption that the Excellon file will have detected the units before the tools are parsed and stored in self.tools - :param units: - :type str: IN or MM + + :param units: 'IN' or 'MM'. String + :return: """ @@ -1109,12 +1110,13 @@ class Excellon(Geometry): Scales geometry on the XY plane in the object by a given factor. Tool sizes, feedrates an Z-plane dimensions are untouched. - :param xfactor: Number by which to scale the object. - :type xfactor: float - :param yfactor: Number by which to scale the object. - :type yfactor: float - :return: None - :rtype: NOne + :param xfactor: Number by which to scale the object. + :type xfactor: float + :param yfactor: Number by which to scale the object. + :type yfactor: float + :param point: Origin point for scale + :return: None + :rtype: None """ log.debug("flatcamParsers.ParseExcellon.Excellon.scale()") @@ -1145,8 +1147,7 @@ class Excellon(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.drills: - self.geo_len += 1 + self.geo_len = len(self.drills) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -1190,12 +1191,12 @@ class Excellon(Geometry): return def offset_geom(obj): - if type(obj) is list: + try: new_obj = [] - for g in obj: - new_obj.append(offset_geom(g)) + for geo in obj: + new_obj.append(offset_geom(geo)) return new_obj - else: + except TypeError: try: return affinity.translate(obj, xoff=dx, yoff=dy) except AttributeError: @@ -1204,8 +1205,7 @@ class Excellon(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.drills: - self.geo_len += 1 + self.geo_len = len(self.drills) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -1237,11 +1237,11 @@ class Excellon(Geometry): def mirror(self, axis, point): """ - :param axis: "X" or "Y" indicates around which axis to mirror. - :type axis: str - :param point: [x, y] point belonging to the mirror axis. - :type point: list - :return: None + :param axis: "X" or "Y" indicates around which axis to mirror. + :type axis: str + :param point: [x, y] point belonging to the mirror axis. + :type point: list + :return: None """ log.debug("flatcamParsers.ParseExcellon.Excellon.mirror()") @@ -1249,12 +1249,12 @@ class Excellon(Geometry): xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis] def mirror_geom(obj): - if type(obj) is list: + try: new_obj = [] - for g in obj: - new_obj.append(mirror_geom(g)) + for geo in obj: + new_obj.append(mirror_geom(geo)) return new_obj - else: + except TypeError: try: return affinity.scale(obj, xscale, yscale, origin=(px, py)) except AttributeError: @@ -1265,8 +1265,7 @@ class Excellon(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.drills: - self.geo_len += 1 + self.geo_len = len(self.drills) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -1300,12 +1299,12 @@ class Excellon(Geometry): Shear/Skew the geometries of an object by angles along x and y dimensions. Tool sizes, feedrates an Z-plane dimensions are untouched. - Parameters - ---------- - xs, ys : float, float + :param angle_x: + :param angle_y: The shear angle(s) for the x and y axes respectively. These can be specified in either degrees (default) or radians by setting use_radians=True. + :param point: Origin point for Skew See shapely manual for more information: http://toblerity.org/shapely/manual.html#affine-transformations @@ -1322,12 +1321,12 @@ class Excellon(Geometry): return def skew_geom(obj): - if type(obj) is list: + try: new_obj = [] for g in obj: new_obj.append(skew_geom(g)) return new_obj - else: + except TypeError: try: return affinity.skew(obj, angle_x, angle_y, origin=(px, py)) except AttributeError: @@ -1336,8 +1335,7 @@ class Excellon(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.drills: - self.geo_len += 1 + self.geo_len = len(self.drills) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -1393,9 +1391,10 @@ class Excellon(Geometry): def rotate(self, angle, point=None): """ Rotate the geometry of an object by an angle around the 'point' coordinates + :param angle: - :param point: tuple of coordinates (x, y) - :return: + :param point: tuple of coordinates (x, y) + :return: None """ log.debug("flatcamParsers.ParseExcellon.Excellon.rotate()") @@ -1423,8 +1422,7 @@ class Excellon(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for g in self.drills: - self.geo_len += 1 + self.geo_len = len(self.drills) except TypeError: self.geo_len = 1 self.old_disp_number = 0 @@ -1476,9 +1474,10 @@ class Excellon(Geometry): def buffer(self, distance, join, factor): """ - :param distance: if 'factor' is True then distance is the factor - :param factor: True or False (None) - :return: + :param distance: if 'factor' is True then distance is the factor + :param factor: True or False (None) + :param join: The type of line joint used by the shapely buffer method: round, square, bevel + :return: None """ log.debug("flatcamParsers.ParseExcellon.Excellon.buffer()") @@ -1486,12 +1485,12 @@ class Excellon(Geometry): return def buffer_geom(obj): - if type(obj) is list: + try: new_obj = [] for g in obj: new_obj.append(buffer_geom(g)) return new_obj - else: + except TypeError: try: if factor is None: return obj.buffer(distance, resolution=self.geo_steps_per_circle) diff --git a/flatcamParsers/ParseGerber.py b/flatcamParsers/ParseGerber.py index bad7b97d..57ba4d49 100644 --- a/flatcamParsers/ParseGerber.py +++ b/flatcamParsers/ParseGerber.py @@ -1,6 +1,5 @@ from PyQt5 import QtWidgets from camlib import Geometry, arc, arc_angle, ApertureMacro -import FlatCAMApp import numpy as np import re @@ -9,15 +8,16 @@ import traceback from copy import deepcopy import sys -from shapely.ops import cascaded_union, unary_union -from shapely.geometry import Polygon, MultiPolygon, LineString, Point +from shapely.ops import cascaded_union +from shapely.affinity import scale, translate import shapely.affinity as affinity -from shapely.geometry import box as shply_box +from shapely.geometry import box as shply_box, Polygon, LineString, Point, MultiPolygon from lxml import etree as ET -from flatcamParsers.ParseSVG import * - +from flatcamParsers.ParseSVG import svgparselength, getsvggeo +from FlatCAMCommon import GracefulException as grace import FlatCAMTranslation as fcTranslate + import gettext import builtins @@ -255,7 +255,7 @@ class Gerber(Geometry): """ if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # Found some Gerber with a leading zero in the aperture id and the # referenced it without the zero, so this is a hack to handle that. @@ -403,7 +403,7 @@ class Gerber(Geometry): # Absolute or Relative/Incremental coordinates # Not implemented - absolute = True + # absolute = True # How to interpret circular interpolation: SINGLE or MULTI quadrant_mode = None @@ -428,7 +428,7 @@ class Gerber(Geometry): for gline in glines: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace line_num += 1 self.source_file += gline + '\n' @@ -986,7 +986,7 @@ class Gerber(Geometry): if 'geometry' not in self.apertures[current_aperture]: self.apertures[current_aperture]['geometry'] = [] self.apertures[current_aperture]['geometry'].append(deepcopy(geo_dict)) - except Exception as e: + except Exception: pass last_path_aperture = current_aperture # we do this for the case that a region is done without having defined any aperture @@ -1229,25 +1229,25 @@ class Gerber(Geometry): try: circular_x = parse_gerber_number(circular_x, self.int_digits, self.frac_digits, self.gerber_zeros) - except Exception as e: + except Exception: circular_x = current_x try: circular_y = parse_gerber_number(circular_y, self.int_digits, self.frac_digits, self.gerber_zeros) - except Exception as e: + except Exception: circular_y = current_y # According to Gerber specification i and j are not modal, which means that when i or j are missing, # they are to be interpreted as being zero try: i = parse_gerber_number(i, self.int_digits, self.frac_digits, self.gerber_zeros) - except Exception as e: + except Exception: i = 0 try: j = parse_gerber_number(j, self.int_digits, self.frac_digits, self.gerber_zeros) - except Exception as e: + except Exception: j = 0 if quadrant_mode is None: @@ -1668,13 +1668,14 @@ class Gerber(Geometry): bbox = bbox.envelope return bbox - def bounds(self): + def bounds(self, flatten=None): """ Returns coordinates of rectangular bounds of Gerber geometry: (xmin, ymin, xmax, ymax). + + :param flatten: Not used, it is here for compatibility with base class method + :return: None """ - # fixed issue of getting bounds only for one level lists of objects - # now it can get bounds for nested lists of objects log.debug("parseGerber.Gerber.bounds()") @@ -1833,7 +1834,7 @@ class Gerber(Geometry): new_el = {} new_el['solid'] = pol new_el['follow'] = pol.exterior - self.apertures['0']['geometry'].append(deepcopy(new_el)) + self.apertures['0']['geometry'].append(new_el) def scale(self, xfactor, yfactor=None, point=None): """ @@ -1999,8 +2000,7 @@ class Gerber(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for __ in self.solid_geometry: - self.geo_len += 1 + self.geo_len = len(self.solid_geometry) except TypeError: self.geo_len = 1 @@ -2078,8 +2078,7 @@ class Gerber(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for __ in self.solid_geometry: - self.geo_len += 1 + self.geo_len = len(self.solid_geometry) except TypeError: self.geo_len = 1 @@ -2217,8 +2216,7 @@ class Gerber(Geometry): # variables to display the percentage of work done self.geo_len = 0 try: - for __ in self.solid_geometry: - self.geo_len += 1 + self.geo_len = len(self.solid_geometry) except TypeError: self.geo_len = 1 @@ -2266,8 +2264,9 @@ class Gerber(Geometry): def buffer(self, distance, join, factor=None): """ - :param distance: if 'factor' is True then distance is the factor - :param factor: True or False (None) + :param distance: If 'factor' is True then distance is the factor + :param join: The type of joining used by the Shapely buffer method. Can be: round, square and bevel + :param factor: True or False (None) :return: """ log.debug("parseGerber.Gerber.buffer()") diff --git a/flatcamParsers/ParseHPGL2.py b/flatcamParsers/ParseHPGL2.py index ead2a67c..b612e7bb 100644 --- a/flatcamParsers/ParseHPGL2.py +++ b/flatcamParsers/ParseHPGL2.py @@ -7,7 +7,6 @@ # ############################################################ from camlib import arc, three_point_circle -import FlatCAMApp import numpy as np import re @@ -19,6 +18,7 @@ import sys from shapely.ops import unary_union from shapely.geometry import LineString, Point +from FlatCAMCommon import GracefulException as grace import FlatCAMTranslation as fcTranslate import gettext import builtins @@ -180,7 +180,7 @@ class HPGL2: for gline in glines: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace line_num += 1 self.source_file += gline + '\n' @@ -304,7 +304,7 @@ class HPGL2: (_("Coordinates missing, line ignored"), str(gline))) if current_x is not None and current_y is not None: - radius = match.group(1) + radius = float(match.group(1)) geo = Point((current_x, current_y)).buffer(radius, int(self.steps_per_circle)) geo_line = geo.exterior self.tools[current_tool]['solid_geometry'].append(geo_line) diff --git a/flatcamParsers/ParseSVG.py b/flatcamParsers/ParseSVG.py index d5634944..a36e4770 100644 --- a/flatcamParsers/ParseSVG.py +++ b/flatcamParsers/ParseSVG.py @@ -21,7 +21,8 @@ # import xml.etree.ElementTree as ET from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path -from svg.path.path import Move, Close +from svg.path.path import Move +from svg.path.path import Close from shapely.geometry import LineString, LinearRing, MultiLineString from shapely.affinity import skew, affine_transform, rotate import numpy as np diff --git a/flatcamTools/ToolAlignObjects.py b/flatcamTools/ToolAlignObjects.py index efb7e1b3..33b9611c 100644 --- a/flatcamTools/ToolAlignObjects.py +++ b/flatcamTools/ToolAlignObjects.py @@ -217,7 +217,7 @@ class AlignObjects(FlatCAMTool): self.aligned_old_line_color = None def run(self, toggle=True): - self.app.report_usage("ToolAlignObjects()") + self.app.defaults.report_usage("ToolAlignObjects()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -382,7 +382,7 @@ class AlignObjects(FlatCAMTool): def check_points(self): if len(self.clicked_points) == 1: self.app.inform.emit('%s: %s. %s' % ( - _("First Point"), _("Click on the DESTINATION point."), _(" Or right click to cancel."))) + _("First Point"), _("Click on the DESTINATION point."), _("Or right click to cancel."))) self.target_obj = self.aligner_obj self.reset_color() self.set_color() @@ -397,14 +397,14 @@ class AlignObjects(FlatCAMTool): return else: self.app.inform.emit('%s: %s. %s' % ( - _("Second Point"), _("Click on the START point."), _(" Or right click to cancel."))) + _("Second Point"), _("Click on the START point."), _("Or right click to cancel."))) self.target_obj = self.aligned_obj self.reset_color() self.set_color() if len(self.clicked_points) == 3: self.app.inform.emit('%s: %s. %s' % ( - _("Second Point"), _("Click on the DESTINATION point."), _(" Or right click to cancel."))) + _("Second Point"), _("Click on the DESTINATION point."), _("Or right click to cancel."))) self.target_obj = self.aligner_obj self.reset_color() self.set_color() diff --git a/flatcamTools/ToolCalculators.py b/flatcamTools/ToolCalculators.py index 5b57be84..5a8874a7 100644 --- a/flatcamTools/ToolCalculators.py +++ b/flatcamTools/ToolCalculators.py @@ -271,7 +271,7 @@ class ToolCalculator(FlatCAMTool): self.reset_button.clicked.connect(self.set_tool_ui) def run(self, toggle=True): - self.app.report_usage("ToolCalculators()") + self.app.defaults.report_usage("ToolCalculators()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolCalibration.py b/flatcamTools/ToolCalibration.py index e1e884aa..98b487cf 100644 --- a/flatcamTools/ToolCalibration.py +++ b/flatcamTools/ToolCalibration.py @@ -731,7 +731,7 @@ class ToolCalibration(FlatCAMTool): self.reset_button.clicked.connect(self.set_tool_ui) def run(self, toggle=True): - self.app.report_usage("ToolCalibration()") + self.app.defaults.report_usage("ToolCalibration()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolCopperThieving.py b/flatcamTools/ToolCopperThieving.py index 99c2a443..d6fd310a 100644 --- a/flatcamTools/ToolCopperThieving.py +++ b/flatcamTools/ToolCopperThieving.py @@ -7,10 +7,9 @@ from PyQt5 import QtWidgets, QtCore -import FlatCAMApp +from FlatCAMCommon import GracefulException as grace from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet, FCEntry, FCComboBox -from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon import shapely.geometry.base as base from shapely.ops import cascaded_union, unary_union @@ -541,7 +540,7 @@ class ToolCopperThieving(FlatCAMTool): self.work_finished.connect(self.on_new_pattern_plating_object) def run(self, toggle=True): - self.app.report_usage("ToolCopperThieving()") + self.app.defaults.report_usage("ToolCopperThieving()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -994,7 +993,7 @@ class ToolCopperThieving(FlatCAMTool): for pol in app_obj.grb_object.solid_geometry: if app_obj.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace clearance_geometry.append( pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4)) @@ -1073,7 +1072,7 @@ class ToolCopperThieving(FlatCAMTool): for poly in working_obj: if app_obj.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)) except TypeError: geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)) @@ -1082,7 +1081,7 @@ class ToolCopperThieving(FlatCAMTool): else: # ref_selected == 'box' geo_n = working_obj.solid_geometry - if isinstance(working_obj, FlatCAMGeometry): + if working_obj.kind == 'geometry': try: __ = iter(geo_n) except Exception as e: @@ -1093,11 +1092,11 @@ class ToolCopperThieving(FlatCAMTool): for poly in geo_n: if app_obj.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) - elif isinstance(working_obj, FlatCAMGerber): + elif working_obj.kind == 'gerber': geo_n = cascaded_union(geo_n).convex_hull bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n) bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre) @@ -1192,7 +1191,7 @@ class ToolCopperThieving(FlatCAMTool): for pol in app_obj.grb_object.solid_geometry: if app_obj.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace outline_geometry.append( pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4)) diff --git a/flatcamTools/ToolCutOut.py b/flatcamTools/ToolCutOut.py index 86f750fb..99f4b218 100644 --- a/flatcamTools/ToolCutOut.py +++ b/flatcamTools/ToolCutOut.py @@ -8,7 +8,6 @@ from PyQt5 import QtWidgets, QtGui, QtCore from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, RadioSet, FCComboBox, OptionalInputSection, FCButton -from FlatCAMObj import FlatCAMGerber from shapely.geometry import box, MultiPolygon, Polygon, LineString, LinearRing from shapely.ops import cascaded_union, unary_union @@ -270,7 +269,7 @@ class CutOut(FlatCAMTool): form_layout_2.addRow(gaps_label, self.gaps) # Buttons - self.ff_cutout_object_btn = QtWidgets.QPushButton(_("Generate Freeform Geometry")) + self.ff_cutout_object_btn = FCButton(_("Generate Freeform Geometry")) self.ff_cutout_object_btn.setToolTip( _("Cutout the selected object.\n" "The cutout shape can be of any shape.\n" @@ -284,7 +283,7 @@ class CutOut(FlatCAMTool): """) grid0.addWidget(self.ff_cutout_object_btn, 20, 0, 1, 2) - self.rect_cutout_object_btn = QtWidgets.QPushButton(_("Generate Rectangular Geometry")) + self.rect_cutout_object_btn = FCButton(_("Generate Rectangular Geometry")) self.rect_cutout_object_btn.setToolTip( _("Cutout the selected object.\n" "The resulting cutout shape is\n" @@ -335,7 +334,7 @@ class CutOut(FlatCAMTool): # form_layout_3.addRow(e_lab_0) - self.man_geo_creation_btn = QtWidgets.QPushButton(_("Generate Manual Geometry")) + self.man_geo_creation_btn = FCButton(_("Generate Manual Geometry")) self.man_geo_creation_btn.setToolTip( _("If the object to be cutout is a Gerber\n" "first create a Geometry that surrounds it,\n" @@ -350,7 +349,7 @@ class CutOut(FlatCAMTool): """) grid0.addWidget(self.man_geo_creation_btn, 24, 0, 1, 2) - self.man_gaps_creation_btn = QtWidgets.QPushButton(_("Manual Add Bridge Gaps")) + self.man_gaps_creation_btn = FCButton(_("Manual Add Bridge Gaps")) self.man_gaps_creation_btn.setToolTip( _("Use the left mouse button (LMB) click\n" "to create a bridge gap to separate the PCB from\n" @@ -369,7 +368,7 @@ class CutOut(FlatCAMTool): self.layout.addStretch() # ## Reset Tool - self.reset_button = QtWidgets.QPushButton(_("Reset Tool")) + self.reset_button = FCButton(_("Reset Tool")) self.reset_button.setToolTip( _("Will reset the tool parameters.") ) @@ -420,7 +419,7 @@ class CutOut(FlatCAMTool): self.obj_combo.obj_type = {"grb": "Gerber", "geo": "Geometry"}[val] def run(self, toggle=True): - self.app.report_usage("ToolCutOut()") + self.app.defaults.report_usage("ToolCutOut()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -525,7 +524,7 @@ class CutOut(FlatCAMTool): def geo_init(geo_obj, app_obj): solid_geo = [] - if isinstance(cutout_obj, FlatCAMGerber): + if cutout_obj.kind == 'gerber': if isinstance(cutout_obj.solid_geometry, list): cutout_obj.solid_geometry = MultiPolygon(cutout_obj.solid_geometry) @@ -542,12 +541,12 @@ class CutOut(FlatCAMTool): def cutout_handler(geom): # Get min and max data for each object as we just cut rectangles across X or Y - xmin, ymin, xmax, ymax = recursive_bounds(geom) + xxmin, yymin, xxmax, yymax = recursive_bounds(geom) - px = 0.5 * (xmin + xmax) + margin - py = 0.5 * (ymin + ymax) + margin - lenx = (xmax - xmin) + (margin * 2) - leny = (ymax - ymin) + (margin * 2) + px = 0.5 * (xxmin + xxmax) + margin + py = 0.5 * (yymin + yymax) + margin + lenx = (xxmax - xxmin) + (margin * 2) + leny = (yymax - yymin) + (margin * 2) proc_geometry = [] if gaps == 'None': @@ -555,41 +554,41 @@ class CutOut(FlatCAMTool): else: if gaps == '8' or gaps == '2LR': geom = self.subtract_poly_from_geo(geom, - xmin - gapsize, # botleft_x + xxmin - gapsize, # botleft_x py - gapsize + leny / 4, # botleft_y - xmax + gapsize, # topright_x + xxmax + gapsize, # topright_x py + gapsize + leny / 4) # topright_y geom = self.subtract_poly_from_geo(geom, - xmin - gapsize, + xxmin - gapsize, py - gapsize - leny / 4, - xmax + gapsize, + xxmax + gapsize, py + gapsize - leny / 4) if gaps == '8' or gaps == '2TB': geom = self.subtract_poly_from_geo(geom, px - gapsize + lenx / 4, - ymin - gapsize, + yymin - gapsize, px + gapsize + lenx / 4, - ymax + gapsize) + yymax + gapsize) geom = self.subtract_poly_from_geo(geom, px - gapsize - lenx / 4, - ymin - gapsize, + yymin - gapsize, px + gapsize - lenx / 4, - ymax + gapsize) + yymax + gapsize) if gaps == '4' or gaps == 'LR': geom = self.subtract_poly_from_geo(geom, - xmin - gapsize, + xxmin - gapsize, py - gapsize, - xmax + gapsize, + xxmax + gapsize, py + gapsize) if gaps == '4' or gaps == 'TB': geom = self.subtract_poly_from_geo(geom, px - gapsize, - ymin - gapsize, + yymin - gapsize, px + gapsize, - ymax + gapsize) + yymax + gapsize) try: for g in geom: @@ -603,7 +602,7 @@ class CutOut(FlatCAMTool): object_geo = unary_union(object_geo) # for geo in object_geo: - if isinstance(cutout_obj, FlatCAMGerber): + if cutout_obj.kind == 'gerber': if isinstance(object_geo, MultiPolygon): x0, y0, x1, y1 = object_geo.bounds object_geo = box(x0, y0, x1, y1) @@ -623,7 +622,7 @@ class CutOut(FlatCAMTool): object_geo = [object_geo] for geom_struct in object_geo: - if isinstance(cutout_obj, FlatCAMGerber): + if cutout_obj.kind == 'gerber': if margin >= 0: geom_struct = (geom_struct.buffer(margin + abs(dia / 2))).exterior else: @@ -775,7 +774,7 @@ class CutOut(FlatCAMTool): # if Gerber create a buffer at a distance # if Geometry then cut through the geometry - if isinstance(cutout_obj, FlatCAMGerber): + if cutout_obj.kind == 'gerber': if margin >= 0: geo = geo.buffer(margin + abs(dia / 2)) else: @@ -909,7 +908,7 @@ class CutOut(FlatCAMTool): "Select one and try again.")) return - if not isinstance(cutout_obj, FlatCAMGerber): + if cutout_obj.kind != 'gerber': self.app.inform.emit('[ERROR_NOTCL] %s' % _("The selected object has to be of Gerber type.\n" "Select a Gerber file and try again.")) @@ -988,11 +987,11 @@ class CutOut(FlatCAMTool): if self.app.is_legacy is False: event_pos = event.pos - event_is_dragging = event.is_dragging + # event_is_dragging = event.is_dragging right_button = 2 else: event_pos = (event.xdata, event.ydata) - event_is_dragging = self.app.plotcanvas.is_dragging + # event_is_dragging = self.app.plotcanvas.is_dragging right_button = 3 try: @@ -1038,11 +1037,11 @@ class CutOut(FlatCAMTool): if self.app.is_legacy is False: event_pos = event.pos event_is_dragging = event.is_dragging - right_button = 2 + # right_button = 2 else: event_pos = (event.xdata, event.ydata) event_is_dragging = self.app.plotcanvas.is_dragging - right_button = 3 + # right_button = 3 try: x = float(event_pos[0]) @@ -1159,13 +1158,17 @@ class CutOut(FlatCAMTool): if '+' in key_string: mod, __, key_text = key_string.rpartition('+') if mod.lower() == 'ctrl': - modifiers = QtCore.Qt.ControlModifier + # modifiers = QtCore.Qt.ControlModifier + pass elif mod.lower() == 'alt': - modifiers = QtCore.Qt.AltModifier + # modifiers = QtCore.Qt.AltModifier + pass elif mod.lower() == 'shift': - modifiers = QtCore.Qt.ShiftModifier + # modifiers = QtCore.Qt.ShiftModifier + pass else: - modifiers = QtCore.Qt.NoModifier + # modifiers = QtCore.Qt.NoModifier + pass key = QtGui.QKeySequence(key_text) # events from Vispy are of type KeyEvent else: @@ -1203,7 +1206,8 @@ class CutOut(FlatCAMTool): geo = self.cutting_geo(pos=(l_x, l_y)) self.draw_utility_geometry(geo=geo) - def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1): + @staticmethod + def subtract_poly_from_geo(solid_geo, x0, y0, x1, y1): """ Subtract polygon made from points from the given object. This only operates on the paths in the original geometry, @@ -1270,8 +1274,9 @@ def flatten(geometry): def recursive_bounds(geometry): """ - Returns coordinates of rectangular bounds - of geometry: (xmin, ymin, xmax, ymax). + + :param geometry: a iterable object that holds geometry + :return: Returns coordinates of rectangular bounds of geometry: (xmin, ymin, xmax, ymax). """ # now it can get bounds for nested lists of objects diff --git a/flatcamTools/ToolDblSided.py b/flatcamTools/ToolDblSided.py index 26aba20c..2aed9a3f 100644 --- a/flatcamTools/ToolDblSided.py +++ b/flatcamTools/ToolDblSided.py @@ -3,7 +3,6 @@ from PyQt5 import QtWidgets, QtCore from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, EvalEntry, FCEntry, FCButton, FCComboBox -from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon, FlatCAMGeometry from numpy import Inf @@ -192,7 +191,7 @@ class DblSidedTool(FlatCAMTool): # Add a reference self.add_point_button = QtWidgets.QPushButton(_("Add")) self.add_point_button.setToolTip( - _("Add the coordinates in format (x, y) through which the mirroring axis \n " + _("Add the coordinates in format (x, y) through which the mirroring axis\n " "selected in 'MIRROR AXIS' pass.\n" "The (x, y) coordinates are captured by pressing SHIFT key\n" "and left mouse button click on canvas or you can enter the coordinates manually.") @@ -515,7 +514,7 @@ class DblSidedTool(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='Alt+D', **kwargs) def run(self, toggle=True): - self.app.report_usage("Tool2Sided()") + self.app.defaults.report_usage("Tool2Sided()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -658,7 +657,7 @@ class DblSidedTool(FlatCAMTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) return - if not isinstance(fcobj, FlatCAMGerber): + if fcobj.kind != 'gerber': self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored.")) return @@ -701,7 +700,7 @@ class DblSidedTool(FlatCAMTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Excellon object loaded ...")) return - if not isinstance(fcobj, FlatCAMExcellon): + if fcobj.kind != 'excellon': self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored.")) return @@ -745,7 +744,7 @@ class DblSidedTool(FlatCAMTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Geometry object loaded ...")) return - if not isinstance(fcobj, FlatCAMGeometry): + if fcobj.kind != 'geometry': self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber, Excellon and Geometry objects can be mirrored.")) return diff --git a/flatcamTools/ToolDistance.py b/flatcamTools/ToolDistance.py index 61c4df42..ea62c64e 100644 --- a/flatcamTools/ToolDistance.py +++ b/flatcamTools/ToolDistance.py @@ -180,7 +180,7 @@ class Distance(FlatCAMTool): self.measure_btn.clicked.connect(self.activate_measure_tool) def run(self, toggle=False): - self.app.report_usage("ToolDistance()") + self.app.defaults.report_usage("ToolDistance()") self.points[:] = [] diff --git a/flatcamTools/ToolDistanceMin.py b/flatcamTools/ToolDistanceMin.py index 1769f8aa..dc4369ea 100644 --- a/flatcamTools/ToolDistanceMin.py +++ b/flatcamTools/ToolDistanceMin.py @@ -136,7 +136,7 @@ class DistanceMin(FlatCAMTool): self.jump_hp_btn.clicked.connect(self.on_jump_to_half_point) def run(self, toggle=False): - self.app.report_usage("ToolDistanceMin()") + self.app.defaults.report_usage("ToolDistanceMin()") if self.app.tool_tab_locked is True: return diff --git a/flatcamTools/ToolExtractDrills.py b/flatcamTools/ToolExtractDrills.py index e3354978..2c127c2b 100644 --- a/flatcamTools/ToolExtractDrills.py +++ b/flatcamTools/ToolExtractDrills.py @@ -366,7 +366,7 @@ class ToolExtractDrills(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='Alt+I', **kwargs) def run(self, toggle=True): - self.app.report_usage("Extract Drills()") + self.app.defaults.report_usage("Extract Drills()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolFiducials.py b/flatcamTools/ToolFiducials.py index cfd3b71f..1b3513d4 100644 --- a/flatcamTools/ToolFiducials.py +++ b/flatcamTools/ToolFiducials.py @@ -367,7 +367,7 @@ class ToolFiducials(FlatCAMTool): self.reset_button.clicked.connect(self.set_tool_ui) def run(self, toggle=True): - self.app.report_usage("ToolFiducials()") + self.app.defaults.report_usage("ToolFiducials()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolFilm.py b/flatcamTools/ToolFilm.py index 68dfadd4..12a3b528 100644 --- a/flatcamTools/ToolFilm.py +++ b/flatcamTools/ToolFilm.py @@ -558,7 +558,7 @@ class Film(FlatCAMTool): }[self.tf_type_obj_combo.get_value()] def run(self, toggle=True): - self.app.report_usage("ToolFilm()") + self.app.defaults.report_usage("ToolFilm()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -933,7 +933,7 @@ class Film(FlatCAMTool): :param ftype: the type of file for saving the film: 'svg', 'png' or 'pdf' :return: """ - self.app.report_usage("export_negative()") + self.app.defaults.report_usage("export_negative()") if filename is None: filename = self.app.defaults["global_last_save_folder"] @@ -1116,7 +1116,7 @@ class Film(FlatCAMTool): :return: """ - self.app.report_usage("export_positive()") + self.app.defaults.report_usage("export_positive()") if filename is None: filename = self.app.defaults["global_last_save_folder"] diff --git a/flatcamTools/ToolImage.py b/flatcamTools/ToolImage.py index f984d7a0..6a077a0d 100644 --- a/flatcamTools/ToolImage.py +++ b/flatcamTools/ToolImage.py @@ -155,7 +155,7 @@ class ToolImage(FlatCAMTool): self.image_type.activated_custom.connect(self.on_image_type) def run(self, toggle=True): - self.app.report_usage("ToolImage()") + self.app.defaults.report_usage("ToolImage()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -263,7 +263,7 @@ class ToolImage(FlatCAMTool): :return: """ - self.app.report_usage("import_image()") + self.app.defaults.report_usage("import_image()") if mask is None: mask = [250, 250, 250, 250] diff --git a/flatcamTools/ToolInvertGerber.py b/flatcamTools/ToolInvertGerber.py index ffacf098..b1329882 100644 --- a/flatcamTools/ToolInvertGerber.py +++ b/flatcamTools/ToolInvertGerber.py @@ -156,7 +156,7 @@ class ToolInvertGerber(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='', **kwargs) def run(self, toggle=True): - self.app.report_usage("ToolInvertGerber()") + self.app.defaults.report_usage("ToolInvertGerber()") log.debug("ToolInvertGerber() is running ...") if toggle: diff --git a/flatcamTools/ToolMove.py b/flatcamTools/ToolMove.py index 0e89764c..18d6a6e0 100644 --- a/flatcamTools/ToolMove.py +++ b/flatcamTools/ToolMove.py @@ -8,7 +8,6 @@ from PyQt5 import QtWidgets, QtCore from FlatCAMTool import FlatCAMTool from flatcamGUI.VisPyVisuals import * -from FlatCAMObj import FlatCAMGerber from copy import copy import logging @@ -64,7 +63,7 @@ class ToolMove(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs) def run(self, toggle): - self.app.report_usage("ToolMove()") + self.app.defaults.report_usage("ToolMove()") if self.app.tool_tab_locked is True: return @@ -128,7 +127,7 @@ class ToolMove(FlatCAMTool): pos_canvas = self.app.plotcanvas.translate_coords(event_pos) # if GRID is active we need to get the snapped positions - if self.app.grid_status() == True: + if self.app.grid_status(): pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1]) else: pos = pos_canvas @@ -148,7 +147,7 @@ class ToolMove(FlatCAMTool): self.delete_shape() # if GRID is active we need to get the snapped positions - if self.app.grid_status() == True: + if self.app.grid_status(): pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1]) else: pos = pos_canvas @@ -171,7 +170,7 @@ class ToolMove(FlatCAMTool): # remove any mark aperture shape that may be displayed for sel_obj in obj_list: # if the Gerber mark shapes are enabled they need to be disabled before move - if isinstance(sel_obj, FlatCAMGerber): + if sel_obj.kind == 'gerber': sel_obj.ui.aperture_table_visibility_cb.setChecked(False) try: @@ -198,8 +197,8 @@ class ToolMove(FlatCAMTool): elif sel_obj.kind == 'excellon': sel_obj.source_file = self.app.export_excellon( obj_name=out_name, filename=None, local_use=sel_obj, use_thread=False) - except Exception as e: - log.debug('[ERROR_NOTCL] %s --> %s' % ('ToolMove.on_left_click()', str(e))) + except Exception as err: + log.debug('[ERROR_NOTCL] %s --> %s' % ('ToolMove.on_left_click()', str(err))) return "fail" # time to plot the moved objects @@ -249,7 +248,7 @@ class ToolMove(FlatCAMTool): pos_canvas = self.app.plotcanvas.translate_coords((x, y)) # if GRID is active we need to get the snapped positions - if self.app.grid_status() == True: + if self.app.grid_status(): pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1]) else: pos = pos_canvas diff --git a/flatcamTools/ToolNCC.py b/flatcamTools/ToolNCC.py index cfbeecaf..f9896e80 100644 --- a/flatcamTools/ToolNCC.py +++ b/flatcamTools/ToolNCC.py @@ -12,7 +12,7 @@ from flatcamGUI.GUIElements import FCCheckBox, FCDoubleSpinner, RadioSet, FCTabl FCComboBox, OptionalInputSection from flatcamParsers.ParseGerber import Gerber -import FlatCAMApp +from FlatCAMCommon import GracefulException as grace from copy import deepcopy @@ -151,7 +151,7 @@ class NonCopperClear(FlatCAMTool, Gerber): "- Circular with 1 ... 4 teeth -> it is informative only. Being circular,\n" "the cut width in material is exactly the tool diameter.\n" "- Ball -> informative only and make reference to the Ball type endmill.\n" - "- V-Shape -> it will disable de Z-Cut parameter in the resulting geometry UI form\n" + "- V-Shape -> it will disable Z-Cut parameter in the resulting geometry UI form\n" "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and\n" "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n" "as the cut width into material will be equal with the value in the Tool Diameter\n" @@ -939,7 +939,7 @@ class NonCopperClear(FlatCAMTool, Gerber): FlatCAMTool.install(self, icon, separator, shortcut='Alt+N', **kwargs) def run(self, toggle=True): - self.app.report_usage("ToolNonCopperClear()") + self.app.defaults.report_usage("ToolNonCopperClear()") log.debug("ToolNCC().run() was launched ...") if toggle: @@ -1582,7 +1582,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # init values for the next usage self.reset_usage() - self.app.report_usage("on_paint_button_click") + self.app.defaults.report_usage("on_paint_button_click") self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"]) self.obj_name = self.object_combo.currentText() @@ -1987,7 +1987,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for poly in env_obj: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) elif ncc_select == _("Reference Object"): @@ -1996,7 +1996,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for poly in env_obj: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) @@ -2090,7 +2090,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if isinstance(geo_elem, Polygon): for ring in self.poly2rings(geo_elem): @@ -2263,7 +2263,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # ########################################################################################## def gen_clear_area(geo_obj, app_obj): assert geo_obj.kind == 'geometry', \ - "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + "Initializer expected a GeometryObject, got %s" % type(geo_obj) # provide the app with a way to process the GUI events when in a blocking loop if not run_threaded: @@ -2312,7 +2312,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("Starting geometry processing for tool: %s" % str(tool)) if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -2377,7 +2377,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # clean the polygon p = p.buffer(0) @@ -2551,7 +2551,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # ########################################################################################### def gen_clear_area_rest(geo_obj, app_obj): assert geo_obj.kind == 'geometry', \ - "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + "Initializer expected a GeometryObject, got %s" % type(geo_obj) log.debug("NCC Tool. Rest machining copper clearing task started.") app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.') @@ -2595,7 +2595,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("Starting geometry processing for tool: %s" % str(tool)) if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -2644,7 +2644,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace try: area = area.difference(poly) except Exception: @@ -2674,7 +2674,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for p in area.geoms: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # clean the polygon p = p.buffer(0) @@ -2753,7 +2753,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # check if there is a geometry at all in the cleared geometry if cleared_geo: @@ -2771,7 +2771,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for p in cleared_area: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace poly = p.buffer(buffer_value) cleared_by_last_tool.append(poly) @@ -2836,7 +2836,7 @@ class NonCopperClear(FlatCAMTool, Gerber): app_obj.new_object("geometry", name, gen_clear_area_rest) else: app_obj.new_object("geometry", name, gen_clear_area) - except FlatCAMApp.GracefulException: + except grace: if run_threaded: proc.done() return @@ -2999,7 +2999,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for poly in geo_n: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) @@ -3017,7 +3017,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for poly in geo_n: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)) bounding_box = cascaded_union(geo_buff_list) @@ -3045,7 +3045,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # ########################################################################################## def gen_clear_area(geo_obj, app_obj): assert geo_obj.kind == 'geometry', \ - "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + "Initializer expected a GeometryObject, got %s" % type(geo_obj) # provide the app with a way to process the GUI events when in a blocking loop if not run_threaded: @@ -3141,7 +3141,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if isinstance(geo_elem, Polygon): for ring in self.poly2rings(geo_elem): @@ -3242,7 +3242,7 @@ class NonCopperClear(FlatCAMTool, Gerber): log.debug("Starting geometry processing for tool: %s" % str(tool)) if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # provide the app with a way to process the GUI events when in a blocking loop QtWidgets.QApplication.processEvents() @@ -3283,7 +3283,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # clean the polygon p = p.buffer(0) @@ -3446,7 +3446,7 @@ class NonCopperClear(FlatCAMTool, Gerber): # ########################################################################################### def gen_clear_area_rest(geo_obj, app_obj): assert geo_obj.kind == 'geometry', \ - "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj) + "Initializer expected a GeometryObject, got %s" % type(geo_obj) log.debug("NCC Tool. Rest machining copper clearing task started.") app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.') @@ -3520,7 +3520,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if isinstance(geo_elem, Polygon): for ring in self.poly2rings(geo_elem): @@ -3614,7 +3614,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if type(empty) is Polygon: empty = MultiPolygon([empty]) @@ -3628,7 +3628,7 @@ class NonCopperClear(FlatCAMTool, Gerber): while sorted_tools: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace tool = sorted_tools.pop(0) log.debug("Starting geometry processing for tool: %s" % str(tool)) @@ -3648,7 +3648,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace try: area = area.difference(poly_r) except Exception: @@ -3678,7 +3678,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for p in area.geoms: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # clean the polygon p = p.buffer(0) @@ -3754,7 +3754,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # check if there is a geometry at all in the cleared geometry if cleared_geo: @@ -3772,7 +3772,7 @@ class NonCopperClear(FlatCAMTool, Gerber): for p in cleared_area: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace r_poly = p.buffer(buffer_value) cleared_by_last_tool.append(r_poly) @@ -3833,7 +3833,7 @@ class NonCopperClear(FlatCAMTool, Gerber): app_obj.new_object("geometry", name, gen_clear_area_rest, plot=plot) else: app_obj.new_object("geometry", name, gen_clear_area, plot=plot) - except FlatCAMApp.GracefulException: + except grace: if run_threaded: proc.done() return @@ -3887,7 +3887,7 @@ class NonCopperClear(FlatCAMTool, Gerber): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace boundary = boundary.difference(el) pol_nr += 1 disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100])) diff --git a/flatcamTools/ToolOptimal.py b/flatcamTools/ToolOptimal.py index f67d9941..de46045e 100644 --- a/flatcamTools/ToolOptimal.py +++ b/flatcamTools/ToolOptimal.py @@ -9,8 +9,7 @@ from PyQt5 import QtWidgets, QtCore, QtGui from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import OptionalHideInputSection, FCTextArea, FCEntry, FCSpinner, FCCheckBox, FCComboBox -from FlatCAMObj import FlatCAMGerber -import FlatCAMApp +from FlatCAMCommon import GracefulException as grace from shapely.geometry import MultiPolygon from shapely.ops import nearest_points @@ -281,7 +280,7 @@ class ToolOptimal(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='Alt+O', **kwargs) def run(self, toggle=True): - self.app.report_usage("ToolOptimal()") + self.app.defaults.report_usage("ToolOptimal()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -343,7 +342,7 @@ class ToolOptimal(FlatCAMTool): self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ...")) return - if not isinstance(fcobj, FlatCAMGerber): + if fcobj.kind != 'gerber': self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated.")) return @@ -365,7 +364,7 @@ class ToolOptimal(FlatCAMTool): for geo_el in fcobj.apertures[ap]['geometry']: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid: total_geo.append(geo_el['solid']) @@ -395,7 +394,7 @@ class ToolOptimal(FlatCAMTool): for s_geo in total_geo[idx:]: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # minimize the number of distances by not taking into considerations those that are too small dist = geo.distance(s_geo) @@ -459,7 +458,7 @@ class ToolOptimal(FlatCAMTool): log.debug("ToolOptimal.on_locate_position() --> first try %s" % str(e)) self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format " "((x0, y0), (x1, y1)).") - return 'fail' + return try: loc_1 = loc[0] @@ -471,7 +470,7 @@ class ToolOptimal(FlatCAMTool): self.app.on_jump_to(custom_location=loc) except Exception as e: log.debug("ToolOptimal.on_locate_position() --> sec try %s" % str(e)) - return 'fail' + return def on_update_text(self, data): txt = '' @@ -567,12 +566,12 @@ class ToolOptimal(FlatCAMTool): if self.selected_locations_text != '': loc = eval(self.selected_locations_text) else: - return 'fail' + return except Exception as e: log.debug("ToolOptimal.on_locate_sec_position() --> first try %s" % str(e)) self.app.inform.emit("[ERROR_NOTCL] The selected text is no valid location in the format " "((x0, y0), (x1, y1)).") - return 'fail' + return try: loc_1 = loc[0] @@ -584,7 +583,7 @@ class ToolOptimal(FlatCAMTool): self.app.on_jump_to(custom_location=loc) except Exception as e: log.debug("ToolOptimal.on_locate_sec_position() --> sec try %s" % str(e)) - return 'fail' + return def reset_fields(self): self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex())) diff --git a/flatcamTools/ToolPDF.py b/flatcamTools/ToolPDF.py index 9f91260f..a21e0f7a 100644 --- a/flatcamTools/ToolPDF.py +++ b/flatcamTools/ToolPDF.py @@ -8,7 +8,7 @@ from PyQt5 import QtWidgets, QtCore from FlatCAMTool import FlatCAMTool -import FlatCAMApp +from FlatCAMCommon import GracefulException as grace from shapely.geometry import Point, Polygon, LineString, MultiPolygon from shapely.ops import unary_union @@ -129,7 +129,7 @@ class ToolPDF(FlatCAMTool): self.point_to_unit_factor = 0.01388888888 def run(self, toggle=True): - self.app.report_usage("ToolPDF()") + self.app.defaults.report_usage("ToolPDF()") self.set_tool_ui() self.on_open_pdf_click() @@ -147,7 +147,7 @@ class ToolPDF(FlatCAMTool): :return: None """ - self.app.report_usage("ToolPDF.on_open_pdf_click()") + self.app.defaults.report_usage("ToolPDF.on_open_pdf_click()") self.app.log.debug("ToolPDF.on_open_pdf_click()") _filter_ = "Adobe PDF Files (*.pdf);;" \ @@ -190,7 +190,7 @@ class ToolPDF(FlatCAMTool): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace with self.app.proc_container.new(_("Parsing PDF file ...")): with open(filename, "rb") as f: @@ -200,7 +200,7 @@ class ToolPDF(FlatCAMTool): for s in re.findall(self.stream_re, pdf): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace stream_nr += 1 log.debug(" PDF STREAM: %d\n" % stream_nr) @@ -291,7 +291,7 @@ class ToolPDF(FlatCAMTool): def layer_rendering_as_gerber(self, filename, ap_dict, layer_nr): outname = filename.split('/')[-1].split('\\')[-1] + "_%s" % str(layer_nr) - def obj_init(grb_obj, app_obj): + def obj_init(grb_obj): grb_obj.apertures = ap_dict @@ -404,7 +404,7 @@ class ToolPDF(FlatCAMTool): for object_name in self.pdf_parsed: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace filename = deepcopy(self.pdf_parsed[object_name]['filename']) pdf_content = deepcopy(self.pdf_parsed[object_name]['pdf']) @@ -412,7 +412,7 @@ class ToolPDF(FlatCAMTool): for k in pdf_content: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace ap_dict = pdf_content[k] if ap_dict: @@ -493,7 +493,7 @@ class ToolPDF(FlatCAMTool): for pline in lines: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace line_nr += 1 log.debug("line %d: %s" % (line_nr, pline)) @@ -868,7 +868,6 @@ class ToolPDF(FlatCAMTool): new_el['solid'] = pdf_geo new_el['follow'] = pdf_geo.exterior apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el)) - found_aperture = None else: if str(aperture) in apertures_dict.keys(): aperture += 1 @@ -1231,7 +1230,6 @@ class ToolPDF(FlatCAMTool): new_el['solid'] = pdf_geo new_el['follow'] = pdf_geo.exterior apertures_dict[copy(found_aperture)]['geometry'].append(deepcopy(new_el)) - found_aperture = None else: if str(aperture) in apertures_dict.keys(): aperture += 1 @@ -1355,7 +1353,7 @@ class ToolPDF(FlatCAMTool): if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace return object_dict diff --git a/flatcamTools/ToolPaint.py b/flatcamTools/ToolPaint.py index d7b85ca7..d6a16405 100644 --- a/flatcamTools/ToolPaint.py +++ b/flatcamTools/ToolPaint.py @@ -14,7 +14,7 @@ from copy import deepcopy from flatcamParsers.ParseGerber import Gerber from camlib import Geometry, FlatCAMRTreeStorage from flatcamGUI.GUIElements import FCTable, FCDoubleSpinner, FCCheckBox, FCInputDialog, RadioSet, FCButton, FCComboBox -import FlatCAMApp +from FlatCAMCommon import GracefulException as grace from shapely.geometry import base, Polygon, MultiPolygon, LinearRing, Point from shapely.ops import cascaded_union, unary_union, linemerge @@ -143,16 +143,16 @@ class ToolPaint(FlatCAMTool, Gerber): "is the cut width into the material.")) self.tools_table.horizontalHeaderItem(2).setToolTip( - _("The Tool Type (TT) can be:
    " - "- Circular with 1 ... 4 teeth -> it is informative only. Being circular,
    " - "the cut width in material is exactly the tool diameter.
    " - "- Ball -> informative only and make reference to the Ball type endmill.
    " - "- V-Shape -> it will disable de Z-Cut parameter in the resulting geometry UI form " - "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and " - "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such " - "as the cut width into material will be equal with the value in the Tool Diameter " - "column of this table.
    " - "Choosing the V-Shape Tool Type automatically will select the Operation Type " + _("The Tool Type (TT) can be:\n" + "- Circular -> it is informative only. Being circular,\n" + "the cut width in material is exactly the tool diameter.\n" + "- Ball -> informative only and make reference to the Ball type endmill.\n" + "- V-Shape -> it will disable Z-Cut parameter in the resulting geometry UI form\n" + "and enable two additional UI form fields in the resulting geometry: V-Tip Dia and\n" + "V-Tip Angle. Adjusting those two values will adjust the Z-Cut parameter such\n" + "as the cut width into material will be equal with the value in the Tool Diameter\n" + "column of this table.\n" + "Choosing the 'V-Shape' Tool Type automatically will select the Operation Type\n" "in the resulting geometry as Isolation.")) self.order_label = QtWidgets.QLabel('%s:' % _('Tool order')) @@ -710,7 +710,7 @@ class ToolPaint(FlatCAMTool, Gerber): FlatCAMTool.install(self, icon, separator, shortcut='Alt+P', **kwargs) def run(self, toggle=True): - self.app.report_usage("ToolPaint()") + self.app.defaults.report_usage("ToolPaint()") log.debug("ToolPaint().run() was launched ...") if toggle: @@ -1374,7 +1374,7 @@ class ToolPaint(FlatCAMTool, Gerber): # init values for the next usage self.reset_usage() - self.app.report_usage("on_paint_button_click") + self.app.defaults.report_usage("on_paint_button_click") # self.app.call_source = 'paint' self.select_method = self.selectmethod_combo.get_value() @@ -1836,7 +1836,7 @@ class ToolPaint(FlatCAMTool, Gerber): contour=cont, connect=conn, prog_plot=prog_plot) - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as ee: log.debug("ToolPaint.paint_polygon_worker() Standard --> %s" % str(ee)) @@ -1850,7 +1850,7 @@ class ToolPaint(FlatCAMTool, Gerber): contour=cont, connect=conn, prog_plot=prog_plot) - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as ee: log.debug("ToolPaint.paint_polygon_worker() Seed --> %s" % str(ee)) @@ -1864,7 +1864,7 @@ class ToolPaint(FlatCAMTool, Gerber): contour=cont, connect=conn, prog_plot=prog_plot) - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as ee: log.debug("ToolPaint.paint_polygon_worker() Lines --> %s" % str(ee)) @@ -2015,7 +2015,7 @@ class ToolPaint(FlatCAMTool, Gerber): # contour=cont, # connect=conn, # prog_plot=prog_plot) - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as ee: log.debug("ToolPaint.paint_polygon_worker() Laser Lines --> %s" % str(ee)) @@ -2052,7 +2052,7 @@ class ToolPaint(FlatCAMTool, Gerber): contour=cont, connect=conn, prog_plot=prog_plot) - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as ee: log.debug("ToolPaint.paint_polygon_worker() Combo --> %s" % str(ee)) @@ -2199,7 +2199,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) @@ -2217,7 +2217,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -2230,7 +2230,7 @@ class ToolPaint(FlatCAMTool, Gerber): for x in cp: total_geometry += list(x.get_objects()) final_solid_geometry += total_geometry - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as e: log.debug("Could not Paint the polygons. %s" % str(e)) @@ -2305,7 +2305,7 @@ class ToolPaint(FlatCAMTool, Gerber): def job_thread(app_obj): try: ret = app_obj.new_object("geometry", name, job_init, plot=plot) - except FlatCAMApp.GracefulException: + except grace: proc.done() return except Exception as er: @@ -2376,7 +2376,7 @@ class ToolPaint(FlatCAMTool, Gerber): """ if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if geometry is None: return @@ -2517,7 +2517,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -2542,7 +2542,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -2705,7 +2705,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, prog_plot=prog_plot) @@ -2723,7 +2723,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -2735,7 +2735,7 @@ class ToolPaint(FlatCAMTool, Gerber): for x in cp: cleared_geo += list(x.get_objects()) final_solid_geometry += cleared_geo - except FlatCAMApp.GracefulException: + except grace: return "fail" except Exception as e: log.debug("Could not Paint the polygons. %s" % str(e)) @@ -2815,7 +2815,7 @@ class ToolPaint(FlatCAMTool, Gerber): ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot) else: ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot) - except FlatCAMApp.GracefulException: + except grace: proc.done() return except Exception as err: @@ -2873,7 +2873,7 @@ class ToolPaint(FlatCAMTool, Gerber): """ if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if geometry is None: return @@ -3015,7 +3015,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -3040,7 +3040,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -3193,7 +3193,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(pp, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -3218,7 +3218,7 @@ class ToolPaint(FlatCAMTool, Gerber): QtWidgets.QApplication.processEvents() if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace geo_res = self.paint_polygon_worker(poly_buf, tooldiameter=tool_dia, over=over, conn=conn, cont=cont, paint_method=paint_method, obj=obj, @@ -3312,7 +3312,7 @@ class ToolPaint(FlatCAMTool, Gerber): ret = app_obj.new_object("geometry", name, gen_paintarea_rest_machining, plot=plot) else: ret = app_obj.new_object("geometry", name, gen_paintarea, plot=plot) - except FlatCAMApp.GracefulException: + except grace: proc.done() return except Exception as err: diff --git a/flatcamTools/ToolPanelize.py b/flatcamTools/ToolPanelize.py index bbe57c18..376b6afa 100644 --- a/flatcamTools/ToolPanelize.py +++ b/flatcamTools/ToolPanelize.py @@ -9,10 +9,8 @@ from PyQt5 import QtWidgets, QtGui, QtCore from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCSpinner, FCDoubleSpinner, RadioSet, FCCheckBox, OptionalInputSection, FCComboBox -from FlatCAMObj import FlatCAMGeometry, FlatCAMGerber, FlatCAMExcellon -import FlatCAMApp +from FlatCAMCommon import GracefulException as grace from copy import deepcopy -# from ObjectCollection import * import numpy as np import shapely.affinity as affinity @@ -295,7 +293,7 @@ class Panelize(FlatCAMTool): self.constrain_flag = False def run(self, toggle=True): - self.app.report_usage("ToolPanelize()") + self.app.defaults.report_usage("ToolPanelize()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -480,13 +478,13 @@ class Panelize(FlatCAMTool): rows -= 1 panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1)) - if isinstance(panel_obj, FlatCAMExcellon) or isinstance(panel_obj, FlatCAMGeometry): + if panel_obj.kind == 'excellon' or panel_obj.kind == 'geometry': # make a copy of the panelized Excellon or Geometry tools copied_tools = {} for tt, tt_val in list(panel_obj.tools.items()): copied_tools[tt] = deepcopy(tt_val) - if isinstance(panel_obj, FlatCAMGerber): + if panel_obj.kind == 'gerber': # make a copy of the panelized Gerber apertures copied_apertures = {} for tt, tt_val in list(panel_obj.apertures.items()): @@ -525,7 +523,7 @@ class Panelize(FlatCAMTool): for tool_dict in panel_obj.drills: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace point_offseted = affinity.translate(tool_dict['point'], currentx, currenty) obj_fin.drills.append( @@ -550,7 +548,7 @@ class Panelize(FlatCAMTool): for tool_dict in panel_obj.slots: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace start_offseted = affinity.translate(tool_dict['start'], currentx, currenty) stop_offseted = affinity.translate(tool_dict['stop'], currentx, currenty) @@ -600,20 +598,20 @@ class Panelize(FlatCAMTool): obj_fin.solid_geometry = [] # create the initial structure on which to create the panel - if isinstance(panel_obj, FlatCAMGeometry): + if panel_obj.kind == 'geometry': obj_fin.multigeo = panel_obj.multigeo obj_fin.tools = copied_tools if panel_obj.multigeo is True: for tool in panel_obj.tools: obj_fin.tools[tool]['solid_geometry'][:] = [] - elif isinstance(panel_obj, FlatCAMGerber): + elif panel_obj.kind == 'gerber': obj_fin.apertures = copied_apertures for ap in obj_fin.apertures: obj_fin.apertures[ap]['geometry'] = [] # find the number of polygons in the source solid_geometry geo_len = 0 - if isinstance(panel_obj, FlatCAMGeometry): + if panel_obj.kind == 'geometry': if panel_obj.multigeo is True: for tool in panel_obj.tools: try: @@ -625,7 +623,7 @@ class Panelize(FlatCAMTool): geo_len = len(panel_obj.solid_geometry) except TypeError: geo_len = 1 - elif isinstance(panel_obj, FlatCAMGerber): + elif panel_obj.kind == 'gerber': for ap in panel_obj.apertures: if 'geometry' in panel_obj.apertures[ap]: try: @@ -641,12 +639,12 @@ class Panelize(FlatCAMTool): element += 1 old_disp_number = 0 - if isinstance(panel_obj, FlatCAMGeometry): + if panel_obj.kind == 'geometry': if panel_obj.multigeo is True: for tool in panel_obj.tools: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace # geo = translate_recursion(panel_obj.tools[tool]['solid_geometry']) # if isinstance(geo, list): @@ -678,7 +676,7 @@ class Panelize(FlatCAMTool): # obj_fin.solid_geometry.append(geo) if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace try: # calculate the number of polygons @@ -690,7 +688,7 @@ class Panelize(FlatCAMTool): for geo_el in panel_obj.solid_geometry: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace trans_geo = translate_recursion(geo_el) obj_fin.solid_geometry.append(trans_geo) @@ -715,13 +713,13 @@ class Panelize(FlatCAMTool): # obj_fin.solid_geometry.append(geo) if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace try: for geo_el in panel_obj.solid_geometry: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace trans_geo = translate_recursion(geo_el) obj_fin.solid_geometry.append(trans_geo) @@ -732,7 +730,7 @@ class Panelize(FlatCAMTool): for apid in panel_obj.apertures: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace if 'geometry' in panel_obj.apertures[apid]: try: # calculate the number of polygons @@ -743,7 +741,7 @@ class Panelize(FlatCAMTool): for el in panel_obj.apertures[apid]['geometry']: if self.app.abort_flag: # graceful abort requested by the user - raise FlatCAMApp.GracefulException + raise grace new_el = {} if 'solid' in el: @@ -786,7 +784,7 @@ class Panelize(FlatCAMTool): self.app.proc_container.update_view_text('') self.app.inform.emit('%s: %d' % (_("Generating panel... Spawning copies"), (int(rows * columns)))) - if isinstance(panel_obj, FlatCAMExcellon): + if panel_obj.kind == 'excellon': self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True) else: self.app.new_object(panel_type, self.outname, job_init_geometry, plot=True, autoselected=True) diff --git a/flatcamTools/ToolPcbWizard.py b/flatcamTools/ToolPcbWizard.py index fe72dccc..832cf191 100644 --- a/flatcamTools/ToolPcbWizard.py +++ b/flatcamTools/ToolPcbWizard.py @@ -170,7 +170,7 @@ class PcbWizard(FlatCAMTool): self.tools_from_inf = {} def run(self, toggle=False): - self.app.report_usage("PcbWizard Tool()") + self.app.defaults.report_usage("PcbWizard Tool()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolProperties.py b/flatcamTools/ToolProperties.py index 3cd2656b..e1cc1c25 100644 --- a/flatcamTools/ToolProperties.py +++ b/flatcamTools/ToolProperties.py @@ -73,7 +73,7 @@ class Properties(FlatCAMTool): self.calculations_finished.connect(self.show_area_chull) def run(self, toggle=True): - self.app.report_usage("ToolProperties()") + self.app.defaults.report_usage("ToolProperties()") if self.app.tool_tab_locked is True: return diff --git a/flatcamTools/ToolPunchGerber.py b/flatcamTools/ToolPunchGerber.py index 2f984f5f..f0c6a9e3 100644 --- a/flatcamTools/ToolPunchGerber.py +++ b/flatcamTools/ToolPunchGerber.py @@ -142,7 +142,7 @@ class ToolPunchGerber(FlatCAMTool): "- Excellon Object-> the Excellon object drills center will serve as reference.\n" "- Fixed Diameter -> will try to use the pads center as reference adding fixed diameter holes.\n" "- Fixed Annular Ring -> will try to keep a set annular ring.\n" - "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.\n") + "- Proportional -> will make a Gerber punch hole having the diameter a percentage of the pad diameter.") ) self.method_punch = RadioSet( [ @@ -397,7 +397,7 @@ class ToolPunchGerber(FlatCAMTool): ) def run(self, toggle=True): - self.app.report_usage("ToolPunchGerber()") + self.app.defaults.report_usage("ToolPunchGerber()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -604,8 +604,8 @@ class ToolPunchGerber(FlatCAMTool): if grb_obj.apertures[apid]['type'] == 'C' and self.circular_cb.get_value(): if punch_size >= float(grb_obj.apertures[apid]['size']): self.app.inform.emit('[ERROR_NOTCL] %s' % - _(" Could not generate punched hole Gerber because the punch hole size" - "is bigger than some of the apertures in the Gerber object.")) + _("Could not generate punched hole Gerber because the punch hole size" + " is bigger than some of the apertures in the Gerber object.")) return 'fail' else: for elem in grb_obj.apertures[apid]['geometry']: @@ -617,7 +617,7 @@ class ToolPunchGerber(FlatCAMTool): punch_size >= float(grb_obj.apertures[apid]['height']): self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not generate punched hole Gerber because the punch hole size" - "is bigger than some of the apertures in the Gerber object.")) + " is bigger than some of the apertures in the Gerber object.")) return 'fail' elif round(float(grb_obj.apertures[apid]['width']), self.decimals) == \ round(float(grb_obj.apertures[apid]['height']), self.decimals) and \ diff --git a/flatcamTools/ToolQRCode.py b/flatcamTools/ToolQRCode.py index fdf01a08..8d54fedd 100644 --- a/flatcamTools/ToolQRCode.py +++ b/flatcamTools/ToolQRCode.py @@ -354,7 +354,7 @@ class QRCode(FlatCAMTool): self.reset_button.clicked.connect(self.set_tool_ui) def run(self, toggle=True): - self.app.report_usage("QRCode()") + self.app.defaults.report_usage("QRCode()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolRulesCheck.py b/flatcamTools/ToolRulesCheck.py index 53f634c2..fe07b683 100644 --- a/flatcamTools/ToolRulesCheck.py +++ b/flatcamTools/ToolRulesCheck.py @@ -589,7 +589,7 @@ class RulesCheck(FlatCAMTool): cb.setChecked(False) def run(self, toggle=True): - self.app.report_usage("ToolRulesCheck()") + self.app.defaults.report_usage("ToolRulesCheck()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolShell.py b/flatcamTools/ToolShell.py index edbb3c7d..f68a9da4 100644 --- a/flatcamTools/ToolShell.py +++ b/flatcamTools/ToolShell.py @@ -13,8 +13,10 @@ from PyQt5.QtWidgets import QVBoxLayout, QWidget from flatcamGUI.GUIElements import _BrowserTextEdit, _ExpandableTextEdit import html import sys +import traceback import tkinter as tk +import tclCommands import gettext import FlatCAMTranslation as fcTranslate @@ -105,25 +107,25 @@ class TermWidget(QWidget): mtype = text[:idx+1].upper() mtype = mtype.replace('_NOTCL', '') body = text[idx+1:] - if style == 'in': + if style.lower() == 'in': text = '%s' % text - elif style == 'err': + elif style.lower() == 'err': text = '%s'\ '%s'\ - %(mtype, body) - elif style == 'warning': + % (mtype, body) + elif style.lower() == 'warning': # text = '%s' % text text = '%s' \ '%s' \ % (mtype, body) - elif style == 'success': + elif style.lower() == 'success': # text = '%s' % text text = '%s' \ '%s' \ % (mtype, body) - elif style == 'selected': + elif style.lower() == 'selected': text = '' - elif style == 'raw': + elif style.lower() == 'raw': text = text else: # without span
    is ignored!!! @@ -224,7 +226,7 @@ class TermWidget(QWidget): def is_command_complete(self, text): """ - Executed by _ExpandableTextEdit. Reimplement this function in the child classes. + Executed by _ExpandableTextEdit. Re-implement this function in the child classes. """ return True @@ -253,30 +255,112 @@ class TermWidget(QWidget): class FCShell(TermWidget): - def __init__(self, sysShell, version, *args): + def __init__(self, app, version, *args): """ + Initialize the TCL Shell. A dock widget that holds the GUI interface to the FlatCAM command line. - :param sysShell: When instantiated the sysShell will be actually the FlatCAMApp.App() class + :param app: When instantiated the sysShell will be actually the FlatCAMApp.App() class :param version: FlatCAM version string :param args: Parameters passed to the TermWidget parent class """ - TermWidget.__init__(self, version, *args, app=sysShell) - self._sysShell = sysShell + TermWidget.__init__(self, version, *args, app=app) + self.app = app + + self.tcl_commands_storage = {} + + self.init_tcl() + + self._edit.set_model_data(self.app.myKeywords) + self.setWindowIcon(self.app.ui.app_icon) + self.setWindowTitle("FlatCAM Shell") + self.resize(*self.app.defaults["global_shell_shape"]) + self._append_to_browser('in', "FlatCAM %s - " % version) + self.append_output('%s\n\n' % _("Type >help< to get started")) + + def init_tcl(self): + if hasattr(self, 'tcl') and self.tcl is not None: + # self.tcl = None + # new object cannot be used here as it will not remember values created for next passes, + # because tcl was executed in old instance of TCL + pass + else: + self.tcl = tk.Tcl() + self.setup_shell() + + def setup_shell(self): + """ + Creates shell functions. Runs once at startup. + + :return: None + """ + + ''' + How to implement TCL shell commands: + + All parameters passed to command should be possible to set as None and test it afterwards. + This is because we need to see error caused in tcl, + if None value as default parameter is not allowed TCL will return empty error. + Use: + def mycommand(name=None,...): + + Test it like this: + if name is None: + + self.raise_tcl_error('Argument name is missing.') + + When error occurred, always use raise_tcl_error, never return "some text" on error, + otherwise we will miss it and processing will silently continue. + Method raise_tcl_error pass error into TCL interpreter, then raise python exception, + which is caught in exec_command and displayed in TCL shell console with red background. + Error in console is displayed with TCL trace. + + This behavior works only within main thread, + errors with promissed tasks can be catched and detected only with log. + TODO: this problem have to be addressed somehow, maybe rewrite promissing to be blocking somehow for + TCL shell. + + Kamil's comment: I will rewrite existing TCL commands from time to time to follow this rules. + + ''' + + # Import/overwrite tcl commands as objects of TclCommand descendants + # This modifies the variable 'self.tcl_commands_storage'. + tclCommands.register_all_commands(self.app, self.tcl_commands_storage) + + # Add commands to the tcl interpreter + for cmd in self.tcl_commands_storage: + self.tcl.createcommand(cmd, self.tcl_commands_storage[cmd]['fcn']) + + # Make the tcl puts function return instead of print to stdout + self.tcl.eval(''' + rename puts original_puts + proc puts {args} { + if {[llength $args] == 1} { + return "[lindex $args 0]" + } else { + eval original_puts $args + } + } + ''') def is_command_complete(self, text): + def skipQuotes(txt): quote = txt[0] text_val = txt[1:] endIndex = str(text_val).index(quote) return text[endIndex:] - while text: - if text[0] in ('"', "'"): - try: - text = skipQuotes(text) - except ValueError: - return False - text = text[1:] + # I'm disabling this because I need to be able to load paths that have spaces by + # enclosing them in quotes --- Marius Stanciu + # while text: + # if text[0] in ('"', "'"): + # try: + # text = skipQuotes(text) + # except ValueError: + # return False + # text = text[1:] + return True def child_exec_command(self, text): @@ -293,7 +377,7 @@ class FCShell(TermWidget): :return: output if there was any """ - self._sysShell.report_usage('exec_command') + self.app.defaults.report_usage('exec_command') return self.exec_command_test(text, False, no_echo=no_echo) @@ -315,15 +399,15 @@ class FCShell(TermWidget): if no_echo is False: self.open_processing() # Disables input box. - result = self._sysShell.tcl.eval(str(tcl_command_string)) + result = self.tcl.eval(str(tcl_command_string)) if result != 'None' and no_echo is False: self.append_output(result + '\n') except tk.TclError as e: # This will display more precise answer if something in TCL shell fails - result = self._sysShell.tcl.eval("set errorInfo") - self._sysShell.log.error("Exec command Exception: %s" % (result + '\n')) + result = self.tcl.eval("set errorInfo") + self.app.log.error("Exec command Exception: %s" % (result + '\n')) if no_echo is False: self.append_error('ERROR: ' + result + '\n') # Show error in console and just return or in test raise exception @@ -335,39 +419,101 @@ class FCShell(TermWidget): pass return result - # """ - # Code below is unsused. Saved for later. - # """ + def raise_tcl_unknown_error(self, unknownException): + """ + Raise exception if is different type than TclErrorException + this is here mainly to show unknown errors inside TCL shell console. - # parts = re.findall(r'([\w\\:\.]+|".*?")+', text) - # parts = [p.replace('\n', '').replace('"', '') for p in parts] - # self.log.debug(parts) - # try: - # if parts[0] not in commands: - # self.shell.append_error("Unknown command\n") - # return - # - # #import inspect - # #inspect.getargspec(someMethod) - # if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \ - # (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]): - # self.shell.append_error( - # "Command %s takes %d arguments. %d given.\n" % - # (parts[0], commands[parts[0]]["params"], len(parts)-1) - # ) - # return - # - # cmdfcn = commands[parts[0]]["fcn"] - # cmdconv = commands[parts[0]]["converters"] - # if len(parts) - 1 > 0: - # retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)]) - # else: - # retval = cmdfcn() - # retfcn = commands[parts[0]]["retfcn"] - # if retval and retfcn(retval): - # self.shell.append_output(retfcn(retval) + "\n") - # - # except Exception as e: - # #self.shell.append_error(''.join(traceback.format_exc())) - # #self.shell.append_error("?\n") - # self.shell.append_error(str(e) + "\n") + :param unknownException: + :return: + """ + + if not isinstance(unknownException, self.TclErrorException): + self.raise_tcl_error("Unknown error: %s" % str(unknownException)) + else: + raise unknownException + + def display_tcl_error(self, error, error_info=None): + """ + Escape bracket [ with '\' otherwise there is error + "ERROR: missing close-bracket" instead of real error + + :param error: it may be text or exception + :param error_info: Some informations about the error + :return: None + """ + + if isinstance(error, Exception): + exc_type, exc_value, exc_traceback = error_info + if not isinstance(error, self.TclErrorException): + show_trace = 1 + else: + show_trace = int(self.app.defaults['global_verbose_error_level']) + + if show_trace > 0: + trc = traceback.format_list(traceback.extract_tb(exc_traceback)) + trc_formated = [] + for a in reversed(trc): + trc_formated.append(a.replace(" ", " > ").replace("\n", "")) + text = "%s\nPython traceback: %s\n%s" % (exc_value, exc_type, "\n".join(trc_formated)) + else: + text = "%s" % error + else: + text = error + + text = text.replace('[', '\\[').replace('"', '\\"') + self.tcl.eval('return -code error "%s"' % text) + + def raise_tcl_error(self, text): + """ + This method pass exception from python into TCL as error, so we get stacktrace and reason + + :param text: text of error + :return: raise exception + """ + + self.display_tcl_error(text) + raise self.TclErrorException(text) + + class TclErrorException(Exception): + """ + this exception is defined here, to be able catch it if we successfully handle all errors from shell command + """ + pass + + # """ + # Code below is unsused. Saved for later. + # """ + + # parts = re.findall(r'([\w\\:\.]+|".*?")+', text) + # parts = [p.replace('\n', '').replace('"', '') for p in parts] + # self.log.debug(parts) + # try: + # if parts[0] not in commands: + # self.shell.append_error("Unknown command\n") + # return + # + # #import inspect + # #inspect.getargspec(someMethod) + # if (type(commands[parts[0]]["params"]) is not list and len(parts)-1 != commands[parts[0]]["params"]) or \ + # (type(commands[parts[0]]["params"]) is list and len(parts)-1 not in commands[parts[0]]["params"]): + # self.shell.append_error( + # "Command %s takes %d arguments. %d given.\n" % + # (parts[0], commands[parts[0]]["params"], len(parts)-1) + # ) + # return + # + # cmdfcn = commands[parts[0]]["fcn"] + # cmdconv = commands[parts[0]]["converters"] + # if len(parts) - 1 > 0: + # retval = cmdfcn(*[cmdconv[i](parts[i + 1]) for i in range(len(parts)-1)]) + # else: + # retval = cmdfcn() + # retfcn = commands[parts[0]]["retfcn"] + # if retval and retfcn(retval): + # self.shell.append_output(retfcn(retval) + "\n") + # + # except Exception as e: + # #self.shell.append_error(''.join(traceback.format_exc())) + # #self.shell.append_error("?\n") + # self.shell.append_error(str(e) + "\n") diff --git a/flatcamTools/ToolSolderPaste.py b/flatcamTools/ToolSolderPaste.py index f471fd7f..f77c2a98 100644 --- a/flatcamTools/ToolSolderPaste.py +++ b/flatcamTools/ToolSolderPaste.py @@ -11,7 +11,6 @@ from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCTable, \ FCInputDialog, FCDoubleSpinner, FCSpinner, FCFileSaveDialog from FlatCAMApp import log from camlib import distance -from FlatCAMObj import FlatCAMCNCjob from flatcamEditors.FlatCAMTextEditor import TextEditor from PyQt5 import QtGui, QtCore, QtWidgets @@ -506,7 +505,8 @@ class SolderPaste(FlatCAMTool): self.flat_geometry = [] # action to be added in the combobox context menu - self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/trash16.png'), _("Delete Object")) + self.combo_context_del_action = QtWidgets.QAction(QtGui.QIcon(self.app.resource_location + '/trash16.png'), + _("Delete Object")) # ## Signals self.combo_context_del_action.triggered.connect(self.on_delete_object) @@ -525,7 +525,7 @@ class SolderPaste(FlatCAMTool): self.reset_button.clicked.connect(self.set_tool_ui) def run(self, toggle=True): - self.app.report_usage("ToolSolderPaste()") + self.app.defaults.report_usage("ToolSolderPaste()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -966,6 +966,7 @@ class SolderPaste(FlatCAMTool): self.build_ui() return else: + old_tool_dia = '' # identify the old tool_dia and restore the text in tool table for k, v in self.tooltable_tools.items(): if k == tooluid: @@ -1332,9 +1333,9 @@ class SolderPaste(FlatCAMTool): # Object initialization function for app.new_object() # RUNNING ON SEPARATE THREAD! - def job_init(job_obj, app_obj): - assert isinstance(job_obj, FlatCAMCNCjob), \ - "Initializer expected a FlatCAMCNCjob, got %s" % type(job_obj) + def job_init(job_obj): + assert job_obj.kind == 'cncjob', \ + "Initializer expected a CNCJobObject, got %s" % type(job_obj) # this turn on the FlatCAMCNCJob plot for multiple tools job_obj.multitool = True @@ -1364,7 +1365,7 @@ class SolderPaste(FlatCAMTool): res = job_obj.generate_gcode_from_solderpaste_geo(**tooluid_value) if res == 'fail': - log.debug("FlatCAMGeometry.mtool_gen_cncjob() --> generate_from_geometry2() failed") + log.debug("GeometryObject.mtool_gen_cncjob() --> generate_from_geometry2() failed") return 'fail' else: tool_cnc_dict['gcode'] = res diff --git a/flatcamTools/ToolSub.py b/flatcamTools/ToolSub.py index adf44dd7..ef826a0f 100644 --- a/flatcamTools/ToolSub.py +++ b/flatcamTools/ToolSub.py @@ -236,7 +236,7 @@ class ToolSub(FlatCAMTool): FlatCAMTool.install(self, icon, separator, shortcut='Alt+W', **kwargs) def run(self, toggle=True): - self.app.report_usage("ToolSub()") + self.app.defaults.report_usage("ToolSub()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same diff --git a/flatcamTools/ToolTransform.py b/flatcamTools/ToolTransform.py index 416de316..1db4fb93 100644 --- a/flatcamTools/ToolTransform.py +++ b/flatcamTools/ToolTransform.py @@ -8,7 +8,6 @@ from PyQt5 import QtWidgets from FlatCAMTool import FlatCAMTool from flatcamGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCButton, OptionalInputSection, EvalEntry2 -from FlatCAMObj import FlatCAMCNCjob import gettext import FlatCAMTranslation as fcTranslate @@ -434,7 +433,7 @@ class ToolTransform(FlatCAMTool): # self.buffer_entry.returnPressed.connect(self.on_buffer_by_distance) def run(self, toggle=True): - self.app.report_usage("ToolTransform()") + self.app.defaults.report_usage("ToolTransform()") if toggle: # if the splitter is hidden, display it, else hide it but only if the current widget is the same @@ -681,7 +680,7 @@ class ToolTransform(FlatCAMTool): try: # first get a bounding box to fit all for obj in obj_list: - if isinstance(obj, FlatCAMCNCjob): + if obj.kind == 'cncjob': pass else: xmin, ymin, xmax, ymax = obj.bounds() @@ -699,7 +698,7 @@ class ToolTransform(FlatCAMTool): px = 0.5 * (xminimal + xmaximal) py = 0.5 * (yminimal + ymaximal) for sel_obj in obj_list: - if isinstance(sel_obj, FlatCAMCNCjob): + if sel_obj.kind == 'cncjob': self.app.inform.emit(_("CNCJob objects can't be rotated.")) else: sel_obj.rotate(-num, point=(px, py)) @@ -735,7 +734,7 @@ class ToolTransform(FlatCAMTool): else: # first get a bounding box to fit all for obj in obj_list: - if isinstance(obj, FlatCAMCNCjob): + if obj.kind == 'cncjob': pass else: xmin, ymin, xmax, ymax = obj.bounds() @@ -755,7 +754,7 @@ class ToolTransform(FlatCAMTool): # execute mirroring for sel_obj in obj_list: - if isinstance(sel_obj, FlatCAMCNCjob): + if sel_obj.kind == 'cncjob': self.app.inform.emit(_("CNCJob objects can't be mirrored/flipped.")) else: if axis == 'X': @@ -803,7 +802,7 @@ class ToolTransform(FlatCAMTool): try: # first get a bounding box to fit all for obj in obj_list: - if isinstance(obj, FlatCAMCNCjob): + if obj.kind == 'cncjob': pass else: xmin, ymin, xmax, ymax = obj.bounds() @@ -815,7 +814,7 @@ class ToolTransform(FlatCAMTool): yminimal = min(yminlist) for sel_obj in obj_list: - if isinstance(sel_obj, FlatCAMCNCjob): + if sel_obj.kind == 'cncjob': self.app.inform.emit(_("CNCJob objects can't be skewed.")) else: if axis == 'X': @@ -842,15 +841,14 @@ class ToolTransform(FlatCAMTool): ymaxlist = [] if not obj_list: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("No object selected. Please Select an object to scale!")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to scale!")) return else: with self.app.proc_container.new(_("Applying Scale")): try: # first get a bounding box to fit all for obj in obj_list: - if isinstance(obj, FlatCAMCNCjob): + if obj.kind == 'cncjob': pass else: xmin, ymin, xmax, ymax = obj.bounds() @@ -873,7 +871,7 @@ class ToolTransform(FlatCAMTool): py = 0 for sel_obj in obj_list: - if isinstance(sel_obj, FlatCAMCNCjob): + if sel_obj.kind == 'cncjob': self.app.inform.emit(_("CNCJob objects can't be scaled.")) else: sel_obj.scale(xfactor, yfactor, point=(px, py)) @@ -883,8 +881,7 @@ class ToolTransform(FlatCAMTool): self.app.object_changed.emit(sel_obj) sel_obj.plot() - self.app.inform.emit('[success] %s %s %s...' % - (_('Scale on the'), str(axis), _('axis done'))) + self.app.inform.emit('[success] %s %s %s...' % (_('Scale on the'), str(axis), _('axis done'))) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) @@ -894,14 +891,13 @@ class ToolTransform(FlatCAMTool): obj_list = self.app.collection.get_selected() if not obj_list: - self.app.inform.emit('[WARNING_NOTCL] %s' % - _("No object selected. Please Select an object to offset!")) + self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object selected. Please Select an object to offset!")) return else: with self.app.proc_container.new(_("Applying Offset")): try: for sel_obj in obj_list: - if isinstance(sel_obj, FlatCAMCNCjob): + if sel_obj.kind == 'cncjob': self.app.inform.emit(_("CNCJob objects can't be offset.")) else: if axis == 'X': @@ -915,8 +911,7 @@ class ToolTransform(FlatCAMTool): self.app.object_changed.emit(sel_obj) sel_obj.plot() - self.app.inform.emit('[success] %s %s %s...' % - (_('Offset on the'), str(axis), _('axis done'))) + self.app.inform.emit('[success] %s %s %s...' % (_('Offset on the'), str(axis), _('axis done'))) except Exception as e: self.app.inform.emit('[ERROR_NOTCL] %s %s, %s.' % (_("Due of"), str(e), _("action was not executed."))) @@ -932,7 +927,7 @@ class ToolTransform(FlatCAMTool): with self.app.proc_container.new(_("Applying Buffer")): try: for sel_obj in obj_list: - if isinstance(sel_obj, FlatCAMCNCjob): + if sel_obj.kind == 'cncjob': self.app.inform.emit(_("CNCJob objects can't be buffered.")) elif sel_obj.kind.lower() == 'gerber': sel_obj.buffer(value, join, factor) diff --git a/locale/de/LC_MESSAGES/strings.mo b/locale/de/LC_MESSAGES/strings.mo index eccc5899..49aee585 100644 Binary files a/locale/de/LC_MESSAGES/strings.mo and b/locale/de/LC_MESSAGES/strings.mo differ diff --git a/locale/de/LC_MESSAGES/strings.po b/locale/de/LC_MESSAGES/strings.po index 7341ac7f..09b00ee4 100644 --- a/locale/de/LC_MESSAGES/strings.po +++ b/locale/de/LC_MESSAGES/strings.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" -"POT-Creation-Date: 2019-12-28 20:20+0200\n" -"PO-Revision-Date: 2019-12-28 20:24+0200\n" +"POT-Creation-Date: 2020-04-29 13:14+0300\n" +"PO-Revision-Date: 2020-04-29 13:16+0300\n" "Last-Translator: \n" "Language-Team: \n" "Language: de\n" @@ -17,17 +17,17 @@ msgstr "" "X-Poedit-SearchPathExcluded-1: doc\n" "X-Poedit-SearchPathExcluded-2: tests\n" -#: FlatCAMApp.py:1040 +#: FlatCAMApp.py:484 msgid "FlatCAM is initializing ..." msgstr "FlatCAM wird initialisiert ..." -#: FlatCAMApp.py:1669 +#: FlatCAMApp.py:633 msgid "Could not find the Language files. The App strings are missing." msgstr "" "Die Sprachdateien konnten nicht gefunden werden. Die App-Zeichenfolgen " "fehlen." -#: FlatCAMApp.py:1763 +#: FlatCAMApp.py:703 msgid "" "FlatCAM is initializing ...\n" "Canvas initialization started." @@ -35,7 +35,7 @@ msgstr "" "FlatCAM wird initialisiert ...\n" "Die Canvas-Initialisierung wurde gestartet." -#: FlatCAMApp.py:1781 +#: FlatCAMApp.py:723 msgid "" "FlatCAM is initializing ...\n" "Canvas initialization started.\n" @@ -45,70 +45,64 @@ msgstr "" "Die Canvas-Initialisierung wurde gestartet.\n" "Canvas-Initialisierung abgeschlossen in" -#: FlatCAMApp.py:2401 -msgid "" -"Type >help< to get started\n" -"\n" -msgstr "" -"Geben Sie> help help< followed by Run Code for a list of FlatCAM Tcl Commands " +"(displayed in Tcl Shell)." +msgstr "" +"Geben Sie >help< gefolgt von Run Code ein, um eine Liste der FlatCAM Tcl-" +"Befehle anzuzeigen (angezeigt in der Tcl-Shell)." + +#: FlatCAMApp.py:2989 FlatCAMApp.py:2995 FlatCAMApp.py:3001 FlatCAMApp.py:3007 +#: FlatCAMApp.py:3013 FlatCAMApp.py:3019 msgid "created/selected" msgstr "erstellt / ausgewählt" -#: FlatCAMApp.py:4423 FlatCAMApp.py:7086 FlatCAMObj.py:271 FlatCAMObj.py:302 -#: FlatCAMObj.py:318 FlatCAMObj.py:398 flatcamTools/ToolCopperThieving.py:1476 -#: flatcamTools/ToolFiducials.py:807 flatcamTools/ToolMove.py:220 -#: flatcamTools/ToolQRCode.py:726 +#: FlatCAMApp.py:3034 FlatCAMApp.py:5114 flatcamObjects/FlatCAMObj.py:248 +#: flatcamObjects/FlatCAMObj.py:279 flatcamObjects/FlatCAMObj.py:295 +#: flatcamObjects/FlatCAMObj.py:375 flatcamTools/ToolCopperThieving.py:1481 +#: flatcamTools/ToolFiducials.py:809 flatcamTools/ToolMove.py:229 +#: flatcamTools/ToolQRCode.py:728 msgid "Plotting" msgstr "Plotten" -#: FlatCAMApp.py:4486 flatcamGUI/FlatCAMGUI.py:491 +#: FlatCAMApp.py:3097 flatcamGUI/FlatCAMGUI.py:533 msgid "About FlatCAM" msgstr "Über FlatCAM" -#: FlatCAMApp.py:4512 +#: FlatCAMApp.py:3123 msgid "2D Computer-Aided Printed Circuit Board Manufacturing" msgstr "2D-Computer-Aided-Printed-Circuit-Board-Herstellung" -#: FlatCAMApp.py:4513 +#: FlatCAMApp.py:3124 msgid "Development" msgstr "Entwicklung" -#: FlatCAMApp.py:4514 +#: FlatCAMApp.py:3125 msgid "DOWNLOAD" msgstr "HERUNTERLADEN" -#: FlatCAMApp.py:4515 +#: FlatCAMApp.py:3126 msgid "Issue tracker" msgstr "Problem Tracker" -#: FlatCAMApp.py:4519 FlatCAMApp.py:4860 +#: FlatCAMApp.py:3130 FlatCAMApp.py:3474 flatcamGUI/GUIElements.py:2583 msgid "Close" msgstr "Schließen" -#: FlatCAMApp.py:4534 +#: FlatCAMApp.py:3145 msgid "Licensed under the MIT license" msgstr "Lizenziert unter der MIT-Lizenz" -#: FlatCAMApp.py:4543 +#: FlatCAMApp.py:3154 msgid "" "Permission is hereby granted, free of charge, to any person obtaining a " "copy\n" @@ -377,7 +354,7 @@ msgstr "" "ZUSAMMENHANG MIT DER\n" " SOFTWARE ODER SONSTIGER VERWENDUNG DER SOFTWARE ENTSTANDEN." -#: FlatCAMApp.py:4565 +#: FlatCAMApp.py:3176 msgid "" "Some of the icons used are from the following sources:
    Icons by FreepikoNline Web FontsoNline Web Fonts" -#: FlatCAMApp.py:4597 +#: FlatCAMApp.py:3209 msgid "Splash" msgstr "Begrüßungsbildschirm" -#: FlatCAMApp.py:4603 +#: FlatCAMApp.py:3215 msgid "Programmers" msgstr "Programmierer" -#: FlatCAMApp.py:4609 +#: FlatCAMApp.py:3221 msgid "Translators" msgstr "Übersetzer" -#: FlatCAMApp.py:4615 +#: FlatCAMApp.py:3227 msgid "License" msgstr "Lizenz" -#: FlatCAMApp.py:4621 +#: FlatCAMApp.py:3233 msgid "Attributions" msgstr "Zuschreibungen" -#: FlatCAMApp.py:4644 +#: FlatCAMApp.py:3256 msgid "Programmer" msgstr "Programmierer" -#: FlatCAMApp.py:4645 +#: FlatCAMApp.py:3257 msgid "Status" msgstr "Status" -#: FlatCAMApp.py:4646 FlatCAMApp.py:4724 +#: FlatCAMApp.py:3258 FlatCAMApp.py:3337 msgid "E-mail" msgstr "Email" -#: FlatCAMApp.py:4654 +#: FlatCAMApp.py:3266 msgid "BETA Maintainer >= 2019" msgstr "Betreuer >= 2019" -#: FlatCAMApp.py:4721 +#: FlatCAMApp.py:3334 msgid "Language" msgstr "Sprache" -#: FlatCAMApp.py:4722 +#: FlatCAMApp.py:3335 msgid "Translator" msgstr "Übersetzer" -#: FlatCAMApp.py:4723 +#: FlatCAMApp.py:3336 msgid "Corrections" msgstr "Korrekturen" -#: FlatCAMApp.py:4832 FlatCAMApp.py:4840 FlatCAMApp.py:7769 -#: flatcamGUI/FlatCAMGUI.py:473 +#: FlatCAMApp.py:3445 FlatCAMApp.py:3454 flatcamGUI/FlatCAMGUI.py:515 msgid "Bookmarks Manager" msgstr "Lesezeichen verwalten" -#: FlatCAMApp.py:4851 +#: FlatCAMApp.py:3465 msgid "" "This entry will resolve to another website if:\n" "\n" @@ -467,27 +443,15 @@ msgstr "" "Wenn Sie keine Informationen zu FlatCAM beta erhalten können\n" "Verwenden Sie den Link zum YouTube-Kanal im Menü Hilfe." -#: FlatCAMApp.py:4858 +#: FlatCAMApp.py:3472 msgid "Alternative website" msgstr "Alternative Website" -#: FlatCAMApp.py:4989 FlatCAMApp.py:7733 -msgid "Preferences saved." -msgstr "Einstellungen gespeichert." - -#: FlatCAMApp.py:5043 -msgid "Failed to write factory defaults to file." -msgstr "Fehler beim Schreiben der Werkseinstellungen in die Datei." - -#: FlatCAMApp.py:5047 -msgid "Factory defaults saved." -msgstr "Werkseinstellungen gespeichert." - -#: FlatCAMApp.py:5057 flatcamGUI/FlatCAMGUI.py:3962 +#: FlatCAMApp.py:3498 flatcamGUI/FlatCAMGUI.py:4185 msgid "Application is saving the project. Please wait ..." msgstr "Anwendung speichert das Projekt. Warten Sie mal ..." -#: FlatCAMApp.py:5062 FlatCAMTranslation.py:188 +#: FlatCAMApp.py:3503 FlatCAMTranslation.py:202 msgid "" "There are files/objects modified in FlatCAM. \n" "Do you want to Save the project?" @@ -495,32 +459,32 @@ msgstr "" "In FlatCAM wurden Dateien / Objekte geändert.\n" "Möchten Sie das Projekt speichern?" -#: FlatCAMApp.py:5065 FlatCAMApp.py:8925 FlatCAMTranslation.py:191 +#: FlatCAMApp.py:3506 FlatCAMApp.py:7272 FlatCAMTranslation.py:205 msgid "Save changes" msgstr "Änderungen speichern" -#: FlatCAMApp.py:5306 +#: FlatCAMApp.py:3766 msgid "Selected Excellon file extensions registered with FlatCAM." msgstr "" "Ausgewählte Excellon-Dateierweiterungen, die bei FlatCAM registriert sind." -#: FlatCAMApp.py:5328 +#: FlatCAMApp.py:3788 msgid "Selected GCode file extensions registered with FlatCAM." msgstr "" "Ausgewählte GCode-Dateierweiterungen, die bei FlatCAM registriert sind." -#: FlatCAMApp.py:5350 +#: FlatCAMApp.py:3810 msgid "Selected Gerber file extensions registered with FlatCAM." msgstr "" "Ausgewählte Gerber-Dateierweiterungen, die bei FlatCAM registriert sind." -#: FlatCAMApp.py:5538 FlatCAMApp.py:5595 FlatCAMApp.py:5623 +#: FlatCAMApp.py:3998 FlatCAMApp.py:4057 FlatCAMApp.py:4085 msgid "At least two objects are required for join. Objects currently selected" msgstr "" "Zum Verbinden sind mindestens zwei Objekte erforderlich. Derzeit ausgewählte " "Objekte" -#: FlatCAMApp.py:5547 +#: FlatCAMApp.py:4007 msgid "" "Failed join. The Geometry objects are of different types.\n" "At least one is MultiGeo type and the other is SingleGeo type. A possibility " @@ -538,56 +502,52 @@ msgstr "" "und das Ergebnis entspricht möglicherweise nicht dem, was erwartet wurde.\n" "Überprüfen Sie den generierten GCODE." -#: FlatCAMApp.py:5559 -msgid "Multigeo. Geometry merging finished" -msgstr "Erledigt. Gerber-Bearbeitung beendet" - -#: FlatCAMApp.py:5568 +#: FlatCAMApp.py:4019 FlatCAMApp.py:4029 msgid "Geometry merging finished" msgstr "Zusammenführung der Geometrien beendet" -#: FlatCAMApp.py:5590 +#: FlatCAMApp.py:4052 msgid "Failed. Excellon joining works only on Excellon objects." msgstr "" "Gescheitert. Die Zusammenfügung von Excellon funktioniert nur bei Excellon-" "Objekten." -#: FlatCAMApp.py:5600 +#: FlatCAMApp.py:4062 msgid "Excellon merging finished" msgstr "Excellon-Bearbeitung abgeschlossen" -#: FlatCAMApp.py:5618 +#: FlatCAMApp.py:4080 msgid "Failed. Gerber joining works only on Gerber objects." msgstr "" "Gescheitert. Das Zusammenfügen für Gerber-Objekte funktioniert nur bei " "Gerber-Objekten." -#: FlatCAMApp.py:5628 +#: FlatCAMApp.py:4090 msgid "Gerber merging finished" msgstr "Erledigt. Gerber-Bearbeitung beendet" -#: FlatCAMApp.py:5648 FlatCAMApp.py:5683 +#: FlatCAMApp.py:4110 FlatCAMApp.py:4145 msgid "Failed. Select a Geometry Object and try again." msgstr "" "Gescheitert. Wählen Sie ein Geometrieobjekt aus und versuchen Sie es erneut." -#: FlatCAMApp.py:5652 FlatCAMApp.py:5688 -msgid "Expected a FlatCAMGeometry, got" -msgstr "Erwartete eine FlatCAMGeometry, fand" +#: FlatCAMApp.py:4114 FlatCAMApp.py:4150 +msgid "Expected a GeometryObject, got" +msgstr "Erwartet ein GeometryObject, bekam" -#: FlatCAMApp.py:5665 +#: FlatCAMApp.py:4127 msgid "A Geometry object was converted to MultiGeo type." msgstr "Ein Geometrieobjekt wurde in den MultiGeo-Typ konvertiert." -#: FlatCAMApp.py:5703 +#: FlatCAMApp.py:4165 msgid "A Geometry object was converted to SingleGeo type." msgstr "Ein Geometrieobjekt wurde in den SingleGeo-Typ konvertiert." -#: FlatCAMApp.py:5919 +#: FlatCAMApp.py:4392 msgid "Toggle Units" msgstr "Einheiten wechseln" -#: FlatCAMApp.py:5921 +#: FlatCAMApp.py:4394 msgid "" "Changing the units of the project\n" "will scale all objects.\n" @@ -599,49 +559,32 @@ msgstr "" "aller Objekte entsprechend skaliert.\n" "Wollen Sie Fortsetzen?" -#: FlatCAMApp.py:5924 FlatCAMApp.py:6929 FlatCAMApp.py:7005 FlatCAMApp.py:9290 -#: FlatCAMApp.py:9304 FlatCAMApp.py:9658 FlatCAMApp.py:9669 +#: FlatCAMApp.py:4397 FlatCAMApp.py:4950 FlatCAMApp.py:5027 FlatCAMApp.py:7647 +#: FlatCAMApp.py:7661 FlatCAMApp.py:7994 FlatCAMApp.py:8004 msgid "Ok" msgstr "Ok" -#: FlatCAMApp.py:5973 +#: FlatCAMApp.py:4446 msgid "Converted units to" msgstr "Einheiten wurden umgerechnet in" -#: FlatCAMApp.py:5987 -msgid "Units conversion cancelled." -msgstr " Einheitenumrechnung abgebrochen." - -#: FlatCAMApp.py:6613 +#: FlatCAMApp.py:4852 msgid "Detachable Tabs" msgstr "Abnehmbare Laschen" -#: FlatCAMApp.py:6828 FlatCAMApp.py:6889 FlatCAMApp.py:7560 FlatCAMApp.py:7622 -#: FlatCAMApp.py:7688 -msgid "Preferences" -msgstr "Einstellungen" - -#: FlatCAMApp.py:6831 -msgid "Preferences applied." -msgstr "Einstellungen werden angewendet." - -#: FlatCAMApp.py:6894 -msgid "Preferences closed without saving." -msgstr "Einstellungen geschlossen ohne zu speichern." - -#: FlatCAMApp.py:6917 flatcamTools/ToolNonCopperClear.py:591 -#: flatcamTools/ToolNonCopperClear.py:987 flatcamTools/ToolPaint.py:502 -#: flatcamTools/ToolSolderPaste.py:562 flatcamTools/ToolSolderPaste.py:892 +#: FlatCAMApp.py:4939 flatcamTools/ToolNCC.py:932 flatcamTools/ToolNCC.py:1426 +#: flatcamTools/ToolPaint.py:858 flatcamTools/ToolSolderPaste.py:568 +#: flatcamTools/ToolSolderPaste.py:893 msgid "Please enter a tool diameter with non-zero value, in Float format." msgstr "" "Bitte geben Sie einen Werkzeugdurchmesser ungleich Null im Float-Format ein." -#: FlatCAMApp.py:6922 flatcamTools/ToolNonCopperClear.py:595 -#: flatcamTools/ToolPaint.py:506 flatcamTools/ToolSolderPaste.py:566 +#: FlatCAMApp.py:4943 flatcamTools/ToolNCC.py:936 flatcamTools/ToolPaint.py:862 +#: flatcamTools/ToolSolderPaste.py:572 msgid "Adding Tool cancelled" msgstr "Addierwerkzeug abgebrochen" -#: FlatCAMApp.py:6925 +#: FlatCAMApp.py:4946 msgid "" "Adding Tool works only when Advanced is checked.\n" "Go to Preferences -> General - Show Advanced Options." @@ -650,11 +593,11 @@ msgstr "" "ist.\n" "Gehen Sie zu Einstellungen -> Allgemein - Erweiterte Optionen anzeigen." -#: FlatCAMApp.py:7000 +#: FlatCAMApp.py:5022 msgid "Delete objects" msgstr "Objekte löschen" -#: FlatCAMApp.py:7003 +#: FlatCAMApp.py:5025 msgid "" "Are you sure you want to permanently delete\n" "the selected objects?" @@ -662,120 +605,152 @@ msgstr "" "Möchten Sie die ausgewählten Objekte\n" "wirklich dauerhaft löschen?" -#: FlatCAMApp.py:7034 +#: FlatCAMApp.py:5063 msgid "Object(s) deleted" msgstr "Objekt (e) gelöscht" -#: FlatCAMApp.py:7038 flatcamTools/ToolDblSided.py:713 +#: FlatCAMApp.py:5067 FlatCAMApp.py:5222 flatcamTools/ToolDblSided.py:818 msgid "Failed. No object(s) selected..." msgstr "Gescheitert. Kein Objekt ausgewählt ..." -#: FlatCAMApp.py:7040 +#: FlatCAMApp.py:5069 msgid "Save the work in Editor and try again ..." msgstr "Speichern Sie den Editor und versuchen Sie es erneut ..." -#: FlatCAMApp.py:7070 +#: FlatCAMApp.py:5098 msgid "Object deleted" msgstr "Objekt (e) gelöscht" -#: FlatCAMApp.py:7097 +#: FlatCAMApp.py:5125 msgid "Click to set the origin ..." msgstr "Klicken Sie hier, um den Ursprung festzulegen ..." -#: FlatCAMApp.py:7119 +#: FlatCAMApp.py:5147 msgid "Setting Origin..." msgstr "Ursprung setzten ..." -#: FlatCAMApp.py:7131 +#: FlatCAMApp.py:5160 FlatCAMApp.py:5262 msgid "Origin set" msgstr "Ursprung gesetzt" -#: FlatCAMApp.py:7138 +#: FlatCAMApp.py:5177 msgid "Origin coordinates specified but incomplete." msgstr "Ursprungskoordinaten angegeben, aber unvollständig." -#: FlatCAMApp.py:7197 +#: FlatCAMApp.py:5218 +msgid "Moving to Origin..." +msgstr "Umzug zum Ursprung ..." + +#: FlatCAMApp.py:5299 msgid "Jump to ..." msgstr "Springen zu ..." -#: FlatCAMApp.py:7198 +#: FlatCAMApp.py:5300 msgid "Enter the coordinates in format X,Y:" msgstr "Geben Sie die Koordinaten im Format X, Y ein:" -#: FlatCAMApp.py:7208 +#: FlatCAMApp.py:5310 msgid "Wrong coordinates. Enter coordinates in format: X,Y" msgstr "Falsche Koordinaten. Koordinaten im Format eingeben: X, Y" -#: FlatCAMApp.py:7288 flatcamEditors/FlatCAMExcEditor.py:3599 -#: flatcamEditors/FlatCAMExcEditor.py:3607 -#: flatcamEditors/FlatCAMGeoEditor.py:4036 -#: flatcamEditors/FlatCAMGeoEditor.py:4051 -#: flatcamEditors/FlatCAMGrbEditor.py:1086 -#: flatcamEditors/FlatCAMGrbEditor.py:1203 -#: flatcamEditors/FlatCAMGrbEditor.py:1489 -#: flatcamEditors/FlatCAMGrbEditor.py:1758 -#: flatcamEditors/FlatCAMGrbEditor.py:4445 -#: flatcamEditors/FlatCAMGrbEditor.py:4460 flatcamGUI/FlatCAMGUI.py:3145 -#: flatcamGUI/FlatCAMGUI.py:3157 +#: FlatCAMApp.py:5388 FlatCAMApp.py:5537 +#: flatcamEditors/FlatCAMExcEditor.py:3624 +#: flatcamEditors/FlatCAMExcEditor.py:3632 +#: flatcamEditors/FlatCAMGeoEditor.py:4349 +#: flatcamEditors/FlatCAMGeoEditor.py:4363 +#: flatcamEditors/FlatCAMGrbEditor.py:1085 +#: flatcamEditors/FlatCAMGrbEditor.py:1202 +#: flatcamEditors/FlatCAMGrbEditor.py:1488 +#: flatcamEditors/FlatCAMGrbEditor.py:1757 +#: flatcamEditors/FlatCAMGrbEditor.py:4489 +#: flatcamEditors/FlatCAMGrbEditor.py:4504 flatcamGUI/FlatCAMGUI.py:3377 +#: flatcamGUI/FlatCAMGUI.py:3389 flatcamTools/ToolAlignObjects.py:393 +#: flatcamTools/ToolAlignObjects.py:415 msgid "Done." msgstr "Fertig." -#: FlatCAMApp.py:7440 FlatCAMApp.py:7511 +#: FlatCAMApp.py:5403 FlatCAMApp.py:7643 FlatCAMApp.py:7738 FlatCAMApp.py:7779 +#: FlatCAMApp.py:7820 FlatCAMApp.py:7861 FlatCAMApp.py:7902 FlatCAMApp.py:7946 +#: FlatCAMApp.py:7990 FlatCAMApp.py:8517 FlatCAMApp.py:8521 +#: flatcamTools/ToolProperties.py:116 +msgid "No object selected." +msgstr "Kein Objekt ausgewählt." + +#: FlatCAMApp.py:5422 +msgid "Bottom-Left" +msgstr "Unten links" + +#: FlatCAMApp.py:5423 flatcamGUI/PreferencesUI.py:9227 +#: flatcamTools/ToolCalibration.py:159 +msgid "Top-Left" +msgstr "Oben links" + +#: FlatCAMApp.py:5424 flatcamGUI/PreferencesUI.py:9228 +#: flatcamTools/ToolCalibration.py:160 +msgid "Bottom-Right" +msgstr "Unten rechts" + +#: FlatCAMApp.py:5425 +msgid "Top-Right" +msgstr "Oben rechts" + +#: FlatCAMApp.py:5426 flatcamGUI/ObjectUI.py:2626 +msgid "Center" +msgstr "Center" + +#: FlatCAMApp.py:5446 +msgid "Locate ..." +msgstr "Lokalisieren ..." + +#: FlatCAMApp.py:5706 FlatCAMApp.py:5783 msgid "No object is selected. Select an object and try again." msgstr "" "Es ist kein Objekt ausgewählt. Wählen Sie ein Objekt und versuchen Sie es " "erneut." -#: FlatCAMApp.py:7531 +#: FlatCAMApp.py:5809 msgid "" "Aborting. The current task will be gracefully closed as soon as possible..." msgstr "" "Abbrechen. Die aktuelle Aufgabe wird so schnell wie möglich ordnungsgemäß " "abgeschlossen ..." -#: FlatCAMApp.py:7537 +#: FlatCAMApp.py:5815 msgid "The current task was gracefully closed on user request..." msgstr "" "Die aktuelle Aufgabe wurde auf Benutzeranforderung ordnungsgemäß " "geschlossen ..." -#: FlatCAMApp.py:7619 -msgid "Preferences edited but not saved." -msgstr "Einstellungen bearbeitet, aber nicht gespeichert." +#: FlatCAMApp.py:5843 flatcamGUI/PreferencesUI.py:909 +#: flatcamGUI/PreferencesUI.py:953 flatcamGUI/PreferencesUI.py:974 +#: flatcamGUI/PreferencesUI.py:1079 +msgid "Preferences" +msgstr "Einstellungen" -#: FlatCAMApp.py:7633 FlatCAMApp.py:7645 FlatCAMApp.py:7662 FlatCAMApp.py:7679 -#: FlatCAMApp.py:7739 FlatCAMCommon.py:1181 FlatCAMCommon.py:1356 -#: FlatCAMObj.py:4256 +#: FlatCAMApp.py:5909 FlatCAMApp.py:5937 FlatCAMApp.py:5964 FlatCAMApp.py:5983 +#: FlatCAMDB.py:738 FlatCAMDB.py:913 FlatCAMDB.py:2168 FlatCAMDB.py:2378 +#: flatcamObjects/FlatCAMGeometry.py:862 flatcamTools/ToolNCC.py:3958 +#: flatcamTools/ToolNCC.py:4042 flatcamTools/ToolPaint.py:3548 +#: flatcamTools/ToolPaint.py:3633 msgid "Tools Database" msgstr "Werkzeugdatenbank" -#: FlatCAMApp.py:7659 +#: FlatCAMApp.py:5961 msgid "Tools in Tools Database edited but not saved." msgstr "Werkzeugdatenbank geschlossen ohne zu speichern." -#: FlatCAMApp.py:7683 +#: FlatCAMApp.py:5987 flatcamTools/ToolNCC.py:3965 +#: flatcamTools/ToolPaint.py:3555 msgid "Tool from DB added in Tool Table." msgstr "Werkzeug aus Werkzeugdatenbank zur Werkzeugtabelle hinzugefügt." -#: FlatCAMApp.py:7685 +#: FlatCAMApp.py:5989 msgid "Adding tool from DB is not allowed for this object." msgstr "" "Das Hinzufügen von Werkzeugen aus der Datenbank ist für dieses Objekt nicht " "zulässig." -#: FlatCAMApp.py:7719 -msgid "" -"One or more values are changed.\n" -"Do you want to save the Preferences?" -msgstr "" -"Ein oder mehrere Werte werden geändert.\n" -"Möchten Sie die Einstellungen speichern?" - -#: FlatCAMApp.py:7721 flatcamGUI/FlatCAMGUI.py:222 -msgid "Save Preferences" -msgstr "Einstellungen speichern" - -#: FlatCAMApp.py:7745 +#: FlatCAMApp.py:6007 msgid "" "One or more Tools are edited.\n" "Do you want to update the Tools Database?" @@ -783,174 +758,175 @@ msgstr "" "Ein oder mehrere Werkzeuge wurden geändert.\n" "Möchten Sie die Werkzeugdatenbank aktualisieren?" -#: FlatCAMApp.py:7747 +#: FlatCAMApp.py:6009 msgid "Save Tools Database" msgstr "Werkzeugdatenbank speichern" -#: FlatCAMApp.py:7766 FlatCAMApp.py:9897 FlatCAMObj.py:6509 -msgid "Code Editor" -msgstr "Code-Editor" - -#: FlatCAMApp.py:7784 +#: FlatCAMApp.py:6062 msgid "No object selected to Flip on Y axis." msgstr "Kein Objekt ausgewählt, um auf der Y-Achse zu spiegeln." -#: FlatCAMApp.py:7810 +#: FlatCAMApp.py:6088 msgid "Flip on Y axis done." msgstr "Y-Achse spiegeln fertig." -#: FlatCAMApp.py:7812 FlatCAMApp.py:7854 -#: flatcamEditors/FlatCAMGrbEditor.py:5858 +#: FlatCAMApp.py:6090 FlatCAMApp.py:6138 +#: flatcamEditors/FlatCAMGrbEditor.py:5893 msgid "Flip action was not executed." msgstr "Flip-Aktion wurde nicht ausgeführt." -#: FlatCAMApp.py:7826 +#: FlatCAMApp.py:6110 msgid "No object selected to Flip on X axis." msgstr "Es wurde kein Objekt zum Spiegeln auf der X-Achse ausgewählt." -#: FlatCAMApp.py:7852 +#: FlatCAMApp.py:6136 msgid "Flip on X axis done." msgstr "Flip on X axis done." -#: FlatCAMApp.py:7868 +#: FlatCAMApp.py:6158 msgid "No object selected to Rotate." msgstr "Es wurde kein Objekt zum Drehen ausgewählt." -#: FlatCAMApp.py:7871 FlatCAMApp.py:7918 FlatCAMApp.py:7951 +#: FlatCAMApp.py:6161 FlatCAMApp.py:6214 FlatCAMApp.py:6253 msgid "Transform" msgstr "Verwandeln" -#: FlatCAMApp.py:7871 FlatCAMApp.py:7918 FlatCAMApp.py:7951 +#: FlatCAMApp.py:6161 FlatCAMApp.py:6214 FlatCAMApp.py:6253 msgid "Enter the Angle value:" msgstr "Geben Sie den Winkelwert ein:" -#: FlatCAMApp.py:7902 +#: FlatCAMApp.py:6192 msgid "Rotation done." msgstr "Rotation abgeschlossen." -#: FlatCAMApp.py:7904 +#: FlatCAMApp.py:6194 msgid "Rotation movement was not executed." msgstr "Drehbewegung wurde nicht ausgeführt." -#: FlatCAMApp.py:7916 +#: FlatCAMApp.py:6212 msgid "No object selected to Skew/Shear on X axis." msgstr "Auf der X-Achse wurde kein Objekt zum Neigen / Schneiden ausgewählt." -#: FlatCAMApp.py:7938 +#: FlatCAMApp.py:6234 msgid "Skew on X axis done." msgstr "Neigung auf der X-Achse." -#: FlatCAMApp.py:7949 +#: FlatCAMApp.py:6251 msgid "No object selected to Skew/Shear on Y axis." msgstr "Kein Objekt für Neigung / Schneiden auf der Y-Achse ausgewählt." -#: FlatCAMApp.py:7971 +#: FlatCAMApp.py:6273 msgid "Skew on Y axis done." msgstr "Neigung auf der Y-Achse." -#: FlatCAMApp.py:8119 FlatCAMApp.py:8166 flatcamGUI/FlatCAMGUI.py:449 -#: flatcamGUI/FlatCAMGUI.py:1612 +#: FlatCAMApp.py:6424 FlatCAMApp.py:6471 flatcamGUI/FlatCAMGUI.py:491 +#: flatcamGUI/FlatCAMGUI.py:1716 msgid "Select All" msgstr "Select All" -#: FlatCAMApp.py:8123 FlatCAMApp.py:8170 flatcamGUI/FlatCAMGUI.py:451 +#: FlatCAMApp.py:6428 FlatCAMApp.py:6475 flatcamGUI/FlatCAMGUI.py:493 msgid "Deselect All" msgstr "Alle abwählen" -#: FlatCAMApp.py:8186 +#: FlatCAMApp.py:6491 msgid "All objects are selected." msgstr "Alle Objekte werden ausgewählt." -#: FlatCAMApp.py:8196 +#: FlatCAMApp.py:6501 msgid "Objects selection is cleared." msgstr "Die Objektauswahl wird gelöscht." -#: FlatCAMApp.py:8216 flatcamGUI/FlatCAMGUI.py:1605 +#: FlatCAMApp.py:6521 flatcamGUI/FlatCAMGUI.py:1709 msgid "Grid On/Off" msgstr "Raster ein/aus" -#: FlatCAMApp.py:8228 flatcamEditors/FlatCAMGeoEditor.py:940 -#: flatcamEditors/FlatCAMGrbEditor.py:2574 -#: flatcamEditors/FlatCAMGrbEditor.py:5431 flatcamGUI/ObjectUI.py:1304 -#: flatcamTools/ToolDblSided.py:187 flatcamTools/ToolDblSided.py:245 -#: flatcamTools/ToolNonCopperClear.py:286 flatcamTools/ToolPaint.py:188 -#: flatcamTools/ToolSolderPaste.py:121 flatcamTools/ToolSolderPaste.py:591 -#: flatcamTools/ToolTransform.py:310 +#: FlatCAMApp.py:6533 flatcamEditors/FlatCAMGeoEditor.py:939 +#: flatcamEditors/FlatCAMGrbEditor.py:2580 +#: flatcamEditors/FlatCAMGrbEditor.py:5475 flatcamGUI/ObjectUI.py:1595 +#: flatcamTools/ToolDblSided.py:192 flatcamTools/ToolDblSided.py:425 +#: flatcamTools/ToolNCC.py:294 flatcamTools/ToolNCC.py:631 +#: flatcamTools/ToolPaint.py:277 flatcamTools/ToolPaint.py:676 +#: flatcamTools/ToolSolderPaste.py:122 flatcamTools/ToolSolderPaste.py:597 +#: flatcamTools/ToolTransform.py:478 msgid "Add" msgstr "Hinzufügen" -#: FlatCAMApp.py:8230 FlatCAMObj.py:3963 -#: flatcamEditors/FlatCAMGrbEditor.py:2579 -#: flatcamEditors/FlatCAMGrbEditor.py:2727 flatcamGUI/FlatCAMGUI.py:680 -#: flatcamGUI/FlatCAMGUI.py:991 flatcamGUI/FlatCAMGUI.py:2018 -#: flatcamGUI/FlatCAMGUI.py:2161 flatcamGUI/FlatCAMGUI.py:2559 -#: flatcamGUI/ObjectUI.py:1330 flatcamTools/ToolNonCopperClear.py:298 -#: flatcamTools/ToolPaint.py:200 flatcamTools/ToolSolderPaste.py:127 -#: flatcamTools/ToolSolderPaste.py:594 +#: FlatCAMApp.py:6535 flatcamEditors/FlatCAMGrbEditor.py:2585 +#: flatcamEditors/FlatCAMGrbEditor.py:2733 flatcamGUI/FlatCAMGUI.py:739 +#: flatcamGUI/FlatCAMGUI.py:1062 flatcamGUI/FlatCAMGUI.py:2129 +#: flatcamGUI/FlatCAMGUI.py:2272 flatcamGUI/FlatCAMGUI.py:2740 +#: flatcamGUI/ObjectUI.py:1623 flatcamObjects/FlatCAMGeometry.py:480 +#: flatcamTools/ToolNCC.py:316 flatcamTools/ToolNCC.py:637 +#: flatcamTools/ToolPaint.py:299 flatcamTools/ToolPaint.py:682 +#: flatcamTools/ToolSolderPaste.py:128 flatcamTools/ToolSolderPaste.py:600 msgid "Delete" msgstr "Löschen" -#: FlatCAMApp.py:8243 +#: FlatCAMApp.py:6551 msgid "New Grid ..." msgstr "Neues Raster ..." -#: FlatCAMApp.py:8244 +#: FlatCAMApp.py:6552 msgid "Enter a Grid Value:" msgstr "Geben Sie einen Rasterwert ein:" -#: FlatCAMApp.py:8252 FlatCAMApp.py:8279 +#: FlatCAMApp.py:6560 FlatCAMApp.py:6587 msgid "Please enter a grid value with non-zero value, in Float format." msgstr "" "Bitte geben Sie im Float-Format einen Rasterwert mit einem Wert ungleich " "Null ein." -#: FlatCAMApp.py:8258 +#: FlatCAMApp.py:6566 msgid "New Grid added" msgstr "Neues Raster" -#: FlatCAMApp.py:8261 +#: FlatCAMApp.py:6569 msgid "Grid already exists" msgstr "Netz existiert bereits" -#: FlatCAMApp.py:8264 +#: FlatCAMApp.py:6572 msgid "Adding New Grid cancelled" msgstr "Neues Netz wurde abgebrochen" -#: FlatCAMApp.py:8286 +#: FlatCAMApp.py:6594 msgid " Grid Value does not exist" msgstr " Rasterwert existiert nicht" -#: FlatCAMApp.py:8289 +#: FlatCAMApp.py:6597 msgid "Grid Value deleted" msgstr "Rasterwert gelöscht" -#: FlatCAMApp.py:8292 +#: FlatCAMApp.py:6600 msgid "Delete Grid value cancelled" msgstr "Rasterwert löschen abgebrochen" -#: FlatCAMApp.py:8298 +#: FlatCAMApp.py:6606 msgid "Key Shortcut List" msgstr "Tastenkürzel Liste" -#: FlatCAMApp.py:8332 +#: FlatCAMApp.py:6640 msgid " No object selected to copy it's name" msgstr " Kein Objekt zum Kopieren des Namens ausgewählt" -#: FlatCAMApp.py:8336 +#: FlatCAMApp.py:6644 msgid "Name copied on clipboard ..." msgstr "Name in Zwischenablage kopiert ..." -#: FlatCAMApp.py:8534 flatcamEditors/FlatCAMGrbEditor.py:4377 +#: FlatCAMApp.py:6857 flatcamEditors/FlatCAMGrbEditor.py:4421 msgid "Coordinates copied to clipboard." msgstr "Koordinaten in die Zwischenablage kopiert." -#: FlatCAMApp.py:8762 FlatCAMApp.py:8768 FlatCAMApp.py:8774 FlatCAMApp.py:8780 -#: ObjectCollection.py:797 ObjectCollection.py:803 ObjectCollection.py:809 -#: ObjectCollection.py:815 ObjectCollection.py:821 ObjectCollection.py:827 +#: FlatCAMApp.py:7096 FlatCAMApp.py:7102 FlatCAMApp.py:7108 FlatCAMApp.py:7114 +#: flatcamObjects/ObjectCollection.py:922 +#: flatcamObjects/ObjectCollection.py:928 +#: flatcamObjects/ObjectCollection.py:934 +#: flatcamObjects/ObjectCollection.py:940 +#: flatcamObjects/ObjectCollection.py:946 +#: flatcamObjects/ObjectCollection.py:952 msgid "selected" msgstr "ausgewählt" -#: FlatCAMApp.py:8922 +#: FlatCAMApp.py:7269 msgid "" "There are files/objects opened in FlatCAM.\n" "Creating a New project will delete them.\n" @@ -960,385 +936,283 @@ msgstr "" "Wenn Sie ein neues Projekt erstellen, werden diese gelöscht.\n" "Möchten Sie das Projekt speichern?" -#: FlatCAMApp.py:8944 +#: FlatCAMApp.py:7290 msgid "New Project created" msgstr "Neues Projekt erstellt" -#: FlatCAMApp.py:9079 FlatCAMApp.py:9083 flatcamGUI/FlatCAMGUI.py:767 -#: flatcamGUI/FlatCAMGUI.py:2352 +#: FlatCAMApp.py:7438 FlatCAMApp.py:7442 flatcamGUI/FlatCAMGUI.py:824 +#: flatcamGUI/FlatCAMGUI.py:2507 msgid "Open Gerber" msgstr "Gerber öffnen" -#: FlatCAMApp.py:9090 +#: FlatCAMApp.py:7447 FlatCAMApp.py:7484 FlatCAMApp.py:7526 FlatCAMApp.py:7596 +#: FlatCAMApp.py:8386 FlatCAMApp.py:9577 FlatCAMApp.py:9639 +msgid "" +"Canvas initialization started.\n" +"Canvas initialization finished in" +msgstr "" +"Die Canvas-Initialisierung wurde gestartet.\n" +"Canvas-Initialisierung abgeschlossen in" + +#: FlatCAMApp.py:7449 msgid "Opening Gerber file." msgstr "Gerber-Datei öffnen." -#: FlatCAMApp.py:9096 -msgid "Open Gerber cancelled." -msgstr "Öffnen der Gerberdatei abgebrochen." - -#: FlatCAMApp.py:9117 FlatCAMApp.py:9121 flatcamGUI/FlatCAMGUI.py:769 -#: flatcamGUI/FlatCAMGUI.py:2354 +#: FlatCAMApp.py:7476 FlatCAMApp.py:7480 flatcamGUI/FlatCAMGUI.py:826 +#: flatcamGUI/FlatCAMGUI.py:2509 msgid "Open Excellon" msgstr "Excellon öffnen" -#: FlatCAMApp.py:9127 +#: FlatCAMApp.py:7486 msgid "Opening Excellon file." msgstr "Excellon-Datei öffnen." -#: FlatCAMApp.py:9133 -msgid " Open Excellon cancelled." -msgstr " Öffnen der Excellon-Datei abgebrochen." - -#: FlatCAMApp.py:9157 FlatCAMApp.py:9161 +#: FlatCAMApp.py:7517 FlatCAMApp.py:7521 msgid "Open G-Code" msgstr "G-Code öffnen" -#: FlatCAMApp.py:9168 +#: FlatCAMApp.py:7528 msgid "Opening G-Code file." msgstr "Öffnen der G-Code-Datei." -#: FlatCAMApp.py:9174 -msgid "Open G-Code cancelled." -msgstr "Öffnen der G-Code-Datei abgebrochen." - -#: FlatCAMApp.py:9192 FlatCAMApp.py:9195 flatcamGUI/FlatCAMGUI.py:1614 +#: FlatCAMApp.py:7551 FlatCAMApp.py:7554 flatcamGUI/FlatCAMGUI.py:1718 msgid "Open Project" msgstr "Projekt öffnen" -#: FlatCAMApp.py:9204 -msgid "Open Project cancelled." -msgstr "Projektdatei öffnen abgebrochen." - -#: FlatCAMApp.py:9228 FlatCAMApp.py:9232 +#: FlatCAMApp.py:7587 FlatCAMApp.py:7591 msgid "Open HPGL2" msgstr "HPGL2 öffnen" -#: FlatCAMApp.py:9239 +#: FlatCAMApp.py:7598 msgid "Opening HPGL2 file." msgstr "HPGL2-Datei öffnen." -#: FlatCAMApp.py:9244 -msgid "Open HPGL2 file cancelled." -msgstr "Öffnen der HPGL2-Datei abgebrochen." - -#: FlatCAMApp.py:9262 FlatCAMApp.py:9265 +#: FlatCAMApp.py:7621 FlatCAMApp.py:7624 msgid "Open Configuration File" msgstr "Einstellungsdatei öffne" -#: FlatCAMApp.py:9270 -msgid "Open Config cancelled." -msgstr "Öffnen der Konfigurationsdatei abgebrochen." - -#: FlatCAMApp.py:9286 FlatCAMApp.py:9654 FlatCAMApp.py:10124 -#: FlatCAMApp.py:10128 -msgid "No object selected." -msgstr "Kein Objekt ausgewählt." - -#: FlatCAMApp.py:9287 FlatCAMApp.py:9655 +#: FlatCAMApp.py:7644 FlatCAMApp.py:7991 msgid "Please Select a Geometry object to export" msgstr "Bitte wählen Sie ein Geometrieobjekt zum Exportieren aus" -#: FlatCAMApp.py:9301 +#: FlatCAMApp.py:7658 msgid "Only Geometry, Gerber and CNCJob objects can be used." msgstr "Es können nur Geometrie-, Gerber- und CNCJob-Objekte verwendet werden." -#: FlatCAMApp.py:9314 FlatCAMApp.py:9318 flatcamTools/ToolQRCode.py:827 -#: flatcamTools/ToolQRCode.py:831 +#: FlatCAMApp.py:7671 FlatCAMApp.py:7675 flatcamTools/ToolQRCode.py:829 +#: flatcamTools/ToolQRCode.py:833 msgid "Export SVG" msgstr "SVG exportieren" -#: FlatCAMApp.py:9324 flatcamTools/ToolQRCode.py:836 -msgid " Export SVG cancelled." -msgstr " Export von SVG abgebrochen." - -#: FlatCAMApp.py:9345 +#: FlatCAMApp.py:7700 msgid "Data must be a 3D array with last dimension 3 or 4" msgstr "Daten müssen ein 3D-Array mit der letzten Dimension 3 oder 4 sein" -#: FlatCAMApp.py:9351 FlatCAMApp.py:9355 +#: FlatCAMApp.py:7706 FlatCAMApp.py:7710 msgid "Export PNG Image" msgstr "PNG-Bild exportieren" -#: FlatCAMApp.py:9360 -msgid "Export PNG cancelled." -msgstr "Export PNG abgebrochen." - -#: FlatCAMApp.py:9384 -msgid "No object selected. Please select an Gerber object to export." -msgstr "" -"Kein Objekt ausgewählt. Bitte wählen Sie ein Gerber-Objekt aus, das Sie " -"exportieren möchten." - -#: FlatCAMApp.py:9390 FlatCAMApp.py:9613 +#: FlatCAMApp.py:7743 FlatCAMApp.py:7951 msgid "Failed. Only Gerber objects can be saved as Gerber files..." msgstr "" "Fehlgeschlagen. Nur Gerber-Objekte können als Gerber-Dateien gespeichert " "werden ..." -#: FlatCAMApp.py:9402 +#: FlatCAMApp.py:7755 msgid "Save Gerber source file" msgstr "Gerber-Quelldatei speichern" -#: FlatCAMApp.py:9408 -msgid "Save Gerber source file cancelled." -msgstr "Gerber Quelldatei speichern abgebrochen." - -#: FlatCAMApp.py:9428 -msgid "No object selected. Please select an Script object to export." -msgstr "" -"Kein Objekt ausgewählt. Bitte wählen Sie ein zu exportierendes Script-Objekt." - -#: FlatCAMApp.py:9434 +#: FlatCAMApp.py:7784 msgid "Failed. Only Script objects can be saved as TCL Script files..." msgstr "" "Gescheitert. Nur Skriptobjekte können als TCL-Skriptdateien gespeichert " "werden ..." -#: FlatCAMApp.py:9446 +#: FlatCAMApp.py:7796 msgid "Save Script source file" msgstr "Speichern Sie die Quelldatei des Skripts" -#: FlatCAMApp.py:9452 -msgid "Save Script source file cancelled." -msgstr "Speichern der Skript-Quelldatei abgebrochen." - -#: FlatCAMApp.py:9472 -msgid "No object selected. Please select an Document object to export." -msgstr "" -"Kein Objekt ausgewählt. Bitte wählen Sie ein zu exportierendes " -"Dokumentobjekt aus." - -#: FlatCAMApp.py:9478 +#: FlatCAMApp.py:7825 msgid "Failed. Only Document objects can be saved as Document files..." msgstr "" "Gescheitert. Nur Dokumentobjekte können als Dokumentdateien gespeichert " "werden ..." -#: FlatCAMApp.py:9490 +#: FlatCAMApp.py:7837 msgid "Save Document source file" msgstr "Speichern Sie die Quelldatei des Dokuments" -#: FlatCAMApp.py:9496 -msgid "Save Document source file cancelled." -msgstr "Quelldatei des Dokuments speichern abgebrochen." - -#: FlatCAMApp.py:9516 -msgid "No object selected. Please select an Excellon object to export." -msgstr "" -"Kein Objekt ausgewählt Bitte wählen Sie ein Excellon-Objekt zum Exportieren " -"aus." - -#: FlatCAMApp.py:9522 FlatCAMApp.py:9566 FlatCAMApp.py:10473 +#: FlatCAMApp.py:7866 FlatCAMApp.py:7907 FlatCAMApp.py:8869 msgid "Failed. Only Excellon objects can be saved as Excellon files..." msgstr "" "Fehlgeschlagen. Nur Excellon-Objekte können als Excellon-Dateien gespeichert " "werden ..." -#: FlatCAMApp.py:9530 FlatCAMApp.py:9534 +#: FlatCAMApp.py:7874 FlatCAMApp.py:7878 msgid "Save Excellon source file" msgstr "Speichern Sie die Excellon-Quelldatei" -#: FlatCAMApp.py:9540 -msgid "Saving Excellon source file cancelled." -msgstr "Speichern der Excellon-Quelldatei abgebrochen." - -#: FlatCAMApp.py:9560 -msgid "No object selected. Please Select an Excellon object to export." -msgstr "" -"Kein Objekt ausgewählt. Bitte wählen Sie ein Excellon-Objekt aus, das Sie " -"exportieren möchten." - -#: FlatCAMApp.py:9574 FlatCAMApp.py:9578 +#: FlatCAMApp.py:7915 FlatCAMApp.py:7919 msgid "Export Excellon" msgstr "Excellon exportieren" -#: FlatCAMApp.py:9584 -msgid "Export Excellon cancelled." -msgstr "Export der Excellon-Datei abgebrochen." - -#: FlatCAMApp.py:9607 -msgid "No object selected. Please Select an Gerber object to export." -msgstr "" -"Kein Objekt ausgewählt. Bitte wählen Sie ein Gerber-Objekt aus, das Sie " -"exportieren möchten." - -#: FlatCAMApp.py:9621 FlatCAMApp.py:9625 +#: FlatCAMApp.py:7959 FlatCAMApp.py:7963 msgid "Export Gerber" msgstr "Gerber exportieren" -#: FlatCAMApp.py:9631 -msgid "Export Gerber cancelled." -msgstr "Export der Gerberdatei abgebrochen." - -#: FlatCAMApp.py:9666 +#: FlatCAMApp.py:8001 msgid "Only Geometry objects can be used." msgstr "Es können nur Geometrieobjekte verwendet werden." -#: FlatCAMApp.py:9680 FlatCAMApp.py:9684 +#: FlatCAMApp.py:8015 FlatCAMApp.py:8019 msgid "Export DXF" msgstr "DXF exportieren" -#: FlatCAMApp.py:9691 -msgid "Export DXF cancelled." -msgstr "Export der DXF-Datei abgebrochen." - -#: FlatCAMApp.py:9711 FlatCAMApp.py:9714 +#: FlatCAMApp.py:8044 FlatCAMApp.py:8047 msgid "Import SVG" msgstr "SVG importieren" -#: FlatCAMApp.py:9724 -msgid "Open SVG cancelled." -msgstr "Öffnen der SVG-Datei abgebrochen." - -#: FlatCAMApp.py:9743 FlatCAMApp.py:9747 +#: FlatCAMApp.py:8075 FlatCAMApp.py:8079 msgid "Import DXF" msgstr "Importieren Sie DXF" -#: FlatCAMApp.py:9757 -msgid "Open DXF cancelled." -msgstr "Öffnen der DXF-Datei abgebrochen." - -#: FlatCAMApp.py:9799 +#: FlatCAMApp.py:8130 msgid "Viewing the source code of the selected object." msgstr "Anzeigen des Quellcodes des ausgewählten Objekts." -#: FlatCAMApp.py:9800 FlatCAMObj.py:6495 FlatCAMObj.py:7225 +#: FlatCAMApp.py:8131 flatcamObjects/FlatCAMCNCJob.py:548 +#: flatcamObjects/FlatCAMScript.py:133 msgid "Loading..." msgstr "Wird geladen..." -#: FlatCAMApp.py:9806 FlatCAMApp.py:9810 +#: FlatCAMApp.py:8137 FlatCAMApp.py:8141 msgid "Select an Gerber or Excellon file to view it's source file." msgstr "" "Wählen Sie eine Gerber- oder Excellon-Datei aus, um die Quelldatei " "anzuzeigen." -#: FlatCAMApp.py:9824 +#: FlatCAMApp.py:8155 msgid "Source Editor" msgstr "Quelleditor" -#: FlatCAMApp.py:9864 FlatCAMApp.py:9871 +#: FlatCAMApp.py:8195 FlatCAMApp.py:8202 msgid "There is no selected object for which to see it's source file code." msgstr "" "Es gibt kein ausgewähltes Objekt, für das man seinen Quelldateien sehen kann." -#: FlatCAMApp.py:9883 +#: FlatCAMApp.py:8214 msgid "Failed to load the source code for the selected object" msgstr "Fehler beim Laden des Quellcodes für das ausgewählte Objekt" -#: FlatCAMApp.py:9925 +#: FlatCAMApp.py:8228 flatcamObjects/FlatCAMCNCJob.py:562 +msgid "Code Editor" +msgstr "Code-Editor" + +#: FlatCAMApp.py:8250 +msgid "Go to Line ..." +msgstr "Gehe zur Linie ..." + +#: FlatCAMApp.py:8251 +msgid "Line:" +msgstr "Linie:" + +#: FlatCAMApp.py:8280 msgid "New TCL script file created in Code Editor." msgstr "Neue TCL-Skriptdatei, die im Code-Editor erstellt wurde." -#: FlatCAMApp.py:9963 FlatCAMApp.py:9965 +#: FlatCAMApp.py:8319 FlatCAMApp.py:8321 FlatCAMApp.py:8358 FlatCAMApp.py:8360 msgid "Open TCL script" msgstr "Öffnen Sie das TCL-Skript" -#: FlatCAMApp.py:9969 -msgid "Open TCL script cancelled." -msgstr "Öffnen der TCL-Skriptdatei abgebrochen." +#: FlatCAMApp.py:8388 +msgid "Executing ScriptObject file." +msgstr "Ausführen der ScriptObject-Datei." -#: FlatCAMApp.py:9993 -msgid "Executing FlatCAMScript file." -msgstr "FlatCAMScript-Datei wird ausgeführt." - -#: FlatCAMApp.py:10000 FlatCAMApp.py:10003 +#: FlatCAMApp.py:8396 FlatCAMApp.py:8399 msgid "Run TCL script" msgstr "Führen Sie das TCL-Skript aus" -#: FlatCAMApp.py:10013 -msgid "Run TCL script cancelled." -msgstr "Ausführen der TCL-Skriptdatei abgebrochen." - -#: FlatCAMApp.py:10029 +#: FlatCAMApp.py:8422 msgid "TCL script file opened in Code Editor and executed." msgstr "TCL-Skriptdatei im Code-Editor geöffnet und ausgeführt." -#: FlatCAMApp.py:10080 FlatCAMApp.py:10086 +#: FlatCAMApp.py:8473 FlatCAMApp.py:8479 msgid "Save Project As ..." msgstr "Projekt speichern als ..." -#: FlatCAMApp.py:10082 flatcamGUI/FlatCAMGUI.py:1051 -#: flatcamGUI/FlatCAMGUI.py:2053 +#: FlatCAMApp.py:8475 flatcamGUI/FlatCAMGUI.py:1122 +#: flatcamGUI/FlatCAMGUI.py:2164 msgid "Project" msgstr "Projekt" -#: FlatCAMApp.py:10091 -msgid "Save Project cancelled." -msgstr "Projekt speichern abgebrochen." - -#: FlatCAMApp.py:10121 +#: FlatCAMApp.py:8514 msgid "FlatCAM objects print" msgstr "FlatCAM-Objekte werden gedruckt" -#: FlatCAMApp.py:10134 FlatCAMApp.py:10141 +#: FlatCAMApp.py:8527 FlatCAMApp.py:8534 msgid "Save Object as PDF ..." msgstr "Objekt als PDF speichern ..." -#: FlatCAMApp.py:10146 -msgid "Save Object PDF cancelled." -msgstr "Objekt speichern PDF abgebrochen." - -#: FlatCAMApp.py:10150 +#: FlatCAMApp.py:8543 msgid "Printing PDF ... Please wait." msgstr "PDF wird gedruckt ... Bitte warten." -#: FlatCAMApp.py:10329 +#: FlatCAMApp.py:8722 msgid "PDF file saved to" msgstr "PDF-Datei gespeichert in" -#: FlatCAMApp.py:10353 +#: FlatCAMApp.py:8747 msgid "Exporting SVG" msgstr "SVG exportieren" -#: FlatCAMApp.py:10397 +#: FlatCAMApp.py:8790 msgid "SVG file exported to" msgstr "SVG-Datei exportiert nach" -#: FlatCAMApp.py:10422 +#: FlatCAMApp.py:8816 msgid "" "Save cancelled because source file is empty. Try to export the Gerber file." msgstr "" "Speichern abgebrochen, da die Quelldatei leer ist. Versuchen Sie einen " "Export der Gerber Datei." -#: FlatCAMApp.py:10568 +#: FlatCAMApp.py:8963 msgid "Excellon file exported to" msgstr "Excellon-Datei exportiert nach" -#: FlatCAMApp.py:10577 +#: FlatCAMApp.py:8972 msgid "Exporting Excellon" msgstr "Excellon exportieren" -#: FlatCAMApp.py:10583 FlatCAMApp.py:10591 +#: FlatCAMApp.py:8977 FlatCAMApp.py:8984 msgid "Could not export Excellon file." msgstr "Excellon-Datei konnte nicht exportiert werden." -#: FlatCAMApp.py:10707 +#: FlatCAMApp.py:9099 msgid "Gerber file exported to" msgstr "Gerberdatei exportiert nach" -#: FlatCAMApp.py:10715 +#: FlatCAMApp.py:9107 msgid "Exporting Gerber" msgstr "Gerber exportieren" -#: FlatCAMApp.py:10721 FlatCAMApp.py:10729 +#: FlatCAMApp.py:9112 FlatCAMApp.py:9119 msgid "Could not export Gerber file." msgstr "Gerber-Datei konnte nicht exportiert werden." -#: FlatCAMApp.py:10763 +#: FlatCAMApp.py:9154 msgid "DXF file exported to" msgstr "DXF-Datei exportiert nach" -#: FlatCAMApp.py:10769 +#: FlatCAMApp.py:9160 msgid "Exporting DXF" msgstr "DXF exportieren" -#: FlatCAMApp.py:10774 FlatCAMApp.py:10781 +#: FlatCAMApp.py:9165 FlatCAMApp.py:9172 msgid "Could not export DXF file." msgstr "DXF-Datei konnte nicht exportiert werden." -#: FlatCAMApp.py:10804 FlatCAMApp.py:10847 flatcamTools/ToolImage.py:278 +#: FlatCAMApp.py:9195 FlatCAMApp.py:9241 flatcamTools/ToolImage.py:277 msgid "" "Not supported type is picked as parameter. Only Geometry and Gerber are " "supported" @@ -1346,82 +1220,85 @@ msgstr "" "Nicht unterstützte Art wird als Parameter ausgewählt. Nur Geometrie und " "Gerber werden unterstützt" -#: FlatCAMApp.py:10814 +#: FlatCAMApp.py:9205 msgid "Importing SVG" msgstr "SVG importieren" -#: FlatCAMApp.py:10825 FlatCAMApp.py:10867 FlatCAMApp.py:10926 -#: FlatCAMApp.py:10993 FlatCAMApp.py:11056 FlatCAMApp.py:11123 -#: FlatCAMApp.py:11161 flatcamTools/ToolImage.py:298 -#: flatcamTools/ToolPDF.py:225 +#: FlatCAMApp.py:9213 FlatCAMApp.py:9258 +msgid "Import failed." +msgstr "Import fehlgeschlagen." + +#: FlatCAMApp.py:9220 FlatCAMApp.py:9265 FlatCAMApp.py:9329 FlatCAMApp.py:9396 +#: FlatCAMApp.py:9462 FlatCAMApp.py:9527 FlatCAMApp.py:9564 +#: flatcamTools/ToolImage.py:297 flatcamTools/ToolPDF.py:225 msgid "Opened" msgstr "Geöffnet" -#: FlatCAMApp.py:10856 +#: FlatCAMApp.py:9250 msgid "Importing DXF" msgstr "DXF importieren" -#: FlatCAMApp.py:10892 FlatCAMApp.py:11082 +#: FlatCAMApp.py:9291 FlatCAMApp.py:9486 msgid "Failed to open file" msgstr "Datei konnte nicht geöffnet werden" -#: FlatCAMApp.py:10895 FlatCAMApp.py:11085 +#: FlatCAMApp.py:9294 FlatCAMApp.py:9489 msgid "Failed to parse file" msgstr "Datei konnte nicht analysiert werden" -#: FlatCAMApp.py:10907 +#: FlatCAMApp.py:9306 msgid "Object is not Gerber file or empty. Aborting object creation." msgstr "" "Objekt ist keine Gerberdatei oder leer. Objekterstellung wird abgebrochen." -#: FlatCAMApp.py:10912 +#: FlatCAMApp.py:9311 msgid "Opening Gerber" msgstr "Gerber öffnen" -#: FlatCAMApp.py:10919 -msgid " Open Gerber failed. Probable not a Gerber file." -msgstr " Gerber öffnen ist fehlgeschlagen. Wahrscheinlich keine Gerber-Datei." +#: FlatCAMApp.py:9322 +msgid "Open Gerber failed. Probable not a Gerber file." +msgstr "Open Gerber ist fehlgeschlagen. Wahrscheinlich keine Gerber-Datei." -#: FlatCAMApp.py:10951 flatcamTools/ToolPcbWizard.py:427 +#: FlatCAMApp.py:9354 flatcamTools/ToolPcbWizard.py:425 msgid "This is not Excellon file." msgstr "Dies ist keine Excellon-Datei." -#: FlatCAMApp.py:10955 +#: FlatCAMApp.py:9358 msgid "Cannot open file" msgstr "Kann Datei nicht öffnen" -#: FlatCAMApp.py:10975 flatcamTools/ToolPDF.py:275 -#: flatcamTools/ToolPcbWizard.py:451 +#: FlatCAMApp.py:9376 flatcamTools/ToolPDF.py:275 +#: flatcamTools/ToolPcbWizard.py:447 msgid "No geometry found in file" msgstr "Keine Geometrie in der Datei gefunden" -#: FlatCAMApp.py:10978 +#: FlatCAMApp.py:9379 msgid "Opening Excellon." msgstr "Eröffnung Excellon." -#: FlatCAMApp.py:10985 +#: FlatCAMApp.py:9389 msgid "Open Excellon file failed. Probable not an Excellon file." msgstr "" "Die Excellon-Datei konnte nicht geöffnet werden. Wahrscheinlich keine " "Excellon-Datei." -#: FlatCAMApp.py:11016 +#: FlatCAMApp.py:9421 msgid "Reading GCode file" msgstr "GCode-Datei wird gelesen" -#: FlatCAMApp.py:11023 +#: FlatCAMApp.py:9427 msgid "Failed to open" msgstr "Gescheitert zu öffnen" -#: FlatCAMApp.py:11031 +#: FlatCAMApp.py:9434 msgid "This is not GCODE" msgstr "Dies ist kein GCODE" -#: FlatCAMApp.py:11036 +#: FlatCAMApp.py:9439 msgid "Opening G-Code." msgstr "G-Code öffnen." -#: FlatCAMApp.py:11045 +#: FlatCAMApp.py:9452 msgid "" "Failed to create CNCJob Object. Probable not a GCode file. Try to load it " "from File menu.\n" @@ -1433,127 +1310,107 @@ msgstr "" "Der Versuch, ein FlatCAM CNCJob-Objekt aus einer G-Code-Datei zu erstellen, " "ist während der Verarbeitung fehlgeschlagen" -#: FlatCAMApp.py:11104 +#: FlatCAMApp.py:9508 msgid "Object is not HPGL2 file or empty. Aborting object creation." msgstr "" "Objekt ist keine HPGL2-Datei oder leer. Objekterstellung wird abgebrochen." -#: FlatCAMApp.py:11109 +#: FlatCAMApp.py:9513 msgid "Opening HPGL2" msgstr "HPGL2 öffnen" -#: FlatCAMApp.py:11116 +#: FlatCAMApp.py:9520 msgid " Open HPGL2 failed. Probable not a HPGL2 file." msgstr " HPGL2 öffnen ist fehlgeschlagen. Wahrscheinlich keine HPGL2-Datei." -#: FlatCAMApp.py:11137 +#: FlatCAMApp.py:9540 msgid "Opening TCL Script..." msgstr "TCL-Skript wird geöffnet ..." -#: FlatCAMApp.py:11145 +#: FlatCAMApp.py:9548 msgid "TCL script file opened in Code Editor." msgstr "TCL-Skriptdatei im Code-Editor geöffnet." -#: FlatCAMApp.py:11148 +#: FlatCAMApp.py:9551 msgid "Failed to open TCL Script." msgstr "TCL-Skript konnte nicht geöffnet werden." -#: FlatCAMApp.py:11176 +#: FlatCAMApp.py:9579 msgid "Opening FlatCAM Config file." msgstr "Öffnen der FlatCAM Config-Datei." -#: FlatCAMApp.py:11204 +#: FlatCAMApp.py:9607 msgid "Failed to open config file" msgstr "Fehler beim Öffnen der Konfigurationsdatei" -#: FlatCAMApp.py:11230 +#: FlatCAMApp.py:9636 msgid "Loading Project ... Please Wait ..." msgstr "Projekt wird geladen ... Bitte warten ..." -#: FlatCAMApp.py:11235 +#: FlatCAMApp.py:9641 msgid "Opening FlatCAM Project file." msgstr "Öffnen der FlatCAM-Projektdatei." -#: FlatCAMApp.py:11245 FlatCAMApp.py:11263 +#: FlatCAMApp.py:9656 FlatCAMApp.py:9660 FlatCAMApp.py:9678 msgid "Failed to open project file" msgstr "Projektdatei konnte nicht geöffnet werden" -#: FlatCAMApp.py:11300 +#: FlatCAMApp.py:9715 msgid "Loading Project ... restoring" msgstr "Projekt wird geladen ... wird wiederhergestellt" -#: FlatCAMApp.py:11310 +#: FlatCAMApp.py:9725 msgid "Project loaded from" msgstr "Projekt geladen von" -#: FlatCAMApp.py:11373 +#: FlatCAMApp.py:9751 msgid "Redrawing all objects" msgstr "Alle Objekte neu zeichnen" -#: FlatCAMApp.py:11405 -msgid "Available commands:\n" -msgstr "Verfügbare Befehle:\n" - -#: FlatCAMApp.py:11407 -msgid "" -"\n" -"\n" -"Type help for usage.\n" -" Example: help open_gerber" -msgstr "" -"\n" -"\n" -"Geben Sie help für die Verwendung ein.\n" -"Beispiel: help open_gerber" - -#: FlatCAMApp.py:11557 -msgid "Shows list of commands." -msgstr "Zeigt eine Liste von Befehlen an." - -#: FlatCAMApp.py:11619 +#: FlatCAMApp.py:9839 msgid "Failed to load recent item list." msgstr "Fehler beim Laden der letzten Elementliste." -#: FlatCAMApp.py:11627 +#: FlatCAMApp.py:9846 msgid "Failed to parse recent item list." msgstr "Liste der letzten Artikel konnte nicht analysiert werden." -#: FlatCAMApp.py:11638 +#: FlatCAMApp.py:9856 msgid "Failed to load recent projects item list." msgstr "Fehler beim Laden der Artikelliste der letzten Projekte." -#: FlatCAMApp.py:11646 +#: FlatCAMApp.py:9863 msgid "Failed to parse recent project item list." msgstr "" "Fehler beim Analysieren der Liste der zuletzt verwendeten Projektelemente." -#: FlatCAMApp.py:11706 +#: FlatCAMApp.py:9924 msgid "Clear Recent projects" msgstr "Letzte Projekte löschen" -#: FlatCAMApp.py:11730 +#: FlatCAMApp.py:9948 msgid "Clear Recent files" msgstr "Letzte Dateien löschen" -#: FlatCAMApp.py:11747 flatcamGUI/FlatCAMGUI.py:1276 +#: FlatCAMApp.py:9970 flatcamGUI/FlatCAMGUI.py:1351 msgid "Shortcut Key List" msgstr " Liste der Tastenkombinationen " -#: FlatCAMApp.py:11821 +#: FlatCAMApp.py:10050 msgid "Selected Tab - Choose an Item from Project Tab" msgstr "" "Ausgewählte Registerkarte - Wählen Sie ein Element auf der Registerkarte " "\"Projekt\" aus" -#: FlatCAMApp.py:11822 +#: FlatCAMApp.py:10051 msgid "Details" msgstr "Einzelheiten" -#: FlatCAMApp.py:11824 +#: FlatCAMApp.py:10053 msgid "The normal flow when working in FlatCAM is the following:" msgstr "Der normale Ablauf beim Arbeiten in FlatCAM ist der folgende:" -#: FlatCAMApp.py:11825 +#: FlatCAMApp.py:10054 msgid "" "Load/Import a Gerber, Excellon, Gcode, DXF, Raster Image or SVG file into " "FlatCAM using either the toolbars, key shortcuts or even dragging and " @@ -1563,7 +1420,7 @@ msgstr "" "oder SVG-Datei mithilfe der Symbolleisten, Tastenkombinationen oder durch " "Ziehen und Ablegen der Dateien auf der GUI in FlatCAM." -#: FlatCAMApp.py:11828 +#: FlatCAMApp.py:10057 msgid "" "You can also load a FlatCAM project by double clicking on the project file, " "drag and drop of the file into the FLATCAM GUI or through the menu (or " @@ -1573,7 +1430,7 @@ msgstr "" "doppelklicken, sie per Drag & Drop in die FLATCAM-Benutzeroberfläche ziehen " "oder über die in der App angebotenen Menü- (oder Symbolleisten-) Aktionen." -#: FlatCAMApp.py:11831 +#: FlatCAMApp.py:10060 msgid "" "Once an object is available in the Project Tab, by selecting it and then " "focusing on SELECTED TAB (more simpler is to double click the object name in " @@ -1586,7 +1443,7 @@ msgstr "" "AUSGEWÄHLTES TAB mit den Objekteigenschaften entsprechend der Art " "aktualisiert: Gerber, Excellon-, Geometrie- oder CNCJob-Objekt." -#: FlatCAMApp.py:11835 +#: FlatCAMApp.py:10064 msgid "" "If the selection of the object is done on the canvas by single click " "instead, and the SELECTED TAB is in focus, again the object properties will " @@ -1601,7 +1458,7 @@ msgstr "" "doppelklicken, um das Ausgewählte Registerkarte zu öffnen und es zu füllen, " "selbst wenn es unscharf war." -#: FlatCAMApp.py:11839 +#: FlatCAMApp.py:10068 msgid "" "You can change the parameters in this screen and the flow direction is like " "this:" @@ -1609,7 +1466,7 @@ msgstr "" "Sie können die Parameter in diesem Bildschirm ändern und die Flussrichtung " "ist wie folgt:" -#: FlatCAMApp.py:11840 +#: FlatCAMApp.py:10069 msgid "" "Gerber/Excellon Object --> Change Parameter --> Generate Geometry --> " "Geometry Object --> Add tools (change param in Selected Tab) --> Generate " @@ -1622,7 +1479,7 @@ msgstr "" "überprüfen (über CNC bearbeiten) Code) und / oder GCode anhängen / " "voranstellen (ebenfalls in Ausgewählte Registerkarte) -> GCode speichern." -#: FlatCAMApp.py:11844 +#: FlatCAMApp.py:10073 msgid "" "A list of key shortcuts is available through an menu entry in Help --> " "Shortcuts List or through its own key shortcut: F3." @@ -1631,107 +1488,165 @@ msgstr "" "der Hilfe -> Liste der Tastenkombinationen oder über eine eigene " "Tastenkombination: F3." -#: FlatCAMApp.py:11906 +#: FlatCAMApp.py:10137 msgid "Failed checking for latest version. Could not connect." msgstr "" "Fehler bei der Suche nach der neuesten Version. Konnte keine Verbindung " "herstellen." -#: FlatCAMApp.py:11914 +#: FlatCAMApp.py:10144 msgid "Could not parse information about latest version." msgstr "Informationen zur neuesten Version konnten nicht analysiert werden." -#: FlatCAMApp.py:11925 +#: FlatCAMApp.py:10154 msgid "FlatCAM is up to date!" msgstr "FlatCAM ist auf dem neuesten Version!" -#: FlatCAMApp.py:11930 +#: FlatCAMApp.py:10159 msgid "Newer Version Available" msgstr "Neuere Version verfügbar" -#: FlatCAMApp.py:11931 -msgid "" -"There is a newer version of FlatCAM available for download:\n" -"\n" -msgstr "" -"Es gibt eine neuere Version von FlatCAM zum Download:\n" -"\n" +#: FlatCAMApp.py:10161 +msgid "There is a newer version of FlatCAM available for download:" +msgstr "Es gibt eine neuere Version von FlatCAM zum Download:" -#: FlatCAMApp.py:11933 +#: FlatCAMApp.py:10165 msgid "info" msgstr "Info" -#: FlatCAMApp.py:12012 +#: FlatCAMApp.py:10193 +msgid "" +"OpenGL canvas initialization failed. HW or HW configuration not supported." +"Change the graphic engine to Legacy(2D) in Edit -> Preferences -> General " +"tab.\n" +"\n" +msgstr "" +"OpenGL-Canvas-Initialisierung fehlgeschlagen. HW- oder HW-Konfiguration wird " +"nicht unterstützt. Ändern Sie die Grafik-Engine unter Bearbeiten -> " +"Einstellungen -> Registerkarte Allgemein in Legacy (2D).\n" +"\n" + +#: FlatCAMApp.py:10272 msgid "All plots disabled." msgstr "Alle Diagramme sind deaktiviert." -#: FlatCAMApp.py:12019 +#: FlatCAMApp.py:10279 msgid "All non selected plots disabled." msgstr "Alle nicht ausgewählten Diagramme sind deaktiviert." -#: FlatCAMApp.py:12026 +#: FlatCAMApp.py:10286 msgid "All plots enabled." msgstr "Alle Diagramme aktiviert." -#: FlatCAMApp.py:12033 +#: FlatCAMApp.py:10292 msgid "Selected plots enabled..." msgstr "Ausgewählte Diagramme aktiviert ..." -#: FlatCAMApp.py:12042 +#: FlatCAMApp.py:10300 msgid "Selected plots disabled..." msgstr "Ausgewählte Diagramme deaktiviert ..." -#: FlatCAMApp.py:12061 +#: FlatCAMApp.py:10333 msgid "Enabling plots ..." msgstr "Diagramm aktivieren..." -#: FlatCAMApp.py:12101 +#: FlatCAMApp.py:10385 msgid "Disabling plots ..." msgstr "Diagramm deaktivieren..." -#: FlatCAMApp.py:12123 +#: FlatCAMApp.py:10408 msgid "Working ..." msgstr "Arbeiten ..." -#: FlatCAMApp.py:12224 +#: FlatCAMApp.py:10463 flatcamGUI/FlatCAMGUI.py:691 +msgid "Red" +msgstr "Rote" + +#: FlatCAMApp.py:10465 flatcamGUI/FlatCAMGUI.py:694 +msgid "Blue" +msgstr "Blau" + +#: FlatCAMApp.py:10468 flatcamGUI/FlatCAMGUI.py:697 +msgid "Yellow" +msgstr "Gelb" + +#: FlatCAMApp.py:10470 flatcamGUI/FlatCAMGUI.py:700 +msgid "Green" +msgstr "Grün" + +#: FlatCAMApp.py:10472 flatcamGUI/FlatCAMGUI.py:703 +msgid "Purple" +msgstr "Lila" + +#: FlatCAMApp.py:10474 flatcamGUI/FlatCAMGUI.py:706 +msgid "Brown" +msgstr "Braun" + +#: FlatCAMApp.py:10476 FlatCAMApp.py:10532 flatcamGUI/FlatCAMGUI.py:709 +msgid "White" +msgstr "Weiß" + +#: FlatCAMApp.py:10478 flatcamGUI/FlatCAMGUI.py:712 +msgid "Black" +msgstr "Schwarz" + +#: FlatCAMApp.py:10481 flatcamGUI/FlatCAMGUI.py:717 +msgid "Custom" +msgstr "Maßgeschn." + +#: FlatCAMApp.py:10491 flatcamGUI/FlatCAMGUI.py:725 +msgid "Default" +msgstr "Standard" + +#: FlatCAMApp.py:10515 flatcamGUI/FlatCAMGUI.py:722 +msgid "Opacity" +msgstr "Opazität" + +#: FlatCAMApp.py:10517 +msgid "Set alpha level ..." +msgstr "Alpha-Level einstellen ..." + +#: FlatCAMApp.py:10517 flatcamGUI/PreferencesUI.py:8017 +#: flatcamGUI/PreferencesUI.py:9346 flatcamGUI/PreferencesUI.py:9560 +#: flatcamTools/ToolExtractDrills.py:164 flatcamTools/ToolExtractDrills.py:285 +#: flatcamTools/ToolPunchGerber.py:192 flatcamTools/ToolPunchGerber.py:308 +#: flatcamTools/ToolTransform.py:357 +msgid "Value" +msgstr "Wert" + +#: FlatCAMApp.py:10594 msgid "Saving FlatCAM Project" msgstr "FlatCAM-Projekt speichern" -#: FlatCAMApp.py:12243 FlatCAMApp.py:12280 +#: FlatCAMApp.py:10615 FlatCAMApp.py:10651 msgid "Project saved to" msgstr "Projekt gespeichert in" -#: FlatCAMApp.py:12250 +#: FlatCAMApp.py:10622 msgid "The object is used by another application." msgstr "Das Objekt wird von einer anderen Anwendung verwendet." -#: FlatCAMApp.py:12264 +#: FlatCAMApp.py:10636 msgid "Failed to verify project file" msgstr "Fehler beim Überprüfen der Projektdatei" -#: FlatCAMApp.py:12264 FlatCAMApp.py:12272 FlatCAMApp.py:12283 +#: FlatCAMApp.py:10636 FlatCAMApp.py:10644 FlatCAMApp.py:10654 msgid "Retry to save it." msgstr "Versuchen Sie erneut, es zu speichern." -#: FlatCAMApp.py:12272 FlatCAMApp.py:12283 +#: FlatCAMApp.py:10644 FlatCAMApp.py:10654 msgid "Failed to parse saved project file" msgstr "Fehler beim Parsen der Projektdatei" -#: FlatCAMApp.py:12398 -msgid "The user requested a graceful exit of the current task." -msgstr "" -"Der Benutzer hat einen ordnungsgemäßen Abschluss der aktuellen Aufgabe " -"angefordert." - -#: FlatCAMCommon.py:136 FlatCAMCommon.py:163 +#: FlatCAMBookmark.py:57 FlatCAMBookmark.py:84 msgid "Title" msgstr "Titel" -#: FlatCAMCommon.py:137 FlatCAMCommon.py:167 +#: FlatCAMBookmark.py:58 FlatCAMBookmark.py:88 msgid "Web Link" msgstr "Weblink" -#: FlatCAMCommon.py:141 +#: FlatCAMBookmark.py:62 msgid "" "Index.\n" "The rows in gray color will populate the Bookmarks menu.\n" @@ -1741,7 +1656,7 @@ msgstr "" "Die grauen Zeilen füllen das Lesezeichen-Menü.\n" "Die Anzahl der grauen Zeilen wird in den Einstellungen festgelegt." -#: FlatCAMCommon.py:145 +#: FlatCAMBookmark.py:66 msgid "" "Description of the link that is set as an menu action.\n" "Try to keep it short because it is installed as a menu item." @@ -1749,96 +1664,94 @@ msgstr "" "Beschreibung des Links, der als Menüaktion festgelegt wird.\n" "Versuchen Sie es kurz zu halten, da es als Menüelement installiert ist." -#: FlatCAMCommon.py:148 +#: FlatCAMBookmark.py:69 msgid "Web Link. E.g: https://your_website.org " msgstr "Weblink. ZB: https://your_website.org " -#: FlatCAMCommon.py:157 +#: FlatCAMBookmark.py:78 msgid "New Bookmark" msgstr "Neues Lesezeichen" -#: FlatCAMCommon.py:176 +#: FlatCAMBookmark.py:97 msgid "Add Entry" msgstr "Eintrag hinzufügen" -#: FlatCAMCommon.py:177 +#: FlatCAMBookmark.py:98 msgid "Remove Entry" msgstr "Eintrag entfernen" -#: FlatCAMCommon.py:178 +#: FlatCAMBookmark.py:99 msgid "Export List" msgstr "Liste exportieren" -#: FlatCAMCommon.py:179 +#: FlatCAMBookmark.py:100 msgid "Import List" msgstr "Liste importieren" -#: FlatCAMCommon.py:260 +#: FlatCAMBookmark.py:181 msgid "Title entry is empty." msgstr "Kein Titel eingegeben." -#: FlatCAMCommon.py:269 +#: FlatCAMBookmark.py:190 msgid "Web link entry is empty." msgstr "Keine Internetadresse angegeben." -#: FlatCAMCommon.py:277 +#: FlatCAMBookmark.py:198 msgid "Either the Title or the Weblink already in the table." msgstr "" "Entweder Titel oder Internetadresse sind bereits in der Tabelle vorhanden." -#: FlatCAMCommon.py:297 +#: FlatCAMBookmark.py:218 msgid "Bookmark added." msgstr "Lesezeichen verwalten." -#: FlatCAMCommon.py:314 +#: FlatCAMBookmark.py:235 msgid "This bookmark can not be removed" msgstr "Dieses Lesezeichen kann nicht entfernt werden" -#: FlatCAMCommon.py:345 +#: FlatCAMBookmark.py:266 msgid "Bookmark removed." msgstr "Lesezeichen entfernt." -#: FlatCAMCommon.py:360 +#: FlatCAMBookmark.py:281 msgid "Export FlatCAM Bookmarks" msgstr "Export der FlatCAM-Lesezeichen" -#: FlatCAMCommon.py:363 flatcamGUI/FlatCAMGUI.py:470 +#: FlatCAMBookmark.py:284 flatcamGUI/FlatCAMGUI.py:512 msgid "Bookmarks" msgstr "Lesezeichen" -#: FlatCAMCommon.py:370 -msgid "FlatCAM bookmarks export cancelled." -msgstr "FlatCAM-Lesezeichen-Export abgebrochen." - -#: FlatCAMCommon.py:389 FlatCAMCommon.py:419 +#: FlatCAMBookmark.py:310 FlatCAMBookmark.py:340 msgid "Could not load bookmarks file." msgstr "Die Lesezeichen-Datei konnte nicht geladen werden." -#: FlatCAMCommon.py:399 +#: FlatCAMBookmark.py:320 msgid "Failed to write bookmarks to file." msgstr "Fehler beim Schreiben der Lesezeichen in die Datei." -#: FlatCAMCommon.py:401 +#: FlatCAMBookmark.py:322 msgid "Exported bookmarks to" msgstr "Exportierte Lesezeichen nach" -#: FlatCAMCommon.py:407 +#: FlatCAMBookmark.py:328 msgid "Import FlatCAM Bookmarks" msgstr "Importieren Sie FlatCAM-Lesezeichen" -#: FlatCAMCommon.py:412 -msgid "FlatCAM bookmarks import cancelled." -msgstr "Import der FlatCAM-Lesezeichen abgebrochen." - -#: FlatCAMCommon.py:426 +#: FlatCAMBookmark.py:347 msgid "Imported Bookmarks from" msgstr "Importierte Lesezeichen von" -#: FlatCAMCommon.py:529 +#: FlatCAMCommon.py:29 +msgid "The user requested a graceful exit of the current task." +msgstr "" +"Der Benutzer hat einen ordnungsgemäßen Abschluss der aktuellen Aufgabe " +"angefordert." + +#: FlatCAMDB.py:86 msgid "Add Geometry Tool in DB" msgstr "Geometriewerkzeug in DB hinzufügen" -#: FlatCAMCommon.py:531 +#: FlatCAMDB.py:88 FlatCAMDB.py:1643 msgid "" "Add a new tool in the Tools Database.\n" "It will be used in the Geometry UI.\n" @@ -1848,35 +1761,35 @@ msgstr "" "Es wird in der Geometrie-Benutzeroberfläche verwendet.\n" "Danach können Sie es modifizieren." -#: FlatCAMCommon.py:545 +#: FlatCAMDB.py:102 FlatCAMDB.py:1657 msgid "Delete Tool from DB" msgstr "Werkzeug aus DB löschen" -#: FlatCAMCommon.py:547 +#: FlatCAMDB.py:104 FlatCAMDB.py:1659 msgid "Remove a selection of tools in the Tools Database." msgstr "Eine Auswahl von Werkzeugen aus der Werkzeugdatenbank entfernen." -#: FlatCAMCommon.py:551 +#: FlatCAMDB.py:108 FlatCAMDB.py:1663 msgid "Export DB" msgstr "DB exportieren" -#: FlatCAMCommon.py:553 +#: FlatCAMDB.py:110 FlatCAMDB.py:1665 msgid "Save the Tools Database to a custom text file." msgstr "Werkzeugdatenbank als Textdatei speichern." -#: FlatCAMCommon.py:557 +#: FlatCAMDB.py:114 FlatCAMDB.py:1669 msgid "Import DB" msgstr "Importieren Sie DB" -#: FlatCAMCommon.py:559 +#: FlatCAMDB.py:116 FlatCAMDB.py:1671 msgid "Load the Tools Database information's from a custom text file." msgstr "Werkzeugdatenbank aus einer Textdatei importieren." -#: FlatCAMCommon.py:563 +#: FlatCAMDB.py:120 FlatCAMDB.py:1675 msgid "Add Tool from Tools DB" msgstr "Werkzeug aus Werkzeugdatenbank hinzufügen" -#: FlatCAMCommon.py:565 +#: FlatCAMDB.py:122 FlatCAMDB.py:1677 msgid "" "Add a new tool in the Tools Table of the\n" "active Geometry object after selecting a tool\n" @@ -1886,138 +1799,147 @@ msgstr "" "aktiven Geometrie hinzu, nachdem Sie das Werkzeug in\n" "der Werkzeugdatenbank ausgewählt haben." -#: FlatCAMCommon.py:601 FlatCAMCommon.py:1276 +#: FlatCAMDB.py:158 FlatCAMDB.py:833 FlatCAMDB.py:1087 msgid "Tool Name" msgstr "Werkzeugname" -#: FlatCAMCommon.py:602 FlatCAMCommon.py:1278 -#: flatcamEditors/FlatCAMExcEditor.py:1602 flatcamGUI/ObjectUI.py:1295 -#: flatcamTools/ToolNonCopperClear.py:271 flatcamTools/ToolPaint.py:176 +#: FlatCAMDB.py:159 FlatCAMDB.py:835 FlatCAMDB.py:1100 +#: flatcamEditors/FlatCAMExcEditor.py:1604 flatcamGUI/ObjectUI.py:1345 +#: flatcamGUI/ObjectUI.py:1583 flatcamGUI/PreferencesUI.py:7088 +#: flatcamTools/ToolNCC.py:278 flatcamTools/ToolNCC.py:287 +#: flatcamTools/ToolPaint.py:261 msgid "Tool Dia" msgstr "Werkzeugdurchm" -#: FlatCAMCommon.py:603 FlatCAMCommon.py:1280 flatcamGUI/ObjectUI.py:1278 +#: FlatCAMDB.py:160 FlatCAMDB.py:837 FlatCAMDB.py:1281 +#: flatcamGUI/ObjectUI.py:1558 msgid "Tool Offset" msgstr "Werkzeugversatz" -#: FlatCAMCommon.py:604 FlatCAMCommon.py:1282 +#: FlatCAMDB.py:161 FlatCAMDB.py:839 FlatCAMDB.py:1298 msgid "Custom Offset" msgstr "Selbstdefinierter Werkzeugversatz" -#: FlatCAMCommon.py:605 FlatCAMCommon.py:1284 flatcamGUI/ObjectUI.py:304 -#: flatcamGUI/PreferencesUI.py:2217 flatcamGUI/PreferencesUI.py:5036 -#: flatcamTools/ToolNonCopperClear.py:213 +#: FlatCAMDB.py:162 FlatCAMDB.py:841 FlatCAMDB.py:1265 +#: flatcamGUI/ObjectUI.py:309 flatcamGUI/PreferencesUI.py:3514 +#: flatcamGUI/PreferencesUI.py:6449 flatcamGUI/PreferencesUI.py:7018 +#: flatcamGUI/PreferencesUI.py:7028 flatcamTools/ToolNCC.py:213 +#: flatcamTools/ToolNCC.py:227 flatcamTools/ToolPaint.py:196 msgid "Tool Type" msgstr "Werkzeugtyp" -#: FlatCAMCommon.py:606 FlatCAMCommon.py:1286 +#: FlatCAMDB.py:163 FlatCAMDB.py:843 FlatCAMDB.py:1113 msgid "Tool Shape" msgstr "Werkzeugform" -#: FlatCAMCommon.py:607 FlatCAMCommon.py:1289 flatcamGUI/ObjectUI.py:345 -#: flatcamGUI/ObjectUI.py:820 flatcamGUI/ObjectUI.py:1405 -#: flatcamGUI/ObjectUI.py:1926 flatcamGUI/PreferencesUI.py:2257 -#: flatcamGUI/PreferencesUI.py:3082 flatcamGUI/PreferencesUI.py:3961 -#: flatcamGUI/PreferencesUI.py:5081 flatcamGUI/PreferencesUI.py:5327 -#: flatcamGUI/PreferencesUI.py:6145 flatcamTools/ToolCalculators.py:114 -#: flatcamTools/ToolCutOut.py:132 flatcamTools/ToolNonCopperClear.py:254 +#: FlatCAMDB.py:164 FlatCAMDB.py:846 FlatCAMDB.py:1129 +#: flatcamGUI/ObjectUI.py:350 flatcamGUI/ObjectUI.py:900 +#: flatcamGUI/ObjectUI.py:1703 flatcamGUI/ObjectUI.py:2256 +#: flatcamGUI/PreferencesUI.py:3554 flatcamGUI/PreferencesUI.py:4428 +#: flatcamGUI/PreferencesUI.py:5358 flatcamGUI/PreferencesUI.py:6494 +#: flatcamGUI/PreferencesUI.py:6783 flatcamGUI/PreferencesUI.py:7061 +#: flatcamGUI/PreferencesUI.py:7069 flatcamGUI/PreferencesUI.py:7752 +#: flatcamTools/ToolCalculators.py:114 flatcamTools/ToolCutOut.py:138 +#: flatcamTools/ToolNCC.py:260 flatcamTools/ToolNCC.py:268 +#: flatcamTools/ToolPaint.py:243 msgid "Cut Z" msgstr "Schnitttiefe Z" -#: FlatCAMCommon.py:608 FlatCAMCommon.py:1291 +#: FlatCAMDB.py:165 FlatCAMDB.py:848 FlatCAMDB.py:1143 msgid "MultiDepth" msgstr "Mehrfache Durchgänge" # Abbrev. unclear: Depth Per Pass? # Perhaps better not translate -#: FlatCAMCommon.py:609 FlatCAMCommon.py:1293 +#: FlatCAMDB.py:166 FlatCAMDB.py:850 FlatCAMDB.py:1156 msgid "DPP" msgstr "DPP" -#: FlatCAMCommon.py:610 FlatCAMCommon.py:1295 +#: FlatCAMDB.py:167 FlatCAMDB.py:852 FlatCAMDB.py:1312 msgid "V-Dia" msgstr "V-Durchm." -#: FlatCAMCommon.py:611 FlatCAMCommon.py:1297 +#: FlatCAMDB.py:168 FlatCAMDB.py:854 FlatCAMDB.py:1326 msgid "V-Angle" msgstr "Winkel der V-Form" -#: FlatCAMCommon.py:612 FlatCAMCommon.py:1299 flatcamGUI/ObjectUI.py:839 -#: flatcamGUI/ObjectUI.py:1452 flatcamGUI/PreferencesUI.py:3100 -#: flatcamGUI/PreferencesUI.py:4014 flatcamGUI/PreferencesUI.py:7535 -#: flatcamTools/ToolCalibration.py:74 +#: FlatCAMDB.py:169 FlatCAMDB.py:856 FlatCAMDB.py:1170 +#: flatcamGUI/ObjectUI.py:946 flatcamGUI/ObjectUI.py:1750 +#: flatcamGUI/PreferencesUI.py:4469 flatcamGUI/PreferencesUI.py:5411 +#: flatcamGUI/PreferencesUI.py:9157 flatcamObjects/FlatCAMExcellon.py:1316 +#: flatcamObjects/FlatCAMGeometry.py:1552 flatcamTools/ToolCalibration.py:74 msgid "Travel Z" msgstr "Bewegungshöhe Z (Travel)" # I think this is FeedRate XY -#: FlatCAMCommon.py:613 FlatCAMCommon.py:1301 +#: FlatCAMDB.py:170 FlatCAMDB.py:858 msgid "FR" msgstr "Vorschub (XY)" -#: FlatCAMCommon.py:614 FlatCAMCommon.py:1303 +#: FlatCAMDB.py:171 FlatCAMDB.py:860 msgid "FR Z" msgstr "Vorschub (Z)" -#: FlatCAMCommon.py:615 FlatCAMCommon.py:1305 +#: FlatCAMDB.py:172 FlatCAMDB.py:862 FlatCAMDB.py:1340 msgid "FR Rapids" msgstr "Vorschub ohne Last" -#: FlatCAMCommon.py:616 FlatCAMCommon.py:1307 flatcamGUI/PreferencesUI.py:3173 +#: FlatCAMDB.py:173 FlatCAMDB.py:864 FlatCAMDB.py:1213 +#: flatcamGUI/PreferencesUI.py:4557 msgid "Spindle Speed" msgstr "Drehgeschwindigkeit" -#: FlatCAMCommon.py:617 FlatCAMCommon.py:1309 flatcamGUI/ObjectUI.py:963 -#: flatcamGUI/ObjectUI.py:1619 +#: FlatCAMDB.py:174 FlatCAMDB.py:866 FlatCAMDB.py:1228 +#: flatcamGUI/ObjectUI.py:1064 flatcamGUI/ObjectUI.py:1857 msgid "Dwell" msgstr "Warten zum Beschleunigen" -#: FlatCAMCommon.py:618 FlatCAMCommon.py:1311 +#: FlatCAMDB.py:175 FlatCAMDB.py:868 FlatCAMDB.py:1241 msgid "Dwelltime" msgstr "Wartezeit zum Beschleunigen" -#: FlatCAMCommon.py:619 FlatCAMCommon.py:1313 flatcamGUI/ObjectUI.py:982 -#: flatcamGUI/ObjectUI.py:1640 flatcamGUI/PreferencesUI.py:3204 -#: flatcamGUI/PreferencesUI.py:4155 flatcamGUI/PreferencesUI.py:6642 -#: flatcamTools/ToolSolderPaste.py:334 +#: FlatCAMDB.py:176 FlatCAMDB.py:870 flatcamGUI/ObjectUI.py:2014 +#: flatcamGUI/PreferencesUI.py:4592 flatcamGUI/PreferencesUI.py:5564 +#: flatcamGUI/PreferencesUI.py:8264 flatcamTools/ToolSolderPaste.py:335 msgid "Preprocessor" msgstr "Postprozessor" -#: FlatCAMCommon.py:620 FlatCAMCommon.py:1315 +#: FlatCAMDB.py:177 FlatCAMDB.py:872 FlatCAMDB.py:1356 msgid "ExtraCut" msgstr "Zusätzlicher Schnitt" -#: FlatCAMCommon.py:621 FlatCAMCommon.py:1317 +#: FlatCAMDB.py:178 FlatCAMDB.py:874 FlatCAMDB.py:1371 msgid "E-Cut Length" msgstr "Extra Schnittlänge" -#: FlatCAMCommon.py:622 FlatCAMCommon.py:1319 +#: FlatCAMDB.py:179 FlatCAMDB.py:876 msgid "Toolchange" msgstr "Werkzeugwechsel" -#: FlatCAMCommon.py:623 FlatCAMCommon.py:1321 +#: FlatCAMDB.py:180 FlatCAMDB.py:878 msgid "Toolchange XY" msgstr "Werkzeugwechsel XY" -#: FlatCAMCommon.py:624 FlatCAMCommon.py:1323 flatcamGUI/PreferencesUI.py:3124 -#: flatcamGUI/PreferencesUI.py:4044 flatcamGUI/PreferencesUI.py:7572 +#: FlatCAMDB.py:181 FlatCAMDB.py:880 flatcamGUI/PreferencesUI.py:4495 +#: flatcamGUI/PreferencesUI.py:5441 flatcamGUI/PreferencesUI.py:9194 #: flatcamTools/ToolCalibration.py:111 msgid "Toolchange Z" msgstr "Werkzeugwechsel Z" -#: FlatCAMCommon.py:625 FlatCAMCommon.py:1325 flatcamGUI/ObjectUI.py:886 -#: flatcamGUI/PreferencesUI.py:3309 flatcamGUI/PreferencesUI.py:4200 +#: FlatCAMDB.py:182 FlatCAMDB.py:882 flatcamGUI/ObjectUI.py:1193 +#: flatcamGUI/PreferencesUI.py:4703 flatcamGUI/PreferencesUI.py:5610 msgid "Start Z" msgstr "Start Z" -#: FlatCAMCommon.py:626 FlatCAMCommon.py:1328 +#: FlatCAMDB.py:183 FlatCAMDB.py:885 msgid "End Z" msgstr "Ende Z" -#: FlatCAMCommon.py:630 +#: FlatCAMDB.py:187 msgid "Tool Index." msgstr "Werkzeugverzeichnis." -#: FlatCAMCommon.py:632 +#: FlatCAMDB.py:189 FlatCAMDB.py:1089 msgid "" "Tool name.\n" "This is not used in the app, it's function\n" @@ -2027,11 +1949,11 @@ msgstr "" "Wird in der App nicht verwendet,\n" "sondern dient als Kommentar für den Nutzer." -#: FlatCAMCommon.py:636 +#: FlatCAMDB.py:193 FlatCAMDB.py:1102 msgid "Tool Diameter." msgstr "Werkzeugdurchmesser." -#: FlatCAMCommon.py:638 +#: FlatCAMDB.py:195 FlatCAMDB.py:1283 msgid "" "Tool Offset.\n" "Can be of a few types:\n" @@ -2047,7 +1969,7 @@ msgstr "" "Out: Offset einen halben Werkzeugdurchmesser ausserhalb\n" "Custom: selbstdefinierter Wert im Feld \"Selbstdefinierter Offset\"" -#: FlatCAMCommon.py:645 +#: FlatCAMDB.py:202 FlatCAMDB.py:1300 msgid "" "Custom Offset.\n" "A value to be used as offset from the current path." @@ -2055,7 +1977,7 @@ msgstr "" "Selbstdefinierter Offset.\n" "Ein Wert der als Offset zum aktellen Pfad hinzugefügt wird." -#: FlatCAMCommon.py:648 +#: FlatCAMDB.py:205 FlatCAMDB.py:1267 msgid "" "Tool Type.\n" "Can be:\n" @@ -2070,7 +1992,7 @@ msgstr "" "Durchgänge\n" "Finish: Finishing, hoher Vorschub" -#: FlatCAMCommon.py:654 +#: FlatCAMDB.py:211 FlatCAMDB.py:1115 msgid "" "Tool Shape. \n" "Can be:\n" @@ -2084,7 +2006,7 @@ msgstr "" "B: Kugelförmig\n" "V: V-Förmig" -#: FlatCAMCommon.py:660 +#: FlatCAMDB.py:217 FlatCAMDB.py:1131 msgid "" "Cutting Depth.\n" "The depth at which to cut into material." @@ -2093,7 +2015,7 @@ msgstr "" "Eindringtiefe in das Material." # MultiDepth is hard to translate, cause it is somewhat artificial. If you need to abbreviate perhaps "MehrfDurchg" could suffice, but stays ugly. -#: FlatCAMCommon.py:663 +#: FlatCAMDB.py:220 FlatCAMDB.py:1145 msgid "" "Multi Depth.\n" "Selecting this will allow cutting in multiple passes,\n" @@ -2103,7 +2025,7 @@ msgstr "" "Wenn ausgewählt wird der Schnitt in mehreren Stufen\n" "durchgeführt. Die Schnitttiefe jedes Schnittes ist in DPP angegeben." -#: FlatCAMCommon.py:667 +#: FlatCAMDB.py:224 FlatCAMDB.py:1158 msgid "" "DPP. Depth per Pass.\n" "The value used to cut into material on each pass." @@ -2111,7 +2033,7 @@ msgstr "" "DPP: Tiefe pro Schnitt. Definiert die einzelne Schnitttiefe in mehrfachen " "Durchgängen." -#: FlatCAMCommon.py:670 +#: FlatCAMDB.py:227 FlatCAMDB.py:1314 msgid "" "V-Dia.\n" "Diameter of the tip for V-Shape Tools." @@ -2120,7 +2042,7 @@ msgstr "" "Durchmesser der Spitze eines V-Förmigen Werkzeugs." # Typo in english? V-Angle, missing n? -#: FlatCAMCommon.py:673 +#: FlatCAMDB.py:230 FlatCAMDB.py:1328 msgid "" "V-Agle.\n" "Angle at the tip for the V-Shape Tools." @@ -2128,7 +2050,7 @@ msgstr "" "V-Winkel.\n" "Öffnungswinkel an der Spitze eine V-Förmigen Werkzeugs." -#: FlatCAMCommon.py:676 +#: FlatCAMDB.py:233 FlatCAMDB.py:1172 msgid "" "Clearance Height.\n" "Height at which the milling bit will travel between cuts,\n" @@ -2138,7 +2060,7 @@ msgstr "" "Die Höhe in der das Fräswerkzeug sich zwischen den Schnitten \n" "frei bewegen kann ohne auf Hindernisse zu stossen." -#: FlatCAMCommon.py:680 +#: FlatCAMDB.py:237 msgid "" "FR. Feedrate\n" "The speed on XY plane used while cutting into material." @@ -2146,7 +2068,7 @@ msgstr "" "FR: Feedrate\n" "Geschwindkeit beim fräsen. Angegeben in cm pro Minute." -#: FlatCAMCommon.py:683 +#: FlatCAMDB.py:240 msgid "" "FR Z. Feedrate Z\n" "The speed on Z plane." @@ -2154,7 +2076,7 @@ msgstr "" "FR Z: Feedrate Z:\n" "Geschwindigkeit beim Fräsen in Z-Richtung." -#: FlatCAMCommon.py:686 +#: FlatCAMDB.py:243 FlatCAMDB.py:1342 msgid "" "FR Rapids. Feedrate Rapids\n" "Speed used while moving as fast as possible.\n" @@ -2166,7 +2088,7 @@ msgstr "" "Wird benutzt bei Geräten die das G0 Kommando nicht \n" "unterstützen (oft 3D Drucker)." -#: FlatCAMCommon.py:691 +#: FlatCAMDB.py:248 FlatCAMDB.py:1215 msgid "" "Spindle Speed.\n" "If it's left empty it will not be used.\n" @@ -2176,22 +2098,26 @@ msgstr "" "Drehzahl des Fräsmotors in U/min.\n" "Wird nicht benutzt, wenn leer." -#: FlatCAMCommon.py:695 +#: FlatCAMDB.py:252 FlatCAMDB.py:1230 msgid "" "Dwell.\n" "Check this if a delay is needed to allow\n" "the spindle motor to reach it's set speed." msgstr "" -"dwelltime = Pausezeit, damit die Spindel ihre eingestellte Drehzahl erreicht." +"Verweilen.\n" +"Überprüfen Sie dies, wenn eine Verzögerung erforderlich ist\n" +"Der Spindelmotor erreicht die eingestellte Drehzahl." -#: FlatCAMCommon.py:699 +#: FlatCAMDB.py:256 FlatCAMDB.py:1243 msgid "" "Dwell Time.\n" "A delay used to allow the motor spindle reach it's set speed." msgstr "" -"dwelltime = Pausezeit, damit die Spindel ihre eingestellte Drehzahl erreicht." +"Verweilzeit.\n" +"Eine Verzögerung, mit der die Motorspindel ihre eingestellte Drehzahl " +"erreicht." -#: FlatCAMCommon.py:702 +#: FlatCAMDB.py:259 msgid "" "Preprocessor.\n" "A selection of files that will alter the generated G-code\n" @@ -2201,7 +2127,7 @@ msgstr "" "Diese Dateien werden den erzeugten G-Code modifizieren\n" "um eine große Anzahl Anwendungsmöglichkeiten zu unterstützen." -#: FlatCAMCommon.py:706 +#: FlatCAMDB.py:263 FlatCAMDB.py:1358 msgid "" "Extra Cut.\n" "If checked, after a isolation is finished an extra cut\n" @@ -2214,7 +2140,7 @@ msgstr "" "durchgeführt, um Start und Endpunkt definitiv zu verbinden und \n" "so eine vollständige Isolation zu gewährleisten." -#: FlatCAMCommon.py:712 +#: FlatCAMDB.py:269 FlatCAMDB.py:1373 msgid "" "Extra Cut length.\n" "If checked, after a isolation is finished an extra cut\n" @@ -2228,7 +2154,7 @@ msgstr "" "durchgeführt, um Start und Endpunkt definitiv zu verbinden und \n" "so eine vollständige Isolation zu gewährleisten." -#: FlatCAMCommon.py:719 +#: FlatCAMDB.py:276 msgid "" "Toolchange.\n" "It will create a toolchange event.\n" @@ -2240,7 +2166,7 @@ msgstr "" "Die Art wie der Werkzeugwechsel durchgeführt wird\n" "hängt vom gewählten Präprozessor ab." -#: FlatCAMCommon.py:724 +#: FlatCAMDB.py:281 msgid "" "Toolchange XY.\n" "A set of coordinates in the format (x, y).\n" @@ -2253,7 +2179,7 @@ msgstr "" "Werkzeugwechselereignis ausgelöst." # Is this really the height of where a toolchange event takes place or is it the position of where to go to for being able to change the tool? -#: FlatCAMCommon.py:729 +#: FlatCAMDB.py:286 msgid "" "Toolchange Z.\n" "The position on Z plane where the tool change event take place." @@ -2262,7 +2188,7 @@ msgstr "" "Die Position in der Z Ebene an der ein Werkzeugwechselereignis ausgelöst " "wird." -#: FlatCAMCommon.py:732 +#: FlatCAMDB.py:289 msgid "" "Start Z.\n" "If it's left empty it will not be used.\n" @@ -2272,7 +2198,7 @@ msgstr "" "Nicht benutzt wenn leer.\n" "Die Z-Position die zum Start angefahren wird." -#: FlatCAMCommon.py:736 +#: FlatCAMDB.py:293 msgid "" "End Z.\n" "A position on Z plane to move immediately after job stop." @@ -2280,584 +2206,510 @@ msgstr "" "End Z.\n" "Die Z-Position die bei Beendigung des Jobs angefahren wird." -#: FlatCAMCommon.py:748 FlatCAMCommon.py:1125 FlatCAMCommon.py:1159 +#: FlatCAMDB.py:305 FlatCAMDB.py:682 FlatCAMDB.py:716 FlatCAMDB.py:1891 +#: FlatCAMDB.py:2112 FlatCAMDB.py:2146 msgid "Could not load Tools DB file." msgstr "Werkzeugdatenbank konnte nicht geladen werden." -#: FlatCAMCommon.py:756 FlatCAMCommon.py:1167 +#: FlatCAMDB.py:313 FlatCAMDB.py:724 FlatCAMDB.py:1899 FlatCAMDB.py:2154 msgid "Failed to parse Tools DB file." msgstr "Formatfehler beim Einlesen der Werkzeugdatenbank." -#: FlatCAMCommon.py:759 FlatCAMCommon.py:1170 +#: FlatCAMDB.py:316 FlatCAMDB.py:727 FlatCAMDB.py:1902 FlatCAMDB.py:2157 msgid "Loaded FlatCAM Tools DB from" -msgstr "FlatCAM Werkzeugdatenbank wurde gelesen aus:" +msgstr "Geladene FlatCAM Tools DB von" -#: FlatCAMCommon.py:765 +#: FlatCAMDB.py:322 FlatCAMDB.py:1816 msgid "Add to DB" msgstr "Hinzufügen" -#: FlatCAMCommon.py:767 +#: FlatCAMDB.py:324 FlatCAMDB.py:1819 msgid "Copy from DB" msgstr "Von Datenbank kopieren" -#: FlatCAMCommon.py:769 +#: FlatCAMDB.py:326 FlatCAMDB.py:1822 msgid "Delete from DB" msgstr "Aus Datenbank löschen" -#: FlatCAMCommon.py:1046 +#: FlatCAMDB.py:603 FlatCAMDB.py:2029 msgid "Tool added to DB." msgstr "Werkzeug wurde zur Werkzeugdatenbank hinzugefügt." -#: FlatCAMCommon.py:1067 +#: FlatCAMDB.py:624 FlatCAMDB.py:2053 msgid "Tool copied from Tools DB." msgstr "Das Werkzeug wurde aus der Werkzeugdatenbank kopiert." -#: FlatCAMCommon.py:1085 +#: FlatCAMDB.py:642 FlatCAMDB.py:2072 msgid "Tool removed from Tools DB." msgstr "Werkzeug wurde aus der Werkzeugdatenbank gelöscht." -#: FlatCAMCommon.py:1096 +#: FlatCAMDB.py:653 FlatCAMDB.py:2083 msgid "Export Tools Database" msgstr "Werkzeugdatenbank exportieren" -#: FlatCAMCommon.py:1099 +#: FlatCAMDB.py:656 FlatCAMDB.py:2086 msgid "Tools_Database" msgstr "Werkzeugdatenbank" -#: FlatCAMCommon.py:1106 -msgid "FlatCAM Tools DB export cancelled." -msgstr "Export der FlatCAM Werkzeugdatenbank abgebrochen." - -#: FlatCAMCommon.py:1136 FlatCAMCommon.py:1139 FlatCAMCommon.py:1191 +#: FlatCAMDB.py:693 FlatCAMDB.py:696 FlatCAMDB.py:748 FlatCAMDB.py:2123 +#: FlatCAMDB.py:2126 FlatCAMDB.py:2178 msgid "Failed to write Tools DB to file." msgstr "Fehler beim Schreiben der Werkzeugdatenbank in eine Datei." -#: FlatCAMCommon.py:1142 +#: FlatCAMDB.py:699 FlatCAMDB.py:2129 msgid "Exported Tools DB to" msgstr "Werkzeugdatenbank wurde exportiert nach" -#: FlatCAMCommon.py:1149 +#: FlatCAMDB.py:706 FlatCAMDB.py:2136 msgid "Import FlatCAM Tools DB" msgstr "Import der FlatCAM-Werkzeugdatenbank" -#: FlatCAMCommon.py:1152 -msgid "FlatCAM Tools DB import cancelled." -msgstr "Import der FlatCAM-Werkzeugdatenbank abgebrochen." - -#: FlatCAMCommon.py:1195 +#: FlatCAMDB.py:752 FlatCAMDB.py:2182 msgid "Saved Tools DB." msgstr "Datenbank der gespeicherten Werkzeuge." -#: FlatCAMCommon.py:1342 +#: FlatCAMDB.py:899 FlatCAMDB.py:2365 msgid "No Tool/row selected in the Tools Database table" msgstr "" "Gescheitert. Kein Werkzeug (keine Spalte) in der Werkzeugtabelle ausgewählt" -#: FlatCAMCommon.py:1360 +#: FlatCAMDB.py:917 FlatCAMDB.py:2382 msgid "Cancelled adding tool from DB." msgstr "Hinzufügen aus der Datenbank wurde abgebrochen." -#: FlatCAMObj.py:257 -msgid "Name changed from" -msgstr "Name geändert von" +#: FlatCAMDB.py:1018 +msgid "Basic Geo Parameters" +msgstr "Grundlegende Geoparameter" -#: FlatCAMObj.py:257 -msgid "to" -msgstr "zu" +#: FlatCAMDB.py:1030 +msgid "Advanced Geo Parameters" +msgstr "Erweiterte Geoparameter" -#: FlatCAMObj.py:268 -msgid "Offsetting..." -msgstr "Offset hinzufügen ..." +#: FlatCAMDB.py:1042 +msgid "NCC Parameters" +msgstr "NCC-Parameter" -#: FlatCAMObj.py:282 FlatCAMObj.py:287 -msgid "Scaling could not be executed." -msgstr "Skalierungsaktion wurde nicht ausgeführt." +#: FlatCAMDB.py:1054 +msgid "Paint Parameters" +msgstr "Lackparameter" -#: FlatCAMObj.py:291 FlatCAMObj.py:299 -msgid "Scale done." -msgstr "Skalieren Sie fertig." +#: FlatCAMDB.py:1185 flatcamGUI/ObjectUI.py:967 flatcamGUI/ObjectUI.py:1769 +#: flatcamGUI/PreferencesUI.py:5495 flatcamGUI/PreferencesUI.py:8175 +#: flatcamTools/ToolSolderPaste.py:253 +msgid "Feedrate X-Y" +msgstr "Vorschub X-Y" -#: FlatCAMObj.py:297 -msgid "Scaling..." -msgstr "Skalierung ..." - -#: FlatCAMObj.py:315 -msgid "Skewing..." -msgstr "Verziehen..." - -#: FlatCAMObj.py:736 FlatCAMObj.py:2746 FlatCAMObj.py:3968 -#: flatcamGUI/PreferencesUI.py:1470 flatcamGUI/PreferencesUI.py:2859 -msgid "Basic" -msgstr "Basis" - -#: FlatCAMObj.py:763 FlatCAMObj.py:2758 FlatCAMObj.py:3989 -#: flatcamGUI/PreferencesUI.py:1471 -msgid "Advanced" -msgstr "Erweitert" - -#: FlatCAMObj.py:980 -msgid "Buffering solid geometry" -msgstr "Festkörpergeometrie puffern" - -#: FlatCAMObj.py:983 camlib.py:965 flatcamGUI/PreferencesUI.py:2296 -#: flatcamTools/ToolCopperThieving.py:1011 -#: flatcamTools/ToolCopperThieving.py:1200 -#: flatcamTools/ToolCopperThieving.py:1212 -#: flatcamTools/ToolNonCopperClear.py:1624 -#: flatcamTools/ToolNonCopperClear.py:1721 -#: flatcamTools/ToolNonCopperClear.py:1732 -#: flatcamTools/ToolNonCopperClear.py:2015 -#: flatcamTools/ToolNonCopperClear.py:2111 -#: flatcamTools/ToolNonCopperClear.py:2123 -msgid "Buffering" -msgstr "Pufferung" - -#: FlatCAMObj.py:989 -msgid "Done" -msgstr "Fertig" - -#: FlatCAMObj.py:1040 -msgid "Isolating..." -msgstr "Isolieren ..." - -#: FlatCAMObj.py:1099 -msgid "Click on a polygon to isolate it." -msgstr "Klicken Sie auf ein Plozgon um es zu isolieren." - -#: FlatCAMObj.py:1138 FlatCAMObj.py:1243 flatcamTools/ToolPaint.py:1120 -msgid "Added polygon" -msgstr "Polygon hinzugefügt" - -#: FlatCAMObj.py:1140 FlatCAMObj.py:1245 -msgid "Click to add next polygon or right click to start isolation." -msgstr "" -"Klicken Sie, um das nächste Polygon hinzuzufügen, oder klicken Sie mit der " -"rechten Maustaste, um den Isolationsvorgang zu beginnen." - -#: FlatCAMObj.py:1152 flatcamTools/ToolPaint.py:1134 -msgid "Removed polygon" -msgstr "Polygon entfernt" - -# nearly the same as before? What good is this? -#: FlatCAMObj.py:1153 -msgid "Click to add/remove next polygon or right click to start isolation." -msgstr "" -"Klicken Sie, um das nächste Polygon hinzuzufügen oder zu entfernen, oder " -"klicken Sie mit der rechten Maustaste, um den Isolationsvorgang zu beginnen." - -#: FlatCAMObj.py:1158 flatcamTools/ToolPaint.py:1140 -msgid "No polygon detected under click position." -msgstr "Kein Polygon an der Stelle an die geklickt wurde." - -#: FlatCAMObj.py:1179 flatcamTools/ToolPaint.py:1169 -msgid "List of single polygons is empty. Aborting." -msgstr "Liste der Einzelpolygone ist leer. Vorgang wird abgebrochen." - -#: FlatCAMObj.py:1248 -msgid "No polygon in selection." -msgstr "Kein Polygon in der Auswahl." - -#: FlatCAMObj.py:1324 FlatCAMObj.py:1457 -#: flatcamTools/ToolNonCopperClear.py:1653 -#: flatcamTools/ToolNonCopperClear.py:2039 -msgid "Isolation geometry could not be generated." -msgstr "Isolationsgeometrie konnte nicht generiert werden." - -#: FlatCAMObj.py:1374 FlatCAMObj.py:3637 FlatCAMObj.py:3922 FlatCAMObj.py:4221 -msgid "Rough" -msgstr "Rau" - -#: FlatCAMObj.py:1400 FlatCAMObj.py:1480 -msgid "Isolation geometry created" -msgstr "Isolationsgeometrie erstellt" - -#: FlatCAMObj.py:1409 FlatCAMObj.py:1487 -msgid "Subtracting Geo" -msgstr "Geo subtrahieren" - -#: FlatCAMObj.py:1807 -msgid "Plotting Apertures" -msgstr "Plotten Apertures" - -#: FlatCAMObj.py:2573 flatcamEditors/FlatCAMExcEditor.py:2427 -msgid "Total Drills" -msgstr "Bohrungen insgesamt" - -#: FlatCAMObj.py:2605 flatcamEditors/FlatCAMExcEditor.py:2459 -msgid "Total Slots" -msgstr "Schlitz insgesamt" - -#: FlatCAMObj.py:3060 FlatCAMObj.py:3155 FlatCAMObj.py:3276 -msgid "Please select one or more tools from the list and try again." -msgstr "" -"Bitte wählen Sie ein oder mehrere Werkzeuge aus der Liste aus und versuchen " -"Sie es erneut." - -#: FlatCAMObj.py:3067 -msgid "Milling tool for DRILLS is larger than hole size. Cancelled." -msgstr "Das Fräswerkzeug für BOHRER ist größer als die Lochgröße. Abgebrochen." - -#: FlatCAMObj.py:3068 FlatCAMObj.py:4533 flatcamEditors/FlatCAMGeoEditor.py:408 -#: flatcamGUI/FlatCAMGUI.py:457 flatcamGUI/FlatCAMGUI.py:1072 -#: flatcamGUI/ObjectUI.py:1353 -msgid "Tool" -msgstr "Werkzeug" - -#: FlatCAMObj.py:3084 FlatCAMObj.py:3177 FlatCAMObj.py:3295 -msgid "Tool_nr" -msgstr "Werkzeugnummer" - -#: FlatCAMObj.py:3084 FlatCAMObj.py:3177 FlatCAMObj.py:3295 -#: flatcamEditors/FlatCAMExcEditor.py:1582 -#: flatcamEditors/FlatCAMExcEditor.py:3048 flatcamGUI/ObjectUI.py:777 -#: flatcamTools/ToolNonCopperClear.py:120 flatcamTools/ToolPaint.py:123 -#: flatcamTools/ToolPcbWizard.py:76 flatcamTools/ToolProperties.py:396 -#: flatcamTools/ToolProperties.py:449 flatcamTools/ToolSolderPaste.py:84 -msgid "Diameter" -msgstr "Durchmesser" - -#: FlatCAMObj.py:3084 FlatCAMObj.py:3177 FlatCAMObj.py:3295 -msgid "Drills_Nr" -msgstr "Bohrnummer" - -#: FlatCAMObj.py:3084 FlatCAMObj.py:3177 FlatCAMObj.py:3295 -msgid "Slots_Nr" -msgstr "Schlitznummer" - -#: FlatCAMObj.py:3164 -msgid "Milling tool for SLOTS is larger than hole size. Cancelled." -msgstr "" -"Das Fräswerkzeug für SCHLITZ ist größer als die Lochgröße. Abgebrochen." - -#: FlatCAMObj.py:3336 +#: FlatCAMDB.py:1187 msgid "" -"Wrong value format for self.defaults[\"z_pdepth\"] or self.options[\"z_pdepth" -"\"]" +"Feedrate X-Y. Feedrate\n" +"The speed on XY plane used while cutting into material." msgstr "" -"Falsches Wertformat für self.defaults [\"z_pdepth\"] oder self.options " -"[\"z_pdepth\"]" +"Vorschub X-Y. Vorschubgeschwindigkeit\n" +"Die Geschwindigkeit in der XY-Ebene, die beim Schneiden in Material " +"verwendet wird." -#: FlatCAMObj.py:3347 +#: FlatCAMDB.py:1199 flatcamGUI/ObjectUI.py:982 flatcamGUI/ObjectUI.py:1783 +#: flatcamGUI/PreferencesUI.py:4542 flatcamGUI/PreferencesUI.py:5510 +#: flatcamGUI/PreferencesUI.py:8188 flatcamTools/ToolSolderPaste.py:265 +msgid "Feedrate Z" +msgstr "Vorschub Z" + +#: FlatCAMDB.py:1201 msgid "" -"Wrong value format for self.defaults[\"feedrate_probe\"] or self." -"options[\"feedrate_probe\"]" +"Feedrate Z\n" +"The speed on Z plane." msgstr "" -"Falsches Wertformat für self.defaults [\"feedrate_probe\"] oder self.options " -"[\"feedrate_probe\"]" +"Vorschub Z.\n" +"Die Geschwindigkeit in der Z-Ebene." -#: FlatCAMObj.py:3377 FlatCAMObj.py:5354 FlatCAMObj.py:5358 FlatCAMObj.py:5493 -msgid "Generating CNC Code" -msgstr "CNC-Code generieren" +#: FlatCAMDB.py:1399 flatcamGUI/ObjectUI.py:845 +#: flatcamGUI/PreferencesUI.py:4381 flatcamTools/ToolNCC.py:341 +msgid "Operation" +msgstr "Operation" -#: FlatCAMObj.py:3637 FlatCAMObj.py:4632 FlatCAMObj.py:4633 FlatCAMObj.py:4642 -msgid "Iso" -msgstr "Iso" - -#: FlatCAMObj.py:3637 -msgid "Finish" -msgstr "Oberfläche" - -#: FlatCAMObj.py:3957 -msgid "Add from Tool DB" -msgstr "Werkzeug aus Werkzeugdatenbank hinzufügen" - -#: FlatCAMObj.py:3960 flatcamGUI/FlatCAMGUI.py:678 flatcamGUI/FlatCAMGUI.py:794 -#: flatcamGUI/FlatCAMGUI.py:989 flatcamGUI/FlatCAMGUI.py:2015 -#: flatcamGUI/FlatCAMGUI.py:2159 flatcamGUI/FlatCAMGUI.py:2378 -#: flatcamGUI/FlatCAMGUI.py:2557 flatcamGUI/ObjectUI.py:1324 -#: flatcamTools/ToolPanelize.py:534 flatcamTools/ToolPanelize.py:561 -#: flatcamTools/ToolPanelize.py:660 flatcamTools/ToolPanelize.py:694 -#: flatcamTools/ToolPanelize.py:759 -msgid "Copy" -msgstr "Kopieren" - -#: FlatCAMObj.py:4054 FlatCAMObj.py:4397 FlatCAMObj.py:5107 FlatCAMObj.py:5744 -#: flatcamEditors/FlatCAMExcEditor.py:2534 -#: flatcamEditors/FlatCAMGeoEditor.py:1078 -#: flatcamEditors/FlatCAMGeoEditor.py:1112 -#: flatcamEditors/FlatCAMGeoEditor.py:1133 -#: flatcamEditors/FlatCAMGeoEditor.py:1154 -#: flatcamEditors/FlatCAMGeoEditor.py:1191 -#: flatcamEditors/FlatCAMGeoEditor.py:1219 -#: flatcamEditors/FlatCAMGeoEditor.py:1240 -#: flatcamTools/ToolNonCopperClear.py:1052 -#: flatcamTools/ToolNonCopperClear.py:1461 flatcamTools/ToolPaint.py:835 -#: flatcamTools/ToolPaint.py:1019 flatcamTools/ToolPaint.py:2198 -#: flatcamTools/ToolSolderPaste.py:882 flatcamTools/ToolSolderPaste.py:957 -msgid "Wrong value format entered, use a number." -msgstr "Falsches Wertformat eingegeben, eine Zahl verwenden." - -#: FlatCAMObj.py:4240 -msgid "Tool added in Tool Table." -msgstr "Werkzeug in der Werkzeugtabelle hinzugefügt." - -#: FlatCAMObj.py:4347 FlatCAMObj.py:4356 -msgid "Failed. Select a tool to copy." -msgstr "Fehlgeschlagen. Wählen Sie ein Werkzeug zum Kopieren aus." - -#: FlatCAMObj.py:4383 -msgid "Tool was copied in Tool Table." -msgstr "Das Werkzeug wurde in die Werkzeugtabelle kopiert." - -#: FlatCAMObj.py:4411 -msgid "Tool was edited in Tool Table." -msgstr "Das Werkzeug wurde in der Werkzeugtabelle bearbeitet." - -#: FlatCAMObj.py:4440 FlatCAMObj.py:4449 -msgid "Failed. Select a tool to delete." -msgstr "Gescheitert. Wählen Sie ein Werkzeug zum Löschen aus." - -#: FlatCAMObj.py:4472 -msgid "Tool was deleted in Tool Table." -msgstr "Werkzeug wurde in der Werkzeugtabelle gelöscht." - -#: FlatCAMObj.py:4533 flatcamGUI/ObjectUI.py:1353 -msgid "Parameters for" -msgstr "Parameter für" - -#: FlatCAMObj.py:4967 -msgid "This Geometry can't be processed because it is" -msgstr "Diese Geometrie kann nicht verarbeitet werden, da dies der Fall ist" - -#: FlatCAMObj.py:4969 -msgid "geometry" -msgstr "geometrie" - -#: FlatCAMObj.py:5012 -msgid "Failed. No tool selected in the tool table ..." -msgstr "Gescheitert. Kein Werkzeug in der Werkzeugtabelle ausgewählt ..." - -#: FlatCAMObj.py:5112 FlatCAMObj.py:5264 +#: FlatCAMDB.py:1401 flatcamTools/ToolNCC.py:343 msgid "" -"Tool Offset is selected in Tool Table but no value is provided.\n" -"Add a Tool Offset or change the Offset Type." +"The 'Operation' can be:\n" +"- Isolation -> will ensure that the non-copper clearing is always complete.\n" +"If it's not successful then the non-copper clearing will fail, too.\n" +"- Clear -> the regular non-copper clearing." msgstr "" -"Werkzeugversatz ist in der Werkzeugtabelle ausgewählt, es wird jedoch kein " -"Wert angegeben.\n" -"Fügen Sie einen Werkzeugversatz hinzu oder ändern Sie den Versatztyp." +"Die 'Operation' kann sein:\n" +"- Isolierung-> stellt sicher, dass das Löschen ohne Kupfer immer " +"abgeschlossen ist.\n" +"Wenn dies nicht erfolgreich ist, schlägt auch das Löschen ohne Kupfer fehl.\n" +"- Klären-> das reguläre Nicht-Kupfer-löschen." -#: FlatCAMObj.py:5177 FlatCAMObj.py:5325 -msgid "G-Code parsing in progress..." -msgstr "G-Code-Analyse läuft ..." +#: FlatCAMDB.py:1408 flatcamEditors/FlatCAMGrbEditor.py:2739 +#: flatcamGUI/GUIElements.py:2577 flatcamTools/ToolNCC.py:350 +msgid "Clear" +msgstr "Klären" -#: FlatCAMObj.py:5179 FlatCAMObj.py:5327 -msgid "G-Code parsing finished..." -msgstr "G-Code-Analyse beendet ..." +#: FlatCAMDB.py:1409 flatcamTools/ToolNCC.py:351 flatcamTools/ToolNCC.py:1618 +msgid "Isolation" +msgstr "Isolation" -#: FlatCAMObj.py:5187 -msgid "Finished G-Code processing" -msgstr "G-Code-Verarbeitung abgeschlossen" +#: FlatCAMDB.py:1417 flatcamGUI/ObjectUI.py:409 flatcamGUI/ObjectUI.py:867 +#: flatcamGUI/PreferencesUI.py:3374 flatcamGUI/PreferencesUI.py:4397 +#: flatcamGUI/PreferencesUI.py:5782 flatcamGUI/PreferencesUI.py:6533 +#: flatcamTools/ToolNCC.py:359 +msgid "Milling Type" +msgstr "Fräsart" -#: FlatCAMObj.py:5189 FlatCAMObj.py:5339 -msgid "G-Code processing failed with error" -msgstr "G-Code-Verarbeitung fehlgeschlagen mit Fehler" - -#: FlatCAMObj.py:5234 flatcamTools/ToolSolderPaste.py:1303 -msgid "Cancelled. Empty file, it has no geometry" -msgstr "Abgebrochen. Leere Datei hat keine Geometrie" - -#: FlatCAMObj.py:5337 FlatCAMObj.py:5486 -msgid "Finished G-Code processing..." -msgstr "Fertige G-Code Verarbeitung ..." - -#: FlatCAMObj.py:5356 FlatCAMObj.py:5360 FlatCAMObj.py:5496 -msgid "CNCjob created" -msgstr "CNCjob erstellt" - -#: FlatCAMObj.py:5527 FlatCAMObj.py:5536 flatcamParsers/ParseGerber.py:1794 -#: flatcamParsers/ParseGerber.py:1804 -msgid "Scale factor has to be a number: integer or float." -msgstr "" -"Der Skalierungsfaktor muss eine Zahl sein: Ganzzahl oder Fließkommazahl." - -#: FlatCAMObj.py:5600 -msgid "Geometry Scale done." -msgstr "Geometrie Skalierung fertig." - -#: FlatCAMObj.py:5617 flatcamParsers/ParseGerber.py:1920 +#: FlatCAMDB.py:1419 FlatCAMDB.py:1427 flatcamGUI/PreferencesUI.py:6535 +#: flatcamGUI/PreferencesUI.py:6543 flatcamTools/ToolNCC.py:361 +#: flatcamTools/ToolNCC.py:369 msgid "" -"An (x,y) pair of values are needed. Probable you entered only one value in " -"the Offset field." +"Milling type when the selected tool is of type: 'iso_op':\n" +"- climb / best for precision milling and to reduce tool usage\n" +"- conventional / useful when there is no backlash compensation" msgstr "" -"Ein (x, y) Wertepaar wird benötigt. Wahrscheinlich haben Sie im Feld Offset " -"nur einen Wert eingegeben." +"Frästyp, wenn das ausgewählte Werkzeug vom Typ 'iso_op' ist:\n" +"- Besteigung / am besten zum Präzisionsfräsen und zur Reduzierung des " +"Werkzeugverbrauchs\n" +"- konventionell / nützlich, wenn kein Spielausgleich vorhanden ist" -#: FlatCAMObj.py:5674 -msgid "Geometry Offset done." -msgstr "Geometrie Offset fertig." +#: FlatCAMDB.py:1424 flatcamGUI/ObjectUI.py:415 +#: flatcamGUI/PreferencesUI.py:3381 flatcamGUI/PreferencesUI.py:5788 +#: flatcamGUI/PreferencesUI.py:6540 flatcamTools/ToolNCC.py:366 +msgid "Climb" +msgstr "Steigen" -#: FlatCAMObj.py:5703 +# Cannot translate without context. +#: FlatCAMDB.py:1425 flatcamGUI/ObjectUI.py:416 +#: flatcamGUI/PreferencesUI.py:3382 flatcamGUI/PreferencesUI.py:5789 +#: flatcamGUI/PreferencesUI.py:6541 flatcamTools/ToolNCC.py:367 +msgid "Conventional" +msgstr "Konventionell" + +#: FlatCAMDB.py:1437 FlatCAMDB.py:1546 flatcamEditors/FlatCAMGeoEditor.py:451 +#: flatcamGUI/PreferencesUI.py:6578 flatcamGUI/PreferencesUI.py:7119 +#: flatcamTools/ToolNCC.py:382 flatcamTools/ToolPaint.py:329 +msgid "Overlap" +msgstr "Überlappung" + +# Double +#: FlatCAMDB.py:1439 flatcamGUI/PreferencesUI.py:6580 +#: flatcamTools/ToolNCC.py:384 msgid "" -"The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, " -"y)\n" -"but now there is only one value, not two." +"How much (percentage) of the tool width to overlap each tool pass.\n" +"Adjust the value starting with lower values\n" +"and increasing it if areas that should be cleared are still \n" +"not cleared.\n" +"Lower values = faster processing, faster execution on CNC.\n" +"Higher values = slow processing and slow execution on CNC\n" +"due of too many paths." msgstr "" -"Das Werkzeugwechsel X, Y Feld in Bearbeiten -> Einstellungen muss im Format " -"(x, y) sein\n" -"Aber jetzt gibt es nur einen Wert, nicht zwei." +"Wie viel (Prozent) der Werkzeugbreite, um jeden Werkzeugdurchlauf zu " +"überlappen.\n" +"Passen Sie den Wert beginnend mit niedrigeren Werten an\n" +"und es zu erhöhen, wenn noch Bereiche sind, die geräumt werden sollen\n" +"ungeklärt.\n" +"Niedrigere Werte = schnellere Verarbeitung, schnellere Ausführung auf CNC.\n" +"Höhere Werte = langsame Verarbeitung und langsame Ausführung auf CNC\n" +"wegen zu vieler Wege." -#: FlatCAMObj.py:6388 FlatCAMObj.py:7175 FlatCAMObj.py:7371 -msgid "Basic" -msgstr "Basic" +#: FlatCAMDB.py:1458 FlatCAMDB.py:1567 flatcamEditors/FlatCAMGeoEditor.py:471 +#: flatcamGUI/PreferencesUI.py:6598 flatcamGUI/PreferencesUI.py:6840 +#: flatcamGUI/PreferencesUI.py:7139 flatcamGUI/PreferencesUI.py:8797 +#: flatcamGUI/PreferencesUI.py:8954 flatcamGUI/PreferencesUI.py:9039 +#: flatcamGUI/PreferencesUI.py:9686 flatcamGUI/PreferencesUI.py:9694 +#: flatcamTools/ToolCopperThieving.py:111 +#: flatcamTools/ToolCopperThieving.py:362 flatcamTools/ToolCutOut.py:190 +#: flatcamTools/ToolFiducials.py:172 flatcamTools/ToolInvertGerber.py:88 +#: flatcamTools/ToolInvertGerber.py:96 flatcamTools/ToolNCC.py:403 +#: flatcamTools/ToolPaint.py:350 +msgid "Margin" +msgstr "Marge" -#: FlatCAMObj.py:6394 FlatCAMObj.py:7179 FlatCAMObj.py:7375 -msgid "Advanced" -msgstr "Erweitert" +#: FlatCAMDB.py:1460 flatcamGUI/PreferencesUI.py:6600 +#: flatcamGUI/PreferencesUI.py:8799 flatcamGUI/PreferencesUI.py:9041 +#: flatcamGUI/PreferencesUI.py:9105 flatcamTools/ToolCopperThieving.py:113 +#: flatcamTools/ToolFiducials.py:174 flatcamTools/ToolFiducials.py:237 +#: flatcamTools/ToolNCC.py:405 +msgid "Bounding box margin." +msgstr "Begrenzungsrahmenrand." -#: FlatCAMObj.py:6437 -msgid "Plotting..." -msgstr "Zeichnung..." +#: FlatCAMDB.py:1471 FlatCAMDB.py:1582 flatcamEditors/FlatCAMGeoEditor.py:485 +#: flatcamGUI/PreferencesUI.py:6611 flatcamGUI/PreferencesUI.py:7154 +#: flatcamGUI/PreferencesUI.py:9320 flatcamGUI/PreferencesUI.py:9533 +#: flatcamTools/ToolExtractDrills.py:128 flatcamTools/ToolNCC.py:416 +#: flatcamTools/ToolPaint.py:365 flatcamTools/ToolPunchGerber.py:139 +msgid "Method" +msgstr "Methode" -#: FlatCAMObj.py:6460 FlatCAMObj.py:6465 flatcamTools/ToolSolderPaste.py:1509 -msgid "Export Machine Code ..." -msgstr "Maschinencode exportieren ..." - -#: FlatCAMObj.py:6470 flatcamTools/ToolSolderPaste.py:1513 -msgid "Export Machine Code cancelled ..." -msgstr "Maschinencode exportieren abgebrochen ..." - -#: FlatCAMObj.py:6492 -msgid "Machine Code file saved to" -msgstr "Maschinencode-Datei gespeichert in" - -#: FlatCAMObj.py:6546 flatcamTools/ToolCalibration.py:1083 -msgid "Loaded Machine Code into Code Editor" -msgstr "Maschinencode in den Code-Editor geladen" - -#: FlatCAMObj.py:6684 -msgid "This CNCJob object can't be processed because it is a" -msgstr "Dieses CNCJob-Objekt kann nicht verarbeitet werden, da es sich um ein" - -#: FlatCAMObj.py:6686 -msgid "CNCJob object" -msgstr "CNCJob-Objekt" - -#: FlatCAMObj.py:6866 +#: FlatCAMDB.py:1473 flatcamGUI/PreferencesUI.py:6613 +#: flatcamTools/ToolNCC.py:418 msgid "" -"G-code does not have a G94 code and we will not include the code in the " -"'Prepend to GCode' text box" +"Algorithm for copper clearing:\n" +"- Standard: Fixed step inwards.\n" +"- Seed-based: Outwards from seed.\n" +"- Line-based: Parallel lines." msgstr "" -"G-Code hat keinen G94-Code und wir werden den Code nicht in das Textfeld " -"\"Vor dem GCode\" aufnehmen" +"Algorithmus zur Kupferreinigung:\n" +"- Standard: Schritt nach innen behoben.\n" +"- Samenbasiert: Aus dem Samen heraus.\n" +"- Linienbasiert: Parallele Linien." -#: FlatCAMObj.py:6877 -msgid "Cancelled. The Toolchange Custom code is enabled but it's empty." -msgstr "" -"Abgebrochen. Der benutzerdefinierte Code zum Ändern des Werkzeugs ist " -"aktiviert, aber er ist leer." +#: FlatCAMDB.py:1481 FlatCAMDB.py:1596 flatcamEditors/FlatCAMGeoEditor.py:499 +#: flatcamGUI/PreferencesUI.py:6626 flatcamGUI/PreferencesUI.py:7173 +#: flatcamTools/ToolNCC.py:431 flatcamTools/ToolNCC.py:2390 +#: flatcamTools/ToolNCC.py:2419 flatcamTools/ToolNCC.py:2688 +#: flatcamTools/ToolNCC.py:2720 flatcamTools/ToolPaint.py:390 +#: flatcamTools/ToolPaint.py:1829 tclCommands/TclCommandCopperClear.py:126 +#: tclCommands/TclCommandCopperClear.py:134 tclCommands/TclCommandPaint.py:125 +msgid "Standard" +msgstr "Standard" -#: FlatCAMObj.py:6882 -msgid "Toolchange G-code was replaced by a custom code." -msgstr "" -"Der Werkzeugwechsel-G-Code wurde durch einen benutzerdefinierten Code " -"ersetzt." +#: FlatCAMDB.py:1481 FlatCAMDB.py:1596 defaults.py:390 defaults.py:422 +#: flatcamEditors/FlatCAMGeoEditor.py:499 +#: flatcamEditors/FlatCAMGeoEditor.py:569 +#: flatcamEditors/FlatCAMGeoEditor.py:5152 flatcamGUI/PreferencesUI.py:6626 +#: flatcamGUI/PreferencesUI.py:7173 flatcamTools/ToolNCC.py:431 +#: flatcamTools/ToolNCC.py:2396 flatcamTools/ToolNCC.py:2424 +#: flatcamTools/ToolNCC.py:2694 flatcamTools/ToolNCC.py:2726 +#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:1843 +#: tclCommands/TclCommandCopperClear.py:128 +#: tclCommands/TclCommandCopperClear.py:136 tclCommands/TclCommandPaint.py:127 +msgid "Seed" +msgstr "Keim" -#: FlatCAMObj.py:6899 flatcamEditors/FlatCAMTextEditor.py:270 -#: flatcamTools/ToolSolderPaste.py:1540 -msgid "No such file or directory" -msgstr "Keine solche Datei oder Ordner" +#: FlatCAMDB.py:1481 FlatCAMDB.py:1596 flatcamEditors/FlatCAMGeoEditor.py:499 +#: flatcamEditors/FlatCAMGeoEditor.py:5156 flatcamGUI/PreferencesUI.py:6626 +#: flatcamGUI/PreferencesUI.py:7173 flatcamTools/ToolNCC.py:431 +#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:699 +#: flatcamTools/ToolPaint.py:1857 tclCommands/TclCommandCopperClear.py:130 +#: tclCommands/TclCommandPaint.py:129 +msgid "Lines" +msgstr "Linien" -#: FlatCAMObj.py:6913 flatcamEditors/FlatCAMTextEditor.py:282 -msgid "Saved to" -msgstr "Gespeichert in" +#: FlatCAMDB.py:1489 FlatCAMDB.py:1607 flatcamGUI/PreferencesUI.py:6633 +#: flatcamGUI/PreferencesUI.py:7180 flatcamTools/ToolNCC.py:439 +#: flatcamTools/ToolPaint.py:401 +msgid "Connect" +msgstr "Verbinden" -#: FlatCAMObj.py:6923 FlatCAMObj.py:6933 +#: FlatCAMDB.py:1493 FlatCAMDB.py:1610 flatcamEditors/FlatCAMGeoEditor.py:508 +#: flatcamGUI/PreferencesUI.py:6635 flatcamGUI/PreferencesUI.py:7182 +#: flatcamTools/ToolNCC.py:443 flatcamTools/ToolPaint.py:404 msgid "" -"The used preprocessor file has to have in it's name: 'toolchange_custom'" +"Draw lines between resulting\n" +"segments to minimize tool lifts." msgstr "" -"Die verwendete Postprozessor-Datei muss im Namen enthalten sein: " -"'toolchange_custom'" +"Zeichnen Sie Linien zwischen den Ergebnissen\n" +"Segmente, um Werkzeuglifte zu minimieren." -#: FlatCAMObj.py:6937 -msgid "There is no preprocessor file." -msgstr "Es gibt keine Postprozessor-Datei." +#: FlatCAMDB.py:1499 FlatCAMDB.py:1614 flatcamGUI/PreferencesUI.py:6642 +#: flatcamGUI/PreferencesUI.py:7188 flatcamTools/ToolNCC.py:449 +#: flatcamTools/ToolPaint.py:408 +msgid "Contour" +msgstr "Kontur" -#: FlatCAMObj.py:7194 -msgid "Script Editor" -msgstr "Script Editor" +#: FlatCAMDB.py:1503 FlatCAMDB.py:1617 flatcamEditors/FlatCAMGeoEditor.py:518 +#: flatcamGUI/PreferencesUI.py:6644 flatcamGUI/PreferencesUI.py:7190 +#: flatcamTools/ToolNCC.py:453 flatcamTools/ToolPaint.py:411 +msgid "" +"Cut around the perimeter of the polygon\n" +"to trim rough edges." +msgstr "" +"Schneiden Sie um den Umfang des Polygons herum\n" +"Ecken und Kanten schneiden." -#: FlatCAMObj.py:7475 -msgid "Document Editor" -msgstr "Dokumenteditor" +#: FlatCAMDB.py:1509 flatcamEditors/FlatCAMGeoEditor.py:612 +#: flatcamEditors/FlatCAMGrbEditor.py:5145 flatcamGUI/ObjectUI.py:143 +#: flatcamGUI/ObjectUI.py:1497 flatcamGUI/ObjectUI.py:2246 +#: flatcamGUI/PreferencesUI.py:6651 flatcamGUI/PreferencesUI.py:7939 +#: flatcamTools/ToolNCC.py:459 flatcamTools/ToolTransform.py:28 +msgid "Offset" +msgstr "Versatz" + +#: FlatCAMDB.py:1513 flatcamGUI/PreferencesUI.py:6653 +#: flatcamTools/ToolNCC.py:463 +msgid "" +"If used, it will add an offset to the copper features.\n" +"The copper clearing will finish to a distance\n" +"from the copper features.\n" +"The value can be between 0 and 10 FlatCAM units." +msgstr "" +"Bei Verwendung wird den Kupferelementen ein Offset hinzugefügt.\n" +"Die Kupferreinigung wird bis zu einer gewissen Entfernung enden\n" +"von den Kupfermerkmalen.\n" +"Der Wert kann zwischen 0 und 10 FlatCAM-Einheiten liegen." + +# 3rd Time +#: FlatCAMDB.py:1548 flatcamEditors/FlatCAMGeoEditor.py:453 +#: flatcamGUI/PreferencesUI.py:7121 flatcamTools/ToolPaint.py:331 +msgid "" +"How much (percentage) of the tool width to overlap each tool pass.\n" +"Adjust the value starting with lower values\n" +"and increasing it if areas that should be painted are still \n" +"not painted.\n" +"Lower values = faster processing, faster execution on CNC.\n" +"Higher values = slow processing and slow execution on CNC\n" +"due of too many paths." +msgstr "" +"Wie viel (Prozent) der Werkzeugbreite, um jeden Werkzeugdurchlauf zu " +"überlappen.\n" +"Passen Sie den Wert beginnend mit niedrigeren Werten an\n" +"und erhöhen, wenn Bereiche, die gestrichen werden sollen, noch vorhanden " +"sind\n" +"nicht gemalt.\n" +"Niedrigere Werte = schnellere Verarbeitung, schnellere Ausführung auf CNC.\n" +"Höhere Werte = langsame Verarbeitung und langsame Ausführung auf CNC\n" +"wegen zu vieler Wege." + +#: FlatCAMDB.py:1569 flatcamEditors/FlatCAMGeoEditor.py:473 +#: flatcamGUI/PreferencesUI.py:7141 flatcamTools/ToolPaint.py:352 +msgid "" +"Distance by which to avoid\n" +"the edges of the polygon to\n" +"be painted." +msgstr "" +"Entfernung, um die es zu vermeiden ist\n" +"die Kanten des Polygons bis\n" +"gemalt werden." + +#: FlatCAMDB.py:1584 flatcamGUI/PreferencesUI.py:7156 +#: flatcamTools/ToolPaint.py:367 +msgid "" +"Algorithm for painting:\n" +"- Standard: Fixed step inwards.\n" +"- Seed-based: Outwards from seed.\n" +"- Line-based: Parallel lines.\n" +"- Laser-lines: Active only for Gerber objects.\n" +"Will create lines that follow the traces.\n" +"- Combo: In case of failure a new method will be picked from the above\n" +"in the order specified." +msgstr "" +"Algorithmus zum Malen:\n" +"- Standard: Schritt nach innen behoben.\n" +"- Samenbasiert: Aus dem Samen heraus.\n" +"- Linienbasiert: Parallele Linien.\n" +"- Laserlinien: Nur für Gerber-Objekte aktiv.\n" +"Erstellt Linien, die den Spuren folgen.\n" +"- Combo: Im Fehlerfall wird eine neue Methode aus den oben genannten " +"ausgewählt\n" +"in der angegebenen Reihenfolge." + +#: FlatCAMDB.py:1596 FlatCAMDB.py:1598 flatcamGUI/PreferencesUI.py:7173 +#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:392 +#: flatcamTools/ToolPaint.py:693 flatcamTools/ToolPaint.py:698 +#: flatcamTools/ToolPaint.py:1871 tclCommands/TclCommandPaint.py:131 +msgid "Laser_lines" +msgstr "LaserlinienLinien" + +#: FlatCAMDB.py:1596 flatcamGUI/PreferencesUI.py:7173 +#: flatcamTools/ToolPaint.py:390 flatcamTools/ToolPaint.py:2022 +#: tclCommands/TclCommandPaint.py:133 +msgid "Combo" +msgstr "Combo" + +#: FlatCAMDB.py:1641 +msgid "Add Tool in DB" +msgstr "Werkzeug in DB hinzufügen" #: FlatCAMProcess.py:172 msgid "processes running." msgstr "laufende Prozesse." -#: FlatCAMTranslation.py:103 +#: FlatCAMTool.py:245 FlatCAMTool.py:252 flatcamGUI/ObjectUI.py:157 +#: flatcamGUI/ObjectUI.py:164 +msgid "Edited value is out of range" +msgstr "Der bearbeitete Wert liegt außerhalb des Bereichs" + +#: FlatCAMTool.py:247 FlatCAMTool.py:254 flatcamGUI/ObjectUI.py:159 +#: flatcamGUI/ObjectUI.py:166 +msgid "Edited value is within limits." +msgstr "Der bearbeitete Wert liegt innerhalb der Grenzen." + +#: FlatCAMTranslation.py:104 msgid "The application will restart." msgstr "Die Anwendung wird neu gestartet." -#: FlatCAMTranslation.py:105 +#: FlatCAMTranslation.py:106 msgid "Are you sure do you want to change the current language to" msgstr "Möchten Sie die aktuelle Sprache wirklich in ändern" -#: FlatCAMTranslation.py:106 +#: FlatCAMTranslation.py:107 msgid "Apply Language ..." msgstr "Sprache anwenden ..." -#: ObjectCollection.py:459 -#, python-brace-format -msgid "Object renamed from {old} to {new}" -msgstr "Objekt umbenannt von {old} zu {new}" +#: assets/linux/flatcam-beta.desktop:3 +msgid "FlatCAM Beta" +msgstr "FlatCAM Beta" -#: ObjectCollection.py:858 -msgid "Cause of error" -msgstr "Fehlerursache" +#: assets/linux/flatcam-beta.desktop:7 +msgid "./assets/icon.png" +msgstr "./assets/icon.png" -#: camlib.py:590 +#: assets/linux/flatcam-beta.desktop:8 +msgid "G-Code from GERBERS" +msgstr "G-Code von GERBERS" + +#: camlib.py:597 msgid "self.solid_geometry is neither BaseGeometry or list." msgstr "self.solid_geometry ist weder BaseGeometry noch eine Liste." -#: camlib.py:953 +#: camlib.py:970 msgid "Pass" msgstr "Pass" -#: camlib.py:974 +#: camlib.py:981 flatcamGUI/PreferencesUI.py:3593 +#: flatcamObjects/FlatCAMGerber.py:497 flatcamTools/ToolCopperThieving.py:1016 +#: flatcamTools/ToolCopperThieving.py:1205 +#: flatcamTools/ToolCopperThieving.py:1217 flatcamTools/ToolNCC.py:2045 +#: flatcamTools/ToolNCC.py:2153 flatcamTools/ToolNCC.py:2167 +#: flatcamTools/ToolNCC.py:3098 flatcamTools/ToolNCC.py:3203 +#: flatcamTools/ToolNCC.py:3218 flatcamTools/ToolNCC.py:3484 +#: flatcamTools/ToolNCC.py:3585 flatcamTools/ToolNCC.py:3600 +msgid "Buffering" +msgstr "Pufferung" + +#: camlib.py:990 msgid "Get Exteriors" msgstr "Holen Sie sich das Äußere" -#: camlib.py:977 +#: camlib.py:993 msgid "Get Interiors" msgstr "Holen Sie sich Innenräume" -#: camlib.py:1964 +#: camlib.py:2172 msgid "Object was mirrored" msgstr "Objekt wurde gespiegelt" -#: camlib.py:1967 +#: camlib.py:2174 msgid "Failed to mirror. No object selected" msgstr "Spiegelung fehlgeschlagen Kein Objekt ausgewählt" -#: camlib.py:2036 +#: camlib.py:2239 msgid "Object was rotated" msgstr "Objekt wurde gedreht" -#: camlib.py:2039 +#: camlib.py:2241 msgid "Failed to rotate. No object selected" msgstr "Fehler beim Drehen. Kein Objekt ausgewählt" -#: camlib.py:2107 +#: camlib.py:2307 msgid "Object was skewed" msgstr "Objekt war schief" -#: camlib.py:2110 +#: camlib.py:2309 msgid "Failed to skew. No object selected" msgstr "Fehler beim Neigen Kein Objekt ausgewählt" -#: camlib.py:2179 +#: camlib.py:2385 msgid "Object was buffered" msgstr "Objekt wurde gepuffert" -#: camlib.py:2181 +#: camlib.py:2387 msgid "Failed to buffer. No object selected" msgstr "Fehler beim Puffern. Kein Objekt ausgewählt" -#: camlib.py:2378 +#: camlib.py:2594 msgid "There is no such parameter" msgstr "Es gibt keinen solchen Parameter" -#: camlib.py:2454 +#: camlib.py:2654 camlib.py:2887 camlib.py:3116 camlib.py:3338 msgid "" "The Cut Z parameter has positive value. It is the depth value to drill into " "material.\n" @@ -2872,13 +2724,14 @@ msgstr "" "einen negativen Wert. \n" "Überprüfen Sie den resultierenden CNC-Code (Gcode usw.)." -#: camlib.py:2462 camlib.py:3181 camlib.py:3539 +#: camlib.py:2662 camlib.py:2897 camlib.py:3126 camlib.py:3348 camlib.py:3634 +#: camlib.py:4020 msgid "The Cut Z parameter is zero. There will be no cut, skipping file" msgstr "" "Der Parameter Cut Z ist Null. Es wird kein Schnitt ausgeführt, und die Datei " "wird übersprungen" -#: camlib.py:2475 camlib.py:3512 +#: camlib.py:2673 camlib.py:3988 msgid "" "The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, " "y) \n" @@ -2888,31 +2741,39 @@ msgstr "" "(x, y) sein\n" "Aber jetzt gibt es nur einen Wert, nicht zwei. " -#: camlib.py:2550 +#: camlib.py:2682 camlib.py:3585 camlib.py:3970 +msgid "" +"The End Move X,Y field in Edit -> Preferences has to be in the format (x, y) " +"but now there is only one value, not two." +msgstr "" +"Das Feld Endverschiebung X, Y unter Bearbeiten -> Einstellungen muss das " +"Format (x, y) haben, aber jetzt gibt es nur einen Wert, nicht zwei." + +#: camlib.py:2770 msgid "Creating a list of points to drill..." msgstr "Erstellen einer Liste von Punkten zum Bohren ..." -#: camlib.py:2632 +#: camlib.py:2860 camlib.py:3732 camlib.py:4124 msgid "Starting G-Code" msgstr "G-Code starten" -#: camlib.py:2727 camlib.py:2870 camlib.py:2972 camlib.py:3292 camlib.py:3653 +#: camlib.py:3001 camlib.py:3220 camlib.py:3384 camlib.py:3745 camlib.py:4135 msgid "Starting G-Code for tool with diameter" msgstr "Start-G-Code für Werkzeug mit Durchmesser" -#: camlib.py:2783 camlib.py:2926 camlib.py:3029 +#: camlib.py:3084 camlib.py:3302 camlib.py:3470 msgid "G91 coordinates not implemented" msgstr "G91 Koordinaten nicht implementiert" -#: camlib.py:2789 camlib.py:2933 camlib.py:3035 +#: camlib.py:3090 camlib.py:3309 camlib.py:3476 msgid "The loaded Excellon file has no drills" msgstr "Die geladene Excellon-Datei hat keine Bohrer" -#: camlib.py:3058 +#: camlib.py:3499 msgid "Finished G-Code generation..." msgstr "Fertige G-Code-Generierung ..." -#: camlib.py:3153 +#: camlib.py:3603 msgid "" "The Toolchange X,Y field in Edit -> Preferences has to be in the format (x, " "y) \n" @@ -2922,7 +2783,7 @@ msgstr "" "das Format (x, y) haben.\n" "Aber jetzt gibt es nur einen Wert, nicht zwei." -#: camlib.py:3166 camlib.py:3525 +#: camlib.py:3617 camlib.py:4003 msgid "" "Cut_Z parameter is None or zero. Most likely a bad combinations of other " "parameters." @@ -2930,7 +2791,7 @@ msgstr "" "Der Parameter Cut_Z ist None oder Null. Höchstwahrscheinlich eine schlechte " "Kombination anderer Parameter." -#: camlib.py:3173 camlib.py:3531 +#: camlib.py:3626 camlib.py:4012 msgid "" "The Cut Z parameter has positive value. It is the depth value to cut into " "material.\n" @@ -2945,11 +2806,11 @@ msgstr "" "einen negativen Wert. \n" "Überprüfen Sie den resultierenden CNC-Code (Gcode usw.)." -#: camlib.py:3186 camlib.py:3545 +#: camlib.py:3639 camlib.py:4026 msgid "Travel Z parameter is None or zero." msgstr "Der Parameter für den Travel Z ist Kein oder Null." -#: camlib.py:3191 camlib.py:3550 +#: camlib.py:3644 camlib.py:4031 msgid "" "The Travel Z parameter has negative value. It is the height value to travel " "between cuts.\n" @@ -2963,40 +2824,36 @@ msgstr "" "einen Tippfehler handelt, konvertiert die App den Wert in einen positiven " "Wert. Überprüfen Sie den resultierenden CNC-Code (Gcode usw.)." -#: camlib.py:3199 camlib.py:3558 +#: camlib.py:3652 camlib.py:4039 msgid "The Z Travel parameter is zero. This is dangerous, skipping file" msgstr "" "Der Parameter Z-Weg ist Null. Dies ist gefährlich, da die %s Datei " "übersprungen wird" -#: camlib.py:3218 camlib.py:3580 +#: camlib.py:3671 camlib.py:4062 msgid "Indexing geometry before generating G-Code..." msgstr "Indizierung der Geometrie vor dem Generieren von G-Code ..." -#: camlib.py:3279 camlib.py:3642 -msgid "Starting G-Code..." -msgstr "G-Code wird gestartet ..." - -#: camlib.py:3362 camlib.py:3724 +#: camlib.py:3815 camlib.py:4204 msgid "Finished G-Code generation" msgstr "Fertige G-Code-Generierung" -#: camlib.py:3364 +#: camlib.py:3815 msgid "paths traced" msgstr "Pfade verfolgt" -#: camlib.py:3399 +#: camlib.py:3865 msgid "Expected a Geometry, got" msgstr "Erwartet eine Geometrie, erhalten" -#: camlib.py:3406 +#: camlib.py:3872 msgid "" "Trying to generate a CNC Job from a Geometry object without solid_geometry." msgstr "" "Der Versuch, einen CNC-Auftrag aus einem Geometrieobjekt ohne solid_geometry " "zu generieren." -#: camlib.py:3446 +#: camlib.py:3913 msgid "" "The Tool Offset value is too negative to use for the current_geometry.\n" "Raise the value (in module) and try again." @@ -3005,188 +2862,205 @@ msgstr "" "Geometrie verwendet zu werden.\n" "Erhöhen Sie den Wert (im Modul) und versuchen Sie es erneut." -#: camlib.py:3724 +#: camlib.py:4204 msgid " paths traced." msgstr " Pfade verfolgt." -#: camlib.py:3752 +#: camlib.py:4232 msgid "There is no tool data in the SolderPaste geometry." msgstr "In der SolderPaste-Geometrie sind keine Werkzeugdaten vorhanden." -#: camlib.py:3839 -msgid "Finished SolderPste G-Code generation" -msgstr "Fertige G-Code-Generierung" +#: camlib.py:4321 +msgid "Finished SolderPaste G-Code generation" +msgstr "Fertige G-Code-Generierung für Lötpaste" -#: camlib.py:3841 +#: camlib.py:4321 msgid "paths traced." msgstr "paths traced." -#: camlib.py:4097 +#: camlib.py:4581 msgid "Parsing GCode file. Number of lines" msgstr "Analysieren der GCode-Datei. Anzahl der Zeilen" -#: camlib.py:4204 +#: camlib.py:4688 msgid "Creating Geometry from the parsed GCode file. " msgstr "Erstellen von Geometrie aus der analysierten GCode-Datei. " -#: camlib.py:4345 camlib.py:4629 camlib.py:4732 camlib.py:4801 +#: camlib.py:4831 camlib.py:5123 camlib.py:5234 camlib.py:5390 msgid "G91 coordinates not implemented ..." msgstr "G91 Koordinaten nicht implementiert ..." -#: camlib.py:4476 +#: camlib.py:4963 msgid "Unifying Geometry from parsed Geometry segments" msgstr "Vereinheitlichen von Geometrie aus analysierten Geometriesegmenten" -#: flatcamEditors/FlatCAMExcEditor.py:51 flatcamEditors/FlatCAMExcEditor.py:75 -#: flatcamEditors/FlatCAMExcEditor.py:169 -#: flatcamEditors/FlatCAMExcEditor.py:386 -#: flatcamEditors/FlatCAMExcEditor.py:590 +#: defaults.py:396 flatcamGUI/PreferencesUI.py:6705 +#: flatcamGUI/PreferencesUI.py:8811 flatcamTools/ToolCopperThieving.py:125 +#: flatcamTools/ToolNCC.py:535 flatcamTools/ToolNCC.py:1301 +#: flatcamTools/ToolNCC.py:1629 flatcamTools/ToolNCC.py:1914 +#: flatcamTools/ToolNCC.py:1978 flatcamTools/ToolNCC.py:2962 +#: flatcamTools/ToolNCC.py:2971 tclCommands/TclCommandCopperClear.py:190 +msgid "Itself" +msgstr "Selbst" + +#: defaults.py:423 flatcamGUI/PreferencesUI.py:7236 +#: flatcamTools/ToolPaint.py:486 flatcamTools/ToolPaint.py:1422 +#: tclCommands/TclCommandPaint.py:162 +msgid "All Polygons" +msgstr "Alle Polygone" + +#: defaults.py:734 +msgid "Could not load defaults file." +msgstr "Voreinstellungen konnte nicht geladen werden." + +#: defaults.py:747 +msgid "Failed to parse defaults file." +msgstr "Fehler beim Einlesen der Voreinstellungen." + +#: flatcamEditors/FlatCAMExcEditor.py:50 flatcamEditors/FlatCAMExcEditor.py:74 +#: flatcamEditors/FlatCAMExcEditor.py:168 +#: flatcamEditors/FlatCAMExcEditor.py:385 +#: flatcamEditors/FlatCAMExcEditor.py:589 #: flatcamEditors/FlatCAMGrbEditor.py:241 #: flatcamEditors/FlatCAMGrbEditor.py:248 msgid "Click to place ..." msgstr "Klicken um zu platzieren ..." -#: flatcamEditors/FlatCAMExcEditor.py:59 +#: flatcamEditors/FlatCAMExcEditor.py:58 msgid "To add a drill first select a tool" msgstr "Um einen Bohrer hinzuzufügen, wählen Sie zuerst ein Werkzeug aus" -#: flatcamEditors/FlatCAMExcEditor.py:123 +#: flatcamEditors/FlatCAMExcEditor.py:122 msgid "Done. Drill added." msgstr "Erledigt. Bohrer hinzugefügt." -#: flatcamEditors/FlatCAMExcEditor.py:177 +#: flatcamEditors/FlatCAMExcEditor.py:176 msgid "To add an Drill Array first select a tool in Tool Table" msgstr "" "Um ein Bohr-Array hinzuzufügen, wählen Sie zunächst ein Werkzeug in der " "Werkzeugtabelle aus" -#: flatcamEditors/FlatCAMExcEditor.py:193 -#: flatcamEditors/FlatCAMExcEditor.py:416 -#: flatcamEditors/FlatCAMExcEditor.py:637 -#: flatcamEditors/FlatCAMExcEditor.py:1155 -#: flatcamEditors/FlatCAMExcEditor.py:1182 +#: flatcamEditors/FlatCAMExcEditor.py:192 +#: flatcamEditors/FlatCAMExcEditor.py:415 +#: flatcamEditors/FlatCAMExcEditor.py:636 +#: flatcamEditors/FlatCAMExcEditor.py:1151 +#: flatcamEditors/FlatCAMExcEditor.py:1178 #: flatcamEditors/FlatCAMGrbEditor.py:471 -#: flatcamEditors/FlatCAMGrbEditor.py:1936 -#: flatcamEditors/FlatCAMGrbEditor.py:1966 +#: flatcamEditors/FlatCAMGrbEditor.py:1935 +#: flatcamEditors/FlatCAMGrbEditor.py:1965 msgid "Click on target location ..." msgstr "Klicken Sie auf den Zielort ..." -#: flatcamEditors/FlatCAMExcEditor.py:212 +#: flatcamEditors/FlatCAMExcEditor.py:211 msgid "Click on the Drill Circular Array Start position" msgstr "Klicken Sie auf die Startposition des Bohrkreis-Arrays" -#: flatcamEditors/FlatCAMExcEditor.py:234 -#: flatcamEditors/FlatCAMExcEditor.py:678 +#: flatcamEditors/FlatCAMExcEditor.py:233 +#: flatcamEditors/FlatCAMExcEditor.py:677 #: flatcamEditors/FlatCAMGrbEditor.py:516 msgid "The value is not Float. Check for comma instead of dot separator." msgstr "" "Der Wert ist nicht Real. Überprüfen Sie das Komma anstelle des Trennzeichens." -#: flatcamEditors/FlatCAMExcEditor.py:238 +#: flatcamEditors/FlatCAMExcEditor.py:237 msgid "The value is mistyped. Check the value" msgstr "Der Wert ist falsch geschrieben. Überprüfen Sie den Wert" -#: flatcamEditors/FlatCAMExcEditor.py:337 +#: flatcamEditors/FlatCAMExcEditor.py:336 msgid "Too many drills for the selected spacing angle." msgstr "Zu viele Bohrer für den ausgewählten Abstandswinkel." -#: flatcamEditors/FlatCAMExcEditor.py:355 +#: flatcamEditors/FlatCAMExcEditor.py:354 msgid "Done. Drill Array added." msgstr "Erledigt. Bohrfeld hinzugefügt." -#: flatcamEditors/FlatCAMExcEditor.py:395 +#: flatcamEditors/FlatCAMExcEditor.py:394 msgid "To add a slot first select a tool" msgstr "Um einen Steckplatz hinzuzufügen, wählen Sie zunächst ein Werkzeug aus" -#: flatcamEditors/FlatCAMExcEditor.py:455 -#: flatcamEditors/FlatCAMExcEditor.py:462 -#: flatcamEditors/FlatCAMExcEditor.py:744 -#: flatcamEditors/FlatCAMExcEditor.py:751 +#: flatcamEditors/FlatCAMExcEditor.py:454 +#: flatcamEditors/FlatCAMExcEditor.py:461 +#: flatcamEditors/FlatCAMExcEditor.py:742 +#: flatcamEditors/FlatCAMExcEditor.py:749 msgid "Value is missing or wrong format. Add it and retry." msgstr "" "Wert fehlt oder falsches Format. Fügen Sie es hinzu und versuchen Sie es " "erneut." -#: flatcamEditors/FlatCAMExcEditor.py:560 +#: flatcamEditors/FlatCAMExcEditor.py:559 msgid "Done. Adding Slot completed." msgstr "Erledigt. Das Hinzufügen des Slots ist abgeschlossen." -#: flatcamEditors/FlatCAMExcEditor.py:598 +#: flatcamEditors/FlatCAMExcEditor.py:597 msgid "To add an Slot Array first select a tool in Tool Table" msgstr "" "Um ein Schlitze-Array hinzuzufügen, wählen Sie zunächst ein Werkzeug in der " "Werkzeugtabelle aus" -#: flatcamEditors/FlatCAMExcEditor.py:656 +#: flatcamEditors/FlatCAMExcEditor.py:655 msgid "Click on the Slot Circular Array Start position" msgstr "Klicken Sie auf die kreisförmige Startposition des Arrays" -#: flatcamEditors/FlatCAMExcEditor.py:682 -#: flatcamEditors/FlatCAMGrbEditor.py:520 +#: flatcamEditors/FlatCAMExcEditor.py:680 +#: flatcamEditors/FlatCAMGrbEditor.py:519 msgid "The value is mistyped. Check the value." msgstr "Der Wert ist falsch geschrieben. Überprüfen Sie den Wert." -#: flatcamEditors/FlatCAMExcEditor.py:861 +#: flatcamEditors/FlatCAMExcEditor.py:859 msgid "Too many Slots for the selected spacing angle." msgstr "Zu viele Slots für den ausgewählten Abstandswinkel." -#: flatcamEditors/FlatCAMExcEditor.py:884 +#: flatcamEditors/FlatCAMExcEditor.py:882 msgid "Done. Slot Array added." msgstr "Erledigt. Schlitze Array hinzugefügt." -#: flatcamEditors/FlatCAMExcEditor.py:906 +#: flatcamEditors/FlatCAMExcEditor.py:904 msgid "Click on the Drill(s) to resize ..." msgstr "Klicken Sie auf die Bohrer, um die Größe zu ändern ..." -#: flatcamEditors/FlatCAMExcEditor.py:936 +#: flatcamEditors/FlatCAMExcEditor.py:934 msgid "Resize drill(s) failed. Please enter a diameter for resize." msgstr "" "Die Größe der Bohrer ist fehlgeschlagen. Bitte geben Sie einen Durchmesser " "für die Größenänderung ein." -#: flatcamEditors/FlatCAMExcEditor.py:1026 -#: flatcamEditors/FlatCAMExcEditor.py:1095 flatcamGUI/FlatCAMGUI.py:3165 -#: flatcamGUI/FlatCAMGUI.py:3377 flatcamGUI/FlatCAMGUI.py:3591 -msgid "Cancelled." -msgstr "Abgebrochen." - -#: flatcamEditors/FlatCAMExcEditor.py:1116 +#: flatcamEditors/FlatCAMExcEditor.py:1112 msgid "Done. Drill/Slot Resize completed." msgstr "Getan. Bohrer / Schlitz Größenänderung abgeschlossen." -#: flatcamEditors/FlatCAMExcEditor.py:1119 +#: flatcamEditors/FlatCAMExcEditor.py:1115 msgid "Cancelled. No drills/slots selected for resize ..." msgstr "Abgebrochen. Keine Bohrer / Schlitze für Größenänderung ausgewählt ..." -#: flatcamEditors/FlatCAMExcEditor.py:1157 -#: flatcamEditors/FlatCAMGrbEditor.py:1938 +#: flatcamEditors/FlatCAMExcEditor.py:1153 +#: flatcamEditors/FlatCAMGrbEditor.py:1937 msgid "Click on reference location ..." msgstr "Klicken Sie auf die Referenzposition ..." -#: flatcamEditors/FlatCAMExcEditor.py:1214 +#: flatcamEditors/FlatCAMExcEditor.py:1210 msgid "Done. Drill(s) Move completed." msgstr "Erledigt. Bohrer Bewegen abgeschlossen." -#: flatcamEditors/FlatCAMExcEditor.py:1322 +#: flatcamEditors/FlatCAMExcEditor.py:1318 msgid "Done. Drill(s) copied." msgstr "Erledigt. Bohrer kopiert." -#: flatcamEditors/FlatCAMExcEditor.py:1555 flatcamGUI/PreferencesUI.py:3551 +#: flatcamEditors/FlatCAMExcEditor.py:1557 flatcamGUI/PreferencesUI.py:4946 msgid "Excellon Editor" msgstr "Excellon Editor" -#: flatcamEditors/FlatCAMExcEditor.py:1562 -#: flatcamEditors/FlatCAMGrbEditor.py:2454 +#: flatcamEditors/FlatCAMExcEditor.py:1564 +#: flatcamEditors/FlatCAMGrbEditor.py:2460 msgid "Name:" msgstr "Name:" -#: flatcamEditors/FlatCAMExcEditor.py:1568 flatcamGUI/ObjectUI.py:757 -#: flatcamGUI/ObjectUI.py:1184 flatcamTools/ToolNonCopperClear.py:109 -#: flatcamTools/ToolPaint.py:112 flatcamTools/ToolSolderPaste.py:73 +#: flatcamEditors/FlatCAMExcEditor.py:1570 flatcamGUI/ObjectUI.py:761 +#: flatcamGUI/ObjectUI.py:1465 flatcamTools/ToolNCC.py:120 +#: flatcamTools/ToolPaint.py:115 flatcamTools/ToolSolderPaste.py:74 msgid "Tools Table" msgstr "Werkzeugtabelle" -#: flatcamEditors/FlatCAMExcEditor.py:1570 flatcamGUI/ObjectUI.py:759 +#: flatcamEditors/FlatCAMExcEditor.py:1572 flatcamGUI/ObjectUI.py:763 msgid "" "Tools in this Excellon object\n" "when are used for drilling." @@ -3194,11 +3068,22 @@ msgstr "" "Werkzeuge in diesem Excellon-Objekt\n" "Wann werden zum Bohren verwendet." -#: flatcamEditors/FlatCAMExcEditor.py:1590 +#: flatcamEditors/FlatCAMExcEditor.py:1584 +#: flatcamEditors/FlatCAMExcEditor.py:3066 flatcamGUI/ObjectUI.py:781 +#: flatcamObjects/FlatCAMExcellon.py:1098 +#: flatcamObjects/FlatCAMExcellon.py:1188 +#: flatcamObjects/FlatCAMExcellon.py:1373 flatcamTools/ToolNCC.py:132 +#: flatcamTools/ToolPaint.py:128 flatcamTools/ToolPcbWizard.py:76 +#: flatcamTools/ToolProperties.py:416 flatcamTools/ToolProperties.py:476 +#: flatcamTools/ToolSolderPaste.py:85 tclCommands/TclCommandDrillcncjob.py:193 +msgid "Diameter" +msgstr "Durchmesser" + +#: flatcamEditors/FlatCAMExcEditor.py:1592 msgid "Add/Delete Tool" msgstr "Werkzeug hinzufügen / löschen" -#: flatcamEditors/FlatCAMExcEditor.py:1592 +#: flatcamEditors/FlatCAMExcEditor.py:1594 msgid "" "Add/Delete a tool to the tool list\n" "for this Excellon object." @@ -3206,16 +3091,16 @@ msgstr "" "Werkzeug zur Werkzeugliste hinzufügen / löschen\n" "für dieses Excellon-Objekt." -#: flatcamEditors/FlatCAMExcEditor.py:1604 flatcamGUI/ObjectUI.py:1297 -#: flatcamGUI/PreferencesUI.py:3582 +#: flatcamEditors/FlatCAMExcEditor.py:1606 flatcamGUI/ObjectUI.py:1585 +#: flatcamGUI/PreferencesUI.py:4977 msgid "Diameter for the new tool" msgstr "Durchmesser für das neue Werkzeug" -#: flatcamEditors/FlatCAMExcEditor.py:1614 +#: flatcamEditors/FlatCAMExcEditor.py:1616 msgid "Add Tool" msgstr "Werkzeug hinzufügen" -#: flatcamEditors/FlatCAMExcEditor.py:1616 +#: flatcamEditors/FlatCAMExcEditor.py:1618 msgid "" "Add a new tool to the tool list\n" "with the diameter specified above." @@ -3223,11 +3108,11 @@ msgstr "" "Fügen Sie der Werkzeugliste ein neues Werkzeug hinzu\n" "mit dem oben angegebenen Durchmesser." -#: flatcamEditors/FlatCAMExcEditor.py:1628 +#: flatcamEditors/FlatCAMExcEditor.py:1630 msgid "Delete Tool" msgstr "Werkzeug löschen" -#: flatcamEditors/FlatCAMExcEditor.py:1630 +#: flatcamEditors/FlatCAMExcEditor.py:1632 msgid "" "Delete a tool in the tool list\n" "by selecting a row in the tool table." @@ -3235,41 +3120,41 @@ msgstr "" "Löschen Sie ein Werkzeug in der Werkzeugliste\n" "indem Sie eine Zeile in der Werkzeugtabelle auswählen." -#: flatcamEditors/FlatCAMExcEditor.py:1648 flatcamGUI/FlatCAMGUI.py:1896 +#: flatcamEditors/FlatCAMExcEditor.py:1650 flatcamGUI/FlatCAMGUI.py:2007 msgid "Resize Drill(s)" msgstr "Größe der Bohrer ändern" -#: flatcamEditors/FlatCAMExcEditor.py:1650 +#: flatcamEditors/FlatCAMExcEditor.py:1652 msgid "Resize a drill or a selection of drills." msgstr "Ändern Sie die Größe eines Bohrers oder einer Auswahl von Bohrern." -#: flatcamEditors/FlatCAMExcEditor.py:1657 +#: flatcamEditors/FlatCAMExcEditor.py:1659 msgid "Resize Dia" msgstr "Durchmesser ändern" -#: flatcamEditors/FlatCAMExcEditor.py:1659 +#: flatcamEditors/FlatCAMExcEditor.py:1661 msgid "Diameter to resize to." msgstr "Durchmesser zur Größenänderung." -#: flatcamEditors/FlatCAMExcEditor.py:1670 +#: flatcamEditors/FlatCAMExcEditor.py:1672 msgid "Resize" msgstr "Größe ändern" -#: flatcamEditors/FlatCAMExcEditor.py:1672 +#: flatcamEditors/FlatCAMExcEditor.py:1674 msgid "Resize drill(s)" msgstr "Bohrer verkleinern" -#: flatcamEditors/FlatCAMExcEditor.py:1697 flatcamGUI/FlatCAMGUI.py:1895 -#: flatcamGUI/FlatCAMGUI.py:2147 +#: flatcamEditors/FlatCAMExcEditor.py:1699 flatcamGUI/FlatCAMGUI.py:2006 +#: flatcamGUI/FlatCAMGUI.py:2258 msgid "Add Drill Array" msgstr "Bohrer-Array hinzufügen" -#: flatcamEditors/FlatCAMExcEditor.py:1699 +#: flatcamEditors/FlatCAMExcEditor.py:1701 msgid "Add an array of drills (linear or circular array)" msgstr "" "Hinzufügen eines Arrays von Bohrern (lineares oder kreisförmiges Array)" -#: flatcamEditors/FlatCAMExcEditor.py:1705 +#: flatcamEditors/FlatCAMExcEditor.py:1707 msgid "" "Select the type of drills array to create.\n" "It can be Linear X(Y) or Circular" @@ -3277,43 +3162,48 @@ msgstr "" "Wählen Sie den Typ des zu erstellenden Bohrfelds aus.\n" "Es kann lineares X (Y) oder rund sein" -#: flatcamEditors/FlatCAMExcEditor.py:1708 -#: flatcamEditors/FlatCAMExcEditor.py:1922 -#: flatcamEditors/FlatCAMGrbEditor.py:2766 +#: flatcamEditors/FlatCAMExcEditor.py:1710 +#: flatcamEditors/FlatCAMExcEditor.py:1924 +#: flatcamEditors/FlatCAMGrbEditor.py:2772 msgid "Linear" msgstr "Linear" -#: flatcamEditors/FlatCAMExcEditor.py:1709 -#: flatcamEditors/FlatCAMExcEditor.py:1923 -#: flatcamEditors/FlatCAMGrbEditor.py:2767 flatcamGUI/ObjectUI.py:311 -#: flatcamGUI/PreferencesUI.py:5044 flatcamGUI/PreferencesUI.py:7465 -#: flatcamTools/ToolFiducials.py:220 flatcamTools/ToolNonCopperClear.py:221 +#: flatcamEditors/FlatCAMExcEditor.py:1711 +#: flatcamEditors/FlatCAMExcEditor.py:1925 +#: flatcamEditors/FlatCAMGrbEditor.py:2773 flatcamGUI/ObjectUI.py:316 +#: flatcamGUI/PreferencesUI.py:6457 flatcamGUI/PreferencesUI.py:7026 +#: flatcamGUI/PreferencesUI.py:9087 flatcamGUI/PreferencesUI.py:9267 +#: flatcamGUI/PreferencesUI.py:9364 flatcamGUI/PreferencesUI.py:9479 +#: flatcamGUI/PreferencesUI.py:9578 flatcamTools/ToolExtractDrills.py:78 +#: flatcamTools/ToolExtractDrills.py:201 flatcamTools/ToolFiducials.py:220 +#: flatcamTools/ToolNCC.py:221 flatcamTools/ToolPaint.py:204 +#: flatcamTools/ToolPunchGerber.py:89 flatcamTools/ToolPunchGerber.py:229 msgid "Circular" msgstr "Kreisförmig" -#: flatcamEditors/FlatCAMExcEditor.py:1717 flatcamGUI/PreferencesUI.py:3593 +#: flatcamEditors/FlatCAMExcEditor.py:1719 flatcamGUI/PreferencesUI.py:4988 msgid "Nr of drills" msgstr "Anzahl der Bohrer" -#: flatcamEditors/FlatCAMExcEditor.py:1718 flatcamGUI/PreferencesUI.py:3595 +#: flatcamEditors/FlatCAMExcEditor.py:1720 flatcamGUI/PreferencesUI.py:4990 msgid "Specify how many drills to be in the array." msgstr "Geben Sie an, wie viele Drills im Array enthalten sein sollen." -#: flatcamEditors/FlatCAMExcEditor.py:1736 -#: flatcamEditors/FlatCAMExcEditor.py:1786 -#: flatcamEditors/FlatCAMExcEditor.py:1858 -#: flatcamEditors/FlatCAMExcEditor.py:1951 -#: flatcamEditors/FlatCAMExcEditor.py:2002 -#: flatcamEditors/FlatCAMGrbEditor.py:1572 -#: flatcamEditors/FlatCAMGrbEditor.py:2795 -#: flatcamEditors/FlatCAMGrbEditor.py:2844 flatcamGUI/PreferencesUI.py:3703 +#: flatcamEditors/FlatCAMExcEditor.py:1738 +#: flatcamEditors/FlatCAMExcEditor.py:1788 +#: flatcamEditors/FlatCAMExcEditor.py:1860 +#: flatcamEditors/FlatCAMExcEditor.py:1953 +#: flatcamEditors/FlatCAMExcEditor.py:2004 +#: flatcamEditors/FlatCAMGrbEditor.py:1571 +#: flatcamEditors/FlatCAMGrbEditor.py:2801 +#: flatcamEditors/FlatCAMGrbEditor.py:2850 flatcamGUI/PreferencesUI.py:5098 msgid "Direction" msgstr "Richtung" -#: flatcamEditors/FlatCAMExcEditor.py:1738 -#: flatcamEditors/FlatCAMExcEditor.py:1953 -#: flatcamEditors/FlatCAMGrbEditor.py:2797 flatcamGUI/PreferencesUI.py:2536 -#: flatcamGUI/PreferencesUI.py:3611 flatcamGUI/PreferencesUI.py:3759 +#: flatcamEditors/FlatCAMExcEditor.py:1740 +#: flatcamEditors/FlatCAMExcEditor.py:1955 +#: flatcamEditors/FlatCAMGrbEditor.py:2803 flatcamGUI/PreferencesUI.py:3835 +#: flatcamGUI/PreferencesUI.py:5006 flatcamGUI/PreferencesUI.py:5154 msgid "" "Direction on which the linear array is oriented:\n" "- 'X' - horizontal axis \n" @@ -3325,62 +3215,62 @@ msgstr "" "- 'Y' - vertikale Achse oder\n" "- 'Winkel' - ein benutzerdefinierter Winkel für die Neigung des Arrays" -#: flatcamEditors/FlatCAMExcEditor.py:1745 -#: flatcamEditors/FlatCAMExcEditor.py:1867 -#: flatcamEditors/FlatCAMExcEditor.py:1960 -#: flatcamEditors/FlatCAMGrbEditor.py:2804 flatcamGUI/PreferencesUI.py:2542 -#: flatcamGUI/PreferencesUI.py:3617 flatcamGUI/PreferencesUI.py:3712 -#: flatcamGUI/PreferencesUI.py:3765 flatcamGUI/PreferencesUI.py:5853 +#: flatcamEditors/FlatCAMExcEditor.py:1747 +#: flatcamEditors/FlatCAMExcEditor.py:1869 +#: flatcamEditors/FlatCAMExcEditor.py:1962 +#: flatcamEditors/FlatCAMGrbEditor.py:2810 flatcamGUI/PreferencesUI.py:3841 +#: flatcamGUI/PreferencesUI.py:5012 flatcamGUI/PreferencesUI.py:5107 +#: flatcamGUI/PreferencesUI.py:5160 flatcamGUI/PreferencesUI.py:7458 #: flatcamTools/ToolFilm.py:256 msgid "X" msgstr "X" -#: flatcamEditors/FlatCAMExcEditor.py:1746 -#: flatcamEditors/FlatCAMExcEditor.py:1868 -#: flatcamEditors/FlatCAMExcEditor.py:1961 -#: flatcamEditors/FlatCAMGrbEditor.py:2805 flatcamGUI/PreferencesUI.py:2543 -#: flatcamGUI/PreferencesUI.py:3618 flatcamGUI/PreferencesUI.py:3713 -#: flatcamGUI/PreferencesUI.py:3766 flatcamGUI/PreferencesUI.py:5854 +#: flatcamEditors/FlatCAMExcEditor.py:1748 +#: flatcamEditors/FlatCAMExcEditor.py:1870 +#: flatcamEditors/FlatCAMExcEditor.py:1963 +#: flatcamEditors/FlatCAMGrbEditor.py:2811 flatcamGUI/PreferencesUI.py:3842 +#: flatcamGUI/PreferencesUI.py:5013 flatcamGUI/PreferencesUI.py:5108 +#: flatcamGUI/PreferencesUI.py:5161 flatcamGUI/PreferencesUI.py:7459 #: flatcamTools/ToolFilm.py:257 msgid "Y" msgstr "Y" -#: flatcamEditors/FlatCAMExcEditor.py:1747 -#: flatcamEditors/FlatCAMExcEditor.py:1764 -#: flatcamEditors/FlatCAMExcEditor.py:1798 -#: flatcamEditors/FlatCAMExcEditor.py:1869 -#: flatcamEditors/FlatCAMExcEditor.py:1873 -#: flatcamEditors/FlatCAMExcEditor.py:1962 -#: flatcamEditors/FlatCAMExcEditor.py:1980 -#: flatcamEditors/FlatCAMExcEditor.py:2014 -#: flatcamEditors/FlatCAMGrbEditor.py:2806 -#: flatcamEditors/FlatCAMGrbEditor.py:2823 -#: flatcamEditors/FlatCAMGrbEditor.py:2859 flatcamGUI/PreferencesUI.py:2544 -#: flatcamGUI/PreferencesUI.py:2562 flatcamGUI/PreferencesUI.py:3619 -#: flatcamGUI/PreferencesUI.py:3638 flatcamGUI/PreferencesUI.py:3714 -#: flatcamGUI/PreferencesUI.py:3719 flatcamGUI/PreferencesUI.py:3767 -#: flatcamGUI/PreferencesUI.py:3788 flatcamGUI/PreferencesUI.py:6246 -#: flatcamTools/ToolDistance.py:66 flatcamTools/ToolDistanceMin.py:68 -#: flatcamTools/ToolTransform.py:63 +#: flatcamEditors/FlatCAMExcEditor.py:1749 +#: flatcamEditors/FlatCAMExcEditor.py:1766 +#: flatcamEditors/FlatCAMExcEditor.py:1800 +#: flatcamEditors/FlatCAMExcEditor.py:1871 +#: flatcamEditors/FlatCAMExcEditor.py:1875 +#: flatcamEditors/FlatCAMExcEditor.py:1964 +#: flatcamEditors/FlatCAMExcEditor.py:1982 +#: flatcamEditors/FlatCAMExcEditor.py:2016 +#: flatcamEditors/FlatCAMGrbEditor.py:2812 +#: flatcamEditors/FlatCAMGrbEditor.py:2829 +#: flatcamEditors/FlatCAMGrbEditor.py:2865 flatcamGUI/PreferencesUI.py:3843 +#: flatcamGUI/PreferencesUI.py:3861 flatcamGUI/PreferencesUI.py:5014 +#: flatcamGUI/PreferencesUI.py:5033 flatcamGUI/PreferencesUI.py:5109 +#: flatcamGUI/PreferencesUI.py:5114 flatcamGUI/PreferencesUI.py:5162 +#: flatcamGUI/PreferencesUI.py:5183 flatcamGUI/PreferencesUI.py:7850 +#: flatcamTools/ToolDistance.py:120 flatcamTools/ToolDistanceMin.py:69 +#: flatcamTools/ToolTransform.py:60 msgid "Angle" msgstr "Winkel" -#: flatcamEditors/FlatCAMExcEditor.py:1751 -#: flatcamEditors/FlatCAMExcEditor.py:1966 -#: flatcamEditors/FlatCAMGrbEditor.py:2810 flatcamGUI/PreferencesUI.py:2550 -#: flatcamGUI/PreferencesUI.py:3625 flatcamGUI/PreferencesUI.py:3773 +#: flatcamEditors/FlatCAMExcEditor.py:1753 +#: flatcamEditors/FlatCAMExcEditor.py:1968 +#: flatcamEditors/FlatCAMGrbEditor.py:2816 flatcamGUI/PreferencesUI.py:3849 +#: flatcamGUI/PreferencesUI.py:5020 flatcamGUI/PreferencesUI.py:5168 msgid "Pitch" msgstr "Abstand" -#: flatcamEditors/FlatCAMExcEditor.py:1753 -#: flatcamEditors/FlatCAMExcEditor.py:1968 -#: flatcamEditors/FlatCAMGrbEditor.py:2812 flatcamGUI/PreferencesUI.py:2552 -#: flatcamGUI/PreferencesUI.py:3627 flatcamGUI/PreferencesUI.py:3775 +#: flatcamEditors/FlatCAMExcEditor.py:1755 +#: flatcamEditors/FlatCAMExcEditor.py:1970 +#: flatcamEditors/FlatCAMGrbEditor.py:2818 flatcamGUI/PreferencesUI.py:3851 +#: flatcamGUI/PreferencesUI.py:5022 flatcamGUI/PreferencesUI.py:5170 msgid "Pitch = Distance between elements of the array." msgstr "Abstand = Abstand zwischen Elementen des Arrays." -#: flatcamEditors/FlatCAMExcEditor.py:1766 -#: flatcamEditors/FlatCAMExcEditor.py:1982 +#: flatcamEditors/FlatCAMExcEditor.py:1768 +#: flatcamEditors/FlatCAMExcEditor.py:1984 msgid "" "Angle at which the linear array is placed.\n" "The precision is of max 2 decimals.\n" @@ -3392,9 +3282,9 @@ msgstr "" "Der Mindestwert beträgt -360 Grad.\n" "Maximalwert ist: 360.00 Grad." -#: flatcamEditors/FlatCAMExcEditor.py:1787 -#: flatcamEditors/FlatCAMExcEditor.py:2003 -#: flatcamEditors/FlatCAMGrbEditor.py:2846 +#: flatcamEditors/FlatCAMExcEditor.py:1789 +#: flatcamEditors/FlatCAMExcEditor.py:2005 +#: flatcamEditors/FlatCAMGrbEditor.py:2852 msgid "" "Direction for circular array.Can be CW = clockwise or CCW = counter " "clockwise." @@ -3402,37 +3292,37 @@ msgstr "" "Richtung für kreisförmige Anordnung. Kann CW = Uhrzeigersinn oder CCW = " "Gegenuhrzeigersinn sein." -#: flatcamEditors/FlatCAMExcEditor.py:1794 -#: flatcamEditors/FlatCAMExcEditor.py:2010 -#: flatcamEditors/FlatCAMGrbEditor.py:2854 flatcamGUI/PreferencesUI.py:2584 -#: flatcamGUI/PreferencesUI.py:3368 flatcamGUI/PreferencesUI.py:3661 -#: flatcamGUI/PreferencesUI.py:3811 flatcamGUI/PreferencesUI.py:4288 +#: flatcamEditors/FlatCAMExcEditor.py:1796 +#: flatcamEditors/FlatCAMExcEditor.py:2012 +#: flatcamEditors/FlatCAMGrbEditor.py:2860 flatcamGUI/PreferencesUI.py:3883 +#: flatcamGUI/PreferencesUI.py:4763 flatcamGUI/PreferencesUI.py:5056 +#: flatcamGUI/PreferencesUI.py:5206 flatcamGUI/PreferencesUI.py:5698 msgid "CW" msgstr "CW" -#: flatcamEditors/FlatCAMExcEditor.py:1795 -#: flatcamEditors/FlatCAMExcEditor.py:2011 -#: flatcamEditors/FlatCAMGrbEditor.py:2855 flatcamGUI/PreferencesUI.py:2585 -#: flatcamGUI/PreferencesUI.py:3369 flatcamGUI/PreferencesUI.py:3662 -#: flatcamGUI/PreferencesUI.py:3812 flatcamGUI/PreferencesUI.py:4289 +#: flatcamEditors/FlatCAMExcEditor.py:1797 +#: flatcamEditors/FlatCAMExcEditor.py:2013 +#: flatcamEditors/FlatCAMGrbEditor.py:2861 flatcamGUI/PreferencesUI.py:3884 +#: flatcamGUI/PreferencesUI.py:4764 flatcamGUI/PreferencesUI.py:5057 +#: flatcamGUI/PreferencesUI.py:5207 flatcamGUI/PreferencesUI.py:5699 msgid "CCW" msgstr "CCW" -#: flatcamEditors/FlatCAMExcEditor.py:1799 -#: flatcamEditors/FlatCAMExcEditor.py:2015 -#: flatcamEditors/FlatCAMGrbEditor.py:2861 flatcamGUI/PreferencesUI.py:2564 -#: flatcamGUI/PreferencesUI.py:2593 flatcamGUI/PreferencesUI.py:3640 -#: flatcamGUI/PreferencesUI.py:3670 flatcamGUI/PreferencesUI.py:3790 -#: flatcamGUI/PreferencesUI.py:3820 +#: flatcamEditors/FlatCAMExcEditor.py:1801 +#: flatcamEditors/FlatCAMExcEditor.py:2017 +#: flatcamEditors/FlatCAMGrbEditor.py:2867 flatcamGUI/PreferencesUI.py:3863 +#: flatcamGUI/PreferencesUI.py:3892 flatcamGUI/PreferencesUI.py:5035 +#: flatcamGUI/PreferencesUI.py:5065 flatcamGUI/PreferencesUI.py:5185 +#: flatcamGUI/PreferencesUI.py:5215 msgid "Angle at which each element in circular array is placed." msgstr "" "Winkel, um den jedes Element in einer kreisförmigen Anordnung platziert wird." -#: flatcamEditors/FlatCAMExcEditor.py:1833 +#: flatcamEditors/FlatCAMExcEditor.py:1835 msgid "Slot Parameters" msgstr "Schlitze-Parameter" -#: flatcamEditors/FlatCAMExcEditor.py:1835 +#: flatcamEditors/FlatCAMExcEditor.py:1837 msgid "" "Parameters for adding a slot (hole with oval shape)\n" "either single or as an part of an array." @@ -3440,16 +3330,16 @@ msgstr "" "Parameter zum Hinzufügen eines Schlitzes (Loch mit ovaler Form)\n" "entweder einzeln oder als Teil eines Arrays." -#: flatcamEditors/FlatCAMExcEditor.py:1844 flatcamGUI/PreferencesUI.py:3687 -#: flatcamTools/ToolProperties.py:555 +#: flatcamEditors/FlatCAMExcEditor.py:1846 flatcamGUI/PreferencesUI.py:5082 +#: flatcamTools/ToolProperties.py:559 msgid "Length" msgstr "Länge" -#: flatcamEditors/FlatCAMExcEditor.py:1846 flatcamGUI/PreferencesUI.py:3689 +#: flatcamEditors/FlatCAMExcEditor.py:1848 flatcamGUI/PreferencesUI.py:5084 msgid "Length = The length of the slot." msgstr "Länge = Die Länge des Schlitzes." -#: flatcamEditors/FlatCAMExcEditor.py:1860 flatcamGUI/PreferencesUI.py:3705 +#: flatcamEditors/FlatCAMExcEditor.py:1862 flatcamGUI/PreferencesUI.py:5100 msgid "" "Direction on which the slot is oriented:\n" "- 'X' - horizontal axis \n" @@ -3461,7 +3351,7 @@ msgstr "" "- 'Y' - vertikale Achse oder\n" "- 'Winkel' - Ein benutzerdefinierter Winkel für die Schlitzneigung" -#: flatcamEditors/FlatCAMExcEditor.py:1875 +#: flatcamEditors/FlatCAMExcEditor.py:1877 msgid "" "Angle at which the slot is placed.\n" "The precision is of max 2 decimals.\n" @@ -3473,16 +3363,16 @@ msgstr "" "Der Mindestwert beträgt: -360 Grad.\n" "Maximaler Wert ist: 360.00 Grad." -#: flatcamEditors/FlatCAMExcEditor.py:1908 +#: flatcamEditors/FlatCAMExcEditor.py:1910 msgid "Slot Array Parameters" msgstr "Schlitzes Array-Parameter" -#: flatcamEditors/FlatCAMExcEditor.py:1910 +#: flatcamEditors/FlatCAMExcEditor.py:1912 msgid "Parameters for the array of slots (linear or circular array)" msgstr "" "Parameter für das Array von Schlitzes (lineares oder kreisförmiges Array)" -#: flatcamEditors/FlatCAMExcEditor.py:1919 +#: flatcamEditors/FlatCAMExcEditor.py:1921 msgid "" "Select the type of slot array to create.\n" "It can be Linear X(Y) or Circular" @@ -3490,15 +3380,41 @@ msgstr "" "Wählen Sie den Typ des zu erstellenden Slot-Arrays.\n" "Es kann ein lineares X (Y) oder ein kreisförmiges sein" -#: flatcamEditors/FlatCAMExcEditor.py:1931 flatcamGUI/PreferencesUI.py:3744 +#: flatcamEditors/FlatCAMExcEditor.py:1933 flatcamGUI/PreferencesUI.py:5139 msgid "Nr of slots" msgstr "Anzahl der Slots" -#: flatcamEditors/FlatCAMExcEditor.py:1932 flatcamGUI/PreferencesUI.py:3746 +#: flatcamEditors/FlatCAMExcEditor.py:1934 flatcamGUI/PreferencesUI.py:5141 msgid "Specify how many slots to be in the array." msgstr "Geben Sie an, wie viele Steckplätze sich im Array befinden sollen." -#: flatcamEditors/FlatCAMExcEditor.py:2546 +#: flatcamEditors/FlatCAMExcEditor.py:2452 +#: flatcamObjects/FlatCAMExcellon.py:410 +msgid "Total Drills" +msgstr "Bohrungen insgesamt" + +#: flatcamEditors/FlatCAMExcEditor.py:2484 +#: flatcamObjects/FlatCAMExcellon.py:441 +msgid "Total Slots" +msgstr "Schlitz insgesamt" + +#: flatcamEditors/FlatCAMExcEditor.py:2559 +#: flatcamEditors/FlatCAMGeoEditor.py:1076 +#: flatcamEditors/FlatCAMGeoEditor.py:1117 +#: flatcamEditors/FlatCAMGeoEditor.py:1145 +#: flatcamEditors/FlatCAMGeoEditor.py:1173 +#: flatcamEditors/FlatCAMGeoEditor.py:1217 +#: flatcamEditors/FlatCAMGeoEditor.py:1252 +#: flatcamEditors/FlatCAMGeoEditor.py:1280 +#: flatcamObjects/FlatCAMGeometry.py:571 flatcamObjects/FlatCAMGeometry.py:1005 +#: flatcamObjects/FlatCAMGeometry.py:1726 +#: flatcamObjects/FlatCAMGeometry.py:2370 flatcamTools/ToolNCC.py:1493 +#: flatcamTools/ToolPaint.py:1244 flatcamTools/ToolPaint.py:1415 +#: flatcamTools/ToolSolderPaste.py:883 flatcamTools/ToolSolderPaste.py:956 +msgid "Wrong value format entered, use a number." +msgstr "Falsches Wertformat eingegeben, eine Zahl verwenden." + +#: flatcamEditors/FlatCAMExcEditor.py:2570 msgid "" "Tool already in the original or actual tool list.\n" "Save and reedit Excellon if you need to add this tool. " @@ -3507,343 +3423,258 @@ msgstr "" "Speichern Sie Excellon und bearbeiten Sie es erneut, wenn Sie dieses Tool " "hinzufügen müssen. " -#: flatcamEditors/FlatCAMExcEditor.py:2555 flatcamGUI/FlatCAMGUI.py:3792 +#: flatcamEditors/FlatCAMExcEditor.py:2579 flatcamGUI/FlatCAMGUI.py:4016 msgid "Added new tool with dia" msgstr "Neues Werkzeug mit Durchmesser hinzugefügt" -#: flatcamEditors/FlatCAMExcEditor.py:2589 +#: flatcamEditors/FlatCAMExcEditor.py:2612 msgid "Select a tool in Tool Table" msgstr "Wählen Sie ein Werkzeug in der Werkzeugtabelle aus" -#: flatcamEditors/FlatCAMExcEditor.py:2622 +#: flatcamEditors/FlatCAMExcEditor.py:2642 msgid "Deleted tool with diameter" msgstr "Gelöschtes Werkzeug mit Durchmesser" -#: flatcamEditors/FlatCAMExcEditor.py:2772 +#: flatcamEditors/FlatCAMExcEditor.py:2790 msgid "Done. Tool edit completed." msgstr "Erledigt. Werkzeugbearbeitung abgeschlossen." -#: flatcamEditors/FlatCAMExcEditor.py:3324 +#: flatcamEditors/FlatCAMExcEditor.py:3352 msgid "There are no Tools definitions in the file. Aborting Excellon creation." msgstr "" "Die Datei enthält keine Werkzeugdefinitionen. Abbruch der Excellon-" "Erstellung." -#: flatcamEditors/FlatCAMExcEditor.py:3328 +#: flatcamEditors/FlatCAMExcEditor.py:3356 msgid "An internal error has ocurred. See Shell.\n" msgstr "" "Ein interner Fehler ist aufgetreten. Siehe Shell.\n" "\n" -#: flatcamEditors/FlatCAMExcEditor.py:3333 +#: flatcamEditors/FlatCAMExcEditor.py:3361 msgid "Creating Excellon." msgstr "Excellon erstellen." -#: flatcamEditors/FlatCAMExcEditor.py:3347 +#: flatcamEditors/FlatCAMExcEditor.py:3373 msgid "Excellon editing finished." msgstr "Excellon-Bearbeitung abgeschlossen." -#: flatcamEditors/FlatCAMExcEditor.py:3365 +#: flatcamEditors/FlatCAMExcEditor.py:3390 msgid "Cancelled. There is no Tool/Drill selected" msgstr "Abgebrochen. Es ist kein Werkzeug / Bohrer ausgewählt" -#: flatcamEditors/FlatCAMExcEditor.py:3978 +#: flatcamEditors/FlatCAMExcEditor.py:4003 msgid "Done. Drill(s) deleted." msgstr "Erledigt. Bohrer gelöscht." -#: flatcamEditors/FlatCAMExcEditor.py:4051 -#: flatcamEditors/FlatCAMExcEditor.py:4061 -#: flatcamEditors/FlatCAMGrbEditor.py:4853 +#: flatcamEditors/FlatCAMExcEditor.py:4076 +#: flatcamEditors/FlatCAMExcEditor.py:4086 +#: flatcamEditors/FlatCAMGrbEditor.py:4897 msgid "Click on the circular array Center position" msgstr "Klicken Sie auf die kreisförmige Anordnung in der Mitte" -#: flatcamEditors/FlatCAMGeoEditor.py:86 +#: flatcamEditors/FlatCAMGeoEditor.py:85 msgid "Buffer distance:" msgstr "Pufferabstand:" -#: flatcamEditors/FlatCAMGeoEditor.py:87 +#: flatcamEditors/FlatCAMGeoEditor.py:86 msgid "Buffer corner:" msgstr "Pufferecke:" -#: flatcamEditors/FlatCAMGeoEditor.py:89 +#: flatcamEditors/FlatCAMGeoEditor.py:88 msgid "" "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 " +" - '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" msgstr "" "Es gibt 3 Arten von Ecken:\n" -"  - 'Rund': Die Ecke wird für den Außenpuffer abgerundet.\n" -"  - 'Quadrat:' Die Ecke wird für den äußeren Puffer in einem spitzen Winkel " +"- 'Rund': Die Ecke wird für den Außenpuffer abgerundet.\n" +"- 'Quadrat:' Die Ecke wird für den äußeren Puffer in einem spitzen Winkel " "getroffen.\n" -"  - 'Abgeschrägt:' Die Ecke ist eine Linie, die die Features, die sich in " -"der Ecke treffen, direkt verbindet" +"- 'Abgeschrägt:' Die Ecke ist eine Linie, die die Features, die sich in der " +"Ecke treffen, direkt verbindet" -#: flatcamEditors/FlatCAMGeoEditor.py:95 -#: flatcamEditors/FlatCAMGrbEditor.py:2622 +#: flatcamEditors/FlatCAMGeoEditor.py:94 +#: flatcamEditors/FlatCAMGrbEditor.py:2628 msgid "Round" msgstr "Runden" -#: flatcamEditors/FlatCAMGeoEditor.py:96 -#: flatcamEditors/FlatCAMGrbEditor.py:2623 flatcamGUI/PreferencesUI.py:7058 +#: flatcamEditors/FlatCAMGeoEditor.py:95 +#: flatcamEditors/FlatCAMGrbEditor.py:2629 flatcamGUI/PreferencesUI.py:6723 +#: flatcamGUI/PreferencesUI.py:7247 flatcamGUI/PreferencesUI.py:8680 +#: flatcamGUI/PreferencesUI.py:9283 flatcamGUI/PreferencesUI.py:9390 +#: flatcamGUI/PreferencesUI.py:9495 flatcamGUI/PreferencesUI.py:9604 +#: flatcamTools/ToolExtractDrills.py:94 flatcamTools/ToolExtractDrills.py:227 +#: flatcamTools/ToolNCC.py:583 flatcamTools/ToolPaint.py:527 +#: flatcamTools/ToolPunchGerber.py:105 flatcamTools/ToolPunchGerber.py:255 #: flatcamTools/ToolQRCode.py:198 msgid "Square" msgstr "Quadrat" -#: flatcamEditors/FlatCAMGeoEditor.py:97 -#: flatcamEditors/FlatCAMGrbEditor.py:2624 +#: flatcamEditors/FlatCAMGeoEditor.py:96 +#: flatcamEditors/FlatCAMGrbEditor.py:2630 msgid "Beveled" msgstr "Abgeschrägt" -#: flatcamEditors/FlatCAMGeoEditor.py:104 +#: flatcamEditors/FlatCAMGeoEditor.py:103 msgid "Buffer Interior" msgstr "Pufferinnenraum" -#: flatcamEditors/FlatCAMGeoEditor.py:106 +#: flatcamEditors/FlatCAMGeoEditor.py:105 msgid "Buffer Exterior" msgstr "Puffer außen" -#: flatcamEditors/FlatCAMGeoEditor.py:112 +#: flatcamEditors/FlatCAMGeoEditor.py:111 msgid "Full Buffer" msgstr "Voller Puffer" -#: flatcamEditors/FlatCAMGeoEditor.py:133 -#: flatcamEditors/FlatCAMGeoEditor.py:2885 flatcamGUI/FlatCAMGUI.py:1805 -#: flatcamGUI/PreferencesUI.py:2604 +#: flatcamEditors/FlatCAMGeoEditor.py:132 +#: flatcamEditors/FlatCAMGeoEditor.py:3017 flatcamGUI/FlatCAMGUI.py:1916 +#: flatcamGUI/PreferencesUI.py:3903 msgid "Buffer Tool" msgstr "Pufferwerkzeug" -#: flatcamEditors/FlatCAMGeoEditor.py:145 -#: flatcamEditors/FlatCAMGeoEditor.py:162 -#: flatcamEditors/FlatCAMGeoEditor.py:179 -#: flatcamEditors/FlatCAMGeoEditor.py:2904 -#: flatcamEditors/FlatCAMGeoEditor.py:2934 -#: flatcamEditors/FlatCAMGeoEditor.py:2964 -#: flatcamEditors/FlatCAMGrbEditor.py:4906 +#: flatcamEditors/FlatCAMGeoEditor.py:144 +#: flatcamEditors/FlatCAMGeoEditor.py:161 +#: flatcamEditors/FlatCAMGeoEditor.py:178 +#: flatcamEditors/FlatCAMGeoEditor.py:3036 +#: flatcamEditors/FlatCAMGeoEditor.py:3064 +#: flatcamEditors/FlatCAMGeoEditor.py:3092 +#: flatcamEditors/FlatCAMGrbEditor.py:4950 msgid "Buffer distance value is missing or wrong format. Add it and retry." msgstr "" "Pufferabstandswert fehlt oder falsches Format. Fügen Sie es hinzu und " "versuchen Sie es erneut." -#: flatcamEditors/FlatCAMGeoEditor.py:243 +#: flatcamEditors/FlatCAMGeoEditor.py:242 msgid "Font" msgstr "Schrift" -#: flatcamEditors/FlatCAMGeoEditor.py:324 flatcamGUI/FlatCAMGUI.py:2085 +#: flatcamEditors/FlatCAMGeoEditor.py:323 flatcamGUI/FlatCAMGUI.py:2196 msgid "Text" msgstr "Text" -#: flatcamEditors/FlatCAMGeoEditor.py:350 +#: flatcamEditors/FlatCAMGeoEditor.py:349 msgid "Text Tool" msgstr "Textwerkzeug" -#: flatcamEditors/FlatCAMGeoEditor.py:442 flatcamGUI/ObjectUI.py:359 -#: flatcamGUI/PreferencesUI.py:2025 flatcamGUI/PreferencesUI.py:3875 -#: flatcamGUI/PreferencesUI.py:5535 +#: flatcamEditors/FlatCAMGeoEditor.py:405 flatcamGUI/FlatCAMGUI.py:499 +#: flatcamGUI/FlatCAMGUI.py:1146 flatcamGUI/ObjectUI.py:818 +#: flatcamGUI/ObjectUI.py:1662 flatcamObjects/FlatCAMExcellon.py:742 +#: flatcamObjects/FlatCAMExcellon.py:1084 flatcamObjects/FlatCAMGeometry.py:731 +#: flatcamTools/ToolNCC.py:331 flatcamTools/ToolNCC.py:797 +#: flatcamTools/ToolPaint.py:314 flatcamTools/ToolPaint.py:767 +msgid "Tool" +msgstr "Werkzeug" + +#: flatcamEditors/FlatCAMGeoEditor.py:439 flatcamGUI/ObjectUI.py:364 +#: flatcamGUI/PreferencesUI.py:3322 msgid "Tool dia" msgstr "Werkzeugdurchmesser" -#: flatcamEditors/FlatCAMGeoEditor.py:444 flatcamGUI/PreferencesUI.py:5537 +#: flatcamEditors/FlatCAMGeoEditor.py:441 +msgid "Diameter of the tool to be used in the operation." +msgstr "Durchmesser des im Betrieb zu verwendenden Werkzeugs." + +#: flatcamEditors/FlatCAMGeoEditor.py:487 msgid "" -"Diameter of the tool to\n" -"be used in the operation." +"Algorithm to paint the polygons:\n" +"- Standard: Fixed step inwards.\n" +"- Seed-based: Outwards from seed.\n" +"- Line-based: Parallel lines." msgstr "" -"Durchmesser des Werkzeugs bis\n" -"in der Operation verwendet werden." +"Algorithmus zum Malen der Polygone:\n" +"- Standard: Schritt nach innen behoben.\n" +"- Samenbasiert: Aus dem Samen heraus.\n" +"- Linienbasiert: Parallele Linien." -#: flatcamEditors/FlatCAMGeoEditor.py:455 flatcamGUI/PreferencesUI.py:5152 -#: flatcamGUI/PreferencesUI.py:5567 flatcamTools/ToolNonCopperClear.py:319 -#: flatcamTools/ToolPaint.py:219 -msgid "Overlap Rate" -msgstr "Überlappungsrate" - -# 3rd Time -#: flatcamEditors/FlatCAMGeoEditor.py:457 flatcamGUI/PreferencesUI.py:5569 -#: flatcamTools/ToolPaint.py:221 -msgid "" -"How much (fraction) of the tool width to overlap each tool pass.\n" -"Adjust the value starting with lower values\n" -"and increasing it if areas that should be painted are still \n" -"not painted.\n" -"Lower values = faster processing, faster execution on CNC.\n" -"Higher values = slow processing and slow execution on CNC\n" -"due of too many paths." -msgstr "" -"Wie viel (Prozent) der Werkzeugbreite überlappt jeden Werkzeugdurchgang.\n" -"Beispiel:\n" -"Ein Wert von 0,25 bedeutet hier 25%% vom oben gefundenen " -"Werkzeugdurchmesser.\n" -"\n" -"Passen Sie den Wert beginnend mit niedrigeren Werten an\n" -"und erhöhen Sie es, wenn nicht alle Bereiche ausgemalt sind.\n" -"Niedrigere Werte = schnellere Verarbeitung, schnellere Ausführung auf der " -"Leiterplatte.\n" -"Höhere Werte = langsame Bearbeitung und langsame Ausführung auf CNC\n" -"wegen zu vieler Pfade." - -#: flatcamEditors/FlatCAMGeoEditor.py:475 flatcamGUI/PreferencesUI.py:5171 -#: flatcamGUI/PreferencesUI.py:5384 flatcamGUI/PreferencesUI.py:5587 -#: flatcamGUI/PreferencesUI.py:7175 flatcamGUI/PreferencesUI.py:7332 -#: flatcamGUI/PreferencesUI.py:7417 flatcamTools/ToolCopperThieving.py:111 -#: flatcamTools/ToolCopperThieving.py:361 flatcamTools/ToolCutOut.py:184 -#: flatcamTools/ToolFiducials.py:172 flatcamTools/ToolNonCopperClear.py:337 -#: flatcamTools/ToolPaint.py:238 -msgid "Margin" -msgstr "Marge" - -#: flatcamEditors/FlatCAMGeoEditor.py:477 flatcamGUI/PreferencesUI.py:5589 -#: flatcamTools/ToolPaint.py:240 -msgid "" -"Distance by which to avoid\n" -"the edges of the polygon to\n" -"be painted." -msgstr "" -"Entfernung, um die es zu vermeiden ist\n" -"die Kanten des Polygons bis\n" -"gemalt werden." - -#: flatcamEditors/FlatCAMGeoEditor.py:489 flatcamGUI/PreferencesUI.py:5184 -#: flatcamGUI/PreferencesUI.py:5602 flatcamTools/ToolNonCopperClear.py:348 -#: flatcamTools/ToolPaint.py:251 -msgid "Method" -msgstr "Methode" - -#: flatcamEditors/FlatCAMGeoEditor.py:491 -msgid "" -"Algorithm to paint the polygon:
    Standard: Fixed step inwards." -"
    Seed-based: Outwards from seed." -msgstr "" -"Algorithmus zum Malen des Polygons:
    Standard: Feststehender " -"Schritt nach innen.
    Samenbasiert: Aus dem Samen heraus." - -#: flatcamEditors/FlatCAMGeoEditor.py:496 flatcamGUI/PreferencesUI.py:5193 -#: flatcamGUI/PreferencesUI.py:5611 flatcamTools/ToolNonCopperClear.py:357 -#: flatcamTools/ToolPaint.py:260 -msgid "Standard" -msgstr "Standard" - -#: flatcamEditors/FlatCAMGeoEditor.py:497 flatcamGUI/PreferencesUI.py:5194 -#: flatcamGUI/PreferencesUI.py:5612 flatcamTools/ToolNonCopperClear.py:358 -#: flatcamTools/ToolPaint.py:261 -msgid "Seed-based" -msgstr "Samenbasiert" - -#: flatcamEditors/FlatCAMGeoEditor.py:498 flatcamGUI/PreferencesUI.py:5195 -#: flatcamGUI/PreferencesUI.py:5613 flatcamTools/ToolNonCopperClear.py:359 -#: flatcamTools/ToolPaint.py:262 -msgid "Straight lines" -msgstr "Gerade Linien" - -#: flatcamEditors/FlatCAMGeoEditor.py:505 +#: flatcamEditors/FlatCAMGeoEditor.py:506 msgid "Connect:" msgstr "Verbinden:" -#: flatcamEditors/FlatCAMGeoEditor.py:507 flatcamGUI/PreferencesUI.py:5204 -#: flatcamGUI/PreferencesUI.py:5620 flatcamTools/ToolNonCopperClear.py:366 -#: flatcamTools/ToolPaint.py:269 -msgid "" -"Draw lines between resulting\n" -"segments to minimize tool lifts." -msgstr "" -"Zeichnen Sie Linien zwischen den Ergebnissen\n" -"Segmente, um Werkzeuglifte zu minimieren." - -#: flatcamEditors/FlatCAMGeoEditor.py:515 +#: flatcamEditors/FlatCAMGeoEditor.py:516 msgid "Contour:" msgstr "Kontur:" -#: flatcamEditors/FlatCAMGeoEditor.py:517 flatcamGUI/PreferencesUI.py:5213 -#: flatcamGUI/PreferencesUI.py:5628 flatcamTools/ToolNonCopperClear.py:373 -#: flatcamTools/ToolPaint.py:276 -msgid "" -"Cut around the perimeter of the polygon\n" -"to trim rough edges." -msgstr "" -"Schneiden Sie um den Umfang des Polygons herum\n" -"Ecken und Kanten schneiden." - -#: flatcamEditors/FlatCAMGeoEditor.py:529 flatcamGUI/FlatCAMGUI.py:2089 +#: flatcamEditors/FlatCAMGeoEditor.py:529 flatcamGUI/FlatCAMGUI.py:2200 msgid "Paint" msgstr "Malen" -#: flatcamEditors/FlatCAMGeoEditor.py:547 flatcamGUI/FlatCAMGUI.py:845 -#: flatcamGUI/FlatCAMGUI.py:2423 flatcamGUI/ObjectUI.py:1731 -#: flatcamTools/ToolPaint.py:41 flatcamTools/ToolPaint.py:533 +#: flatcamEditors/FlatCAMGeoEditor.py:547 flatcamGUI/FlatCAMGUI.py:912 +#: flatcamGUI/FlatCAMGUI.py:2591 flatcamGUI/ObjectUI.py:2059 +#: flatcamTools/ToolPaint.py:43 flatcamTools/ToolPaint.py:738 msgid "Paint Tool" msgstr "Werkzeug Malen" -#: flatcamEditors/FlatCAMGeoEditor.py:584 -msgid "Paint cancelled. No shape selected." -msgstr "Malwerkzeug abgebrochen. Keine Form ausgewählt." +#: flatcamEditors/FlatCAMGeoEditor.py:583 +#: flatcamEditors/FlatCAMGeoEditor.py:1055 +#: flatcamEditors/FlatCAMGeoEditor.py:3024 +#: flatcamEditors/FlatCAMGeoEditor.py:3052 +#: flatcamEditors/FlatCAMGeoEditor.py:3080 +#: flatcamEditors/FlatCAMGeoEditor.py:4502 +#: flatcamEditors/FlatCAMGrbEditor.py:5601 +msgid "Cancelled. No shape selected." +msgstr "Abgebrochen. Keine Form ausgewählt." -#: flatcamEditors/FlatCAMGeoEditor.py:597 -#: flatcamEditors/FlatCAMGeoEditor.py:2910 -#: flatcamEditors/FlatCAMGeoEditor.py:2940 -#: flatcamEditors/FlatCAMGeoEditor.py:2970 flatcamGUI/PreferencesUI.py:3871 -#: flatcamTools/ToolProperties.py:120 flatcamTools/ToolProperties.py:158 +#: flatcamEditors/FlatCAMGeoEditor.py:596 +#: flatcamEditors/FlatCAMGeoEditor.py:3042 +#: flatcamEditors/FlatCAMGeoEditor.py:3070 +#: flatcamEditors/FlatCAMGeoEditor.py:3098 flatcamGUI/PreferencesUI.py:5266 +#: flatcamTools/ToolProperties.py:117 flatcamTools/ToolProperties.py:162 msgid "Tools" msgstr "Werkzeuge" -#: flatcamEditors/FlatCAMGeoEditor.py:608 -#: flatcamEditors/FlatCAMGeoEditor.py:992 -#: flatcamEditors/FlatCAMGrbEditor.py:5096 -#: flatcamEditors/FlatCAMGrbEditor.py:5493 flatcamGUI/FlatCAMGUI.py:866 -#: flatcamGUI/FlatCAMGUI.py:2441 flatcamTools/ToolTransform.py:422 +#: flatcamEditors/FlatCAMGeoEditor.py:607 +#: flatcamEditors/FlatCAMGeoEditor.py:991 +#: flatcamEditors/FlatCAMGrbEditor.py:5140 +#: flatcamEditors/FlatCAMGrbEditor.py:5537 flatcamGUI/FlatCAMGUI.py:933 +#: flatcamGUI/FlatCAMGUI.py:2612 flatcamTools/ToolTransform.py:460 msgid "Transform Tool" msgstr "Werkzeug Umwandeln" -#: flatcamEditors/FlatCAMGeoEditor.py:609 -#: flatcamEditors/FlatCAMGeoEditor.py:674 -#: flatcamEditors/FlatCAMGrbEditor.py:5097 -#: flatcamEditors/FlatCAMGrbEditor.py:5162 flatcamGUI/PreferencesUI.py:6238 -#: flatcamTools/ToolTransform.py:25 flatcamTools/ToolTransform.py:80 +#: flatcamEditors/FlatCAMGeoEditor.py:608 +#: flatcamEditors/FlatCAMGeoEditor.py:673 +#: flatcamEditors/FlatCAMGrbEditor.py:5141 +#: flatcamEditors/FlatCAMGrbEditor.py:5206 flatcamGUI/PreferencesUI.py:7842 +#: flatcamTools/ToolTransform.py:24 flatcamTools/ToolTransform.py:466 msgid "Rotate" msgstr "Drehen" -#: flatcamEditors/FlatCAMGeoEditor.py:610 -#: flatcamEditors/FlatCAMGrbEditor.py:5098 flatcamTools/ToolTransform.py:26 +#: flatcamEditors/FlatCAMGeoEditor.py:609 +#: flatcamEditors/FlatCAMGrbEditor.py:5142 flatcamTools/ToolTransform.py:25 msgid "Skew/Shear" msgstr "Neigung/Schere" -#: flatcamEditors/FlatCAMGeoEditor.py:611 -#: flatcamEditors/FlatCAMGrbEditor.py:2671 -#: flatcamEditors/FlatCAMGrbEditor.py:5099 flatcamGUI/FlatCAMGUI.py:980 -#: flatcamGUI/FlatCAMGUI.py:2017 flatcamGUI/FlatCAMGUI.py:2132 -#: flatcamGUI/FlatCAMGUI.py:2549 flatcamGUI/ObjectUI.py:103 -#: flatcamGUI/ObjectUI.py:121 flatcamGUI/PreferencesUI.py:6288 -#: flatcamTools/ToolTransform.py:27 +#: flatcamEditors/FlatCAMGeoEditor.py:610 +#: flatcamEditors/FlatCAMGrbEditor.py:2677 +#: flatcamEditors/FlatCAMGrbEditor.py:5143 flatcamGUI/FlatCAMGUI.py:1051 +#: flatcamGUI/FlatCAMGUI.py:2128 flatcamGUI/FlatCAMGUI.py:2243 +#: flatcamGUI/FlatCAMGUI.py:2730 flatcamGUI/ObjectUI.py:125 +#: flatcamGUI/PreferencesUI.py:7892 flatcamTools/ToolTransform.py:26 msgid "Scale" msgstr "Skalieren" -#: flatcamEditors/FlatCAMGeoEditor.py:612 -#: flatcamEditors/FlatCAMGrbEditor.py:5100 flatcamTools/ToolTransform.py:28 +#: flatcamEditors/FlatCAMGeoEditor.py:611 +#: flatcamEditors/FlatCAMGrbEditor.py:5144 flatcamTools/ToolTransform.py:27 msgid "Mirror (Flip)" msgstr "Spiegeln (Flip)" -#: flatcamEditors/FlatCAMGeoEditor.py:613 -#: flatcamEditors/FlatCAMGrbEditor.py:5101 flatcamGUI/ObjectUI.py:132 -#: flatcamGUI/ObjectUI.py:148 flatcamGUI/ObjectUI.py:1217 -#: flatcamGUI/ObjectUI.py:1916 flatcamGUI/PreferencesUI.py:5234 -#: flatcamGUI/PreferencesUI.py:6335 flatcamTools/ToolNonCopperClear.py:393 -#: flatcamTools/ToolTransform.py:29 -msgid "Offset" -msgstr "Versatz" - -#: flatcamEditors/FlatCAMGeoEditor.py:626 -#: flatcamEditors/FlatCAMGrbEditor.py:5114 flatcamGUI/FlatCAMGUI.py:787 -#: flatcamGUI/FlatCAMGUI.py:2370 +#: flatcamEditors/FlatCAMGeoEditor.py:625 +#: flatcamEditors/FlatCAMGrbEditor.py:5158 flatcamGUI/FlatCAMGUI.py:844 +#: flatcamGUI/FlatCAMGUI.py:2527 msgid "Editor" msgstr "Editor" -#: flatcamEditors/FlatCAMGeoEditor.py:658 -#: flatcamEditors/FlatCAMGrbEditor.py:5146 +#: flatcamEditors/FlatCAMGeoEditor.py:657 +#: flatcamEditors/FlatCAMGrbEditor.py:5190 msgid "Angle:" msgstr "Winkel:" -#: flatcamEditors/FlatCAMGeoEditor.py:660 -#: flatcamEditors/FlatCAMGrbEditor.py:5148 flatcamGUI/PreferencesUI.py:6248 -#: flatcamTools/ToolTransform.py:65 +#: flatcamEditors/FlatCAMGeoEditor.py:659 +#: flatcamEditors/FlatCAMGrbEditor.py:5192 flatcamGUI/PreferencesUI.py:7852 +#: flatcamTools/ToolTransform.py:62 msgid "" "Angle for Rotation action, in degrees.\n" "Float number between -360 and 359.\n" @@ -3855,8 +3686,8 @@ msgstr "" "Positive Zahlen für CW-Bewegung.\n" "Negative Zahlen für CCW-Bewegung." -#: flatcamEditors/FlatCAMGeoEditor.py:676 -#: flatcamEditors/FlatCAMGrbEditor.py:5164 +#: flatcamEditors/FlatCAMGeoEditor.py:675 +#: flatcamEditors/FlatCAMGrbEditor.py:5208 msgid "" "Rotate the selected shape(s).\n" "The point of reference is the middle of\n" @@ -3866,17 +3697,17 @@ msgstr "" "Der Bezugspunkt ist die Mitte von\n" "der Begrenzungsrahmen für alle ausgewählten Formen." -#: flatcamEditors/FlatCAMGeoEditor.py:699 -#: flatcamEditors/FlatCAMGrbEditor.py:5187 +#: flatcamEditors/FlatCAMGeoEditor.py:698 +#: flatcamEditors/FlatCAMGrbEditor.py:5231 msgid "Angle X:" msgstr "Winkel X:" -#: flatcamEditors/FlatCAMGeoEditor.py:701 -#: flatcamEditors/FlatCAMGeoEditor.py:721 -#: flatcamEditors/FlatCAMGrbEditor.py:5189 -#: flatcamEditors/FlatCAMGrbEditor.py:5209 flatcamGUI/PreferencesUI.py:6267 -#: flatcamGUI/PreferencesUI.py:6281 flatcamTools/ToolCalibration.py:508 -#: flatcamTools/ToolCalibration.py:521 +#: flatcamEditors/FlatCAMGeoEditor.py:700 +#: flatcamEditors/FlatCAMGeoEditor.py:720 +#: flatcamEditors/FlatCAMGrbEditor.py:5233 +#: flatcamEditors/FlatCAMGrbEditor.py:5253 flatcamGUI/PreferencesUI.py:7871 +#: flatcamGUI/PreferencesUI.py:7885 flatcamTools/ToolCalibration.py:505 +#: flatcamTools/ToolCalibration.py:518 msgid "" "Angle for Skew action, in degrees.\n" "Float number between -360 and 359." @@ -3884,15 +3715,15 @@ msgstr "" "Winkel für die Schräglage in Grad.\n" "Float-Nummer zwischen -360 und 359." -#: flatcamEditors/FlatCAMGeoEditor.py:712 -#: flatcamEditors/FlatCAMGrbEditor.py:5200 flatcamTools/ToolTransform.py:109 +#: flatcamEditors/FlatCAMGeoEditor.py:711 +#: flatcamEditors/FlatCAMGrbEditor.py:5244 flatcamTools/ToolTransform.py:467 msgid "Skew X" msgstr "Neigung X" -#: flatcamEditors/FlatCAMGeoEditor.py:714 -#: flatcamEditors/FlatCAMGeoEditor.py:734 -#: flatcamEditors/FlatCAMGrbEditor.py:5202 -#: flatcamEditors/FlatCAMGrbEditor.py:5222 +#: flatcamEditors/FlatCAMGeoEditor.py:713 +#: flatcamEditors/FlatCAMGeoEditor.py:733 +#: flatcamEditors/FlatCAMGrbEditor.py:5246 +#: flatcamEditors/FlatCAMGrbEditor.py:5266 msgid "" "Skew/shear the selected shape(s).\n" "The point of reference is the middle of\n" @@ -3902,35 +3733,35 @@ msgstr "" "Der Bezugspunkt ist die Mitte von\n" "der Begrenzungsrahmen für alle ausgewählten Formen." -#: flatcamEditors/FlatCAMGeoEditor.py:719 -#: flatcamEditors/FlatCAMGrbEditor.py:5207 +#: flatcamEditors/FlatCAMGeoEditor.py:718 +#: flatcamEditors/FlatCAMGrbEditor.py:5251 msgid "Angle Y:" msgstr "Winkel Y:" -#: flatcamEditors/FlatCAMGeoEditor.py:732 -#: flatcamEditors/FlatCAMGrbEditor.py:5220 flatcamTools/ToolTransform.py:131 +#: flatcamEditors/FlatCAMGeoEditor.py:731 +#: flatcamEditors/FlatCAMGrbEditor.py:5264 flatcamTools/ToolTransform.py:468 msgid "Skew Y" msgstr "Neigung Y" -#: flatcamEditors/FlatCAMGeoEditor.py:760 -#: flatcamEditors/FlatCAMGrbEditor.py:5248 +#: flatcamEditors/FlatCAMGeoEditor.py:759 +#: flatcamEditors/FlatCAMGrbEditor.py:5292 msgid "Factor X:" msgstr "Faktor X:" -#: flatcamEditors/FlatCAMGeoEditor.py:762 -#: flatcamEditors/FlatCAMGrbEditor.py:5250 flatcamTools/ToolCalibration.py:472 +#: flatcamEditors/FlatCAMGeoEditor.py:761 +#: flatcamEditors/FlatCAMGrbEditor.py:5294 flatcamTools/ToolCalibration.py:469 msgid "Factor for Scale action over X axis." msgstr "Faktor für die Skalierungsaktion über der X-Achse." -#: flatcamEditors/FlatCAMGeoEditor.py:772 -#: flatcamEditors/FlatCAMGrbEditor.py:5260 flatcamTools/ToolTransform.py:158 +#: flatcamEditors/FlatCAMGeoEditor.py:771 +#: flatcamEditors/FlatCAMGrbEditor.py:5304 flatcamTools/ToolTransform.py:469 msgid "Scale X" msgstr "Maßstab X" -#: flatcamEditors/FlatCAMGeoEditor.py:774 -#: flatcamEditors/FlatCAMGeoEditor.py:793 -#: flatcamEditors/FlatCAMGrbEditor.py:5262 -#: flatcamEditors/FlatCAMGrbEditor.py:5281 +#: flatcamEditors/FlatCAMGeoEditor.py:773 +#: flatcamEditors/FlatCAMGeoEditor.py:792 +#: flatcamEditors/FlatCAMGrbEditor.py:5306 +#: flatcamEditors/FlatCAMGrbEditor.py:5325 msgid "" "Scale the selected shape(s).\n" "The point of reference depends on \n" @@ -3940,29 +3771,29 @@ msgstr "" "Der Bezugspunkt hängt von ab\n" "das Kontrollkästchen Skalenreferenz." -#: flatcamEditors/FlatCAMGeoEditor.py:779 -#: flatcamEditors/FlatCAMGrbEditor.py:5267 +#: flatcamEditors/FlatCAMGeoEditor.py:778 +#: flatcamEditors/FlatCAMGrbEditor.py:5311 msgid "Factor Y:" msgstr "Faktor Y:" -#: flatcamEditors/FlatCAMGeoEditor.py:781 -#: flatcamEditors/FlatCAMGrbEditor.py:5269 flatcamTools/ToolCalibration.py:484 +#: flatcamEditors/FlatCAMGeoEditor.py:780 +#: flatcamEditors/FlatCAMGrbEditor.py:5313 flatcamTools/ToolCalibration.py:481 msgid "Factor for Scale action over Y axis." msgstr "Faktor für die Skalierungsaktion über der Y-Achse." -#: flatcamEditors/FlatCAMGeoEditor.py:791 -#: flatcamEditors/FlatCAMGrbEditor.py:5279 flatcamTools/ToolTransform.py:179 +#: flatcamEditors/FlatCAMGeoEditor.py:790 +#: flatcamEditors/FlatCAMGrbEditor.py:5323 flatcamTools/ToolTransform.py:470 msgid "Scale Y" msgstr "Maßstab Y" -#: flatcamEditors/FlatCAMGeoEditor.py:800 -#: flatcamEditors/FlatCAMGrbEditor.py:5288 flatcamGUI/PreferencesUI.py:6317 -#: flatcamTools/ToolTransform.py:192 +#: flatcamEditors/FlatCAMGeoEditor.py:799 +#: flatcamEditors/FlatCAMGrbEditor.py:5332 flatcamGUI/PreferencesUI.py:7921 +#: flatcamTools/ToolTransform.py:189 msgid "Link" msgstr "Verknüpfung" -#: flatcamEditors/FlatCAMGeoEditor.py:802 -#: flatcamEditors/FlatCAMGrbEditor.py:5290 +#: flatcamEditors/FlatCAMGeoEditor.py:801 +#: flatcamEditors/FlatCAMGrbEditor.py:5334 msgid "" "Scale the selected shape(s)\n" "using the Scale Factor X for both axis." @@ -3970,14 +3801,14 @@ msgstr "" "Skalieren der ausgewählten Form (en)\n" "Verwenden des Skalierungsfaktors X für beide Achsen." -#: flatcamEditors/FlatCAMGeoEditor.py:808 -#: flatcamEditors/FlatCAMGrbEditor.py:5296 flatcamGUI/PreferencesUI.py:6325 -#: flatcamTools/ToolTransform.py:200 +#: flatcamEditors/FlatCAMGeoEditor.py:807 +#: flatcamEditors/FlatCAMGrbEditor.py:5340 flatcamGUI/PreferencesUI.py:7929 +#: flatcamTools/ToolTransform.py:196 msgid "Scale Reference" msgstr "Skalenreferenz" -#: flatcamEditors/FlatCAMGeoEditor.py:810 -#: flatcamEditors/FlatCAMGrbEditor.py:5298 +#: flatcamEditors/FlatCAMGeoEditor.py:809 +#: flatcamEditors/FlatCAMGrbEditor.py:5342 msgid "" "Scale the selected shape(s)\n" "using the origin reference when checked,\n" @@ -3989,25 +3820,25 @@ msgstr "" "und die Mitte der größten Begrenzungsbox\n" "der ausgewählten Formen, wenn nicht markiert." -#: flatcamEditors/FlatCAMGeoEditor.py:838 -#: flatcamEditors/FlatCAMGrbEditor.py:5327 +#: flatcamEditors/FlatCAMGeoEditor.py:837 +#: flatcamEditors/FlatCAMGrbEditor.py:5371 msgid "Value X:" msgstr "Wert X:" -#: flatcamEditors/FlatCAMGeoEditor.py:840 -#: flatcamEditors/FlatCAMGrbEditor.py:5329 +#: flatcamEditors/FlatCAMGeoEditor.py:839 +#: flatcamEditors/FlatCAMGrbEditor.py:5373 msgid "Value for Offset action on X axis." msgstr "Wert für die Offset-Aktion auf der X-Achse." -#: flatcamEditors/FlatCAMGeoEditor.py:850 -#: flatcamEditors/FlatCAMGrbEditor.py:5339 flatcamTools/ToolTransform.py:227 +#: flatcamEditors/FlatCAMGeoEditor.py:849 +#: flatcamEditors/FlatCAMGrbEditor.py:5383 flatcamTools/ToolTransform.py:473 msgid "Offset X" msgstr "Versatz X" -#: flatcamEditors/FlatCAMGeoEditor.py:852 -#: flatcamEditors/FlatCAMGeoEditor.py:872 -#: flatcamEditors/FlatCAMGrbEditor.py:5341 -#: flatcamEditors/FlatCAMGrbEditor.py:5361 +#: flatcamEditors/FlatCAMGeoEditor.py:851 +#: flatcamEditors/FlatCAMGeoEditor.py:871 +#: flatcamEditors/FlatCAMGrbEditor.py:5385 +#: flatcamEditors/FlatCAMGrbEditor.py:5405 msgid "" "Offset the selected shape(s).\n" "The point of reference is the middle of\n" @@ -4017,30 +3848,30 @@ msgstr "" "Der Bezugspunkt ist die Mitte von\n" "der Begrenzungsrahmen für alle ausgewählten Formen.\n" -#: flatcamEditors/FlatCAMGeoEditor.py:858 -#: flatcamEditors/FlatCAMGrbEditor.py:5347 +#: flatcamEditors/FlatCAMGeoEditor.py:857 +#: flatcamEditors/FlatCAMGrbEditor.py:5391 msgid "Value Y:" msgstr "Wert Y:" -#: flatcamEditors/FlatCAMGeoEditor.py:860 -#: flatcamEditors/FlatCAMGrbEditor.py:5349 +#: flatcamEditors/FlatCAMGeoEditor.py:859 +#: flatcamEditors/FlatCAMGrbEditor.py:5393 msgid "Value for Offset action on Y axis." msgstr "Wert für die Offset-Aktion auf der Y-Achse." -#: flatcamEditors/FlatCAMGeoEditor.py:870 -#: flatcamEditors/FlatCAMGrbEditor.py:5359 flatcamTools/ToolTransform.py:248 +#: flatcamEditors/FlatCAMGeoEditor.py:869 +#: flatcamEditors/FlatCAMGrbEditor.py:5403 flatcamTools/ToolTransform.py:474 msgid "Offset Y" msgstr "Versatz Y" -#: flatcamEditors/FlatCAMGeoEditor.py:901 -#: flatcamEditors/FlatCAMGrbEditor.py:5390 flatcamTools/ToolTransform.py:266 +#: flatcamEditors/FlatCAMGeoEditor.py:900 +#: flatcamEditors/FlatCAMGrbEditor.py:5434 flatcamTools/ToolTransform.py:475 msgid "Flip on X" msgstr "Flip auf X" -#: flatcamEditors/FlatCAMGeoEditor.py:903 -#: flatcamEditors/FlatCAMGeoEditor.py:910 -#: flatcamEditors/FlatCAMGrbEditor.py:5392 -#: flatcamEditors/FlatCAMGrbEditor.py:5399 +#: flatcamEditors/FlatCAMGeoEditor.py:902 +#: flatcamEditors/FlatCAMGeoEditor.py:909 +#: flatcamEditors/FlatCAMGrbEditor.py:5436 +#: flatcamEditors/FlatCAMGrbEditor.py:5443 msgid "" "Flip the selected shape(s) over the X axis.\n" "Does not create a new shape." @@ -4048,18 +3879,18 @@ msgstr "" "Kippen Sie die ausgewählte Form (en) über die X-Achse.\n" "Erzeugt keine neue Form." -#: flatcamEditors/FlatCAMGeoEditor.py:908 -#: flatcamEditors/FlatCAMGrbEditor.py:5397 flatcamTools/ToolTransform.py:272 +#: flatcamEditors/FlatCAMGeoEditor.py:907 +#: flatcamEditors/FlatCAMGrbEditor.py:5441 flatcamTools/ToolTransform.py:476 msgid "Flip on Y" msgstr "Flip auf Y" -#: flatcamEditors/FlatCAMGeoEditor.py:916 -#: flatcamEditors/FlatCAMGrbEditor.py:5405 +#: flatcamEditors/FlatCAMGeoEditor.py:915 +#: flatcamEditors/FlatCAMGrbEditor.py:5449 msgid "Ref Pt" msgstr "Ref. Pt" -#: flatcamEditors/FlatCAMGeoEditor.py:918 -#: flatcamEditors/FlatCAMGrbEditor.py:5407 +#: flatcamEditors/FlatCAMGeoEditor.py:917 +#: flatcamEditors/FlatCAMGrbEditor.py:5451 msgid "" "Flip the selected shape(s)\n" "around the point in Point Entry Field.\n" @@ -4082,13 +3913,13 @@ msgstr "" "Oder geben Sie die Koordinaten im Format (x, y) in ein\n" "Punkt-Eingabefeld und klicken Sie auf X (Y) drehen" -#: flatcamEditors/FlatCAMGeoEditor.py:930 -#: flatcamEditors/FlatCAMGrbEditor.py:5419 +#: flatcamEditors/FlatCAMGeoEditor.py:929 +#: flatcamEditors/FlatCAMGrbEditor.py:5463 msgid "Point:" msgstr "Punkt:" -#: flatcamEditors/FlatCAMGeoEditor.py:932 -#: flatcamEditors/FlatCAMGrbEditor.py:5421 flatcamTools/ToolTransform.py:301 +#: flatcamEditors/FlatCAMGeoEditor.py:931 +#: flatcamEditors/FlatCAMGrbEditor.py:5465 flatcamTools/ToolTransform.py:299 msgid "" "Coordinates in format (x, y) used as reference for mirroring.\n" "The 'x' in (x, y) will be used when using Flip on X and\n" @@ -4099,8 +3930,8 @@ msgstr "" "Das 'x' in (x, y) wird verwendet, wenn Sie bei X und\n" "Das 'y' in (x, y) wird verwendet, wenn Flip auf Y verwendet wird." -#: flatcamEditors/FlatCAMGeoEditor.py:942 -#: flatcamEditors/FlatCAMGrbEditor.py:5433 flatcamTools/ToolTransform.py:312 +#: flatcamEditors/FlatCAMGeoEditor.py:941 +#: flatcamEditors/FlatCAMGrbEditor.py:5477 flatcamTools/ToolTransform.py:309 msgid "" "The point coordinates can be captured by\n" "left click on canvas together with pressing\n" @@ -4111,352 +3942,352 @@ msgstr "" "Shift Taste. Klicken Sie dann auf die Schaltfläche Hinzufügen, um sie " "einzufügen." -#: flatcamEditors/FlatCAMGeoEditor.py:1057 -#: flatcamEditors/FlatCAMGrbEditor.py:5558 -msgid "Transformation cancelled. No shape selected." -msgstr "Umwandlung abgebrochen. Keine Form ausgewählt." - -#: flatcamEditors/FlatCAMGeoEditor.py:1258 -#: flatcamEditors/FlatCAMGrbEditor.py:5742 +#: flatcamEditors/FlatCAMGeoEditor.py:1304 +#: flatcamEditors/FlatCAMGrbEditor.py:5785 msgid "No shape selected. Please Select a shape to rotate!" msgstr "Keine Form ausgewählt Bitte wählen Sie eine Form zum Drehen aus!" -#: flatcamEditors/FlatCAMGeoEditor.py:1261 -#: flatcamEditors/FlatCAMGrbEditor.py:5745 flatcamTools/ToolTransform.py:611 +#: flatcamEditors/FlatCAMGeoEditor.py:1307 +#: flatcamEditors/FlatCAMGrbEditor.py:5788 flatcamTools/ToolTransform.py:679 msgid "Appying Rotate" msgstr "Anwenden Drehen" -#: flatcamEditors/FlatCAMGeoEditor.py:1290 -#: flatcamEditors/FlatCAMGrbEditor.py:5779 +#: flatcamEditors/FlatCAMGeoEditor.py:1333 +#: flatcamEditors/FlatCAMGrbEditor.py:5820 msgid "Done. Rotate completed." msgstr "Erledigt. Drehen abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:1295 +#: flatcamEditors/FlatCAMGeoEditor.py:1335 msgid "Rotation action was not executed" msgstr "Rotationsaktion wurde nicht ausgeführt" -#: flatcamEditors/FlatCAMGeoEditor.py:1307 -#: flatcamEditors/FlatCAMGrbEditor.py:5800 +#: flatcamEditors/FlatCAMGeoEditor.py:1354 +#: flatcamEditors/FlatCAMGrbEditor.py:5839 msgid "No shape selected. Please Select a shape to flip!" msgstr "Keine Form ausgewählt. Bitte wählen Sie eine Form zum Kippen!" -#: flatcamEditors/FlatCAMGeoEditor.py:1310 -#: flatcamEditors/FlatCAMGrbEditor.py:5803 flatcamTools/ToolTransform.py:664 +#: flatcamEditors/FlatCAMGeoEditor.py:1357 +#: flatcamEditors/FlatCAMGrbEditor.py:5842 flatcamTools/ToolTransform.py:728 msgid "Applying Flip" msgstr "Flip anwenden" -#: flatcamEditors/FlatCAMGeoEditor.py:1341 -#: flatcamEditors/FlatCAMGrbEditor.py:5843 flatcamTools/ToolTransform.py:707 +#: flatcamEditors/FlatCAMGeoEditor.py:1386 +#: flatcamEditors/FlatCAMGrbEditor.py:5880 flatcamTools/ToolTransform.py:769 msgid "Flip on the Y axis done" msgstr "Spiegeln Sie die Y-Achse bereit" -#: flatcamEditors/FlatCAMGeoEditor.py:1345 -#: flatcamEditors/FlatCAMGrbEditor.py:5852 flatcamTools/ToolTransform.py:717 +#: flatcamEditors/FlatCAMGeoEditor.py:1390 +#: flatcamEditors/FlatCAMGrbEditor.py:5889 flatcamTools/ToolTransform.py:778 msgid "Flip on the X axis done" msgstr "Spiegeln Sie die X-Achse bereit" -#: flatcamEditors/FlatCAMGeoEditor.py:1355 +#: flatcamEditors/FlatCAMGeoEditor.py:1398 msgid "Flip action was not executed" msgstr "Spiegeln-Aktion wurde nicht ausgeführt" -#: flatcamEditors/FlatCAMGeoEditor.py:1365 -#: flatcamEditors/FlatCAMGrbEditor.py:5874 +#: flatcamEditors/FlatCAMGeoEditor.py:1416 +#: flatcamEditors/FlatCAMGrbEditor.py:5909 msgid "No shape selected. Please Select a shape to shear/skew!" msgstr "" "Keine Form ausgewählt. Bitte wählen Sie eine Form zum Scheren / " "Schrägstellen!" -#: flatcamEditors/FlatCAMGeoEditor.py:1368 -#: flatcamEditors/FlatCAMGrbEditor.py:5877 flatcamTools/ToolTransform.py:742 +#: flatcamEditors/FlatCAMGeoEditor.py:1419 +#: flatcamEditors/FlatCAMGrbEditor.py:5912 flatcamTools/ToolTransform.py:801 msgid "Applying Skew" msgstr "Schräglauf anwenden" -#: flatcamEditors/FlatCAMGeoEditor.py:1394 -#: flatcamEditors/FlatCAMGrbEditor.py:5913 +#: flatcamEditors/FlatCAMGeoEditor.py:1442 +#: flatcamEditors/FlatCAMGrbEditor.py:5946 msgid "Skew on the X axis done" msgstr "Schrägstellung auf der X-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1397 -#: flatcamEditors/FlatCAMGrbEditor.py:5915 +#: flatcamEditors/FlatCAMGeoEditor.py:1444 +#: flatcamEditors/FlatCAMGrbEditor.py:5948 msgid "Skew on the Y axis done" msgstr "Schrägstellung auf der Y-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1401 +#: flatcamEditors/FlatCAMGeoEditor.py:1447 msgid "Skew action was not executed" msgstr "Die Versatzaktion wurde nicht ausgeführt" -#: flatcamEditors/FlatCAMGeoEditor.py:1413 -#: flatcamEditors/FlatCAMGrbEditor.py:5939 +#: flatcamEditors/FlatCAMGeoEditor.py:1469 +#: flatcamEditors/FlatCAMGrbEditor.py:5970 msgid "No shape selected. Please Select a shape to scale!" msgstr "Keine Form ausgewählt. Bitte wählen Sie eine zu skalierende Form!" -#: flatcamEditors/FlatCAMGeoEditor.py:1416 -#: flatcamEditors/FlatCAMGrbEditor.py:5942 flatcamTools/ToolTransform.py:794 +#: flatcamEditors/FlatCAMGeoEditor.py:1472 +#: flatcamEditors/FlatCAMGrbEditor.py:5973 flatcamTools/ToolTransform.py:847 msgid "Applying Scale" msgstr "Maßstab anwenden" -#: flatcamEditors/FlatCAMGeoEditor.py:1451 -#: flatcamEditors/FlatCAMGrbEditor.py:5981 +#: flatcamEditors/FlatCAMGeoEditor.py:1504 +#: flatcamEditors/FlatCAMGrbEditor.py:6010 msgid "Scale on the X axis done" msgstr "Skalieren auf der X-Achse erledigt" -#: flatcamEditors/FlatCAMGeoEditor.py:1454 -#: flatcamEditors/FlatCAMGrbEditor.py:5983 +#: flatcamEditors/FlatCAMGeoEditor.py:1506 +#: flatcamEditors/FlatCAMGrbEditor.py:6012 msgid "Scale on the Y axis done" msgstr "Skalieren auf der Y-Achse erledigt" -#: flatcamEditors/FlatCAMGeoEditor.py:1457 +#: flatcamEditors/FlatCAMGeoEditor.py:1508 msgid "Scale action was not executed" msgstr "Skalierungsaktion wurde nicht ausgeführt" -#: flatcamEditors/FlatCAMGeoEditor.py:1467 -#: flatcamEditors/FlatCAMGrbEditor.py:6000 +#: flatcamEditors/FlatCAMGeoEditor.py:1523 +#: flatcamEditors/FlatCAMGrbEditor.py:6029 msgid "No shape selected. Please Select a shape to offset!" msgstr "Keine Form ausgewählt. Bitte wählen Sie eine zu versetzende Form!" -#: flatcamEditors/FlatCAMGeoEditor.py:1470 -#: flatcamEditors/FlatCAMGrbEditor.py:6003 flatcamTools/ToolTransform.py:849 +#: flatcamEditors/FlatCAMGeoEditor.py:1526 +#: flatcamEditors/FlatCAMGrbEditor.py:6032 flatcamTools/ToolTransform.py:897 msgid "Applying Offset" msgstr "Offsetdruck anwenden" -#: flatcamEditors/FlatCAMGeoEditor.py:1483 -#: flatcamEditors/FlatCAMGrbEditor.py:6024 +#: flatcamEditors/FlatCAMGeoEditor.py:1536 +#: flatcamEditors/FlatCAMGrbEditor.py:6053 msgid "Offset on the X axis done" msgstr "Versatz auf der X-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1486 -#: flatcamEditors/FlatCAMGrbEditor.py:6026 +#: flatcamEditors/FlatCAMGeoEditor.py:1538 +#: flatcamEditors/FlatCAMGrbEditor.py:6055 msgid "Offset on the Y axis done" msgstr "Versatz auf der Y-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1490 +#: flatcamEditors/FlatCAMGeoEditor.py:1541 msgid "Offset action was not executed" msgstr "Offsetaktion wurde nicht ausgeführt" -#: flatcamEditors/FlatCAMGeoEditor.py:1494 -#: flatcamEditors/FlatCAMGrbEditor.py:6033 +#: flatcamEditors/FlatCAMGeoEditor.py:1545 +#: flatcamEditors/FlatCAMGrbEditor.py:6062 msgid "Rotate ..." msgstr "Drehen ..." -#: flatcamEditors/FlatCAMGeoEditor.py:1495 -#: flatcamEditors/FlatCAMGeoEditor.py:1550 -#: flatcamEditors/FlatCAMGeoEditor.py:1567 -#: flatcamEditors/FlatCAMGrbEditor.py:6034 -#: flatcamEditors/FlatCAMGrbEditor.py:6083 -#: flatcamEditors/FlatCAMGrbEditor.py:6098 +#: flatcamEditors/FlatCAMGeoEditor.py:1546 +#: flatcamEditors/FlatCAMGeoEditor.py:1601 +#: flatcamEditors/FlatCAMGeoEditor.py:1618 +#: flatcamEditors/FlatCAMGrbEditor.py:6063 +#: flatcamEditors/FlatCAMGrbEditor.py:6112 +#: flatcamEditors/FlatCAMGrbEditor.py:6127 msgid "Enter an Angle Value (degrees)" msgstr "Geben Sie einen Winkelwert (Grad) ein" -#: flatcamEditors/FlatCAMGeoEditor.py:1504 -#: flatcamEditors/FlatCAMGrbEditor.py:6042 +#: flatcamEditors/FlatCAMGeoEditor.py:1555 +#: flatcamEditors/FlatCAMGrbEditor.py:6071 msgid "Geometry shape rotate done" msgstr "Geometrieform drehen fertig" -#: flatcamEditors/FlatCAMGeoEditor.py:1508 -#: flatcamEditors/FlatCAMGrbEditor.py:6045 +#: flatcamEditors/FlatCAMGeoEditor.py:1559 +#: flatcamEditors/FlatCAMGrbEditor.py:6074 msgid "Geometry shape rotate cancelled" msgstr "Geometrieform drehen abgebrochen" -#: flatcamEditors/FlatCAMGeoEditor.py:1513 -#: flatcamEditors/FlatCAMGrbEditor.py:6050 +#: flatcamEditors/FlatCAMGeoEditor.py:1564 +#: flatcamEditors/FlatCAMGrbEditor.py:6079 msgid "Offset on X axis ..." msgstr "Versatz auf der X-Achse ..." -#: flatcamEditors/FlatCAMGeoEditor.py:1514 -#: flatcamEditors/FlatCAMGeoEditor.py:1533 -#: flatcamEditors/FlatCAMGrbEditor.py:6051 -#: flatcamEditors/FlatCAMGrbEditor.py:6068 +#: flatcamEditors/FlatCAMGeoEditor.py:1565 +#: flatcamEditors/FlatCAMGeoEditor.py:1584 +#: flatcamEditors/FlatCAMGrbEditor.py:6080 +#: flatcamEditors/FlatCAMGrbEditor.py:6097 msgid "Enter a distance Value" msgstr "Geben Sie einen Abstandswert ein" -#: flatcamEditors/FlatCAMGeoEditor.py:1523 -#: flatcamEditors/FlatCAMGrbEditor.py:6059 +#: flatcamEditors/FlatCAMGeoEditor.py:1574 +#: flatcamEditors/FlatCAMGrbEditor.py:6088 msgid "Geometry shape offset on X axis done" msgstr "Geometrieformversatz auf der X-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1527 -#: flatcamEditors/FlatCAMGrbEditor.py:6062 +#: flatcamEditors/FlatCAMGeoEditor.py:1578 +#: flatcamEditors/FlatCAMGrbEditor.py:6091 msgid "Geometry shape offset X cancelled" msgstr "[WARNING_NOTCL] Geometrieformversatz X abgebrochen" -#: flatcamEditors/FlatCAMGeoEditor.py:1532 -#: flatcamEditors/FlatCAMGrbEditor.py:6067 +#: flatcamEditors/FlatCAMGeoEditor.py:1583 +#: flatcamEditors/FlatCAMGrbEditor.py:6096 msgid "Offset on Y axis ..." msgstr "Versatz auf der Y-Achse ..." -#: flatcamEditors/FlatCAMGeoEditor.py:1542 -#: flatcamEditors/FlatCAMGrbEditor.py:6076 +#: flatcamEditors/FlatCAMGeoEditor.py:1593 +#: flatcamEditors/FlatCAMGrbEditor.py:6105 msgid "Geometry shape offset on Y axis done" msgstr "Geometrieformversatz auf Y-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1546 +#: flatcamEditors/FlatCAMGeoEditor.py:1597 msgid "Geometry shape offset on Y axis canceled" msgstr "Geometrieformversatz auf Y-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1549 -#: flatcamEditors/FlatCAMGrbEditor.py:6082 +#: flatcamEditors/FlatCAMGeoEditor.py:1600 +#: flatcamEditors/FlatCAMGrbEditor.py:6111 msgid "Skew on X axis ..." msgstr "Neigung auf der X-Achse ..." -#: flatcamEditors/FlatCAMGeoEditor.py:1559 -#: flatcamEditors/FlatCAMGrbEditor.py:6091 +#: flatcamEditors/FlatCAMGeoEditor.py:1610 +#: flatcamEditors/FlatCAMGrbEditor.py:6120 msgid "Geometry shape skew on X axis done" msgstr "Geometrieformversatz auf X-Achse" -#: flatcamEditors/FlatCAMGeoEditor.py:1563 +#: flatcamEditors/FlatCAMGeoEditor.py:1614 msgid "Geometry shape skew on X axis canceled" msgstr "Geometrieformversatz auf X-Achse" -#: flatcamEditors/FlatCAMGeoEditor.py:1566 -#: flatcamEditors/FlatCAMGrbEditor.py:6097 +#: flatcamEditors/FlatCAMGeoEditor.py:1617 +#: flatcamEditors/FlatCAMGrbEditor.py:6126 msgid "Skew on Y axis ..." msgstr "Neigung auf der Y-Achse ..." -#: flatcamEditors/FlatCAMGeoEditor.py:1576 -#: flatcamEditors/FlatCAMGrbEditor.py:6106 +#: flatcamEditors/FlatCAMGeoEditor.py:1627 +#: flatcamEditors/FlatCAMGrbEditor.py:6135 msgid "Geometry shape skew on Y axis done" msgstr "Geometrieformversatz auf Y-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1580 +#: flatcamEditors/FlatCAMGeoEditor.py:1631 msgid "Geometry shape skew on Y axis canceled" msgstr "Geometrieformversatz auf Y-Achse erfolgt" -#: flatcamEditors/FlatCAMGeoEditor.py:1951 -#: flatcamEditors/FlatCAMGeoEditor.py:2016 -#: flatcamEditors/FlatCAMGrbEditor.py:1436 -#: flatcamEditors/FlatCAMGrbEditor.py:1514 +#: flatcamEditors/FlatCAMGeoEditor.py:2008 +#: flatcamEditors/FlatCAMGeoEditor.py:2079 +#: flatcamEditors/FlatCAMGrbEditor.py:1435 +#: flatcamEditors/FlatCAMGrbEditor.py:1513 msgid "Click on Center point ..." msgstr "Klicken Sie auf Mittelpunkt." -#: flatcamEditors/FlatCAMGeoEditor.py:1958 -#: flatcamEditors/FlatCAMGrbEditor.py:1446 +#: flatcamEditors/FlatCAMGeoEditor.py:2021 +#: flatcamEditors/FlatCAMGrbEditor.py:1445 msgid "Click on Perimeter point to complete ..." msgstr "Klicken Sie auf Umfangspunkt, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGeoEditor.py:1990 +#: flatcamEditors/FlatCAMGeoEditor.py:2053 msgid "Done. Adding Circle completed." msgstr "Erledigt. Hinzufügen des Kreises abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2038 -#: flatcamEditors/FlatCAMGrbEditor.py:1547 +#: flatcamEditors/FlatCAMGeoEditor.py:2107 +#: flatcamEditors/FlatCAMGrbEditor.py:1546 msgid "Click on Start point ..." msgstr "Klicken Sie auf Startpunkt ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2040 -#: flatcamEditors/FlatCAMGrbEditor.py:1549 +#: flatcamEditors/FlatCAMGeoEditor.py:2109 +#: flatcamEditors/FlatCAMGrbEditor.py:1548 msgid "Click on Point3 ..." msgstr "Klicken Sie auf Punkt3 ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2042 -#: flatcamEditors/FlatCAMGrbEditor.py:1551 +#: flatcamEditors/FlatCAMGeoEditor.py:2111 +#: flatcamEditors/FlatCAMGrbEditor.py:1550 msgid "Click on Stop point ..." msgstr "Klicken Sie auf Haltepunkt ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2047 -#: flatcamEditors/FlatCAMGrbEditor.py:1556 +#: flatcamEditors/FlatCAMGeoEditor.py:2116 +#: flatcamEditors/FlatCAMGrbEditor.py:1555 msgid "Click on Stop point to complete ..." msgstr "Klicken Sie auf Stopp, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGeoEditor.py:2049 -#: flatcamEditors/FlatCAMGrbEditor.py:1558 +#: flatcamEditors/FlatCAMGeoEditor.py:2118 +#: flatcamEditors/FlatCAMGrbEditor.py:1557 msgid "Click on Point2 to complete ..." msgstr "Klicken Sie auf Punkt2, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGeoEditor.py:2051 -#: flatcamEditors/FlatCAMGrbEditor.py:1560 +#: flatcamEditors/FlatCAMGeoEditor.py:2120 +#: flatcamEditors/FlatCAMGrbEditor.py:1559 msgid "Click on Center point to complete ..." msgstr "Klicken Sie auf Mittelpunkt, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGeoEditor.py:2063 +#: flatcamEditors/FlatCAMGeoEditor.py:2132 #, python-format msgid "Direction: %s" msgstr "Richtung: %s" -#: flatcamEditors/FlatCAMGeoEditor.py:2077 -#: flatcamEditors/FlatCAMGrbEditor.py:1586 +#: flatcamEditors/FlatCAMGeoEditor.py:2146 +#: flatcamEditors/FlatCAMGrbEditor.py:1585 msgid "Mode: Start -> Stop -> Center. Click on Start point ..." msgstr "Modus: Start -> Stopp -> Zentrieren. Klicken Sie auf Startpunkt ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2080 -#: flatcamEditors/FlatCAMGrbEditor.py:1589 +#: flatcamEditors/FlatCAMGeoEditor.py:2149 +#: flatcamEditors/FlatCAMGrbEditor.py:1588 msgid "Mode: Point1 -> Point3 -> Point2. Click on Point1 ..." msgstr "Modus: Punkt 1 -> Punkt 3 -> Punkt 2. Klicken Sie auf Punkt1 ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2083 -#: flatcamEditors/FlatCAMGrbEditor.py:1592 +#: flatcamEditors/FlatCAMGeoEditor.py:2152 +#: flatcamEditors/FlatCAMGrbEditor.py:1591 msgid "Mode: Center -> Start -> Stop. Click on Center point ..." msgstr "Modus: Mitte -> Start -> Stopp. Klicken Sie auf Mittelpunkt." -#: flatcamEditors/FlatCAMGeoEditor.py:2224 +#: flatcamEditors/FlatCAMGeoEditor.py:2293 msgid "Done. Arc completed." msgstr "Erledigt. Arc abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2255 -#: flatcamEditors/FlatCAMGeoEditor.py:2322 +#: flatcamEditors/FlatCAMGeoEditor.py:2324 +#: flatcamEditors/FlatCAMGeoEditor.py:2397 msgid "Click on 1st corner ..." msgstr "Klicken Sie auf die 1. Ecke ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2261 +#: flatcamEditors/FlatCAMGeoEditor.py:2336 msgid "Click on opposite corner to complete ..." msgstr "" "Klicken Sie auf die gegenüberliegende Ecke, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGeoEditor.py:2291 +#: flatcamEditors/FlatCAMGeoEditor.py:2366 msgid "Done. Rectangle completed." msgstr "Erledigt. Rechteck fertiggestellt." -#: flatcamEditors/FlatCAMGeoEditor.py:2329 +#: flatcamEditors/FlatCAMGeoEditor.py:2410 flatcamTools/ToolNCC.py:1728 +#: flatcamTools/ToolPaint.py:1623 msgid "Click on next Point or click right mouse button to complete ..." msgstr "" "Klicken Sie auf den nächsten Punkt oder klicken Sie mit der rechten " "Maustaste, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGeoEditor.py:2360 +#: flatcamEditors/FlatCAMGeoEditor.py:2441 msgid "Done. Polygon completed." msgstr "Erledigt. Polygon fertiggestellt." -#: flatcamEditors/FlatCAMGeoEditor.py:2374 -#: flatcamEditors/FlatCAMGeoEditor.py:2439 -#: flatcamEditors/FlatCAMGrbEditor.py:1112 -#: flatcamEditors/FlatCAMGrbEditor.py:1323 +#: flatcamEditors/FlatCAMGeoEditor.py:2455 +#: flatcamEditors/FlatCAMGeoEditor.py:2520 +#: flatcamEditors/FlatCAMGrbEditor.py:1111 +#: flatcamEditors/FlatCAMGrbEditor.py:1322 msgid "Backtracked one point ..." msgstr "Einen Punkt zurückverfolgt ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2417 +#: flatcamEditors/FlatCAMGeoEditor.py:2498 msgid "Done. Path completed." msgstr "Getan. Pfad abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2580 +#: flatcamEditors/FlatCAMGeoEditor.py:2657 +msgid "No shape selected. Select a shape to explode" +msgstr "Keine Form ausgewählt. Wählen Sie eine Form zum Auflösen aus" + +#: flatcamEditors/FlatCAMGeoEditor.py:2690 msgid "Done. Polygons exploded into lines." msgstr "Getan. Polygone explodierten in Linien." -#: flatcamEditors/FlatCAMGeoEditor.py:2612 +#: flatcamEditors/FlatCAMGeoEditor.py:2722 msgid "MOVE: No shape selected. Select a shape to move" msgstr "Bewegen: Keine Form ausgewählt. Wähle eine Form zum Bewegen aus" -#: flatcamEditors/FlatCAMGeoEditor.py:2615 -#: flatcamEditors/FlatCAMGeoEditor.py:2628 +#: flatcamEditors/FlatCAMGeoEditor.py:2725 +#: flatcamEditors/FlatCAMGeoEditor.py:2745 msgid " MOVE: Click on reference point ..." msgstr " Bewegen: Referenzpunkt anklicken ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2619 +#: flatcamEditors/FlatCAMGeoEditor.py:2730 msgid " Click on destination point ..." msgstr " Klicken Sie auf den Zielpunkt ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2653 +#: flatcamEditors/FlatCAMGeoEditor.py:2770 msgid "Done. Geometry(s) Move completed." msgstr "Erledigt. Geometrie(n) Bewegung abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2783 +#: flatcamEditors/FlatCAMGeoEditor.py:2903 msgid "Done. Geometry(s) Copy completed." msgstr "Erledigt. Geometrie(n) Kopieren abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2811 -#: flatcamEditors/FlatCAMGrbEditor.py:898 +#: flatcamEditors/FlatCAMGeoEditor.py:2934 +#: flatcamEditors/FlatCAMGrbEditor.py:897 msgid "Click on 1st point ..." msgstr "Klicken Sie auf den 1. Punkt ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2829 +#: flatcamEditors/FlatCAMGeoEditor.py:2958 msgid "" "Font not supported. Only Regular, Bold, Italic and BoldItalic are supported. " "Error" @@ -4464,96 +4295,132 @@ msgstr "" "Schrift wird nicht unterstützt. Es werden nur Regular, Bold, Italic und " "BoldItalic unterstützt. Error" -#: flatcamEditors/FlatCAMGeoEditor.py:2837 +#: flatcamEditors/FlatCAMGeoEditor.py:2966 msgid "No text to add." msgstr "Kein Text zum Hinzufügen." -#: flatcamEditors/FlatCAMGeoEditor.py:2844 +#: flatcamEditors/FlatCAMGeoEditor.py:2976 msgid " Done. Adding Text completed." msgstr " Erledigt. Hinzufügen von Text abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2881 +#: flatcamEditors/FlatCAMGeoEditor.py:3013 msgid "Create buffer geometry ..." msgstr "Puffergeometrie erstellen ..." -#: flatcamEditors/FlatCAMGeoEditor.py:2892 -#: flatcamEditors/FlatCAMGeoEditor.py:2922 -#: flatcamEditors/FlatCAMGeoEditor.py:2952 -msgid "Buffer cancelled. No shape selected." -msgstr "Puffer abgebrochen. Keine Form ausgewählt." - -#: flatcamEditors/FlatCAMGeoEditor.py:2917 -#: flatcamEditors/FlatCAMGrbEditor.py:4950 +#: flatcamEditors/FlatCAMGeoEditor.py:3048 +#: flatcamEditors/FlatCAMGrbEditor.py:4994 msgid "Done. Buffer Tool completed." msgstr "Erledigt. Pufferwerkzeug abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2947 +#: flatcamEditors/FlatCAMGeoEditor.py:3076 msgid "Done. Buffer Int Tool completed." msgstr "Erledigt. Innenpufferwerkzeug abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:2977 +#: flatcamEditors/FlatCAMGeoEditor.py:3104 msgid "Done. Buffer Ext Tool completed." msgstr "Erledigt. Außenpufferwerkzeug abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:3023 -#: flatcamEditors/FlatCAMGrbEditor.py:2152 +#: flatcamEditors/FlatCAMGeoEditor.py:3153 +#: flatcamEditors/FlatCAMGrbEditor.py:2151 msgid "Select a shape to act as deletion area ..." msgstr "Wählen Sie eine Form als Löschbereich aus ..." -#: flatcamEditors/FlatCAMGeoEditor.py:3025 -#: flatcamEditors/FlatCAMGeoEditor.py:3045 -#: flatcamEditors/FlatCAMGeoEditor.py:3051 -#: flatcamEditors/FlatCAMGrbEditor.py:2154 +#: flatcamEditors/FlatCAMGeoEditor.py:3155 +#: flatcamEditors/FlatCAMGeoEditor.py:3181 +#: flatcamEditors/FlatCAMGeoEditor.py:3187 +#: flatcamEditors/FlatCAMGrbEditor.py:2153 msgid "Click to pick-up the erase shape..." msgstr "Klicken Sie, um die Löschform aufzunehmen ..." -#: flatcamEditors/FlatCAMGeoEditor.py:3055 -#: flatcamEditors/FlatCAMGrbEditor.py:2213 +#: flatcamEditors/FlatCAMGeoEditor.py:3191 +#: flatcamEditors/FlatCAMGrbEditor.py:2212 msgid "Click to erase ..." msgstr "Klicken zum Löschen ..." -#: flatcamEditors/FlatCAMGeoEditor.py:3084 -#: flatcamEditors/FlatCAMGrbEditor.py:2246 +#: flatcamEditors/FlatCAMGeoEditor.py:3220 +#: flatcamEditors/FlatCAMGrbEditor.py:2245 msgid "Done. Eraser tool action completed." msgstr "Erledigt. Radiergummi-Aktion abgeschlossen." -#: flatcamEditors/FlatCAMGeoEditor.py:3131 +#: flatcamEditors/FlatCAMGeoEditor.py:3270 msgid "Create Paint geometry ..." msgstr "Malen geometrie erstellen ..." -#: flatcamEditors/FlatCAMGeoEditor.py:3144 -#: flatcamEditors/FlatCAMGrbEditor.py:2402 +#: flatcamEditors/FlatCAMGeoEditor.py:3283 +#: flatcamEditors/FlatCAMGrbEditor.py:2408 msgid "Shape transformations ..." msgstr "Formtransformationen ..." -#: flatcamEditors/FlatCAMGeoEditor.py:3763 +#: flatcamEditors/FlatCAMGeoEditor.py:3339 flatcamGUI/PreferencesUI.py:5753 +msgid "Geometry Editor" +msgstr "Geo-Editor" + +#: flatcamEditors/FlatCAMGeoEditor.py:3345 +#: flatcamEditors/FlatCAMGrbEditor.py:2486 +#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:263 +#: flatcamGUI/ObjectUI.py:1497 flatcamGUI/ObjectUI.py:2246 +#: flatcamTools/ToolCutOut.py:95 +msgid "Type" +msgstr "Typ" + +#: flatcamEditors/FlatCAMGeoEditor.py:3345 flatcamGUI/ObjectUI.py:218 +#: flatcamGUI/ObjectUI.py:742 flatcamGUI/ObjectUI.py:1433 +#: flatcamGUI/ObjectUI.py:2155 flatcamGUI/ObjectUI.py:2459 +#: flatcamGUI/ObjectUI.py:2526 flatcamTools/ToolCalibration.py:234 +#: flatcamTools/ToolFiducials.py:73 +msgid "Name" +msgstr "Name" + +#: flatcamEditors/FlatCAMGeoEditor.py:3587 +msgid "Ring" +msgstr "Ring" + +#: flatcamEditors/FlatCAMGeoEditor.py:3589 +msgid "Line" +msgstr "Linie" + +#: flatcamEditors/FlatCAMGeoEditor.py:3591 flatcamGUI/FlatCAMGUI.py:2190 +#: flatcamGUI/PreferencesUI.py:6724 flatcamGUI/PreferencesUI.py:7248 +#: flatcamTools/ToolNCC.py:584 flatcamTools/ToolPaint.py:528 +msgid "Polygon" +msgstr "Polygon" + +#: flatcamEditors/FlatCAMGeoEditor.py:3593 +msgid "Multi-Line" +msgstr "Mehrzeilig" + +#: flatcamEditors/FlatCAMGeoEditor.py:3595 +msgid "Multi-Polygon" +msgstr "Multi-Polygon" + +#: flatcamEditors/FlatCAMGeoEditor.py:3602 +msgid "Geo Elem" +msgstr "Geoelement" + +#: flatcamEditors/FlatCAMGeoEditor.py:4076 msgid "Editing MultiGeo Geometry, tool" msgstr "Bearbeiten von MultiGeo Geometry, Werkzeug" -#: flatcamEditors/FlatCAMGeoEditor.py:3765 +#: flatcamEditors/FlatCAMGeoEditor.py:4078 msgid "with diameter" msgstr "mit Durchmesser" -#: flatcamEditors/FlatCAMGeoEditor.py:4169 -msgid "Copy cancelled. No shape selected." -msgstr "Kopieren abgebrochen. Keine Form ausgewählt." - -#: flatcamEditors/FlatCAMGeoEditor.py:4176 flatcamGUI/FlatCAMGUI.py:3472 -#: flatcamGUI/FlatCAMGUI.py:3519 flatcamGUI/FlatCAMGUI.py:3538 -#: flatcamGUI/FlatCAMGUI.py:3679 flatcamGUI/FlatCAMGUI.py:3719 -#: flatcamGUI/FlatCAMGUI.py:3732 flatcamGUI/FlatCAMGUI.py:3749 +#: flatcamEditors/FlatCAMGeoEditor.py:4509 flatcamGUI/FlatCAMGUI.py:3702 +#: flatcamGUI/FlatCAMGUI.py:3748 flatcamGUI/FlatCAMGUI.py:3766 +#: flatcamGUI/FlatCAMGUI.py:3906 flatcamGUI/FlatCAMGUI.py:3945 +#: flatcamGUI/FlatCAMGUI.py:3957 flatcamGUI/FlatCAMGUI.py:3974 msgid "Click on target point." msgstr "Klicken Sie auf den Zielpunkt." -#: flatcamEditors/FlatCAMGeoEditor.py:4479 -#: flatcamEditors/FlatCAMGeoEditor.py:4514 +#: flatcamEditors/FlatCAMGeoEditor.py:4823 +#: flatcamEditors/FlatCAMGeoEditor.py:4858 msgid "A selection of at least 2 geo items is required to do Intersection." msgstr "" "Eine Auswahl von mindestens 2 Geo-Elementen ist erforderlich, um die " "Kreuzung durchzuführen." -#: flatcamEditors/FlatCAMGeoEditor.py:4600 -#: flatcamEditors/FlatCAMGeoEditor.py:4704 +#: flatcamEditors/FlatCAMGeoEditor.py:4944 +#: flatcamEditors/FlatCAMGeoEditor.py:5048 msgid "" "Negative buffer value is not accepted. Use Buffer interior to generate an " "'inside' shape" @@ -4561,60 +4428,59 @@ msgstr "" "Negativer Pufferwert wird nicht akzeptiert. Verwenden Sie den " "Pufferinnenraum, um eine Innenform zu erzeugen" -#: flatcamEditors/FlatCAMGeoEditor.py:4610 -#: flatcamEditors/FlatCAMGeoEditor.py:4663 -#: flatcamEditors/FlatCAMGeoEditor.py:4713 +#: flatcamEditors/FlatCAMGeoEditor.py:4954 +#: flatcamEditors/FlatCAMGeoEditor.py:5007 +#: flatcamEditors/FlatCAMGeoEditor.py:5057 msgid "Nothing selected for buffering." msgstr "Nichts ist für die Pufferung ausgewählt." -#: flatcamEditors/FlatCAMGeoEditor.py:4615 -#: flatcamEditors/FlatCAMGeoEditor.py:4667 -#: flatcamEditors/FlatCAMGeoEditor.py:4718 +#: flatcamEditors/FlatCAMGeoEditor.py:4959 +#: flatcamEditors/FlatCAMGeoEditor.py:5011 +#: flatcamEditors/FlatCAMGeoEditor.py:5062 msgid "Invalid distance for buffering." msgstr "Ungültige Entfernung zum Puffern." -#: flatcamEditors/FlatCAMGeoEditor.py:4639 -#: flatcamEditors/FlatCAMGeoEditor.py:4738 +#: flatcamEditors/FlatCAMGeoEditor.py:4983 +#: flatcamEditors/FlatCAMGeoEditor.py:5082 msgid "Failed, the result is empty. Choose a different buffer value." msgstr "" "Fehlgeschlagen, das Ergebnis ist leer. Wählen Sie einen anderen Pufferwert." -#: flatcamEditors/FlatCAMGeoEditor.py:4650 +#: flatcamEditors/FlatCAMGeoEditor.py:4994 msgid "Full buffer geometry created." msgstr "Volle Puffergeometrie erstellt." -#: flatcamEditors/FlatCAMGeoEditor.py:4656 +#: flatcamEditors/FlatCAMGeoEditor.py:5000 msgid "Negative buffer value is not accepted." msgstr "Negativer Pufferwert wird nicht akzeptiert." -#: flatcamEditors/FlatCAMGeoEditor.py:4687 +#: flatcamEditors/FlatCAMGeoEditor.py:5031 msgid "Failed, the result is empty. Choose a smaller buffer value." msgstr "" "Fehlgeschlagen, das Ergebnis ist leer. Wählen Sie einen kleineren Pufferwert." -#: flatcamEditors/FlatCAMGeoEditor.py:4697 +#: flatcamEditors/FlatCAMGeoEditor.py:5041 msgid "Interior buffer geometry created." msgstr "Innere Puffergeometrie erstellt." -#: flatcamEditors/FlatCAMGeoEditor.py:4748 +#: flatcamEditors/FlatCAMGeoEditor.py:5092 msgid "Exterior buffer geometry created." msgstr "Außenpuffergeometrie erstellt." -#: flatcamEditors/FlatCAMGeoEditor.py:4754 +#: flatcamEditors/FlatCAMGeoEditor.py:5098 #, python-format -msgid "Could not do Paint. Overlap value has to be less than 1.00 (100%%)." -msgstr "" -"Kann nicht Malen machen. Der Überlappungswert muss unter 1,00 (100%%) liegen." +msgid "Could not do Paint. Overlap value has to be less than 100%%." +msgstr "Konnte nicht Malen. Der Überlappungswert muss kleiner als 100 %% sein." -#: flatcamEditors/FlatCAMGeoEditor.py:4761 +#: flatcamEditors/FlatCAMGeoEditor.py:5105 msgid "Nothing selected for painting." msgstr "Nichts zum Malen ausgewählt." -#: flatcamEditors/FlatCAMGeoEditor.py:4767 +#: flatcamEditors/FlatCAMGeoEditor.py:5111 msgid "Invalid value for" msgstr "Ungültiger Wert für" -#: flatcamEditors/FlatCAMGeoEditor.py:4826 +#: flatcamEditors/FlatCAMGeoEditor.py:5170 msgid "" "Could not do Paint. Try a different combination of parameters. Or a " "different method of Paint" @@ -4622,7 +4488,7 @@ msgstr "" "Konnte nicht malen. Probieren Sie eine andere Kombination von Parametern " "aus. Oder eine andere Malmethode" -#: flatcamEditors/FlatCAMGeoEditor.py:4840 +#: flatcamEditors/FlatCAMGeoEditor.py:5181 msgid "Paint done." msgstr "Malen fertig." @@ -4638,7 +4504,7 @@ msgid "Aperture size is zero. It needs to be greater than zero." msgstr "Die Größe der Blende ist Null. Es muss größer als Null sein." #: flatcamEditors/FlatCAMGrbEditor.py:371 -#: flatcamEditors/FlatCAMGrbEditor.py:685 +#: flatcamEditors/FlatCAMGrbEditor.py:684 msgid "" "Incompatible aperture type. Select an aperture with type 'C', 'R' or 'O'." msgstr "" @@ -4659,173 +4525,167 @@ msgstr "" msgid "Click on the Pad Circular Array Start position" msgstr "Klicken Sie auf die Startposition des Pad-Kreis-Arrays" -#: flatcamEditors/FlatCAMGrbEditor.py:711 +#: flatcamEditors/FlatCAMGrbEditor.py:710 msgid "Too many Pads for the selected spacing angle." msgstr "Zu viele Pad für den ausgewählten Abstandswinkel." -#: flatcamEditors/FlatCAMGrbEditor.py:734 +#: flatcamEditors/FlatCAMGrbEditor.py:733 msgid "Done. Pad Array added." msgstr "Erledigt. Pad Array hinzugefügt." -#: flatcamEditors/FlatCAMGrbEditor.py:759 +#: flatcamEditors/FlatCAMGrbEditor.py:758 msgid "Select shape(s) and then click ..." msgstr "Wählen Sie die Form (en) aus und klicken Sie dann auf ..." -#: flatcamEditors/FlatCAMGrbEditor.py:771 +#: flatcamEditors/FlatCAMGrbEditor.py:770 msgid "Failed. Nothing selected." msgstr "Gescheitert. Nichts ausgewählt." -#: flatcamEditors/FlatCAMGrbEditor.py:787 +#: flatcamEditors/FlatCAMGrbEditor.py:786 msgid "" "Failed. Poligonize works only on geometries belonging to the same aperture." msgstr "" "Gescheitert. Poligonize funktioniert nur bei Geometrien, die zur selben " "Apertur gehören." -#: flatcamEditors/FlatCAMGrbEditor.py:841 +#: flatcamEditors/FlatCAMGrbEditor.py:840 msgid "Done. Poligonize completed." msgstr "Erledigt. Poligonize abgeschlossen." -#: flatcamEditors/FlatCAMGrbEditor.py:896 -#: flatcamEditors/FlatCAMGrbEditor.py:1129 -#: flatcamEditors/FlatCAMGrbEditor.py:1153 +#: flatcamEditors/FlatCAMGrbEditor.py:895 +#: flatcamEditors/FlatCAMGrbEditor.py:1128 +#: flatcamEditors/FlatCAMGrbEditor.py:1152 msgid "Corner Mode 1: 45 degrees ..." msgstr "Eckmodus 1: 45 Grad ..." -#: flatcamEditors/FlatCAMGrbEditor.py:908 -#: flatcamEditors/FlatCAMGrbEditor.py:1238 +#: flatcamEditors/FlatCAMGrbEditor.py:907 +#: flatcamEditors/FlatCAMGrbEditor.py:1237 msgid "Click on next Point or click Right mouse button to complete ..." msgstr "" "Klicken Sie auf den nächsten Punkt oder klicken Sie mit der rechten " "Maustaste, um den Vorgang abzuschließen." -#: flatcamEditors/FlatCAMGrbEditor.py:1117 -#: flatcamEditors/FlatCAMGrbEditor.py:1150 +#: flatcamEditors/FlatCAMGrbEditor.py:1116 +#: flatcamEditors/FlatCAMGrbEditor.py:1149 msgid "Corner Mode 2: Reverse 45 degrees ..." msgstr "Eckmodus 2: 45 Grad umkehren ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1120 -#: flatcamEditors/FlatCAMGrbEditor.py:1147 +#: flatcamEditors/FlatCAMGrbEditor.py:1119 +#: flatcamEditors/FlatCAMGrbEditor.py:1146 msgid "Corner Mode 3: 90 degrees ..." msgstr "Eckmodus 3: 90 Grad ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1123 -#: flatcamEditors/FlatCAMGrbEditor.py:1144 +#: flatcamEditors/FlatCAMGrbEditor.py:1122 +#: flatcamEditors/FlatCAMGrbEditor.py:1143 msgid "Corner Mode 4: Reverse 90 degrees ..." msgstr "Eckmodus 4: Um 90 Grad umkehren ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1126 -#: flatcamEditors/FlatCAMGrbEditor.py:1141 +#: flatcamEditors/FlatCAMGrbEditor.py:1125 +#: flatcamEditors/FlatCAMGrbEditor.py:1140 msgid "Corner Mode 5: Free angle ..." msgstr "Eckmodus 5: Freiwinkel ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1183 -#: flatcamEditors/FlatCAMGrbEditor.py:1359 -#: flatcamEditors/FlatCAMGrbEditor.py:1398 +#: flatcamEditors/FlatCAMGrbEditor.py:1182 +#: flatcamEditors/FlatCAMGrbEditor.py:1358 +#: flatcamEditors/FlatCAMGrbEditor.py:1397 msgid "Track Mode 1: 45 degrees ..." msgstr "Spurmodus 1: 45 Grad ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1339 -#: flatcamEditors/FlatCAMGrbEditor.py:1393 +#: flatcamEditors/FlatCAMGrbEditor.py:1338 +#: flatcamEditors/FlatCAMGrbEditor.py:1392 msgid "Track Mode 2: Reverse 45 degrees ..." msgstr "Spurmodus 2: 45 Grad umkehren ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1344 -#: flatcamEditors/FlatCAMGrbEditor.py:1388 +#: flatcamEditors/FlatCAMGrbEditor.py:1343 +#: flatcamEditors/FlatCAMGrbEditor.py:1387 msgid "Track Mode 3: 90 degrees ..." msgstr "Spurmodus 3: 90 Grad ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1349 -#: flatcamEditors/FlatCAMGrbEditor.py:1383 +#: flatcamEditors/FlatCAMGrbEditor.py:1348 +#: flatcamEditors/FlatCAMGrbEditor.py:1382 msgid "Track Mode 4: Reverse 90 degrees ..." msgstr "Spurmodus 4: Um 90 Grad umkehren ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1354 -#: flatcamEditors/FlatCAMGrbEditor.py:1378 +#: flatcamEditors/FlatCAMGrbEditor.py:1353 +#: flatcamEditors/FlatCAMGrbEditor.py:1377 msgid "Track Mode 5: Free angle ..." msgstr "Spurmodus 5: Freiwinkel ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1779 +#: flatcamEditors/FlatCAMGrbEditor.py:1778 msgid "Scale the selected Gerber apertures ..." msgstr "Skalieren Sie die ausgewählten Gerber-Öffnungen ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1821 +#: flatcamEditors/FlatCAMGrbEditor.py:1820 msgid "Buffer the selected apertures ..." msgstr "Die ausgewählten Öffnungen puffern ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1863 +#: flatcamEditors/FlatCAMGrbEditor.py:1862 msgid "Mark polygon areas in the edited Gerber ..." msgstr "Markiere Polygonbereiche im bearbeiteten Gerber ..." -#: flatcamEditors/FlatCAMGrbEditor.py:1929 +#: flatcamEditors/FlatCAMGrbEditor.py:1928 msgid "Nothing selected to move" msgstr "Nichts zum Bewegen ausgewählt" -#: flatcamEditors/FlatCAMGrbEditor.py:2054 +#: flatcamEditors/FlatCAMGrbEditor.py:2053 msgid "Done. Apertures Move completed." msgstr "Erledigt. Öffnungsbewegung abgeschlossen." -#: flatcamEditors/FlatCAMGrbEditor.py:2136 +#: flatcamEditors/FlatCAMGrbEditor.py:2135 msgid "Done. Apertures copied." msgstr "Erledigt. Blende kopiert." -#: flatcamEditors/FlatCAMGrbEditor.py:2447 flatcamGUI/FlatCAMGUI.py:2110 -#: flatcamGUI/PreferencesUI.py:2443 +#: flatcamEditors/FlatCAMGrbEditor.py:2453 flatcamGUI/FlatCAMGUI.py:2221 +#: flatcamGUI/PreferencesUI.py:3740 msgid "Gerber Editor" msgstr "Gerber-Editor" -#: flatcamEditors/FlatCAMGrbEditor.py:2467 flatcamGUI/ObjectUI.py:223 -#: flatcamTools/ToolProperties.py:156 +#: flatcamEditors/FlatCAMGrbEditor.py:2473 flatcamGUI/ObjectUI.py:228 +#: flatcamTools/ToolProperties.py:159 msgid "Apertures" msgstr "Öffnungen" -#: flatcamEditors/FlatCAMGrbEditor.py:2469 flatcamGUI/ObjectUI.py:225 +#: flatcamEditors/FlatCAMGrbEditor.py:2475 flatcamGUI/ObjectUI.py:230 msgid "Apertures Table for the Gerber Object." msgstr "Blendentabelle für das Gerberobjekt." -#: flatcamEditors/FlatCAMGrbEditor.py:2480 -#: flatcamEditors/FlatCAMGrbEditor.py:3832 flatcamGUI/ObjectUI.py:258 +#: flatcamEditors/FlatCAMGrbEditor.py:2486 +#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:263 msgid "Code" msgstr "Code" -#: flatcamEditors/FlatCAMGrbEditor.py:2480 -#: flatcamEditors/FlatCAMGrbEditor.py:3832 flatcamGUI/ObjectUI.py:258 -#: flatcamGUI/ObjectUI.py:1217 flatcamGUI/ObjectUI.py:1916 -msgid "Type" -msgstr "Typ" - -#: flatcamEditors/FlatCAMGrbEditor.py:2480 -#: flatcamEditors/FlatCAMGrbEditor.py:3832 flatcamGUI/ObjectUI.py:258 -#: flatcamGUI/PreferencesUI.py:1009 flatcamGUI/PreferencesUI.py:7270 -#: flatcamGUI/PreferencesUI.py:7299 flatcamGUI/PreferencesUI.py:7401 -#: flatcamTools/ToolCopperThieving.py:260 -#: flatcamTools/ToolCopperThieving.py:300 flatcamTools/ToolFiducials.py:156 +#: flatcamEditors/FlatCAMGrbEditor.py:2486 +#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:263 +#: flatcamGUI/PreferencesUI.py:2299 flatcamGUI/PreferencesUI.py:8892 +#: flatcamGUI/PreferencesUI.py:8921 flatcamGUI/PreferencesUI.py:9023 +#: flatcamTools/ToolCopperThieving.py:261 +#: flatcamTools/ToolCopperThieving.py:301 flatcamTools/ToolFiducials.py:156 msgid "Size" msgstr "Größe" -#: flatcamEditors/FlatCAMGrbEditor.py:2480 -#: flatcamEditors/FlatCAMGrbEditor.py:3832 flatcamGUI/ObjectUI.py:258 +#: flatcamEditors/FlatCAMGrbEditor.py:2486 +#: flatcamEditors/FlatCAMGrbEditor.py:3846 flatcamGUI/ObjectUI.py:263 msgid "Dim" msgstr "Maße" -#: flatcamEditors/FlatCAMGrbEditor.py:2484 flatcamGUI/ObjectUI.py:262 +#: flatcamEditors/FlatCAMGrbEditor.py:2490 flatcamGUI/ObjectUI.py:267 msgid "Index" msgstr "Index" -#: flatcamEditors/FlatCAMGrbEditor.py:2486 -#: flatcamEditors/FlatCAMGrbEditor.py:2515 flatcamGUI/ObjectUI.py:264 +#: flatcamEditors/FlatCAMGrbEditor.py:2492 +#: flatcamEditors/FlatCAMGrbEditor.py:2521 flatcamGUI/ObjectUI.py:269 msgid "Aperture Code" msgstr "Öffnungscode" -#: flatcamEditors/FlatCAMGrbEditor.py:2488 flatcamGUI/ObjectUI.py:266 +#: flatcamEditors/FlatCAMGrbEditor.py:2494 flatcamGUI/ObjectUI.py:271 msgid "Type of aperture: circular, rectangle, macros etc" msgstr "Öffnungsart: kreisförmig, rechteckig, Makros usw" -#: flatcamEditors/FlatCAMGrbEditor.py:2490 flatcamGUI/ObjectUI.py:268 +#: flatcamEditors/FlatCAMGrbEditor.py:2496 flatcamGUI/ObjectUI.py:273 msgid "Aperture Size:" msgstr "Öffnungsgröße:" -#: flatcamEditors/FlatCAMGrbEditor.py:2492 flatcamGUI/ObjectUI.py:270 +#: flatcamEditors/FlatCAMGrbEditor.py:2498 flatcamGUI/ObjectUI.py:275 msgid "" "Aperture Dimensions:\n" " - (width, height) for R, O type.\n" @@ -4835,15 +4695,15 @@ msgstr "" "  - (Breite, Höhe) für R, O-Typ.\n" "  - (dia, nVertices) für P-Typ" -#: flatcamEditors/FlatCAMGrbEditor.py:2516 flatcamGUI/PreferencesUI.py:2474 +#: flatcamEditors/FlatCAMGrbEditor.py:2522 flatcamGUI/PreferencesUI.py:3771 msgid "Code for the new aperture" msgstr "Code für die neue Blende" -#: flatcamEditors/FlatCAMGrbEditor.py:2525 +#: flatcamEditors/FlatCAMGrbEditor.py:2531 msgid "Aperture Size" msgstr "Öffnungsgröße" -#: flatcamEditors/FlatCAMGrbEditor.py:2527 +#: flatcamEditors/FlatCAMGrbEditor.py:2533 msgid "" "Size for the new aperture.\n" "If aperture type is 'R' or 'O' then\n" @@ -4857,11 +4717,11 @@ msgstr "" "berechnet als:\n" "Quadrat (Breite ** 2 + Höhe ** 2)" -#: flatcamEditors/FlatCAMGrbEditor.py:2541 +#: flatcamEditors/FlatCAMGrbEditor.py:2547 msgid "Aperture Type" msgstr "Blendentyp" -#: flatcamEditors/FlatCAMGrbEditor.py:2543 +#: flatcamEditors/FlatCAMGrbEditor.py:2549 msgid "" "Select the type of new aperture. Can be:\n" "C = circular\n" @@ -4873,11 +4733,11 @@ msgstr "" "R = rechteckig\n" "O = länglich" -#: flatcamEditors/FlatCAMGrbEditor.py:2554 +#: flatcamEditors/FlatCAMGrbEditor.py:2560 msgid "Aperture Dim" msgstr "Öffnungsmaße" -#: flatcamEditors/FlatCAMGrbEditor.py:2556 +#: flatcamEditors/FlatCAMGrbEditor.py:2562 msgid "" "Dimensions for the new aperture.\n" "Active only for rectangular apertures (type R).\n" @@ -4887,73 +4747,72 @@ msgstr "" "Aktiv nur für rechteckige Öffnungen (Typ R).\n" "Das Format ist (Breite, Höhe)" -#: flatcamEditors/FlatCAMGrbEditor.py:2565 +#: flatcamEditors/FlatCAMGrbEditor.py:2571 msgid "Add/Delete Aperture" msgstr "Blende hinzufügen / löschen" -#: flatcamEditors/FlatCAMGrbEditor.py:2567 +#: flatcamEditors/FlatCAMGrbEditor.py:2573 msgid "Add/Delete an aperture in the aperture table" msgstr "Eine Blende in der Blendentabelle hinzufügen / löschen" -#: flatcamEditors/FlatCAMGrbEditor.py:2576 +#: flatcamEditors/FlatCAMGrbEditor.py:2582 msgid "Add a new aperture to the aperture list." msgstr "Fügen Sie der Blendenliste eine neue Blende hinzu." -#: flatcamEditors/FlatCAMGrbEditor.py:2581 +#: flatcamEditors/FlatCAMGrbEditor.py:2587 msgid "Delete a aperture in the aperture list" msgstr "Löschen Sie eine Blende in der Blendenliste" -#: flatcamEditors/FlatCAMGrbEditor.py:2598 +#: flatcamEditors/FlatCAMGrbEditor.py:2604 msgid "Buffer Aperture" msgstr "Pufferblende" -#: flatcamEditors/FlatCAMGrbEditor.py:2600 +#: flatcamEditors/FlatCAMGrbEditor.py:2606 msgid "Buffer a aperture in the aperture list" msgstr "Puffern Sie eine Blende in der Blendenliste" -#: flatcamEditors/FlatCAMGrbEditor.py:2613 flatcamGUI/PreferencesUI.py:2608 +#: flatcamEditors/FlatCAMGrbEditor.py:2619 flatcamGUI/PreferencesUI.py:3907 msgid "Buffer distance" msgstr "Pufferabstand" -#: flatcamEditors/FlatCAMGrbEditor.py:2614 +#: flatcamEditors/FlatCAMGrbEditor.py:2620 msgid "Buffer corner" msgstr "Pufferecke" -#: flatcamEditors/FlatCAMGrbEditor.py:2616 +#: flatcamEditors/FlatCAMGrbEditor.py:2622 msgid "" "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 " +" - '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" msgstr "" "Es gibt 3 Arten von Ecken:\n" -"  - 'Kreis': Die Ecke ist abgerundet.\n" -"  - 'Quadrat:' Die Ecke wird in einem spitzen Winkel getroffen.\n" -"  - 'Abgeschrägt:' Die Ecke ist eine Linie, die die Features, die sich in " -"der Ecke treffen, direkt verbindet" +"- 'Kreis': Die Ecke ist abgerundet.\n" +"- 'Quadrat:' Die Ecke wird in einem spitzen Winkel getroffen.\n" +"- 'Abgeschrägt:' Die Ecke ist eine Linie, die die Features, die sich in der " +"Ecke treffen, direkt verbindet" -#: flatcamEditors/FlatCAMGrbEditor.py:2631 flatcamGUI/FlatCAMGUI.py:978 -#: flatcamGUI/FlatCAMGUI.py:2015 flatcamGUI/FlatCAMGUI.py:2087 -#: flatcamGUI/FlatCAMGUI.py:2130 flatcamGUI/FlatCAMGUI.py:2547 -#: flatcamGUI/PreferencesUI.py:6393 flatcamTools/ToolTransform.py:30 -#: flatcamTools/ToolTransform.py:349 +#: flatcamEditors/FlatCAMGrbEditor.py:2637 flatcamGUI/FlatCAMGUI.py:1049 +#: flatcamGUI/FlatCAMGUI.py:2126 flatcamGUI/FlatCAMGUI.py:2198 +#: flatcamGUI/FlatCAMGUI.py:2241 flatcamGUI/FlatCAMGUI.py:2728 +#: flatcamGUI/PreferencesUI.py:7997 flatcamTools/ToolTransform.py:29 msgid "Buffer" msgstr "Puffer" -#: flatcamEditors/FlatCAMGrbEditor.py:2646 +#: flatcamEditors/FlatCAMGrbEditor.py:2652 msgid "Scale Aperture" msgstr "Skalenöffnung" -#: flatcamEditors/FlatCAMGrbEditor.py:2648 +#: flatcamEditors/FlatCAMGrbEditor.py:2654 msgid "Scale a aperture in the aperture list" msgstr "Skalieren Sie eine Blende in der Blendenliste" -#: flatcamEditors/FlatCAMGrbEditor.py:2656 flatcamGUI/PreferencesUI.py:2623 +#: flatcamEditors/FlatCAMGrbEditor.py:2662 flatcamGUI/PreferencesUI.py:3922 msgid "Scale factor" msgstr "Skalierungsfaktor" -#: flatcamEditors/FlatCAMGrbEditor.py:2658 +#: flatcamEditors/FlatCAMGrbEditor.py:2664 msgid "" "The factor by which to scale the selected aperture.\n" "Values can be between 0.0000 and 999.9999" @@ -4961,19 +4820,19 @@ msgstr "" "Der Faktor, um den die ausgewählte Blende skaliert werden soll.\n" "Die Werte können zwischen 0,0000 und 999,9999 liegen" -#: flatcamEditors/FlatCAMGrbEditor.py:2686 +#: flatcamEditors/FlatCAMGrbEditor.py:2692 msgid "Mark polygons" msgstr "Polygone markieren" -#: flatcamEditors/FlatCAMGrbEditor.py:2688 +#: flatcamEditors/FlatCAMGrbEditor.py:2694 msgid "Mark the polygon areas." msgstr "Markieren Sie die Polygonbereiche." -#: flatcamEditors/FlatCAMGrbEditor.py:2696 +#: flatcamEditors/FlatCAMGrbEditor.py:2702 msgid "Area UPPER threshold" msgstr "Flächenobergrenze" -#: flatcamEditors/FlatCAMGrbEditor.py:2698 +#: flatcamEditors/FlatCAMGrbEditor.py:2704 msgid "" "The threshold value, all areas less than this are marked.\n" "Can have a value between 0.0000 and 9999.9999" @@ -4981,11 +4840,11 @@ msgstr "" "Der Schwellenwert, alle Bereiche, die darunter liegen, sind markiert.\n" "Kann einen Wert zwischen 0,0000 und 9999,9999 haben" -#: flatcamEditors/FlatCAMGrbEditor.py:2705 +#: flatcamEditors/FlatCAMGrbEditor.py:2711 msgid "Area LOWER threshold" msgstr "Bereichsuntergrenze" -#: flatcamEditors/FlatCAMGrbEditor.py:2707 +#: flatcamEditors/FlatCAMGrbEditor.py:2713 msgid "" "The threshold value, all areas more than this are marked.\n" "Can have a value between 0.0000 and 9999.9999" @@ -4994,36 +4853,32 @@ msgstr "" "hinausgehen.\n" "Kann einen Wert zwischen 0,0000 und 9999,9999 haben" -#: flatcamEditors/FlatCAMGrbEditor.py:2721 +#: flatcamEditors/FlatCAMGrbEditor.py:2727 msgid "Mark" msgstr "Kennzeichen" -#: flatcamEditors/FlatCAMGrbEditor.py:2723 +#: flatcamEditors/FlatCAMGrbEditor.py:2729 msgid "Mark the polygons that fit within limits." msgstr "Markieren Sie die Polygone, die in Grenzen passen." -#: flatcamEditors/FlatCAMGrbEditor.py:2729 +#: flatcamEditors/FlatCAMGrbEditor.py:2735 msgid "Delete all the marked polygons." msgstr "Löschen Sie alle markierten Polygone." -#: flatcamEditors/FlatCAMGrbEditor.py:2733 -msgid "Clear" -msgstr "Klären" - -#: flatcamEditors/FlatCAMGrbEditor.py:2735 +#: flatcamEditors/FlatCAMGrbEditor.py:2741 msgid "Clear all the markings." msgstr "Alle Markierungen entfernen." -#: flatcamEditors/FlatCAMGrbEditor.py:2755 flatcamGUI/FlatCAMGUI.py:963 -#: flatcamGUI/FlatCAMGUI.py:2015 flatcamGUI/FlatCAMGUI.py:2532 +#: flatcamEditors/FlatCAMGrbEditor.py:2761 flatcamGUI/FlatCAMGUI.py:1034 +#: flatcamGUI/FlatCAMGUI.py:2126 flatcamGUI/FlatCAMGUI.py:2713 msgid "Add Pad Array" msgstr "Pad-Array hinzufügen" -#: flatcamEditors/FlatCAMGrbEditor.py:2757 +#: flatcamEditors/FlatCAMGrbEditor.py:2763 msgid "Add an array of pads (linear or circular array)" msgstr "Hinzufügen eines Arrays von Pads (lineares oder kreisförmiges Array)" -#: flatcamEditors/FlatCAMGrbEditor.py:2763 +#: flatcamEditors/FlatCAMGrbEditor.py:2769 msgid "" "Select the type of pads array to create.\n" "It can be Linear X(Y) or Circular" @@ -5031,15 +4886,15 @@ msgstr "" "Wählen Sie den zu erstellenden Pad-Array-Typ aus.\n" "Es kann lineares X (Y) oder rund sein" -#: flatcamEditors/FlatCAMGrbEditor.py:2774 flatcamGUI/PreferencesUI.py:2511 +#: flatcamEditors/FlatCAMGrbEditor.py:2780 flatcamGUI/PreferencesUI.py:3808 msgid "Nr of pads" msgstr "Anzahl der Pads" -#: flatcamEditors/FlatCAMGrbEditor.py:2776 flatcamGUI/PreferencesUI.py:2513 +#: flatcamEditors/FlatCAMGrbEditor.py:2782 flatcamGUI/PreferencesUI.py:3810 msgid "Specify how many pads to be in the array." msgstr "Geben Sie an, wie viele Pads sich im Array befinden sollen." -#: flatcamEditors/FlatCAMGrbEditor.py:2825 +#: flatcamEditors/FlatCAMGrbEditor.py:2831 msgid "" "Angle at which the linear array is placed.\n" "The precision is of max 2 decimals.\n" @@ -5051,14 +4906,14 @@ msgstr "" "Der Mindestwert beträgt -359,99 Grad.\n" "Maximalwert ist: 360.00 Grad." -#: flatcamEditors/FlatCAMGrbEditor.py:3307 -#: flatcamEditors/FlatCAMGrbEditor.py:3311 +#: flatcamEditors/FlatCAMGrbEditor.py:3321 +#: flatcamEditors/FlatCAMGrbEditor.py:3325 msgid "Aperture code value is missing or wrong format. Add it and retry." msgstr "" "Blendencodewert fehlt oder falsches Format. Fügen Sie es hinzu und versuchen " "Sie es erneut." -#: flatcamEditors/FlatCAMGrbEditor.py:3347 +#: flatcamEditors/FlatCAMGrbEditor.py:3361 msgid "" "Aperture dimensions value is missing or wrong format. Add it in format " "(width, height) and retry." @@ -5066,191 +4921,191 @@ msgstr "" "Wert für Blendenmaße fehlt oder falsches Format. Fügen Sie es im Format " "(Breite, Höhe) hinzu und versuchen Sie es erneut." -#: flatcamEditors/FlatCAMGrbEditor.py:3360 +#: flatcamEditors/FlatCAMGrbEditor.py:3374 msgid "Aperture size value is missing or wrong format. Add it and retry." msgstr "" "Der Wert für die Blendengröße fehlt oder das Format ist falsch. Fügen Sie es " "hinzu und versuchen Sie es erneut." -#: flatcamEditors/FlatCAMGrbEditor.py:3371 +#: flatcamEditors/FlatCAMGrbEditor.py:3385 msgid "Aperture already in the aperture table." msgstr "Blende bereits in der Blendentabelle." -#: flatcamEditors/FlatCAMGrbEditor.py:3379 +#: flatcamEditors/FlatCAMGrbEditor.py:3393 msgid "Added new aperture with code" msgstr "Neue Blende mit Code hinzugefügt" -#: flatcamEditors/FlatCAMGrbEditor.py:3408 +#: flatcamEditors/FlatCAMGrbEditor.py:3422 msgid " Select an aperture in Aperture Table" msgstr " Wählen Sie in Blende Table eine Blende aus" -#: flatcamEditors/FlatCAMGrbEditor.py:3416 +#: flatcamEditors/FlatCAMGrbEditor.py:3430 msgid "Select an aperture in Aperture Table -->" msgstr "Wählen Sie in Blende Table eine Blende aus -->" -#: flatcamEditors/FlatCAMGrbEditor.py:3439 +#: flatcamEditors/FlatCAMGrbEditor.py:3453 msgid "Deleted aperture with code" msgstr "Blende mit Code gelöscht" -#: flatcamEditors/FlatCAMGrbEditor.py:3924 +#: flatcamEditors/FlatCAMGrbEditor.py:3950 msgid "Loading Gerber into Editor" msgstr "Gerber File wird in den Editor geladen" -#: flatcamEditors/FlatCAMGrbEditor.py:4034 +#: flatcamEditors/FlatCAMGrbEditor.py:4078 msgid "Setting up the UI" msgstr "UI wird initialisiert" -#: flatcamEditors/FlatCAMGrbEditor.py:4035 +#: flatcamEditors/FlatCAMGrbEditor.py:4079 msgid "Adding geometry finished. Preparing the GUI" msgstr "Geometrie wurde hinzugefügt. User Interface wird vorbereitet" -#: flatcamEditors/FlatCAMGrbEditor.py:4044 +#: flatcamEditors/FlatCAMGrbEditor.py:4088 msgid "Finished loading the Gerber object into the editor." msgstr "Gerber-Objekte wurde in den Editor geladen." -#: flatcamEditors/FlatCAMGrbEditor.py:4184 +#: flatcamEditors/FlatCAMGrbEditor.py:4228 msgid "" "There are no Aperture definitions in the file. Aborting Gerber creation." msgstr "" "Die Datei enthält keine Aperture-Definitionen. Abbruch der Gerber-Erstellung." -#: flatcamEditors/FlatCAMGrbEditor.py:4194 +#: flatcamEditors/FlatCAMGrbEditor.py:4238 msgid "Creating Gerber." msgstr "Gerber erstellen." -#: flatcamEditors/FlatCAMGrbEditor.py:4203 +#: flatcamEditors/FlatCAMGrbEditor.py:4247 msgid "Done. Gerber editing finished." msgstr "Erledigt. Gerber-Bearbeitung beendet." -#: flatcamEditors/FlatCAMGrbEditor.py:4222 +#: flatcamEditors/FlatCAMGrbEditor.py:4265 msgid "Cancelled. No aperture is selected" msgstr "Abgebrochen. Es ist keine Blende ausgewählt" -#: flatcamEditors/FlatCAMGrbEditor.py:4782 +#: flatcamEditors/FlatCAMGrbEditor.py:4826 msgid "Failed. No aperture geometry is selected." msgstr "Gescheitert. Es ist keine Aperturgeometrie ausgewählt." -#: flatcamEditors/FlatCAMGrbEditor.py:4791 -#: flatcamEditors/FlatCAMGrbEditor.py:5062 +#: flatcamEditors/FlatCAMGrbEditor.py:4835 +#: flatcamEditors/FlatCAMGrbEditor.py:5106 msgid "Done. Apertures geometry deleted." msgstr "Fertig. Blendengeometrie gelöscht." -#: flatcamEditors/FlatCAMGrbEditor.py:4934 +#: flatcamEditors/FlatCAMGrbEditor.py:4978 msgid "No aperture to buffer. Select at least one aperture and try again." msgstr "" "Keine Blende zum Puffern Wählen Sie mindestens eine Blende und versuchen Sie " "es erneut." -#: flatcamEditors/FlatCAMGrbEditor.py:4946 +#: flatcamEditors/FlatCAMGrbEditor.py:4990 msgid "Failed." msgstr "Gescheitert." -#: flatcamEditors/FlatCAMGrbEditor.py:4965 +#: flatcamEditors/FlatCAMGrbEditor.py:5009 msgid "Scale factor value is missing or wrong format. Add it and retry." msgstr "" "Der Skalierungsfaktor ist nicht vorhanden oder das Format ist falsch. Fügen " "Sie es hinzu und versuchen Sie es erneut." -#: flatcamEditors/FlatCAMGrbEditor.py:4997 +#: flatcamEditors/FlatCAMGrbEditor.py:5041 msgid "No aperture to scale. Select at least one aperture and try again." msgstr "" "Keine zu skalierende Blende Wählen Sie mindestens eine Blende und versuchen " "Sie es erneut." -#: flatcamEditors/FlatCAMGrbEditor.py:5013 +#: flatcamEditors/FlatCAMGrbEditor.py:5057 msgid "Done. Scale Tool completed." msgstr "Erledigt. Skalierungswerkzeug abgeschlossen." -#: flatcamEditors/FlatCAMGrbEditor.py:5051 +#: flatcamEditors/FlatCAMGrbEditor.py:5095 msgid "Polygons marked." msgstr "Polygone markiert." -#: flatcamEditors/FlatCAMGrbEditor.py:5054 +#: flatcamEditors/FlatCAMGrbEditor.py:5098 msgid "No polygons were marked. None fit within the limits." msgstr "Es wurden keine Polygone markiert. Keiner passt in die Grenzen." -#: flatcamEditors/FlatCAMGrbEditor.py:5783 +#: flatcamEditors/FlatCAMGrbEditor.py:5822 msgid "Rotation action was not executed." msgstr "Rotationsaktion wurde nicht ausgeführt." -#: flatcamEditors/FlatCAMGrbEditor.py:5919 +#: flatcamEditors/FlatCAMGrbEditor.py:5950 msgid "Skew action was not executed." msgstr "Die Versatzaktion wurde nicht ausgeführt." -#: flatcamEditors/FlatCAMGrbEditor.py:5986 +#: flatcamEditors/FlatCAMGrbEditor.py:6015 msgid "Scale action was not executed." msgstr "Skalierungsaktion wurde nicht ausgeführt." -#: flatcamEditors/FlatCAMGrbEditor.py:6029 +#: flatcamEditors/FlatCAMGrbEditor.py:6058 msgid "Offset action was not executed." msgstr "Offsetaktion wurde nicht ausgeführt." -#: flatcamEditors/FlatCAMGrbEditor.py:6079 +#: flatcamEditors/FlatCAMGrbEditor.py:6108 msgid "Geometry shape offset Y cancelled" msgstr "Geometrieform-Versatz Y abgebrochen" -#: flatcamEditors/FlatCAMGrbEditor.py:6094 +#: flatcamEditors/FlatCAMGrbEditor.py:6123 msgid "Geometry shape skew X cancelled" msgstr "Geometrieformverzerren X abgebrochen" -#: flatcamEditors/FlatCAMGrbEditor.py:6109 +#: flatcamEditors/FlatCAMGrbEditor.py:6138 msgid "Geometry shape skew Y cancelled" msgstr "Geometrieformverzerren Y abgebrochen" -#: flatcamEditors/FlatCAMTextEditor.py:72 +#: flatcamEditors/FlatCAMTextEditor.py:74 msgid "Print Preview" msgstr "Druckvorschau" -#: flatcamEditors/FlatCAMTextEditor.py:73 +#: flatcamEditors/FlatCAMTextEditor.py:75 msgid "Open a OS standard Preview Print window." msgstr "" "Öffnen Sie ein Standardfenster für die Druckvorschau des Betriebssystems." -#: flatcamEditors/FlatCAMTextEditor.py:76 +#: flatcamEditors/FlatCAMTextEditor.py:78 msgid "Print Code" msgstr "Code drucken" -#: flatcamEditors/FlatCAMTextEditor.py:77 +#: flatcamEditors/FlatCAMTextEditor.py:79 msgid "Open a OS standard Print window." msgstr "Öffnen Sie ein Betriebssystem-Standard-Druckfenster." -#: flatcamEditors/FlatCAMTextEditor.py:79 +#: flatcamEditors/FlatCAMTextEditor.py:81 msgid "Find in Code" msgstr "Im Code suchen" -#: flatcamEditors/FlatCAMTextEditor.py:80 +#: flatcamEditors/FlatCAMTextEditor.py:82 msgid "Will search and highlight in yellow the string in the Find box." msgstr "Sucht und hebt die Zeichenfolge im Feld Suchen gelb hervor." -#: flatcamEditors/FlatCAMTextEditor.py:84 +#: flatcamEditors/FlatCAMTextEditor.py:86 msgid "Find box. Enter here the strings to be searched in the text." msgstr "" "Suchfeld. Geben Sie hier die Zeichenfolgen ein, nach denen im Text gesucht " "werden soll." -#: flatcamEditors/FlatCAMTextEditor.py:86 +#: flatcamEditors/FlatCAMTextEditor.py:88 msgid "Replace With" msgstr "Ersetzen mit" -#: flatcamEditors/FlatCAMTextEditor.py:87 +#: flatcamEditors/FlatCAMTextEditor.py:89 msgid "" "Will replace the string from the Find box with the one in the Replace box." msgstr "" "Ersetzt die Zeichenfolge aus dem Feld Suchen durch die Zeichenfolge aus dem " "Feld Ersetzen." -#: flatcamEditors/FlatCAMTextEditor.py:91 +#: flatcamEditors/FlatCAMTextEditor.py:93 msgid "String to replace the one in the Find box throughout the text." msgstr "" "Zeichenfolge, die die Zeichenfolge im Feld Suchen im gesamten Text ersetzt." -#: flatcamEditors/FlatCAMTextEditor.py:93 flatcamGUI/ObjectUI.py:482 -#: flatcamGUI/ObjectUI.py:1809 flatcamGUI/PreferencesUI.py:2070 -#: flatcamGUI/PreferencesUI.py:4419 flatcamGUI/PreferencesUI.py:5647 +#: flatcamEditors/FlatCAMTextEditor.py:95 flatcamGUI/ObjectUI.py:486 +#: flatcamGUI/ObjectUI.py:2139 flatcamGUI/PreferencesUI.py:3367 +#: flatcamGUI/PreferencesUI.py:5829 msgid "All" msgstr "Alles" -#: flatcamEditors/FlatCAMTextEditor.py:94 +#: flatcamEditors/FlatCAMTextEditor.py:96 msgid "" "When checked it will replace all instances in the 'Find' box\n" "with the text in the 'Replace' box.." @@ -5259,161 +5114,183 @@ msgstr "" "ersetzt\n" "mit dem Text im Feld \"Ersetzen\" .." -#: flatcamEditors/FlatCAMTextEditor.py:97 +#: flatcamEditors/FlatCAMTextEditor.py:99 msgid "Copy All" msgstr "Kopiere alles" -#: flatcamEditors/FlatCAMTextEditor.py:98 +#: flatcamEditors/FlatCAMTextEditor.py:100 msgid "Will copy all the text in the Code Editor to the clipboard." msgstr "Kopiert den gesamten Text im Code-Editor in die Zwischenablage." -#: flatcamEditors/FlatCAMTextEditor.py:101 +#: flatcamEditors/FlatCAMTextEditor.py:103 msgid "Open Code" msgstr "Code öffnen" -#: flatcamEditors/FlatCAMTextEditor.py:102 +#: flatcamEditors/FlatCAMTextEditor.py:104 msgid "Will open a text file in the editor." msgstr "Öffnet eine Textdatei im Editor." -#: flatcamEditors/FlatCAMTextEditor.py:104 +#: flatcamEditors/FlatCAMTextEditor.py:106 msgid "Save Code" msgstr "Code speichern" -#: flatcamEditors/FlatCAMTextEditor.py:105 +#: flatcamEditors/FlatCAMTextEditor.py:107 msgid "Will save the text in the editor into a file." msgstr "Speichert den Text im Editor in einer Datei." -#: flatcamEditors/FlatCAMTextEditor.py:107 +#: flatcamEditors/FlatCAMTextEditor.py:109 msgid "Run Code" msgstr "Code ausführen" -#: flatcamEditors/FlatCAMTextEditor.py:108 +#: flatcamEditors/FlatCAMTextEditor.py:110 msgid "Will run the TCL commands found in the text file, one by one." msgstr "Führt die in der Textdatei enthaltenen TCL-Befehle nacheinander aus." -#: flatcamEditors/FlatCAMTextEditor.py:182 +#: flatcamEditors/FlatCAMTextEditor.py:184 msgid "Open file" msgstr "Datei öffnen" -#: flatcamEditors/FlatCAMTextEditor.py:213 -#: flatcamEditors/FlatCAMTextEditor.py:218 +#: flatcamEditors/FlatCAMTextEditor.py:215 +#: flatcamEditors/FlatCAMTextEditor.py:220 msgid "Export Code ..." msgstr "Code exportieren ..." -#: flatcamEditors/FlatCAMTextEditor.py:221 -msgid "Export Code cancelled." -msgstr "Exportcode abgebrochen." +#: flatcamEditors/FlatCAMTextEditor.py:272 flatcamObjects/FlatCAMCNCJob.py:955 +#: flatcamTools/ToolSolderPaste.py:1530 +msgid "No such file or directory" +msgstr "Keine solche Datei oder Ordner" -#: flatcamEditors/FlatCAMTextEditor.py:332 +#: flatcamEditors/FlatCAMTextEditor.py:284 flatcamObjects/FlatCAMCNCJob.py:969 +msgid "Saved to" +msgstr "Gespeichert in" + +#: flatcamEditors/FlatCAMTextEditor.py:334 msgid "Code Editor content copied to clipboard ..." msgstr "Code Editor Inhalt in die Zwischenablage kopiert ..." -#: flatcamGUI/FlatCAMGUI.py:52 flatcamGUI/FlatCAMGUI.py:54 -#: flatcamGUI/FlatCAMGUI.py:2040 +#: flatcamGUI/FlatCAMGUI.py:66 flatcamGUI/FlatCAMGUI.py:68 +#: flatcamGUI/FlatCAMGUI.py:2151 msgid "Toggle Panel" msgstr "Panel umschalten" -#: flatcamGUI/FlatCAMGUI.py:64 +#: flatcamGUI/FlatCAMGUI.py:78 msgid "File" msgstr "Datei" -#: flatcamGUI/FlatCAMGUI.py:69 +#: flatcamGUI/FlatCAMGUI.py:83 msgid "&New Project ...\tCtrl+N" msgstr "&Neues Projekt ...\\STRG+N" -#: flatcamGUI/FlatCAMGUI.py:71 +#: flatcamGUI/FlatCAMGUI.py:85 msgid "Will create a new, blank project" msgstr "Erzeugt ein neues leeres Projekt" -#: flatcamGUI/FlatCAMGUI.py:76 +#: flatcamGUI/FlatCAMGUI.py:90 msgid "&New" msgstr "&Neu" -#: flatcamGUI/FlatCAMGUI.py:80 +#: flatcamGUI/FlatCAMGUI.py:94 msgid "Geometry\tN" msgstr "Geometrie\tN" -#: flatcamGUI/FlatCAMGUI.py:82 +#: flatcamGUI/FlatCAMGUI.py:96 msgid "Will create a new, empty Geometry Object." msgstr "Erzeugt ein neues, leeres Geometrieobjekt." -#: flatcamGUI/FlatCAMGUI.py:84 +#: flatcamGUI/FlatCAMGUI.py:99 msgid "Gerber\tB" msgstr "Gerber\tB" -#: flatcamGUI/FlatCAMGUI.py:86 +#: flatcamGUI/FlatCAMGUI.py:101 msgid "Will create a new, empty Gerber Object." msgstr "Erzeugt ein neues, leeres Gerber-Objekt." -#: flatcamGUI/FlatCAMGUI.py:88 +#: flatcamGUI/FlatCAMGUI.py:104 msgid "Excellon\tL" msgstr "Excellon\tL" -#: flatcamGUI/FlatCAMGUI.py:90 +#: flatcamGUI/FlatCAMGUI.py:106 msgid "Will create a new, empty Excellon Object." msgstr "Erzeugt ein neues, leeres Excellon-Objekt." -#: flatcamGUI/FlatCAMGUI.py:94 +#: flatcamGUI/FlatCAMGUI.py:111 msgid "Document\tD" msgstr "Dokumentieren\tD" -#: flatcamGUI/FlatCAMGUI.py:96 +#: flatcamGUI/FlatCAMGUI.py:113 msgid "Will create a new, empty Document Object." msgstr "Erstellt ein neues, leeres Dokumentobjekt." -#: flatcamGUI/FlatCAMGUI.py:99 flatcamGUI/FlatCAMGUI.py:4111 +#: flatcamGUI/FlatCAMGUI.py:117 flatcamGUI/FlatCAMGUI.py:4338 #: flatcamTools/ToolPcbWizard.py:62 flatcamTools/ToolPcbWizard.py:69 msgid "Open" msgstr "Öffnen" -#: flatcamGUI/FlatCAMGUI.py:103 +#: flatcamGUI/FlatCAMGUI.py:122 msgid "Open &Project ..." msgstr "&Projekt öffnen..." -#: flatcamGUI/FlatCAMGUI.py:109 flatcamGUI/FlatCAMGUI.py:4121 +#: flatcamGUI/FlatCAMGUI.py:128 flatcamGUI/FlatCAMGUI.py:4348 msgid "Open &Gerber ...\tCtrl+G" msgstr "&Gerber öffnen...\\STRG+G" -#: flatcamGUI/FlatCAMGUI.py:114 flatcamGUI/FlatCAMGUI.py:4126 +#: flatcamGUI/FlatCAMGUI.py:133 flatcamGUI/FlatCAMGUI.py:4353 msgid "Open &Excellon ...\tCtrl+E" msgstr "&Excellon öffnen...\\STRG+E" -#: flatcamGUI/FlatCAMGUI.py:118 flatcamGUI/FlatCAMGUI.py:4131 +#: flatcamGUI/FlatCAMGUI.py:138 flatcamGUI/FlatCAMGUI.py:4358 msgid "Open G-&Code ..." msgstr "G-&Code öffnen..." -#: flatcamGUI/FlatCAMGUI.py:124 +#: flatcamGUI/FlatCAMGUI.py:145 msgid "Open Config ..." msgstr "Config öffnen..." -#: flatcamGUI/FlatCAMGUI.py:128 +#: flatcamGUI/FlatCAMGUI.py:150 msgid "Recent projects" msgstr "Letzte Projekte" -#: flatcamGUI/FlatCAMGUI.py:129 +#: flatcamGUI/FlatCAMGUI.py:152 msgid "Recent files" msgstr "Neueste Dateien" -#: flatcamGUI/FlatCAMGUI.py:135 +#: flatcamGUI/FlatCAMGUI.py:155 flatcamGUI/FlatCAMGUI.py:741 +#: flatcamGUI/FlatCAMGUI.py:1327 +msgid "Save" +msgstr "Speichern" + +#: flatcamGUI/FlatCAMGUI.py:159 +msgid "&Save Project ...\tCtrl+S" +msgstr "Projekt speichern ...\\STRG+S" + +#: flatcamGUI/FlatCAMGUI.py:164 +msgid "Save Project &As ...\tCtrl+Shift+S" +msgstr "Projekt speichern als ...\\STRG+Shift+S" + +#: flatcamGUI/FlatCAMGUI.py:179 msgid "Scripting" msgstr "Scripting" -#: flatcamGUI/FlatCAMGUI.py:138 flatcamGUI/FlatCAMGUI.py:829 -#: flatcamGUI/FlatCAMGUI.py:2409 +#: flatcamGUI/FlatCAMGUI.py:183 flatcamGUI/FlatCAMGUI.py:891 +#: flatcamGUI/FlatCAMGUI.py:2570 msgid "New Script ..." msgstr "Neues Skript ..." -#: flatcamGUI/FlatCAMGUI.py:139 flatcamGUI/FlatCAMGUI.py:831 -#: flatcamGUI/FlatCAMGUI.py:2411 +#: flatcamGUI/FlatCAMGUI.py:185 flatcamGUI/FlatCAMGUI.py:893 +#: flatcamGUI/FlatCAMGUI.py:2572 msgid "Open Script ..." msgstr "Skript öffnen ..." -#: flatcamGUI/FlatCAMGUI.py:141 flatcamGUI/FlatCAMGUI.py:833 -#: flatcamGUI/FlatCAMGUI.py:2413 flatcamGUI/FlatCAMGUI.py:4100 +#: flatcamGUI/FlatCAMGUI.py:187 +msgid "Open Example ..." +msgstr "Beispiel öffnen ..." + +#: flatcamGUI/FlatCAMGUI.py:189 flatcamGUI/FlatCAMGUI.py:895 +#: flatcamGUI/FlatCAMGUI.py:2574 flatcamGUI/FlatCAMGUI.py:4327 msgid "Run Script ..." msgstr "Skript ausführen ..." -#: flatcamGUI/FlatCAMGUI.py:143 flatcamGUI/FlatCAMGUI.py:4102 +#: flatcamGUI/FlatCAMGUI.py:191 flatcamGUI/FlatCAMGUI.py:4329 msgid "" "Will run the opened Tcl Script thus\n" "enabling the automation of certain\n" @@ -5423,47 +5300,47 @@ msgstr "" "Ermöglichung der Automatisierung bestimmter\n" "Funktionen von FlatCAM." -#: flatcamGUI/FlatCAMGUI.py:156 +#: flatcamGUI/FlatCAMGUI.py:206 msgid "Import" msgstr "Importieren" -#: flatcamGUI/FlatCAMGUI.py:158 +#: flatcamGUI/FlatCAMGUI.py:208 msgid "&SVG as Geometry Object ..." msgstr "&SVG als Geometrieobjekt ..." -#: flatcamGUI/FlatCAMGUI.py:161 +#: flatcamGUI/FlatCAMGUI.py:211 msgid "&SVG as Gerber Object ..." msgstr "&SVG als Gerberobjekt ..." -#: flatcamGUI/FlatCAMGUI.py:166 +#: flatcamGUI/FlatCAMGUI.py:216 msgid "&DXF as Geometry Object ..." msgstr "&DXF als Geometrieobjekt ..." -#: flatcamGUI/FlatCAMGUI.py:169 +#: flatcamGUI/FlatCAMGUI.py:219 msgid "&DXF as Gerber Object ..." msgstr "&DXF als Gerberobjekt ..." -#: flatcamGUI/FlatCAMGUI.py:173 +#: flatcamGUI/FlatCAMGUI.py:223 msgid "HPGL2 as Geometry Object ..." msgstr "HPGL2 als Geometrieobjekt ..." -#: flatcamGUI/FlatCAMGUI.py:178 +#: flatcamGUI/FlatCAMGUI.py:229 msgid "Export" msgstr "Exportieren" -#: flatcamGUI/FlatCAMGUI.py:181 +#: flatcamGUI/FlatCAMGUI.py:233 msgid "Export &SVG ..." msgstr "SVG exportieren ..." -#: flatcamGUI/FlatCAMGUI.py:184 +#: flatcamGUI/FlatCAMGUI.py:237 msgid "Export DXF ..." msgstr "DXF exportieren ..." -#: flatcamGUI/FlatCAMGUI.py:189 +#: flatcamGUI/FlatCAMGUI.py:243 msgid "Export &PNG ..." msgstr "PNG exportieren ..." -#: flatcamGUI/FlatCAMGUI.py:191 +#: flatcamGUI/FlatCAMGUI.py:245 msgid "" "Will export an image in PNG format,\n" "the saved image will contain the visual \n" @@ -5473,11 +5350,11 @@ msgstr "" "Das gespeicherte Bild enthält die\n" "Bildinformationen des FlatCAM-Plotbereiches." -#: flatcamGUI/FlatCAMGUI.py:200 +#: flatcamGUI/FlatCAMGUI.py:254 msgid "Export &Excellon ..." msgstr "Excellon exportieren ..." -#: flatcamGUI/FlatCAMGUI.py:202 +#: flatcamGUI/FlatCAMGUI.py:256 msgid "" "Will export an Excellon Object as Excellon file,\n" "the coordinates format, the file units and zeros\n" @@ -5487,11 +5364,11 @@ msgstr "" "Das Koordinatenformat, die Dateieinheiten und Nullen\n" "werden in den Einstellungen -> Excellon Export.Excellon eingestellt ..." -#: flatcamGUI/FlatCAMGUI.py:209 +#: flatcamGUI/FlatCAMGUI.py:263 msgid "Export &Gerber ..." msgstr "Gerber exportieren ..." -#: flatcamGUI/FlatCAMGUI.py:211 +#: flatcamGUI/FlatCAMGUI.py:265 msgid "" "Will export an Gerber Object as Gerber file,\n" "the coordinates format, the file units and zeros\n" @@ -5501,65 +5378,52 @@ msgstr "" "das Koordinatenformat, die Dateieinheiten und Nullen\n" "werden in den Einstellungen -> Gerber Export eingestellt." -#: flatcamGUI/FlatCAMGUI.py:229 +#: flatcamGUI/FlatCAMGUI.py:275 msgid "Backup" msgstr "Sicherungskopie" -#: flatcamGUI/FlatCAMGUI.py:233 +#: flatcamGUI/FlatCAMGUI.py:280 msgid "Import Preferences from file ..." msgstr "Einstellungen aus Datei importieren ..." -#: flatcamGUI/FlatCAMGUI.py:238 +#: flatcamGUI/FlatCAMGUI.py:286 msgid "Export Preferences to file ..." msgstr "Einstellungen in Datei exportieren ..." -#: flatcamGUI/FlatCAMGUI.py:244 flatcamGUI/FlatCAMGUI.py:1614 +#: flatcamGUI/FlatCAMGUI.py:294 flatcamGUI/PreferencesUI.py:1123 +msgid "Save Preferences" +msgstr "Einstellungen speichern" + +#: flatcamGUI/FlatCAMGUI.py:300 flatcamGUI/FlatCAMGUI.py:1718 msgid "Print (PDF)" msgstr "Drucken (PDF)" -#: flatcamGUI/FlatCAMGUI.py:247 flatcamGUI/FlatCAMGUI.py:682 -#: flatcamGUI/FlatCAMGUI.py:1252 -msgid "Save" -msgstr "Speichern" - -#: flatcamGUI/FlatCAMGUI.py:251 -msgid "&Save Project ..." -msgstr "Projekt speichern ..." - -#: flatcamGUI/FlatCAMGUI.py:256 -msgid "Save Project &As ...\tCtrl+S" -msgstr "Projekt speichern als ...\\STRG+S" - -#: flatcamGUI/FlatCAMGUI.py:261 -msgid "Save Project C&opy ..." -msgstr "Projektkopie speichern ..." - -#: flatcamGUI/FlatCAMGUI.py:271 +#: flatcamGUI/FlatCAMGUI.py:308 msgid "E&xit" msgstr "Ausgang" -#: flatcamGUI/FlatCAMGUI.py:279 flatcamGUI/FlatCAMGUI.py:676 -#: flatcamGUI/FlatCAMGUI.py:2163 +#: flatcamGUI/FlatCAMGUI.py:316 flatcamGUI/FlatCAMGUI.py:735 +#: flatcamGUI/FlatCAMGUI.py:2274 msgid "Edit" msgstr "Bearbeiten" -#: flatcamGUI/FlatCAMGUI.py:283 +#: flatcamGUI/FlatCAMGUI.py:320 msgid "Edit Object\tE" msgstr "Objekt bearbeiten\tE" -#: flatcamGUI/FlatCAMGUI.py:285 +#: flatcamGUI/FlatCAMGUI.py:322 msgid "Close Editor\tCtrl+S" msgstr "Schließen Sie Editor\tSTRG+S" -#: flatcamGUI/FlatCAMGUI.py:294 +#: flatcamGUI/FlatCAMGUI.py:331 msgid "Conversion" msgstr "Umwandlung" -#: flatcamGUI/FlatCAMGUI.py:296 +#: flatcamGUI/FlatCAMGUI.py:333 msgid "&Join Geo/Gerber/Exc -> Geo" msgstr "Geo/Gerber/Exc -> Geo zusammenfassen" -#: flatcamGUI/FlatCAMGUI.py:298 +#: flatcamGUI/FlatCAMGUI.py:335 msgid "" "Merge a selection of objects, which can be of type:\n" "- Gerber\n" @@ -5573,31 +5437,31 @@ msgstr "" "- Geometrie\n" "in ein neues Geometrieobjekt kombinieren." -#: flatcamGUI/FlatCAMGUI.py:305 +#: flatcamGUI/FlatCAMGUI.py:342 msgid "Join Excellon(s) -> Excellon" msgstr "Excellon(s) -> Excellon zusammenfassen" -#: flatcamGUI/FlatCAMGUI.py:307 +#: flatcamGUI/FlatCAMGUI.py:344 msgid "Merge a selection of Excellon objects into a new combo Excellon object." msgstr "" "Fassen Sie eine Auswahl von Excellon-Objekten in einem neuen Excellon-Objekt " "zusammen." -#: flatcamGUI/FlatCAMGUI.py:310 +#: flatcamGUI/FlatCAMGUI.py:347 msgid "Join Gerber(s) -> Gerber" msgstr "Gerber(s) -> Gerber zusammenfassen" -#: flatcamGUI/FlatCAMGUI.py:312 +#: flatcamGUI/FlatCAMGUI.py:349 msgid "Merge a selection of Gerber objects into a new combo Gerber object." msgstr "" "Mischen Sie eine Auswahl von Gerber-Objekten in ein neues Gerber-" "Kombinationsobjekt." -#: flatcamGUI/FlatCAMGUI.py:317 +#: flatcamGUI/FlatCAMGUI.py:354 msgid "Convert Single to MultiGeo" msgstr "Konvertieren Sie Single in MultiGeo" -#: flatcamGUI/FlatCAMGUI.py:319 +#: flatcamGUI/FlatCAMGUI.py:356 msgid "" "Will convert a Geometry object from single_geometry type\n" "to a multi_geometry type." @@ -5605,11 +5469,11 @@ msgstr "" "Konvertiert ein Geometrieobjekt vom Typ single_geometry\n" "zu einem multi_geometry-Typ." -#: flatcamGUI/FlatCAMGUI.py:323 +#: flatcamGUI/FlatCAMGUI.py:360 msgid "Convert Multi to SingleGeo" msgstr "Konvertieren Sie Multi in SingleGeo" -#: flatcamGUI/FlatCAMGUI.py:325 +#: flatcamGUI/FlatCAMGUI.py:362 msgid "" "Will convert a Geometry object from multi_geometry type\n" "to a single_geometry type." @@ -5617,736 +5481,758 @@ msgstr "" "Konvertiert ein Geometrieobjekt vom Typ multi_geometry\n" "zu einem single_geometry-Typ." -#: flatcamGUI/FlatCAMGUI.py:332 +#: flatcamGUI/FlatCAMGUI.py:369 msgid "Convert Any to Geo" msgstr "Konvertieren Sie Any zu Geo" -#: flatcamGUI/FlatCAMGUI.py:335 +#: flatcamGUI/FlatCAMGUI.py:372 msgid "Convert Any to Gerber" msgstr "Konvertieren Sie Any zu Gerber" -#: flatcamGUI/FlatCAMGUI.py:341 +#: flatcamGUI/FlatCAMGUI.py:378 msgid "&Copy\tCtrl+C" msgstr "Kopieren\tSTRG+C" -#: flatcamGUI/FlatCAMGUI.py:346 +#: flatcamGUI/FlatCAMGUI.py:383 msgid "&Delete\tDEL" msgstr "Löschen\tDEL" -#: flatcamGUI/FlatCAMGUI.py:351 +#: flatcamGUI/FlatCAMGUI.py:388 msgid "Se&t Origin\tO" msgstr "Ursprung festlegen\tO" -#: flatcamGUI/FlatCAMGUI.py:353 +#: flatcamGUI/FlatCAMGUI.py:390 +msgid "Move to Origin\tShift+O" +msgstr "Zum Ursprung wechseln\tShift+O" + +#: flatcamGUI/FlatCAMGUI.py:393 msgid "Jump to Location\tJ" msgstr "Zum Ort springen\tJ" -#: flatcamGUI/FlatCAMGUI.py:358 +#: flatcamGUI/FlatCAMGUI.py:395 +msgid "Locate in Object\tShift+J" +msgstr "Suchen Sie im Objekt\tShift+J" + +#: flatcamGUI/FlatCAMGUI.py:400 msgid "Toggle Units\tQ" msgstr "Einheiten umschalten\tQ" -#: flatcamGUI/FlatCAMGUI.py:360 +#: flatcamGUI/FlatCAMGUI.py:402 msgid "&Select All\tCtrl+A" msgstr "Alles auswählen\tSTRG+A" -#: flatcamGUI/FlatCAMGUI.py:365 +#: flatcamGUI/FlatCAMGUI.py:407 msgid "&Preferences\tShift+P" msgstr "Einstellungen\tShift+P" -#: flatcamGUI/FlatCAMGUI.py:371 flatcamTools/ToolProperties.py:153 +#: flatcamGUI/FlatCAMGUI.py:413 flatcamTools/ToolProperties.py:155 msgid "Options" msgstr "Optionen" -#: flatcamGUI/FlatCAMGUI.py:373 +#: flatcamGUI/FlatCAMGUI.py:415 msgid "&Rotate Selection\tShift+(R)" msgstr "Auswahl drehen\tShift+(R)" -#: flatcamGUI/FlatCAMGUI.py:378 +#: flatcamGUI/FlatCAMGUI.py:420 msgid "&Skew on X axis\tShift+X" msgstr "Neigung auf der X-Achse\tShift+X" -#: flatcamGUI/FlatCAMGUI.py:380 +#: flatcamGUI/FlatCAMGUI.py:422 msgid "S&kew on Y axis\tShift+Y" msgstr "Neigung auf der Y-Achse\tShift+Y" -#: flatcamGUI/FlatCAMGUI.py:385 +#: flatcamGUI/FlatCAMGUI.py:427 msgid "Flip on &X axis\tX" msgstr "X-Achse kippen\tX" -#: flatcamGUI/FlatCAMGUI.py:387 +#: flatcamGUI/FlatCAMGUI.py:429 msgid "Flip on &Y axis\tY" msgstr "Y-Achse kippen\tY" -#: flatcamGUI/FlatCAMGUI.py:392 +#: flatcamGUI/FlatCAMGUI.py:434 msgid "View source\tAlt+S" msgstr "Quelltext anzeigen\tAlt+S" -#: flatcamGUI/FlatCAMGUI.py:394 +#: flatcamGUI/FlatCAMGUI.py:436 msgid "Tools DataBase\tCtrl+D" msgstr "Werkzeugdatenbank\tSTRG+D" -#: flatcamGUI/FlatCAMGUI.py:401 flatcamGUI/FlatCAMGUI.py:2060 +#: flatcamGUI/FlatCAMGUI.py:443 flatcamGUI/FlatCAMGUI.py:2171 msgid "View" msgstr "Aussicht" -#: flatcamGUI/FlatCAMGUI.py:403 +#: flatcamGUI/FlatCAMGUI.py:445 msgid "Enable all plots\tAlt+1" msgstr "Alle Diagramme aktivieren\tAlt+1" -#: flatcamGUI/FlatCAMGUI.py:405 +#: flatcamGUI/FlatCAMGUI.py:447 msgid "Disable all plots\tAlt+2" msgstr "Alle Diagramme deaktivieren\tAlt+2" -#: flatcamGUI/FlatCAMGUI.py:407 +#: flatcamGUI/FlatCAMGUI.py:449 msgid "Disable non-selected\tAlt+3" msgstr "Nicht ausgewählte Diagramme deaktivieren\tAlt+3" -#: flatcamGUI/FlatCAMGUI.py:411 +#: flatcamGUI/FlatCAMGUI.py:453 msgid "&Zoom Fit\tV" msgstr "Passed zoomen\tV" -#: flatcamGUI/FlatCAMGUI.py:413 +#: flatcamGUI/FlatCAMGUI.py:455 msgid "&Zoom In\t=" msgstr "Hineinzoomen\t=" -#: flatcamGUI/FlatCAMGUI.py:415 +#: flatcamGUI/FlatCAMGUI.py:457 msgid "&Zoom Out\t-" msgstr "Rauszoomen\t-" -#: flatcamGUI/FlatCAMGUI.py:420 +#: flatcamGUI/FlatCAMGUI.py:462 msgid "Redraw All\tF5" msgstr "Alles neu zeichnen\tF5" -#: flatcamGUI/FlatCAMGUI.py:424 +#: flatcamGUI/FlatCAMGUI.py:466 msgid "Toggle Code Editor\tShift+E" msgstr "Code-Editor umschalten\tShift+E" -#: flatcamGUI/FlatCAMGUI.py:427 +#: flatcamGUI/FlatCAMGUI.py:469 msgid "&Toggle FullScreen\tAlt+F10" msgstr "FullScreen umschalten\tAlt+F10" -#: flatcamGUI/FlatCAMGUI.py:429 +#: flatcamGUI/FlatCAMGUI.py:471 msgid "&Toggle Plot Area\tCtrl+F10" msgstr "Plotbereich umschalten\tSTRG+F10" -#: flatcamGUI/FlatCAMGUI.py:431 +#: flatcamGUI/FlatCAMGUI.py:473 msgid "&Toggle Project/Sel/Tool\t`" msgstr "Projekt/Auswahl/Werkzeug umschalten\t`" -#: flatcamGUI/FlatCAMGUI.py:435 +#: flatcamGUI/FlatCAMGUI.py:477 msgid "&Toggle Grid Snap\tG" msgstr "Schaltet den Rasterfang ein\tG" -#: flatcamGUI/FlatCAMGUI.py:437 +#: flatcamGUI/FlatCAMGUI.py:479 msgid "&Toggle Grid Lines\tAlt+G" msgstr "Gitterlinien umschalten\tAlt+G" -#: flatcamGUI/FlatCAMGUI.py:439 +#: flatcamGUI/FlatCAMGUI.py:481 msgid "&Toggle Axis\tShift+G" msgstr "Achse umschalten\tShift+G" -#: flatcamGUI/FlatCAMGUI.py:441 +#: flatcamGUI/FlatCAMGUI.py:483 msgid "Toggle Workspace\tShift+W" msgstr "Arbeitsbereich umschalten\tShift+W" -#: flatcamGUI/FlatCAMGUI.py:446 +#: flatcamGUI/FlatCAMGUI.py:488 msgid "Objects" msgstr "Objekte" -#: flatcamGUI/FlatCAMGUI.py:460 +#: flatcamGUI/FlatCAMGUI.py:502 msgid "&Command Line\tS" msgstr "Befehlszeile\tS" -#: flatcamGUI/FlatCAMGUI.py:465 +#: flatcamGUI/FlatCAMGUI.py:507 msgid "Help" msgstr "Hilfe" -#: flatcamGUI/FlatCAMGUI.py:467 +#: flatcamGUI/FlatCAMGUI.py:509 msgid "Online Help\tF1" msgstr "Onlinehilfe\tF1" -#: flatcamGUI/FlatCAMGUI.py:477 +#: flatcamGUI/FlatCAMGUI.py:519 msgid "Report a bug" msgstr "Einen Fehler melden" -#: flatcamGUI/FlatCAMGUI.py:480 +#: flatcamGUI/FlatCAMGUI.py:522 msgid "Excellon Specification" msgstr "Excellon-Spezifikation" -#: flatcamGUI/FlatCAMGUI.py:482 +#: flatcamGUI/FlatCAMGUI.py:524 msgid "Gerber Specification" msgstr "Gerber-Spezifikation" -#: flatcamGUI/FlatCAMGUI.py:487 +#: flatcamGUI/FlatCAMGUI.py:529 msgid "Shortcuts List\tF3" msgstr "Tastenkürzel Liste\tF3" -#: flatcamGUI/FlatCAMGUI.py:489 +#: flatcamGUI/FlatCAMGUI.py:531 msgid "YouTube Channel\tF4" msgstr "Youtube Kanal\tF4" -#: flatcamGUI/FlatCAMGUI.py:500 +#: flatcamGUI/FlatCAMGUI.py:542 msgid "Add Circle\tO" msgstr "Kreis hinzufügen\tO" -#: flatcamGUI/FlatCAMGUI.py:503 +#: flatcamGUI/FlatCAMGUI.py:545 msgid "Add Arc\tA" msgstr "Bogen hinzufügen\tA" -#: flatcamGUI/FlatCAMGUI.py:506 +#: flatcamGUI/FlatCAMGUI.py:548 msgid "Add Rectangle\tR" msgstr "Rechteck hinzufügen\tR" -#: flatcamGUI/FlatCAMGUI.py:509 +#: flatcamGUI/FlatCAMGUI.py:551 msgid "Add Polygon\tN" msgstr "Polygon hinzufügen\tN" -#: flatcamGUI/FlatCAMGUI.py:512 +#: flatcamGUI/FlatCAMGUI.py:554 msgid "Add Path\tP" msgstr "Pfad hinzufügen\tP" -#: flatcamGUI/FlatCAMGUI.py:515 +#: flatcamGUI/FlatCAMGUI.py:557 msgid "Add Text\tT" msgstr "Text hinzufügen\tT" -#: flatcamGUI/FlatCAMGUI.py:518 +#: flatcamGUI/FlatCAMGUI.py:560 msgid "Polygon Union\tU" msgstr "Polygon-Vereinigung\tU" -#: flatcamGUI/FlatCAMGUI.py:520 +#: flatcamGUI/FlatCAMGUI.py:562 msgid "Polygon Intersection\tE" msgstr "Polygonschnitt\tE" -#: flatcamGUI/FlatCAMGUI.py:522 +#: flatcamGUI/FlatCAMGUI.py:564 msgid "Polygon Subtraction\tS" msgstr "Polygon-Subtraktion\tS" -#: flatcamGUI/FlatCAMGUI.py:526 +#: flatcamGUI/FlatCAMGUI.py:568 msgid "Cut Path\tX" msgstr "Pfad ausschneiden\tX" -#: flatcamGUI/FlatCAMGUI.py:529 +#: flatcamGUI/FlatCAMGUI.py:572 msgid "Copy Geom\tC" msgstr "Geometrie kopieren\tC" -#: flatcamGUI/FlatCAMGUI.py:531 +#: flatcamGUI/FlatCAMGUI.py:574 msgid "Delete Shape\tDEL" msgstr "Form löschen\tDEL" -#: flatcamGUI/FlatCAMGUI.py:535 flatcamGUI/FlatCAMGUI.py:622 +#: flatcamGUI/FlatCAMGUI.py:578 flatcamGUI/FlatCAMGUI.py:665 msgid "Move\tM" msgstr "Bewegung\tM" -#: flatcamGUI/FlatCAMGUI.py:537 +#: flatcamGUI/FlatCAMGUI.py:580 msgid "Buffer Tool\tB" msgstr "Pufferwerkzeug\tB" -#: flatcamGUI/FlatCAMGUI.py:540 +#: flatcamGUI/FlatCAMGUI.py:583 msgid "Paint Tool\tI" msgstr "Malenwerkzeug\tI" -#: flatcamGUI/FlatCAMGUI.py:543 +#: flatcamGUI/FlatCAMGUI.py:586 msgid "Transform Tool\tAlt+R" msgstr "Transformationswerkzeug\tAlt+R" -#: flatcamGUI/FlatCAMGUI.py:547 +#: flatcamGUI/FlatCAMGUI.py:590 msgid "Toggle Corner Snap\tK" msgstr "Eckfang umschalten\tK" -#: flatcamGUI/FlatCAMGUI.py:553 +#: flatcamGUI/FlatCAMGUI.py:596 msgid ">Excellon Editor<" msgstr ">Excellon Editor<" -#: flatcamGUI/FlatCAMGUI.py:557 +#: flatcamGUI/FlatCAMGUI.py:600 msgid "Add Drill Array\tA" msgstr "Bohrfeld hinzufügen\tA" -#: flatcamGUI/FlatCAMGUI.py:559 +#: flatcamGUI/FlatCAMGUI.py:602 msgid "Add Drill\tD" msgstr "Bohrer hinzufügen\tD" -#: flatcamGUI/FlatCAMGUI.py:563 +#: flatcamGUI/FlatCAMGUI.py:606 msgid "Add Slot Array\tQ" msgstr "Steckplatz-Array hinzufügen\tQ" -#: flatcamGUI/FlatCAMGUI.py:565 +#: flatcamGUI/FlatCAMGUI.py:608 msgid "Add Slot\tW" msgstr "Slot hinzufügen\tW" -#: flatcamGUI/FlatCAMGUI.py:569 +#: flatcamGUI/FlatCAMGUI.py:612 msgid "Resize Drill(S)\tR" msgstr "Bohrer verkleinern\tR" -#: flatcamGUI/FlatCAMGUI.py:572 flatcamGUI/FlatCAMGUI.py:616 +#: flatcamGUI/FlatCAMGUI.py:615 flatcamGUI/FlatCAMGUI.py:659 msgid "Copy\tC" msgstr "Kopieren\tC" -#: flatcamGUI/FlatCAMGUI.py:574 flatcamGUI/FlatCAMGUI.py:618 +#: flatcamGUI/FlatCAMGUI.py:617 flatcamGUI/FlatCAMGUI.py:661 msgid "Delete\tDEL" msgstr "Löschen\tDEL" -#: flatcamGUI/FlatCAMGUI.py:579 +#: flatcamGUI/FlatCAMGUI.py:622 msgid "Move Drill(s)\tM" msgstr "Bohrer verschieben\tM" -#: flatcamGUI/FlatCAMGUI.py:584 +#: flatcamGUI/FlatCAMGUI.py:627 msgid ">Gerber Editor<" msgstr ">Gerber-Editor<" -#: flatcamGUI/FlatCAMGUI.py:588 +#: flatcamGUI/FlatCAMGUI.py:631 msgid "Add Pad\tP" msgstr "Pad hinzufügen\tP" -#: flatcamGUI/FlatCAMGUI.py:590 +#: flatcamGUI/FlatCAMGUI.py:633 msgid "Add Pad Array\tA" msgstr "Pad-Array hinzufügen\tA" -#: flatcamGUI/FlatCAMGUI.py:592 +#: flatcamGUI/FlatCAMGUI.py:635 msgid "Add Track\tT" msgstr "Track hinzufügen\tA" -#: flatcamGUI/FlatCAMGUI.py:594 +#: flatcamGUI/FlatCAMGUI.py:637 msgid "Add Region\tN" msgstr "Region hinzufügen\tN" -#: flatcamGUI/FlatCAMGUI.py:598 +#: flatcamGUI/FlatCAMGUI.py:641 msgid "Poligonize\tAlt+N" msgstr "Polygonisieren\tAlt+N" -#: flatcamGUI/FlatCAMGUI.py:600 +#: flatcamGUI/FlatCAMGUI.py:643 msgid "Add SemiDisc\tE" msgstr "Halbschibe hinzufügen\tE" -#: flatcamGUI/FlatCAMGUI.py:602 +#: flatcamGUI/FlatCAMGUI.py:645 msgid "Add Disc\tD" msgstr "Schibe hinzufügen\tD" -#: flatcamGUI/FlatCAMGUI.py:604 +#: flatcamGUI/FlatCAMGUI.py:647 msgid "Buffer\tB" msgstr "Puffer\tB" -#: flatcamGUI/FlatCAMGUI.py:606 +#: flatcamGUI/FlatCAMGUI.py:649 msgid "Scale\tS" msgstr "Skalieren\tS" -#: flatcamGUI/FlatCAMGUI.py:608 +#: flatcamGUI/FlatCAMGUI.py:651 msgid "Mark Area\tAlt+A" msgstr "Bereich markieren\tAlt+A" -#: flatcamGUI/FlatCAMGUI.py:610 +#: flatcamGUI/FlatCAMGUI.py:653 msgid "Eraser\tCtrl+E" msgstr "Radiergummi\tSTRG+E" -#: flatcamGUI/FlatCAMGUI.py:612 +#: flatcamGUI/FlatCAMGUI.py:655 msgid "Transform\tAlt+R" msgstr "Transformationswerkzeug\tSTRG+R" -#: flatcamGUI/FlatCAMGUI.py:639 +#: flatcamGUI/FlatCAMGUI.py:682 msgid "Enable Plot" msgstr "Diagramm aktivieren" -#: flatcamGUI/FlatCAMGUI.py:641 +#: flatcamGUI/FlatCAMGUI.py:684 msgid "Disable Plot" msgstr "Diagramm deaktivieren" -#: flatcamGUI/FlatCAMGUI.py:645 +#: flatcamGUI/FlatCAMGUI.py:688 msgid "Set Color" msgstr "Farbsatz" -#: flatcamGUI/FlatCAMGUI.py:648 -msgid "Red" -msgstr "Rote" - -#: flatcamGUI/FlatCAMGUI.py:651 -msgid "Blue" -msgstr "Blau" - -#: flatcamGUI/FlatCAMGUI.py:654 -msgid "Yellow" -msgstr "Gelb" - -#: flatcamGUI/FlatCAMGUI.py:657 -msgid "Green" -msgstr "Grün" - -#: flatcamGUI/FlatCAMGUI.py:660 -msgid "Purple" -msgstr "Lila" - -#: flatcamGUI/FlatCAMGUI.py:663 -msgid "Brown" -msgstr "Braun" - -#: flatcamGUI/FlatCAMGUI.py:666 -msgid "Custom" -msgstr "Maßgeschn." - -#: flatcamGUI/FlatCAMGUI.py:671 +#: flatcamGUI/FlatCAMGUI.py:730 msgid "Generate CNC" msgstr "CNC generieren" -#: flatcamGUI/FlatCAMGUI.py:673 +#: flatcamGUI/FlatCAMGUI.py:732 msgid "View Source" msgstr "Quelltext anzeigen" -#: flatcamGUI/FlatCAMGUI.py:686 flatcamGUI/FlatCAMGUI.py:2172 -#: flatcamTools/ToolProperties.py:30 +#: flatcamGUI/FlatCAMGUI.py:737 flatcamGUI/FlatCAMGUI.py:851 +#: flatcamGUI/FlatCAMGUI.py:1060 flatcamGUI/FlatCAMGUI.py:2126 +#: flatcamGUI/FlatCAMGUI.py:2270 flatcamGUI/FlatCAMGUI.py:2535 +#: flatcamGUI/FlatCAMGUI.py:2738 flatcamGUI/ObjectUI.py:1617 +#: flatcamObjects/FlatCAMGeometry.py:477 flatcamTools/ToolPanelize.py:541 +#: flatcamTools/ToolPanelize.py:568 flatcamTools/ToolPanelize.py:667 +#: flatcamTools/ToolPanelize.py:701 flatcamTools/ToolPanelize.py:766 +msgid "Copy" +msgstr "Kopieren" + +#: flatcamGUI/FlatCAMGUI.py:745 flatcamGUI/FlatCAMGUI.py:2283 +#: flatcamTools/ToolProperties.py:31 msgid "Properties" msgstr "Eigenschaften" -#: flatcamGUI/FlatCAMGUI.py:715 +#: flatcamGUI/FlatCAMGUI.py:774 msgid "File Toolbar" msgstr "Dateisymbolleiste" -#: flatcamGUI/FlatCAMGUI.py:719 +#: flatcamGUI/FlatCAMGUI.py:778 msgid "Edit Toolbar" msgstr "Symbolleiste bearbeiten" -#: flatcamGUI/FlatCAMGUI.py:723 +#: flatcamGUI/FlatCAMGUI.py:782 msgid "View Toolbar" msgstr "Symbolleiste anzeigen" -#: flatcamGUI/FlatCAMGUI.py:727 +#: flatcamGUI/FlatCAMGUI.py:786 msgid "Shell Toolbar" msgstr "Shell-Symbolleiste" -#: flatcamGUI/FlatCAMGUI.py:731 +#: flatcamGUI/FlatCAMGUI.py:790 msgid "Tools Toolbar" msgstr "Werkzeugleiste" -#: flatcamGUI/FlatCAMGUI.py:735 +#: flatcamGUI/FlatCAMGUI.py:794 msgid "Excellon Editor Toolbar" msgstr "Excellon Editor-Symbolleiste" -#: flatcamGUI/FlatCAMGUI.py:741 +#: flatcamGUI/FlatCAMGUI.py:800 msgid "Geometry Editor Toolbar" msgstr "Geometrie Editor-Symbolleiste" -#: flatcamGUI/FlatCAMGUI.py:745 +#: flatcamGUI/FlatCAMGUI.py:804 msgid "Gerber Editor Toolbar" msgstr "Gerber Editor-Symbolleiste" -#: flatcamGUI/FlatCAMGUI.py:749 +#: flatcamGUI/FlatCAMGUI.py:808 msgid "Grid Toolbar" msgstr "Raster-Symbolleiste" -#: flatcamGUI/FlatCAMGUI.py:772 flatcamGUI/FlatCAMGUI.py:2357 +#: flatcamGUI/FlatCAMGUI.py:829 flatcamGUI/FlatCAMGUI.py:2512 msgid "Open project" msgstr "Projekt öffnen" -#: flatcamGUI/FlatCAMGUI.py:774 flatcamGUI/FlatCAMGUI.py:2359 +#: flatcamGUI/FlatCAMGUI.py:831 flatcamGUI/FlatCAMGUI.py:2514 msgid "Save project" msgstr "Projekt speichern" -#: flatcamGUI/FlatCAMGUI.py:780 flatcamGUI/FlatCAMGUI.py:2363 +#: flatcamGUI/FlatCAMGUI.py:837 flatcamGUI/FlatCAMGUI.py:2520 msgid "New Blank Geometry" msgstr "Neue Geometrie erstellen" -#: flatcamGUI/FlatCAMGUI.py:782 flatcamGUI/FlatCAMGUI.py:2365 +#: flatcamGUI/FlatCAMGUI.py:839 flatcamGUI/FlatCAMGUI.py:2522 msgid "New Blank Gerber" msgstr "Neues Gerber erstellen" -#: flatcamGUI/FlatCAMGUI.py:784 flatcamGUI/FlatCAMGUI.py:2367 +#: flatcamGUI/FlatCAMGUI.py:841 flatcamGUI/FlatCAMGUI.py:2524 msgid "New Blank Excellon" msgstr "Neuen Excellon erstellen" -#: flatcamGUI/FlatCAMGUI.py:789 flatcamGUI/FlatCAMGUI.py:2373 +#: flatcamGUI/FlatCAMGUI.py:846 flatcamGUI/FlatCAMGUI.py:2530 msgid "Save Object and close the Editor" msgstr "Speichern Sie das Objekt und schließen Sie den Editor" -#: flatcamGUI/FlatCAMGUI.py:796 flatcamGUI/FlatCAMGUI.py:2380 +#: flatcamGUI/FlatCAMGUI.py:853 flatcamGUI/FlatCAMGUI.py:2537 msgid "&Delete" msgstr "&Löschen" -#: flatcamGUI/FlatCAMGUI.py:799 flatcamGUI/FlatCAMGUI.py:1613 -#: flatcamGUI/FlatCAMGUI.py:1812 flatcamGUI/FlatCAMGUI.py:2383 -#: flatcamTools/ToolDistance.py:30 flatcamTools/ToolDistance.py:160 +#: flatcamGUI/FlatCAMGUI.py:856 flatcamGUI/FlatCAMGUI.py:1717 +#: flatcamGUI/FlatCAMGUI.py:1923 flatcamGUI/FlatCAMGUI.py:2540 +#: flatcamTools/ToolDistance.py:35 flatcamTools/ToolDistance.py:195 msgid "Distance Tool" msgstr "Entfernungswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:801 flatcamGUI/FlatCAMGUI.py:2385 +#: flatcamGUI/FlatCAMGUI.py:858 flatcamGUI/FlatCAMGUI.py:2542 msgid "Distance Min Tool" msgstr "Werkzeug für Mindestabstand" -#: flatcamGUI/FlatCAMGUI.py:803 flatcamGUI/FlatCAMGUI.py:1606 -#: flatcamGUI/FlatCAMGUI.py:2387 +#: flatcamGUI/FlatCAMGUI.py:860 flatcamGUI/FlatCAMGUI.py:1710 +#: flatcamGUI/FlatCAMGUI.py:2544 msgid "Set Origin" msgstr "Nullpunkt festlegen" -#: flatcamGUI/FlatCAMGUI.py:805 flatcamGUI/FlatCAMGUI.py:2389 +#: flatcamGUI/FlatCAMGUI.py:862 +msgid "Move to Origin" +msgstr "Zum Ursprung wechseln" + +#: flatcamGUI/FlatCAMGUI.py:865 flatcamGUI/FlatCAMGUI.py:2546 msgid "Jump to Location" msgstr "Zur Position springen\tJ" -#: flatcamGUI/FlatCAMGUI.py:811 flatcamGUI/FlatCAMGUI.py:2393 +#: flatcamGUI/FlatCAMGUI.py:867 flatcamGUI/FlatCAMGUI.py:1722 +#: flatcamGUI/FlatCAMGUI.py:2548 +msgid "Locate in Object" +msgstr "Suchen Sie im Objekt" + +#: flatcamGUI/FlatCAMGUI.py:873 flatcamGUI/FlatCAMGUI.py:2554 msgid "&Replot" msgstr "Neuzeichnen &R" -#: flatcamGUI/FlatCAMGUI.py:813 flatcamGUI/FlatCAMGUI.py:2395 +#: flatcamGUI/FlatCAMGUI.py:875 flatcamGUI/FlatCAMGUI.py:2556 msgid "&Clear plot" msgstr "Darstellung löschen &C" -#: flatcamGUI/FlatCAMGUI.py:815 flatcamGUI/FlatCAMGUI.py:1609 -#: flatcamGUI/FlatCAMGUI.py:2397 +#: flatcamGUI/FlatCAMGUI.py:877 flatcamGUI/FlatCAMGUI.py:1713 +#: flatcamGUI/FlatCAMGUI.py:2558 msgid "Zoom In" msgstr "Hineinzoomen" -#: flatcamGUI/FlatCAMGUI.py:817 flatcamGUI/FlatCAMGUI.py:1609 -#: flatcamGUI/FlatCAMGUI.py:2399 +#: flatcamGUI/FlatCAMGUI.py:879 flatcamGUI/FlatCAMGUI.py:1713 +#: flatcamGUI/FlatCAMGUI.py:2560 msgid "Zoom Out" msgstr "Rauszoomen" -#: flatcamGUI/FlatCAMGUI.py:819 flatcamGUI/FlatCAMGUI.py:1608 -#: flatcamGUI/FlatCAMGUI.py:2062 flatcamGUI/FlatCAMGUI.py:2401 +#: flatcamGUI/FlatCAMGUI.py:881 flatcamGUI/FlatCAMGUI.py:1712 +#: flatcamGUI/FlatCAMGUI.py:2173 flatcamGUI/FlatCAMGUI.py:2562 msgid "Zoom Fit" msgstr "Passend zoomen" -#: flatcamGUI/FlatCAMGUI.py:827 flatcamGUI/FlatCAMGUI.py:2407 +#: flatcamGUI/FlatCAMGUI.py:889 flatcamGUI/FlatCAMGUI.py:2568 msgid "&Command Line" msgstr "Befehlszeile" -#: flatcamGUI/FlatCAMGUI.py:839 flatcamGUI/FlatCAMGUI.py:2417 +#: flatcamGUI/FlatCAMGUI.py:901 flatcamGUI/FlatCAMGUI.py:2580 msgid "2Sided Tool" msgstr "2Seitiges Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:841 flatcamGUI/ObjectUI.py:588 -#: flatcamTools/ToolCutOut.py:436 +#: flatcamGUI/FlatCAMGUI.py:903 flatcamGUI/FlatCAMGUI.py:1728 +#: flatcamGUI/FlatCAMGUI.py:2582 +msgid "Align Objects Tool" +msgstr "Werkzeug \"Objekte ausrichten\"" + +#: flatcamGUI/FlatCAMGUI.py:905 flatcamGUI/FlatCAMGUI.py:1729 +#: flatcamGUI/FlatCAMGUI.py:2584 flatcamTools/ToolExtractDrills.py:393 +msgid "Extract Drills Tool" +msgstr "Bohrer Extrahieren Werkzeug" + +#: flatcamGUI/FlatCAMGUI.py:908 flatcamGUI/ObjectUI.py:596 +#: flatcamTools/ToolCutOut.py:446 msgid "Cutout Tool" msgstr "Ausschnittwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:843 flatcamGUI/FlatCAMGUI.py:2421 -#: flatcamGUI/ObjectUI.py:566 flatcamGUI/ObjectUI.py:1749 -#: flatcamTools/ToolNonCopperClear.py:632 +#: flatcamGUI/FlatCAMGUI.py:910 flatcamGUI/FlatCAMGUI.py:2589 +#: flatcamGUI/ObjectUI.py:574 flatcamGUI/ObjectUI.py:2077 +#: flatcamTools/ToolNCC.py:974 msgid "NCC Tool" msgstr "NCC Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:849 flatcamGUI/FlatCAMGUI.py:2427 +#: flatcamGUI/FlatCAMGUI.py:916 flatcamGUI/FlatCAMGUI.py:2595 msgid "Panel Tool" msgstr "Platte Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:851 flatcamGUI/FlatCAMGUI.py:2429 -#: flatcamTools/ToolFilm.py:578 +#: flatcamGUI/FlatCAMGUI.py:918 flatcamGUI/FlatCAMGUI.py:2597 +#: flatcamTools/ToolFilm.py:586 msgid "Film Tool" msgstr "Filmwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:853 flatcamGUI/FlatCAMGUI.py:2432 -#: flatcamTools/ToolSolderPaste.py:547 +#: flatcamGUI/FlatCAMGUI.py:920 flatcamGUI/FlatCAMGUI.py:2599 +#: flatcamTools/ToolSolderPaste.py:553 msgid "SolderPaste Tool" msgstr "Lötpaste-Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:855 flatcamGUI/FlatCAMGUI.py:2434 +#: flatcamGUI/FlatCAMGUI.py:922 flatcamGUI/FlatCAMGUI.py:2601 #: flatcamTools/ToolSub.py:35 msgid "Subtract Tool" msgstr "Subtraktionswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:857 flatcamTools/ToolRulesCheck.py:607 +#: flatcamGUI/FlatCAMGUI.py:924 flatcamGUI/FlatCAMGUI.py:2603 +#: flatcamTools/ToolRulesCheck.py:616 msgid "Rules Tool" msgstr "Regelwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:859 flatcamGUI/FlatCAMGUI.py:1624 -#: flatcamTools/ToolOptimal.py:34 flatcamTools/ToolOptimal.py:310 +#: flatcamGUI/FlatCAMGUI.py:926 flatcamGUI/FlatCAMGUI.py:1731 +#: flatcamGUI/FlatCAMGUI.py:2605 flatcamTools/ToolOptimal.py:33 +#: flatcamTools/ToolOptimal.py:307 msgid "Optimal Tool" msgstr "Optimierungswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:864 flatcamGUI/FlatCAMGUI.py:1622 -#: flatcamGUI/FlatCAMGUI.py:2439 +#: flatcamGUI/FlatCAMGUI.py:931 flatcamGUI/FlatCAMGUI.py:1728 +#: flatcamGUI/FlatCAMGUI.py:2610 msgid "Calculators Tool" msgstr "Rechnerwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:868 flatcamGUI/FlatCAMGUI.py:1625 -#: flatcamGUI/FlatCAMGUI.py:2443 flatcamTools/ToolQRCode.py:43 +#: flatcamGUI/FlatCAMGUI.py:935 flatcamGUI/FlatCAMGUI.py:1732 +#: flatcamGUI/FlatCAMGUI.py:2614 flatcamTools/ToolQRCode.py:43 #: flatcamTools/ToolQRCode.py:382 msgid "QRCode Tool" msgstr "QRCode Werkzeug" # Really don't know -#: flatcamGUI/FlatCAMGUI.py:870 flatcamGUI/FlatCAMGUI.py:2445 -#: flatcamTools/ToolCopperThieving.py:40 flatcamTools/ToolCopperThieving.py:566 +#: flatcamGUI/FlatCAMGUI.py:937 flatcamGUI/FlatCAMGUI.py:2616 +#: flatcamTools/ToolCopperThieving.py:39 flatcamTools/ToolCopperThieving.py:568 msgid "Copper Thieving Tool" msgstr "Copper Thieving Werkzeug" # Really don't know -#: flatcamGUI/FlatCAMGUI.py:873 flatcamGUI/FlatCAMGUI.py:1622 -#: flatcamGUI/FlatCAMGUI.py:2448 flatcamTools/ToolFiducials.py:33 -#: flatcamTools/ToolFiducials.py:393 +#: flatcamGUI/FlatCAMGUI.py:940 flatcamGUI/FlatCAMGUI.py:1729 +#: flatcamGUI/FlatCAMGUI.py:2619 flatcamTools/ToolFiducials.py:33 +#: flatcamTools/ToolFiducials.py:395 msgid "Fiducials Tool" msgstr "Passermarken-Tool" -#: flatcamGUI/FlatCAMGUI.py:875 flatcamGUI/FlatCAMGUI.py:2450 -#: flatcamTools/ToolCalibration.py:37 flatcamTools/ToolCalibration.py:762 +#: flatcamGUI/FlatCAMGUI.py:942 flatcamGUI/FlatCAMGUI.py:2621 +#: flatcamTools/ToolCalibration.py:37 flatcamTools/ToolCalibration.py:759 msgid "Calibration Tool" msgstr "Kalibierungswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:881 flatcamGUI/FlatCAMGUI.py:907 -#: flatcamGUI/FlatCAMGUI.py:959 flatcamGUI/FlatCAMGUI.py:2454 -#: flatcamGUI/FlatCAMGUI.py:2528 +#: flatcamGUI/FlatCAMGUI.py:944 flatcamGUI/FlatCAMGUI.py:1729 +#: flatcamGUI/FlatCAMGUI.py:2623 +msgid "Punch Gerber Tool" +msgstr "Stanzen Sie das Gerber-Werkzeug" + +#: flatcamGUI/FlatCAMGUI.py:946 flatcamGUI/FlatCAMGUI.py:2625 +#: flatcamTools/ToolInvertGerber.py:31 +msgid "Invert Gerber Tool" +msgstr "Invertieren Sie das Gerber-Werkzeug" + +#: flatcamGUI/FlatCAMGUI.py:952 flatcamGUI/FlatCAMGUI.py:978 +#: flatcamGUI/FlatCAMGUI.py:1030 flatcamGUI/FlatCAMGUI.py:2631 +#: flatcamGUI/FlatCAMGUI.py:2709 msgid "Select" msgstr "Wählen" -#: flatcamGUI/FlatCAMGUI.py:883 flatcamGUI/FlatCAMGUI.py:2456 +#: flatcamGUI/FlatCAMGUI.py:954 flatcamGUI/FlatCAMGUI.py:2633 msgid "Add Drill Hole" msgstr "Bohrloch hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:885 flatcamGUI/FlatCAMGUI.py:2458 +#: flatcamGUI/FlatCAMGUI.py:956 flatcamGUI/FlatCAMGUI.py:2635 msgid "Add Drill Hole Array" msgstr "Bohrlochfeld hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:887 flatcamGUI/FlatCAMGUI.py:1897 -#: flatcamGUI/FlatCAMGUI.py:2150 flatcamGUI/FlatCAMGUI.py:2462 +#: flatcamGUI/FlatCAMGUI.py:958 flatcamGUI/FlatCAMGUI.py:2008 +#: flatcamGUI/FlatCAMGUI.py:2261 flatcamGUI/FlatCAMGUI.py:2639 msgid "Add Slot" msgstr "Steckplatz hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:889 flatcamGUI/FlatCAMGUI.py:1896 -#: flatcamGUI/FlatCAMGUI.py:2152 flatcamGUI/FlatCAMGUI.py:2464 +#: flatcamGUI/FlatCAMGUI.py:960 flatcamGUI/FlatCAMGUI.py:2007 +#: flatcamGUI/FlatCAMGUI.py:2263 flatcamGUI/FlatCAMGUI.py:2641 msgid "Add Slot Array" msgstr "Steckplatz-Array hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:891 flatcamGUI/FlatCAMGUI.py:2155 -#: flatcamGUI/FlatCAMGUI.py:2460 +#: flatcamGUI/FlatCAMGUI.py:962 flatcamGUI/FlatCAMGUI.py:2266 +#: flatcamGUI/FlatCAMGUI.py:2637 msgid "Resize Drill" msgstr "Bohrergröße ändern" -#: flatcamGUI/FlatCAMGUI.py:895 flatcamGUI/FlatCAMGUI.py:2468 +#: flatcamGUI/FlatCAMGUI.py:966 flatcamGUI/FlatCAMGUI.py:2645 msgid "Copy Drill" msgstr "Bohrer kopieren" -#: flatcamGUI/FlatCAMGUI.py:897 flatcamGUI/FlatCAMGUI.py:2470 +#: flatcamGUI/FlatCAMGUI.py:968 flatcamGUI/FlatCAMGUI.py:2647 msgid "Delete Drill" msgstr "Bohrer löschen" -#: flatcamGUI/FlatCAMGUI.py:901 flatcamGUI/FlatCAMGUI.py:2474 +#: flatcamGUI/FlatCAMGUI.py:972 flatcamGUI/FlatCAMGUI.py:2651 msgid "Move Drill" msgstr "Bohrer bewegen" -#: flatcamGUI/FlatCAMGUI.py:909 flatcamGUI/FlatCAMGUI.py:2480 +#: flatcamGUI/FlatCAMGUI.py:980 flatcamGUI/FlatCAMGUI.py:2659 msgid "Add Circle" msgstr "Kreis hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:911 flatcamGUI/FlatCAMGUI.py:2482 +#: flatcamGUI/FlatCAMGUI.py:982 flatcamGUI/FlatCAMGUI.py:2661 msgid "Add Arc" msgstr "Bogen hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:913 flatcamGUI/FlatCAMGUI.py:2484 +#: flatcamGUI/FlatCAMGUI.py:984 flatcamGUI/FlatCAMGUI.py:2663 msgid "Add Rectangle" msgstr "Rechteck hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:917 flatcamGUI/FlatCAMGUI.py:2488 +#: flatcamGUI/FlatCAMGUI.py:988 flatcamGUI/FlatCAMGUI.py:2667 msgid "Add Path" msgstr "Pfad hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:919 flatcamGUI/FlatCAMGUI.py:2490 +#: flatcamGUI/FlatCAMGUI.py:990 flatcamGUI/FlatCAMGUI.py:2669 msgid "Add Polygon" msgstr "Polygon hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:922 flatcamGUI/FlatCAMGUI.py:2493 +#: flatcamGUI/FlatCAMGUI.py:993 flatcamGUI/FlatCAMGUI.py:2672 msgid "Add Text" msgstr "Text hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:924 flatcamGUI/FlatCAMGUI.py:2495 +#: flatcamGUI/FlatCAMGUI.py:995 flatcamGUI/FlatCAMGUI.py:2674 msgid "Add Buffer" msgstr "Puffer hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:926 flatcamGUI/FlatCAMGUI.py:2497 +#: flatcamGUI/FlatCAMGUI.py:997 flatcamGUI/FlatCAMGUI.py:2676 msgid "Paint Shape" msgstr "Malen Form" -#: flatcamGUI/FlatCAMGUI.py:928 flatcamGUI/FlatCAMGUI.py:985 -#: flatcamGUI/FlatCAMGUI.py:2091 flatcamGUI/FlatCAMGUI.py:2136 -#: flatcamGUI/FlatCAMGUI.py:2499 flatcamGUI/FlatCAMGUI.py:2553 +#: flatcamGUI/FlatCAMGUI.py:999 flatcamGUI/FlatCAMGUI.py:1056 +#: flatcamGUI/FlatCAMGUI.py:2202 flatcamGUI/FlatCAMGUI.py:2247 +#: flatcamGUI/FlatCAMGUI.py:2678 flatcamGUI/FlatCAMGUI.py:2734 msgid "Eraser" msgstr "Radiergummi" -#: flatcamGUI/FlatCAMGUI.py:932 flatcamGUI/FlatCAMGUI.py:2503 +#: flatcamGUI/FlatCAMGUI.py:1003 flatcamGUI/FlatCAMGUI.py:2682 msgid "Polygon Union" msgstr "Polygon-Vereinigung" -#: flatcamGUI/FlatCAMGUI.py:934 flatcamGUI/FlatCAMGUI.py:2505 +#: flatcamGUI/FlatCAMGUI.py:1005 flatcamGUI/FlatCAMGUI.py:2684 msgid "Polygon Explode" msgstr "Polygon explodieren" -#: flatcamGUI/FlatCAMGUI.py:937 flatcamGUI/FlatCAMGUI.py:2508 +#: flatcamGUI/FlatCAMGUI.py:1008 flatcamGUI/FlatCAMGUI.py:2687 msgid "Polygon Intersection" msgstr "Polygonschnitt" -#: flatcamGUI/FlatCAMGUI.py:939 flatcamGUI/FlatCAMGUI.py:2510 +#: flatcamGUI/FlatCAMGUI.py:1010 flatcamGUI/FlatCAMGUI.py:2689 msgid "Polygon Subtraction" msgstr "Polygon-Subtraktion" -#: flatcamGUI/FlatCAMGUI.py:943 flatcamGUI/FlatCAMGUI.py:2514 +#: flatcamGUI/FlatCAMGUI.py:1014 flatcamGUI/FlatCAMGUI.py:2693 msgid "Cut Path" msgstr "Pfad ausschneiden" -#: flatcamGUI/FlatCAMGUI.py:945 +#: flatcamGUI/FlatCAMGUI.py:1016 msgid "Copy Shape(s)" msgstr "Form kopieren" -#: flatcamGUI/FlatCAMGUI.py:948 +#: flatcamGUI/FlatCAMGUI.py:1019 msgid "Delete Shape '-'" msgstr "Form löschen" -#: flatcamGUI/FlatCAMGUI.py:950 flatcamGUI/FlatCAMGUI.py:993 -#: flatcamGUI/FlatCAMGUI.py:2103 flatcamGUI/FlatCAMGUI.py:2140 -#: flatcamGUI/FlatCAMGUI.py:2520 flatcamGUI/FlatCAMGUI.py:2561 +#: flatcamGUI/FlatCAMGUI.py:1021 flatcamGUI/FlatCAMGUI.py:1064 +#: flatcamGUI/FlatCAMGUI.py:2214 flatcamGUI/FlatCAMGUI.py:2251 +#: flatcamGUI/FlatCAMGUI.py:2699 flatcamGUI/FlatCAMGUI.py:2742 +#: flatcamGUI/ObjectUI.py:109 msgid "Transformations" msgstr "Transformationen" -#: flatcamGUI/FlatCAMGUI.py:953 +#: flatcamGUI/FlatCAMGUI.py:1024 msgid "Move Objects " msgstr "Objekte verschieben " -#: flatcamGUI/FlatCAMGUI.py:961 flatcamGUI/FlatCAMGUI.py:2016 -#: flatcamGUI/FlatCAMGUI.py:2530 +#: flatcamGUI/FlatCAMGUI.py:1032 flatcamGUI/FlatCAMGUI.py:2127 +#: flatcamGUI/FlatCAMGUI.py:2711 msgid "Add Pad" msgstr "Pad hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:965 flatcamGUI/FlatCAMGUI.py:2017 -#: flatcamGUI/FlatCAMGUI.py:2534 +#: flatcamGUI/FlatCAMGUI.py:1036 flatcamGUI/FlatCAMGUI.py:2128 +#: flatcamGUI/FlatCAMGUI.py:2715 msgid "Add Track" msgstr "Track hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:967 flatcamGUI/FlatCAMGUI.py:2016 -#: flatcamGUI/FlatCAMGUI.py:2536 +#: flatcamGUI/FlatCAMGUI.py:1038 flatcamGUI/FlatCAMGUI.py:2127 +#: flatcamGUI/FlatCAMGUI.py:2717 msgid "Add Region" msgstr "Region hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:969 flatcamGUI/FlatCAMGUI.py:2122 -#: flatcamGUI/FlatCAMGUI.py:2538 +#: flatcamGUI/FlatCAMGUI.py:1040 flatcamGUI/FlatCAMGUI.py:2233 +#: flatcamGUI/FlatCAMGUI.py:2719 msgid "Poligonize" msgstr "Polygonisieren" -#: flatcamGUI/FlatCAMGUI.py:972 flatcamGUI/FlatCAMGUI.py:2124 -#: flatcamGUI/FlatCAMGUI.py:2541 +#: flatcamGUI/FlatCAMGUI.py:1043 flatcamGUI/FlatCAMGUI.py:2235 +#: flatcamGUI/FlatCAMGUI.py:2722 msgid "SemiDisc" msgstr "Halbscheibe" -#: flatcamGUI/FlatCAMGUI.py:974 flatcamGUI/FlatCAMGUI.py:2126 -#: flatcamGUI/FlatCAMGUI.py:2543 +#: flatcamGUI/FlatCAMGUI.py:1045 flatcamGUI/FlatCAMGUI.py:2237 +#: flatcamGUI/FlatCAMGUI.py:2724 msgid "Disc" msgstr "Scheibe" -#: flatcamGUI/FlatCAMGUI.py:982 flatcamGUI/FlatCAMGUI.py:2134 -#: flatcamGUI/FlatCAMGUI.py:2551 +#: flatcamGUI/FlatCAMGUI.py:1053 flatcamGUI/FlatCAMGUI.py:2245 +#: flatcamGUI/FlatCAMGUI.py:2732 msgid "Mark Area" msgstr "Bereich markieren" -#: flatcamGUI/FlatCAMGUI.py:996 flatcamGUI/FlatCAMGUI.py:2016 -#: flatcamGUI/FlatCAMGUI.py:2107 flatcamGUI/FlatCAMGUI.py:2170 -#: flatcamGUI/FlatCAMGUI.py:2564 flatcamTools/ToolMove.py:28 +#: flatcamGUI/FlatCAMGUI.py:1067 flatcamGUI/FlatCAMGUI.py:2127 +#: flatcamGUI/FlatCAMGUI.py:2218 flatcamGUI/FlatCAMGUI.py:2281 +#: flatcamGUI/FlatCAMGUI.py:2745 flatcamTools/ToolMove.py:27 msgid "Move" msgstr "Bewegung" -#: flatcamGUI/FlatCAMGUI.py:1004 flatcamGUI/FlatCAMGUI.py:2571 +#: flatcamGUI/FlatCAMGUI.py:1075 flatcamGUI/FlatCAMGUI.py:2754 msgid "Snap to grid" msgstr "Am Raster ausrichten" -#: flatcamGUI/FlatCAMGUI.py:1007 flatcamGUI/FlatCAMGUI.py:2574 +#: flatcamGUI/FlatCAMGUI.py:1078 flatcamGUI/FlatCAMGUI.py:2757 msgid "Grid X snapping distance" msgstr "Raster X Fangdistanz" -#: flatcamGUI/FlatCAMGUI.py:1012 flatcamGUI/FlatCAMGUI.py:2579 +#: flatcamGUI/FlatCAMGUI.py:1083 flatcamGUI/FlatCAMGUI.py:2762 msgid "Grid Y snapping distance" msgstr "Raster Y Fangdistanz" -#: flatcamGUI/FlatCAMGUI.py:1018 flatcamGUI/FlatCAMGUI.py:2585 +#: flatcamGUI/FlatCAMGUI.py:1089 flatcamGUI/FlatCAMGUI.py:2768 msgid "" "When active, value on Grid_X\n" "is copied to the Grid_Y value." @@ -6354,63 +6240,64 @@ msgstr "" "Wenn aktiv, Wert auf Grid_X\n" "wird in den Wert von Grid_Y kopiert." -#: flatcamGUI/FlatCAMGUI.py:1025 flatcamGUI/FlatCAMGUI.py:2592 +#: flatcamGUI/FlatCAMGUI.py:1096 flatcamGUI/FlatCAMGUI.py:2775 msgid "Snap to corner" msgstr "In der Ecke ausrichten" -#: flatcamGUI/FlatCAMGUI.py:1029 flatcamGUI/FlatCAMGUI.py:2596 -#: flatcamGUI/PreferencesUI.py:984 +#: flatcamGUI/FlatCAMGUI.py:1100 flatcamGUI/FlatCAMGUI.py:2779 +#: flatcamGUI/PreferencesUI.py:2274 msgid "Max. magnet distance" msgstr "Max. Magnetabstand" -#: flatcamGUI/FlatCAMGUI.py:1063 +#: flatcamGUI/FlatCAMGUI.py:1137 msgid "Selected" msgstr "Ausgewählt" -#: flatcamGUI/FlatCAMGUI.py:1090 flatcamGUI/FlatCAMGUI.py:1098 +#: flatcamGUI/FlatCAMGUI.py:1165 flatcamGUI/FlatCAMGUI.py:1173 msgid "Plot Area" msgstr "Grundstücksfläche" -#: flatcamGUI/FlatCAMGUI.py:1125 +#: flatcamGUI/FlatCAMGUI.py:1200 msgid "General" msgstr "Allgemeines" -#: flatcamGUI/FlatCAMGUI.py:1140 flatcamTools/ToolCopperThieving.py:74 -#: flatcamTools/ToolDblSided.py:59 flatcamTools/ToolOptimal.py:71 -#: flatcamTools/ToolQRCode.py:77 +#: flatcamGUI/FlatCAMGUI.py:1215 flatcamTools/ToolCopperThieving.py:74 +#: flatcamTools/ToolDblSided.py:64 flatcamTools/ToolExtractDrills.py:61 +#: flatcamTools/ToolInvertGerber.py:72 flatcamTools/ToolOptimal.py:71 +#: flatcamTools/ToolPunchGerber.py:64 msgid "GERBER" msgstr "GERBER" -#: flatcamGUI/FlatCAMGUI.py:1150 flatcamTools/ToolDblSided.py:87 +#: flatcamGUI/FlatCAMGUI.py:1225 flatcamTools/ToolDblSided.py:92 msgid "EXCELLON" msgstr "EXCELLON" -#: flatcamGUI/FlatCAMGUI.py:1160 flatcamTools/ToolDblSided.py:115 +#: flatcamGUI/FlatCAMGUI.py:1235 flatcamTools/ToolDblSided.py:120 msgid "GEOMETRY" msgstr "GEOMETRY" -#: flatcamGUI/FlatCAMGUI.py:1170 +#: flatcamGUI/FlatCAMGUI.py:1245 msgid "CNC-JOB" msgstr "CNC-Auftrag" -#: flatcamGUI/FlatCAMGUI.py:1179 flatcamGUI/ObjectUI.py:555 -#: flatcamGUI/ObjectUI.py:1724 +#: flatcamGUI/FlatCAMGUI.py:1254 flatcamGUI/ObjectUI.py:563 +#: flatcamGUI/ObjectUI.py:2052 msgid "TOOLS" msgstr "WERKZEUGE" -#: flatcamGUI/FlatCAMGUI.py:1188 +#: flatcamGUI/FlatCAMGUI.py:1263 msgid "TOOLS 2" msgstr "WERKZEUGE 2" -#: flatcamGUI/FlatCAMGUI.py:1198 +#: flatcamGUI/FlatCAMGUI.py:1273 msgid "UTILITIES" msgstr "NUTZEN" -#: flatcamGUI/FlatCAMGUI.py:1215 flatcamGUI/PreferencesUI.py:2833 +#: flatcamGUI/FlatCAMGUI.py:1290 flatcamGUI/PreferencesUI.py:4132 msgid "Restore Defaults" msgstr "Standard wiederherstellen" -#: flatcamGUI/FlatCAMGUI.py:1218 +#: flatcamGUI/FlatCAMGUI.py:1293 msgid "" "Restore the entire set of default values\n" "to the initial values loaded after first launch." @@ -6418,16 +6305,20 @@ msgstr "" "Stellen Sie den gesamten Satz von Standardwerten wieder her\n" "auf die nach dem ersten Start geladenen Anfangswerte." -#: flatcamGUI/FlatCAMGUI.py:1223 +#: flatcamGUI/FlatCAMGUI.py:1298 msgid "Open Pref Folder" msgstr "Öffnen Sie den Einstellungsordner" -#: flatcamGUI/FlatCAMGUI.py:1226 +#: flatcamGUI/FlatCAMGUI.py:1301 msgid "Open the folder where FlatCAM save the preferences files." msgstr "" "Öffnen Sie den Ordner, in dem FlatCAM die Voreinstellungsdateien speichert." -#: flatcamGUI/FlatCAMGUI.py:1234 +#: flatcamGUI/FlatCAMGUI.py:1305 flatcamGUI/FlatCAMGUI.py:2480 +msgid "Clear GUI Settings" +msgstr "Löschen Sie die GUI-Einstellungen" + +#: flatcamGUI/FlatCAMGUI.py:1309 msgid "" "Clear the GUI settings for FlatCAM,\n" "such as: layout, gui state, style, hdpi support etc." @@ -6435,15 +6326,15 @@ msgstr "" "Löschen Sie die GUI-Einstellungen für FlatCAM.\n" "wie zum Beispiel: Layout, GUI-Status, Stil, HDPI-Unterstützung usw." -#: flatcamGUI/FlatCAMGUI.py:1245 +#: flatcamGUI/FlatCAMGUI.py:1320 msgid "Apply" msgstr "Anwenden" -#: flatcamGUI/FlatCAMGUI.py:1248 +#: flatcamGUI/FlatCAMGUI.py:1323 msgid "Apply the current preferences without saving to a file." msgstr "Anwenden ohne zu speichern." -#: flatcamGUI/FlatCAMGUI.py:1255 +#: flatcamGUI/FlatCAMGUI.py:1330 msgid "" "Save the current settings in the 'current_defaults' file\n" "which is the file storing the working default preferences." @@ -6451,568 +6342,606 @@ msgstr "" "Speichern Sie die aktuellen Einstellungen in der Datei 'current_defaults'\n" "Dies ist die Datei, in der die Arbeitseinstellungen gespeichert sind." -#: flatcamGUI/FlatCAMGUI.py:1263 +#: flatcamGUI/FlatCAMGUI.py:1338 msgid "Will not save the changes and will close the preferences window." msgstr "Einstellungen werden geschlossen ohne die Änderungen zu speichern." -#: flatcamGUI/FlatCAMGUI.py:1603 +#: flatcamGUI/FlatCAMGUI.py:1707 msgid "SHOW SHORTCUT LIST" msgstr "Verknüpfungsliste anzeigen" -#: flatcamGUI/FlatCAMGUI.py:1603 +#: flatcamGUI/FlatCAMGUI.py:1707 msgid "Switch to Project Tab" msgstr "Wechseln Sie zur Registerkarte Projekt" -#: flatcamGUI/FlatCAMGUI.py:1603 +#: flatcamGUI/FlatCAMGUI.py:1707 msgid "Switch to Selected Tab" msgstr "Wechseln Sie zur ausgewählten Registerkarte" -#: flatcamGUI/FlatCAMGUI.py:1604 +#: flatcamGUI/FlatCAMGUI.py:1708 msgid "Switch to Tool Tab" msgstr "Wechseln Sie zur Werkzeugregisterkarte" -#: flatcamGUI/FlatCAMGUI.py:1605 +#: flatcamGUI/FlatCAMGUI.py:1709 msgid "New Gerber" msgstr "Neuer Gerber" -#: flatcamGUI/FlatCAMGUI.py:1605 +#: flatcamGUI/FlatCAMGUI.py:1709 msgid "Edit Object (if selected)" msgstr "Objekt bearbeiten (falls ausgewählt)" -#: flatcamGUI/FlatCAMGUI.py:1605 +#: flatcamGUI/FlatCAMGUI.py:1709 msgid "Jump to Coordinates" msgstr "Springe zu den Koordinaten" -#: flatcamGUI/FlatCAMGUI.py:1606 +#: flatcamGUI/FlatCAMGUI.py:1710 msgid "New Excellon" msgstr "Neuer Excellon" -#: flatcamGUI/FlatCAMGUI.py:1606 +#: flatcamGUI/FlatCAMGUI.py:1710 msgid "Move Obj" msgstr "Objekt verschieben" -#: flatcamGUI/FlatCAMGUI.py:1606 +#: flatcamGUI/FlatCAMGUI.py:1710 msgid "New Geometry" msgstr "Neue Geometrie" -#: flatcamGUI/FlatCAMGUI.py:1606 +#: flatcamGUI/FlatCAMGUI.py:1710 msgid "Change Units" msgstr "Einheiten ändern" -#: flatcamGUI/FlatCAMGUI.py:1607 +#: flatcamGUI/FlatCAMGUI.py:1711 msgid "Open Properties Tool" msgstr "Öffnen Sie das Eigenschaften-Tool" -#: flatcamGUI/FlatCAMGUI.py:1607 +#: flatcamGUI/FlatCAMGUI.py:1711 msgid "Rotate by 90 degree CW" msgstr "Um 90 Grad im Uhrzeigersinn drehen" -#: flatcamGUI/FlatCAMGUI.py:1607 +#: flatcamGUI/FlatCAMGUI.py:1711 msgid "Shell Toggle" msgstr "Shell umschalten" -#: flatcamGUI/FlatCAMGUI.py:1608 +#: flatcamGUI/FlatCAMGUI.py:1712 msgid "" "Add a Tool (when in Geometry Selected Tab or in Tools NCC or Tools Paint)" msgstr "" "Hinzufügen eines Werkzeugs (auf der Registerkarte \"Geometrie ausgewählt\" " "oder unter \"Werkzeuge\", \"NCC\" oder \"Werkzeuge\", \"Malen\")" -#: flatcamGUI/FlatCAMGUI.py:1609 +#: flatcamGUI/FlatCAMGUI.py:1713 msgid "Flip on X_axis" msgstr "Auf X-Achse spiegeln" -#: flatcamGUI/FlatCAMGUI.py:1609 +#: flatcamGUI/FlatCAMGUI.py:1713 msgid "Flip on Y_axis" msgstr "Auf Y-Achse spiegeln" -#: flatcamGUI/FlatCAMGUI.py:1612 +#: flatcamGUI/FlatCAMGUI.py:1716 msgid "Copy Obj" msgstr "Objekt kopieren" -#: flatcamGUI/FlatCAMGUI.py:1612 +#: flatcamGUI/FlatCAMGUI.py:1716 msgid "Open Tools Database" msgstr "Werkzeugdatenbank öffnen" -#: flatcamGUI/FlatCAMGUI.py:1613 +#: flatcamGUI/FlatCAMGUI.py:1717 msgid "Open Excellon File" msgstr "Öffnen Sie die Excellon-Datei" -#: flatcamGUI/FlatCAMGUI.py:1613 +#: flatcamGUI/FlatCAMGUI.py:1717 msgid "Open Gerber File" msgstr "Öffnen Sie die Gerber-Datei" -#: flatcamGUI/FlatCAMGUI.py:1613 +#: flatcamGUI/FlatCAMGUI.py:1717 msgid "New Project" msgstr "Neues Projekt" -#: flatcamGUI/FlatCAMGUI.py:1614 flatcamTools/ToolPDF.py:42 +#: flatcamGUI/FlatCAMGUI.py:1718 flatcamTools/ToolPDF.py:42 msgid "PDF Import Tool" msgstr "PDF-Importwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1614 -msgid "Save Project As" -msgstr "Projekt speichern als" +#: flatcamGUI/FlatCAMGUI.py:1718 +msgid "Save Project" +msgstr "Projekt speichern" -#: flatcamGUI/FlatCAMGUI.py:1614 +#: flatcamGUI/FlatCAMGUI.py:1718 msgid "Toggle Plot Area" msgstr "Zeichenbereich umschalten0" -#: flatcamGUI/FlatCAMGUI.py:1617 +#: flatcamGUI/FlatCAMGUI.py:1721 msgid "Copy Obj_Name" msgstr "Kopieren Sie den Namen des Objekts" -#: flatcamGUI/FlatCAMGUI.py:1618 +#: flatcamGUI/FlatCAMGUI.py:1722 msgid "Toggle Code Editor" msgstr "Code-Editor umschalten" -#: flatcamGUI/FlatCAMGUI.py:1618 +#: flatcamGUI/FlatCAMGUI.py:1722 msgid "Toggle the axis" msgstr "Achse umschalten" -#: flatcamGUI/FlatCAMGUI.py:1618 flatcamGUI/FlatCAMGUI.py:1810 -#: flatcamGUI/FlatCAMGUI.py:1897 flatcamGUI/FlatCAMGUI.py:2019 +#: flatcamGUI/FlatCAMGUI.py:1722 flatcamGUI/FlatCAMGUI.py:1921 +#: flatcamGUI/FlatCAMGUI.py:2008 flatcamGUI/FlatCAMGUI.py:2130 msgid "Distance Minimum Tool" msgstr "Mindestabstand Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:1618 +#: flatcamGUI/FlatCAMGUI.py:1723 msgid "Open Preferences Window" msgstr "Öffnen Sie das Einstellungsfenster" -#: flatcamGUI/FlatCAMGUI.py:1619 +#: flatcamGUI/FlatCAMGUI.py:1724 msgid "Rotate by 90 degree CCW" msgstr "Um 90 Grad gegen den Uhrzeigersinn drehen" -#: flatcamGUI/FlatCAMGUI.py:1619 +#: flatcamGUI/FlatCAMGUI.py:1724 msgid "Run a Script" msgstr "Führen Sie ein Skript aus" -#: flatcamGUI/FlatCAMGUI.py:1619 +#: flatcamGUI/FlatCAMGUI.py:1724 msgid "Toggle the workspace" msgstr "Arbeitsbereich umschalten" -#: flatcamGUI/FlatCAMGUI.py:1619 +#: flatcamGUI/FlatCAMGUI.py:1724 msgid "Skew on X axis" msgstr "Neigung auf der X-Achse" -#: flatcamGUI/FlatCAMGUI.py:1620 +#: flatcamGUI/FlatCAMGUI.py:1725 msgid "Skew on Y axis" msgstr "Neigung auf der Y-Achse" -#: flatcamGUI/FlatCAMGUI.py:1622 +#: flatcamGUI/FlatCAMGUI.py:1728 msgid "2-Sided PCB Tool" msgstr "2-seitiges PCB Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:1622 +#: flatcamGUI/FlatCAMGUI.py:1728 msgid "Transformations Tool" msgstr "Transformations-Tool" -#: flatcamGUI/FlatCAMGUI.py:1623 +#: flatcamGUI/FlatCAMGUI.py:1730 msgid "Solder Paste Dispensing Tool" msgstr "Lotpasten-Dosierwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1624 +#: flatcamGUI/FlatCAMGUI.py:1731 msgid "Film PCB Tool" msgstr "Film PCB Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:1624 +#: flatcamGUI/FlatCAMGUI.py:1731 msgid "Non-Copper Clearing Tool" msgstr "Nicht-Kupfer-Räumwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1625 +#: flatcamGUI/FlatCAMGUI.py:1732 msgid "Paint Area Tool" msgstr "Malbereichswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1625 +#: flatcamGUI/FlatCAMGUI.py:1732 msgid "Rules Check Tool" msgstr "Regelprüfwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1626 +#: flatcamGUI/FlatCAMGUI.py:1733 msgid "View File Source" msgstr "Dateiquelle anzeigen" -#: flatcamGUI/FlatCAMGUI.py:1627 +#: flatcamGUI/FlatCAMGUI.py:1734 msgid "Cutout PCB Tool" msgstr "Ausschnitt PCB Tool" -#: flatcamGUI/FlatCAMGUI.py:1627 +#: flatcamGUI/FlatCAMGUI.py:1734 msgid "Enable all Plots" msgstr "Alle Zeichnungen aktivieren" -#: flatcamGUI/FlatCAMGUI.py:1627 +#: flatcamGUI/FlatCAMGUI.py:1734 msgid "Disable all Plots" msgstr "Alle Zeichnungen deaktivieren" -#: flatcamGUI/FlatCAMGUI.py:1627 +#: flatcamGUI/FlatCAMGUI.py:1734 msgid "Disable Non-selected Plots" msgstr "Nicht ausgewählte Zeichnungen deaktiv" -#: flatcamGUI/FlatCAMGUI.py:1628 +#: flatcamGUI/FlatCAMGUI.py:1735 msgid "Toggle Full Screen" msgstr "Vollbild umschalten" -#: flatcamGUI/FlatCAMGUI.py:1631 +#: flatcamGUI/FlatCAMGUI.py:1738 msgid "Abort current task (gracefully)" msgstr "Aktuelle Aufgabe abbrechen (ordnungsgemäß)" -#: flatcamGUI/FlatCAMGUI.py:1634 +#: flatcamGUI/FlatCAMGUI.py:1741 +msgid "Save Project As" +msgstr "Projekt speichern als" + +#: flatcamGUI/FlatCAMGUI.py:1742 +msgid "" +"Paste Special. Will convert a Windows path style to the one required in Tcl " +"Shell" +msgstr "" +"Paste Special. Konvertiert einen Windows-Pfadstil in den in Tcl Shell " +"erforderlichen" + +#: flatcamGUI/FlatCAMGUI.py:1745 msgid "Open Online Manual" msgstr "Online-Handbuch öffnen" -#: flatcamGUI/FlatCAMGUI.py:1635 +#: flatcamGUI/FlatCAMGUI.py:1746 msgid "Open Online Tutorials" msgstr "Öffnen Sie Online-Tutorials" -#: flatcamGUI/FlatCAMGUI.py:1635 +#: flatcamGUI/FlatCAMGUI.py:1746 msgid "Refresh Plots" msgstr "Zeichnungen aktualisieren" -#: flatcamGUI/FlatCAMGUI.py:1635 flatcamTools/ToolSolderPaste.py:503 +#: flatcamGUI/FlatCAMGUI.py:1746 flatcamTools/ToolSolderPaste.py:509 msgid "Delete Object" msgstr "Objekt löschen" -#: flatcamGUI/FlatCAMGUI.py:1635 +#: flatcamGUI/FlatCAMGUI.py:1746 msgid "Alternate: Delete Tool" msgstr "Alternative: Werkzeug löschen" -#: flatcamGUI/FlatCAMGUI.py:1636 -msgid "(left to Key_1)Toogle Notebook Area (Left Side)" +#: flatcamGUI/FlatCAMGUI.py:1747 +msgid "(left to Key_1)Toggle Notebook Area (Left Side)" msgstr "(links neben Taste_1) Notebook-Bereich umschalten (linke Seite)" -#: flatcamGUI/FlatCAMGUI.py:1636 +#: flatcamGUI/FlatCAMGUI.py:1747 msgid "En(Dis)able Obj Plot" msgstr "Objektzeichnung (de)aktivieren" -#: flatcamGUI/FlatCAMGUI.py:1637 +#: flatcamGUI/FlatCAMGUI.py:1748 msgid "Deselects all objects" msgstr "Hebt die Auswahl aller Objekte auf" -#: flatcamGUI/FlatCAMGUI.py:1651 +#: flatcamGUI/FlatCAMGUI.py:1762 msgid "Editor Shortcut list" msgstr "Editor-Verknüpfungsliste" -#: flatcamGUI/FlatCAMGUI.py:1805 +#: flatcamGUI/FlatCAMGUI.py:1916 msgid "GEOMETRY EDITOR" msgstr "GEOMETRIE-EDITOR" -#: flatcamGUI/FlatCAMGUI.py:1805 +#: flatcamGUI/FlatCAMGUI.py:1916 msgid "Draw an Arc" msgstr "Zeichnen Sie einen Bogen" -#: flatcamGUI/FlatCAMGUI.py:1805 +#: flatcamGUI/FlatCAMGUI.py:1916 msgid "Copy Geo Item" msgstr "Geo-Objekt kopieren" -#: flatcamGUI/FlatCAMGUI.py:1806 +#: flatcamGUI/FlatCAMGUI.py:1917 msgid "Within Add Arc will toogle the ARC direction: CW or CCW" msgstr "" "Innerhalb von Bogen hinzufügen wird die ARC-Richtung getippt: CW oder CCW" -#: flatcamGUI/FlatCAMGUI.py:1806 +#: flatcamGUI/FlatCAMGUI.py:1917 msgid "Polygon Intersection Tool" msgstr "Werkzeug Polygonschnitt" -#: flatcamGUI/FlatCAMGUI.py:1807 +#: flatcamGUI/FlatCAMGUI.py:1918 msgid "Geo Paint Tool" msgstr "Geo-Malwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1807 flatcamGUI/FlatCAMGUI.py:1896 -#: flatcamGUI/FlatCAMGUI.py:2016 +#: flatcamGUI/FlatCAMGUI.py:1918 flatcamGUI/FlatCAMGUI.py:2007 +#: flatcamGUI/FlatCAMGUI.py:2127 msgid "Jump to Location (x, y)" msgstr "Zum Standort springen (x, y)" -#: flatcamGUI/FlatCAMGUI.py:1807 +#: flatcamGUI/FlatCAMGUI.py:1918 msgid "Toggle Corner Snap" msgstr "Eckfang umschalten" -#: flatcamGUI/FlatCAMGUI.py:1807 +#: flatcamGUI/FlatCAMGUI.py:1918 msgid "Move Geo Item" msgstr "Geo-Objekt verschieben" -#: flatcamGUI/FlatCAMGUI.py:1808 +#: flatcamGUI/FlatCAMGUI.py:1919 msgid "Within Add Arc will cycle through the ARC modes" msgstr "Innerhalb von Bogen hinzufügen werden die ARC-Modi durchlaufen" -#: flatcamGUI/FlatCAMGUI.py:1808 +#: flatcamGUI/FlatCAMGUI.py:1919 msgid "Draw a Polygon" msgstr "Zeichnen Sie ein Polygon" -#: flatcamGUI/FlatCAMGUI.py:1808 +#: flatcamGUI/FlatCAMGUI.py:1919 msgid "Draw a Circle" msgstr "Zeichne einen Kreis" -#: flatcamGUI/FlatCAMGUI.py:1809 +#: flatcamGUI/FlatCAMGUI.py:1920 msgid "Draw a Path" msgstr "Zeichne einen Pfad" -#: flatcamGUI/FlatCAMGUI.py:1809 +#: flatcamGUI/FlatCAMGUI.py:1920 msgid "Draw Rectangle" msgstr "Rechteck zeichnen" -#: flatcamGUI/FlatCAMGUI.py:1809 +#: flatcamGUI/FlatCAMGUI.py:1920 msgid "Polygon Subtraction Tool" msgstr "Polygon-Subtraktionswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1809 +#: flatcamGUI/FlatCAMGUI.py:1920 msgid "Add Text Tool" msgstr "Textwerkzeug hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:1810 +#: flatcamGUI/FlatCAMGUI.py:1921 msgid "Polygon Union Tool" msgstr "Polygonverbindungswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1810 +#: flatcamGUI/FlatCAMGUI.py:1921 msgid "Flip shape on X axis" msgstr "Form auf der X-Achse spiegeln" -#: flatcamGUI/FlatCAMGUI.py:1810 +#: flatcamGUI/FlatCAMGUI.py:1921 msgid "Flip shape on Y axis" msgstr "Form auf der Y-Achse spiegeln" -#: flatcamGUI/FlatCAMGUI.py:1811 +#: flatcamGUI/FlatCAMGUI.py:1922 msgid "Skew shape on X axis" msgstr "Neigung auf der X-Achse" -#: flatcamGUI/FlatCAMGUI.py:1811 +#: flatcamGUI/FlatCAMGUI.py:1922 msgid "Skew shape on Y axis" msgstr "Neigung auf der Y-Achse" -#: flatcamGUI/FlatCAMGUI.py:1811 +#: flatcamGUI/FlatCAMGUI.py:1922 msgid "Editor Transformation Tool" msgstr "Editor-Transformationstool" -#: flatcamGUI/FlatCAMGUI.py:1812 +#: flatcamGUI/FlatCAMGUI.py:1923 msgid "Offset shape on X axis" msgstr "Versetzte Form auf der X-Achse" -#: flatcamGUI/FlatCAMGUI.py:1812 +#: flatcamGUI/FlatCAMGUI.py:1923 msgid "Offset shape on Y axis" msgstr "Versetzte Form auf der Y-Achse" -#: flatcamGUI/FlatCAMGUI.py:1813 flatcamGUI/FlatCAMGUI.py:1899 -#: flatcamGUI/FlatCAMGUI.py:2021 +#: flatcamGUI/FlatCAMGUI.py:1924 flatcamGUI/FlatCAMGUI.py:2010 +#: flatcamGUI/FlatCAMGUI.py:2132 msgid "Save Object and Exit Editor" msgstr "Objekt speichern und Editor beenden" -#: flatcamGUI/FlatCAMGUI.py:1813 +#: flatcamGUI/FlatCAMGUI.py:1924 msgid "Polygon Cut Tool" msgstr "Polygon-Schneidewerkzeug" -#: flatcamGUI/FlatCAMGUI.py:1814 +#: flatcamGUI/FlatCAMGUI.py:1925 msgid "Rotate Geometry" msgstr "Geometrie drehen" -#: flatcamGUI/FlatCAMGUI.py:1814 +#: flatcamGUI/FlatCAMGUI.py:1925 msgid "Finish drawing for certain tools" msgstr "Beenden Sie das Zeichnen für bestimmte Werkzeuge" -#: flatcamGUI/FlatCAMGUI.py:1814 flatcamGUI/FlatCAMGUI.py:1899 -#: flatcamGUI/FlatCAMGUI.py:2019 +#: flatcamGUI/FlatCAMGUI.py:1925 flatcamGUI/FlatCAMGUI.py:2010 +#: flatcamGUI/FlatCAMGUI.py:2130 msgid "Abort and return to Select" msgstr "Abbrechen und zurück zu Auswählen" -#: flatcamGUI/FlatCAMGUI.py:1815 flatcamGUI/FlatCAMGUI.py:2518 +#: flatcamGUI/FlatCAMGUI.py:1926 flatcamGUI/FlatCAMGUI.py:2697 msgid "Delete Shape" msgstr "Form löschen" -#: flatcamGUI/FlatCAMGUI.py:1895 +#: flatcamGUI/FlatCAMGUI.py:2006 msgid "EXCELLON EDITOR" msgstr "EXCELLON EDITOR" -#: flatcamGUI/FlatCAMGUI.py:1895 +#: flatcamGUI/FlatCAMGUI.py:2006 msgid "Copy Drill(s)" msgstr "Bohrer kopieren" -#: flatcamGUI/FlatCAMGUI.py:1895 flatcamGUI/FlatCAMGUI.py:2145 +#: flatcamGUI/FlatCAMGUI.py:2006 flatcamGUI/FlatCAMGUI.py:2256 msgid "Add Drill" msgstr "Bohrer hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:1896 +#: flatcamGUI/FlatCAMGUI.py:2007 msgid "Move Drill(s)" msgstr "Bohrer verschieben" -#: flatcamGUI/FlatCAMGUI.py:1897 +#: flatcamGUI/FlatCAMGUI.py:2008 msgid "Add a new Tool" msgstr "Fügen Sie ein neues Werkzeug hinzu" -#: flatcamGUI/FlatCAMGUI.py:1898 +#: flatcamGUI/FlatCAMGUI.py:2009 msgid "Delete Drill(s)" msgstr "Bohrer löschen" -#: flatcamGUI/FlatCAMGUI.py:1898 +#: flatcamGUI/FlatCAMGUI.py:2009 msgid "Alternate: Delete Tool(s)" msgstr "Alternative: Werkzeug (e) löschen" -#: flatcamGUI/FlatCAMGUI.py:2015 +#: flatcamGUI/FlatCAMGUI.py:2126 msgid "GERBER EDITOR" msgstr "GERBER EDITOR" -#: flatcamGUI/FlatCAMGUI.py:2015 +#: flatcamGUI/FlatCAMGUI.py:2126 msgid "Add Disc" msgstr "Fügen Sie eine Scheiben hinzu" -#: flatcamGUI/FlatCAMGUI.py:2015 +#: flatcamGUI/FlatCAMGUI.py:2126 msgid "Add SemiDisc" msgstr "Halbschibe hinzufügen" -#: flatcamGUI/FlatCAMGUI.py:2017 +#: flatcamGUI/FlatCAMGUI.py:2128 msgid "Within Track & Region Tools will cycle in REVERSE the bend modes" msgstr "" "Innerhalb von Track- und Region-Werkzeugen werden die Biegemodi umgekehrt" -#: flatcamGUI/FlatCAMGUI.py:2018 +#: flatcamGUI/FlatCAMGUI.py:2129 msgid "Within Track & Region Tools will cycle FORWARD the bend modes" msgstr "" "Innerhalb von Track und Region werden mit Tools die Biegemodi vorwärts " "durchlaufen" -#: flatcamGUI/FlatCAMGUI.py:2019 +#: flatcamGUI/FlatCAMGUI.py:2130 msgid "Alternate: Delete Apertures" msgstr "Alternative: Löschen Sie die Blenden" -#: flatcamGUI/FlatCAMGUI.py:2020 +#: flatcamGUI/FlatCAMGUI.py:2131 msgid "Eraser Tool" msgstr "Radiergummi" -#: flatcamGUI/FlatCAMGUI.py:2021 flatcamGUI/PreferencesUI.py:2634 +#: flatcamGUI/FlatCAMGUI.py:2132 flatcamGUI/PreferencesUI.py:3933 msgid "Mark Area Tool" msgstr "Bereich markieren Werkzeug" -#: flatcamGUI/FlatCAMGUI.py:2021 +#: flatcamGUI/FlatCAMGUI.py:2132 msgid "Poligonize Tool" msgstr "Werkzeug Polygonisieren" -#: flatcamGUI/FlatCAMGUI.py:2021 +#: flatcamGUI/FlatCAMGUI.py:2132 msgid "Transformation Tool" msgstr "Transformationswerkzeug" -#: flatcamGUI/FlatCAMGUI.py:2038 +#: flatcamGUI/FlatCAMGUI.py:2149 msgid "Toggle Visibility" msgstr "Sichtbarkeit umschalten" -#: flatcamGUI/FlatCAMGUI.py:2044 +#: flatcamGUI/FlatCAMGUI.py:2155 msgid "New" msgstr "Neu" -#: flatcamGUI/FlatCAMGUI.py:2046 flatcamTools/ToolCalibration.py:634 +#: flatcamGUI/FlatCAMGUI.py:2157 flatcamGUI/ObjectUI.py:450 +#: flatcamObjects/FlatCAMGerber.py:239 flatcamObjects/FlatCAMGerber.py:327 +#: flatcamTools/ToolCalibration.py:631 flatcamTools/ToolCalibration.py:648 +#: flatcamTools/ToolCalibration.py:815 flatcamTools/ToolCopperThieving.py:144 +#: flatcamTools/ToolCopperThieving.py:158 +#: flatcamTools/ToolCopperThieving.py:604 flatcamTools/ToolCutOut.py:92 +#: flatcamTools/ToolDblSided.py:226 flatcamTools/ToolFilm.py:69 +#: flatcamTools/ToolFilm.py:102 flatcamTools/ToolFilm.py:549 +#: flatcamTools/ToolFilm.py:557 flatcamTools/ToolImage.py:49 +#: flatcamTools/ToolImage.py:271 flatcamTools/ToolNCC.py:95 +#: flatcamTools/ToolNCC.py:558 flatcamTools/ToolNCC.py:1295 +#: flatcamTools/ToolPaint.py:502 flatcamTools/ToolPaint.py:706 +#: flatcamTools/ToolPanelize.py:116 flatcamTools/ToolPanelize.py:372 +#: flatcamTools/ToolPanelize.py:389 msgid "Geometry" msgstr "Geometrie" -#: flatcamGUI/FlatCAMGUI.py:2050 flatcamTools/ToolCalibration.py:197 -#: flatcamTools/ToolCalibration.py:634 flatcamTools/ToolFilm.py:359 +#: flatcamGUI/FlatCAMGUI.py:2161 flatcamGUI/PreferencesUI.py:9526 +#: flatcamTools/ToolAlignObjects.py:74 flatcamTools/ToolAlignObjects.py:110 +#: flatcamTools/ToolCalibration.py:197 flatcamTools/ToolCalibration.py:631 +#: flatcamTools/ToolCalibration.py:648 flatcamTools/ToolCalibration.py:807 +#: flatcamTools/ToolCalibration.py:815 flatcamTools/ToolCopperThieving.py:144 +#: flatcamTools/ToolCopperThieving.py:158 +#: flatcamTools/ToolCopperThieving.py:604 flatcamTools/ToolDblSided.py:225 +#: flatcamTools/ToolFilm.py:359 flatcamTools/ToolNCC.py:558 +#: flatcamTools/ToolNCC.py:1295 flatcamTools/ToolPaint.py:502 +#: flatcamTools/ToolPaint.py:706 flatcamTools/ToolPanelize.py:372 +#: flatcamTools/ToolPunchGerber.py:149 flatcamTools/ToolPunchGerber.py:164 msgid "Excellon" msgstr "Excellon" -#: flatcamGUI/FlatCAMGUI.py:2057 +#: flatcamGUI/FlatCAMGUI.py:2168 msgid "Grids" msgstr "Raster" -#: flatcamGUI/FlatCAMGUI.py:2064 +#: flatcamGUI/FlatCAMGUI.py:2175 msgid "Clear Plot" msgstr "Plot klar löschen" -#: flatcamGUI/FlatCAMGUI.py:2066 +#: flatcamGUI/FlatCAMGUI.py:2177 msgid "Replot" msgstr "Replotieren" -#: flatcamGUI/FlatCAMGUI.py:2070 +#: flatcamGUI/FlatCAMGUI.py:2181 msgid "Geo Editor" msgstr "Geo-Editor" -#: flatcamGUI/FlatCAMGUI.py:2072 +#: flatcamGUI/FlatCAMGUI.py:2183 msgid "Path" msgstr "Pfad" -#: flatcamGUI/FlatCAMGUI.py:2074 +#: flatcamGUI/FlatCAMGUI.py:2185 msgid "Rectangle" msgstr "Rechteck" -#: flatcamGUI/FlatCAMGUI.py:2077 +#: flatcamGUI/FlatCAMGUI.py:2188 msgid "Circle" msgstr "Kreis" -#: flatcamGUI/FlatCAMGUI.py:2079 -msgid "Polygon" -msgstr "Polygon" - -#: flatcamGUI/FlatCAMGUI.py:2081 +#: flatcamGUI/FlatCAMGUI.py:2192 msgid "Arc" msgstr "Bogen" -#: flatcamGUI/FlatCAMGUI.py:2095 +#: flatcamGUI/FlatCAMGUI.py:2206 msgid "Union" msgstr "Vereinigung" -#: flatcamGUI/FlatCAMGUI.py:2097 +#: flatcamGUI/FlatCAMGUI.py:2208 msgid "Intersection" msgstr "Überschneidung" -#: flatcamGUI/FlatCAMGUI.py:2099 +#: flatcamGUI/FlatCAMGUI.py:2210 msgid "Subtraction" msgstr "Subtraktion" -#: flatcamGUI/FlatCAMGUI.py:2101 flatcamGUI/ObjectUI.py:1811 -#: flatcamGUI/PreferencesUI.py:4421 +#: flatcamGUI/FlatCAMGUI.py:2212 flatcamGUI/ObjectUI.py:2141 +#: flatcamGUI/PreferencesUI.py:5831 msgid "Cut" msgstr "Schnitt" -#: flatcamGUI/FlatCAMGUI.py:2112 +#: flatcamGUI/FlatCAMGUI.py:2223 msgid "Pad" msgstr "Pad" -#: flatcamGUI/FlatCAMGUI.py:2114 +#: flatcamGUI/FlatCAMGUI.py:2225 msgid "Pad Array" msgstr "Pad-Array" -#: flatcamGUI/FlatCAMGUI.py:2118 +#: flatcamGUI/FlatCAMGUI.py:2229 msgid "Track" msgstr "Track" -#: flatcamGUI/FlatCAMGUI.py:2120 +#: flatcamGUI/FlatCAMGUI.py:2231 msgid "Region" msgstr "Region" -#: flatcamGUI/FlatCAMGUI.py:2143 +#: flatcamGUI/FlatCAMGUI.py:2254 msgid "Exc Editor" msgstr "Exc-Editor" -#: flatcamGUI/FlatCAMGUI.py:2188 +#: flatcamGUI/FlatCAMGUI.py:2299 msgid "" -"Relative neasurement.\n" +"Relative measurement.\n" "Reference is last click position" msgstr "" "Relative Messung\n" "Referenz ist Position des letzten Klicks" -#: flatcamGUI/FlatCAMGUI.py:2194 +#: flatcamGUI/FlatCAMGUI.py:2305 msgid "" -"Absolute neasurement.\n" +"Absolute measurement.\n" "Reference is (X=0, Y= 0) position" msgstr "" "Absolute Messung.\n" "Referenz ist (X = 0, Y = 0)" -#: flatcamGUI/FlatCAMGUI.py:2301 +#: flatcamGUI/FlatCAMGUI.py:2409 msgid "Lock Toolbars" msgstr "Symbolleisten sperren" -#: flatcamGUI/FlatCAMGUI.py:2419 +#: flatcamGUI/FlatCAMGUI.py:2468 +msgid "FlatCAM Preferences Folder opened." +msgstr "FlatCAM-Einstellungsordner geöffnet." + +#: flatcamGUI/FlatCAMGUI.py:2479 +msgid "Are you sure you want to delete the GUI Settings? \n" +msgstr "Möchten Sie die GUI-Einstellungen wirklich löschen?\n" + +#: flatcamGUI/FlatCAMGUI.py:2587 msgid "&Cutout Tool" msgstr "Ausschnittwerkzeug" -#: flatcamGUI/FlatCAMGUI.py:2478 +#: flatcamGUI/FlatCAMGUI.py:2657 msgid "Select 'Esc'" msgstr "Wählen" -#: flatcamGUI/FlatCAMGUI.py:2516 +#: flatcamGUI/FlatCAMGUI.py:2695 msgid "Copy Objects" msgstr "Objekte kopieren" -#: flatcamGUI/FlatCAMGUI.py:2524 +#: flatcamGUI/FlatCAMGUI.py:2703 msgid "Move Objects" msgstr "Objekte verschieben" -#: flatcamGUI/FlatCAMGUI.py:3087 +#: flatcamGUI/FlatCAMGUI.py:3319 msgid "" "Please first select a geometry item to be cutted\n" "then select the geometry item that will be cutted\n" @@ -7024,12 +6953,12 @@ msgstr "" "aus dem ersten Artikel. Zum Schluss drücken Sie die Taste ~ X ~ oder\n" "die Symbolleisten-Schaltfläche." -#: flatcamGUI/FlatCAMGUI.py:3094 flatcamGUI/FlatCAMGUI.py:3254 -#: flatcamGUI/FlatCAMGUI.py:3299 flatcamGUI/FlatCAMGUI.py:3319 +#: flatcamGUI/FlatCAMGUI.py:3326 flatcamGUI/FlatCAMGUI.py:3485 +#: flatcamGUI/FlatCAMGUI.py:3530 flatcamGUI/FlatCAMGUI.py:3550 msgid "Warning" msgstr "Warnung" -#: flatcamGUI/FlatCAMGUI.py:3249 +#: flatcamGUI/FlatCAMGUI.py:3480 msgid "" "Please select geometry items \n" "on which to perform Intersection Tool." @@ -7037,7 +6966,7 @@ msgstr "" "Bitte wählen Sie Geometrieelemente aus\n" "auf dem das Verschneidungswerkzeug ausgeführt werden soll." -#: flatcamGUI/FlatCAMGUI.py:3294 +#: flatcamGUI/FlatCAMGUI.py:3525 msgid "" "Please select geometry items \n" "on which to perform Substraction Tool." @@ -7045,7 +6974,7 @@ msgstr "" "Bitte wählen Sie Geometrieelemente aus\n" "auf dem das Subtraktionswerkzeug ausgeführt werden soll." -#: flatcamGUI/FlatCAMGUI.py:3314 +#: flatcamGUI/FlatCAMGUI.py:3545 msgid "" "Please select geometry items \n" "on which to perform union." @@ -7053,61 +6982,62 @@ msgstr "" "Bitte wählen Sie Geometrieelemente aus\n" "auf dem die Polygonverbindung ausgeführt werden soll." -#: flatcamGUI/FlatCAMGUI.py:3394 flatcamGUI/FlatCAMGUI.py:3608 +#: flatcamGUI/FlatCAMGUI.py:3624 flatcamGUI/FlatCAMGUI.py:3835 msgid "Cancelled. Nothing selected to delete." msgstr "Abgebrochen. Nichts zum Löschen ausgewählt." -#: flatcamGUI/FlatCAMGUI.py:3479 flatcamGUI/FlatCAMGUI.py:3726 +#: flatcamGUI/FlatCAMGUI.py:3708 flatcamGUI/FlatCAMGUI.py:3951 msgid "Cancelled. Nothing selected to copy." msgstr "Abgebrochen. Nichts zum Kopieren ausgewählt." -#: flatcamGUI/FlatCAMGUI.py:3526 flatcamGUI/FlatCAMGUI.py:3756 +#: flatcamGUI/FlatCAMGUI.py:3754 flatcamGUI/FlatCAMGUI.py:3980 msgid "Cancelled. Nothing selected to move." msgstr "Abgebrochen. Nichts ausgewählt, um sich zu bewegen." -#: flatcamGUI/FlatCAMGUI.py:3782 +#: flatcamGUI/FlatCAMGUI.py:4006 msgid "New Tool ..." msgstr "Neues Werkzeug ..." -#: flatcamGUI/FlatCAMGUI.py:3783 flatcamTools/ToolNonCopperClear.py:583 -#: flatcamTools/ToolPaint.py:494 flatcamTools/ToolSolderPaste.py:554 +#: flatcamGUI/FlatCAMGUI.py:4007 flatcamTools/ToolNCC.py:924 +#: flatcamTools/ToolPaint.py:850 flatcamTools/ToolSolderPaste.py:560 msgid "Enter a Tool Diameter" msgstr "Geben Sie einen Werkzeugdurchmesser ein" -#: flatcamGUI/FlatCAMGUI.py:3795 +#: flatcamGUI/FlatCAMGUI.py:4019 msgid "Adding Tool cancelled ..." msgstr "Tool wird hinzugefügt abgebrochen ..." -#: flatcamGUI/FlatCAMGUI.py:3808 +#: flatcamGUI/FlatCAMGUI.py:4032 msgid "Distance Tool exit..." msgstr "Entfernungstool beenden ..." -#: flatcamGUI/FlatCAMGUI.py:4018 flatcamGUI/FlatCAMGUI.py:4025 +#: flatcamGUI/FlatCAMGUI.py:4241 flatcamGUI/FlatCAMGUI.py:4248 msgid "Idle." msgstr "Untätig." -#: flatcamGUI/FlatCAMGUI.py:4056 +#: flatcamGUI/FlatCAMGUI.py:4279 msgid "Application started ..." msgstr "Bewerbung gestartet ..." -#: flatcamGUI/FlatCAMGUI.py:4057 +#: flatcamGUI/FlatCAMGUI.py:4280 msgid "Hello!" msgstr "Hello!" -#: flatcamGUI/FlatCAMGUI.py:4115 +#: flatcamGUI/FlatCAMGUI.py:4342 msgid "Open Project ..." msgstr "Offenes Projekt ..." -#: flatcamGUI/FlatCAMGUI.py:4141 +#: flatcamGUI/FlatCAMGUI.py:4368 msgid "Exit" msgstr "Ausgang" -#: flatcamGUI/GUIElements.py:2261 flatcamGUI/PreferencesUI.py:5265 -#: flatcamGUI/PreferencesUI.py:5825 flatcamTools/ToolFilm.py:219 +#: flatcamGUI/GUIElements.py:2513 flatcamGUI/PreferencesUI.py:7430 +#: flatcamTools/ToolDblSided.py:173 flatcamTools/ToolDblSided.py:388 +#: flatcamTools/ToolFilm.py:219 msgid "Reference" msgstr "Referenz" -#: flatcamGUI/GUIElements.py:2263 +#: flatcamGUI/GUIElements.py:2515 msgid "" "The reference can be:\n" "- Absolute -> the reference point is point (0,0)\n" @@ -7117,19 +7047,19 @@ msgstr "" "- Absolut -> Der Bezugspunkt ist Punkt (0,0)\n" "- Relativ -> Der Referenzpunkt ist die Mausposition vor dem Sprung" -#: flatcamGUI/GUIElements.py:2268 +#: flatcamGUI/GUIElements.py:2520 msgid "Abs" msgstr "Abs" -#: flatcamGUI/GUIElements.py:2269 +#: flatcamGUI/GUIElements.py:2521 msgid "Relative" msgstr "Relativ" -#: flatcamGUI/GUIElements.py:2279 +#: flatcamGUI/GUIElements.py:2531 msgid "Location" msgstr "Ort" -#: flatcamGUI/GUIElements.py:2281 +#: flatcamGUI/GUIElements.py:2533 msgid "" "The Location value is a tuple (x,y).\n" "If the reference is Absolute then the Jump will be at the position (x,y).\n" @@ -7143,11 +7073,19 @@ msgstr "" "(x, y)\n" "vom aktuellen Mausstandort aus." +#: flatcamGUI/GUIElements.py:2573 +msgid "Save Log" +msgstr "Protokoll speichern" + +#: flatcamGUI/GUIElements.py:2592 flatcamTools/ToolShell.py:278 +msgid "Type >help< to get started" +msgstr "Geben Sie> help