Merged in marius_stanciu/flatcam_beta/Beta (pull request #237)

Beta
This commit is contained in:
Marius Stanciu 2019-10-14 23:40:54 +00:00
commit 8ce5050cdd
355 changed files with 55286 additions and 37159 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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
#

View File

@ -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
View File

@ -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

4306
camlib.py

File diff suppressed because it is too large Load Diff

View File

@ -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):

View File

@ -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_))

View File

@ -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()

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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"]:

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
"""

View File

@ -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 #

View File

@ -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"

File diff suppressed because it is too large Load Diff

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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&nbsp;&nbsp; <b>Dy</b>: "
"%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (0.0, 0.0))
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.*f&nbsp;&nbsp; <b>Dy</b>: "
"%.*f&nbsp;&nbsp;&nbsp;&nbsp;" %
(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}&nbsp;&nbsp; <b>Dy</b>: "
"{0:.4f}&nbsp;&nbsp;&nbsp;&nbsp;".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>: {}&nbsp;&nbsp; <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".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("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {0:.4f}&nbsp;&nbsp; "
"<b>Y</b>: {0:.4f}".format(pos[0], pos[1]))
self.app.ui.position_label.setText(
"&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {}&nbsp;&nbsp; <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}&nbsp;&nbsp; <b>Dy</b>: "
"{0:.4f}&nbsp;&nbsp;&nbsp;&nbsp;".format(dx, dy))
self.app.ui.rel_position_label.setText(
"<b>Dx</b>: {}&nbsp;&nbsp; <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".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:

View File

@ -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

View 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()))

View File

@ -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()

View File

@ -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()

View File

@ -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("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
@ -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

550
flatcamTools/ToolOptimal.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
@ -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

View File

@ -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())

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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!"))

View File

@ -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

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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/,

BIN
share/align_center32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

BIN
share/align_justify32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

BIN
share/align_left32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

BIN
share/align_right32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

BIN
share/bookmarks16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

BIN
share/bookmarks32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

BIN
share/calculator16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 675 B

BIN
share/dark/about32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

BIN
share/dark/addarray16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

BIN
share/dark/addarray20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

BIN
share/dark/addarray32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

BIN
share/dark/align_left32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

BIN
share/dark/aperture16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

BIN
share/dark/aperture32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

BIN
share/dark/arc16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

BIN
share/dark/arc24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

BIN
share/dark/arc32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

BIN
share/dark/axis32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

BIN
share/dark/backup24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

BIN
share/dark/blocked16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
share/dark/bluelight12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

BIN
share/dark/bold32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

BIN
share/dark/buffer16-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

BIN
share/dark/buffer16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

BIN
share/dark/buffer20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

BIN
share/dark/buffer24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

BIN
share/dark/bug16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

BIN
share/dark/bug32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Some files were not shown because too many files have changed in this diff Show More