|
@ -7,6 +7,8 @@ from FlatCAMApp import App
|
|||
from flatcamGUI import VisPyPatches
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
# import copyreg
|
||||
# import types
|
||||
|
||||
if sys.platform == "win32":
|
||||
# cx_freeze 'module win32' workaround
|
||||
|
|
1911
FlatCAMApp.py
882
FlatCAMObj.py
|
@ -1,5 +1,5 @@
|
|||
from PyQt5 import QtCore
|
||||
from multiprocessing import Pool
|
||||
from multiprocessing import Pool, cpu_count
|
||||
import dill
|
||||
|
||||
|
||||
|
@ -23,7 +23,7 @@ class WorkerPool(QtCore.QObject):
|
|||
|
||||
def __init__(self):
|
||||
super(WorkerPool, self).__init__()
|
||||
self.pool = Pool(2)
|
||||
self.pool = Pool(cpu_count())
|
||||
|
||||
def add_task(self, task):
|
||||
print("adding task", task)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
|
@ -8,14 +7,15 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
from PyQt5.QtCore import QSettings
|
||||
|
||||
from flatcamGUI.GUIElements import log
|
||||
import gettext
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
# import builtins
|
||||
#
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# File modified by: Dennis Hayrullin #
|
||||
# ########################################################## ##
|
||||
# File modified by: Marius Stanciu #
|
||||
# ##########################################################
|
||||
|
||||
# from PyQt5.QtCore import QModelIndex
|
||||
from FlatCAMObj import *
|
||||
|
@ -186,21 +187,27 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
("gerber", "Gerber"),
|
||||
("excellon", "Excellon"),
|
||||
("geometry", "Geometry"),
|
||||
("cncjob", "CNC Job")
|
||||
("cncjob", "CNC Job"),
|
||||
("script", "Scripts"),
|
||||
("document", "Document"),
|
||||
]
|
||||
|
||||
classdict = {
|
||||
"gerber": FlatCAMGerber,
|
||||
"excellon": FlatCAMExcellon,
|
||||
"cncjob": FlatCAMCNCjob,
|
||||
"geometry": FlatCAMGeometry
|
||||
"geometry": FlatCAMGeometry,
|
||||
"script": FlatCAMScript,
|
||||
"document": FlatCAMDocument
|
||||
}
|
||||
|
||||
icon_files = {
|
||||
"gerber": "share/flatcam_icon16.png",
|
||||
"excellon": "share/drill16.png",
|
||||
"cncjob": "share/cnc16.png",
|
||||
"geometry": "share/geometry16.png"
|
||||
"geometry": "share/geometry16.png",
|
||||
"script": "share/script_new16.png",
|
||||
"document": "share/notes16_1.png"
|
||||
}
|
||||
|
||||
root_item = None
|
||||
|
@ -322,6 +329,14 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
self.app.ui.menuprojectedit.setVisible(False)
|
||||
if type(obj) != FlatCAMGerber and type(obj) != FlatCAMExcellon and type(obj) != FlatCAMCNCjob:
|
||||
self.app.ui.menuprojectviewsource.setVisible(False)
|
||||
if type(obj) != FlatCAMGerber and type(obj) != FlatCAMGeometry and type(obj) != FlatCAMExcellon and \
|
||||
type(obj) != FlatCAMCNCjob:
|
||||
# meaning for Scripts and for Document type of FlatCAM object
|
||||
self.app.ui.menuprojectenable.setVisible(False)
|
||||
self.app.ui.menuprojectdisable.setVisible(False)
|
||||
self.app.ui.menuprojectedit.setVisible(False)
|
||||
self.app.ui.menuprojectproperties.setVisible(False)
|
||||
self.app.ui.menuprojectgeneratecnc.setVisible(False)
|
||||
else:
|
||||
self.app.ui.menuprojectgeneratecnc.setVisible(False)
|
||||
|
||||
|
@ -411,17 +426,17 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
# rename the object
|
||||
obj.options["name"] = deepcopy(data)
|
||||
|
||||
self.app.object_status_changed.emit(obj, 'rename', 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 as e:
|
||||
log.debug(
|
||||
"setData() --> Could not remove the old object name from auto-completer model list. %s" %
|
||||
str(e))
|
||||
|
||||
# obj.build_ui()
|
||||
self.app.inform.emit(_("Object renamed from <b>{old}</b> to <b>{new}</b>").format(old=old_name,
|
||||
new=new_name))
|
||||
|
@ -490,7 +505,7 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
|
||||
self.app.should_we_save = True
|
||||
|
||||
self.app.object_status_changed.emit(obj, 'append')
|
||||
self.app.object_status_changed.emit(obj, 'append', name)
|
||||
|
||||
# decide if to show or hide the Notebook side of the screen
|
||||
if self.app.defaults["global_project_autohide"] is True:
|
||||
|
@ -547,7 +562,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()")
|
||||
# FlatCAMApp.App.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():
|
||||
|
@ -570,24 +585,33 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
# send signal with the object that is deleted
|
||||
# self.app.object_status_changed.emit(active.obj, 'delete')
|
||||
|
||||
# some objects add a Tab on creation, close it here
|
||||
for idx in range(self.app.ui.plot_tab_area.count()):
|
||||
if self.app.ui.plot_tab_area.widget(idx).objectName() == active.obj.options['name']:
|
||||
self.app.ui.plot_tab_area.removeTab(idx)
|
||||
break
|
||||
|
||||
# update the SHELL auto-completer model data
|
||||
name = active.obj.options['name']
|
||||
try:
|
||||
self.app.myKeywords.remove(name)
|
||||
self.app.shell._edit.set_model_data(self.app.myKeywords)
|
||||
self.app.ui.code_editor.set_model_data(self.app.myKeywords)
|
||||
# this is not needed any more because now the code editor is created on demand
|
||||
# self.app.ui.code_editor.set_model_data(self.app.myKeywords)
|
||||
except Exception as e:
|
||||
log.debug(
|
||||
"delete_active() --> Could not remove the old object name from auto-completer model list. %s" % str(e))
|
||||
|
||||
self.app.object_status_changed.emit(active.obj, 'delete', name)
|
||||
|
||||
# ############ OBJECT DELETION FROM MODEL STARTS HERE ####################
|
||||
self.beginRemoveRows(self.index(group.row(), 0, QtCore.QModelIndex()), active.row(), active.row())
|
||||
|
||||
group.remove_child(active)
|
||||
|
||||
# after deletion of object store the current list of objects into the self.app.all_objects_list
|
||||
self.app.all_objects_list = self.get_list()
|
||||
|
||||
self.endRemoveRows()
|
||||
# ############ OBJECT DELETION FROM MODEL STOPS HERE ####################
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
self.app.plotcanvas.redraw()
|
||||
|
||||
|
@ -605,6 +629,9 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
|
||||
def delete_all(self):
|
||||
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
|
||||
|
||||
self.app.object_status_changed.emit(None, 'delete_all', '')
|
||||
|
||||
try:
|
||||
self.app.all_objects_list.clear()
|
||||
|
||||
|
@ -688,6 +715,16 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
log.error("[ERROR] Cause: %s" % str(e))
|
||||
raise
|
||||
|
||||
def set_exclusive_active(self, name):
|
||||
"""
|
||||
Make the object with the name in parameters the only selected object
|
||||
|
||||
:param name: name of object to be selected and made the only active object
|
||||
:return: None
|
||||
"""
|
||||
self.set_all_inactive()
|
||||
self.set_active(name)
|
||||
|
||||
def set_inactive(self, name):
|
||||
"""
|
||||
Unselect object by name from the project list. This triggers the
|
||||
|
@ -736,7 +773,12 @@ class ObjectCollection(QtCore.QAbstractItemModel):
|
|||
elif obj.kind == 'geometry':
|
||||
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
|
||||
color='red', name=str(obj.options['name'])))
|
||||
|
||||
elif obj.kind == 'script':
|
||||
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
|
||||
color='orange', name=str(obj.options['name'])))
|
||||
elif obj.kind == 'document':
|
||||
self.app.inform.emit(_('[selected]<span style="color:{color};">{name}</span> selected').format(
|
||||
color='darkCyan', name=str(obj.options['name'])))
|
||||
except IndexError:
|
||||
# FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
|
||||
self.app.inform.emit('')
|
||||
|
|
195
README.md
|
@ -9,12 +9,205 @@ CAD program, and create G-Code for Isolation routing.
|
|||
|
||||
=================================================
|
||||
|
||||
14.10.2019
|
||||
|
||||
- modified the result highlight color in Check Rules Tool
|
||||
- added the Check Rules Tool parameters to the unit conversion list
|
||||
- converted more of the Preferences entries to FCDoubleSpinner and FCSpinner
|
||||
- converted all ObjectUI entries to FCDoubleSpinner and FCSpinner
|
||||
- updated the translation files (~ 89% translation level)
|
||||
- changed the splash screen as it seems that FlatCAM beta will never be more than beta
|
||||
- changed some of the signals from returnPressed to editingFinished due of now using the SpinBoxes
|
||||
- fixed an issue that caused the impossibility to load a GCode file that contained the % symbol even when was loaded in a regular way from the File menu
|
||||
- re-added the CNC tool diameter entry for the CNCjob object in Selected tab.FCSpinner
|
||||
- since the CNCjob geometry creation is only useful for graphical purposes and have no impact on the GCode creation I have removed the cascaded union on the GCode geometry therefore speeding up the Gcode display by many factors (perhaps hundreds of times faster)
|
||||
- added a secondary link in the bookmark manager
|
||||
- fixed the bookmark manager order of bookmark links; first two links are always protected from deletion or drag-and-drop to other positions
|
||||
- fixed a whole load of PyQT signal problems generated by recent changes to the usage of SpinBoxes; added a signal returnPressed for the FCSpinner and for FCDoubleSpinner
|
||||
- fixed issue in Paint Tool where the first added tool was expected to have a float diameter but it was a string
|
||||
- updated the translation files to the latest state in the app
|
||||
|
||||
13.10.2019
|
||||
|
||||
- fixed a bug in the Merge functions
|
||||
- fixed the Export PNG function when using the 2D legacy graphic engine
|
||||
- added a new capability to toggle the grid lines for both graphic engines: menu link in View and key shortcut combo ALT+G
|
||||
- changed the grid colors for 3D graphic engine when in Dark mode
|
||||
- enhanced the Tool Film adding the Film adjustments and added the GUI in Preferences
|
||||
- set the GUI layout in Preferences for a new category named Tools 2
|
||||
- added the Preferences for Check Rules Tool and for Optimal Tool and also updated the Film Tool to use the default settings in Preferences
|
||||
|
||||
12.10.2019
|
||||
|
||||
- fixed the Gerber Parser convert units unnecessary usage. The only units conversion should be done when creating the new object, after the parsing
|
||||
- more fixes in Rules Check Tool
|
||||
- optimized the Move Tool
|
||||
- added support for key-based panning in 3D graphic engine. Moving the mouse wheel while pressing the CTRL key will pan up-down and while pressing SHIFT key will pan left-right
|
||||
- fixed a bug in NCC Tool and start trying to make the App responsive while the NCC tool is run in a non-threaded way
|
||||
- fixed a GUI bug with the QMenuBar recently introduced
|
||||
|
||||
11.10.2019
|
||||
|
||||
- added a Bookmark Manager and a Bookmark menu in the Help Menu
|
||||
- added an initial support for rows drag and drop in FCTable in GUIElements; it crashes for CellWidgets for now, if CellWidgetsare in the table rows
|
||||
- fixed some issues in the Bookmark Manager
|
||||
- modified the Bookmark manager to be installed as a widget tab in Plot Area; fixed the drag & drop function for the table rows that have CellWidgets inside
|
||||
- marked in gray color the rows in the Bookmark Manager table that will populate the BookMark menu
|
||||
- made sure that only one instance of the BookmarkManager class is active at one time
|
||||
|
||||
10.10.2019
|
||||
|
||||
- fixed Tool Move to work only for objects that are selected but also plotted, therefore disabled objects will not be moved even if selected
|
||||
|
||||
9.10.2019
|
||||
|
||||
- updated the Rules Check Tool - solved some issues
|
||||
- made FCDoubleSpinner to use either comma or dot as a decimal separator
|
||||
- fixed the FCDoubleSpinner to only allow the amount of decimals already set with set_precision()
|
||||
- fixed ToolPanelize to use FCDoubleSpinner in some places
|
||||
|
||||
8.10.2019
|
||||
|
||||
- modified the FCSpinner and FCDoubleSpinner GUI elements such that the wheel event will not change the values inside unless there is a focus in the lineedit of the SpinBox
|
||||
- in Preferences General, Gerber, Geometry, Excellon, CNCJob sections made all the input fields of type SpinBox (where possible)
|
||||
- updated the Distance Tool utility geometry color to adapt to the dark theme canvas
|
||||
- Toggle Code Editor now works as expected even when the user is closing the Editor tab and not using the command Toggle Code Editor
|
||||
- more changes in Preferences GUI, replacing the FCEntries with Spinners
|
||||
- some small fixes in toggle units conversion
|
||||
- small GUI changes
|
||||
|
||||
7.10.2019
|
||||
|
||||
- fixed an conflict in a signal usage that was triggered by Tool SolderPaste when a new project was created
|
||||
- updated Optimal Tool to display both points coordinates that made a distance (and the minimum) not only the middle point (which is still the place where the jump happen)
|
||||
- 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
|
||||
- added inverted icons
|
||||
|
||||
6.10.2019
|
||||
|
||||
- remade the Mark area Tool in Gerber Editor to be able to clear the markings and also to delete the marked polygons (Gerber apertures)
|
||||
- working in adding to the Optimal Tool the rest of the distances found in the Gerber and the locations associated; added GUI
|
||||
- added display of the results for the Rules Check Tool in a formatted way
|
||||
- made the Rules Check Tool document window Read Only
|
||||
- made Excellon and Gerber classes from camlib into their own files in the flatcamParser folder
|
||||
- moved the ApertureMacro class from camlib to ParseGerber file
|
||||
- moved back the ApertureMacro class to camlib for now and made some import changes in the new ParseGerber and ParseExcellon classes
|
||||
- some changes to the tests - perhaps I will try adding a few tests in the future
|
||||
- changed the Jump To icon and reverted some changes to the parseGerber and ParseExcellon classes
|
||||
- updated Tool Optimal with display of all distances (and locations of the middle point between where they happen) found in the Gerber Object
|
||||
|
||||
5.10.2019
|
||||
|
||||
- remade the Tool Calculators to use the QSpinBox in order to simplify the user interaction and remove possible errors
|
||||
- remade: Tool Cutout, Tool 2Sided, Tool Image, Panelize Tool, NCC Tool, Paint Tool to use the QSpinBox GUI elements
|
||||
- optimized the Transformation Tool both in GUI and in functionality and replaced the entries with QSpinBox
|
||||
- fixed an issue with the tool table context menu in Paint Tool
|
||||
- made some changes in the GUI in Paint Tool, NCC Tool and SolderPaste Tool
|
||||
- changed some of the icons; added attributions for icons source in the About FlatCAM window
|
||||
- added a new tool in the Geometry Editor named Explode which is the opposite of Union Tool: it will explode the polygons into lines
|
||||
|
||||
4.10.2019
|
||||
|
||||
- updated the Film Tool and added the ability to generate Punched Positive films (holes in the pads) when a Gerber file is the film's source. The punch holes source can be either an Excellon file or the pads center
|
||||
- optimized Rules Check Tool so it runs faster when doing Copper 2 Copper rule
|
||||
- small GUI changes in Optimal Tool and in Film Tool
|
||||
- some PEP8 corrections
|
||||
- some code annotations to make it easier to navigate in the FlatCAMGUI.py
|
||||
- fixed exit FullScreen with Escape key
|
||||
- added a new menu category in the MenuBar named 'Objects'. It will hold the objects found in the Project tab. Useful when working in FullScreen
|
||||
- disabled a log.debug in ObjectColection.get_by_name()
|
||||
- added a Toggle Notebook button named 'NB' in the QMenBar which toggle the notebook
|
||||
- in Gerber isolation section, the tool dia value is updated when changing from Circular to V-shape and reverse
|
||||
- in Tool Film, when punching holes in a positive film, if the resulting object geometry is the same as the source object geometry, the film will not ge generated
|
||||
- fixed a bug that when a Gerber object is edited and it has as solid_geometry a single Polygon, saving the result was failing due of len() function not working on a single Polygon
|
||||
- added the Distance Tool, Distance Min Tool, Jump To and Set Origin functions to the Edit Toolbar
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
|
||||
1.10.2019
|
||||
|
||||
- fixed the FCSpinner and FCDoubleSpinner GUI elements to select all on first click and deselect on second click in the Spinbox LineEdit
|
||||
- for Gerber object in Selected Tab added ability to chose a V-Shape tool and therefore control the isolation better by adjusting the cut width of the isolation in function of the cut depth, tip width of the tool and the tip angle of the tool
|
||||
- when in Gerber UI is selected the V-Shape tool, all those parameters (tip dia, tip angle, tool_type = 'V' and cut Z) are transferred to the generated Geometry and prefilled in the Geoemtry UI
|
||||
- added a fix in the Gerber parser to work even when there is no information about zero suppression in the Gerber file
|
||||
- added new settings in Edit -> Preferences -> Gerber for Gerber Units and Gerber Zeros to be used as defaults in case that those informations are missing from the Gerber file
|
||||
- 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
|
||||
|
||||
30.09.2019
|
||||
|
||||
- modified the Distance Tool such that the number of decimals all over the tool is set in one place by the self.decimals
|
||||
- added a new tool named Minimum Distance Tool who will calculate the minimum distance between two objects; key shortcut: SHIFT + M
|
||||
- finished the Minimum Distance Tool in case of using it at the object level (not in Editors)
|
||||
- completed the Minimum Distance Tool by adding the usage in Editors
|
||||
- made the Minimum Distance Tool more precise for the Excellon Editor since in the Excellon Editor the holes shape are represented as a cross line but in reality they should be evaluated as circles
|
||||
- small change in the UI layout for Check Rules Tool by adding a new rule (Check trace size)
|
||||
- changed a tooltip in Optimal Tool
|
||||
- in Optimal Tool added display of how frequent that minimum distance is found
|
||||
- in Tool Distance and Tool Minimal Distance made the entry fields read-only
|
||||
- in Optimal Tool added the display of the locations where the minimum distance was detected
|
||||
- added support to use Multi Processing (multi core usage, not simple threading) in Rules Check Tool
|
||||
- in Rules Check Tool added the functionality for the following rules: Hole Size, Trace Size, Hole to Hole Clearance
|
||||
- in Rules Check Tool added the functionality for Copper to Copper Clearance
|
||||
- in Rules Check Tool added the functionality for Copper to Outline Clearance, Silk to Silk Clearance, Silk to Solder Mask Clearance, Silk to Outline Clearance, Minimum Solder Mask Sliver, Minimum Annular Ring
|
||||
- fixes to cover all possible situations for the Minimum Annular Ring Rule in Rules Check Tool
|
||||
- some fixes in Rules Check Tool and added a QSignal that is fired at the end of the job
|
||||
|
||||
29.09.2019
|
||||
|
||||
- work done for the GUI layout of the Rule Check Tool
|
||||
- setup signals in the Rules Check Tool GUI
|
||||
- changed the name of the Measurement Tool to Distance Tool. Moved it's location to the Edit Menu
|
||||
- added Angle parameter which is continuously updated to the Distance Tool
|
||||
|
||||
28.09.2019
|
||||
|
||||
- changed the icon for Open Script and reused it for the Check Rules Tool
|
||||
- added a new tool named "Optimal Tool" which will determine the minimum distance between the copper features for a Gerber object, in fact determining the maximum diameter for a isolation tool that can be used for a complete isolation
|
||||
- fixed the ToolMeasurement geometry not being displayed
|
||||
- fixed a bug in Excellon Editor that crashed the app when editing the first tool added automatically into a new black Excellon file
|
||||
- made sure that if the big mouse cursor is selected, the utility geometry in Excellon Editor has a thicker line width (2 pixels now) so it is visible over the geometry of the mouse cursor
|
||||
- fixed issue #319 where generating a CNCJob from a geometry made with NCC Tool made the app crash
|
||||
- replaced in FlatCAM Tools and in FLatCAMObj.py and in Editors all references to hardcoded decimals in string formats for tools with a variable declared in the __init__()
|
||||
- fixed a small bug that made app crash when the splash screen is disabled: it was trying to close it without being open
|
||||
|
||||
27.09.2019
|
||||
|
||||
- optimized the toggle axis command
|
||||
- added posibility of using a big mouse cursor or a small mouse cursor. The big mouse cursor is made from 2 infinite lines. This was implemented for both graphic engines
|
||||
- added possibility of using a big mouse cursor or a small mouse cursor. The big mouse cursor is made from 2 infinite lines. This was implemented for both graphic engines
|
||||
- added ability to change the cursor size when the small mouse cursor is selected in Preferences -> General
|
||||
- removed the line that remove the spaces from the path parameter in the Tcl commands that open something (Gerber, Gcode, Excellon)
|
||||
- fixed issue with the old SysTray icon not hidden when the application is restarted programmatically
|
||||
- if an object is edited but the result is not saved, the app will reload the edited object UI and set the Selected tab as active
|
||||
- made the mouse cursor (big, small) change in real time for both graphic engines
|
||||
- started to work on a new FlatCAM tool: Rules Check
|
||||
- created the GUI for the Rule Check Tool
|
||||
- if there are (x, y) coordinates in the clipboard, when launching the "Jump to" function, those coordinates will be preloaded in the Dialog box.
|
||||
- when the combo SHIFT + LMB is executed there is no longer a deselection of objects
|
||||
- when the "Jump to" function is called, the mouse cursor (if active) will be moved to the new position and the screen position labels will be updated accordingly
|
||||
|
||||
|
||||
27.09.2019
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 8/17/2019 #
|
||||
# MIT Licence #
|
||||
|
@ -19,6 +18,7 @@ from rtree import index as rtindex
|
|||
from camlib import *
|
||||
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, SpinBoxDelegate
|
||||
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
|
||||
from flatcamParsers.ParseExcellon import Excellon
|
||||
|
||||
from copy import copy, deepcopy
|
||||
|
||||
|
@ -1984,7 +1984,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
self.app.ui.delete_drill_btn.triggered.connect(self.on_delete_btn)
|
||||
self.name_entry.returnPressed.connect(self.on_name_activate)
|
||||
self.addtool_btn.clicked.connect(self.on_tool_add)
|
||||
self.addtool_entry.returnPressed.connect(self.on_tool_add)
|
||||
self.addtool_entry.editingFinished.connect(self.on_tool_add)
|
||||
self.deltool_btn.clicked.connect(self.on_tool_delete)
|
||||
# self.tools_table_exc.selectionModel().currentChanged.connect(self.on_row_selected)
|
||||
self.tools_table_exc.cellPressed.connect(self.on_row_selected)
|
||||
|
@ -2014,7 +2014,10 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
# VisPy Visuals
|
||||
if self.app.is_legacy is False:
|
||||
self.shapes = self.app.plotcanvas.new_shape_collection(layers=1)
|
||||
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
|
||||
if self.app.plotcanvas.big_cursor is True:
|
||||
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1, line_width=2)
|
||||
else:
|
||||
self.tool_shape = self.app.plotcanvas.new_shape_collection(layers=1)
|
||||
else:
|
||||
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
self.shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='shapes_exc_editor')
|
||||
|
@ -2043,6 +2046,9 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
|
||||
self.complete = False
|
||||
|
||||
# Number of decimals used by tools in this class
|
||||
self.decimals = 4
|
||||
|
||||
def make_callback(thetool):
|
||||
def f():
|
||||
self.on_tool_select(thetool)
|
||||
|
@ -2113,16 +2119,18 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
# updated units
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
if self.units == "IN":
|
||||
self.decimals = 4
|
||||
else:
|
||||
self.decimals = 2
|
||||
|
||||
self.olddia_newdia.clear()
|
||||
self.tool2tooldia.clear()
|
||||
|
||||
# build the self.points_edit dict {dimaters: [point_list]}
|
||||
for drill in self.exc_obj.drills:
|
||||
if drill['tool'] in self.exc_obj.tools:
|
||||
if self.units == 'IN':
|
||||
tool_dia = float('%.4f' % self.exc_obj.tools[drill['tool']]['C'])
|
||||
else:
|
||||
tool_dia = float('%.2f' % self.exc_obj.tools[drill['tool']]['C'])
|
||||
tool_dia = float('%.*f' % (self.decimals, self.exc_obj.tools[drill['tool']]['C']))
|
||||
|
||||
try:
|
||||
self.points_edit[tool_dia].append(drill['point'])
|
||||
|
@ -2132,10 +2140,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
# build the self.slot_points_edit dict {dimaters: {"start": Point, "stop": Point}}
|
||||
for slot in self.exc_obj.slots:
|
||||
if slot['tool'] in self.exc_obj.tools:
|
||||
if self.units == 'IN':
|
||||
tool_dia = float('%.4f' % self.exc_obj.tools[slot['tool']]['C'])
|
||||
else:
|
||||
tool_dia = float('%.2f' % self.exc_obj.tools[slot['tool']]['C'])
|
||||
tool_dia = float('%.*f' % (self.decimals, self.exc_obj.tools[slot['tool']]['C']))
|
||||
|
||||
try:
|
||||
self.slot_points_edit[tool_dia].append({
|
||||
|
@ -2171,10 +2176,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
# Excellon file has no tool diameter information. In this case do not order the diameter in the table
|
||||
# but use the real order found in the exc_obj.tools
|
||||
for k, v in self.exc_obj.tools.items():
|
||||
if self.units == 'IN':
|
||||
tool_dia = float('%.4f' % v['C'])
|
||||
else:
|
||||
tool_dia = float('%.2f' % v['C'])
|
||||
tool_dia = float('%.*f' % (self.decimals, v['C']))
|
||||
self.tool2tooldia[int(k)] = tool_dia
|
||||
|
||||
# Init GUI
|
||||
|
@ -2271,12 +2273,9 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
self.tools_table_exc.setItem(self.tool_row, 0, idd) # Tool name/id
|
||||
|
||||
# Make sure that the drill diameter when in MM is with no more than 2 decimals
|
||||
# There are no drill bits in MM with more than 3 decimals diameter
|
||||
# For INCH the decimals should be no more than 3. There are no drills under 10mils
|
||||
if self.units == 'MM':
|
||||
dia = QtWidgets.QTableWidgetItem('%.2f' % self.olddia_newdia[tool_no])
|
||||
else:
|
||||
dia = QtWidgets.QTableWidgetItem('%.4f' % self.olddia_newdia[tool_no])
|
||||
# There are no drill bits in MM with more than 2 decimals diameter
|
||||
# For INCH the decimals should be no more than 4. There are no drills under 10mils
|
||||
dia = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, self.olddia_newdia[tool_no]))
|
||||
|
||||
dia.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
|
@ -2474,9 +2473,9 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
else:
|
||||
if isinstance(dia, list):
|
||||
for dd in dia:
|
||||
deleted_tool_dia_list.append(float('%.4f' % dd))
|
||||
deleted_tool_dia_list.append(float('%.*f' % (self.decimals, dd)))
|
||||
else:
|
||||
deleted_tool_dia_list.append(float('%.4f' % dia))
|
||||
deleted_tool_dia_list.append(float('%.*f' % (self.decimals, dia)))
|
||||
except Exception as e:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Select a tool in Tool Table"))
|
||||
|
@ -2814,7 +2813,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_mouse_double_click_over_plot)
|
||||
else:
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mp)
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mm)
|
||||
|
@ -2844,7 +2843,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
|
||||
self.app.on_mouse_click_release_over_plot)
|
||||
self.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click',
|
||||
self.app.on_double_click_over_plot)
|
||||
self.app.on_mouse_double_click_over_plot)
|
||||
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
|
@ -2977,7 +2976,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
|
||||
# add a first tool in the Tool Table but only if the Excellon Object is empty
|
||||
if not self.tool2tooldia:
|
||||
self.on_tool_add(tooldia=float(self.app.defaults['excellon_editor_newdia']))
|
||||
self.on_tool_add(tooldia=float('%.2f' % float(self.app.defaults['excellon_editor_newdia'])))
|
||||
|
||||
def update_fcexcellon(self, exc_obj):
|
||||
"""
|
||||
|
@ -3657,7 +3656,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
x, y = self.app.geo_editor.snap(x, y)
|
||||
|
||||
# Update cursor
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black',
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
self.snap_x = x
|
||||
|
@ -3706,7 +3705,7 @@ class FlatCAMExcEditor(QtCore.QObject):
|
|||
self.app.selection_type = None
|
||||
|
||||
# Update cursor
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black',
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
def on_canvas_key_release(self, event):
|
||||
|
|
|
@ -236,7 +236,7 @@ class TextInputTool(FlatCAMTool):
|
|||
|
||||
self.font_type_cb = QtWidgets.QFontComboBox(self)
|
||||
self.font_type_cb.setCurrentFont(f_current)
|
||||
self.form_layout.addRow("Font:", self.font_type_cb)
|
||||
self.form_layout.addRow(QtWidgets.QLabel('%s:' % _("Font")), self.font_type_cb)
|
||||
|
||||
# Flag variables to show if font is bold, italic, both or none (regular)
|
||||
self.font_bold = False
|
||||
|
@ -308,7 +308,7 @@ class TextInputTool(FlatCAMTool):
|
|||
self.font_italic_tb.setIcon(QtGui.QIcon('share/italic32.png'))
|
||||
hlay.addWidget(self.font_italic_tb)
|
||||
|
||||
self.form_layout.addRow("Size:", hlay)
|
||||
self.form_layout.addRow(QtWidgets.QLabel('%s:' % "Size"), hlay)
|
||||
|
||||
# Text input
|
||||
self.text_input_entry = FCTextAreaRich()
|
||||
|
@ -317,7 +317,7 @@ class TextInputTool(FlatCAMTool):
|
|||
# self.text_input_entry.setMaximumHeight(150)
|
||||
self.text_input_entry.setCurrentFont(f_current)
|
||||
self.text_input_entry.setFontPointSize(10)
|
||||
self.form_layout.addRow("Text:", self.text_input_entry)
|
||||
self.form_layout.addRow(QtWidgets.QLabel('%s:' % _("Text")), self.text_input_entry)
|
||||
|
||||
# Buttons
|
||||
hlay1 = QtWidgets.QHBoxLayout()
|
||||
|
@ -973,13 +973,13 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.flipy_button.clicked.connect(self.on_flipy)
|
||||
self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
|
||||
|
||||
self.rotate_entry.returnPressed.connect(self.on_rotate)
|
||||
self.skewx_entry.returnPressed.connect(self.on_skewx)
|
||||
self.skewy_entry.returnPressed.connect(self.on_skewy)
|
||||
self.scalex_entry.returnPressed.connect(self.on_scalex)
|
||||
self.scaley_entry.returnPressed.connect(self.on_scaley)
|
||||
self.offx_entry.returnPressed.connect(self.on_offx)
|
||||
self.offy_entry.returnPressed.connect(self.on_offy)
|
||||
self.rotate_entry.editingFinished.connect(self.on_rotate)
|
||||
self.skewx_entry.editingFinished.connect(self.on_skewx)
|
||||
self.skewy_entry.editingFinished.connect(self.on_skewy)
|
||||
self.scalex_entry.editingFinished.connect(self.on_scalex)
|
||||
self.scaley_entry.editingFinished.connect(self.on_scaley)
|
||||
self.offx_entry.editingFinished.connect(self.on_offx)
|
||||
self.offy_entry.editingFinished.connect(self.on_offy)
|
||||
|
||||
self.set_tool_ui()
|
||||
|
||||
|
@ -1324,7 +1324,7 @@ class TransformEditorTool(FlatCAMTool):
|
|||
# get mirroring coords from the point entry
|
||||
if self.flip_ref_cb.isChecked():
|
||||
px, py = eval('{}'.format(self.flip_ref_entry.text()))
|
||||
# get mirroing coords from the center of an all-enclosing bounding box
|
||||
# get mirroring coords from the center of an all-enclosing bounding box
|
||||
else:
|
||||
# first get a bounding box to fit all
|
||||
for sha in shape_list:
|
||||
|
@ -2455,6 +2455,61 @@ class FCSelect(DrawTool):
|
|||
return ""
|
||||
|
||||
|
||||
class FCExplode(FCShapeTool):
|
||||
def __init__(self, draw_app):
|
||||
FCShapeTool.__init__(self, draw_app)
|
||||
self.name = 'explode'
|
||||
|
||||
self.draw_app = draw_app
|
||||
|
||||
try:
|
||||
QtGui.QGuiApplication.restoreOverrideCursor()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
self.storage = self.draw_app.storage
|
||||
self.origin = (0, 0)
|
||||
self.destination = None
|
||||
|
||||
self.draw_app.active_tool = self
|
||||
if len(self.draw_app.get_selected()) == 0:
|
||||
self.draw_app.app.inform.emit('[WARNING_NOTCL] %s...' %
|
||||
_("No shape selected. Select a shape to explode"))
|
||||
else:
|
||||
self.make()
|
||||
|
||||
def make(self):
|
||||
to_be_deleted_list = list()
|
||||
lines = list()
|
||||
|
||||
for shape in self.draw_app.get_selected():
|
||||
to_be_deleted_list.append(shape)
|
||||
geo = shape.geo
|
||||
ext_coords = list(geo.exterior.coords)
|
||||
|
||||
for c in range(len(ext_coords)):
|
||||
if c < len(ext_coords) - 1:
|
||||
lines.append(LineString([ext_coords[c], ext_coords[c + 1]]))
|
||||
|
||||
for int_geo in geo.interiors:
|
||||
int_coords = list(int_geo.coords)
|
||||
for c in range(len(int_coords)):
|
||||
if c < len(int_coords):
|
||||
lines.append(LineString([int_coords[c], int_coords[c + 1]]))
|
||||
|
||||
for shape in to_be_deleted_list:
|
||||
self.draw_app.storage.remove(shape)
|
||||
if shape in self.draw_app.selected:
|
||||
self.draw_app.selected.remove(shape)
|
||||
|
||||
geo_list = list()
|
||||
for line in lines:
|
||||
geo_list.append(DrawToolShape(line))
|
||||
self.geometry = geo_list
|
||||
self.draw_app.on_shape_complete()
|
||||
self.draw_app.app.inform.emit('[success] %s...' % _("Done. Polygons exploded into lines."))
|
||||
|
||||
|
||||
class FCMove(FCShapeTool):
|
||||
def __init__(self, draw_app):
|
||||
FCShapeTool.__init__(self, draw_app)
|
||||
|
@ -3015,7 +3070,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
"transform": {"button": self.app.ui.geo_transform_btn,
|
||||
"constructor": FCTransform},
|
||||
"copy": {"button": self.app.ui.geo_copy_btn,
|
||||
"constructor": FCCopy}
|
||||
"constructor": FCCopy},
|
||||
"explode": {"button": self.app.ui.geo_explode_btn,
|
||||
"constructor": FCExplode}
|
||||
}
|
||||
|
||||
# # ## Data
|
||||
|
@ -3104,6 +3161,9 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
|
||||
self.rtree_index = rtindex.Index()
|
||||
|
||||
# Number of decimals used by tools in this class
|
||||
self.decimals = 4
|
||||
|
||||
def entry2option(option, entry):
|
||||
try:
|
||||
self.options[option] = float(entry.text())
|
||||
|
@ -3330,7 +3390,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
|
||||
self.app.plotcanvas.graph_event_disconnect('mouse_double_click', self.app.on_mouse_double_click_over_plot)
|
||||
else:
|
||||
|
||||
self.app.plotcanvas.graph_event_disconnect(self.app.mp)
|
||||
|
@ -3377,7 +3437,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
|
||||
self.app.on_mouse_click_release_over_plot)
|
||||
self.app.mdc = self.app.plotcanvas.graph_event_connect('mouse_double_click',
|
||||
self.app.on_double_click_over_plot)
|
||||
self.app.on_mouse_double_click_over_plot)
|
||||
# self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
|
@ -3602,6 +3662,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
|
||||
self.replot()
|
||||
|
||||
# updated units
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
if self.units == "IN":
|
||||
self.decimals = 4
|
||||
else:
|
||||
self.decimals = 2
|
||||
|
||||
# start with GRID toolbar activated
|
||||
if self.app.ui.grid_snap_btn.isChecked() is False:
|
||||
self.app.ui.grid_snap_btn.trigger()
|
||||
|
@ -3755,7 +3823,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
x, y = self.snap(x, y)
|
||||
|
||||
# Update cursor
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black',
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
self.snap_x = x
|
||||
|
@ -4030,7 +4098,8 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
|
||||
if type(geometry) == LineString or type(geometry) == LinearRing:
|
||||
plot_elements.append(self.shapes.add(shape=geometry, color=color, layer=0,
|
||||
tolerance=self.fcgeometry.drawing_tolerance))
|
||||
tolerance=self.fcgeometry.drawing_tolerance,
|
||||
linewidth=linewidth))
|
||||
|
||||
if type(geometry) == Point:
|
||||
pass
|
||||
|
@ -4070,7 +4139,12 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
def on_shape_complete(self):
|
||||
self.app.log.debug("on_shape_complete()")
|
||||
|
||||
geom = self.active_tool.geometry.geo
|
||||
geom = []
|
||||
try:
|
||||
for shape in self.active_tool.geometry:
|
||||
geom.append(shape.geo)
|
||||
except TypeError:
|
||||
geom = self.active_tool.geometry.geo
|
||||
|
||||
if self.app.defaults['geometry_editor_milling_type'] == 'cl':
|
||||
# reverse the geometry coordinates direction to allow creation of Gcode for climb milling
|
||||
|
@ -4082,9 +4156,14 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
pl.append(Polygon(p.exterior.coords[::-1], p.interiors))
|
||||
elif isinstance(p, LinearRing):
|
||||
pl.append(Polygon(p.coords[::-1]))
|
||||
# elif isinstance(p, LineString):
|
||||
# pl.append(LineString(p.coords[::-1]))
|
||||
geom = MultiPolygon(pl)
|
||||
elif isinstance(p, LineString):
|
||||
pl.append(LineString(p.coords[::-1]))
|
||||
try:
|
||||
geom = MultiPolygon(pl)
|
||||
except TypeError:
|
||||
# this may happen if the geom elements are made out of LineStrings because you can't create a
|
||||
# MultiPolygon out of LineStrings
|
||||
pass
|
||||
except TypeError:
|
||||
if isinstance(geom, Polygon) and geom is not None:
|
||||
geom = Polygon(geom.exterior.coords[::-1], geom.interiors)
|
||||
|
@ -4099,8 +4178,15 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
log.debug("FlatCAMGeoEditor.on_shape_complete() Error --> %s" % str(e))
|
||||
return 'fail'
|
||||
|
||||
shape_list = list()
|
||||
try:
|
||||
for geo in geom:
|
||||
shape_list.append(DrawToolShape(geo))
|
||||
except TypeError:
|
||||
shape_list.append(DrawToolShape(geom))
|
||||
|
||||
# Add shape
|
||||
self.add_shape(DrawToolShape(geom))
|
||||
self.add_shape(shape_list)
|
||||
|
||||
# Remove any utility shapes
|
||||
self.delete_utility_geometry()
|
||||
|
@ -4170,7 +4256,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
# # ## Grid snap
|
||||
if self.options["grid_snap"]:
|
||||
if self.options["global_gridx"] != 0:
|
||||
snap_x_ = round(x / self.options["global_gridx"]) * self.options['global_gridx']
|
||||
snap_x_ = round(x / float(self.options["global_gridx"])) * float(self.options['global_gridx'])
|
||||
else:
|
||||
snap_x_ = x
|
||||
|
||||
|
@ -4178,12 +4264,12 @@ class FlatCAMGeoEditor(QtCore.QObject):
|
|||
# and it will use the snap distance from GridX entry
|
||||
if self.app.ui.grid_gap_link_cb.isChecked():
|
||||
if self.options["global_gridx"] != 0:
|
||||
snap_y_ = round(y / self.options["global_gridx"]) * self.options['global_gridx']
|
||||
snap_y_ = round(y / float(self.options["global_gridx"])) * float(self.options['global_gridx'])
|
||||
else:
|
||||
snap_y_ = y
|
||||
else:
|
||||
if self.options["global_gridy"] != 0:
|
||||
snap_y_ = round(y / self.options["global_gridy"]) * self.options['global_gridy']
|
||||
snap_y_ = round(y / float(self.options["global_gridy"])) * float(self.options['global_gridy'])
|
||||
else:
|
||||
snap_y_ = y
|
||||
nearest_grid_distance = distance((x, y), (snap_x_, snap_y_))
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 8/17/2019 #
|
||||
# MIT Licence #
|
||||
|
@ -24,6 +23,7 @@ from camlib import *
|
|||
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \
|
||||
SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
|
||||
from FlatCAMObj import FlatCAMGerber
|
||||
from flatcamParsers.ParseGerber import Gerber
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
|
||||
from numpy.linalg import norm as numpy_norm
|
||||
|
@ -1809,28 +1809,46 @@ class FCMarkArea(FCShapeTool):
|
|||
self.activate_markarea()
|
||||
|
||||
def activate_markarea(self):
|
||||
self.draw_app.hide_tool('all')
|
||||
self.draw_app.ma_tool_frame.show()
|
||||
|
||||
# clear previous marking
|
||||
self.draw_app.ma_annotation.clear(update=True)
|
||||
|
||||
try:
|
||||
self.draw_app.ma_threshold__button.clicked.disconnect()
|
||||
self.draw_app.ma_threshold_button.clicked.disconnect()
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
self.draw_app.ma_threshold__button.clicked.connect(self.on_markarea_click)
|
||||
self.draw_app.ma_threshold_button.clicked.connect(self.on_markarea_click)
|
||||
|
||||
try:
|
||||
self.draw_app.ma_delete_button.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.draw_app.ma_delete_button.clicked.connect(self.on_markarea_delete)
|
||||
|
||||
try:
|
||||
self.draw_app.ma_clear_button.clicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.draw_app.ma_clear_button.clicked.connect(self.on_markarea_clear)
|
||||
|
||||
def deactivate_markarea(self):
|
||||
self.draw_app.ma_threshold__button.clicked.disconnect()
|
||||
self.draw_app.ma_threshold_button.clicked.disconnect()
|
||||
self.complete = True
|
||||
self.draw_app.select_tool("select")
|
||||
self.draw_app.hide_tool(self.name)
|
||||
|
||||
def on_markarea_click(self):
|
||||
self.draw_app.on_markarea()
|
||||
|
||||
def on_markarea_clear(self):
|
||||
self.draw_app.ma_annotation.clear(update=True)
|
||||
self.deactivate_markarea()
|
||||
|
||||
def on_markarea_delete(self):
|
||||
self.draw_app.delete_marked_polygons()
|
||||
self.on_markarea_clear()
|
||||
|
||||
def clean_up(self):
|
||||
self.draw_app.selected = []
|
||||
self.draw_app.apertures_table.clearSelection()
|
||||
|
@ -2332,6 +2350,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
|
||||
self.app = app
|
||||
self.canvas = self.app.plotcanvas
|
||||
self.decimals = 4
|
||||
|
||||
# Current application units in Upper Case
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
@ -2581,7 +2600,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.ma_tool_frame.hide()
|
||||
|
||||
# Title
|
||||
ma_title_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Mark polygon areas'))
|
||||
ma_title_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Mark polygons'))
|
||||
ma_title_lbl.setToolTip(
|
||||
_("Mark the polygon areas.")
|
||||
)
|
||||
|
@ -2596,16 +2615,18 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
_("The threshold value, all areas less than this are marked.\n"
|
||||
"Can have a value between 0.0000 and 9999.9999")
|
||||
)
|
||||
self.ma_upper_threshold_entry = FCEntry()
|
||||
self.ma_upper_threshold_entry.setValidator(QtGui.QDoubleValidator(0.0000, 9999.9999, 4))
|
||||
self.ma_upper_threshold_entry = FCDoubleSpinner()
|
||||
self.ma_upper_threshold_entry.set_precision(self.decimals)
|
||||
self.ma_upper_threshold_entry.set_range(0, 10000)
|
||||
|
||||
self.ma_lower_threshold_lbl = QtWidgets.QLabel('%s:' % _("Area LOWER threshold"))
|
||||
self.ma_lower_threshold_lbl.setToolTip(
|
||||
_("The threshold value, all areas more than this are marked.\n"
|
||||
"Can have a value between 0.0000 and 9999.9999")
|
||||
)
|
||||
self.ma_lower_threshold_entry = FCEntry()
|
||||
self.ma_lower_threshold_entry.setValidator(QtGui.QDoubleValidator(0.0000, 9999.9999, 4))
|
||||
self.ma_lower_threshold_entry = FCDoubleSpinner()
|
||||
self.ma_lower_threshold_entry.set_precision(self.decimals)
|
||||
self.ma_lower_threshold_entry.set_range(0, 10000)
|
||||
|
||||
ma_form_layout.addRow(self.ma_lower_threshold_lbl, self.ma_lower_threshold_entry)
|
||||
ma_form_layout.addRow(self.ma_upper_threshold_lbl, self.ma_upper_threshold_entry)
|
||||
|
@ -2614,8 +2635,23 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
hlay_ma = QtWidgets.QHBoxLayout()
|
||||
self.ma_tools_box.addLayout(hlay_ma)
|
||||
|
||||
self.ma_threshold__button = QtWidgets.QPushButton(_("Go"))
|
||||
hlay_ma.addWidget(self.ma_threshold__button)
|
||||
self.ma_threshold_button = QtWidgets.QPushButton(_("Mark"))
|
||||
self.ma_threshold_button.setToolTip(
|
||||
_("Mark the polygons that fit within limits.")
|
||||
)
|
||||
hlay_ma.addWidget(self.ma_threshold_button)
|
||||
|
||||
self.ma_delete_button = QtWidgets.QPushButton(_("Delete"))
|
||||
self.ma_delete_button.setToolTip(
|
||||
_("Delete all the marked polygons.")
|
||||
)
|
||||
hlay_ma.addWidget(self.ma_delete_button)
|
||||
|
||||
self.ma_clear_button = QtWidgets.QPushButton(_("Clear"))
|
||||
self.ma_clear_button.setToolTip(
|
||||
_("Clear all the markings.")
|
||||
)
|
||||
hlay_ma.addWidget(self.ma_clear_button)
|
||||
|
||||
# ######################
|
||||
# ### Add Pad Array ####
|
||||
|
@ -2786,27 +2822,30 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
# # ## Data
|
||||
self.active_tool = None
|
||||
|
||||
self.storage_dict = {}
|
||||
self.current_storage = []
|
||||
self.storage_dict = dict()
|
||||
self.current_storage = list()
|
||||
|
||||
self.sorted_apid = []
|
||||
self.sorted_apid = list()
|
||||
|
||||
self.new_apertures = {}
|
||||
self.new_aperture_macros = {}
|
||||
self.new_apertures = dict()
|
||||
self.new_aperture_macros = dict()
|
||||
|
||||
# store here the plot promises, if empty the delayed plot will be activated
|
||||
self.grb_plot_promises = []
|
||||
self.grb_plot_promises = list()
|
||||
|
||||
# dictionary to store the tool_row and aperture codes in Tool_table
|
||||
# it will be updated everytime self.build_ui() is called
|
||||
self.olddia_newdia = {}
|
||||
self.olddia_newdia = dict()
|
||||
|
||||
self.tool2tooldia = {}
|
||||
self.tool2tooldia = dict()
|
||||
|
||||
# this will store the value for the last selected tool, for use after clicking on canvas when the selection
|
||||
# is cleared but as a side effect also the selected tool is cleared
|
||||
self.last_aperture_selected = None
|
||||
self.utility = []
|
||||
self.utility = list()
|
||||
|
||||
# this will store the polygons marked by mark are to be perhaps deleted
|
||||
self.geo_to_delete = list()
|
||||
|
||||
# this will flag if the Editor "tools" are launched from key shortcuts (True) or from menu toolbar (False)
|
||||
self.launched_from_shortcuts = False
|
||||
|
@ -2920,8 +2959,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.aptype_cb.currentIndexChanged[str].connect(self.on_aptype_changed)
|
||||
|
||||
self.addaperture_btn.clicked.connect(self.on_aperture_add)
|
||||
self.apsize_entry.returnPressed.connect(self.on_aperture_add)
|
||||
self.apdim_entry.returnPressed.connect(self.on_aperture_add)
|
||||
self.apsize_entry.editingFinished.connect(self.on_aperture_add)
|
||||
self.apdim_entry.editingFinished.connect(self.on_aperture_add)
|
||||
|
||||
self.delaperture_btn.clicked.connect(self.on_aperture_delete)
|
||||
self.apertures_table.cellPressed.connect(self.on_row_selected)
|
||||
|
@ -2955,6 +2994,9 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
|
||||
self.conversion_factor = 1
|
||||
|
||||
# number of decimals for the tool diameters to be used in this editor
|
||||
self.decimals = 4
|
||||
|
||||
self.set_ui()
|
||||
log.debug("Initialization of the FlatCAM Gerber Editor is finished ...")
|
||||
|
||||
|
@ -2966,6 +3008,11 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
# updated units
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
if self.units == "IN":
|
||||
self.decimals = 4
|
||||
else:
|
||||
self.decimals = 2
|
||||
|
||||
self.olddia_newdia.clear()
|
||||
self.tool2tooldia.clear()
|
||||
|
||||
|
@ -2994,14 +3041,14 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.aptype_cb.set_value(self.app.defaults["gerber_editor_newtype"])
|
||||
self.apdim_entry.set_value(self.app.defaults["gerber_editor_newdim"])
|
||||
|
||||
self.pad_array_size_entry.set_value(self.app.defaults["gerber_editor_array_size"])
|
||||
self.pad_array_size_entry.set_value(int(self.app.defaults["gerber_editor_array_size"]))
|
||||
# linear array
|
||||
self.pad_axis_radio.set_value(self.app.defaults["gerber_editor_lin_axis"])
|
||||
self.pad_pitch_entry.set_value(self.app.defaults["gerber_editor_lin_pitch"])
|
||||
self.pad_pitch_entry.set_value(float(self.app.defaults["gerber_editor_lin_pitch"]))
|
||||
self.linear_angle_spinner.set_value(self.app.defaults["gerber_editor_lin_angle"])
|
||||
# circular array
|
||||
self.pad_direction_radio.set_value(self.app.defaults["gerber_editor_circ_dir"])
|
||||
self.pad_angle_entry.set_value(self.app.defaults["gerber_editor_circ_angle"])
|
||||
self.pad_angle_entry.set_value(float(self.app.defaults["gerber_editor_circ_angle"]))
|
||||
|
||||
def build_ui(self, first_run=None):
|
||||
|
||||
|
@ -3056,15 +3103,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
|
||||
if str(self.storage_dict[ap_code]['type']) == 'R' or str(self.storage_dict[ap_code]['type']) == 'O':
|
||||
ap_dim_item = QtWidgets.QTableWidgetItem(
|
||||
'%.4f, %.4f' % (self.storage_dict[ap_code]['width'],
|
||||
self.storage_dict[ap_code]['height']
|
||||
'%.*f, %.*f' % (self.decimals, self.storage_dict[ap_code]['width'],
|
||||
self.decimals, self.storage_dict[ap_code]['height']
|
||||
)
|
||||
)
|
||||
ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
elif str(self.storage_dict[ap_code]['type']) == 'P':
|
||||
ap_dim_item = QtWidgets.QTableWidgetItem(
|
||||
'%.4f, %.4f' % (self.storage_dict[ap_code]['diam'],
|
||||
self.storage_dict[ap_code]['nVertices'])
|
||||
'%.*f, %.*f' % (self.decimals, self.storage_dict[ap_code]['diam'],
|
||||
self.decimals, self.storage_dict[ap_code]['nVertices'])
|
||||
)
|
||||
ap_dim_item.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
else:
|
||||
|
@ -3073,8 +3120,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
|
||||
try:
|
||||
if self.storage_dict[ap_code]['size'] is not None:
|
||||
ap_size_item = QtWidgets.QTableWidgetItem('%.4f' % float(
|
||||
self.storage_dict[ap_code]['size']))
|
||||
ap_size_item = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals,
|
||||
float(self.storage_dict[ap_code]['size'])))
|
||||
else:
|
||||
ap_size_item = QtWidgets.QTableWidgetItem('')
|
||||
except KeyError:
|
||||
|
@ -3534,7 +3581,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
|
||||
self.canvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
|
||||
self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||
self.canvas.graph_event_disconnect('mouse_double_click', self.app.on_double_click_over_plot)
|
||||
self.canvas.graph_event_disconnect('mouse_double_click', self.app.on_mouse_double_click_over_plot)
|
||||
else:
|
||||
self.canvas.graph_event_disconnect(self.app.mp)
|
||||
self.canvas.graph_event_disconnect(self.app.mm)
|
||||
|
@ -3575,7 +3622,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.app.mp = self.canvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
|
||||
self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
|
||||
self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
|
||||
self.app.mdc = self.canvas.graph_event_connect('mouse_double_click', self.app.on_double_click_over_plot)
|
||||
self.app.mdc = self.canvas.graph_event_connect('mouse_double_click', self.app.on_mouse_double_click_over_plot)
|
||||
self.app.collection.view.clicked.connect(self.app.collection.on_mouse_down)
|
||||
|
||||
if self.app.is_legacy is False:
|
||||
|
@ -3691,7 +3738,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.gerber_obj = orig_grb_obj
|
||||
self.gerber_obj_options = orig_grb_obj.options
|
||||
|
||||
file_units = self.gerber_obj.gerber_units if self.gerber_obj.gerber_units else 'IN'
|
||||
file_units = self.gerber_obj.units if self.gerber_obj.units else 'IN'
|
||||
app_units = self.app.defaults['units']
|
||||
self.conversion_factor = 25.4 if file_units == 'IN' else (1 / 25.4) if file_units != app_units else 1
|
||||
|
||||
|
@ -3725,7 +3772,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
conv_apertures[apid][key] = self.gerber_obj.apertures[apid][key]
|
||||
|
||||
self.gerber_obj.apertures = conv_apertures
|
||||
self.gerber_obj.gerber_units = app_units
|
||||
self.gerber_obj.units = app_units
|
||||
|
||||
# ############################################################# ##
|
||||
# APPLY CLEAR_GEOMETRY on the SOLID_GEOMETRY
|
||||
|
@ -3987,7 +4034,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
|
||||
grb_obj.multigeo = False
|
||||
grb_obj.follow = False
|
||||
grb_obj.gerber_units = app_obj.defaults['units']
|
||||
grb_obj.units = app_obj.defaults['units']
|
||||
|
||||
try:
|
||||
grb_obj.create_geometry()
|
||||
|
@ -4096,7 +4143,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
if specific_shape:
|
||||
geo = specific_shape
|
||||
else:
|
||||
geo = self.active_tool.geometry
|
||||
geo = deepcopy(self.active_tool.geometry)
|
||||
if geo is None:
|
||||
return
|
||||
|
||||
|
@ -4398,7 +4445,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
x, y = self.app.geo_editor.snap(x, y)
|
||||
|
||||
# Update cursor
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color='black',
|
||||
self.app.app_cursor.set_data(np.asarray([(x, y)]), symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
self.snap_x = x
|
||||
|
@ -4820,16 +4867,15 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
self.ma_annotation.clear(update=True)
|
||||
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
upper_threshold_val = None
|
||||
lower_threshold_val = None
|
||||
|
||||
text = []
|
||||
position = []
|
||||
|
||||
for apid in self.gerber_obj.apertures:
|
||||
if 'geometry' in self.gerber_obj.apertures[apid]:
|
||||
for geo_el in self.gerber_obj.apertures[apid]['geometry']:
|
||||
if 'solid' in geo_el:
|
||||
area = geo_el['solid'].area
|
||||
for apid in self.storage_dict:
|
||||
if 'geometry' in self.storage_dict[apid]:
|
||||
for geo_el in self.storage_dict[apid]['geometry']:
|
||||
if 'solid' in geo_el.geo:
|
||||
area = geo_el.geo['solid'].area
|
||||
try:
|
||||
upper_threshold_val = self.ma_upper_threshold_entry.get_value()
|
||||
except Exception as e:
|
||||
|
@ -4841,20 +4887,29 @@ class FlatCAMGrbEditor(QtCore.QObject):
|
|||
lower_threshold_val = 0.0
|
||||
|
||||
if float(upper_threshold_val) > area > float(lower_threshold_val):
|
||||
current_pos = geo_el['solid'].exterior.coords[-1]
|
||||
current_pos = geo_el.geo['solid'].exterior.coords[-1]
|
||||
text_elem = '%.4f' % area
|
||||
text.append(text_elem)
|
||||
position.append(current_pos)
|
||||
self.geo_to_delete.append(geo_el)
|
||||
|
||||
if text:
|
||||
self.ma_annotation.set(text=text, pos=position, visible=True,
|
||||
font_size=self.app.defaults["cncjob_annotation_fontsize"],
|
||||
color='#000000FF')
|
||||
self.app.inform.emit('[success] %s' %
|
||||
_("Polygon areas marked."))
|
||||
_("Polygons marked."))
|
||||
else:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("There are no polygons to mark area."))
|
||||
_("No polygons were marked. None fit within the limits."))
|
||||
|
||||
def delete_marked_polygons(self):
|
||||
for shape_sel in self.geo_to_delete:
|
||||
self.delete_shape(shape_sel)
|
||||
|
||||
self.build_ui()
|
||||
self.plot_all()
|
||||
self.app.inform.emit('[success] %s' % _("Done. Apertures geometry deleted."))
|
||||
|
||||
def on_eraser(self):
|
||||
self.select_tool('eraser')
|
||||
|
@ -5244,13 +5299,13 @@ class TransformEditorTool(FlatCAMTool):
|
|||
self.flipy_button.clicked.connect(self.on_flipy)
|
||||
self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
|
||||
|
||||
self.rotate_entry.returnPressed.connect(self.on_rotate)
|
||||
self.skewx_entry.returnPressed.connect(self.on_skewx)
|
||||
self.skewy_entry.returnPressed.connect(self.on_skewy)
|
||||
self.scalex_entry.returnPressed.connect(self.on_scalex)
|
||||
self.scaley_entry.returnPressed.connect(self.on_scaley)
|
||||
self.offx_entry.returnPressed.connect(self.on_offx)
|
||||
self.offy_entry.returnPressed.connect(self.on_offy)
|
||||
self.rotate_entry.editingFinished.connect(self.on_rotate)
|
||||
self.skewx_entry.editingFinished.connect(self.on_skewx)
|
||||
self.skewy_entry.editingFinished.connect(self.on_skewy)
|
||||
self.scalex_entry.editingFinished.connect(self.on_scalex)
|
||||
self.scaley_entry.editingFinished.connect(self.on_scaley)
|
||||
self.offx_entry.editingFinished.connect(self.on_offx)
|
||||
self.offy_entry.editingFinished.connect(self.on_offy)
|
||||
|
||||
self.set_tool_ui()
|
||||
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 10/10/2019 #
|
||||
# MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from flatcamGUI.GUIElements import *
|
||||
from PyQt5 import QtPrintSupport
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class TextEditor(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, app, text=None):
|
||||
super().__init__()
|
||||
|
||||
self.app = app
|
||||
self.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.MinimumExpanding,
|
||||
QtWidgets.QSizePolicy.MinimumExpanding
|
||||
)
|
||||
|
||||
self.main_editor_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.main_editor_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.t_frame = QtWidgets.QFrame()
|
||||
self.t_frame.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_editor_layout.addWidget(self.t_frame)
|
||||
|
||||
self.work_editor_layout = QtWidgets.QGridLayout(self.t_frame)
|
||||
self.work_editor_layout.setContentsMargins(2, 2, 2, 2)
|
||||
self.t_frame.setLayout(self.work_editor_layout)
|
||||
|
||||
self.code_editor = FCTextAreaExtended()
|
||||
stylesheet = """
|
||||
QTextEdit { selection-background-color:yellow;
|
||||
selection-color:black;
|
||||
}
|
||||
"""
|
||||
|
||||
self.code_editor.setStyleSheet(stylesheet)
|
||||
|
||||
if text:
|
||||
self.code_editor.setPlainText(text)
|
||||
|
||||
self.buttonPreview = QtWidgets.QPushButton(_('Print Preview'))
|
||||
self.buttonPreview.setToolTip(_("Open a OS standard Preview Print window."))
|
||||
self.buttonPreview.setMinimumWidth(100)
|
||||
|
||||
self.buttonPrint = QtWidgets.QPushButton(_('Print Code'))
|
||||
self.buttonPrint.setToolTip(_("Open a OS standard Print window."))
|
||||
|
||||
self.buttonFind = QtWidgets.QPushButton(_('Find in Code'))
|
||||
self.buttonFind.setToolTip(_("Will search and highlight in yellow the string in the Find box."))
|
||||
self.buttonFind.setMinimumWidth(100)
|
||||
|
||||
self.entryFind = FCEntry()
|
||||
self.entryFind.setToolTip(_("Find box. Enter here the strings to be searched in the text."))
|
||||
|
||||
self.buttonReplace = QtWidgets.QPushButton(_('Replace With'))
|
||||
self.buttonReplace.setToolTip(_("Will replace the string from the Find box with the one in the Replace box."))
|
||||
self.buttonReplace.setMinimumWidth(100)
|
||||
|
||||
self.entryReplace = FCEntry()
|
||||
self.entryReplace.setToolTip(_("String to replace the one in the Find box throughout the text."))
|
||||
|
||||
self.sel_all_cb = QtWidgets.QCheckBox(_('All'))
|
||||
self.sel_all_cb.setToolTip(_("When checked it will replace all instances in the 'Find' box\n"
|
||||
"with the text in the 'Replace' box.."))
|
||||
|
||||
self.button_copy_all = QtWidgets.QPushButton(_('Copy All'))
|
||||
self.button_copy_all.setToolTip(_("Will copy all the text in the Code Editor to the clipboard."))
|
||||
self.button_copy_all.setMinimumWidth(100)
|
||||
|
||||
self.buttonOpen = QtWidgets.QPushButton(_('Open Code'))
|
||||
self.buttonOpen.setToolTip(_("Will open a text file in the editor."))
|
||||
|
||||
self.buttonSave = QtWidgets.QPushButton(_('Save Code'))
|
||||
self.buttonSave.setToolTip(_("Will save the text in the editor into a file."))
|
||||
|
||||
self.buttonRun = QtWidgets.QPushButton(_('Run Code'))
|
||||
self.buttonRun.setToolTip(_("Will run the TCL commands found in the text file, one by one."))
|
||||
|
||||
self.buttonRun.hide()
|
||||
self.work_editor_layout.addWidget(self.code_editor, 0, 0, 1, 5)
|
||||
|
||||
editor_hlay_1 = QtWidgets.QHBoxLayout()
|
||||
# cnc_tab_lay_1.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
editor_hlay_1.addWidget(self.buttonFind)
|
||||
editor_hlay_1.addWidget(self.entryFind)
|
||||
editor_hlay_1.addWidget(self.buttonReplace)
|
||||
editor_hlay_1.addWidget(self.entryReplace)
|
||||
editor_hlay_1.addWidget(self.sel_all_cb)
|
||||
editor_hlay_1.addWidget(self.button_copy_all)
|
||||
self.work_editor_layout.addLayout(editor_hlay_1, 1, 0, 1, 5)
|
||||
|
||||
editor_hlay_2 = QtWidgets.QHBoxLayout()
|
||||
editor_hlay_2.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
|
||||
editor_hlay_2.addWidget(self.buttonPreview)
|
||||
editor_hlay_2.addWidget(self.buttonPrint)
|
||||
self.work_editor_layout.addLayout(editor_hlay_2, 2, 0, 1, 1, QtCore.Qt.AlignLeft)
|
||||
|
||||
cnc_tab_lay_4 = QtWidgets.QHBoxLayout()
|
||||
cnc_tab_lay_4.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
cnc_tab_lay_4.addWidget(self.buttonOpen)
|
||||
cnc_tab_lay_4.addWidget(self.buttonSave)
|
||||
cnc_tab_lay_4.addWidget(self.buttonRun)
|
||||
self.work_editor_layout.addLayout(cnc_tab_lay_4, 2, 4, 1, 1)
|
||||
|
||||
# #################################################################################
|
||||
# ################### SIGNALS #####################################################
|
||||
# #################################################################################
|
||||
|
||||
self.code_editor.textChanged.connect(self.handleTextChanged)
|
||||
self.buttonOpen.clicked.connect(self.handleOpen)
|
||||
self.buttonSave.clicked.connect(self.handleSaveGCode)
|
||||
self.buttonPrint.clicked.connect(self.handlePrint)
|
||||
self.buttonPreview.clicked.connect(self.handlePreview)
|
||||
self.buttonFind.clicked.connect(self.handleFindGCode)
|
||||
self.buttonReplace.clicked.connect(self.handleReplaceGCode)
|
||||
self.button_copy_all.clicked.connect(self.handleCopyAll)
|
||||
|
||||
self.code_editor.set_model_data(self.app.myKeywords)
|
||||
|
||||
self.gcode_edited = ''
|
||||
|
||||
def handlePrint(self):
|
||||
self.app.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()")
|
||||
|
||||
dialog = QtPrintSupport.QPrintPreviewDialog()
|
||||
dialog.paintRequested.connect(self.code_editor.print_)
|
||||
dialog.exec_()
|
||||
|
||||
def handleTextChanged(self):
|
||||
# enable = not self.ui.code_editor.document().isEmpty()
|
||||
# self.ui.buttonPrint.setEnabled(enable)
|
||||
# self.ui.buttonPreview.setEnabled(enable)
|
||||
pass
|
||||
|
||||
def handleOpen(self, filt=None):
|
||||
self.app.report_usage("handleOpen()")
|
||||
|
||||
if filt:
|
||||
_filter_ = filt
|
||||
else:
|
||||
_filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
|
||||
"All Files (*.*)"
|
||||
|
||||
path, _f = QtWidgets.QFileDialog.getOpenFileName(
|
||||
caption=_('Open file'), directory=self.app.get_last_folder(), filter=_filter_)
|
||||
|
||||
if path:
|
||||
file = QtCore.QFile(path)
|
||||
if file.open(QtCore.QIODevice.ReadOnly):
|
||||
stream = QtCore.QTextStream(file)
|
||||
self.gcode_edited = stream.readAll()
|
||||
self.code_editor.setPlainText(self.gcode_edited)
|
||||
file.close()
|
||||
|
||||
def handleSaveGCode(self, name=None, filt=None):
|
||||
self.app.report_usage("handleSaveGCode()")
|
||||
|
||||
if filt:
|
||||
_filter_ = filt
|
||||
else:
|
||||
_filter_ = "G-Code Files (*.nc);; G-Code Files (*.txt);; G-Code Files (*.tap);; G-Code Files (*.cnc);; " \
|
||||
"All Files (*.*)"
|
||||
|
||||
if name:
|
||||
obj_name = name
|
||||
else:
|
||||
try:
|
||||
obj_name = self.app.collection.get_active().options['name']
|
||||
except AttributeError:
|
||||
obj_name = 'file'
|
||||
if filt is None:
|
||||
_filter_ = "FlatConfig Files (*.FlatConfig);;All Files (*.*)"
|
||||
|
||||
try:
|
||||
filename = str(QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export G-Code ..."),
|
||||
directory=self.app.defaults["global_last_folder"] + '/' + str(obj_name),
|
||||
filter=_filter_
|
||||
)[0])
|
||||
except TypeError:
|
||||
filename = str(QtWidgets.QFileDialog.getSaveFileName(caption=_("Export G-Code ..."), filter=_filter_)[0])
|
||||
|
||||
if filename == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Code cancelled."))
|
||||
return
|
||||
else:
|
||||
try:
|
||||
my_gcode = self.code_editor.toPlainText()
|
||||
with open(filename, 'w') as f:
|
||||
for line in my_gcode:
|
||||
f.write(line)
|
||||
except FileNotFoundError:
|
||||
self.app.inform.emit('[WARNING] %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
|
||||
|
||||
# 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('%s: %s' % (_("Saved to"), str(filename)))
|
||||
|
||||
def handleFindGCode(self):
|
||||
self.app.report_usage("handleFindGCode()")
|
||||
|
||||
flags = QtGui.QTextDocument.FindCaseSensitively
|
||||
text_to_be_found = self.entryFind.get_value()
|
||||
|
||||
r = self.code_editor.find(str(text_to_be_found), flags)
|
||||
if r is False:
|
||||
self.code_editor.moveCursor(QtGui.QTextCursor.Start)
|
||||
|
||||
def handleReplaceGCode(self):
|
||||
self.app.report_usage("handleReplaceGCode()")
|
||||
|
||||
old = self.entryFind.get_value()
|
||||
new = self.entryReplace.get_value()
|
||||
|
||||
if self.sel_all_cb.isChecked():
|
||||
while True:
|
||||
cursor = self.code_editor.textCursor()
|
||||
cursor.beginEditBlock()
|
||||
flags = QtGui.QTextDocument.FindCaseSensitively
|
||||
# self.ui.editor is the QPlainTextEdit
|
||||
r = self.code_editor.find(str(old), flags)
|
||||
if r:
|
||||
qc = self.code_editor.textCursor()
|
||||
if qc.hasSelection():
|
||||
qc.insertText(new)
|
||||
else:
|
||||
self.ui.code_editor.moveCursor(QtGui.QTextCursor.Start)
|
||||
break
|
||||
# Mark end of undo block
|
||||
cursor.endEditBlock()
|
||||
else:
|
||||
cursor = self.code_editor.textCursor()
|
||||
cursor.beginEditBlock()
|
||||
qc = self.code_editor.textCursor()
|
||||
if qc.hasSelection():
|
||||
qc.insertText(new)
|
||||
# Mark end of undo block
|
||||
cursor.endEditBlock()
|
||||
|
||||
def handleCopyAll(self):
|
||||
text = self.code_editor.toPlainText()
|
||||
self.app.clipboard.setText(text)
|
||||
self.app.inform.emit(_("Code Editor content copied to clipboard ..."))
|
||||
|
||||
# def closeEvent(self, QCloseEvent):
|
||||
# super().closeEvent(QCloseEvent)
|
|
@ -1,15 +1,15 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# File Modified (major mod): Marius Adrian Stanciu #
|
||||
# Date: 3/10/2019 #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
||||
|
@ -20,6 +20,10 @@ from copy import copy
|
|||
import re
|
||||
import logging
|
||||
import html
|
||||
import webbrowser
|
||||
from copy import deepcopy
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
|
@ -507,6 +511,168 @@ class EvalEntry2(QtWidgets.QLineEdit):
|
|||
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
|
||||
|
||||
|
||||
class FCSpinner(QtWidgets.QSpinBox):
|
||||
|
||||
returnPressed = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FCSpinner, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
self.editingFinished.connect(self.on_edit_finished)
|
||||
self.lineEdit().installEventFilter(self)
|
||||
|
||||
def eventFilter(self, object, event):
|
||||
if event.type() == QtCore.QEvent.MouseButtonPress:
|
||||
if self.readyToEdit:
|
||||
self.lineEdit().selectAll()
|
||||
self.readyToEdit = False
|
||||
else:
|
||||
self.lineEdit().deselect()
|
||||
return True
|
||||
return False
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Enter:
|
||||
self.returnPressed.emit()
|
||||
self.clearFocus()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def wheelEvent(self, *args, **kwargs):
|
||||
# should work only there is a focus in the lineedit of the SpinBox
|
||||
if self.readyToEdit is False:
|
||||
super().wheelEvent(*args, **kwargs)
|
||||
|
||||
def on_edit_finished(self):
|
||||
self.clearFocus()
|
||||
|
||||
# def mousePressEvent(self, e, parent=None):
|
||||
# super(FCSpinner, self).mousePressEvent(e) # required to deselect on 2e click
|
||||
# if self.readyToEdit:
|
||||
# self.lineEdit().selectAll()
|
||||
# self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# don't focus out if the user requests an popup menu
|
||||
if e.reason() != QtCore.Qt.PopupFocusReason:
|
||||
super(FCSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
|
||||
self.lineEdit().deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def get_value(self):
|
||||
return int(self.value())
|
||||
|
||||
def set_value(self, val):
|
||||
try:
|
||||
k = int(val)
|
||||
except Exception as e:
|
||||
log.debug(str(e))
|
||||
return
|
||||
self.setValue(k)
|
||||
|
||||
def set_range(self, min_val, max_val):
|
||||
self.setRange(min_val, max_val)
|
||||
|
||||
# def sizeHint(self):
|
||||
# default_hint_size = super(FCSpinner, self).sizeHint()
|
||||
# return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
|
||||
|
||||
|
||||
class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
|
||||
|
||||
returnPressed = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FCDoubleSpinner, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
|
||||
self.editingFinished.connect(self.on_edit_finished)
|
||||
self.lineEdit().installEventFilter(self)
|
||||
|
||||
# by default don't allow the minus sign to be entered as the default for QDoubleSpinBox is the positive range
|
||||
# between 0.00 and 99.00 (2 decimals)
|
||||
self.lineEdit().setValidator(
|
||||
QtGui.QRegExpValidator(QtCore.QRegExp("[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
|
||||
|
||||
def on_edit_finished(self):
|
||||
self.clearFocus()
|
||||
|
||||
def eventFilter(self, object, event):
|
||||
if event.type() == QtCore.QEvent.MouseButtonPress:
|
||||
if self.readyToEdit:
|
||||
self.lineEdit().selectAll()
|
||||
self.readyToEdit = False
|
||||
else:
|
||||
self.lineEdit().deselect()
|
||||
return True
|
||||
return False
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Enter:
|
||||
self.returnPressed.emit()
|
||||
self.clearFocus()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def wheelEvent(self, *args, **kwargs):
|
||||
# should work only there is a focus in the lineedit of the SpinBox
|
||||
if self.readyToEdit is False:
|
||||
super().wheelEvent(*args, **kwargs)
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# don't focus out if the user requests an popup menu
|
||||
if e.reason() != QtCore.Qt.PopupFocusReason:
|
||||
super(FCDoubleSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
|
||||
self.lineEdit().deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def valueFromText(self, p_str):
|
||||
text = p_str.replace(',', '.')
|
||||
try:
|
||||
ret_val = float(text)
|
||||
except ValueError:
|
||||
ret_val = 0.0
|
||||
|
||||
return ret_val
|
||||
|
||||
def validate(self, p_str, p_int):
|
||||
try:
|
||||
if float(p_str) < self.minimum() or float(p_str) > self.maximum():
|
||||
return QtGui.QValidator.Intermediate, p_str, p_int
|
||||
except ValueError:
|
||||
pass
|
||||
return QtGui.QValidator.Acceptable, p_str, p_int
|
||||
|
||||
def get_value(self):
|
||||
return float(self.value())
|
||||
|
||||
def set_value(self, val):
|
||||
try:
|
||||
k = float(val)
|
||||
except Exception as e:
|
||||
log.debug(str(e))
|
||||
return
|
||||
self.setValue(k)
|
||||
|
||||
def set_precision(self, val):
|
||||
self.setDecimals(val)
|
||||
|
||||
# make sure that the user can't type more decimals than the set precision
|
||||
if self.minimum() < 0 or self.maximum() < 0:
|
||||
self.lineEdit().setValidator(
|
||||
QtGui.QRegExpValidator(QtCore.QRegExp("-?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
|
||||
else:
|
||||
self.lineEdit().setValidator(
|
||||
QtGui.QRegExpValidator(QtCore.QRegExp("[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
|
||||
|
||||
def set_range(self, min_val, max_val):
|
||||
if min_val < 0 or max_val < 0:
|
||||
self.lineEdit().setValidator(
|
||||
QtGui.QRegExpValidator(QtCore.QRegExp("-?[0-9]*[.,]?[0-9]{%d}" % self.decimals()), self))
|
||||
|
||||
self.setRange(min_val, max_val)
|
||||
|
||||
|
||||
class FCCheckBox(QtWidgets.QCheckBox):
|
||||
def __init__(self, label='', parent=None):
|
||||
super(FCCheckBox, self).__init__(str(label), parent)
|
||||
|
@ -557,7 +723,7 @@ class FCTextAreaRich(QtWidgets.QTextEdit):
|
|||
|
||||
class FCTextAreaExtended(QtWidgets.QTextEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(FCTextAreaExtended, self).__init__(parent)
|
||||
super().__init__(parent)
|
||||
|
||||
self.completer = MyCompleter()
|
||||
|
||||
|
@ -1425,7 +1591,7 @@ class FCDetachableTab(QtWidgets.QTabWidget):
|
|||
|
||||
|
||||
class FCDetachableTab2(FCDetachableTab):
|
||||
tab_closed_signal = pyqtSignal()
|
||||
tab_closed_signal = pyqtSignal(object)
|
||||
|
||||
def __init__(self, protect=None, protect_by_name=None, parent=None):
|
||||
super(FCDetachableTab2, self).__init__(protect=protect, protect_by_name=protect_by_name, parent=parent)
|
||||
|
@ -1439,9 +1605,7 @@ class FCDetachableTab2(FCDetachableTab):
|
|||
"""
|
||||
idx = self.currentIndex()
|
||||
|
||||
# emit the signal only if the name is the one we want; the name should be a parameter somehow
|
||||
if self.tabText(idx) == _("Preferences"):
|
||||
self.tab_closed_signal.emit()
|
||||
self.tab_closed_signal.emit(self.tabText(idx))
|
||||
|
||||
self.removeTab(currentIndex)
|
||||
|
||||
|
@ -1564,9 +1728,33 @@ class OptionalHideInputSection:
|
|||
|
||||
|
||||
class FCTable(QtWidgets.QTableWidget):
|
||||
def __init__(self, parent=None):
|
||||
|
||||
drag_drop_sig = pyqtSignal()
|
||||
|
||||
def __init__(self, drag_drop=False, protected_rows=None, parent=None):
|
||||
super(FCTable, self).__init__(parent)
|
||||
|
||||
if drag_drop:
|
||||
self.setDragEnabled(True)
|
||||
self.setAcceptDrops(True)
|
||||
self.viewport().setAcceptDrops(True)
|
||||
self.setDragDropOverwriteMode(False)
|
||||
self.setDropIndicatorShown(True)
|
||||
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)
|
||||
|
||||
self.rows_not_for_drag_and_drop = list()
|
||||
if protected_rows:
|
||||
try:
|
||||
for r in protected_rows:
|
||||
self.rows_not_for_drag_and_drop.append(r)
|
||||
except TypeError:
|
||||
self.rows_not_for_drag_and_drop = [protected_rows]
|
||||
|
||||
self.rows_to_move = list()
|
||||
|
||||
def sizeHint(self):
|
||||
default_hint_size = super(FCTable, self).sizeHint()
|
||||
return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
|
||||
|
@ -1583,7 +1771,7 @@ class FCTable(QtWidgets.QTableWidget):
|
|||
width += self.columnWidth(i)
|
||||
return width
|
||||
|
||||
# color is in format QtGui.Qcolor(r, g, b, alpha) with or without alpfa
|
||||
# color is in format QtGui.Qcolor(r, g, b, alpha) with or without alpha
|
||||
def setColortoRow(self, rowIndex, color):
|
||||
for j in range(self.columnCount()):
|
||||
self.item(rowIndex, j).setBackground(color)
|
||||
|
@ -1609,6 +1797,124 @@ class FCTable(QtWidgets.QTableWidget):
|
|||
self.addAction(action)
|
||||
action.triggered.connect(call_function)
|
||||
|
||||
# def dropEvent(self, event: QtGui.QDropEvent):
|
||||
# if not event.isAccepted() and event.source() == self:
|
||||
# drop_row = self.drop_on(event)
|
||||
#
|
||||
# rows = sorted(set(item.row() for item in self.selectedItems()))
|
||||
# # rows_to_move = [
|
||||
# # [QtWidgets.QTableWidgetItem(self.item(row_index, column_index))
|
||||
# # for column_index in range(self.columnCount())] for row_index in rows
|
||||
# # ]
|
||||
# self.rows_to_move[:] = []
|
||||
# for row_index in rows:
|
||||
# row_items = list()
|
||||
# for column_index in range(self.columnCount()):
|
||||
# r_item = self.item(row_index, column_index)
|
||||
# w_item = self.cellWidget(row_index, column_index)
|
||||
#
|
||||
# if r_item is not None:
|
||||
# row_items.append(QtWidgets.QTableWidgetItem(r_item))
|
||||
# elif w_item is not None:
|
||||
# row_items.append(w_item)
|
||||
#
|
||||
# self.rows_to_move.append(row_items)
|
||||
#
|
||||
# for row_index in reversed(rows):
|
||||
# self.removeRow(row_index)
|
||||
# if row_index < drop_row:
|
||||
# drop_row -= 1
|
||||
#
|
||||
# for row_index, data in enumerate(self.rows_to_move):
|
||||
# row_index += drop_row
|
||||
# self.insertRow(row_index)
|
||||
#
|
||||
# for column_index, column_data in enumerate(data):
|
||||
# if isinstance(column_data, QtWidgets.QTableWidgetItem):
|
||||
# self.setItem(row_index, column_index, column_data)
|
||||
# else:
|
||||
# self.setCellWidget(row_index, column_index, column_data)
|
||||
#
|
||||
# event.accept()
|
||||
# for row_index in range(len(self.rows_to_move)):
|
||||
# self.item(drop_row + row_index, 0).setSelected(True)
|
||||
# self.item(drop_row + row_index, 1).setSelected(True)
|
||||
#
|
||||
# super().dropEvent(event)
|
||||
#
|
||||
# def drop_on(self, event):
|
||||
# ret_val = False
|
||||
# index = self.indexAt(event.pos())
|
||||
# if not index.isValid():
|
||||
# return self.rowCount()
|
||||
#
|
||||
# ret_val = index.row() + 1 if self.is_below(event.pos(), index) else index.row()
|
||||
#
|
||||
# return ret_val
|
||||
#
|
||||
# def is_below(self, pos, index):
|
||||
# rect = self.visualRect(index)
|
||||
# margin = 2
|
||||
# if pos.y() - rect.top() < margin:
|
||||
# return False
|
||||
# elif rect.bottom() - pos.y() < margin:
|
||||
# return True
|
||||
# # noinspection PyTypeChecker
|
||||
# return rect.contains(pos, True) and not (
|
||||
# int(self.model().flags(index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()
|
||||
|
||||
def dropEvent(self, event):
|
||||
"""
|
||||
From here: https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
|
||||
:param event:
|
||||
:return:
|
||||
"""
|
||||
if event.source() == self:
|
||||
rows = set([mi.row() for mi in self.selectedIndexes()])
|
||||
|
||||
# if one of the selected rows for drag and drop is within the protected list, return
|
||||
for r in rows:
|
||||
if r in self.rows_not_for_drag_and_drop:
|
||||
return
|
||||
|
||||
targetRow = self.indexAt(event.pos()).row()
|
||||
rows.discard(targetRow)
|
||||
rows = sorted(rows)
|
||||
|
||||
if not rows:
|
||||
return
|
||||
if targetRow == -1:
|
||||
targetRow = self.rowCount()
|
||||
|
||||
for _ in range(len(rows)):
|
||||
self.insertRow(targetRow)
|
||||
|
||||
rowMapping = dict() # Src row to target row.
|
||||
for idx, row in enumerate(rows):
|
||||
if row < targetRow:
|
||||
rowMapping[row] = targetRow + idx
|
||||
else:
|
||||
rowMapping[row + len(rows)] = targetRow + idx
|
||||
|
||||
colCount = self.columnCount()
|
||||
for srcRow, tgtRow in sorted(rowMapping.items()):
|
||||
for col in range(0, colCount):
|
||||
new_item = self.item(srcRow, col)
|
||||
if new_item is None:
|
||||
new_item = self.cellWidget(srcRow, col)
|
||||
if isinstance(new_item, QtWidgets.QTableWidgetItem):
|
||||
new_item = self.takeItem(srcRow, col)
|
||||
self.setItem(tgtRow, col, new_item)
|
||||
else:
|
||||
self.setCellWidget(tgtRow, col, new_item)
|
||||
|
||||
for row in reversed(sorted(rowMapping.keys())):
|
||||
self.removeRow(row)
|
||||
event.accept()
|
||||
self.drag_drop_sig.emit()
|
||||
|
||||
return
|
||||
|
||||
|
||||
class SpinBoxDelegate(QtWidgets.QItemDelegate):
|
||||
|
||||
|
@ -1652,103 +1958,27 @@ class SpinBoxDelegate(QtWidgets.QItemDelegate):
|
|||
spinbox.setDecimals(digits)
|
||||
|
||||
|
||||
class FCSpinner(QtWidgets.QSpinBox):
|
||||
def __init__(self, parent=None):
|
||||
super(FCSpinner, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
self.editingFinished.connect(self.on_edit_finished)
|
||||
|
||||
def on_edit_finished(self):
|
||||
self.clearFocus()
|
||||
|
||||
def mousePressEvent(self, e, parent=None):
|
||||
super(FCSpinner, self).mousePressEvent(e) # required to deselect on 2e click
|
||||
if self.readyToEdit:
|
||||
self.lineEdit().selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# don't focus out if the user requests an popup menu
|
||||
if e.reason() != QtCore.Qt.PopupFocusReason:
|
||||
super(FCSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
|
||||
self.lineEdit().deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def get_value(self):
|
||||
return str(self.value())
|
||||
|
||||
def set_value(self, val):
|
||||
try:
|
||||
k = int(val)
|
||||
except Exception as e:
|
||||
log.debug(str(e))
|
||||
return
|
||||
self.setValue(k)
|
||||
|
||||
def set_range(self, min_val, max_val):
|
||||
self.setRange(min_val, max_val)
|
||||
|
||||
# def sizeHint(self):
|
||||
# default_hint_size = super(FCSpinner, self).sizeHint()
|
||||
# return QtCore.QSize(EDIT_SIZE_HINT, default_hint_size.height())
|
||||
|
||||
|
||||
class FCDoubleSpinner(QtWidgets.QDoubleSpinBox):
|
||||
def __init__(self, parent=None):
|
||||
super(FCDoubleSpinner, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
self.editingFinished.connect(self.on_edit_finished)
|
||||
|
||||
def on_edit_finished(self):
|
||||
self.clearFocus()
|
||||
|
||||
def mousePressEvent(self, e, parent=None):
|
||||
super(FCDoubleSpinner, self).mousePressEvent(e) # required to deselect on 2e click
|
||||
if self.readyToEdit:
|
||||
self.lineEdit().selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# don't focus out if the user requests an popup menu
|
||||
if e.reason() != QtCore.Qt.PopupFocusReason:
|
||||
super(FCDoubleSpinner, self).focusOutEvent(e) # required to remove cursor on focusOut
|
||||
self.lineEdit().deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def get_value(self):
|
||||
return str(self.value())
|
||||
|
||||
def set_value(self, val):
|
||||
try:
|
||||
k = float(val)
|
||||
except Exception as e:
|
||||
log.debug(str(e))
|
||||
return
|
||||
self.setValue(k)
|
||||
|
||||
def set_precision(self, val):
|
||||
self.setDecimals(val)
|
||||
|
||||
def set_range(self, min_val, max_val):
|
||||
self.setRange(min_val, max_val)
|
||||
|
||||
|
||||
class Dialog_box(QtWidgets.QWidget):
|
||||
def __init__(self, title=None, label=None, icon=None):
|
||||
def __init__(self, title=None, label=None, icon=None, initial_text=None):
|
||||
"""
|
||||
|
||||
:param title: string with the window title
|
||||
:param label: string with the message inside the dialog box
|
||||
"""
|
||||
super(Dialog_box, self).__init__()
|
||||
self.location = (0, 0)
|
||||
if initial_text is None:
|
||||
self.location = str((0, 0))
|
||||
else:
|
||||
self.location = initial_text
|
||||
|
||||
self.ok = False
|
||||
|
||||
dialog_box = QtWidgets.QInputDialog()
|
||||
dialog_box.setMinimumWidth(290)
|
||||
self.dialog_box = QtWidgets.QInputDialog()
|
||||
self.dialog_box.setMinimumWidth(290)
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
self.location, self.ok = dialog_box.getText(self, title, label, text="0, 0")
|
||||
self.location, self.ok = self.dialog_box.getText(self, title, label,
|
||||
text=str(self.location).replace('(', '').replace(')', ''))
|
||||
self.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, parent=None):
|
||||
|
@ -1782,7 +2012,7 @@ class _BrowserTextEdit(QTextEdit):
|
|||
|
||||
def clear(self):
|
||||
QTextEdit.clear(self)
|
||||
text = "FlatCAM %s - Open Source Software - Type help to get started\n\n" % self.version
|
||||
text = "FlatCAM %s - Type >help< to get started\n\n" % self.version
|
||||
text = html.escape(text)
|
||||
text = text.replace('\n', '<br/>')
|
||||
self.moveCursor(QTextCursor.End)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://caram.cl/software/flatcam #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# Author: Dennis Hayrullin (c) #
|
||||
# Date: 2016 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
import logging
|
||||
from flatcamGUI.VisPyCanvas import VisPyCanvas, time
|
||||
from flatcamGUI.VisPyCanvas import VisPyCanvas, time, Color
|
||||
from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
|
||||
from vispy.scene.visuals import InfiniteLine, Line
|
||||
|
||||
import numpy as np
|
||||
from vispy.geometry import Rect
|
||||
|
||||
|
@ -44,6 +44,17 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
# Parent container
|
||||
self.container = container
|
||||
|
||||
settings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("theme"):
|
||||
theme = settings.value('theme', type=str)
|
||||
else:
|
||||
theme = 'white'
|
||||
|
||||
if theme == 'white':
|
||||
self.line_color = (0.3, 0.0, 0.0, 1.0)
|
||||
else:
|
||||
self.line_color = (0.4, 0.4, 0.4, 1.0)
|
||||
|
||||
# workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
|
||||
# which might decrease performance
|
||||
self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
|
||||
|
@ -68,7 +79,6 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
self.draw_workspace()
|
||||
|
||||
self.line_parent = None
|
||||
self.line_color = (0.3, 0.0, 0.0, 1.0)
|
||||
self.cursor_v_line = InfiniteLine(pos=None, color=self.line_color, vertical=True,
|
||||
parent=self.line_parent)
|
||||
|
||||
|
@ -89,10 +99,12 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
self.text_collection.enabled = True
|
||||
|
||||
self.c = None
|
||||
|
||||
self.big_cursor = None
|
||||
# Keep VisPy canvas happy by letting it be "frozen" again.
|
||||
self.freeze()
|
||||
|
||||
self.graph_event_connect('mouse_wheel', self.on_mouse_scroll)
|
||||
|
||||
# draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
|
||||
# all CNC have a limited workspace
|
||||
def draw_workspace(self):
|
||||
|
@ -104,7 +116,7 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
a3l_in = np.array([(0, 0), (16.5, 0), (16.5, 11.7), (0, 11.7)])
|
||||
|
||||
a4p_mm = np.array([(0, 0), (210, 0), (210, 297), (0, 297)])
|
||||
a4l_mm = np.array([(0, 0), (297, 0), (297,210), (0, 210)])
|
||||
a4l_mm = np.array([(0, 0), (297, 0), (297, 210), (0, 210)])
|
||||
a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
|
||||
a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)])
|
||||
|
||||
|
@ -130,14 +142,14 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
self.delete_workspace()
|
||||
|
||||
self.b_line = Line(pos=a[0:2], color=(0.70, 0.3, 0.3, 1.0),
|
||||
antialias= True, method='agg', parent=self.view.scene)
|
||||
antialias=True, method='agg', parent=self.view.scene)
|
||||
self.r_line = Line(pos=a[1:3], color=(0.70, 0.3, 0.3, 1.0),
|
||||
antialias= True, method='agg', parent=self.view.scene)
|
||||
antialias=True, method='agg', parent=self.view.scene)
|
||||
|
||||
self.t_line = Line(pos=a[2:4], color=(0.70, 0.3, 0.3, 1.0),
|
||||
antialias= True, method='agg', parent=self.view.scene)
|
||||
antialias=True, method='agg', parent=self.view.scene)
|
||||
self.l_line = Line(pos=np.array((a[0], a[3])), color=(0.70, 0.3, 0.3, 1.0),
|
||||
antialias= True, method='agg', parent=self.view.scene)
|
||||
antialias=True, method='agg', parent=self.view.scene)
|
||||
|
||||
if self.fcapp.defaults['global_workspace'] is False:
|
||||
self.delete_workspace()
|
||||
|
@ -196,13 +208,33 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
return ShapeCollection(parent=self.view.scene, pool=self.fcapp.pool, **kwargs)
|
||||
|
||||
def new_cursor(self, big=None):
|
||||
"""
|
||||
Will create a mouse cursor pointer on canvas
|
||||
|
||||
:param big: if True will create a mouse cursor made out of infinite lines
|
||||
:return: the mouse cursor object
|
||||
"""
|
||||
if big is True:
|
||||
self.big_cursor = True
|
||||
self.c = CursorBig()
|
||||
|
||||
# in case there are multiple new_cursor calls, best to disconnect first the signals
|
||||
try:
|
||||
self.c.mouse_state_updated.disconnect(self.on_mouse_state)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
self.c.mouse_position_updated.disconnect(self.on_mouse_position)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
self.c.mouse_state_updated.connect(self.on_mouse_state)
|
||||
self.c.mouse_position_updated.connect(self.on_mouse_position)
|
||||
else:
|
||||
self.big_cursor = False
|
||||
self.c = Cursor(pos=np.empty((0, 2)), parent=self.view.scene)
|
||||
self.c.antialias = 0
|
||||
|
||||
return self.c
|
||||
|
||||
def on_mouse_state(self, state):
|
||||
|
@ -220,6 +252,42 @@ class PlotCanvas(QtCore.QObject, VisPyCanvas):
|
|||
self.cursor_v_line.set_data(pos=pos[0], color=self.line_color)
|
||||
self.view.scene.update()
|
||||
|
||||
def on_mouse_scroll(self, event):
|
||||
# key modifiers
|
||||
modifiers = event.modifiers
|
||||
pan_delta_x = self.fcapp.defaults["global_gridx"]
|
||||
pan_delta_y = self.fcapp.defaults["global_gridy"]
|
||||
curr_pos = event.pos
|
||||
|
||||
# Controlled pan by mouse wheel
|
||||
if 'Shift' in modifiers:
|
||||
p1 = np.array(curr_pos)[:2]
|
||||
|
||||
if event.delta[1] > 0:
|
||||
curr_pos[0] -= pan_delta_x
|
||||
else:
|
||||
curr_pos[0] += pan_delta_x
|
||||
p2 = np.array(curr_pos)[:2]
|
||||
self.view.camera.pan(p2 - p1)
|
||||
elif 'Control' in modifiers:
|
||||
p1 = np.array(curr_pos)[:2]
|
||||
|
||||
if event.delta[1] > 0:
|
||||
curr_pos[1] += pan_delta_y
|
||||
else:
|
||||
curr_pos[1] -= pan_delta_y
|
||||
p2 = np.array(curr_pos)[:2]
|
||||
self.view.camera.pan(p2 - p1)
|
||||
|
||||
if self.fcapp.grid_status() == True:
|
||||
pos_canvas = self.translate_coords(curr_pos)
|
||||
pos = self.fcapp.geo_editor.snap(pos_canvas[0], pos_canvas[1])
|
||||
|
||||
# Update cursor
|
||||
self.fcapp.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
|
||||
symbol='++', edge_color=self.fcapp.cursor_color_3D,
|
||||
size=self.fcapp.defaults["global_cursor_size"])
|
||||
|
||||
def new_text_group(self, collection=None):
|
||||
if collection:
|
||||
return TextGroup(collection)
|
||||
|
@ -308,7 +376,10 @@ class CursorBig(QtCore.QObject):
|
|||
if 'edge_color' in kwargs:
|
||||
color = kwargs['edge_color']
|
||||
else:
|
||||
color = (0.0, 0.0, 0.0, 1.0)
|
||||
if self.app.defaults['global_theme'] == 'white':
|
||||
color = '#000000FF'
|
||||
else:
|
||||
color = '#FFFFFFFF'
|
||||
|
||||
position = [pos[0][0], pos[0][1]]
|
||||
self.mouse_position_updated.emit(position)
|
||||
|
|
|
@ -77,6 +77,11 @@ class CanvasCache(QtCore.QObject):
|
|||
self.axes.set_xticks([])
|
||||
self.axes.set_yticks([])
|
||||
|
||||
if self.app.defaults['global_theme'] == 'white':
|
||||
self.axes.set_facecolor('#FFFFFF')
|
||||
else:
|
||||
self.axes.set_facecolor('#000000')
|
||||
|
||||
self.canvas = FigureCanvas(self.figure)
|
||||
|
||||
self.cache = None
|
||||
|
@ -140,6 +145,13 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
|
||||
self.app = app
|
||||
|
||||
if self.app.defaults['global_theme'] == 'white':
|
||||
theme_color = '#FFFFFF'
|
||||
tick_color = '#000000'
|
||||
else:
|
||||
theme_color = '#000000'
|
||||
tick_color = '#FFFFFF'
|
||||
|
||||
# Options
|
||||
self.x_margin = 15 # pixels
|
||||
self.y_margin = 25 # Pixels
|
||||
|
@ -149,16 +161,26 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
|
||||
# Plots go onto a single matplotlib.figure
|
||||
self.figure = Figure(dpi=50) # TODO: dpi needed?
|
||||
self.figure.patch.set_visible(False)
|
||||
self.figure.patch.set_visible(True)
|
||||
self.figure.set_facecolor(theme_color)
|
||||
|
||||
# These axes show the ticks and grid. No plotting done here.
|
||||
# New axes must have a label, otherwise mpl returns an existing one.
|
||||
self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
|
||||
self.axes.set_aspect(1)
|
||||
self.axes.grid(True)
|
||||
self.axes.grid(True, color='gray')
|
||||
self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
|
||||
self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
|
||||
|
||||
self.axes.tick_params(axis='x', color=tick_color, labelcolor=tick_color)
|
||||
self.axes.tick_params(axis='y', color=tick_color, labelcolor=tick_color)
|
||||
self.axes.spines['bottom'].set_color(tick_color)
|
||||
self.axes.spines['top'].set_color(tick_color)
|
||||
self.axes.spines['right'].set_color(tick_color)
|
||||
self.axes.spines['left'].set_color(tick_color)
|
||||
|
||||
self.axes.set_facecolor(theme_color)
|
||||
|
||||
self.ch_line = None
|
||||
self.cv_line = None
|
||||
|
||||
|
@ -264,10 +286,18 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
# else:
|
||||
# c = MplCursor(axes=axes, color='black', linewidth=1)
|
||||
|
||||
if self.app.defaults['global_theme'] == 'white':
|
||||
color = '#000000'
|
||||
else:
|
||||
color = '#FFFFFF'
|
||||
|
||||
if big is True:
|
||||
self.big_cursor = True
|
||||
self.ch_line = self.axes.axhline(color=(0.0, 0.0, 0.0), linewidth=1)
|
||||
self.cv_line = self.axes.axvline(color=(0.0, 0.0, 0.0), linewidth=1)
|
||||
self.ch_line = self.axes.axhline(color=color, linewidth=1)
|
||||
self.cv_line = self.axes.axvline(color=color, linewidth=1)
|
||||
else:
|
||||
self.big_cursor = False
|
||||
|
||||
c = FakeCursor()
|
||||
c.mouse_state_updated.connect(self.clear_cursor)
|
||||
|
||||
|
@ -283,6 +313,11 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
"""
|
||||
# there is no point in drawing mouse cursor when panning as it jumps in a confusing way
|
||||
if self.app.app_cursor.enabled is True and self.panning is False:
|
||||
if self.app.defaults['global_theme'] == 'white':
|
||||
color = '#000000'
|
||||
else:
|
||||
color = '#FFFFFF'
|
||||
|
||||
if self.big_cursor is False:
|
||||
try:
|
||||
x, y = self.app.geo_editor.snap(x_pos, y_pos)
|
||||
|
@ -291,13 +326,13 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
# The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
|
||||
# one in the OpenGL(3D) graphic engine
|
||||
pointer_size = int(float(self.app.defaults["global_cursor_size"] ) * 1.65)
|
||||
elements = self.axes.plot(x, y, 'k+', ms=pointer_size, mew=1, animated=True)
|
||||
elements = self.axes.plot(x, y, '+', color=color, ms=pointer_size, mew=1, animated=True)
|
||||
for el in elements:
|
||||
self.axes.draw_artist(el)
|
||||
except Exception as e:
|
||||
# this happen at app initialization since self.app.geo_editor does not exist yet
|
||||
# I could reshuffle the object instantiating order but what's the point? I could crash something else
|
||||
# and that's pythonic, too
|
||||
# I could reshuffle the object instantiating order but what's the point?
|
||||
# I could crash something else and that's pythonic, too
|
||||
pass
|
||||
else:
|
||||
self.ch_line.set_ydata(y_pos)
|
||||
|
@ -311,6 +346,11 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
if state is True:
|
||||
self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1])
|
||||
else:
|
||||
if self.big_cursor is True:
|
||||
self.ch_line.remove()
|
||||
self.cv_line.remove()
|
||||
self.canvas.draw_idle()
|
||||
|
||||
self.canvas.restore_region(self.background)
|
||||
self.canvas.blit(self.axes.bbox)
|
||||
|
||||
|
@ -468,7 +508,7 @@ class PlotCanvasLegacy(QtCore.QObject):
|
|||
|
||||
# Adjust axes
|
||||
for ax in self.figure.get_axes():
|
||||
ax.set_xlim((x - half_width , x + half_width))
|
||||
ax.set_xlim((x - half_width, x + half_width))
|
||||
ax.set_ylim((y - half_height, y + half_height))
|
||||
|
||||
# Re-draw
|
||||
|
@ -802,7 +842,8 @@ class ShapeCollectionLegacy:
|
|||
self.axes = self.app.plotcanvas.new_axes(axes_name)
|
||||
|
||||
def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
|
||||
update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None):
|
||||
update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None,
|
||||
linewidth=None):
|
||||
"""
|
||||
This function will add shapes to the shape collection
|
||||
|
||||
|
@ -818,6 +859,7 @@ class ShapeCollectionLegacy:
|
|||
:param gcode_parsed: not used; just for compatibility with VIsPy canvas
|
||||
:param tool_tolerance: just for compatibility with VIsPy canvas
|
||||
:param tooldia:
|
||||
:param linewidth: the width of the line
|
||||
:return:
|
||||
"""
|
||||
self._color = color[:-2] if color is not None else None
|
||||
|
@ -845,6 +887,7 @@ class ShapeCollectionLegacy:
|
|||
self.shape_dict.update({
|
||||
'color': self._color,
|
||||
'face_color': self._face_color,
|
||||
'linewidth': linewidth,
|
||||
'alpha': self._alpha,
|
||||
'shape': sh
|
||||
})
|
||||
|
@ -857,6 +900,7 @@ class ShapeCollectionLegacy:
|
|||
self.shape_dict.update({
|
||||
'color': self._color,
|
||||
'face_color': self._face_color,
|
||||
'linewidth': linewidth,
|
||||
'alpha': self._alpha,
|
||||
'shape': shape
|
||||
})
|
||||
|
@ -920,15 +964,21 @@ class ShapeCollectionLegacy:
|
|||
elif obj_type == 'geometry':
|
||||
if type(local_shapes[element]['shape']) == Polygon:
|
||||
x, y = local_shapes[element]['shape'].exterior.coords.xy
|
||||
self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
|
||||
self.axes.plot(x, y, local_shapes[element]['color'],
|
||||
linestyle='-',
|
||||
linewidth=local_shapes[element]['linewidth'])
|
||||
for ints in local_shapes[element]['shape'].interiors:
|
||||
x, y = ints.coords.xy
|
||||
self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
|
||||
self.axes.plot(x, y, local_shapes[element]['color'],
|
||||
linestyle='-',
|
||||
linewidth=local_shapes[element]['linewidth'])
|
||||
elif type(local_shapes[element]['shape']) == LineString or \
|
||||
type(local_shapes[element]['shape']) == LinearRing:
|
||||
|
||||
x, y = local_shapes[element]['shape'].coords.xy
|
||||
self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
|
||||
self.axes.plot(x, y, local_shapes[element]['color'],
|
||||
linestyle='-',
|
||||
linewidth=local_shapes[element]['linewidth'])
|
||||
|
||||
elif obj_type == 'gerber':
|
||||
if self.obj.options["multicolored"]:
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Dennis Hayrullin #
|
||||
# Date: 2/5/2016 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
import numpy as np
|
||||
from PyQt5.QtGui import QPalette
|
||||
|
@ -25,7 +25,27 @@ class VisPyCanvas(scene.SceneCanvas):
|
|||
|
||||
self.unfreeze()
|
||||
|
||||
back_color = str(QPalette().color(QPalette.Window).name())
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("axis_font_size"):
|
||||
a_fsize = settings.value('axis_font_size', type=int)
|
||||
else:
|
||||
a_fsize = 8
|
||||
|
||||
if settings.contains("theme"):
|
||||
theme = settings.value('theme', type=str)
|
||||
else:
|
||||
theme = 'white'
|
||||
|
||||
if theme == 'white':
|
||||
theme_color = Color('#FFFFFF')
|
||||
tick_color = Color('#000000')
|
||||
back_color = str(QPalette().color(QPalette.Window).name())
|
||||
else:
|
||||
theme_color = Color('#000000')
|
||||
tick_color = Color('gray')
|
||||
back_color = Color('#000000')
|
||||
# back_color = Color('#272822') # darker
|
||||
# back_color = Color('#3c3f41') # lighter
|
||||
|
||||
self.central_widget.bgcolor = back_color
|
||||
self.central_widget.border_color = back_color
|
||||
|
@ -36,18 +56,16 @@ class VisPyCanvas(scene.SceneCanvas):
|
|||
top_padding = self.grid_widget.add_widget(row=0, col=0, col_span=2)
|
||||
top_padding.height_max = 0
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("axis_font_size"):
|
||||
a_fsize = settings.value('axis_font_size', type=int)
|
||||
else:
|
||||
a_fsize = 8
|
||||
|
||||
self.yaxis = scene.AxisWidget(orientation='left', axis_color='black', text_color='black', font_size=a_fsize)
|
||||
self.yaxis = scene.AxisWidget(
|
||||
orientation='left', axis_color=tick_color, text_color=tick_color, font_size=a_fsize, axis_width=1
|
||||
)
|
||||
self.yaxis.width_max = 55
|
||||
self.grid_widget.add_widget(self.yaxis, row=1, col=0)
|
||||
|
||||
self.xaxis = scene.AxisWidget(orientation='bottom', axis_color='black', text_color='black', font_size=a_fsize,
|
||||
anchors=['center', 'bottom'])
|
||||
self.xaxis = scene.AxisWidget(
|
||||
orientation='bottom', axis_color=tick_color, text_color=tick_color, font_size=a_fsize, axis_width=1,
|
||||
anchors=['center', 'bottom']
|
||||
)
|
||||
self.xaxis.height_max = 30
|
||||
self.grid_widget.add_widget(self.xaxis, row=2, col=1)
|
||||
|
||||
|
@ -55,7 +73,7 @@ class VisPyCanvas(scene.SceneCanvas):
|
|||
# right_padding.width_max = 24
|
||||
right_padding.width_max = 0
|
||||
|
||||
view = self.grid_widget.add_view(row=1, col=1, border_color='black', bgcolor='white')
|
||||
view = self.grid_widget.add_view(row=1, col=1, border_color=tick_color, bgcolor=theme_color)
|
||||
view.camera = Camera(aspect=1, rect=(-25, -25, 150, 150))
|
||||
|
||||
# Following function was removed from 'prepare_draw()' of 'Grid' class by patch,
|
||||
|
@ -65,11 +83,22 @@ class VisPyCanvas(scene.SceneCanvas):
|
|||
self.xaxis.link_view(view)
|
||||
self.yaxis.link_view(view)
|
||||
|
||||
grid1 = scene.GridLines(parent=view.scene, color='dimgray')
|
||||
grid1.set_gl_state(depth_test=False)
|
||||
# grid1 = scene.GridLines(parent=view.scene, color='dimgray')
|
||||
# grid1.set_gl_state(depth_test=False)
|
||||
|
||||
settings = QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("theme"):
|
||||
theme = settings.value('theme', type=str)
|
||||
else:
|
||||
theme = 'white'
|
||||
|
||||
self.view = view
|
||||
self.grid = grid1
|
||||
if theme == 'white':
|
||||
self.grid = scene.GridLines(parent=self.view.scene, color='dimgray')
|
||||
else:
|
||||
self.grid = scene.GridLines(parent=self.view.scene, color='#dededeff')
|
||||
|
||||
self.grid.set_gl_state(depth_test=False)
|
||||
|
||||
self.freeze()
|
||||
|
||||
|
@ -115,6 +144,9 @@ class Camera(scene.PanZoomCamera):
|
|||
if event.handled or not self.interactive:
|
||||
return
|
||||
|
||||
# key modifiers
|
||||
modifiers = event.mouse_event.modifiers
|
||||
|
||||
# Limit mouse move events
|
||||
last_event = event.last_event
|
||||
t = time.time()
|
||||
|
@ -129,21 +161,21 @@ class Camera(scene.PanZoomCamera):
|
|||
event.handled = True
|
||||
return
|
||||
|
||||
# Scrolling
|
||||
# ################### Scrolling ##########################
|
||||
BaseCamera.viewbox_mouse_event(self, event)
|
||||
|
||||
if event.type == 'mouse_wheel':
|
||||
center = self._scene_transform.imap(event.pos)
|
||||
scale = (1 + self.zoom_factor) ** (-event.delta[1] * 30)
|
||||
self.limited_zoom(scale, center)
|
||||
if not modifiers:
|
||||
center = self._scene_transform.imap(event.pos)
|
||||
scale = (1 + self.zoom_factor) ** (-event.delta[1] * 30)
|
||||
self.limited_zoom(scale, center)
|
||||
event.handled = True
|
||||
|
||||
elif event.type == 'mouse_move':
|
||||
if event.press_event is None:
|
||||
return
|
||||
|
||||
modifiers = event.mouse_event.modifiers
|
||||
|
||||
# ################ Panning ############################
|
||||
# self.pan_button_setting is actually self.FlatCAM.APP.defaults['global_pan_button']
|
||||
if event.button == int(self.pan_button_setting) and not modifiers:
|
||||
# Translate
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Dennis Hayrullin #
|
||||
# Date: 2/5/2016 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from vispy.visuals import markers, LineVisual, InfiniteLineVisual
|
||||
from vispy.visuals.axis import Ticker, _get_ticks_talbot
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Dennis Hayrullin #
|
||||
# Date: 2/5/2016 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from OpenGL import GLU
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Dennis Hayrullin #
|
||||
# Date: 2/5/2016 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from vispy.visuals import CompoundVisual, LineVisual, MeshVisual, TextVisual, MarkersVisual
|
||||
from vispy.scene.visuals import VisualNode, generate_docstring, visuals
|
||||
|
@ -235,7 +235,7 @@ class ShapeCollectionVisual(CompoundVisual):
|
|||
self.freeze()
|
||||
|
||||
def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
|
||||
update=False, layer=1, tolerance=0.01):
|
||||
update=False, layer=1, tolerance=0.01, linewidth=None):
|
||||
"""
|
||||
Adds shape to collection
|
||||
:return:
|
||||
|
@ -253,6 +253,8 @@ class ShapeCollectionVisual(CompoundVisual):
|
|||
Layer number. 0 - lowest.
|
||||
:param tolerance: float
|
||||
Geometry simplifying tolerance
|
||||
:param linewidth: int
|
||||
Not used, for compatibility
|
||||
:return: int
|
||||
Index of shape
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
|
|
|
@ -2,30 +2,32 @@
|
|||
# Vasilis Vlachoudis
|
||||
# Date: 20-Oct-2015
|
||||
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File modified: Marius Adrian Stanciu #
|
||||
# Date: 3/10/2019 #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
|
||||
def norm(v):
|
||||
return math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
|
||||
|
||||
|
||||
def normalize_2(v):
|
||||
m = norm(v)
|
||||
return [v[0]/m, v[1]/m, v[2]/m]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Convert a B-spline to polyline with a fixed number of segments
|
||||
#
|
||||
# FIXME to become adaptive
|
||||
# ------------------------------------------------------------------------------
|
||||
def spline2Polyline(xyz, degree, closed, segments, knots):
|
||||
'''
|
||||
"""
|
||||
:param xyz: DXF spline control points
|
||||
:param degree: degree of the Spline curve
|
||||
:param closed: closed Spline
|
||||
|
@ -33,7 +35,7 @@ def spline2Polyline(xyz, degree, closed, segments, knots):
|
|||
:param segments: how many lines to use for Spline approximation
|
||||
:param knots: DXF spline knots
|
||||
:return: x,y,z coordinates (each is a list)
|
||||
'''
|
||||
"""
|
||||
|
||||
# Check if last point coincide with the first one
|
||||
if (Vector(xyz[0]) - Vector(xyz[-1])).length2() < 1e-10:
|
||||
|
@ -51,16 +53,16 @@ def spline2Polyline(xyz, degree, closed, segments, knots):
|
|||
|
||||
npts = len(xyz)
|
||||
|
||||
if degree<1 or degree>3:
|
||||
#print "invalid degree"
|
||||
return None,None,None
|
||||
if degree < 1 or degree > 3:
|
||||
# print "invalid degree"
|
||||
return None, None, None
|
||||
|
||||
# order:
|
||||
k = degree+1
|
||||
|
||||
if npts < k:
|
||||
#print "not enough control points"
|
||||
return None,None,None
|
||||
# print "not enough control points"
|
||||
return None, None, None
|
||||
|
||||
# resolution:
|
||||
nseg = segments * npts
|
||||
|
@ -72,12 +74,12 @@ def spline2Polyline(xyz, degree, closed, segments, knots):
|
|||
|
||||
i = 1
|
||||
for pt in xyz:
|
||||
b[i] = pt[0]
|
||||
b[i] = pt[0]
|
||||
b[i+1] = pt[1]
|
||||
b[i+2] = pt[2]
|
||||
i +=3
|
||||
i += 3
|
||||
|
||||
#if periodic:
|
||||
# if periodic:
|
||||
if closed:
|
||||
_rbsplinu(npts, k, nseg, b, h, p, knots)
|
||||
else:
|
||||
|
@ -86,7 +88,7 @@ def spline2Polyline(xyz, degree, closed, segments, knots):
|
|||
x = []
|
||||
y = []
|
||||
z = []
|
||||
for i in range(1,3*nseg+1,3):
|
||||
for i in range(1, 3*nseg+1, 3):
|
||||
x.append(p[i])
|
||||
y.append(p[i+1])
|
||||
z.append(p[i+2])
|
||||
|
@ -94,7 +96,8 @@ def spline2Polyline(xyz, degree, closed, segments, knots):
|
|||
# for i,xyz in enumerate(zip(x,y,z)):
|
||||
# print i,xyz
|
||||
|
||||
return x,y,z
|
||||
return x, y, z
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Subroutine to generate a B-spline open knot vector with multiplicity
|
||||
|
@ -108,12 +111,13 @@ def spline2Polyline(xyz, degree, closed, segments, knots):
|
|||
def _knot(n, order):
|
||||
x = [0.0]*(n+order+1)
|
||||
for i in range(2, n+order+1):
|
||||
if i>order and i<n+2:
|
||||
if order < i < n+2:
|
||||
x[i] = x[i-1] + 1.0
|
||||
else:
|
||||
x[i] = x[i-1]
|
||||
return x
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Subroutine to generate a B-spline uniform (periodic) knot vector.
|
||||
#
|
||||
|
@ -128,6 +132,7 @@ def _knotu(n, order):
|
|||
x[i] = float(i-1)
|
||||
return x
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Subroutine to generate rational B-spline basis functions--open knot vector
|
||||
|
||||
|
@ -163,8 +168,8 @@ def _rbasis(c, t, npts, x, h, r):
|
|||
temp[i] = 0.0
|
||||
|
||||
# calculate the higher order non-rational basis functions
|
||||
for k in range(2,c+1):
|
||||
for i in range(1,nplusc-k+1):
|
||||
for k in range(2, c+1):
|
||||
for i in range(1, nplusc-k+1):
|
||||
# if the lower order basis function is zero skip the calculation
|
||||
if temp[i] != 0.0:
|
||||
d = ((t-x[i])*temp[i])/(x[i+k-1]-x[i])
|
||||
|
@ -184,7 +189,7 @@ def _rbasis(c, t, npts, x, h, r):
|
|||
|
||||
# calculate sum for denominator of rational basis functions
|
||||
s = 0.0
|
||||
for i in range(1,npts+1):
|
||||
for i in range(1, npts+1):
|
||||
s += temp[i]*h[i]
|
||||
|
||||
# form rational basis functions and put in r vector
|
||||
|
@ -194,6 +199,7 @@ def _rbasis(c, t, npts, x, h, r):
|
|||
else:
|
||||
r[i] = 0
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Generates a rational B-spline curve using a uniform open knot vector.
|
||||
#
|
||||
|
@ -245,11 +251,12 @@ def _rbspline(npts, k, p1, b, h, p, x):
|
|||
p[icount+j] = 0.0
|
||||
# Do local matrix multiplication
|
||||
for i in range(1, npts+1):
|
||||
p[icount+j] += nbasis[i]*b[jcount]
|
||||
p[icount+j] += nbasis[i]*b[jcount]
|
||||
jcount += 3
|
||||
icount += 3
|
||||
t += step
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Subroutine to generate a rational B-spline curve using an uniform periodic knot vector
|
||||
#
|
||||
|
@ -296,23 +303,24 @@ def _rbsplinu(npts, k, p1, b, h, p, x=None):
|
|||
nbasis = [0.0]*(npts+1)
|
||||
_rbasis(k, t, npts, x, h, nbasis)
|
||||
# generate a point on the curve
|
||||
for j in range(1,4):
|
||||
for j in range(1, 4):
|
||||
jcount = j
|
||||
p[icount+j] = 0.0
|
||||
# Do local matrix multiplication
|
||||
for i in range(1,npts+1):
|
||||
for i in range(1, npts+1):
|
||||
p[icount+j] += nbasis[i]*b[jcount]
|
||||
jcount += 3
|
||||
icount += 3
|
||||
t += step
|
||||
|
||||
|
||||
# Accuracy for comparison operators
|
||||
_accuracy = 1E-15
|
||||
|
||||
|
||||
def Cmp0(x):
|
||||
"""Compare against zero within _accuracy"""
|
||||
return abs(x)<_accuracy
|
||||
return abs(x) < _accuracy
|
||||
|
||||
|
||||
def gauss(A, B):
|
||||
|
@ -337,7 +345,8 @@ def gauss(A, B):
|
|||
j = i
|
||||
ap = api
|
||||
|
||||
if j != k: p[k], p[j] = p[j], p[k] # Swap values
|
||||
if j != k:
|
||||
p[k], p[j] = p[j], p[k] # Swap values
|
||||
|
||||
for i in range(k + 1, n):
|
||||
z = A[p[i]][k] / A[p[k]][k]
|
||||
|
@ -384,20 +393,22 @@ class Vector(list):
|
|||
"""Set vector"""
|
||||
self[0] = x
|
||||
self[1] = y
|
||||
if z: self[2] = z
|
||||
if z:
|
||||
self[2] = z
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __repr__(self):
|
||||
return "[%s]" % (", ".join([repr(x) for x in self]))
|
||||
return "[%s]" % ", ".join([repr(x) for x in self])
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __str__(self):
|
||||
return "[%s]" % (", ".join([("%15g" % (x)).strip() for x in self]))
|
||||
return "[%s]" % ", ".join([("%15g" % (x)).strip() for x in self])
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def eq(self, v, acc=_accuracy):
|
||||
"""Test for equality with vector v within accuracy"""
|
||||
if len(self) != len(v): return False
|
||||
if len(self) != len(v):
|
||||
return False
|
||||
s2 = 0.0
|
||||
for a, b in zip(self, v):
|
||||
s2 += (a - b) ** 2
|
||||
|
@ -523,12 +534,12 @@ class Vector(list):
|
|||
# ----------------------------------------------------------------------
|
||||
def norm(self):
|
||||
"""Normalize vector and return length"""
|
||||
l = self.length()
|
||||
if l > 0.0:
|
||||
invlen = 1.0 / l
|
||||
length = self.length()
|
||||
if length > 0.0:
|
||||
invlen = 1.0 / length
|
||||
for i in range(len(self)):
|
||||
self[i] *= invlen
|
||||
return l
|
||||
return length
|
||||
|
||||
normalize = norm
|
||||
|
||||
|
@ -580,8 +591,9 @@ class Vector(list):
|
|||
"""return containing the direction if normalized with any of the axis"""
|
||||
|
||||
v = self.clone()
|
||||
l = v.norm()
|
||||
if abs(l) <= zero: return "O"
|
||||
length = v.norm()
|
||||
if abs(length) <= zero:
|
||||
return "O"
|
||||
|
||||
if abs(v[0] - 1.0) < zero:
|
||||
return "X"
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
# ####################################################################### ##
|
||||
# ## Borrowed code from 'https://github.com/gddc/ttfquery/blob/master/ # ##
|
||||
# ## and made it work with Python 3 ########### ##
|
||||
# ####################################################################### ##
|
||||
# ######################################################################
|
||||
# ## Borrowed code from 'https://github.com/gddc/ttfquery/blob/master/ #
|
||||
# ## and made it work with Python 3 #
|
||||
# ######################################################################
|
||||
|
||||
import re, os, sys, glob
|
||||
import itertools
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
|
@ -17,7 +17,7 @@
|
|||
# * All transformations #
|
||||
# #
|
||||
# Reference: www.w3.org/TR/SVG/Overview.html #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
# import xml.etree.ElementTree as ET
|
||||
from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path
|
||||
|
@ -136,6 +136,7 @@ def path2shapely(path, object_type, res=1.0):
|
|||
|
||||
return geometry
|
||||
|
||||
|
||||
def svgrect2shapely(rect, n_points=32):
|
||||
"""
|
||||
Converts an SVG rect into Shapely geometry.
|
||||
|
@ -284,7 +285,7 @@ def svgpolygon2shapely(polygon):
|
|||
# return LinearRing(points)
|
||||
|
||||
|
||||
def getsvggeo(node, object_type, root = None):
|
||||
def getsvggeo(node, object_type, root=None):
|
||||
"""
|
||||
Extracts and flattens all geometry from an SVG node
|
||||
into a list of Shapely geometry.
|
||||
|
@ -482,6 +483,7 @@ def getsvgtext(node, object_type, units='MM'):
|
|||
|
||||
return geo
|
||||
|
||||
|
||||
def parse_svg_point_list(ptliststr):
|
||||
"""
|
||||
Returns a list of coordinate pairs extracted from the "points"
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMObj import *
|
||||
|
@ -30,6 +29,7 @@ class ToolCalculator(FlatCAMTool):
|
|||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
self.app = app
|
||||
self.decimals = 6
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
|
@ -63,13 +63,14 @@ class ToolCalculator(FlatCAMTool):
|
|||
grid_units_layout.addWidget(inch_label, 0, 1)
|
||||
|
||||
self.inch_entry = FCEntry()
|
||||
|
||||
# self.inch_entry.setFixedWidth(70)
|
||||
self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.inch_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.inch_entry.setToolTip(_("Here you enter the value to be converted from INCH to MM"))
|
||||
|
||||
self.mm_entry = FCEntry()
|
||||
# self.mm_entry.setFixedWidth(130)
|
||||
self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.mm_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.mm_entry.setToolTip(_("Here you enter the value to be converted from MM to INCH"))
|
||||
|
||||
grid_units_layout.addWidget(self.mm_entry, 1, 0)
|
||||
|
@ -90,31 +91,35 @@ class ToolCalculator(FlatCAMTool):
|
|||
self.layout.addLayout(form_layout)
|
||||
|
||||
self.tipDia_label = QtWidgets.QLabel('%s:' % _("Tip Diameter"))
|
||||
self.tipDia_entry = FCEntry()
|
||||
# self.tipDia_entry.setFixedWidth(70)
|
||||
self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.tipDia_entry = FCDoubleSpinner()
|
||||
self.tipDia_entry.set_precision(self.decimals)
|
||||
|
||||
# self.tipDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.tipDia_label.setToolTip(
|
||||
_("This is the tool tip diameter.\n"
|
||||
"It is specified by manufacturer.")
|
||||
)
|
||||
self.tipAngle_label = QtWidgets.QLabel('%s:' % _("Tip Angle"))
|
||||
self.tipAngle_entry = FCEntry()
|
||||
# self.tipAngle_entry.setFixedWidth(70)
|
||||
self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.tipAngle_entry = FCSpinner()
|
||||
|
||||
# self.tipAngle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.tipAngle_label.setToolTip(_("This is the angle of the tip of the tool.\n"
|
||||
"It is specified by manufacturer."))
|
||||
|
||||
self.cutDepth_label = QtWidgets.QLabel('%s:' % _("Cut Z"))
|
||||
self.cutDepth_entry = FCEntry()
|
||||
# self.cutDepth_entry.setFixedWidth(70)
|
||||
self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.cutDepth_entry = FCDoubleSpinner()
|
||||
self.cutDepth_entry.setMinimum(-1e10) # to allow negative numbers without actually adding a real limit
|
||||
self.cutDepth_entry.set_precision(self.decimals)
|
||||
|
||||
# self.cutDepth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.cutDepth_label.setToolTip(_("This is the depth to cut into the material.\n"
|
||||
"In the CNCJob is the CutZ parameter."))
|
||||
|
||||
self.effectiveToolDia_label = QtWidgets.QLabel('%s:' % _("Tool Diameter"))
|
||||
self.effectiveToolDia_entry = FCEntry()
|
||||
# self.effectiveToolDia_entry.setFixedWidth(70)
|
||||
self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.effectiveToolDia_entry = FCDoubleSpinner()
|
||||
self.effectiveToolDia_entry.set_precision(self.decimals)
|
||||
|
||||
# self.effectiveToolDia_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.effectiveToolDia_label.setToolTip(_("This is the tool diameter to be entered into\n"
|
||||
"FlatCAM Gerber section.\n"
|
||||
"In the CNCJob section it is called >Tool dia<."))
|
||||
|
@ -132,9 +137,8 @@ class ToolCalculator(FlatCAMTool):
|
|||
_("Calculate either the Cut Z or the effective tool diameter,\n "
|
||||
"depending on which is desired and which is known. ")
|
||||
)
|
||||
self.empty_label = QtWidgets.QLabel(" ")
|
||||
|
||||
form_layout.addRow(self.empty_label, self.calculate_vshape_button)
|
||||
self.layout.addWidget(self.calculate_vshape_button)
|
||||
|
||||
# ####################################
|
||||
# ## ElectroPlating Tool Calculator ##
|
||||
|
@ -156,48 +160,54 @@ class ToolCalculator(FlatCAMTool):
|
|||
self.layout.addLayout(plate_form_layout)
|
||||
|
||||
self.pcblengthlabel = QtWidgets.QLabel('%s:' % _("Board Length"))
|
||||
self.pcblength_entry = FCEntry()
|
||||
# self.pcblengthlabel.setFixedWidth(70)
|
||||
self.pcblength_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.pcblength_entry = FCDoubleSpinner()
|
||||
self.pcblength_entry.set_precision(self.decimals)
|
||||
|
||||
# self.pcblength_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.pcblengthlabel.setToolTip(_('This is the board length. In centimeters.'))
|
||||
|
||||
self.pcbwidthlabel = QtWidgets.QLabel('%s:' % _("Board Width"))
|
||||
self.pcbwidth_entry = FCEntry()
|
||||
# self.pcbwidthlabel.setFixedWidth(70)
|
||||
self.pcbwidth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.pcbwidth_entry = FCDoubleSpinner()
|
||||
self.pcbwidth_entry.set_precision(self.decimals)
|
||||
|
||||
# self.pcbwidth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.pcbwidthlabel.setToolTip(_('This is the board width.In centimeters.'))
|
||||
|
||||
self.cdensity_label = QtWidgets.QLabel('%s:' % _("Current Density"))
|
||||
self.cdensity_entry = FCEntry()
|
||||
# self.cdensity_entry.setFixedWidth(70)
|
||||
self.cdensity_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.cdensity_entry = FCDoubleSpinner()
|
||||
self.cdensity_entry.set_precision(self.decimals)
|
||||
|
||||
# self.cdensity_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.cdensity_label.setToolTip(_("Current density to pass through the board. \n"
|
||||
"In Amps per Square Feet ASF."))
|
||||
|
||||
self.growth_label = QtWidgets.QLabel('%s:' % _("Copper Growth"))
|
||||
self.growth_entry = FCEntry()
|
||||
# self.growth_entry.setFixedWidth(70)
|
||||
self.growth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.growth_entry = FCDoubleSpinner()
|
||||
self.growth_entry.set_precision(self.decimals)
|
||||
|
||||
# self.growth_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.growth_label.setToolTip(_("How thick the copper growth is intended to be.\n"
|
||||
"In microns."))
|
||||
|
||||
# self.growth_entry.setEnabled(False)
|
||||
|
||||
self.cvaluelabel = QtWidgets.QLabel('%s:' % _("Current Value"))
|
||||
self.cvalue_entry = FCEntry()
|
||||
# self.cvaluelabel.setFixedWidth(70)
|
||||
self.cvalue_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.cvalue_entry = FCDoubleSpinner()
|
||||
self.cvalue_entry.set_precision(self.decimals)
|
||||
|
||||
# self.cvalue_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.cvaluelabel.setToolTip(_('This is the current intensity value\n'
|
||||
'to be set on the Power Supply. In Amps.'))
|
||||
self.cvalue_entry.setDisabled(True)
|
||||
self.cvalue_entry.setReadOnly(True)
|
||||
|
||||
self.timelabel = QtWidgets.QLabel('%s:' % _("Time"))
|
||||
self.time_entry = FCEntry()
|
||||
# self.timelabel.setFixedWidth(70)
|
||||
self.time_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.time_entry = FCDoubleSpinner()
|
||||
self.time_entry.set_precision(self.decimals)
|
||||
|
||||
# self.time_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.timelabel.setToolTip(_('This is the calculated time required for the procedure.\n'
|
||||
'In minutes.'))
|
||||
self.time_entry.setDisabled(True)
|
||||
self.time_entry.setReadOnly(True)
|
||||
|
||||
plate_form_layout.addRow(self.pcblengthlabel, self.pcblength_entry)
|
||||
plate_form_layout.addRow(self.pcbwidthlabel, self.pcbwidth_entry)
|
||||
|
@ -213,19 +223,17 @@ class ToolCalculator(FlatCAMTool):
|
|||
_("Calculate the current intensity value and the procedure time,\n"
|
||||
"depending on the parameters above")
|
||||
)
|
||||
self.empty_label_2 = QtWidgets.QLabel(" ")
|
||||
|
||||
plate_form_layout.addRow(self.empty_label_2, self.calculate_plate_button)
|
||||
self.layout.addWidget(self.calculate_plate_button)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
self.units = ''
|
||||
|
||||
# ## Signals
|
||||
self.cutDepth_entry.textChanged.connect(self.on_calculate_tool_dia)
|
||||
self.cutDepth_entry.editingFinished.connect(self.on_calculate_tool_dia)
|
||||
self.tipDia_entry.editingFinished.connect(self.on_calculate_tool_dia)
|
||||
self.tipAngle_entry.editingFinished.connect(self.on_calculate_tool_dia)
|
||||
self.cutDepth_entry.valueChanged.connect(self.on_calculate_tool_dia)
|
||||
self.cutDepth_entry.returnPressed.connect(self.on_calculate_tool_dia)
|
||||
self.tipDia_entry.returnPressed.connect(self.on_calculate_tool_dia)
|
||||
self.tipAngle_entry.returnPressed.connect(self.on_calculate_tool_dia)
|
||||
self.calculate_vshape_button.clicked.connect(self.on_calculate_tool_dia)
|
||||
|
||||
self.mm_entry.editingFinished.connect(self.on_calculate_inch_units)
|
||||
|
@ -268,8 +276,8 @@ class ToolCalculator(FlatCAMTool):
|
|||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
# ## Initialize form
|
||||
self.mm_entry.set_value('0')
|
||||
self.inch_entry.set_value('0')
|
||||
self.mm_entry.set_value('%.*f' % (self.decimals, 0))
|
||||
self.inch_entry.set_value('%.*f' % (self.decimals, 0))
|
||||
|
||||
length = self.app.defaults["tools_calc_electro_length"]
|
||||
width = self.app.defaults["tools_calc_electro_width"]
|
||||
|
@ -300,114 +308,30 @@ class ToolCalculator(FlatCAMTool):
|
|||
# effective_diameter = tip_diameter + (2 * part_of_real_dia_left_side)
|
||||
# effective diameter = tip_diameter + (2 * depth_of_cut * tangent(half_tip_angle))
|
||||
|
||||
try:
|
||||
tip_diameter = float(self.tipDia_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
tip_diameter = float(self.tipDia_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
tip_diameter = float(self.tipDia_entry.get_value())
|
||||
|
||||
try:
|
||||
half_tip_angle = float(self.tipAngle_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
half_tip_angle = float(self.tipAngle_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
half_tip_angle = float(self.tipAngle_entry.get_value())
|
||||
half_tip_angle /= 2
|
||||
|
||||
try:
|
||||
cut_depth = float(self.cutDepth_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
cut_depth = float(self.cutDepth_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
cut_depth = float(self.cutDepth_entry.get_value())
|
||||
cut_depth = -cut_depth if cut_depth < 0 else cut_depth
|
||||
|
||||
tool_diameter = tip_diameter + (2 * cut_depth * math.tan(math.radians(half_tip_angle)))
|
||||
self.effectiveToolDia_entry.set_value("%.4f" % tool_diameter)
|
||||
self.effectiveToolDia_entry.set_value("%.*f" % (self.decimals, tool_diameter))
|
||||
|
||||
def on_calculate_inch_units(self):
|
||||
try:
|
||||
mm_val = float(self.mm_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
mm_val = float(self.mm_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
self.inch_entry.set_value('%.6f' % (mm_val / 25.4))
|
||||
mm_val = float(self.mm_entry.get_value())
|
||||
self.inch_entry.set_value('%.*f' % (self.decimals,(mm_val / 25.4)))
|
||||
|
||||
def on_calculate_mm_units(self):
|
||||
try:
|
||||
inch_val = float(self.inch_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
inch_val = float(self.inch_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
self.mm_entry.set_value('%.6f' % (inch_val * 25.4))
|
||||
inch_val = float(self.inch_entry.get_value())
|
||||
self.mm_entry.set_value('%.*f' % (self.decimals,(inch_val * 25.4)))
|
||||
|
||||
def on_calculate_eplate(self):
|
||||
|
||||
try:
|
||||
length = float(self.pcblength_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
length = float(self.pcblength_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
try:
|
||||
width = float(self.pcbwidth_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
width = float(self.pcbwidth_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
try:
|
||||
density = float(self.cdensity_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
density = float(self.cdensity_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
try:
|
||||
copper = float(self.growth_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
copper = float(self.growth_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
length = float(self.pcblength_entry.get_value())
|
||||
width = float(self.pcbwidth_entry.get_value())
|
||||
density = float(self.cdensity_entry.get_value())
|
||||
copper = float(self.growth_entry.get_value())
|
||||
|
||||
calculated_current = (length * width * density) * 0.0021527820833419
|
||||
calculated_time = copper * 2.142857142857143 * float(20 / density)
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from ObjectCollection import *
|
||||
from FlatCAMApp import *
|
||||
|
@ -22,6 +29,7 @@ class CutOut(FlatCAMTool):
|
|||
|
||||
self.app = app
|
||||
self.canvas = app.plotcanvas
|
||||
self.decimals = 4
|
||||
|
||||
# Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
|
@ -87,7 +95,9 @@ class CutOut(FlatCAMTool):
|
|||
form_layout.addRow(self.kindlabel, self.obj_kind_combo)
|
||||
|
||||
# Tool Diameter
|
||||
self.dia = FCEntry()
|
||||
self.dia = FCDoubleSpinner()
|
||||
self.dia.set_precision(self.decimals)
|
||||
|
||||
self.dia_label = QtWidgets.QLabel('%s:' % _("Tool dia"))
|
||||
self.dia_label.setToolTip(
|
||||
_("Diameter of the tool used to cutout\n"
|
||||
|
@ -96,7 +106,9 @@ class CutOut(FlatCAMTool):
|
|||
form_layout.addRow(self.dia_label, self.dia)
|
||||
|
||||
# Margin
|
||||
self.margin = FCEntry()
|
||||
self.margin = FCDoubleSpinner()
|
||||
self.margin.set_precision(self.decimals)
|
||||
|
||||
self.margin_label = QtWidgets.QLabel('%s:' % _("Margin:"))
|
||||
self.margin_label.setToolTip(
|
||||
_("Margin over bounds. A positive value here\n"
|
||||
|
@ -106,7 +118,9 @@ class CutOut(FlatCAMTool):
|
|||
form_layout.addRow(self.margin_label, self.margin)
|
||||
|
||||
# Gapsize
|
||||
self.gapsize = FCEntry()
|
||||
self.gapsize = FCDoubleSpinner()
|
||||
self.gapsize.set_precision(self.decimals)
|
||||
|
||||
self.gapsize_label = QtWidgets.QLabel('%s:' % _("Gap size:"))
|
||||
self.gapsize_label.setToolTip(
|
||||
_("The size of the bridge gaps in the cutout\n"
|
||||
|
@ -381,17 +395,7 @@ class CutOut(FlatCAMTool):
|
|||
_("There is no object selected for Cutout.\nSelect one and try again."))
|
||||
return
|
||||
|
||||
try:
|
||||
dia = float(self.dia.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
dia = float(self.dia.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Tool diameter value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
dia = float(self.dia.get_value())
|
||||
if 0 in {dia}:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Tool Diameter is zero value. Change it to a positive real number."))
|
||||
|
@ -402,27 +406,8 @@ class CutOut(FlatCAMTool):
|
|||
except ValueError:
|
||||
return
|
||||
|
||||
try:
|
||||
margin = float(self.margin.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
margin = float(self.margin.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Margin value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
try:
|
||||
gapsize = float(self.gapsize.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
gapsize = float(self.gapsize.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Gap size value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
margin = float(self.margin.get_value())
|
||||
gapsize = float(self.gapsize.get_value())
|
||||
|
||||
try:
|
||||
gaps = self.gaps.get_value()
|
||||
|
@ -579,17 +564,7 @@ class CutOut(FlatCAMTool):
|
|||
if cutout_obj is None:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(name)))
|
||||
|
||||
try:
|
||||
dia = float(self.dia.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
dia = float(self.dia.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Tool diameter value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
dia = float(self.dia.get_value())
|
||||
if 0 in {dia}:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Tool Diameter is zero value. Change it to a positive real number."))
|
||||
|
@ -600,27 +575,8 @@ class CutOut(FlatCAMTool):
|
|||
except ValueError:
|
||||
return
|
||||
|
||||
try:
|
||||
margin = float(self.margin.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
margin = float(self.margin.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Margin value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
try:
|
||||
gapsize = float(self.gapsize.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
gapsize = float(self.gapsize.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Gap size value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
margin = float(self.margin.get_value())
|
||||
gapsize = float(self.gapsize.get_value())
|
||||
|
||||
try:
|
||||
gaps = self.gaps.get_value()
|
||||
|
@ -749,32 +705,13 @@ class CutOut(FlatCAMTool):
|
|||
self.app.inform.emit(_("Click on the selected geometry object perimeter to create a bridge gap ..."))
|
||||
self.app.geo_editor.tool_shape.enabled = True
|
||||
|
||||
try:
|
||||
self.cutting_dia = float(self.dia.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
self.cutting_dia = float(self.dia.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Tool diameter value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
self.cutting_dia = float(self.dia.get_value())
|
||||
if 0 in {self.cutting_dia}:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Tool Diameter is zero value. Change it to a positive real number."))
|
||||
return "Tool Diameter is zero value. Change it to a positive real number."
|
||||
|
||||
try:
|
||||
self.cutting_gapsize = float(self.gapsize.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
self.cutting_gapsize = float(self.gapsize.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Gap size value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
self.cutting_gapsize = float(self.gapsize.get_value())
|
||||
|
||||
name = self.man_object_combo.currentText()
|
||||
# Get Geometry source object to be used as target for Manual adding Gaps
|
||||
|
@ -800,7 +737,6 @@ class CutOut(FlatCAMTool):
|
|||
self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
|
||||
self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
|
||||
|
||||
|
||||
def on_manual_cutout(self, click_pos):
|
||||
name = self.man_object_combo.currentText()
|
||||
|
||||
|
@ -851,17 +787,7 @@ class CutOut(FlatCAMTool):
|
|||
"Select a Gerber file and try again."))
|
||||
return
|
||||
|
||||
try:
|
||||
dia = float(self.dia.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
dia = float(self.dia.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Tool diameter value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
dia = float(self.dia.get_value())
|
||||
if 0 in {dia}:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Tool Diameter is zero value. Change it to a positive real number."))
|
||||
|
@ -872,17 +798,7 @@ class CutOut(FlatCAMTool):
|
|||
except ValueError:
|
||||
return
|
||||
|
||||
try:
|
||||
margin = float(self.margin.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
margin = float(self.margin.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Margin value is missing or wrong format. Add it and retry."))
|
||||
return
|
||||
|
||||
margin = float(self.margin.get_value())
|
||||
convex_box = self.convex_box.get_value()
|
||||
|
||||
def geo_init(geo_obj, app_obj):
|
||||
|
|
|
@ -19,6 +19,7 @@ class DblSidedTool(FlatCAMTool):
|
|||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
self.decimals = 4
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
|
@ -219,25 +220,30 @@ class DblSidedTool(FlatCAMTool):
|
|||
grid_lay3.addWidget(self.alignment_holes, 0, 0)
|
||||
grid_lay3.addWidget(self.add_drill_point_button, 0, 1)
|
||||
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
grid0.setColumnStretch(0, 0)
|
||||
grid0.setColumnStretch(1, 1)
|
||||
|
||||
# ## Drill diameter for alignment holes
|
||||
self.dt_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Diameter'))
|
||||
self.dt_label.setToolTip(
|
||||
_("Diameter of the drill for the "
|
||||
"alignment holes.")
|
||||
)
|
||||
self.layout.addWidget(self.dt_label)
|
||||
grid0.addWidget(self.dt_label, 0, 0, 1, 2)
|
||||
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay)
|
||||
# Drill diameter value
|
||||
self.drill_dia = FCDoubleSpinner()
|
||||
self.drill_dia.set_precision(self.decimals)
|
||||
|
||||
self.drill_dia = FCEntry()
|
||||
self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
|
||||
self.dd_label.setToolTip(
|
||||
_("Diameter of the drill for the "
|
||||
"alignment holes.")
|
||||
)
|
||||
hlay.addWidget(self.dd_label)
|
||||
hlay.addWidget(self.drill_dia)
|
||||
grid0.addWidget(self.dd_label, 1, 0)
|
||||
grid0.addWidget(self.drill_dia, 1, 1)
|
||||
|
||||
hlay2 = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay2)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMObj import *
|
||||
|
@ -21,9 +20,9 @@ if '_' not in builtins.__dict__:
|
|||
_ = gettext.gettext
|
||||
|
||||
|
||||
class Measurement(FlatCAMTool):
|
||||
class Distance(FlatCAMTool):
|
||||
|
||||
toolName = _("Measurement")
|
||||
toolName = _("Distance Tool")
|
||||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
|
@ -57,26 +56,39 @@ class Measurement(FlatCAMTool):
|
|||
self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
|
||||
self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
|
||||
|
||||
self.angle_label = QtWidgets.QLabel('%s:' % _("Angle"))
|
||||
self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
|
||||
|
||||
self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
|
||||
self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
|
||||
|
||||
self.start_entry = FCEntry()
|
||||
self.start_entry.setReadOnly(True)
|
||||
self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.start_entry.setToolTip(_("This is measuring Start point coordinates."))
|
||||
|
||||
self.stop_entry = FCEntry()
|
||||
self.stop_entry.setReadOnly(True)
|
||||
self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates."))
|
||||
|
||||
self.distance_x_entry = FCEntry()
|
||||
self.distance_x_entry.setReadOnly(True)
|
||||
self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis."))
|
||||
|
||||
self.distance_y_entry = FCEntry()
|
||||
self.distance_y_entry.setReadOnly(True)
|
||||
self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis."))
|
||||
|
||||
self.angle_entry = FCEntry()
|
||||
self.angle_entry.setReadOnly(True)
|
||||
self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.angle_entry.setToolTip(_("This is orientation angle of the measuring line."))
|
||||
|
||||
self.total_distance_entry = FCEntry()
|
||||
self.total_distance_entry.setReadOnly(True)
|
||||
self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance."))
|
||||
|
||||
|
@ -89,14 +101,16 @@ class Measurement(FlatCAMTool):
|
|||
form_layout.addRow(self.stop_label, self.stop_entry)
|
||||
form_layout.addRow(self.distance_x_label, self.distance_x_entry)
|
||||
form_layout.addRow(self.distance_y_label, self.distance_y_entry)
|
||||
form_layout.addRow(self.angle_label, self.angle_entry)
|
||||
form_layout.addRow(self.total_distance_label, self.total_distance_entry)
|
||||
|
||||
# initial view of the layout
|
||||
self.start_entry.set_value('(0, 0)')
|
||||
self.stop_entry.set_value('(0, 0)')
|
||||
self.distance_x_entry.set_value('0')
|
||||
self.distance_y_entry.set_value('0')
|
||||
self.total_distance_entry.set_value('0')
|
||||
self.distance_x_entry.set_value('0.0')
|
||||
self.distance_y_entry.set_value('0.0')
|
||||
self.angle_entry.set_value('0.0')
|
||||
self.total_distance_entry.set_value('0.0')
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
|
@ -112,6 +126,12 @@ class Measurement(FlatCAMTool):
|
|||
|
||||
self.original_call_source = 'app'
|
||||
|
||||
# store here the event connection ID's
|
||||
self.mm = None
|
||||
self.mr = None
|
||||
|
||||
self.decimals = 4
|
||||
|
||||
# VisPy visuals
|
||||
if self.app.is_legacy is False:
|
||||
self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
|
||||
|
@ -122,7 +142,7 @@ class Measurement(FlatCAMTool):
|
|||
self.measure_btn.clicked.connect(self.activate_measure_tool)
|
||||
|
||||
def run(self, toggle=False):
|
||||
self.app.report_usage("ToolMeasurement()")
|
||||
self.app.report_usage("ToolDistance()")
|
||||
|
||||
self.points[:] = []
|
||||
|
||||
|
@ -132,7 +152,7 @@ class Measurement(FlatCAMTool):
|
|||
if self.app.tool_tab_locked is True:
|
||||
return
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Meas. Tool"))
|
||||
self.app.ui.notebook.setTabText(2, _("Distance Tool"))
|
||||
|
||||
# if the splitter is hidden, display it
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
|
@ -159,16 +179,17 @@ class Measurement(FlatCAMTool):
|
|||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
|
||||
|
||||
self.app.command_active = "Measurement"
|
||||
self.app.command_active = "Distance"
|
||||
|
||||
# initial view of the layout
|
||||
self.start_entry.set_value('(0, 0)')
|
||||
self.stop_entry.set_value('(0, 0)')
|
||||
|
||||
self.distance_x_entry.set_value('0')
|
||||
self.distance_y_entry.set_value('0')
|
||||
self.total_distance_entry.set_value('0')
|
||||
log.debug("Measurement Tool --> tool initialized")
|
||||
self.distance_x_entry.set_value('0.0')
|
||||
self.distance_y_entry.set_value('0.0')
|
||||
self.angle_entry.set_value('0.0')
|
||||
self.total_distance_entry.set_value('0.0')
|
||||
log.debug("Distance Tool --> tool initialized")
|
||||
|
||||
def activate_measure_tool(self):
|
||||
# ENABLE the Measuring TOOL
|
||||
|
@ -275,13 +296,13 @@ class Measurement(FlatCAMTool):
|
|||
# delete the measuring line
|
||||
self.delete_shape()
|
||||
|
||||
log.debug("Measurement Tool --> exit tool")
|
||||
log.debug("Distance Tool --> exit tool")
|
||||
|
||||
def on_mouse_click_release(self, event):
|
||||
# mouse click releases will be accepted only if the left button is clicked
|
||||
# this is necessary because right mouse click or middle mouse click
|
||||
# are used for panning on the canvas
|
||||
log.debug("Measuring Tool --> mouse click release")
|
||||
log.debug("Distance Tool --> mouse click release")
|
||||
|
||||
if event.button == 1:
|
||||
if self.app.is_legacy is False:
|
||||
|
@ -300,30 +321,44 @@ class Measurement(FlatCAMTool):
|
|||
|
||||
# Reset here the relative coordinates so there is a new reference on the click position
|
||||
if self.rel_point1 is None:
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.4f " % (0.0, 0.0))
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.*f <b>Dy</b>: "
|
||||
"%.*f " %
|
||||
(self.decimals, 0.0, self.decimals, 0.0))
|
||||
self.rel_point1 = pos
|
||||
else:
|
||||
self.rel_point2 = copy(self.rel_point1)
|
||||
self.rel_point1 = pos
|
||||
|
||||
if len(self.points) == 1:
|
||||
self.start_entry.set_value("(%.4f, %.4f)" % pos)
|
||||
self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
|
||||
self.app.inform.emit(_("MEASURING: Click on the Destination point ..."))
|
||||
elif len(self.points) == 2:
|
||||
dx = self.points[1][0] - self.points[0][0]
|
||||
dy = self.points[1][1] - self.points[0][1]
|
||||
d = sqrt(dx ** 2 + dy ** 2)
|
||||
self.stop_entry.set_value("(%.4f, %.4f)" % pos)
|
||||
self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
|
||||
|
||||
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format(
|
||||
d_x='%4f' % abs(dx), d_y='%4f' % abs(dy), d_z='%4f' % abs(d)))
|
||||
d_x='%*f' % (self.decimals, abs(dx)),
|
||||
d_y='%*f' % (self.decimals, abs(dy)),
|
||||
d_z='%*f' % (self.decimals, abs(d)))
|
||||
)
|
||||
|
||||
self.distance_x_entry.set_value('%.4f' % abs(dx))
|
||||
self.distance_y_entry.set_value('%.4f' % abs(dy))
|
||||
self.total_distance_entry.set_value('%.4f' % abs(d))
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: {0:.4f} <b>Dy</b>: "
|
||||
"{0:.4f} ".format(pos[0], pos[1]))
|
||||
self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
|
||||
self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
|
||||
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
|
||||
self.app.ui.rel_position_label.setText(
|
||||
"<b>Dx</b>: {} <b>Dy</b>: {} ".format(
|
||||
'%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
|
||||
)
|
||||
)
|
||||
self.deactivate_measure_tool()
|
||||
|
||||
def on_mouse_move_meas(self, event):
|
||||
|
@ -346,13 +381,16 @@ class Measurement(FlatCAMTool):
|
|||
|
||||
# Update cursor
|
||||
self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
|
||||
symbol='++', edge_color='black',
|
||||
symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
else:
|
||||
pos = (pos_canvas[0], pos_canvas[1])
|
||||
|
||||
self.app.ui.position_label.setText(" <b>X</b>: {0:.4f} "
|
||||
"<b>Y</b>: {0:.4f}".format(pos[0], pos[1]))
|
||||
self.app.ui.position_label.setText(
|
||||
" <b>X</b>: {} <b>Y</b>: {}".format(
|
||||
'%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
|
||||
)
|
||||
)
|
||||
|
||||
if self.rel_point1 is not None:
|
||||
dx = pos[0] - float(self.rel_point1[0])
|
||||
|
@ -361,15 +399,24 @@ class Measurement(FlatCAMTool):
|
|||
dx = pos[0]
|
||||
dy = pos[1]
|
||||
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: {0:.4f} <b>Dy</b>: "
|
||||
"{0:.4f} ".format(dx, dy))
|
||||
self.app.ui.rel_position_label.setText(
|
||||
"<b>Dx</b>: {} <b>Dy</b>: {} ".format(
|
||||
'%.*f' % (self.decimals, dx), '%.*f' % (self.decimals, dy)
|
||||
)
|
||||
)
|
||||
|
||||
# update utility geometry
|
||||
|
||||
if len(self.points) == 1:
|
||||
self.utility_geometry(pos=pos)
|
||||
# and display the temporary angle
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
log.debug("Measurement.on_mouse_move_meas() --> %s" % str(e))
|
||||
log.debug("Distance.on_mouse_move_meas() --> %s" % str(e))
|
||||
self.app.ui.position_label.setText("")
|
||||
self.app.ui.rel_position_label.setText("")
|
||||
|
||||
|
@ -380,7 +427,17 @@ class Measurement(FlatCAMTool):
|
|||
# second draw the new shape of the utility geometry
|
||||
meas_line = LineString([pos, self.points[0]])
|
||||
|
||||
color = '#00000000'
|
||||
settings = QtCore.QSettings("Open Source", "FlatCAM")
|
||||
if settings.contains("theme"):
|
||||
theme = settings.value('theme', type=str)
|
||||
else:
|
||||
theme = 'white'
|
||||
|
||||
if theme == 'white':
|
||||
color = '#000000FF'
|
||||
else:
|
||||
color = '#FFFFFFFF'
|
||||
|
||||
self.sel_shapes.add(meas_line, color=color, update=True, layer=0, tolerance=None)
|
||||
|
||||
if self.app.is_legacy is True:
|
|
@ -0,0 +1,296 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 09/29/2019 #
|
||||
# MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMObj import *
|
||||
from flatcamGUI.VisPyVisuals import *
|
||||
|
||||
from shapely.ops import nearest_points
|
||||
|
||||
from math import sqrt
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class DistanceMin(FlatCAMTool):
|
||||
|
||||
toolName = _("Minimum Distance Tool")
|
||||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
self.app = app
|
||||
self.canvas = self.app.plotcanvas
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
|
||||
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
# ## Form Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_layout)
|
||||
|
||||
self.units_label = QtWidgets.QLabel('%s:' % _("Units"))
|
||||
self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
|
||||
self.units_value = QtWidgets.QLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
|
||||
self.units_value.setDisabled(True)
|
||||
|
||||
self.start_label = QtWidgets.QLabel("%s:" % _('First object point'))
|
||||
self.start_label.setToolTip(_("This is first object point coordinates.\n"
|
||||
"This is the start point for measuring distance."))
|
||||
|
||||
self.stop_label = QtWidgets.QLabel("%s:" % _('Second object point'))
|
||||
self.stop_label.setToolTip(_("This is second object point coordinates.\n"
|
||||
"This is the end point for measuring distance."))
|
||||
|
||||
self.distance_x_label = QtWidgets.QLabel('%s:' % _("Dx"))
|
||||
self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
|
||||
|
||||
self.distance_y_label = QtWidgets.QLabel('%s:' % _("Dy"))
|
||||
self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
|
||||
|
||||
self.angle_label = QtWidgets.QLabel('%s:' % _("Angle"))
|
||||
self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
|
||||
|
||||
self.total_distance_label = QtWidgets.QLabel("<b>%s:</b>" % _('DISTANCE'))
|
||||
self.total_distance_label.setToolTip(_("This is the point to point Euclidean distance."))
|
||||
|
||||
self.half_point_label = QtWidgets.QLabel("<b>%s:</b>" % _('Half Point'))
|
||||
self.half_point_label.setToolTip(_("This is the middle point of the point to point Euclidean distance."))
|
||||
|
||||
self.start_entry = FCEntry()
|
||||
self.start_entry.setReadOnly(True)
|
||||
self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.start_entry.setToolTip(_("This is first object point coordinates.\n"
|
||||
"This is the start point for measuring distance."))
|
||||
|
||||
self.stop_entry = FCEntry()
|
||||
self.stop_entry.setReadOnly(True)
|
||||
self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.stop_entry.setToolTip(_("This is second object point coordinates.\n"
|
||||
"This is the end point for measuring distance."))
|
||||
|
||||
self.distance_x_entry = FCEntry()
|
||||
self.distance_x_entry.setReadOnly(True)
|
||||
self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis."))
|
||||
|
||||
self.distance_y_entry = FCEntry()
|
||||
self.distance_y_entry.setReadOnly(True)
|
||||
self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis."))
|
||||
|
||||
self.angle_entry = FCEntry()
|
||||
self.angle_entry.setReadOnly(True)
|
||||
self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.angle_entry.setToolTip(_("This is orientation angle of the measuring line."))
|
||||
|
||||
self.total_distance_entry = FCEntry()
|
||||
self.total_distance_entry.setReadOnly(True)
|
||||
self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.total_distance_entry.setToolTip(_("This is the point to point Euclidean distance."))
|
||||
|
||||
self.half_point_entry = FCEntry()
|
||||
self.half_point_entry.setReadOnly(True)
|
||||
self.half_point_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.half_point_entry.setToolTip(_("This is the middle point of the point to point Euclidean distance."))
|
||||
|
||||
self.measure_btn = QtWidgets.QPushButton(_("Measure"))
|
||||
self.layout.addWidget(self.measure_btn)
|
||||
|
||||
self.jump_hp_btn = QtWidgets.QPushButton(_("Jump to Half Point"))
|
||||
self.layout.addWidget(self.jump_hp_btn)
|
||||
self.jump_hp_btn.setDisabled(True)
|
||||
|
||||
form_layout.addRow(self.units_label, self.units_value)
|
||||
form_layout.addRow(self.start_label, self.start_entry)
|
||||
form_layout.addRow(self.stop_label, self.stop_entry)
|
||||
form_layout.addRow(self.distance_x_label, self.distance_x_entry)
|
||||
form_layout.addRow(self.distance_y_label, self.distance_y_entry)
|
||||
form_layout.addRow(self.angle_label, self.angle_entry)
|
||||
form_layout.addRow(self.total_distance_label, self.total_distance_entry)
|
||||
form_layout.addRow(self.half_point_label, self.half_point_entry)
|
||||
|
||||
# initial view of the layout
|
||||
self.start_entry.set_value('(0, 0)')
|
||||
self.stop_entry.set_value('(0, 0)')
|
||||
self.distance_x_entry.set_value('0.0')
|
||||
self.distance_y_entry.set_value('0.0')
|
||||
self.angle_entry.set_value('0.0')
|
||||
self.total_distance_entry.set_value('0.0')
|
||||
self.half_point_entry.set_value('(0, 0)')
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
self.decimals = 4
|
||||
self.h_point = (0, 0)
|
||||
|
||||
self.measure_btn.clicked.connect(self.activate_measure_tool)
|
||||
self.jump_hp_btn.clicked.connect(self.on_jump_to_half_point)
|
||||
|
||||
def run(self, toggle=False):
|
||||
self.app.report_usage("ToolDistanceMin()")
|
||||
|
||||
if self.app.tool_tab_locked is True:
|
||||
return
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Minimum Distance Tool"))
|
||||
|
||||
# if the splitter is hidden, display it
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
if toggle:
|
||||
pass
|
||||
|
||||
self.set_tool_ui()
|
||||
self.app.inform.emit('MEASURING: %s' %
|
||||
_("Select two objects and no more, to measure the distance between them ..."))
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='SHIFT+M', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
# Remove anything else in the GUI
|
||||
self.app.ui.tool_scroll_area.takeWidget()
|
||||
|
||||
# Put oneself in the GUI
|
||||
self.app.ui.tool_scroll_area.setWidget(self)
|
||||
|
||||
# Switch notebook to tool page
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
|
||||
|
||||
# initial view of the layout
|
||||
self.start_entry.set_value('(0, 0)')
|
||||
self.stop_entry.set_value('(0, 0)')
|
||||
|
||||
self.distance_x_entry.set_value('0.0')
|
||||
self.distance_y_entry.set_value('0.0')
|
||||
self.angle_entry.set_value('0.0')
|
||||
self.total_distance_entry.set_value('0.0')
|
||||
self.half_point_entry.set_value('(0, 0)')
|
||||
|
||||
self.jump_hp_btn.setDisabled(True)
|
||||
|
||||
log.debug("Minimum Distance Tool --> tool initialized")
|
||||
|
||||
def activate_measure_tool(self):
|
||||
# ENABLE the Measuring TOOL
|
||||
self.jump_hp_btn.setDisabled(False)
|
||||
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
|
||||
|
||||
if self.app.call_source == 'app':
|
||||
selected_objs = self.app.collection.get_selected()
|
||||
if len(selected_objs) != 2:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
|
||||
(_("Select two objects and no more. Currently the selection has objects: "),
|
||||
str(len(selected_objs))))
|
||||
return
|
||||
else:
|
||||
first_pos, last_pos = nearest_points(selected_objs[0].solid_geometry, selected_objs[1].solid_geometry)
|
||||
|
||||
elif self.app.call_source == 'geo_editor':
|
||||
selected_objs = self.app.geo_editor.selected
|
||||
if len(selected_objs) != 2:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
|
||||
(_("Select two objects and no more. Currently the selection has objects: "),
|
||||
str(len(selected_objs))))
|
||||
return
|
||||
else:
|
||||
first_pos, last_pos = nearest_points(selected_objs[0].geo, selected_objs[1].geo)
|
||||
elif self.app.call_source == 'exc_editor':
|
||||
selected_objs = self.app.exc_editor.selected
|
||||
if len(selected_objs) != 2:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
|
||||
(_("Select two objects and no more. Currently the selection has objects: "),
|
||||
str(len(selected_objs))))
|
||||
return
|
||||
else:
|
||||
# the objects are really MultiLinesStrings made out of 2 lines in cross shape
|
||||
xmin, ymin, xmax, ymax = selected_objs[0].geo.bounds
|
||||
first_geo_radius = (xmax - xmin) / 2
|
||||
first_geo_center = Point(xmin + first_geo_radius, ymin + first_geo_radius)
|
||||
first_geo = first_geo_center.buffer(first_geo_radius)
|
||||
|
||||
# the objects are really MultiLinesStrings made out of 2 lines in cross shape
|
||||
xmin, ymin, xmax, ymax = selected_objs[1].geo.bounds
|
||||
last_geo_radius = (xmax - xmin) / 2
|
||||
last_geo_center = Point(xmin + last_geo_radius, ymin + last_geo_radius)
|
||||
last_geo = last_geo_center.buffer(last_geo_radius)
|
||||
|
||||
first_pos, last_pos = nearest_points(first_geo, last_geo)
|
||||
elif self.app.call_source == 'grb_editor':
|
||||
selected_objs = self.app.grb_editor.selected
|
||||
if len(selected_objs) != 2:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s %s' %
|
||||
(_("Select two objects and no more. Currently the selection has objects: "),
|
||||
str(len(selected_objs))))
|
||||
return
|
||||
else:
|
||||
first_pos, last_pos = nearest_points(selected_objs[0].geo['solid'], selected_objs[1].geo['solid'])
|
||||
else:
|
||||
first_pos, last_pos = 0, 0
|
||||
|
||||
self.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, first_pos.x, self.decimals, first_pos.y))
|
||||
self.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, last_pos.x, self.decimals, last_pos.y))
|
||||
|
||||
dx = first_pos.x - last_pos.x
|
||||
dy = first_pos.y - last_pos.y
|
||||
|
||||
self.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
|
||||
self.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
|
||||
|
||||
try:
|
||||
angle = math.degrees(math.atan(dy / dx))
|
||||
self.angle_entry.set_value('%.*f' % (self.decimals, angle))
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
d = sqrt(dx ** 2 + dy ** 2)
|
||||
self.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
|
||||
|
||||
self.h_point = (min(first_pos.x, last_pos.x) + (abs(dx) / 2), min(first_pos.y, last_pos.y) + (abs(dy) / 2))
|
||||
if d != 0:
|
||||
self.half_point_entry.set_value(
|
||||
"(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1])
|
||||
)
|
||||
else:
|
||||
self.half_point_entry.set_value(
|
||||
"(%.*f, %.*f)" % (self.decimals, 0.0, self.decimals, 0.0)
|
||||
)
|
||||
|
||||
if d != 0:
|
||||
self.app.inform.emit(_("MEASURING: Result D(x) = {d_x} | D(y) = {d_y} | Distance = {d_z}").format(
|
||||
d_x='%*f' % (self.decimals, abs(dx)),
|
||||
d_y='%*f' % (self.decimals, abs(dy)),
|
||||
d_z='%*f' % (self.decimals, abs(d)))
|
||||
)
|
||||
else:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s: %s' %
|
||||
(_("Objects intersects or touch at"),
|
||||
"(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1])))
|
||||
|
||||
def on_jump_to_half_point(self):
|
||||
self.app.on_jump_to(custom_location=self.h_point)
|
||||
self.app.inform.emit('[success] %s: %s' %
|
||||
(_("Jumped to the half point between the two selected objects"),
|
||||
"(%.*f, %.*f)" % (self.decimals, self.h_point[0], self.decimals, self.h_point[1])))
|
||||
|
||||
def set_meas_units(self, units):
|
||||
self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")
|
||||
|
||||
# end of file
|
|
@ -1,16 +1,19 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMObj import *
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCEntry
|
||||
from flatcamGUI.GUIElements import RadioSet, FCDoubleSpinner, FCCheckBox, \
|
||||
OptionalHideInputSection, OptionalInputSection
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
@ -27,6 +30,8 @@ class Film(FlatCAMTool):
|
|||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
self.decimals = 4
|
||||
|
||||
# Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet("""
|
||||
|
@ -39,8 +44,11 @@ class Film(FlatCAMTool):
|
|||
self.layout.addWidget(title_label)
|
||||
|
||||
# Form Layout
|
||||
tf_form_layout = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(tf_form_layout)
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.layout.addLayout(grid0)
|
||||
|
||||
grid0.setColumnStretch(0, 0)
|
||||
grid0.setColumnStretch(1, 1)
|
||||
|
||||
# Type of object for which to create the film
|
||||
self.tf_type_obj_combo = QtWidgets.QComboBox()
|
||||
|
@ -60,7 +68,8 @@ class Film(FlatCAMTool):
|
|||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Film Object combobox.")
|
||||
)
|
||||
tf_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
|
||||
grid0.addWidget(self.tf_type_obj_combo_label, 0, 0)
|
||||
grid0.addWidget(self.tf_type_obj_combo, 0, 1)
|
||||
|
||||
# List of objects for which we can create the film
|
||||
self.tf_object_combo = QtWidgets.QComboBox()
|
||||
|
@ -72,7 +81,8 @@ class Film(FlatCAMTool):
|
|||
self.tf_object_label.setToolTip(
|
||||
_("Object for which to create the film.")
|
||||
)
|
||||
tf_form_layout.addRow(self.tf_object_label, self.tf_object_combo)
|
||||
grid0.addWidget(self.tf_object_label, 1, 0)
|
||||
grid0.addWidget(self.tf_object_combo, 1, 1)
|
||||
|
||||
# Type of Box Object to be used as an envelope for film creation
|
||||
# Within this we can create negative
|
||||
|
@ -93,7 +103,8 @@ class Film(FlatCAMTool):
|
|||
"The selection here decide the type of objects that will be\n"
|
||||
"in the Box Object combobox.")
|
||||
)
|
||||
tf_form_layout.addRow(self.tf_type_box_combo_label, self.tf_type_box_combo)
|
||||
grid0.addWidget(self.tf_type_box_combo_label, 2, 0)
|
||||
grid0.addWidget(self.tf_type_box_combo, 2, 1)
|
||||
|
||||
# Box
|
||||
self.tf_box_combo = QtWidgets.QComboBox()
|
||||
|
@ -108,11 +119,149 @@ class Film(FlatCAMTool):
|
|||
"Usually it is the PCB outline but it can be also the\n"
|
||||
"same object for which the film is created.")
|
||||
)
|
||||
tf_form_layout.addRow(self.tf_box_combo_label, self.tf_box_combo)
|
||||
grid0.addWidget(self.tf_box_combo_label, 3, 0)
|
||||
grid0.addWidget(self.tf_box_combo, 3, 1)
|
||||
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 4, 0)
|
||||
|
||||
self.film_adj_label = QtWidgets.QLabel('<b>%s</b>' % _("Film Adjustments"))
|
||||
self.film_adj_label.setToolTip(
|
||||
_("Sometime the printers will distort the print shape, especially the Laser types.\n"
|
||||
"This section provide the tools to compensate for the print distortions.")
|
||||
)
|
||||
|
||||
grid0.addWidget(self.film_adj_label, 5, 0, 1, 2)
|
||||
|
||||
# Scale Geometry
|
||||
self.film_scale_cb = FCCheckBox('%s' % _("Scale Film geometry"))
|
||||
self.film_scale_cb.setToolTip(
|
||||
_("A value greater than 1 will stretch the film\n"
|
||||
"while a value less than 1 will jolt it.")
|
||||
)
|
||||
self.film_scale_cb.setStyleSheet(
|
||||
"""
|
||||
QCheckBox {font-weight: bold; color: black}
|
||||
"""
|
||||
)
|
||||
grid0.addWidget(self.film_scale_cb, 6, 0, 1, 2)
|
||||
|
||||
self.film_scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
|
||||
self.film_scalex_entry = FCDoubleSpinner()
|
||||
self.film_scalex_entry.set_range(-999.9999, 999.9999)
|
||||
self.film_scalex_entry.set_precision(self.decimals)
|
||||
self.film_scalex_entry.setSingleStep(0.01)
|
||||
|
||||
grid0.addWidget(self.film_scalex_label, 7, 0)
|
||||
grid0.addWidget(self.film_scalex_entry, 7, 1)
|
||||
|
||||
self.film_scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
|
||||
self.film_scaley_entry = FCDoubleSpinner()
|
||||
self.film_scaley_entry.set_range(-999.9999, 999.9999)
|
||||
self.film_scaley_entry.set_precision(self.decimals)
|
||||
self.film_scaley_entry.setSingleStep(0.01)
|
||||
|
||||
grid0.addWidget(self.film_scaley_label, 8, 0)
|
||||
grid0.addWidget(self.film_scaley_entry, 8, 1)
|
||||
|
||||
self.ois_scale = OptionalInputSection(self.film_scale_cb, [self.film_scalex_label, self.film_scalex_entry,
|
||||
self.film_scaley_label, self.film_scaley_entry])
|
||||
# Skew Geometry
|
||||
self.film_skew_cb =FCCheckBox('%s' % _("Skew Film geometry"))
|
||||
self.film_skew_cb.setToolTip(
|
||||
_("Positive values will skew to the right\n"
|
||||
"while negative values will skew to the left.")
|
||||
)
|
||||
self.film_skew_cb.setStyleSheet(
|
||||
"""
|
||||
QCheckBox {font-weight: bold; color: black}
|
||||
"""
|
||||
)
|
||||
grid0.addWidget(self.film_skew_cb, 9, 0, 1, 2)
|
||||
|
||||
self.film_skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
|
||||
self.film_skewx_entry = FCDoubleSpinner()
|
||||
self.film_skewx_entry.set_range(-999.9999, 999.9999)
|
||||
self.film_skewx_entry.set_precision(self.decimals)
|
||||
self.film_skewx_entry.setSingleStep(0.01)
|
||||
|
||||
grid0.addWidget(self.film_skewx_label, 10, 0)
|
||||
grid0.addWidget(self.film_skewx_entry, 10, 1)
|
||||
|
||||
self.film_skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
|
||||
self.film_skewy_entry = FCDoubleSpinner()
|
||||
self.film_skewy_entry.set_range(-999.9999, 999.9999)
|
||||
self.film_skewy_entry.set_precision(self.decimals)
|
||||
self.film_skewy_entry.setSingleStep(0.01)
|
||||
|
||||
grid0.addWidget(self.film_skewy_label, 11, 0)
|
||||
grid0.addWidget(self.film_skewy_entry, 11, 1)
|
||||
|
||||
self.film_skew_ref_label = QtWidgets.QLabel('%s:' % _("Reference"))
|
||||
self.film_skew_ref_label.setToolTip(
|
||||
_("The reference point to be used as origin for the skew.\n"
|
||||
"It can be one of the four points of the geometry bounding box.")
|
||||
)
|
||||
self.film_skew_reference = RadioSet([{'label': _('Bottom Left'), 'value': 'bottomleft'},
|
||||
{'label': _('Top Left'), 'value': 'topleft'},
|
||||
{'label': _('Bottom Right'), 'value': 'bottomright'},
|
||||
{'label': _('Top right'), 'value': 'topright'}],
|
||||
orientation='vertical',
|
||||
stretch=False)
|
||||
|
||||
grid0.addWidget(self.film_skew_ref_label, 12, 0)
|
||||
grid0.addWidget(self.film_skew_reference, 12, 1)
|
||||
|
||||
self.ois_skew = OptionalInputSection(self.film_skew_cb, [self.film_skewx_label, self.film_skewx_entry,
|
||||
self.film_skewy_label, self.film_skewy_entry,
|
||||
self.film_skew_reference])
|
||||
# Mirror Geometry
|
||||
self.film_mirror_cb = FCCheckBox('%s' % _("Mirror Film geometry"))
|
||||
self.film_mirror_cb.setToolTip(
|
||||
_("Mirror the film geometry on the selected axis or on both.")
|
||||
)
|
||||
self.film_mirror_cb.setStyleSheet(
|
||||
"""
|
||||
QCheckBox {font-weight: bold; color: black}
|
||||
"""
|
||||
)
|
||||
grid0.addWidget(self.film_mirror_cb, 13, 0, 1, 2)
|
||||
|
||||
self.film_mirror_axis = RadioSet([{'label': _('None'), 'value': 'none'},
|
||||
{'label': _('X'), 'value': 'x'},
|
||||
{'label': _('Y'), 'value': 'y'},
|
||||
{'label': _('Both'), 'value': 'both'}],
|
||||
stretch=False)
|
||||
self.film_mirror_axis_label = QtWidgets.QLabel('%s:' % _("Mirror axis"))
|
||||
|
||||
grid0.addWidget(self.film_mirror_axis_label, 14, 0)
|
||||
grid0.addWidget(self.film_mirror_axis, 14, 1)
|
||||
|
||||
self.ois_mirror = OptionalInputSection(self.film_mirror_cb,
|
||||
[self.film_mirror_axis_label, self.film_mirror_axis])
|
||||
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 15, 0)
|
||||
|
||||
# Scale Stroke size
|
||||
self.film_scale_stroke_entry = FCDoubleSpinner()
|
||||
self.film_scale_stroke_entry.set_range(-999.9999, 999.9999)
|
||||
self.film_scale_stroke_entry.setSingleStep(0.01)
|
||||
self.film_scale_stroke_entry.set_precision(self.decimals)
|
||||
|
||||
self.film_scale_stroke_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
|
||||
self.film_scale_stroke_label.setToolTip(
|
||||
_("Scale the line stroke thickness of each feature in the SVG file.\n"
|
||||
"It means that the line that envelope each SVG feature will be thicker or thinner,\n"
|
||||
"therefore the fine features may be more affected by this parameter.")
|
||||
)
|
||||
grid0.addWidget(self.film_scale_stroke_label, 16, 0)
|
||||
grid0.addWidget(self.film_scale_stroke_entry, 16, 1)
|
||||
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 17, 0)
|
||||
|
||||
# Film Type
|
||||
self.film_type = RadioSet([{'label': _('Positive'), 'value': 'pos'},
|
||||
{'label': _('Negative'), 'value': 'neg'}])
|
||||
{'label': _('Negative'), 'value': 'neg'}],
|
||||
stretch=False)
|
||||
self.film_type_label = QtWidgets.QLabel(_("Film Type:"))
|
||||
self.film_type_label.setToolTip(
|
||||
_("Generate a Positive black film or a Negative film.\n"
|
||||
|
@ -122,11 +271,15 @@ class Film(FlatCAMTool):
|
|||
"with white on a black canvas.\n"
|
||||
"The Film format is SVG.")
|
||||
)
|
||||
tf_form_layout.addRow(self.film_type_label, self.film_type)
|
||||
grid0.addWidget(self.film_type_label, 18, 0)
|
||||
grid0.addWidget(self.film_type, 18, 1)
|
||||
|
||||
# Boundary for negative film generation
|
||||
self.boundary_entry = FCDoubleSpinner()
|
||||
self.boundary_entry.set_range(-999.9999, 999.9999)
|
||||
self.boundary_entry.setSingleStep(0.01)
|
||||
self.boundary_entry.set_precision(self.decimals)
|
||||
|
||||
self.boundary_entry = FCEntry()
|
||||
self.boundary_label = QtWidgets.QLabel('%s:' % _("Border"))
|
||||
self.boundary_label.setToolTip(
|
||||
_("Specify a border around the object.\n"
|
||||
|
@ -138,21 +291,74 @@ class Film(FlatCAMTool):
|
|||
"white color like the rest and which may confound with the\n"
|
||||
"surroundings if not for this border.")
|
||||
)
|
||||
tf_form_layout.addRow(self.boundary_label, self.boundary_entry)
|
||||
grid0.addWidget(self.boundary_label, 19, 0)
|
||||
grid0.addWidget(self.boundary_entry, 19, 1)
|
||||
|
||||
self.film_scale_entry = FCEntry()
|
||||
self.film_scale_label = QtWidgets.QLabel('%s:' % _("Scale Stroke"))
|
||||
self.film_scale_label.setToolTip(
|
||||
_("Scale the line stroke thickness of each feature in the SVG file.\n"
|
||||
"It means that the line that envelope each SVG feature will be thicker or thinner,\n"
|
||||
"therefore the fine features may be more affected by this parameter.")
|
||||
self.boundary_label.hide()
|
||||
self.boundary_entry.hide()
|
||||
|
||||
# Punch Drill holes
|
||||
self.punch_cb = FCCheckBox(_("Punch drill holes"))
|
||||
self.punch_cb.setToolTip(_("When checked the generated film will have holes in pads when\n"
|
||||
"the generated film is positive. This is done to help drilling,\n"
|
||||
"when done manually."))
|
||||
grid0.addWidget(self.punch_cb, 20, 0, 1, 2)
|
||||
|
||||
# this way I can hide/show the frame
|
||||
self.punch_frame = QtWidgets.QFrame()
|
||||
self.punch_frame.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.addWidget(self.punch_frame)
|
||||
punch_grid = QtWidgets.QGridLayout()
|
||||
punch_grid.setContentsMargins(0, 0, 0, 0)
|
||||
self.punch_frame.setLayout(punch_grid)
|
||||
|
||||
punch_grid.setColumnStretch(0, 0)
|
||||
punch_grid.setColumnStretch(1, 1)
|
||||
|
||||
self.ois_p = OptionalHideInputSection(self.punch_cb, [self.punch_frame])
|
||||
|
||||
self.source_label = QtWidgets.QLabel('%s:' % _("Source"))
|
||||
self.source_label.setToolTip(
|
||||
_("The punch hole source can be:\n"
|
||||
"- Excellon -> an Excellon holes center will serve as reference.\n"
|
||||
"- Pad Center -> will try to use the pads center as reference.")
|
||||
)
|
||||
tf_form_layout.addRow(self.film_scale_label, self.film_scale_entry)
|
||||
self.source_punch = RadioSet([{'label': _('Excellon'), 'value': 'exc'},
|
||||
{'label': _('Pad center'), 'value': 'pad'}],
|
||||
stretch=False)
|
||||
punch_grid.addWidget(self.source_label, 0, 0)
|
||||
punch_grid.addWidget(self.source_punch, 0, 1)
|
||||
|
||||
self.exc_label = QtWidgets.QLabel('%s:' % _("Excellon Obj"))
|
||||
self.exc_label.setToolTip(
|
||||
_("Remove the geometry of Excellon from the Film to create tge holes in pads.")
|
||||
)
|
||||
self.exc_combo = QtWidgets.QComboBox()
|
||||
self.exc_combo.setModel(self.app.collection)
|
||||
self.exc_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
|
||||
self.exc_combo.setCurrentIndex(1)
|
||||
punch_grid.addWidget(self.exc_label, 1, 0)
|
||||
punch_grid.addWidget(self.exc_combo, 1, 1)
|
||||
|
||||
self.exc_label.hide()
|
||||
self.exc_combo.hide()
|
||||
|
||||
self.punch_size_label = QtWidgets.QLabel('%s:' % _("Punch Size"))
|
||||
self.punch_size_label.setToolTip(_("The value here will control how big is the punch hole in the pads."))
|
||||
self.punch_size_spinner = FCDoubleSpinner()
|
||||
self.punch_size_spinner.set_range(0, 999.9999)
|
||||
self.punch_size_spinner.setSingleStep(0.1)
|
||||
self.punch_size_spinner.set_precision(self.decimals)
|
||||
|
||||
punch_grid.addWidget(self.punch_size_label, 2, 0)
|
||||
punch_grid.addWidget(self.punch_size_spinner, 2, 1)
|
||||
|
||||
self.punch_size_label.hide()
|
||||
self.punch_size_spinner.hide()
|
||||
|
||||
# Buttons
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay)
|
||||
hlay.addStretch()
|
||||
|
||||
self.film_object_button = QtWidgets.QPushButton(_("Save Film"))
|
||||
self.film_object_button.setToolTip(
|
||||
|
@ -170,6 +376,9 @@ class Film(FlatCAMTool):
|
|||
self.tf_type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||
self.tf_type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
|
||||
|
||||
self.film_type.activated_custom.connect(self.on_film_type)
|
||||
self.source_punch.activated_custom.connect(self.on_punch_source)
|
||||
|
||||
def on_type_obj_index_changed(self, index):
|
||||
obj_type = self.tf_type_obj_combo.currentIndex()
|
||||
self.tf_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
|
@ -216,14 +425,61 @@ class Film(FlatCAMTool):
|
|||
|
||||
f_type = self.app.defaults["tools_film_type"] if self.app.defaults["tools_film_type"] else 'neg'
|
||||
self.film_type.set_value(str(f_type))
|
||||
self.on_film_type(val=f_type)
|
||||
|
||||
b_entry = self.app.defaults["tools_film_boundary"] if self.app.defaults["tools_film_boundary"] else 0.0
|
||||
self.boundary_entry.set_value(float(b_entry))
|
||||
|
||||
scale_stroke_width = self.app.defaults["tools_film_scale"] if self.app.defaults["tools_film_scale"] else 0.0
|
||||
self.film_scale_entry.set_value(int(scale_stroke_width))
|
||||
scale_stroke_width = self.app.defaults["tools_film_scale_stroke"] if \
|
||||
self.app.defaults["tools_film_scale_stroke"] else 0.0
|
||||
self.film_scale_stroke_entry.set_value(int(scale_stroke_width))
|
||||
|
||||
self.punch_cb.set_value(False)
|
||||
self.source_punch.set_value('exc')
|
||||
|
||||
self.film_scale_cb.set_value(self.app.defaults["tools_film_scale_cb"])
|
||||
self.film_scalex_entry.set_value(float(self.app.defaults["tools_film_scale_x_entry"]))
|
||||
self.film_scaley_entry.set_value(float(self.app.defaults["tools_film_scale_y_entry"]))
|
||||
self.film_skew_cb.set_value(self.app.defaults["tools_film_skew_cb"])
|
||||
self.film_skewx_entry.set_value(float(self.app.defaults["tools_film_skew_x_entry"]))
|
||||
self.film_skewy_entry.set_value(float(self.app.defaults["tools_film_skew_y_entry"]))
|
||||
self.film_skew_reference.set_value(self.app.defaults["tools_film_skew_ref_radio"])
|
||||
self.film_mirror_cb.set_value(self.app.defaults["tools_film_mirror_cb"])
|
||||
self.film_mirror_axis.set_value(self.app.defaults["tools_film_mirror_axis_radio"])
|
||||
|
||||
def on_film_type(self, val):
|
||||
type_of_film = val
|
||||
|
||||
if type_of_film == 'neg':
|
||||
self.boundary_label.show()
|
||||
self.boundary_entry.show()
|
||||
self.punch_cb.set_value(False) # required so the self.punch_frame it's hidden also by the signal emitted
|
||||
self.punch_cb.hide()
|
||||
else:
|
||||
self.boundary_label.hide()
|
||||
self.boundary_entry.hide()
|
||||
self.punch_cb.show()
|
||||
|
||||
def on_punch_source(self, val):
|
||||
if val == 'pad' and self.punch_cb.get_value():
|
||||
self.punch_size_label.show()
|
||||
self.punch_size_spinner.show()
|
||||
self.exc_label.hide()
|
||||
self.exc_combo.hide()
|
||||
else:
|
||||
self.punch_size_label.hide()
|
||||
self.punch_size_spinner.hide()
|
||||
self.exc_label.show()
|
||||
self.exc_combo.show()
|
||||
|
||||
if val == 'pad' and self.tf_type_obj_combo.currentText() == 'Geometry':
|
||||
self.source_punch.set_value('exc')
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Using the Pad center does not work on Geometry objects. "
|
||||
"Only a Gerber object has pads."))
|
||||
|
||||
def on_film_creation(self):
|
||||
log.debug("ToolFilm.Film.on_film_creation() started ...")
|
||||
|
||||
try:
|
||||
name = self.tf_object_combo.currentText()
|
||||
except Exception as e:
|
||||
|
@ -238,59 +494,201 @@ class Film(FlatCAMTool):
|
|||
_("No FlatCAM object selected. Load an object for Box and retry."))
|
||||
return
|
||||
|
||||
try:
|
||||
border = float(self.boundary_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
border = float(self.boundary_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
|
||||
return
|
||||
scale_stroke_width = float(self.film_scale_stroke_entry.get_value())
|
||||
|
||||
try:
|
||||
scale_stroke_width = int(self.film_scale_entry.get_value())
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
|
||||
return
|
||||
source = self.source_punch.get_value()
|
||||
|
||||
if border is None:
|
||||
border = 0
|
||||
# #################################################################
|
||||
# ################ STARTING THE JOB ###############################
|
||||
# #################################################################
|
||||
|
||||
self.app.inform.emit(_("Generating Film ..."))
|
||||
|
||||
if self.film_type.get_value() == "pos":
|
||||
try:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export SVG positive"),
|
||||
directory=self.app.get_last_save_folder() + '/' + name,
|
||||
filter="*.svg")
|
||||
except TypeError:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
if str(filename) == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
|
||||
return
|
||||
if self.punch_cb.get_value() is False:
|
||||
self.generate_positive_normal_film(name, boxname, factor=scale_stroke_width)
|
||||
else:
|
||||
self.app.export_svg_positive(name, boxname, filename, scale_factor=scale_stroke_width)
|
||||
self.generate_positive_punched_film(name, boxname, source, factor=scale_stroke_width)
|
||||
else:
|
||||
self.generate_negative_film(name, boxname, factor=scale_stroke_width)
|
||||
|
||||
def generate_positive_normal_film(self, name, boxname, factor):
|
||||
log.debug("ToolFilm.Film.generate_positive_normal_film() started ...")
|
||||
|
||||
scale_factor_x = None
|
||||
scale_factor_y = None
|
||||
skew_factor_x = None
|
||||
skew_factor_y = None
|
||||
mirror = None
|
||||
skew_reference = 'center'
|
||||
|
||||
if self.film_scale_cb.get_value():
|
||||
if self.film_scalex_entry.get_value() != 1.0:
|
||||
scale_factor_x = self.film_scalex_entry.get_value()
|
||||
if self.film_scaley_entry.get_value() != 1.0:
|
||||
scale_factor_y = self.film_scaley_entry.get_value()
|
||||
if self.film_skew_cb.get_value():
|
||||
if self.film_skewx_entry.get_value() != 0.0:
|
||||
skew_factor_x = self.film_skewx_entry.get_value()
|
||||
if self.film_skewy_entry.get_value() != 0.0:
|
||||
skew_factor_y = self.film_skewy_entry.get_value()
|
||||
|
||||
skew_reference = self.film_skew_reference.get_value()
|
||||
if self.film_mirror_cb.get_value():
|
||||
if self.film_mirror_axis.get_value() != 'none':
|
||||
mirror = self.film_mirror_axis.get_value()
|
||||
try:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export SVG positive"),
|
||||
directory=self.app.get_last_save_folder() + '/' + name,
|
||||
filter="*.svg")
|
||||
except TypeError:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG positive"))
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
if str(filename) == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG positive cancelled."))
|
||||
return
|
||||
else:
|
||||
self.app.export_svg_positive(name, boxname, filename,
|
||||
scale_stroke_factor=factor,
|
||||
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
|
||||
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
|
||||
skew_reference=skew_reference,
|
||||
mirror=mirror
|
||||
)
|
||||
|
||||
def generate_positive_punched_film(self, name, boxname, source, factor):
|
||||
|
||||
film_obj = self.app.collection.get_by_name(name)
|
||||
|
||||
if source == 'exc':
|
||||
log.debug("ToolFilm.Film.generate_positive_punched_film() with Excellon source started ...")
|
||||
|
||||
try:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export SVG negative"),
|
||||
directory=self.app.get_last_save_folder() + '/' + name,
|
||||
filter="*.svg")
|
||||
except TypeError:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
if str(filename) == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
|
||||
exc_name = self.exc_combo.currentText()
|
||||
except Exception as e:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("No Excellon object selected. Load an object for punching reference and retry."))
|
||||
return
|
||||
|
||||
exc_obj = self.app.collection.get_by_name(exc_name)
|
||||
exc_solid_geometry = MultiPolygon(exc_obj.solid_geometry)
|
||||
punched_solid_geometry = MultiPolygon(film_obj.solid_geometry).difference(exc_solid_geometry)
|
||||
|
||||
def init_func(new_obj, app_obj):
|
||||
new_obj.solid_geometry = deepcopy(punched_solid_geometry)
|
||||
|
||||
outname = name + "_punched"
|
||||
self.app.new_object('gerber', outname, init_func)
|
||||
|
||||
self.generate_positive_normal_film(outname, boxname, factor=factor)
|
||||
else:
|
||||
log.debug("ToolFilm.Film.generate_positive_punched_film() with Pad center source started ...")
|
||||
|
||||
punch_size = float(self.punch_size_spinner.get_value())
|
||||
|
||||
punching_geo = list()
|
||||
for apid in film_obj.apertures:
|
||||
if film_obj.apertures[apid]['type'] == 'C':
|
||||
if punch_size >= float(film_obj.apertures[apid]['size']):
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_(" Could not generate punched hole film because the punch hole size"
|
||||
"is bigger than some of the apertures in the Gerber object."))
|
||||
return 'fail'
|
||||
else:
|
||||
for elem in film_obj.apertures[apid]['geometry']:
|
||||
if 'follow' in elem:
|
||||
if isinstance(elem['follow'], Point):
|
||||
punching_geo.append(elem['follow'].buffer(punch_size / 2))
|
||||
else:
|
||||
if punch_size >= float(film_obj.apertures[apid]['width']) or \
|
||||
punch_size >= float(film_obj.apertures[apid]['height']):
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Could not generate punched hole film because the punch hole size"
|
||||
"is bigger than some of the apertures in the Gerber object."))
|
||||
return 'fail'
|
||||
else:
|
||||
for elem in film_obj.apertures[apid]['geometry']:
|
||||
if 'follow' in elem:
|
||||
if isinstance(elem['follow'], Point):
|
||||
punching_geo.append(elem['follow'].buffer(punch_size / 2))
|
||||
|
||||
punching_geo = MultiPolygon(punching_geo)
|
||||
if not isinstance(film_obj.solid_geometry, Polygon):
|
||||
temp_solid_geometry = MultiPolygon(film_obj.solid_geometry)
|
||||
else:
|
||||
self.app.export_svg_negative(name, boxname, filename, border, scale_factor=scale_stroke_width)
|
||||
temp_solid_geometry = film_obj.solid_geometry
|
||||
punched_solid_geometry = temp_solid_geometry.difference(punching_geo)
|
||||
|
||||
if punched_solid_geometry == temp_solid_geometry:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Could not generate punched hole film because the newly created object geometry "
|
||||
"is the same as the one in the source object geometry..."))
|
||||
return 'fail'
|
||||
|
||||
def init_func(new_obj, app_obj):
|
||||
new_obj.solid_geometry = deepcopy(punched_solid_geometry)
|
||||
|
||||
outname = name + "_punched"
|
||||
self.app.new_object('gerber', outname, init_func)
|
||||
|
||||
self.generate_positive_normal_film(outname, boxname, factor=factor)
|
||||
|
||||
def generate_negative_film(self, name, boxname, factor):
|
||||
log.debug("ToolFilm.Film.generate_negative_film() started ...")
|
||||
|
||||
scale_factor_x = None
|
||||
scale_factor_y = None
|
||||
skew_factor_x = None
|
||||
skew_factor_y = None
|
||||
mirror = None
|
||||
skew_reference = 'center'
|
||||
|
||||
if self.film_scale_cb.get_value():
|
||||
if self.film_scalex_entry.get_value() != 1.0:
|
||||
scale_factor_x = self.film_scalex_entry.get_value()
|
||||
if self.film_scaley_entry.get_value() != 1.0:
|
||||
scale_factor_y = self.film_scaley_entry.get_value()
|
||||
if self.film_skew_cb.get_value():
|
||||
if self.film_skewx_entry.get_value() != 0.0:
|
||||
skew_factor_x = self.film_skewx_entry.get_value()
|
||||
if self.film_skewy_entry.get_value() != 0.0:
|
||||
skew_factor_y = self.film_skewy_entry.get_value()
|
||||
|
||||
skew_reference = self.film_skew_reference.get_value()
|
||||
if self.film_mirror_cb.get_value():
|
||||
if self.film_mirror_axis.get_value() != 'none':
|
||||
mirror = self.film_mirror_axis.get_value()
|
||||
|
||||
border = float(self.boundary_entry.get_value())
|
||||
|
||||
if border is None:
|
||||
border = 0
|
||||
|
||||
try:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(
|
||||
caption=_("Export SVG negative"),
|
||||
directory=self.app.get_last_save_folder() + '/' + name,
|
||||
filter="*.svg")
|
||||
except TypeError:
|
||||
filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export SVG negative"))
|
||||
|
||||
filename = str(filename)
|
||||
|
||||
if str(filename) == "":
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export SVG negative cancelled."))
|
||||
return
|
||||
else:
|
||||
self.app.export_svg_negative(name, boxname, filename, border,
|
||||
scale_stroke_factor=factor,
|
||||
scale_factor_x=scale_factor_x, scale_factor_y=scale_factor_y,
|
||||
skew_factor_x=skew_factor_x, skew_factor_y=skew_factor_y,
|
||||
skew_reference=skew_reference,
|
||||
mirror=mirror
|
||||
)
|
||||
|
||||
def reset_fields(self):
|
||||
self.tf_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
|
||||
from flatcamGUI.GUIElements import RadioSet, FCComboBox, IntEntry
|
||||
from flatcamGUI.GUIElements import RadioSet, FCComboBox, FCSpinner
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
|
||||
import gettext
|
||||
|
@ -59,11 +58,9 @@ class ToolImage(FlatCAMTool):
|
|||
ti_form_layout.addRow(self.tf_type_obj_combo_label, self.tf_type_obj_combo)
|
||||
|
||||
# DPI value of the imported image
|
||||
self.dpi_entry = IntEntry()
|
||||
self.dpi_entry = FCSpinner()
|
||||
self.dpi_label = QtWidgets.QLabel('%s:' % _("DPI value"))
|
||||
self.dpi_label.setToolTip(
|
||||
_("Specify a DPI value for the image.")
|
||||
)
|
||||
self.dpi_label.setToolTip(_("Specify a DPI value for the image.") )
|
||||
ti_form_layout.addRow(self.dpi_label, self.dpi_entry)
|
||||
|
||||
self.emty_lbl = QtWidgets.QLabel("")
|
||||
|
@ -86,7 +83,9 @@ class ToolImage(FlatCAMTool):
|
|||
ti2_form_layout.addRow(self.image_type_label, self.image_type)
|
||||
|
||||
# Mask value of the imported image when image monochrome
|
||||
self.mask_bw_entry = IntEntry()
|
||||
self.mask_bw_entry = FCSpinner()
|
||||
self.mask_bw_entry.set_range(0, 255)
|
||||
|
||||
self.mask_bw_label = QtWidgets.QLabel("%s <b>B/W</b>:" % _('Mask value'))
|
||||
self.mask_bw_label.setToolTip(
|
||||
_("Mask for monochrome image.\n"
|
||||
|
@ -99,7 +98,9 @@ class ToolImage(FlatCAMTool):
|
|||
ti2_form_layout.addRow(self.mask_bw_label, self.mask_bw_entry)
|
||||
|
||||
# Mask value of the imported image for RED color when image color
|
||||
self.mask_r_entry = IntEntry()
|
||||
self.mask_r_entry = FCSpinner()
|
||||
self.mask_r_entry.set_range(0, 255)
|
||||
|
||||
self.mask_r_label = QtWidgets.QLabel("%s <b>R:</b>" % _('Mask value'))
|
||||
self.mask_r_label.setToolTip(
|
||||
_("Mask for RED color.\n"
|
||||
|
@ -110,7 +111,9 @@ class ToolImage(FlatCAMTool):
|
|||
ti2_form_layout.addRow(self.mask_r_label, self.mask_r_entry)
|
||||
|
||||
# Mask value of the imported image for GREEN color when image color
|
||||
self.mask_g_entry = IntEntry()
|
||||
self.mask_g_entry = FCSpinner()
|
||||
self.mask_g_entry.set_range(0, 255)
|
||||
|
||||
self.mask_g_label = QtWidgets.QLabel("%s <b>G:</b>" % _('Mask value'))
|
||||
self.mask_g_label.setToolTip(
|
||||
_("Mask for GREEN color.\n"
|
||||
|
@ -121,7 +124,9 @@ class ToolImage(FlatCAMTool):
|
|||
ti2_form_layout.addRow(self.mask_g_label, self.mask_g_entry)
|
||||
|
||||
# Mask value of the imported image for BLUE color when image color
|
||||
self.mask_b_entry = IntEntry()
|
||||
self.mask_b_entry = FCSpinner()
|
||||
self.mask_b_entry.set_range(0, 255)
|
||||
|
||||
self.mask_b_label = QtWidgets.QLabel("%s <b>B:</b>" % _('Mask value'))
|
||||
self.mask_b_label.setToolTip(
|
||||
_("Mask for BLUE color.\n"
|
||||
|
@ -132,15 +137,11 @@ class ToolImage(FlatCAMTool):
|
|||
ti2_form_layout.addRow(self.mask_b_label, self.mask_b_entry)
|
||||
|
||||
# Buttons
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
self.layout.addLayout(hlay)
|
||||
hlay.addStretch()
|
||||
|
||||
self.import_button = QtWidgets.QPushButton(_("Import image"))
|
||||
self.import_button.setToolTip(
|
||||
_("Open a image of raster type and then import it in FlatCAM.")
|
||||
)
|
||||
hlay.addWidget(self.import_button)
|
||||
self.layout.addWidget(self.import_button)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
|
@ -50,6 +49,10 @@ class ToolMove(FlatCAMTool):
|
|||
from flatcamGUI.PlotCanvasLegacy import ShapeCollectionLegacy
|
||||
self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="move")
|
||||
|
||||
self.mm = None
|
||||
self.mp = None
|
||||
self.kr = None
|
||||
|
||||
self.replot_signal[list].connect(self.replot)
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
|
@ -90,14 +93,19 @@ class ToolMove(FlatCAMTool):
|
|||
# signal that there is a command active and it is 'Move'
|
||||
self.app.command_active = "Move"
|
||||
|
||||
if self.app.collection.get_selected():
|
||||
sel_obj_list = self.app.collection.get_selected()
|
||||
if sel_obj_list:
|
||||
self.app.inform.emit(_("MOVE: Click on the Start point ..."))
|
||||
|
||||
# if we have an object selected then we can safely activate the mouse events
|
||||
self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_move)
|
||||
self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.on_left_click)
|
||||
self.kr = self.app.plotcanvas.graph_event_connect('key_release', self.on_key_press)
|
||||
|
||||
# draw the selection box
|
||||
self.draw_sel_bbox()
|
||||
else:
|
||||
self.setVisible(False)
|
||||
# signal that there is no command active
|
||||
self.app.command_active = None
|
||||
self.toggle()
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("MOVE action cancelled. No object(s) to move."))
|
||||
|
||||
def on_left_click(self, event):
|
||||
|
@ -143,7 +151,9 @@ class ToolMove(FlatCAMTool):
|
|||
dx = pos[0] - self.point1[0]
|
||||
dy = pos[1] - self.point1[1]
|
||||
|
||||
obj_list = self.app.collection.get_selected()
|
||||
# move only the objects selected and plotted and visible
|
||||
obj_list = [obj for obj in self.app.collection.get_selected()
|
||||
if obj.options['plot'] and obj.visible is True]
|
||||
|
||||
def job_move(app_obj):
|
||||
with self.app.proc_container.new(_("Moving...")) as proc:
|
||||
|
@ -152,20 +162,21 @@ class ToolMove(FlatCAMTool):
|
|||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("No object(s) selected."))
|
||||
return "fail"
|
||||
|
||||
# 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):
|
||||
sel_obj.ui.aperture_table_visibility_cb.setChecked(False)
|
||||
|
||||
# offset solid_geometry
|
||||
sel_obj.offset((dx, dy))
|
||||
# sel_obj.plot()
|
||||
|
||||
try:
|
||||
sel_obj.replotApertures.emit()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
for sel_obj in obj_list:
|
||||
# offset solid_geometry
|
||||
sel_obj.offset((dx, dy))
|
||||
|
||||
# Update the object bounding box options
|
||||
a, b, c, d = sel_obj.bounds()
|
||||
sel_obj.options['xmin'] = a
|
||||
|
@ -254,38 +265,33 @@ class ToolMove(FlatCAMTool):
|
|||
ymaxlist = []
|
||||
|
||||
obj_list = self.app.collection.get_selected()
|
||||
if not obj_list:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Object(s) not selected"))
|
||||
self.toggle()
|
||||
else:
|
||||
# if we have an object selected then we can safely activate the mouse events
|
||||
self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_move)
|
||||
self.mp = self.app.plotcanvas.graph_event_connect('mouse_press', self.on_left_click)
|
||||
self.kr = self.app.plotcanvas.graph_event_connect('key_release', self.on_key_press)
|
||||
# first get a bounding box to fit all
|
||||
for obj in obj_list:
|
||||
|
||||
# first get a bounding box to fit all
|
||||
for obj in obj_list:
|
||||
# don't move disabled objects, move only plotted objects
|
||||
if obj.options['plot']:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
xminlist.append(xmin)
|
||||
yminlist.append(ymin)
|
||||
xmaxlist.append(xmax)
|
||||
ymaxlist.append(ymax)
|
||||
|
||||
# get the minimum x,y and maximum x,y for all objects selected
|
||||
xminimal = min(xminlist)
|
||||
yminimal = min(yminlist)
|
||||
xmaximal = max(xmaxlist)
|
||||
ymaximal = max(ymaxlist)
|
||||
# get the minimum x,y and maximum x,y for all objects selected
|
||||
xminimal = min(xminlist)
|
||||
yminimal = min(yminlist)
|
||||
xmaximal = max(xmaxlist)
|
||||
ymaximal = max(ymaxlist)
|
||||
|
||||
p1 = (xminimal, yminimal)
|
||||
p2 = (xmaximal, yminimal)
|
||||
p3 = (xmaximal, ymaximal)
|
||||
p4 = (xminimal, ymaximal)
|
||||
p1 = (xminimal, yminimal)
|
||||
p2 = (xmaximal, yminimal)
|
||||
p3 = (xmaximal, ymaximal)
|
||||
p4 = (xminimal, ymaximal)
|
||||
|
||||
self.old_coords = [p1, p2, p3, p4]
|
||||
self.draw_shape(Polygon(self.old_coords))
|
||||
self.old_coords = [p1, p2, p3, p4]
|
||||
self.draw_shape(Polygon(self.old_coords))
|
||||
|
||||
if self.app.is_legacy is True:
|
||||
self.sel_shapes.redraw()
|
||||
if self.app.is_legacy is True:
|
||||
self.sel_shapes.redraw()
|
||||
|
||||
def update_sel_bbox(self, pos):
|
||||
self.delete_shape()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Modified by: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from copy import copy, deepcopy
|
||||
|
@ -27,6 +26,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.decimals = 4
|
||||
|
||||
FlatCAMTool.__init__(self, app)
|
||||
Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"])
|
||||
|
@ -213,14 +213,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.addtool_entry_lbl.setToolTip(
|
||||
_("Diameter for the new tool to add in the Tool Table")
|
||||
)
|
||||
self.addtool_entry = FCEntry2()
|
||||
self.addtool_entry = FCDoubleSpinner()
|
||||
self.addtool_entry.set_precision(self.decimals)
|
||||
|
||||
form.addRow(self.addtool_entry_lbl, self.addtool_entry)
|
||||
|
||||
# Tip Dia
|
||||
self.tipdialabel = QtWidgets.QLabel('%s:' % _('V-Tip Dia'))
|
||||
self.tipdialabel.setToolTip(
|
||||
_("The tip diameter for V-Shape Tool"))
|
||||
self.tipdia_entry = LengthEntry()
|
||||
self.tipdia_entry = FCDoubleSpinner()
|
||||
self.tipdia_entry.set_precision(self.decimals)
|
||||
self.tipdia_entry.setSingleStep(0.1)
|
||||
|
||||
form.addRow(self.tipdialabel, self.tipdia_entry)
|
||||
|
||||
# Tip Angle
|
||||
|
@ -228,7 +233,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.tipanglelabel.setToolTip(
|
||||
_("The tip angle for V-Shape Tool.\n"
|
||||
"In degree."))
|
||||
self.tipangle_entry = LengthEntry()
|
||||
self.tipangle_entry = FCDoubleSpinner()
|
||||
self.tipangle_entry.set_precision(self.decimals)
|
||||
self.tipangle_entry.setSingleStep(5)
|
||||
|
||||
form.addRow(self.tipanglelabel, self.tipangle_entry)
|
||||
|
||||
grid2 = QtWidgets.QGridLayout()
|
||||
|
@ -271,7 +279,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
_("Depth of cut into material. Negative value.\n"
|
||||
"In FlatCAM units.")
|
||||
)
|
||||
self.cutz_entry = FloatEntry()
|
||||
self.cutz_entry = FCDoubleSpinner()
|
||||
self.cutz_entry.set_precision(self.decimals)
|
||||
self.cutz_entry.set_range(-99999, -0.00000000000001)
|
||||
|
||||
self.cutz_entry.setToolTip(
|
||||
_("Depth of cut into material. Negative value.\n"
|
||||
"In FlatCAM units.")
|
||||
|
@ -305,7 +316,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
_("Bounding box margin.")
|
||||
)
|
||||
grid3.addWidget(nccmarginlabel, 3, 0)
|
||||
self.ncc_margin_entry = FCEntry()
|
||||
self.ncc_margin_entry = FCDoubleSpinner()
|
||||
self.ncc_margin_entry.set_precision(self.decimals)
|
||||
|
||||
grid3.addWidget(self.ncc_margin_entry, 3, 1)
|
||||
|
||||
# Method
|
||||
|
@ -448,30 +461,39 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
)
|
||||
self.tools_box.addWidget(self.generate_ncc_button)
|
||||
self.tools_box.addStretch()
|
||||
# ############################ FINSIHED GUI ###################################
|
||||
# #############################################################################
|
||||
|
||||
# #############################################################################
|
||||
# ###################### Setup CONTEXT MENU ###################################
|
||||
# #############################################################################
|
||||
self.tools_table.setupContextMenu()
|
||||
self.tools_table.addContextMenu(
|
||||
"Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
|
||||
"Add", self.on_add_tool_by_key, icon=QtGui.QIcon("share/plus16.png"))
|
||||
self.tools_table.addContextMenu(
|
||||
"Delete", lambda:
|
||||
self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
|
||||
|
||||
# #############################################################################
|
||||
# ########################## VARIABLES ########################################
|
||||
# #############################################################################
|
||||
self.units = ''
|
||||
self.ncc_tools = {}
|
||||
self.ncc_tools = dict()
|
||||
self.tooluid = 0
|
||||
|
||||
# store here the default data for Geometry Data
|
||||
self.default_data = {}
|
||||
self.default_data = dict()
|
||||
|
||||
self.obj_name = ""
|
||||
self.ncc_obj = None
|
||||
|
||||
self.sel_rect = []
|
||||
self.sel_rect = list()
|
||||
|
||||
self.bound_obj_name = ""
|
||||
self.bound_obj = None
|
||||
|
||||
self.ncc_dia_list = []
|
||||
self.iso_dia_list = []
|
||||
self.ncc_dia_list = list()
|
||||
self.iso_dia_list = list()
|
||||
self.has_offset = None
|
||||
self.o_name = None
|
||||
self.overlap = None
|
||||
|
@ -485,11 +507,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
|
||||
self.mm = None
|
||||
self.mr = None
|
||||
|
||||
# store here solid_geometry when there are tool with isolation job
|
||||
self.solid_geometry = []
|
||||
self.solid_geometry = list()
|
||||
|
||||
self.tool_type_item_options = []
|
||||
self.select_method = None
|
||||
self.tool_type_item_options = list()
|
||||
|
||||
self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
|
||||
|
||||
# #############################################################################
|
||||
# ############################ SGINALS ########################################
|
||||
# #############################################################################
|
||||
self.addtool_btn.clicked.connect(self.on_tool_add)
|
||||
self.addtool_entry.returnPressed.connect(self.on_tool_add)
|
||||
self.deltool_btn.clicked.connect(self.on_tool_delete)
|
||||
|
@ -508,6 +537,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
self.object_combo.setCurrentIndex(0)
|
||||
|
||||
def on_add_tool_by_key(self):
|
||||
tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
|
||||
text='%s:' % _('Enter a Tool Diameter'),
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Please enter a tool diameter with non-zero value, in Float format."))
|
||||
return
|
||||
self.on_tool_add(dia=float(val))
|
||||
else:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
|
||||
|
||||
|
@ -546,6 +591,13 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.app.ui.notebook.setTabText(2, _("NCC Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
if self.units == "IN":
|
||||
self.decimals = 4
|
||||
else:
|
||||
self.decimals = 2
|
||||
|
||||
self.tools_frame.show()
|
||||
|
||||
self.ncc_order_radio.set_value(self.app.defaults["tools_nccorder"])
|
||||
|
@ -619,7 +671,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.tooluid += 1
|
||||
self.ncc_tools.update({
|
||||
int(self.tooluid): {
|
||||
'tooldia': float('%.4f' % tool_dia),
|
||||
'tooldia': float('%.*f' % (self.decimals, tool_dia)),
|
||||
'offset': 'Path',
|
||||
'offset_value': 0.0,
|
||||
'type': 'Iso',
|
||||
|
@ -651,7 +703,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
|
||||
sorted_tools = []
|
||||
for k, v in self.ncc_tools.items():
|
||||
sorted_tools.append(float('%.4f' % float(v['tooldia'])))
|
||||
if self.units == "IN":
|
||||
sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
|
||||
else:
|
||||
sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
|
||||
|
||||
order = self.ncc_order_radio.get_value()
|
||||
if order == 'fwd':
|
||||
|
@ -667,7 +722,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
|
||||
for tool_sorted in sorted_tools:
|
||||
for tooluid_key, tooluid_value in self.ncc_tools.items():
|
||||
if float('%.4f' % tooluid_value['tooldia']) == tool_sorted:
|
||||
if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
|
||||
tool_id += 1
|
||||
id_ = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
|
||||
id_.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
|
@ -675,12 +730,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.tools_table.setItem(row_no, 0, id_) # Tool name/id
|
||||
|
||||
# Make sure that the drill diameter when in MM is with no more than 2 decimals
|
||||
# There are no drill bits in MM with more than 3 decimals diameter
|
||||
# For INCH the decimals should be no more than 3. There are no drills under 10mils
|
||||
if self.units == 'MM':
|
||||
dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
|
||||
else:
|
||||
dia = QtWidgets.QTableWidgetItem('%.4f' % tooluid_value['tooldia'])
|
||||
# There are no drill bits in MM with more than 2 decimals diameter
|
||||
# For INCH the decimals should be no more than 4. There are no drills under 10mils
|
||||
dia = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, tooluid_value['tooldia']))
|
||||
|
||||
dia.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
|
@ -867,36 +919,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
else:
|
||||
if self.tool_type_radio.get_value() == 'V':
|
||||
|
||||
try:
|
||||
tip_dia = float(self.tipdia_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
tip_dia = float(self.tipdia_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, "
|
||||
"use a number."))
|
||||
return
|
||||
tip_dia = float(self.tipdia_entry.get_value())
|
||||
tip_angle = float(self.tipangle_entry.get_value()) / 2
|
||||
cut_z = float(self.cutz_entry.get_value())
|
||||
|
||||
try:
|
||||
tip_angle = float(self.tipangle_entry.get_value()) / 2
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
tip_angle = float(self.tipangle_entry.get_value().replace(',', '.')) / 2
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
try:
|
||||
cut_z = float(self.cutz_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
cut_z = float(self.cutz_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
|
||||
return
|
||||
# calculated tool diameter so the cut_z parameter is obeyed
|
||||
tool_dia = tip_dia + 2 * cut_z * math.tan(math.radians(tip_angle))
|
||||
|
||||
|
@ -921,10 +947,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter to add, in Float format."))
|
||||
return
|
||||
|
||||
if self.units == 'MM':
|
||||
tool_dia = float('%.2f' % tool_dia)
|
||||
else:
|
||||
tool_dia = float('%.4f' % tool_dia)
|
||||
tool_dia = float('%.*f' % (self.decimals, tool_dia))
|
||||
|
||||
if tool_dia == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Please enter a tool diameter with non-zero value, "
|
||||
|
@ -948,9 +971,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
for k, v in self.ncc_tools.items():
|
||||
for tool_v in v.keys():
|
||||
if tool_v == 'tooldia':
|
||||
tool_dias.append(float('%.4f' % v[tool_v]))
|
||||
tool_dias.append(float('%.*f' % (self.decimals, (v[tool_v]))))
|
||||
|
||||
if float('%.4f' % tool_dia) in tool_dias:
|
||||
if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
|
||||
if muted is None:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Adding tool cancelled. Tool already in Tool Table."))
|
||||
self.tools_table.itemChanged.connect(self.on_tool_edit)
|
||||
|
@ -960,7 +983,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.app.inform.emit('[success] %s' % _("New tool added to Tool Table."))
|
||||
self.ncc_tools.update({
|
||||
int(self.tooluid): {
|
||||
'tooldia': float('%.4f' % tool_dia),
|
||||
'tooldia': float('%.*f' % (self.decimals, tool_dia)),
|
||||
'offset': 'Path',
|
||||
'offset_value': 0.0,
|
||||
'type': 'Iso',
|
||||
|
@ -981,7 +1004,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
for k, v in self.ncc_tools.items():
|
||||
for tool_v in v.keys():
|
||||
if tool_v == 'tooldia':
|
||||
tool_dias.append(float('%.4f' % v[tool_v]))
|
||||
tool_dias.append(float('%.*f' % (self.decimals, v[tool_v])))
|
||||
|
||||
for row in range(self.tools_table.rowCount()):
|
||||
|
||||
|
@ -1061,22 +1084,18 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
self.build_ui()
|
||||
|
||||
def on_ncc_click(self):
|
||||
"""
|
||||
Slot for clicking signal of the self.generate.ncc_button
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# init values for the next usage
|
||||
self.reset_usage()
|
||||
|
||||
self.app.report_usage("on_paint_button_click")
|
||||
|
||||
try:
|
||||
self.overlap = float(self.ncc_overlap_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
self.overlap = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, "
|
||||
"use a number."))
|
||||
return
|
||||
self.overlap = float(self.ncc_overlap_entry.get_value())
|
||||
|
||||
self.grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
|
||||
|
||||
if self.overlap >= 1 or self.overlap < 0:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Overlap value must be between "
|
||||
|
@ -1085,9 +1104,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
|
||||
self.connect = self.ncc_connect_cb.get_value()
|
||||
self.contour = self.ncc_contour_cb.get_value()
|
||||
|
||||
self.has_offset = self.ncc_choice_offset_cb.isChecked()
|
||||
|
||||
self.rest = self.ncc_rest_cb.get_value()
|
||||
|
||||
self.obj_name = self.object_combo.currentText()
|
||||
|
@ -1282,7 +1299,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
|
||||
|
||||
self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
|
||||
symbol='++', edge_color='black', size=self.app.defaults["global_cursor_size"])
|
||||
symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
# update the positions on status bar
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
|
@ -1339,12 +1357,16 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
:param rest: True if to use rest-machining
|
||||
:param tools_storage: whether to use the current tools_storage self.ncc_tools or a different one.
|
||||
Usage of the different one is related to when this function is called from a TcL command.
|
||||
:param plot: if True after the job is finished the result will be plotted, else it will not.
|
||||
:param run_threaded: If True the method will be run in a threaded way suitable for GUI usage; if False it will
|
||||
run non-threaded for TclShell usage
|
||||
:return:
|
||||
"""
|
||||
|
||||
proc = self.app.proc_container.new(_("Non-Copper clearing ..."))
|
||||
if run_threaded:
|
||||
proc = self.app.proc_container.new(_("Non-Copper clearing ..."))
|
||||
else:
|
||||
self.app.proc_container.view.set_busy(_("Non-Copper clearing ..."))
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
# #####################################################################
|
||||
# ####### Read the parameters #########################################
|
||||
|
@ -1360,22 +1382,15 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
if margin is not None:
|
||||
ncc_margin = margin
|
||||
else:
|
||||
try:
|
||||
ncc_margin = float(self.ncc_margin_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
ncc_margin = float(self.ncc_margin_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Wrong value format entered, use a number."))
|
||||
return
|
||||
ncc_margin = float(self.ncc_margin_entry.get_value())
|
||||
|
||||
if select_method is not None:
|
||||
ncc_select = select_method
|
||||
else:
|
||||
ncc_select = self.reference_radio.get_value()
|
||||
|
||||
overlap = overlap if overlap else self.app.defaults["tools_nccoverlap"]
|
||||
overlap = overlap if overlap else float(self.app.defaults["tools_nccoverlap"])
|
||||
|
||||
connect = connect if connect else self.app.defaults["tools_nccconnect"]
|
||||
contour = contour if contour else self.app.defaults["tools_ncccontour"]
|
||||
order = order if order else self.ncc_order_radio.get_value()
|
||||
|
@ -1514,6 +1529,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
assert isinstance(geo_obj, FlatCAMGeometry), \
|
||||
"Initializer expected a FlatCAMGeometry, 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:
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
log.debug("NCC Tool. Normal copper clearing task started.")
|
||||
self.app.inform.emit(_("NCC Tool. Finished non-copper polygons. Normal copper clearing task started."))
|
||||
|
||||
|
@ -1592,6 +1611,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
else:
|
||||
try:
|
||||
for geo_elem in isolated_geo:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
@ -1641,7 +1663,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
break
|
||||
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_iso):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals,
|
||||
tool_iso)):
|
||||
current_uid = int(k)
|
||||
# add the solid_geometry to the current too in self.paint_tools dictionary
|
||||
# and then reset the temporary list that stored that solid_geometry
|
||||
|
@ -1696,6 +1719,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
app_obj.inform.emit(
|
||||
'[success] %s %s%s %s' % (_('NCC Tool clearing with tool diameter = '),
|
||||
str(tool),
|
||||
|
@ -1730,6 +1756,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
if len(area.geoms) > 0:
|
||||
pol_nr = 0
|
||||
for p in area.geoms:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
@ -1737,15 +1766,15 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
try:
|
||||
if isinstance(p, Polygon):
|
||||
if ncc_method == 'standard':
|
||||
cp = self.clear_polygon(p, tool, self.app.defaults["gerber_circle_steps"],
|
||||
cp = self.clear_polygon(p, tool, self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour, connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
elif ncc_method == 'seed':
|
||||
cp = self.clear_polygon2(p, tool, self.app.defaults["gerber_circle_steps"],
|
||||
cp = self.clear_polygon2(p, tool, self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour, connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
else:
|
||||
cp = self.clear_polygon3(p, tool, self.app.defaults["gerber_circle_steps"],
|
||||
cp = self.clear_polygon3(p, tool, self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour, connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
if cp:
|
||||
|
@ -1755,19 +1784,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
if pol is not None:
|
||||
if ncc_method == 'standard':
|
||||
cp = self.clear_polygon(pol, tool,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour,
|
||||
connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
elif ncc_method == 'seed':
|
||||
cp = self.clear_polygon2(pol, tool,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour,
|
||||
connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
else:
|
||||
cp = self.clear_polygon3(pol, tool,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour,
|
||||
connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
|
@ -1799,7 +1828,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
# find the tooluid associated with the current tool_dia so we know where to add the tool
|
||||
# solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals,
|
||||
tool)):
|
||||
current_uid = int(k)
|
||||
|
||||
# add the solid_geometry to the current too in self.paint_tools dictionary
|
||||
|
@ -1838,7 +1868,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
if geo_obj.tools[tooluid]['solid_geometry']:
|
||||
has_solid_geo += 1
|
||||
if has_solid_geo == 0:
|
||||
app_obj.inform.emit('[ERROR] %s' % _("There is no Painting Geometry in the file.\n"
|
||||
app_obj.inform.emit('[ERROR] %s' % _("There is no NCC Geometry in the file.\n"
|
||||
"Usually it means that the tool diameter is too big "
|
||||
"for the painted geometry.\n"
|
||||
"Change the painting parameters and try again."))
|
||||
|
@ -1865,6 +1895,10 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
log.debug("NCC Tool. Rest machining copper clearing task started.")
|
||||
app_obj.inform.emit('_(NCC Tool. Rest machining copper clearing task started.')
|
||||
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
if not run_threaded:
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
# a flag to signal that the isolation is broken by the bounding box in 'area' and 'box' cases
|
||||
# will store the number of tools for which the isolation is broken
|
||||
warning_flag = 0
|
||||
|
@ -1920,6 +1954,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
else:
|
||||
try:
|
||||
for geo_elem in isolated_geo:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
@ -1972,7 +2009,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
break
|
||||
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_iso):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals,
|
||||
tool_iso)):
|
||||
current_uid = int(k)
|
||||
# add the solid_geometry to the current too in self.paint_tools dictionary
|
||||
# and then reset the temporary list that stored that solid_geometry
|
||||
|
@ -2047,6 +2085,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
|
||||
# Area to clear
|
||||
for poly in cleared_by_last_tool:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
@ -2083,21 +2124,24 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
raise FlatCAMApp.GracefulException
|
||||
|
||||
if p is not None:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
if isinstance(p, Polygon):
|
||||
try:
|
||||
if ncc_method == 'standard':
|
||||
cp = self.clear_polygon(p, tool_used,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour, connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
elif ncc_method == 'seed':
|
||||
cp = self.clear_polygon2(p, tool_used,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour, connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
else:
|
||||
cp = self.clear_polygon3(p, tool_used,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour, connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
cleared_geo.append(list(cp.get_objects()))
|
||||
|
@ -2109,22 +2153,25 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
elif isinstance(p, MultiPolygon):
|
||||
for poly in p:
|
||||
if poly is not None:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
try:
|
||||
if ncc_method == 'standard':
|
||||
cp = self.clear_polygon(poly, tool_used,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour,
|
||||
connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
elif ncc_method == 'seed':
|
||||
cp = self.clear_polygon2(poly, tool_used,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour,
|
||||
connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
else:
|
||||
cp = self.clear_polygon3(poly, tool_used,
|
||||
self.app.defaults["gerber_circle_steps"],
|
||||
self.grb_circle_steps,
|
||||
overlap=overlap, contour=contour,
|
||||
connect=connect,
|
||||
prog_plot=prog_plot)
|
||||
|
@ -2172,7 +2219,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
# find the tooluid associated with the current tool_dia so we know
|
||||
# where to add the tool solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals,
|
||||
tool)):
|
||||
current_uid = int(k)
|
||||
|
||||
# add the solid_geometry to the current too in self.paint_tools dictionary
|
||||
|
@ -2219,13 +2267,19 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
else:
|
||||
app_obj.new_object("geometry", name, gen_clear_area, plot=plot)
|
||||
except FlatCAMApp.GracefulException:
|
||||
proc.done()
|
||||
if run_threaded:
|
||||
proc.done()
|
||||
return
|
||||
except Exception as e:
|
||||
proc.done()
|
||||
if run_threaded:
|
||||
proc.done()
|
||||
traceback.print_stack()
|
||||
return
|
||||
proc.done()
|
||||
if run_threaded:
|
||||
proc.done()
|
||||
else:
|
||||
app_obj.proc_container.view.set_idle()
|
||||
|
||||
# focus on Selected Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
|
||||
|
||||
|
@ -2614,6 +2668,9 @@ class NonCopperClear(FlatCAMTool, Gerber):
|
|||
except Exception as e:
|
||||
try:
|
||||
for el in target:
|
||||
# provide the app with a way to process the GUI events when in a blocking loop
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
|
|
@ -0,0 +1,550 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 09/27/2019 #
|
||||
# MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMObj import *
|
||||
from shapely.geometry import Point
|
||||
from shapely import affinity
|
||||
from shapely.ops import nearest_points
|
||||
from PyQt5 import QtCore
|
||||
|
||||
import gettext
|
||||
import FlatCAMTranslation as fcTranslate
|
||||
import builtins
|
||||
|
||||
fcTranslate.apply_language('strings')
|
||||
if '_' not in builtins.__dict__:
|
||||
_ = gettext.gettext
|
||||
|
||||
|
||||
class ToolOptimal(FlatCAMTool):
|
||||
|
||||
toolName = _("Optimal Tool")
|
||||
|
||||
update_text = pyqtSignal(list)
|
||||
update_sec_distances = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
self.decimals = 4
|
||||
|
||||
# ############################################################################
|
||||
# ############################ GUI creation ##################################
|
||||
# ## Title
|
||||
title_label = QtWidgets.QLabel("%s" % self.toolName)
|
||||
title_label.setStyleSheet(
|
||||
"""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
self.layout.addWidget(title_label)
|
||||
|
||||
# ## Form Layout
|
||||
form_lay = QtWidgets.QFormLayout()
|
||||
self.layout.addLayout(form_lay)
|
||||
|
||||
form_lay.addRow(QtWidgets.QLabel(""))
|
||||
|
||||
# ## Gerber Object to mirror
|
||||
self.gerber_object_combo = QtWidgets.QComboBox()
|
||||
self.gerber_object_combo.setModel(self.app.collection)
|
||||
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.gerber_object_combo.setCurrentIndex(1)
|
||||
|
||||
self.gerber_object_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
|
||||
self.gerber_object_label.setToolTip(
|
||||
"Gerber object for which to find the minimum distance between copper features."
|
||||
)
|
||||
form_lay.addRow(self.gerber_object_label, self.gerber_object_combo)
|
||||
|
||||
# Precision = nr of decimals
|
||||
self.precision_label = QtWidgets.QLabel('%s:' % _("Precision"))
|
||||
self.precision_label.setToolTip(_("Number of decimals kept for found distances."))
|
||||
|
||||
self.precision_spinner = FCSpinner()
|
||||
self.precision_spinner.set_range(2, 10)
|
||||
self.precision_spinner.setWrapping(True)
|
||||
form_lay.addRow(self.precision_label, self.precision_spinner)
|
||||
|
||||
# Results Title
|
||||
self.title_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Minimum distance"))
|
||||
self.title_res_label.setToolTip(_("Display minimum distance between copper features."))
|
||||
form_lay.addRow(self.title_res_label)
|
||||
|
||||
# Result value
|
||||
self.result_label = QtWidgets.QLabel('%s:' % _("Determined"))
|
||||
self.result_entry = FCEntry()
|
||||
self.result_entry.setReadOnly(True)
|
||||
|
||||
self.units_lbl = QtWidgets.QLabel(self.units.lower())
|
||||
self.units_lbl.setDisabled(True)
|
||||
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
hlay.addWidget(self.result_entry)
|
||||
hlay.addWidget(self.units_lbl)
|
||||
|
||||
form_lay.addRow(self.result_label, hlay)
|
||||
|
||||
# Frequency of minimum encounter
|
||||
self.freq_label = QtWidgets.QLabel('%s:' % _("Occurring"))
|
||||
self.freq_label.setToolTip(_("How many times this minimum is found."))
|
||||
self.freq_entry = FCEntry()
|
||||
self.freq_entry.setReadOnly(True)
|
||||
form_lay.addRow(self.freq_label, self.freq_entry)
|
||||
|
||||
# Control if to display the locations of where the minimum was found
|
||||
self.locations_cb = FCCheckBox(_("Minimum points coordinates"))
|
||||
self.locations_cb.setToolTip(_("Coordinates for points where minimum distance was found."))
|
||||
form_lay.addRow(self.locations_cb)
|
||||
|
||||
# Locations where minimum was found
|
||||
self.locations_textb = FCTextArea(parent=self)
|
||||
self.locations_textb.setReadOnly(True)
|
||||
stylesheet = """
|
||||
QTextEdit { selection-background-color:blue;
|
||||
selection-color:white;
|
||||
}
|
||||
"""
|
||||
|
||||
self.locations_textb.setStyleSheet(stylesheet)
|
||||
form_lay.addRow(self.locations_textb)
|
||||
|
||||
# Jump button
|
||||
self.locate_button = QtWidgets.QPushButton(_("Jump to selected position"))
|
||||
self.locate_button.setToolTip(
|
||||
_("Select a position in the Locations text box and then\n"
|
||||
"click this button.")
|
||||
)
|
||||
self.locate_button.setMinimumWidth(60)
|
||||
self.locate_button.setDisabled(True)
|
||||
form_lay.addRow(self.locate_button)
|
||||
|
||||
# Other distances in Gerber
|
||||
self.title_second_res_label = QtWidgets.QLabel('<b>%s:</b>' % _("Other distances"))
|
||||
self.title_second_res_label.setToolTip(_("Will display other distances in the Gerber file ordered from\n"
|
||||
"the minimum to the maximum, not including the absolute minimum."))
|
||||
form_lay.addRow(self.title_second_res_label)
|
||||
|
||||
# Control if to display the locations of where the minimum was found
|
||||
self.sec_locations_cb = FCCheckBox(_("Other distances points coordinates"))
|
||||
self.sec_locations_cb.setToolTip(_("Other distances and the coordinates for points\n"
|
||||
"where the distance was found."))
|
||||
form_lay.addRow(self.sec_locations_cb)
|
||||
|
||||
# this way I can hide/show the frame
|
||||
self.sec_locations_frame = QtWidgets.QFrame()
|
||||
self.sec_locations_frame.setContentsMargins(0, 0, 0, 0)
|
||||
self.layout.addWidget(self.sec_locations_frame)
|
||||
self.distances_box = QtWidgets.QVBoxLayout()
|
||||
self.distances_box.setContentsMargins(0, 0, 0, 0)
|
||||
self.sec_locations_frame.setLayout(self.distances_box)
|
||||
|
||||
# Other Distances label
|
||||
self.distances_label = QtWidgets.QLabel('%s' % _("Gerber distances"))
|
||||
self.distances_label.setToolTip(_("Other distances and the coordinates for points\n"
|
||||
"where the distance was found."))
|
||||
self.distances_box.addWidget(self.distances_label)
|
||||
|
||||
# Other distances
|
||||
self.distances_textb = FCTextArea(parent=self)
|
||||
self.distances_textb.setReadOnly(True)
|
||||
stylesheet = """
|
||||
QTextEdit { selection-background-color:blue;
|
||||
selection-color:white;
|
||||
}
|
||||
"""
|
||||
|
||||
self.distances_textb.setStyleSheet(stylesheet)
|
||||
self.distances_box.addWidget(self.distances_textb)
|
||||
|
||||
self.distances_box.addWidget(QtWidgets.QLabel(''))
|
||||
|
||||
# Other Locations label
|
||||
self.locations_label = QtWidgets.QLabel('%s' % _("Points coordinates"))
|
||||
self.locations_label.setToolTip(_("Other distances and the coordinates for points\n"
|
||||
"where the distance was found."))
|
||||
self.distances_box.addWidget(self.locations_label)
|
||||
|
||||
# Locations where minimum was found
|
||||
self.locations_sec_textb = FCTextArea(parent=self)
|
||||
self.locations_sec_textb.setReadOnly(True)
|
||||
stylesheet = """
|
||||
QTextEdit { selection-background-color:blue;
|
||||
selection-color:white;
|
||||
}
|
||||
"""
|
||||
|
||||
self.locations_sec_textb.setStyleSheet(stylesheet)
|
||||
self.distances_box.addWidget(self.locations_sec_textb)
|
||||
|
||||
# Jump button
|
||||
self.locate_sec_button = QtWidgets.QPushButton(_("Jump to selected position"))
|
||||
self.locate_sec_button.setToolTip(
|
||||
_("Select a position in the Locations text box and then\n"
|
||||
"click this button.")
|
||||
)
|
||||
self.locate_sec_button.setMinimumWidth(60)
|
||||
self.locate_sec_button.setDisabled(True)
|
||||
self.distances_box.addWidget(self.locate_sec_button)
|
||||
|
||||
# GO button
|
||||
self.calculate_button = QtWidgets.QPushButton(_("Find Minimum"))
|
||||
self.calculate_button.setToolTip(
|
||||
_("Calculate the minimum distance between copper features,\n"
|
||||
"this will allow the determination of the right tool to\n"
|
||||
"use for isolation or copper clearing.")
|
||||
)
|
||||
self.calculate_button.setMinimumWidth(60)
|
||||
self.layout.addWidget(self.calculate_button)
|
||||
|
||||
self.loc_ois = OptionalHideInputSection(self.locations_cb, [self.locations_textb, self.locate_button])
|
||||
self.sec_loc_ois = OptionalHideInputSection(self.sec_locations_cb, [self.sec_locations_frame])
|
||||
# ################## Finished GUI creation ###################################
|
||||
# ############################################################################
|
||||
|
||||
# this is the line selected in the textbox with the locations of the minimum
|
||||
self.selected_text = ''
|
||||
|
||||
# this is the line selected in the textbox with the locations of the other distances found in the Gerber object
|
||||
self.selected_locations_text = ''
|
||||
|
||||
# dict to hold the distances between every two elements in Gerber as keys and the actual locations where that
|
||||
# distances happen as values
|
||||
self.min_dict = dict()
|
||||
|
||||
# ############################################################################
|
||||
# ############################ Signals #######################################
|
||||
# ############################################################################
|
||||
self.calculate_button.clicked.connect(self.find_minimum_distance)
|
||||
self.locate_button.clicked.connect(self.on_locate_position)
|
||||
self.update_text.connect(self.on_update_text)
|
||||
self.locations_textb.cursorPositionChanged.connect(self.on_textbox_clicked)
|
||||
|
||||
self.locate_sec_button.clicked.connect(self.on_locate_sec_position)
|
||||
self.update_sec_distances.connect(self.on_update_sec_distances_txt)
|
||||
self.distances_textb.cursorPositionChanged.connect(self.on_distances_textb_clicked)
|
||||
self.locations_sec_textb.cursorPositionChanged.connect(self.on_locations_sec_clicked)
|
||||
|
||||
self.layout.addStretch()
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+O', **kwargs)
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.report_usage("ToolOptimal()")
|
||||
|
||||
self.result_entry.set_value(0.0)
|
||||
self.freq_entry.set_value('0')
|
||||
|
||||
if toggle:
|
||||
# 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:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
else:
|
||||
try:
|
||||
if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
|
||||
# if tab is populated with the tool but it does not have the focus, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
|
||||
else:
|
||||
self.app.ui.splitter.setSizes([0, 1])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
FlatCAMTool.run(self)
|
||||
self.set_tool_ui()
|
||||
|
||||
self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.precision_spinner.set_value(int(self.app.defaults["tools_opt_precision"]))
|
||||
self.locations_textb.clear()
|
||||
# new cursor - select all document
|
||||
cursor = self.locations_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.Document)
|
||||
|
||||
# clear previous selection highlight
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.clearBackground()
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
self.locations_textb.setVisible(False)
|
||||
self.locate_button.setVisible(False)
|
||||
|
||||
self.result_entry.set_value(0.0)
|
||||
self.freq_entry.set_value('0')
|
||||
self.reset_fields()
|
||||
|
||||
def find_minimum_distance(self):
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
self.decimals = int(self.precision_spinner.get_value())
|
||||
|
||||
selection_index = self.gerber_object_combo.currentIndex()
|
||||
|
||||
model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
|
||||
try:
|
||||
fcobj = model_index.internalPointer().obj
|
||||
except Exception as e:
|
||||
log.debug("ToolOptimal.find_minimum_distance() --> %s" % str(e))
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
|
||||
return
|
||||
|
||||
if not isinstance(fcobj, FlatCAMGerber):
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
|
||||
return
|
||||
|
||||
proc = self.app.proc_container.new(_("Working..."))
|
||||
|
||||
def job_thread(app_obj):
|
||||
app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
|
||||
try:
|
||||
old_disp_number = 0
|
||||
pol_nr = 0
|
||||
app_obj.proc_container.update_view_text(' %d%%' % 0)
|
||||
total_geo = list()
|
||||
|
||||
for ap in list(fcobj.apertures.keys()):
|
||||
if 'geometry' in fcobj.apertures[ap]:
|
||||
app_obj.inform.emit(
|
||||
'%s: %s' % (_("Optimal Tool. Parsing geometry for aperture"), str(ap)))
|
||||
|
||||
for geo_el in fcobj.apertures[ap]['geometry']:
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
||||
if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
|
||||
total_geo.append(geo_el['solid'])
|
||||
|
||||
app_obj.inform.emit(
|
||||
_("Optimal Tool. Creating a buffer for the object geometry."))
|
||||
total_geo = MultiPolygon(total_geo)
|
||||
total_geo = total_geo.buffer(0)
|
||||
|
||||
try:
|
||||
__ = iter(total_geo)
|
||||
geo_len = len(total_geo)
|
||||
geo_len = (geo_len * (geo_len - 1)) / 2
|
||||
except TypeError:
|
||||
app_obj.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("The Gerber object has one Polygon as geometry.\n"
|
||||
"There are no distances between geometry elements to be found."))
|
||||
return 'fail'
|
||||
|
||||
app_obj.inform.emit(
|
||||
'%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
|
||||
str(geo_len)))
|
||||
|
||||
self.min_dict = dict()
|
||||
idx = 1
|
||||
for geo in total_geo:
|
||||
for s_geo in total_geo[idx:]:
|
||||
if self.app.abort_flag:
|
||||
# graceful abort requested by the user
|
||||
raise FlatCAMApp.GracefulException
|
||||
|
||||
# minimize the number of distances by not taking into considerations those that are too small
|
||||
dist = geo.distance(s_geo)
|
||||
dist = float('%.*f' % (self.decimals, dist))
|
||||
loc_1, loc_2 = nearest_points(geo, s_geo)
|
||||
|
||||
proc_loc = (
|
||||
(float('%.*f' % (self.decimals, loc_1.x)), float('%.*f' % (self.decimals, loc_1.y))),
|
||||
(float('%.*f' % (self.decimals, loc_2.x)), float('%.*f' % (self.decimals, loc_2.y)))
|
||||
)
|
||||
|
||||
if dist in self.min_dict:
|
||||
self.min_dict[dist].append(proc_loc)
|
||||
else:
|
||||
self.min_dict[dist] = [proc_loc]
|
||||
|
||||
pol_nr += 1
|
||||
disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
|
||||
|
||||
if old_disp_number < disp_number <= 100:
|
||||
app_obj.proc_container.update_view_text(' %d%%' % disp_number)
|
||||
old_disp_number = disp_number
|
||||
idx += 1
|
||||
|
||||
app_obj.inform.emit(
|
||||
_("Optimal Tool. Finding the minimum distance."))
|
||||
|
||||
min_list = list(self.min_dict.keys())
|
||||
min_dist = min(min_list)
|
||||
min_dist_string = '%.*f' % (self.decimals, float(min_dist))
|
||||
self.result_entry.set_value(min_dist_string)
|
||||
|
||||
freq = len(self.min_dict[min_dist])
|
||||
freq = '%d' % int(freq)
|
||||
self.freq_entry.set_value(freq)
|
||||
|
||||
min_locations = self.min_dict.pop(min_dist)
|
||||
|
||||
self.update_text.emit(min_locations)
|
||||
self.update_sec_distances.emit(self.min_dict)
|
||||
|
||||
app_obj.inform.emit('[success] %s' % _("Optimal Tool. Finished successfully."))
|
||||
except Exception as ee:
|
||||
proc.done()
|
||||
log.debug(str(ee))
|
||||
return
|
||||
proc.done()
|
||||
|
||||
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
|
||||
|
||||
def on_locate_position(self):
|
||||
# cursor = self.locations_textb.textCursor()
|
||||
# self.selected_text = cursor.selectedText()
|
||||
|
||||
try:
|
||||
if self.selected_text != '':
|
||||
loc = eval(self.selected_text)
|
||||
else:
|
||||
return 'fail'
|
||||
except Exception as e:
|
||||
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'
|
||||
|
||||
try:
|
||||
loc_1 = loc[0]
|
||||
loc_2 = loc[1]
|
||||
dx = loc_1[0] - loc_2[0]
|
||||
dy = loc_1[1] - loc_2[1]
|
||||
loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
|
||||
float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
|
||||
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'
|
||||
|
||||
def on_update_text(self, data):
|
||||
txt = ''
|
||||
for loc in data:
|
||||
if loc:
|
||||
txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
|
||||
self.locations_textb.setPlainText(txt)
|
||||
self.locate_button.setDisabled(False)
|
||||
|
||||
def on_textbox_clicked(self):
|
||||
# new cursor - select all document
|
||||
cursor = self.locations_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.Document)
|
||||
|
||||
# clear previous selection highlight
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.clearBackground()
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
# new cursor - select the current line
|
||||
cursor = self.locations_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.LineUnderCursor)
|
||||
|
||||
# highlight the current selected line
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
self.selected_text = cursor.selectedText()
|
||||
|
||||
def on_update_sec_distances_txt(self, data):
|
||||
distance_list = sorted(list(data.keys()))
|
||||
txt = ''
|
||||
for loc in distance_list:
|
||||
txt += '%s\n' % str(loc)
|
||||
self.distances_textb.setPlainText(txt)
|
||||
self.locate_sec_button.setDisabled(False)
|
||||
|
||||
def on_distances_textb_clicked(self):
|
||||
# new cursor - select all document
|
||||
cursor = self.distances_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.Document)
|
||||
|
||||
# clear previous selection highlight
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.clearBackground()
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
# new cursor - select the current line
|
||||
cursor = self.distances_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.LineUnderCursor)
|
||||
|
||||
# highlight the current selected line
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
distance_text = cursor.selectedText()
|
||||
key_in_min_dict = eval(distance_text)
|
||||
self.on_update_locations_text(dist=key_in_min_dict)
|
||||
|
||||
def on_update_locations_text(self, dist):
|
||||
distance_list = self.min_dict[dist]
|
||||
txt = ''
|
||||
for loc in distance_list:
|
||||
if loc:
|
||||
txt += '%s, %s\n' % (str(loc[0]), str(loc[1]))
|
||||
self.locations_sec_textb.setPlainText(txt)
|
||||
|
||||
def on_locations_sec_clicked(self):
|
||||
# new cursor - select all document
|
||||
cursor = self.locations_sec_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.Document)
|
||||
|
||||
# clear previous selection highlight
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.clearBackground()
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
# new cursor - select the current line
|
||||
cursor = self.locations_sec_textb.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.LineUnderCursor)
|
||||
|
||||
# highlight the current selected line
|
||||
tmp = cursor.blockFormat()
|
||||
tmp.setBackground(QtGui.QBrush(QtCore.Qt.yellow))
|
||||
cursor.setBlockFormat(tmp)
|
||||
|
||||
self.selected_locations_text = cursor.selectedText()
|
||||
|
||||
def on_locate_sec_position(self):
|
||||
try:
|
||||
if self.selected_locations_text != '':
|
||||
loc = eval(self.selected_locations_text)
|
||||
else:
|
||||
return 'fail'
|
||||
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'
|
||||
|
||||
try:
|
||||
loc_1 = loc[0]
|
||||
loc_2 = loc[1]
|
||||
dx = loc_1[0] - loc_2[0]
|
||||
dy = loc_1[1] - loc_2[1]
|
||||
loc = (float('%.*f' % (self.decimals, (min(loc_1[0], loc_2[0]) + (abs(dx) / 2)))),
|
||||
float('%.*f' % (self.decimals, (min(loc_1[1], loc_2[1]) + (abs(dy) / 2)))))
|
||||
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'
|
||||
|
||||
def reset_fields(self):
|
||||
self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
|
||||
self.gerber_object_combo.setCurrentIndex(0)
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 4/23/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from shapely.geometry import Point, Polygon, LineString
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Modified: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
|
@ -26,6 +25,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.decimals = 4
|
||||
|
||||
FlatCAMTool.__init__(self, app)
|
||||
Geometry.__init__(self, geo_steps_per_circle=self.app.defaults["geometry_circle_steps"])
|
||||
|
@ -156,19 +156,14 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
form.addRow(self.order_label, self.order_radio)
|
||||
|
||||
# ### Add a new Tool ## ##
|
||||
hlay = QtWidgets.QHBoxLayout()
|
||||
self.tools_box.addLayout(hlay)
|
||||
|
||||
self.addtool_entry_lbl = QtWidgets.QLabel('<b>%s:</b>' % _('Tool Dia'))
|
||||
self.addtool_entry_lbl.setToolTip(
|
||||
_("Diameter for the new tool.")
|
||||
)
|
||||
self.addtool_entry = FCEntry2()
|
||||
self.addtool_entry = FCDoubleSpinner()
|
||||
self.addtool_entry.set_precision(self.decimals)
|
||||
|
||||
# hlay.addWidget(self.addtool_label)
|
||||
# hlay.addStretch()
|
||||
hlay.addWidget(self.addtool_entry_lbl)
|
||||
hlay.addWidget(self.addtool_entry)
|
||||
form.addRow(self.addtool_entry_lbl, self.addtool_entry)
|
||||
|
||||
grid2 = QtWidgets.QGridLayout()
|
||||
self.tools_box.addLayout(grid2)
|
||||
|
@ -200,6 +195,8 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
grid3 = QtWidgets.QGridLayout()
|
||||
self.tools_box.addLayout(grid3)
|
||||
grid3.setColumnStretch(0, 0)
|
||||
grid3.setColumnStretch(1, 1)
|
||||
|
||||
# Overlap
|
||||
ovlabel = QtWidgets.QLabel('%s:' % _('Overlap Rate'))
|
||||
|
@ -230,7 +227,9 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
"be painted.")
|
||||
)
|
||||
grid3.addWidget(marginlabel, 2, 0)
|
||||
self.paintmargin_entry = FCEntry()
|
||||
self.paintmargin_entry = FCDoubleSpinner()
|
||||
self.paintmargin_entry.set_precision(self.decimals)
|
||||
|
||||
grid3.addWidget(self.paintmargin_entry, 2, 1)
|
||||
|
||||
# Method
|
||||
|
@ -351,6 +350,8 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
self.tools_box.addWidget(self.generate_paint_button)
|
||||
|
||||
self.tools_box.addStretch()
|
||||
# #################################### FINSIHED GUI #####################################
|
||||
# #######################################################################################
|
||||
|
||||
self.obj_name = ""
|
||||
self.paint_obj = None
|
||||
|
@ -412,7 +413,9 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
self.tool_type_item_options = ["C1", "C2", "C3", "C4", "B", "V"]
|
||||
|
||||
# ## Signals
|
||||
# #############################################################################
|
||||
# ################################# Signals ###################################
|
||||
# #############################################################################
|
||||
self.addtool_btn.clicked.connect(self.on_tool_add)
|
||||
self.addtool_entry.returnPressed.connect(self.on_tool_add)
|
||||
# self.copytool_btn.clicked.connect(lambda: self.on_tool_copy())
|
||||
|
@ -426,6 +429,16 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
|
||||
self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
|
||||
|
||||
# #############################################################################
|
||||
# ###################### Setup CONTEXT MENU ###################################
|
||||
# #############################################################################
|
||||
self.tools_table.setupContextMenu()
|
||||
self.tools_table.addContextMenu(
|
||||
"Add", self.on_add_tool_by_key, icon=QtGui.QIcon("share/plus16.png"))
|
||||
self.tools_table.addContextMenu(
|
||||
"Delete", lambda:
|
||||
self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
|
||||
|
||||
def on_type_obj_index_changed(self, index):
|
||||
obj_type = self.type_obj_combo.currentIndex()
|
||||
self.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
|
||||
|
@ -434,6 +447,22 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+P', **kwargs)
|
||||
|
||||
def on_add_tool_by_key(self):
|
||||
tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
|
||||
text='%s:' % _('Enter a Tool Diameter'),
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Please enter a tool diameter with non-zero value, in Float format."))
|
||||
return
|
||||
self.on_tool_add(dia=float(val))
|
||||
else:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
|
||||
|
||||
def run(self, toggle=True):
|
||||
self.app.report_usage("ToolPaint()")
|
||||
|
||||
|
@ -488,15 +517,6 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
# disable rest-machining for single polygon painting
|
||||
self.rest_cb.set_value(False)
|
||||
self.rest_cb.setDisabled(True)
|
||||
# delete all tools except first row / tool for single polygon painting
|
||||
# list_to_del = list(range(1, self.tools_table.rowCount()))
|
||||
# if list_to_del:
|
||||
# self.on_tool_delete(rows_to_delete=list_to_del)
|
||||
# # disable addTool and delTool
|
||||
# self.addtool_entry.setDisabled(True)
|
||||
# self.addtool_btn.setDisabled(True)
|
||||
# self.deltool_btn.setDisabled(True)
|
||||
# self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
|
||||
if self.selectmethod_combo.get_value() == 'area':
|
||||
# disable rest-machining for single polygon painting
|
||||
self.rest_cb.set_value(False)
|
||||
|
@ -540,17 +560,12 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
if self.units == "IN":
|
||||
self.decimals = 4
|
||||
self.addtool_entry.set_value(0.039)
|
||||
else:
|
||||
self.decimals = 2
|
||||
self.addtool_entry.set_value(1)
|
||||
|
||||
self.tools_table.setupContextMenu()
|
||||
self.tools_table.addContextMenu(
|
||||
"Add", lambda: self.on_tool_add(dia=None, muted=None), icon=QtGui.QIcon("share/plus16.png"))
|
||||
self.tools_table.addContextMenu(
|
||||
"Delete", lambda:
|
||||
self.on_tool_delete(rows_to_delete=None, all=None), icon=QtGui.QIcon("share/delete32.png"))
|
||||
|
||||
# set the working variables to a known state
|
||||
self.paint_tools.clear()
|
||||
self.tooluid = 0
|
||||
|
@ -559,28 +574,28 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
self.default_data.update({
|
||||
"name": '_paint',
|
||||
"plot": self.app.defaults["geometry_plot"],
|
||||
"cutz": self.app.defaults["geometry_cutz"],
|
||||
"cutz": float(self.app.defaults["geometry_cutz"]),
|
||||
"vtipdia": 0.1,
|
||||
"vtipangle": 30,
|
||||
"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"],
|
||||
"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"]),
|
||||
"dwell": self.app.defaults["geometry_dwell"],
|
||||
"dwelltime": self.app.defaults["geometry_dwelltime"],
|
||||
"dwelltime": float(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"],
|
||||
"depthperpass": float(self.app.defaults["geometry_depthperpass"]),
|
||||
"extracut": self.app.defaults["geometry_extracut"],
|
||||
"toolchange": self.app.defaults["geometry_toolchange"],
|
||||
"toolchangez": self.app.defaults["geometry_toolchangez"],
|
||||
"endz": self.app.defaults["geometry_endz"],
|
||||
"toolchangez": float(self.app.defaults["geometry_toolchangez"]),
|
||||
"endz": float(self.app.defaults["geometry_endz"]),
|
||||
"spindlespeed": self.app.defaults["geometry_spindlespeed"],
|
||||
"toolchangexy": self.app.defaults["geometry_toolchangexy"],
|
||||
"startz": self.app.defaults["geometry_startz"],
|
||||
|
||||
"tooldia": self.app.defaults["tools_painttooldia"],
|
||||
"paintmargin": self.app.defaults["tools_paintmargin"],
|
||||
"tooldia": float(self.app.defaults["tools_painttooldia"]),
|
||||
"paintmargin": float(self.app.defaults["tools_paintmargin"]),
|
||||
"paintmethod": self.app.defaults["tools_paintmethod"],
|
||||
"selectmethod": self.app.defaults["tools_selectmethod"],
|
||||
"pathconnect": self.app.defaults["tools_pathconnect"],
|
||||
|
@ -590,7 +605,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
# call on self.on_tool_add() counts as an call to self.build_ui()
|
||||
# through this, we add a initial row / tool in the tool_table
|
||||
self.on_tool_add(self.app.defaults["tools_painttooldia"], muted=True)
|
||||
self.on_tool_add(float(self.app.defaults["tools_painttooldia"]), muted=True)
|
||||
|
||||
# if the Paint Method is "Single" disable the tool table context menu
|
||||
if self.default_data["selectmethod"] == "single":
|
||||
|
@ -608,7 +623,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
sorted_tools = []
|
||||
for k, v in self.paint_tools.items():
|
||||
sorted_tools.append(float('%.4f' % float(v['tooldia'])))
|
||||
sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
|
||||
|
||||
order = self.order_radio.get_value()
|
||||
if order == 'fwd':
|
||||
|
@ -624,7 +639,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
for tool_sorted in sorted_tools:
|
||||
for tooluid_key, tooluid_value in self.paint_tools.items():
|
||||
if float('%.4f' % tooluid_value['tooldia']) == tool_sorted:
|
||||
if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
|
||||
tool_id += 1
|
||||
id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
|
||||
id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
|
@ -632,12 +647,10 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
self.tools_table.setItem(row_no, 0, id) # Tool name/id
|
||||
|
||||
# Make sure that the drill diameter when in MM is with no more than 2 decimals
|
||||
# There are no drill bits in MM with more than 3 decimals diameter
|
||||
# For INCH the decimals should be no more than 3. There are no drills under 10mils
|
||||
if self.units == 'MM':
|
||||
dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
|
||||
else:
|
||||
dia = QtWidgets.QTableWidgetItem('%.4f' % tooluid_value['tooldia'])
|
||||
# There are no drill bits in MM with more than 2 decimals diameter
|
||||
# For INCH the decimals should be no more than 4. There are no drills under 10mils
|
||||
|
||||
dia = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, tooluid_value['tooldia']))
|
||||
|
||||
dia.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
|
@ -702,16 +715,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
if dia:
|
||||
tool_dia = dia
|
||||
else:
|
||||
try:
|
||||
tool_dia = float(self.addtool_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
tool_dia = float(self.addtool_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
tool_dia = float(self.addtool_entry.get_value())
|
||||
|
||||
if tool_dia is None:
|
||||
self.build_ui()
|
||||
|
@ -736,9 +740,9 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
for k, v in self.paint_tools.items():
|
||||
for tool_v in v.keys():
|
||||
if tool_v == 'tooldia':
|
||||
tool_dias.append(float('%.4f' % v[tool_v]))
|
||||
tool_dias.append(float('%.*f' % (self.decimals, v[tool_v])))
|
||||
|
||||
if float('%.4f' % tool_dia) in tool_dias:
|
||||
if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
|
||||
if muted is None:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Adding tool cancelled. Tool already in Tool Table."))
|
||||
|
@ -750,7 +754,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
_("New tool added to Tool Table."))
|
||||
self.paint_tools.update({
|
||||
int(self.tooluid): {
|
||||
'tooldia': float('%.4f' % tool_dia),
|
||||
'tooldia': float('%.*f' % (self.decimals, tool_dia)),
|
||||
'offset': 'Path',
|
||||
'offset_value': 0.0,
|
||||
'type': 'Iso',
|
||||
|
@ -774,7 +778,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
for k, v in self.paint_tools.items():
|
||||
for tool_v in v.keys():
|
||||
if tool_v == 'tooldia':
|
||||
tool_dias.append(float('%.4f' % v[tool_v]))
|
||||
tool_dias.append(float('%.*f' % (self.decimals, v[tool_v])))
|
||||
|
||||
for row in range(self.tools_table.rowCount()):
|
||||
try:
|
||||
|
@ -925,16 +929,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
# #####################################################
|
||||
self.app.inform.emit(_("Paint Tool. Reading parameters."))
|
||||
|
||||
try:
|
||||
self.overlap = float(self.paintoverlap_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
self.overlap = float(self.paintoverlap_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
self.overlap = float(self.paintoverlap_entry.get_value())
|
||||
|
||||
if self.overlap >= 1 or self.overlap < 0:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
|
@ -1185,7 +1180,8 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
|
||||
|
||||
self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
|
||||
symbol='++', edge_color='black', size=self.app.defaults["global_cursor_size"])
|
||||
symbol='++', edge_color=self.app.cursor_color_3D,
|
||||
size=self.app.defaults["global_cursor_size"])
|
||||
|
||||
# update the positions on status bar
|
||||
self.app.ui.position_label.setText(" <b>X</b>: %.4f "
|
||||
|
@ -1392,7 +1388,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
for tool_dia in sorted_tools:
|
||||
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
|
||||
current_uid = int(k)
|
||||
break
|
||||
|
||||
|
@ -1688,7 +1684,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
|
||||
current_uid = int(k)
|
||||
break
|
||||
|
||||
|
@ -1916,7 +1912,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
|
||||
current_uid = int(k)
|
||||
break
|
||||
|
||||
|
@ -2162,7 +2158,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
|
||||
current_uid = int(k)
|
||||
break
|
||||
|
||||
|
@ -2391,7 +2387,7 @@ class ToolPaint(FlatCAMTool, Gerber):
|
|||
|
||||
# find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
|
||||
for k, v in tools_storage.items():
|
||||
if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
|
||||
if float('%.*f' % (self.decimals, v['tooldia'])) == float('%.*f' % (self.decimals, tool_dia)):
|
||||
current_uid = int(k)
|
||||
break
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from copy import copy, deepcopy
|
||||
|
@ -143,7 +142,10 @@ class Panelize(FlatCAMTool):
|
|||
form_layout.addRow(panel_data_label)
|
||||
|
||||
# Spacing Columns
|
||||
self.spacing_columns = FCEntry()
|
||||
self.spacing_columns = FCDoubleSpinner()
|
||||
self.spacing_columns.set_range(0, 9999)
|
||||
self.spacing_columns.set_precision(4)
|
||||
|
||||
self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
|
||||
self.spacing_columns_label.setToolTip(
|
||||
_("Spacing between columns of the desired panel.\n"
|
||||
|
@ -152,7 +154,10 @@ class Panelize(FlatCAMTool):
|
|||
form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
|
||||
|
||||
# Spacing Rows
|
||||
self.spacing_rows = FCEntry()
|
||||
self.spacing_rows = FCDoubleSpinner()
|
||||
self.spacing_rows.set_range(0, 9999)
|
||||
self.spacing_rows.set_precision(4)
|
||||
|
||||
self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
|
||||
self.spacing_rows_label.setToolTip(
|
||||
_("Spacing between rows of the desired panel.\n"
|
||||
|
@ -161,7 +166,9 @@ class Panelize(FlatCAMTool):
|
|||
form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
|
||||
|
||||
# Columns
|
||||
self.columns = FCEntry()
|
||||
self.columns = FCSpinner()
|
||||
self.columns.set_range(0, 9999)
|
||||
|
||||
self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
|
||||
self.columns_label.setToolTip(
|
||||
_("Number of columns of the desired panel")
|
||||
|
@ -169,7 +176,9 @@ class Panelize(FlatCAMTool):
|
|||
form_layout.addRow(self.columns_label, self.columns)
|
||||
|
||||
# Rows
|
||||
self.rows = FCEntry()
|
||||
self.rows = FCSpinner()
|
||||
self.rows.set_range(0, 9999)
|
||||
|
||||
self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
|
||||
self.rows_label.setToolTip(
|
||||
_("Number of rows of the desired panel")
|
||||
|
@ -200,7 +209,10 @@ class Panelize(FlatCAMTool):
|
|||
)
|
||||
form_layout.addRow(self.constrain_cb)
|
||||
|
||||
self.x_width_entry = FCEntry()
|
||||
self.x_width_entry = FCDoubleSpinner()
|
||||
self.x_width_entry.set_precision(4)
|
||||
self.x_width_entry.set_range(0, 9999)
|
||||
|
||||
self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
|
||||
self.x_width_lbl.setToolTip(
|
||||
_("The width (DX) within which the panel must fit.\n"
|
||||
|
@ -208,7 +220,10 @@ class Panelize(FlatCAMTool):
|
|||
)
|
||||
form_layout.addRow(self.x_width_lbl, self.x_width_entry)
|
||||
|
||||
self.y_height_entry = FCEntry()
|
||||
self.y_height_entry = FCDoubleSpinner()
|
||||
self.y_height_entry.set_range(0, 9999)
|
||||
self.y_height_entry.set_precision(4)
|
||||
|
||||
self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
|
||||
self.y_height_lbl.setToolTip(
|
||||
_("The height (DY)within which the panel must fit.\n"
|
||||
|
@ -386,77 +401,20 @@ class Panelize(FlatCAMTool):
|
|||
|
||||
self.outname = name + '_panelized'
|
||||
|
||||
try:
|
||||
spacing_columns = float(self.spacing_columns.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
spacing_columns = float(self.spacing_columns.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
spacing_columns = float(self.spacing_columns.get_value())
|
||||
spacing_columns = spacing_columns if spacing_columns is not None else 0
|
||||
|
||||
try:
|
||||
spacing_rows = float(self.spacing_rows.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
spacing_rows = float(self.spacing_rows.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
spacing_rows = float(self.spacing_rows.get_value())
|
||||
spacing_rows = spacing_rows if spacing_rows is not None else 0
|
||||
|
||||
try:
|
||||
rows = int(self.rows.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
rows = float(self.rows.get_value().replace(',', '.'))
|
||||
rows = int(rows)
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
rows = int(self.rows.get_value())
|
||||
rows = rows if rows is not None else 1
|
||||
|
||||
try:
|
||||
columns = int(self.columns.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
columns = float(self.columns.get_value().replace(',', '.'))
|
||||
columns = int(columns)
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
columns = int(self.columns.get_value())
|
||||
columns = columns if columns is not None else 1
|
||||
|
||||
try:
|
||||
constrain_dx = float(self.x_width_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
constrain_dx = float(self.x_width_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
try:
|
||||
constrain_dy = float(self.y_height_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
constrain_dy = float(self.y_height_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
constrain_dx = float(self.x_width_entry.get_value())
|
||||
constrain_dy = float(self.y_height_entry.get_value())
|
||||
|
||||
panel_type = str(self.panel_type_radio.get_value())
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 4/15/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||
from PyQt5.QtCore import Qt
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
# from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtCore import Qt
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMCommon import LoudDict
|
||||
from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCEntry2, FCTable
|
||||
from flatcamGUI.GUIElements import FCComboBox, FCEntry, FCEntry2, FCTable, FCInputDialog
|
||||
from FlatCAMApp import log
|
||||
from camlib import distance
|
||||
from FlatCAMObj import FlatCAMCNCjob
|
||||
|
@ -402,6 +401,9 @@ class SolderPaste(FlatCAMTool):
|
|||
self.units = ''
|
||||
self.name = ""
|
||||
|
||||
# Number of decimals to be used for tools/nozzles in this FlatCAM Tool
|
||||
self.decimals = 4
|
||||
|
||||
# this will be used in the combobox context menu, for delete entry
|
||||
self.obj_to_be_deleted_name = ''
|
||||
|
||||
|
@ -414,7 +416,7 @@ class SolderPaste(FlatCAMTool):
|
|||
# ## Signals
|
||||
self.combo_context_del_action.triggered.connect(self.on_delete_object)
|
||||
self.addtool_btn.clicked.connect(self.on_tool_add)
|
||||
self.addtool_entry.returnPressed.connect(self.on_tool_add)
|
||||
self.addtool_entry.editingFinished.connect(self.on_tool_add)
|
||||
self.deltool_btn.clicked.connect(self.on_tool_delete)
|
||||
self.soldergeo_btn.clicked.connect(self.on_create_geo_click)
|
||||
self.solder_gcode_btn.clicked.connect(self.on_create_gcode_click)
|
||||
|
@ -457,6 +459,22 @@ class SolderPaste(FlatCAMTool):
|
|||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+K', **kwargs)
|
||||
|
||||
def on_add_tool_by_key(self):
|
||||
tool_add_popup = FCInputDialog(title='%s...' % _("New Tool"),
|
||||
text='%s:' % _('Enter a Tool Diameter'),
|
||||
min=0.0000, max=99.9999, decimals=4)
|
||||
tool_add_popup.setWindowIcon(QtGui.QIcon('share/letter_t_32.png'))
|
||||
|
||||
val, ok = tool_add_popup.get_value()
|
||||
if ok:
|
||||
if float(val) == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Please enter a tool diameter with non-zero value, in Float format."))
|
||||
return
|
||||
self.on_tool_add(dia=float(val))
|
||||
else:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s...' % _("Adding Tool cancelled"))
|
||||
|
||||
def set_tool_ui(self):
|
||||
self.form_fields.update({
|
||||
"tools_solderpaste_new": self.addtool_entry,
|
||||
|
@ -499,7 +517,7 @@ class SolderPaste(FlatCAMTool):
|
|||
self.tooluid += 1
|
||||
self.tooltable_tools.update({
|
||||
int(self.tooluid): {
|
||||
'tooldia': float('%.4f' % tool_dia),
|
||||
'tooldia': float('%.*f' % (self.decimals, tool_dia)),
|
||||
'data': deepcopy(self.options),
|
||||
'solid_geometry': []
|
||||
}
|
||||
|
@ -510,6 +528,11 @@ class SolderPaste(FlatCAMTool):
|
|||
|
||||
self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
|
||||
|
||||
if self.units == "IN":
|
||||
self.decimals = 4
|
||||
else:
|
||||
self.decimals = 2
|
||||
|
||||
for name in list(self.app.postprocessors.keys()):
|
||||
# populate only with postprocessor files that start with 'Paste_'
|
||||
if name.partition('_')[0] != 'Paste':
|
||||
|
@ -530,7 +553,7 @@ class SolderPaste(FlatCAMTool):
|
|||
|
||||
sorted_tools = []
|
||||
for k, v in self.tooltable_tools.items():
|
||||
sorted_tools.append(float('%.4f' % float(v['tooldia'])))
|
||||
sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
|
||||
sorted_tools.sort(reverse=True)
|
||||
|
||||
n = len(sorted_tools)
|
||||
|
@ -539,7 +562,7 @@ class SolderPaste(FlatCAMTool):
|
|||
|
||||
for tool_sorted in sorted_tools:
|
||||
for tooluid_key, tooluid_value in self.tooltable_tools.items():
|
||||
if float('%.4f' % tooluid_value['tooldia']) == tool_sorted:
|
||||
if float('%.*f' % (self.decimals, tooluid_value['tooldia'])) == tool_sorted:
|
||||
tool_id += 1
|
||||
id = QtWidgets.QTableWidgetItem('%d' % int(tool_id))
|
||||
id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
|
||||
|
@ -547,12 +570,9 @@ class SolderPaste(FlatCAMTool):
|
|||
self.tools_table.setItem(row_no, 0, id) # Tool name/id
|
||||
|
||||
# Make sure that the drill diameter when in MM is with no more than 2 decimals
|
||||
# There are no drill bits in MM with more than 3 decimals diameter
|
||||
# For INCH the decimals should be no more than 3. There are no drills under 10mils
|
||||
if self.units == 'MM':
|
||||
dia = QtWidgets.QTableWidgetItem('%.2f' % tooluid_value['tooldia'])
|
||||
else:
|
||||
dia = QtWidgets.QTableWidgetItem('%.4f' % tooluid_value['tooldia'])
|
||||
# There are no drill bits in MM with more than 2 decimals diameter
|
||||
# For INCH the decimals should be no more than 4. There are no drills under 10mils
|
||||
dia = QtWidgets.QTableWidgetItem('%.*f' % (self.decimals, tooluid_value['tooldia']))
|
||||
|
||||
dia.setFlags(QtCore.Qt.ItemIsEnabled)
|
||||
|
||||
|
@ -678,7 +698,12 @@ class SolderPaste(FlatCAMTool):
|
|||
:param status: what kind of change happened: 'append' or 'delete'
|
||||
:return:
|
||||
"""
|
||||
obj_name = obj.options['name']
|
||||
try:
|
||||
obj_name = obj.options['name']
|
||||
except AttributeError:
|
||||
# this happen when the 'delete all' is emitted since in that case the obj is set to None and None has no
|
||||
# attribute named 'options'
|
||||
return
|
||||
|
||||
if status == 'append':
|
||||
idx = self.obj_combo.findText(obj_name)
|
||||
|
@ -791,9 +816,9 @@ class SolderPaste(FlatCAMTool):
|
|||
for k, v in self.tooltable_tools.items():
|
||||
for tool_v in v.keys():
|
||||
if tool_v == 'tooldia':
|
||||
tool_dias.append(float('%.4f' % v[tool_v]))
|
||||
tool_dias.append(float('%.*f' % (self.decimals, v[tool_v])))
|
||||
|
||||
if float('%.4f' % tool_dia) in tool_dias:
|
||||
if float('%.*f' % (self.decimals, tool_dia)) in tool_dias:
|
||||
if muted is None:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Adding Nozzle tool cancelled. Tool already in Tool Table."))
|
||||
|
@ -805,7 +830,7 @@ class SolderPaste(FlatCAMTool):
|
|||
_("New Nozzle tool added to Tool Table."))
|
||||
self.tooltable_tools.update({
|
||||
int(self.tooluid): {
|
||||
'tooldia': float('%.4f' % tool_dia),
|
||||
'tooldia': float('%.*f' % (self.decimals, tool_dia)),
|
||||
'data': deepcopy(self.options),
|
||||
'solid_geometry': []
|
||||
}
|
||||
|
@ -824,7 +849,7 @@ class SolderPaste(FlatCAMTool):
|
|||
for k, v in self.tooltable_tools.items():
|
||||
for tool_v in v.keys():
|
||||
if tool_v == 'tooldia':
|
||||
tool_dias.append(float('%.4f' % v[tool_v]))
|
||||
tool_dias.append(float('%.*f' % (self.decimals, v[tool_v])))
|
||||
|
||||
for row in range(self.tools_table.rowCount()):
|
||||
|
||||
|
@ -991,7 +1016,7 @@ class SolderPaste(FlatCAMTool):
|
|||
for k, v in self.tooltable_tools.items():
|
||||
# make sure that the tools diameter is more than zero and not zero
|
||||
if float(v['tooldia']) > 0:
|
||||
sorted_tools.append(float('%.4f' % float(v['tooldia'])))
|
||||
sorted_tools.append(float('%.*f' % (self.decimals, float(v['tooldia']))))
|
||||
sorted_tools.sort(reverse=True)
|
||||
|
||||
if not sorted_tools:
|
||||
|
@ -1049,7 +1074,7 @@ class SolderPaste(FlatCAMTool):
|
|||
for tool in sorted_tools:
|
||||
offset = tool / 2
|
||||
for uid, vl in self.tooltable_tools.items():
|
||||
if float('%.4f' % float(vl['tooldia'])) == tool:
|
||||
if float('%.*f' % (self.decimals, float(vl['tooldia']))) == tool:
|
||||
tooluid = int(uid)
|
||||
break
|
||||
|
||||
|
@ -1301,13 +1326,13 @@ class SolderPaste(FlatCAMTool):
|
|||
time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
|
||||
|
||||
# add the tab if it was closed
|
||||
self.app.ui.plot_tab_area.addTab(self.app.ui.cncjob_tab, _("Code Editor"))
|
||||
self.app.ui.plot_tab_area.addTab(self.app.ui.text_editor_tab, _("Code Editor"))
|
||||
|
||||
# first clear previous text in text editor (if any)
|
||||
self.app.ui.code_editor.clear()
|
||||
|
||||
# Switch plot_area to CNCJob tab
|
||||
self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.cncjob_tab)
|
||||
self.app.ui.plot_tab_area.setCurrentWidget(self.app.ui.text_editor_tab)
|
||||
|
||||
name = self.cnc_obj_combo.currentText()
|
||||
obj = self.app.collection.get_by_name(name)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 4/24/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# File Author: Marius Adrian Stanciu (c) #
|
||||
# Date: 3/10/2019 #
|
||||
# MIT Licence #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from FlatCAMObj import *
|
||||
|
@ -29,6 +28,7 @@ class ToolTransform(FlatCAMTool):
|
|||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
self.decimals = 4
|
||||
|
||||
self.transform_lay = QtWidgets.QVBoxLayout()
|
||||
self.layout.addLayout(self.transform_lay)
|
||||
|
@ -42,28 +42,20 @@ class ToolTransform(FlatCAMTool):
|
|||
}
|
||||
""")
|
||||
self.transform_lay.addWidget(title_label)
|
||||
self.transform_lay.addWidget(QtWidgets.QLabel(''))
|
||||
|
||||
self.empty_label = QtWidgets.QLabel("")
|
||||
self.empty_label.setMinimumWidth(70)
|
||||
# ## Layout
|
||||
grid0 = QtWidgets.QGridLayout()
|
||||
self.transform_lay.addLayout(grid0)
|
||||
grid0.setColumnStretch(0, 0)
|
||||
grid0.setColumnStretch(1, 1)
|
||||
grid0.setColumnStretch(2, 0)
|
||||
|
||||
self.empty_label1 = QtWidgets.QLabel("")
|
||||
self.empty_label1.setMinimumWidth(70)
|
||||
self.empty_label2 = QtWidgets.QLabel("")
|
||||
self.empty_label2.setMinimumWidth(70)
|
||||
self.empty_label3 = QtWidgets.QLabel("")
|
||||
self.empty_label3.setMinimumWidth(70)
|
||||
self.empty_label4 = QtWidgets.QLabel("")
|
||||
self.empty_label4.setMinimumWidth(70)
|
||||
self.transform_lay.addWidget(self.empty_label)
|
||||
grid0.addWidget(QtWidgets.QLabel(''))
|
||||
|
||||
# ## Rotate Title
|
||||
rotate_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.rotateName)
|
||||
self.transform_lay.addWidget(rotate_title_label)
|
||||
|
||||
# ## Layout
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form_layout)
|
||||
form_child = QtWidgets.QHBoxLayout()
|
||||
grid0.addWidget(rotate_title_label, 0, 0, 1, 3)
|
||||
|
||||
self.rotate_label = QtWidgets.QLabel('%s:' % _("Angle"))
|
||||
self.rotate_label.setToolTip(
|
||||
|
@ -72,11 +64,14 @@ class ToolTransform(FlatCAMTool):
|
|||
"Positive numbers for CW motion.\n"
|
||||
"Negative numbers for CCW motion.")
|
||||
)
|
||||
self.rotate_label.setMinimumWidth(70)
|
||||
|
||||
self.rotate_entry = FCEntry()
|
||||
# self.rotate_entry.setFixedWidth(70)
|
||||
self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.rotate_entry = FCDoubleSpinner()
|
||||
self.rotate_entry.set_precision(self.decimals)
|
||||
self.rotate_entry.setSingleStep(45)
|
||||
self.rotate_entry.setWrapping(True)
|
||||
self.rotate_entry.set_range(-360, 360)
|
||||
|
||||
# self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
|
||||
self.rotate_button = FCButton()
|
||||
self.rotate_button.set_value(_("Rotate"))
|
||||
|
@ -87,32 +82,25 @@ class ToolTransform(FlatCAMTool):
|
|||
)
|
||||
self.rotate_button.setMinimumWidth(90)
|
||||
|
||||
form_child.addWidget(self.rotate_entry)
|
||||
form_child.addWidget(self.rotate_button)
|
||||
grid0.addWidget(self.rotate_label, 1, 0)
|
||||
grid0.addWidget(self.rotate_entry, 1, 1)
|
||||
grid0.addWidget(self.rotate_button, 1, 2)
|
||||
|
||||
form_layout.addRow(self.rotate_label, form_child)
|
||||
|
||||
self.transform_lay.addWidget(self.empty_label1)
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 2, 0)
|
||||
|
||||
# ## Skew Title
|
||||
skew_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.skewName)
|
||||
self.transform_lay.addWidget(skew_title_label)
|
||||
grid0.addWidget(skew_title_label, 3, 0, 1, 3)
|
||||
|
||||
# ## Form Layout
|
||||
form1_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form1_layout)
|
||||
form1_child_1 = QtWidgets.QHBoxLayout()
|
||||
form1_child_2 = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.skewx_label = QtWidgets.QLabel('%s:' % _("Skew_X angle"))
|
||||
self.skewx_label = QtWidgets.QLabel('%s:' % _("X angle"))
|
||||
self.skewx_label.setToolTip(
|
||||
_("Angle for Skew action, in degrees.\n"
|
||||
"Float number between -360 and 359.")
|
||||
"Float number between -360 and 360.")
|
||||
)
|
||||
self.skewx_label.setMinimumWidth(70)
|
||||
self.skewx_entry = FCEntry()
|
||||
self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.skewx_entry.setFixedWidth(70)
|
||||
self.skewx_entry = FCDoubleSpinner()
|
||||
# self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.skewx_entry.set_precision(self.decimals)
|
||||
self.skewx_entry.set_range(-360, 360)
|
||||
|
||||
self.skewx_button = FCButton()
|
||||
self.skewx_button.set_value(_("Skew X"))
|
||||
|
@ -122,15 +110,19 @@ class ToolTransform(FlatCAMTool):
|
|||
"the bounding box for all selected objects."))
|
||||
self.skewx_button.setMinimumWidth(90)
|
||||
|
||||
self.skewy_label = QtWidgets.QLabel('%s:' % _("Skew_Y angle"))
|
||||
grid0.addWidget(self.skewx_label, 4, 0)
|
||||
grid0.addWidget(self.skewx_entry, 4, 1)
|
||||
grid0.addWidget(self.skewx_button, 4, 2)
|
||||
|
||||
self.skewy_label = QtWidgets.QLabel('%s:' % _("Y angle"))
|
||||
self.skewy_label.setToolTip(
|
||||
_("Angle for Skew action, in degrees.\n"
|
||||
"Float number between -360 and 359.")
|
||||
"Float number between -360 and 360.")
|
||||
)
|
||||
self.skewy_label.setMinimumWidth(70)
|
||||
self.skewy_entry = FCEntry()
|
||||
self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.skewy_entry.setFixedWidth(70)
|
||||
self.skewy_entry = FCDoubleSpinner()
|
||||
# self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.skewy_entry.set_precision(self.decimals)
|
||||
self.skewy_entry.set_range(-360, 360)
|
||||
|
||||
self.skewy_button = FCButton()
|
||||
self.skewy_button.set_value(_("Skew Y"))
|
||||
|
@ -140,35 +132,24 @@ class ToolTransform(FlatCAMTool):
|
|||
"the bounding box for all selected objects."))
|
||||
self.skewy_button.setMinimumWidth(90)
|
||||
|
||||
form1_child_1.addWidget(self.skewx_entry)
|
||||
form1_child_1.addWidget(self.skewx_button)
|
||||
grid0.addWidget(self.skewy_label, 5, 0)
|
||||
grid0.addWidget(self.skewy_entry, 5, 1)
|
||||
grid0.addWidget(self.skewy_button, 5, 2)
|
||||
|
||||
form1_child_2.addWidget(self.skewy_entry)
|
||||
form1_child_2.addWidget(self.skewy_button)
|
||||
|
||||
form1_layout.addRow(self.skewx_label, form1_child_1)
|
||||
form1_layout.addRow(self.skewy_label, form1_child_2)
|
||||
|
||||
self.transform_lay.addWidget(self.empty_label2)
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 6, 0)
|
||||
|
||||
# ## Scale Title
|
||||
scale_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.scaleName)
|
||||
self.transform_lay.addWidget(scale_title_label)
|
||||
grid0.addWidget(scale_title_label, 7, 0, 1, 3)
|
||||
|
||||
# ## Form Layout
|
||||
form2_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form2_layout)
|
||||
form2_child_1 = QtWidgets.QHBoxLayout()
|
||||
form2_child_2 = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.scalex_label = QtWidgets.QLabel('%s:' % _("Scale_X factor"))
|
||||
self.scalex_label = QtWidgets.QLabel('%s:' % _("X factor"))
|
||||
self.scalex_label.setToolTip(
|
||||
_("Factor for scaling on X axis.")
|
||||
)
|
||||
self.scalex_label.setMinimumWidth(70)
|
||||
self.scalex_entry = FCEntry()
|
||||
self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.scalex_entry.setFixedWidth(70)
|
||||
self.scalex_entry = FCDoubleSpinner()
|
||||
# self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.scalex_entry.set_precision(self.decimals)
|
||||
self.scalex_entry.setMinimum(-1e6)
|
||||
|
||||
self.scalex_button = FCButton()
|
||||
self.scalex_button.set_value(_("Scale X"))
|
||||
|
@ -178,14 +159,18 @@ class ToolTransform(FlatCAMTool):
|
|||
"the Scale reference checkbox state."))
|
||||
self.scalex_button.setMinimumWidth(90)
|
||||
|
||||
self.scaley_label = QtWidgets.QLabel('%s:' % _("Scale_Y factor"))
|
||||
grid0.addWidget(self.scalex_label, 8, 0)
|
||||
grid0.addWidget(self.scalex_entry, 8, 1)
|
||||
grid0.addWidget(self.scalex_button, 8, 2)
|
||||
|
||||
self.scaley_label = QtWidgets.QLabel('%s:' % _("Y factor"))
|
||||
self.scaley_label.setToolTip(
|
||||
_("Factor for scaling on Y axis.")
|
||||
)
|
||||
self.scaley_label.setMinimumWidth(70)
|
||||
self.scaley_entry = FCEntry()
|
||||
self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.scaley_entry.setFixedWidth(70)
|
||||
self.scaley_entry = FCDoubleSpinner()
|
||||
# self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.scaley_entry.set_precision(self.decimals)
|
||||
self.scaley_entry.setMinimum(-1e6)
|
||||
|
||||
self.scaley_button = FCButton()
|
||||
self.scaley_button.set_value(_("Scale Y"))
|
||||
|
@ -195,6 +180,10 @@ class ToolTransform(FlatCAMTool):
|
|||
"the Scale reference checkbox state."))
|
||||
self.scaley_button.setMinimumWidth(90)
|
||||
|
||||
grid0.addWidget(self.scaley_label, 9, 0)
|
||||
grid0.addWidget(self.scaley_entry, 9, 1)
|
||||
grid0.addWidget(self.scaley_button, 9, 2)
|
||||
|
||||
self.scale_link_cb = FCCheckBox()
|
||||
self.scale_link_cb.set_value(True)
|
||||
self.scale_link_cb.setText(_("Link"))
|
||||
|
@ -202,7 +191,6 @@ class ToolTransform(FlatCAMTool):
|
|||
_("Scale the selected object(s)\n"
|
||||
"using the Scale_X factor for both axis.")
|
||||
)
|
||||
self.scale_link_cb.setMinimumWidth(70)
|
||||
|
||||
self.scale_zero_ref_cb = FCCheckBox()
|
||||
self.scale_zero_ref_cb.set_value(True)
|
||||
|
@ -213,37 +201,24 @@ class ToolTransform(FlatCAMTool):
|
|||
"and the center of the biggest bounding box\n"
|
||||
"of the selected objects when unchecked."))
|
||||
|
||||
form2_child_1.addWidget(self.scalex_entry)
|
||||
form2_child_1.addWidget(self.scalex_button)
|
||||
|
||||
form2_child_2.addWidget(self.scaley_entry)
|
||||
form2_child_2.addWidget(self.scaley_button)
|
||||
|
||||
form2_layout.addRow(self.scalex_label, form2_child_1)
|
||||
form2_layout.addRow(self.scaley_label, form2_child_2)
|
||||
form2_layout.addRow(self.scale_link_cb, self.scale_zero_ref_cb)
|
||||
self.ois_scale = OptionalInputSection(self.scale_link_cb, [self.scaley_entry, self.scaley_button], logic=False)
|
||||
|
||||
self.transform_lay.addWidget(self.empty_label3)
|
||||
grid0.addWidget(self.scale_link_cb, 10, 0)
|
||||
grid0.addWidget(self.scale_zero_ref_cb, 10, 1)
|
||||
grid0.addWidget(QtWidgets.QLabel(''), 11, 0)
|
||||
|
||||
# ## Offset Title
|
||||
offset_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.offsetName)
|
||||
self.transform_lay.addWidget(offset_title_label)
|
||||
grid0.addWidget(offset_title_label, 12, 0, 1, 3)
|
||||
|
||||
# ## Form Layout
|
||||
form3_layout = QtWidgets.QFormLayout()
|
||||
self.transform_lay.addLayout(form3_layout)
|
||||
form3_child_1 = QtWidgets.QHBoxLayout()
|
||||
form3_child_2 = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.offx_label = QtWidgets.QLabel('%s:' % _("Offset_X val"))
|
||||
self.offx_label = QtWidgets.QLabel('%s:' % _("X val"))
|
||||
self.offx_label.setToolTip(
|
||||
_("Distance to offset on X axis. In current units.")
|
||||
)
|
||||
self.offx_label.setMinimumWidth(70)
|
||||
self.offx_entry = FCEntry()
|
||||
self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.offx_entry.setFixedWidth(70)
|
||||
self.offx_entry = FCDoubleSpinner()
|
||||
# self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.offx_entry.set_precision(self.decimals)
|
||||
self.offx_entry.setMinimum(-1e6)
|
||||
|
||||
self.offx_button = FCButton()
|
||||
self.offx_button.set_value(_("Offset X"))
|
||||
|
@ -253,14 +228,18 @@ class ToolTransform(FlatCAMTool):
|
|||
"the bounding box for all selected objects.\n"))
|
||||
self.offx_button.setMinimumWidth(90)
|
||||
|
||||
self.offy_label = QtWidgets.QLabel('%s:' % _("Offset_Y val"))
|
||||
grid0.addWidget(self.offx_label, 13, 0)
|
||||
grid0.addWidget(self.offx_entry, 13, 1)
|
||||
grid0.addWidget(self.offx_button, 13, 2)
|
||||
|
||||
self.offy_label = QtWidgets.QLabel('%s:' % _("Y val"))
|
||||
self.offy_label.setToolTip(
|
||||
_("Distance to offset on Y axis. In current units.")
|
||||
)
|
||||
self.offy_label.setMinimumWidth(70)
|
||||
self.offy_entry = FCEntry()
|
||||
self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.offy_entry.setFixedWidth(70)
|
||||
self.offy_entry = FCDoubleSpinner()
|
||||
# self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.offy_entry.set_precision(self.decimals)
|
||||
self.offy_entry.setMinimum(-1e6)
|
||||
|
||||
self.offy_button = FCButton()
|
||||
self.offy_button.set_value(_("Offset Y"))
|
||||
|
@ -270,43 +249,33 @@ class ToolTransform(FlatCAMTool):
|
|||
"the bounding box for all selected objects.\n"))
|
||||
self.offy_button.setMinimumWidth(90)
|
||||
|
||||
form3_child_1.addWidget(self.offx_entry)
|
||||
form3_child_1.addWidget(self.offx_button)
|
||||
grid0.addWidget(self.offy_label, 14, 0)
|
||||
grid0.addWidget(self.offy_entry, 14, 1)
|
||||
grid0.addWidget(self.offy_button, 14, 2)
|
||||
|
||||
form3_child_2.addWidget(self.offy_entry)
|
||||
form3_child_2.addWidget(self.offy_button)
|
||||
|
||||
form3_layout.addRow(self.offx_label, form3_child_1)
|
||||
form3_layout.addRow(self.offy_label, form3_child_2)
|
||||
|
||||
self.transform_lay.addWidget(self.empty_label4)
|
||||
grid0.addWidget(QtWidgets.QLabel(''))
|
||||
|
||||
# ## Flip Title
|
||||
flip_title_label = QtWidgets.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
|
||||
self.transform_lay.addWidget(flip_title_label)
|
||||
|
||||
# ## Form Layout
|
||||
form4_layout = QtWidgets.QFormLayout()
|
||||
form4_child_hlay = QtWidgets.QHBoxLayout()
|
||||
self.transform_lay.addLayout(form4_child_hlay)
|
||||
self.transform_lay.addLayout(form4_layout)
|
||||
form4_child_1 = QtWidgets.QHBoxLayout()
|
||||
|
||||
self.flipx_button = FCButton()
|
||||
self.flipx_button.set_value(_("Flip on X"))
|
||||
self.flipx_button.setToolTip(
|
||||
_("Flip the selected object(s) over the X axis.\n"
|
||||
"Does not create a new object.\n ")
|
||||
_("Flip the selected object(s) over the X axis.")
|
||||
)
|
||||
self.flipx_button.setMinimumWidth(100)
|
||||
|
||||
self.flipy_button = FCButton()
|
||||
self.flipy_button.set_value(_("Flip on Y"))
|
||||
self.flipy_button.setToolTip(
|
||||
_("Flip the selected object(s) over the X axis.\n"
|
||||
"Does not create a new object.\n ")
|
||||
_("Flip the selected object(s) over the X axis.")
|
||||
)
|
||||
self.flipy_button.setMinimumWidth(90)
|
||||
|
||||
hlay0= QtWidgets.QHBoxLayout()
|
||||
self.transform_lay.addLayout(hlay0)
|
||||
|
||||
hlay0.addWidget(self.flipx_button)
|
||||
hlay0.addWidget(self.flipy_button)
|
||||
|
||||
self.flip_ref_cb = FCCheckBox()
|
||||
self.flip_ref_cb.set_value(True)
|
||||
|
@ -321,17 +290,17 @@ class ToolTransform(FlatCAMTool):
|
|||
"Then click Add button to insert coordinates.\n"
|
||||
"Or enter the coords in format (x, y) in the\n"
|
||||
"Point Entry field and click Flip on X(Y)"))
|
||||
self.flip_ref_cb.setMinimumWidth(70)
|
||||
|
||||
self.flip_ref_label = QtWidgets.QLabel('%s:' % _(" Mirror Ref. Point"))
|
||||
self.transform_lay.addWidget(self.flip_ref_cb)
|
||||
|
||||
self.flip_ref_label = QtWidgets.QLabel('%s:' % _("Ref. Point"))
|
||||
self.flip_ref_label.setToolTip(
|
||||
_("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"
|
||||
"the 'y' in (x, y) will be used when using Flip on Y and")
|
||||
"the 'y' in (x, y) will be used when using Flip on Y.")
|
||||
)
|
||||
self.flip_ref_label.setMinimumWidth(70)
|
||||
self.flip_ref_entry = EvalEntry2("(0, 0)")
|
||||
self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
# self.flip_ref_entry.setFixedWidth(70)
|
||||
|
||||
self.flip_ref_button = FCButton()
|
||||
|
@ -340,19 +309,16 @@ class ToolTransform(FlatCAMTool):
|
|||
_("The point coordinates can be captured by\n"
|
||||
"left click on canvas together with pressing\n"
|
||||
"SHIFT key. Then click Add button to insert."))
|
||||
self.flip_ref_button.setMinimumWidth(90)
|
||||
|
||||
form4_child_hlay.addStretch()
|
||||
form4_child_hlay.addWidget(self.flipx_button)
|
||||
form4_child_hlay.addWidget(self.flipy_button)
|
||||
|
||||
form4_child_1.addWidget(self.flip_ref_entry)
|
||||
form4_child_1.addWidget(self.flip_ref_button)
|
||||
|
||||
form4_layout.addRow(self.flip_ref_cb)
|
||||
form4_layout.addRow(self.flip_ref_label, form4_child_1)
|
||||
self.ois_flip = OptionalInputSection(self.flip_ref_cb, [self.flip_ref_entry, self.flip_ref_button], logic=True)
|
||||
|
||||
hlay1= QtWidgets.QHBoxLayout()
|
||||
self.transform_lay.addLayout(hlay1)
|
||||
|
||||
hlay1.addWidget(self.flip_ref_label)
|
||||
hlay1.addWidget(self.flip_ref_entry)
|
||||
|
||||
self.transform_lay.addWidget(self.flip_ref_button)
|
||||
self.transform_lay.addStretch()
|
||||
|
||||
# ## Signals
|
||||
|
@ -403,7 +369,7 @@ class ToolTransform(FlatCAMTool):
|
|||
self.app.ui.notebook.setTabText(2, _("Transform Tool"))
|
||||
|
||||
def install(self, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+R', **kwargs)
|
||||
FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs)
|
||||
|
||||
def set_tool_ui(self):
|
||||
# ## Initialize form
|
||||
|
@ -463,33 +429,23 @@ class ToolTransform(FlatCAMTool):
|
|||
self.flip_ref_entry.set_value((0, 0))
|
||||
|
||||
def on_rotate(self):
|
||||
try:
|
||||
value = float(self.rotate_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
value = float(self.rotate_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
self.app.worker_task.emit({'fcn': self.on_rotate_action,
|
||||
'params': [value]})
|
||||
# self.on_rotate_action(value)
|
||||
value = float(self.rotate_entry.get_value())
|
||||
if value == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Rotate transformation can not be done for a value of 0."))
|
||||
self.app.worker_task.emit({'fcn': self.on_rotate_action, 'params': [value]})
|
||||
return
|
||||
|
||||
def on_flipx(self):
|
||||
# self.on_flip("Y")
|
||||
axis = 'Y'
|
||||
self.app.worker_task.emit({'fcn': self.on_flip,
|
||||
'params': [axis]})
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis]})
|
||||
return
|
||||
|
||||
def on_flipy(self):
|
||||
# self.on_flip("X")
|
||||
axis = 'X'
|
||||
self.app.worker_task.emit({'fcn': self.on_flip,
|
||||
'params': [axis]})
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_flip, 'params': [axis]})
|
||||
return
|
||||
|
||||
def on_flip_add_coords(self):
|
||||
|
@ -497,56 +453,27 @@ class ToolTransform(FlatCAMTool):
|
|||
self.flip_ref_entry.set_value(val)
|
||||
|
||||
def on_skewx(self):
|
||||
try:
|
||||
value = float(self.skewx_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
value = float(self.skewx_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
# self.on_skew("X", value)
|
||||
value = float(self.skewx_entry.get_value())
|
||||
axis = 'X'
|
||||
self.app.worker_task.emit({'fcn': self.on_skew,
|
||||
'params': [axis, value]})
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, value]})
|
||||
return
|
||||
|
||||
def on_skewy(self):
|
||||
try:
|
||||
value = float(self.skewy_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
value = float(self.skewy_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
# self.on_skew("Y", value)
|
||||
value = float(self.skewy_entry.get_value())
|
||||
axis = 'Y'
|
||||
self.app.worker_task.emit({'fcn': self.on_skew,
|
||||
'params': [axis, value]})
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_skew, 'params': [axis, value]})
|
||||
return
|
||||
|
||||
def on_scalex(self):
|
||||
try:
|
||||
xvalue = float(self.scalex_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
xvalue = float(self.scalex_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
xvalue = float(self.scalex_entry.get_value())
|
||||
|
||||
if xvalue == 0 or xvalue == 1:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Scale transformation can not be done for a factor of 0 or 1."))
|
||||
return
|
||||
|
||||
# scaling to zero has no sense so we remove it, because scaling with 1 does nothing
|
||||
if xvalue == 0:
|
||||
xvalue = 1
|
||||
if self.scale_link_cb.get_value():
|
||||
yvalue = xvalue
|
||||
else:
|
||||
|
@ -555,80 +482,50 @@ class ToolTransform(FlatCAMTool):
|
|||
axis = 'X'
|
||||
point = (0, 0)
|
||||
if self.scale_zero_ref_cb.get_value():
|
||||
self.app.worker_task.emit({'fcn': self.on_scale,
|
||||
'params': [axis, xvalue, yvalue, point]})
|
||||
# self.on_scale("X", xvalue, yvalue, point=(0,0))
|
||||
self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
|
||||
else:
|
||||
# self.on_scale("X", xvalue, yvalue)
|
||||
self.app.worker_task.emit({'fcn': self.on_scale,
|
||||
'params': [axis, xvalue, yvalue]})
|
||||
self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue]})
|
||||
|
||||
return
|
||||
|
||||
def on_scaley(self):
|
||||
xvalue = 1
|
||||
try:
|
||||
yvalue = float(self.scaley_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
yvalue = float(self.scaley_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
yvalue = float(self.scaley_entry.get_value())
|
||||
|
||||
# scaling to zero has no sense so we remove it, because scaling with 1 does nothing
|
||||
if yvalue == 0:
|
||||
yvalue = 1
|
||||
if yvalue == 0 or yvalue == 1:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Scale transformation can not be done for a factor of 0 or 1."))
|
||||
return
|
||||
|
||||
axis = 'Y'
|
||||
point = (0, 0)
|
||||
if self.scale_zero_ref_cb.get_value():
|
||||
self.app.worker_task.emit({'fcn': self.on_scale,
|
||||
'params': [axis, xvalue, yvalue, point]})
|
||||
# self.on_scale("Y", xvalue, yvalue, point=(0,0))
|
||||
self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue, point]})
|
||||
else:
|
||||
# self.on_scale("Y", xvalue, yvalue)
|
||||
self.app.worker_task.emit({'fcn': self.on_scale,
|
||||
'params': [axis, xvalue, yvalue]})
|
||||
self.app.worker_task.emit({'fcn': self.on_scale, 'params': [axis, xvalue, yvalue]})
|
||||
|
||||
return
|
||||
|
||||
def on_offx(self):
|
||||
try:
|
||||
value = float(self.offx_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
value = float(self.offx_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
# self.on_offset("X", value)
|
||||
value = float(self.offx_entry.get_value())
|
||||
if value == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Offset transformation can not be done for a value of 0."))
|
||||
return
|
||||
axis = 'X'
|
||||
self.app.worker_task.emit({'fcn': self.on_offset,
|
||||
'params': [axis, value]})
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
|
||||
return
|
||||
|
||||
def on_offy(self):
|
||||
try:
|
||||
value = float(self.offy_entry.get_value())
|
||||
except ValueError:
|
||||
# try to convert comma to decimal point. if it's still not working error message and return
|
||||
try:
|
||||
value = float(self.offy_entry.get_value().replace(',', '.'))
|
||||
except ValueError:
|
||||
self.app.inform.emit('[ERROR_NOTCL] %s' %
|
||||
_("Wrong value format entered, use a number."))
|
||||
return
|
||||
|
||||
# self.on_offset("Y", value)
|
||||
value = float(self.offy_entry.get_value())
|
||||
if value == 0:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Offset transformation can not be done for a value of 0."))
|
||||
return
|
||||
axis = 'Y'
|
||||
self.app.worker_task.emit({'fcn': self.on_offset,
|
||||
'params': [axis, value]})
|
||||
|
||||
self.app.worker_task.emit({'fcn': self.on_offset, 'params': [axis, value]})
|
||||
return
|
||||
|
||||
def on_rotate_action(self, num):
|
||||
|
@ -764,6 +661,11 @@ class ToolTransform(FlatCAMTool):
|
|||
xminlist = []
|
||||
yminlist = []
|
||||
|
||||
if num == 0 or num == 90 or num == 180:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("Skew transformation can not be done for 0, 90 and 180 degrees."))
|
||||
return
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit('[WARNING_NOTCL] %s' %
|
||||
_("No object selected. Please Select an object to shear/skew!"))
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
import sys
|
||||
|
||||
from flatcamTools.ToolMeasurement import Measurement
|
||||
from flatcamTools.ToolPanelize import Panelize
|
||||
from flatcamTools.ToolFilm import Film
|
||||
from flatcamTools.ToolMove import ToolMove
|
||||
|
||||
from flatcamTools.ToolCalculators import ToolCalculator
|
||||
from flatcamTools.ToolCutOut import CutOut
|
||||
|
||||
from flatcamTools.ToolDblSided import DblSidedTool
|
||||
|
||||
from flatcamTools.ToolCutOut import CutOut
|
||||
from flatcamTools.ToolCalculators import ToolCalculator
|
||||
from flatcamTools.ToolProperties import Properties
|
||||
from flatcamTools.ToolFilm import Film
|
||||
|
||||
from flatcamTools.ToolImage import ToolImage
|
||||
from flatcamTools.ToolPaint import ToolPaint
|
||||
|
||||
from flatcamTools.ToolDistance import Distance
|
||||
from flatcamTools.ToolDistanceMin import DistanceMin
|
||||
|
||||
from flatcamTools.ToolMove import ToolMove
|
||||
|
||||
from flatcamTools.ToolNonCopperClear import NonCopperClear
|
||||
from flatcamTools.ToolTransform import ToolTransform
|
||||
from flatcamTools.ToolSolderPaste import SolderPaste
|
||||
|
||||
from flatcamTools.ToolOptimal import ToolOptimal
|
||||
|
||||
from flatcamTools.ToolPaint import ToolPaint
|
||||
from flatcamTools.ToolPanelize import Panelize
|
||||
from flatcamTools.ToolPcbWizard import PcbWizard
|
||||
from flatcamTools.ToolPDF import ToolPDF
|
||||
from flatcamTools.ToolSub import ToolSub
|
||||
from flatcamTools.ToolProperties import Properties
|
||||
|
||||
from flatcamTools.ToolRulesCheck import RulesCheck
|
||||
|
||||
from flatcamTools.ToolShell import FCShell
|
||||
from flatcamTools.ToolSolderPaste import SolderPaste
|
||||
from flatcamTools.ToolSub import ToolSub
|
||||
|
||||
from flatcamTools.ToolTransform import ToolTransform
|
||||
|
|
10
make_win.py
|
@ -1,4 +1,4 @@
|
|||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
|
@ -11,12 +11,12 @@
|
|||
# This is not an aid to install FlatCAM from source on #
|
||||
# Windows platforms. It is only useful when FlatCAM is up #
|
||||
# and running and ready to be packaged. #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
# ########################################################## ##
|
||||
# File Modified (major mod): Marius Adrian Stanciu #
|
||||
# ##########################################################
|
||||
# File Modified: Marius Adrian Stanciu #
|
||||
# Date: 3/10/2019 #
|
||||
# ########################################################## ##
|
||||
# ##########################################################
|
||||
|
||||
|
||||
# Files not needed: Qt, tk.dll, tcl.dll, tk/, tcl/, vtk/,
|
||||
|
|
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 201 B |
After Width: | Height: | Size: 190 B |
After Width: | Height: | Size: 213 B |
After Width: | Height: | Size: 264 B |
After Width: | Height: | Size: 546 B |
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 675 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 249 B |
After Width: | Height: | Size: 191 B |
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 301 B |
After Width: | Height: | Size: 222 B |
After Width: | Height: | Size: 207 B |
After Width: | Height: | Size: 227 B |
After Width: | Height: | Size: 218 B |
After Width: | Height: | Size: 358 B |
After Width: | Height: | Size: 787 B |
After Width: | Height: | Size: 387 B |
After Width: | Height: | Size: 533 B |
After Width: | Height: | Size: 699 B |
After Width: | Height: | Size: 474 B |
After Width: | Height: | Size: 348 B |
After Width: | Height: | Size: 232 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 921 B |
After Width: | Height: | Size: 489 B |
After Width: | Height: | Size: 258 B |
After Width: | Height: | Size: 428 B |
After Width: | Height: | Size: 291 B |
After Width: | Height: | Size: 355 B |
After Width: | Height: | Size: 390 B |
After Width: | Height: | Size: 889 B |