diff --git a/CHANGELOG.md b/CHANGELOG.md
index fec46966..2bd7f594 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,13 @@ CHANGELOG for FlatCAM beta
=================================================
+12.11.2020
+
+- some fixes in the app_Main class
+- removed the "follow" functionality from the Isolation Tool
+- created a new application Tool named Follow Tool
+- added the "follow" functionality in the Follow Tool and added the new feature of allowing to perform "follow" on an area selection
+
11.11.2020
- removed the forcing of multigeo geometry usage when creating CNCJobs
diff --git a/appDatabase.py b/appDatabase.py
index 38f1e182..3f285d94 100644
--- a/appDatabase.py
+++ b/appDatabase.py
@@ -869,23 +869,6 @@ class ToolsDB2UI:
self.grid4.addWidget(self.iso_milling_type_label, 4, 0)
self.grid4.addWidget(self.iso_milling_type_radio, 4, 1)
- # Follow
- self.follow_label = FCLabel('%s:' % _('Follow'))
- self.follow_label.setToolTip(
- _("Generate a 'Follow' geometry.\n"
- "This means that it will cut through\n"
- "the middle of the trace.")
- )
-
- self.iso_follow_cb = FCCheckBox()
- self.iso_follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
- "This means that it will cut through\n"
- "the middle of the trace."))
- self.iso_follow_cb.setObjectName("gdb_i_follow")
-
- self.grid4.addWidget(self.follow_label, 6, 0)
- self.grid4.addWidget(self.iso_follow_cb, 6, 1)
-
# Isolation Type
self.iso_type_label = FCLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
@@ -1493,7 +1476,6 @@ class ToolsDB2(QtWidgets.QWidget):
"tools_iso_passes": self.ui.iso_passes_entry,
"tools_iso_overlap": self.ui.iso_overlap_entry,
"tools_iso_milling_type": self.ui.iso_milling_type_radio,
- "tools_iso_follow": self.ui.iso_follow_cb,
"tools_iso_isotype": self.ui.iso_type_radio,
# Drilling
@@ -1577,7 +1559,6 @@ class ToolsDB2(QtWidgets.QWidget):
"gdb_i_passes": "tools_iso_passes",
"gdb_i_overlap": "tools_iso_overlap",
"gdb_i_milling_type": "tools_iso_milling_type",
- "gdb_i_follow": "tools_iso_follow",
"gdb_i_iso_type": "tools_iso_isotype",
# Drilling
@@ -1951,7 +1932,6 @@ class ToolsDB2(QtWidgets.QWidget):
"tools_iso_passes": int(self.app.defaults["tools_iso_passes"]),
"tools_iso_overlap": float(self.app.defaults["tools_iso_overlap"]),
"tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"],
- "tools_iso_follow": self.app.defaults["tools_iso_follow"],
"tools_iso_isotype": self.app.defaults["tools_iso_isotype"],
# Drilling
@@ -2519,8 +2499,6 @@ class ToolsDB2(QtWidgets.QWidget):
self.db_tool_dict[tool_id]['data']['tools_iso_overlap'] = val
elif wdg_name == "gdb_i_milling_type":
self.db_tool_dict[tool_id]['data']['tools_iso_milling_type'] = val
- elif wdg_name == "gdb_i_follow":
- self.db_tool_dict[tool_id]['data']['tools_iso_follow'] = val
elif wdg_name == "gdb_i_iso_type":
self.db_tool_dict[tool_id]['data']['tools_iso_isotype'] = val
diff --git a/appGUI/MainGUI.py b/appGUI/MainGUI.py
index 13da5919..23366434 100644
--- a/appGUI/MainGUI.py
+++ b/appGUI/MainGUI.py
@@ -1050,6 +1050,8 @@ class MainGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/paint20_1.png'), _("Paint Tool"))
self.isolation_btn = self.toolbartools.addAction(
QtGui.QIcon(self.app.resource_location + '/iso_16.png'), _("Isolation Tool"))
+ self.follow_btn = self.toolbartools.addAction(
+ QtGui.QIcon(self.app.resource_location + '/follow32.png'), _("Follow Tool"))
self.drill_btn = self.toolbartools.addAction(
QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Drilling Tool"))
self.toolbartools.addSeparator()
@@ -2219,6 +2221,8 @@ class MainGUI(QtWidgets.QMainWindow):
QtGui.QIcon(self.app.resource_location + '/paint20_1.png'), _("Paint Tool"))
self.isolation_btn = self.toolbartools.addAction(
QtGui.QIcon(self.app.resource_location + '/iso_16.png'), _("Isolation Tool"))
+ self.follow_btn = self.toolbartools.addAction(
+ QtGui.QIcon(self.app.resource_location + '/follow32.png'), _("Follow Tool"))
self.drill_btn = self.toolbartools.addAction(
QtGui.QIcon(self.app.resource_location + '/extract_drill32.png'), _("Drilling Tool"))
self.toolbartools.addSeparator()
diff --git a/appGUI/ObjectUI.py b/appGUI/ObjectUI.py
index b6d3a1fb..d645e30b 100644
--- a/appGUI/ObjectUI.py
+++ b/appGUI/ObjectUI.py
@@ -398,10 +398,21 @@ class GerberObjectUI(ObjectUI):
# """)
grid0.addWidget(self.generate_cutout_button, 20, 0, 1, 3)
+ # Follow Tool
+ self.generate_follow_button = FCButton(_('Follow Tool'))
+ self.generate_follow_button.setIcon(QtGui.QIcon(self.app.resource_location + '/follow32.png'))
+ self.generate_follow_button.setToolTip(
+ _("Generate a 'Follow' geometry.\n"
+ "This means that it will cut through\n"
+ "the middle of the trace."))
+ # self.generate_cutout_button.setStyleSheet("""
+
+ grid0.addWidget(self.generate_follow_button, 22, 0, 1, 3)
+
separator_line = QtWidgets.QFrame()
separator_line.setFrameShape(QtWidgets.QFrame.HLine)
separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
- grid0.addWidget(separator_line, 22, 0, 1, 3)
+ grid0.addWidget(separator_line, 24, 0, 1, 3)
# UTILITIES BUTTON
self.util_button = FCButton('%s' % _("UTILTIES"), checkable=True)
@@ -413,12 +424,12 @@ class GerberObjectUI(ObjectUI):
font-weight: bold;
}
""")
- grid0.addWidget(self.util_button, 24, 0, 1, 3)
+ grid0.addWidget(self.util_button, 26, 0, 1, 3)
# UTILITIES Frame
self.util_frame = QtWidgets.QFrame()
self.util_frame.setContentsMargins(0, 0, 0, 0)
- grid0.addWidget(self.util_frame, 25, 0, 1, 3)
+ grid0.addWidget(self.util_frame, 28, 0, 1, 3)
self.util_box = QtWidgets.QVBoxLayout()
self.util_box.setContentsMargins(0, 0, 0, 0)
self.util_frame.setLayout(self.util_box)
diff --git a/appGUI/preferences/PreferencesUIManager.py b/appGUI/preferences/PreferencesUIManager.py
index 8f1d60c1..3d8b81be 100644
--- a/appGUI/preferences/PreferencesUIManager.py
+++ b/appGUI/preferences/PreferencesUIManager.py
@@ -347,7 +347,6 @@ class PreferencesUIManager:
"tools_iso_passes": self.ui.tools_defaults_form.tools_iso_group.passes_entry,
"tools_iso_overlap": self.ui.tools_defaults_form.tools_iso_group.overlap_entry,
"tools_iso_milling_type": self.ui.tools_defaults_form.tools_iso_group.milling_type_radio,
- "tools_iso_follow": self.ui.tools_defaults_form.tools_iso_group.follow_cb,
"tools_iso_isotype": self.ui.tools_defaults_form.tools_iso_group.iso_type_radio,
"tools_iso_rest": self.ui.tools_defaults_form.tools_iso_group.rest_cb,
diff --git a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py
index bc4d51d3..4c34ad09 100644
--- a/appGUI/preferences/tools/ToolsISOPrefGroupUI.py
+++ b/appGUI/preferences/tools/ToolsISOPrefGroupUI.py
@@ -197,23 +197,6 @@ class ToolsISOPrefGroupUI(OptionsGroupUI):
grid0.addWidget(self.milling_type_label, 10, 0)
grid0.addWidget(self.milling_type_radio, 10, 1, 1, 2)
- # Follow
- self.follow_label = QtWidgets.QLabel('%s:' % _('Follow'))
- self.follow_label.setToolTip(
- _("Generate a 'Follow' geometry.\n"
- "This means that it will cut through\n"
- "the middle of the trace.")
- )
-
- self.follow_cb = FCCheckBox()
- self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
- "This means that it will cut through\n"
- "the middle of the trace."))
- self.follow_cb.setObjectName("i_follow")
-
- grid0.addWidget(self.follow_label, 11, 0)
- grid0.addWidget(self.follow_cb, 11, 1, 1, 2)
-
# Isolation Type
self.iso_type_label = QtWidgets.QLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
diff --git a/appObjects/AppObject.py b/appObjects/AppObject.py
index fcf9bcff..ce77af1c 100644
--- a/appObjects/AppObject.py
+++ b/appObjects/AppObject.py
@@ -7,7 +7,6 @@
# Modified by Marius Stanciu (2020) #
# ###########################################################
-from PyQt5 import QtCore
from appObjects.ObjectCollection import *
from appObjects.FlatCAMCNCJob import CNCJobObject
from appObjects.FlatCAMDocument import DocumentObject
@@ -56,7 +55,8 @@ class AppObject(QtCore.QObject):
self.object_plotted.connect(self.on_object_plotted)
self.plots_updated.connect(self.app.on_plots_updated)
- def new_object(self, kind, name, initialize, plot=True, autoselected=True, callback=None, callback_params=None):
+ def new_object(self, kind, name, initialize: object, plot=True, autoselected=True, callback=None,
+ callback_params=None):
"""
Creates a new specialized FlatCAMObj and attaches it to the application,
this is, updates the GUI accordingly, any other records and plots it.
@@ -74,7 +74,7 @@ class AppObject(QtCore.QObject):
:param initialize: Function to run after creation of the object but before it is attached to the
application.
The function is called with 2 parameters: the new object and the App instance.
- :type initialize: function
+ :type initialize: object
:param plot: If to plot the resulting object
:param autoselected: if the resulting object is autoselected in the Project tab and therefore in the
self.collection
diff --git a/appObjects/FlatCAMGeometry.py b/appObjects/FlatCAMGeometry.py
index 54b1b0e9..0b1675c5 100644
--- a/appObjects/FlatCAMGeometry.py
+++ b/appObjects/FlatCAMGeometry.py
@@ -2302,7 +2302,8 @@ class GeometryObject(FlatCAMObj, Geometry):
'tooldia': tooldia_val
})
if "optimization_type" not in tools_dict[tooluid_key]['data']:
- tools_dict[tooluid_key]['data']["optimization_type"] = self.app.defaults["geometry_optimization_type"]
+ tools_dict[tooluid_key]['data']["optimization_type"] = \
+ self.app.defaults["geometry_optimization_type"]
# find the tool_dia associated with the tooluid_key
# search in the self.tools for the sel_tool_dia and when found see what tooluid has
@@ -2574,7 +2575,7 @@ class GeometryObject(FlatCAMObj, Geometry):
def job_thread(app_obj):
with self.app.proc_container.new(_("Generating CNC Code")):
app_obj.app_obj.new_object("cncjob", outname, job_init, plot=plot)
- app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created")), outname)
+ app_obj.inform.emit('[success] %s: %s' % (_("CNCjob created"), outname))
# Create a promise with the name
self.app.collection.promise(outname)
diff --git a/appObjects/FlatCAMGerber.py b/appObjects/FlatCAMGerber.py
index 6d23f705..83eece83 100644
--- a/appObjects/FlatCAMGerber.py
+++ b/appObjects/FlatCAMGerber.py
@@ -156,6 +156,7 @@ class GerberObject(FlatCAMObj, Gerber):
self.ui.iso_button.clicked.connect(self.app.isolation_tool.run)
self.ui.generate_ncc_button.clicked.connect(self.app.ncclear_tool.run)
self.ui.generate_cutout_button.clicked.connect(self.app.cutout_tool.run)
+ self.ui.generate_follow_button.clicked.connect(self.app.follow_tool.run)
# Utilties
self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
diff --git a/appParsers/ParseHPGL2.py b/appParsers/ParseHPGL2.py
index fa2c2a7a..1a73513c 100644
--- a/appParsers/ParseHPGL2.py
+++ b/appParsers/ParseHPGL2.py
@@ -105,7 +105,6 @@ class HPGL2:
"tools_iso_passes": self.app.defaults["tools_iso_passes"],
"tools_iso_overlap": self.app.defaults["tools_iso_overlap"],
"tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"],
- "tools_iso_follow": self.app.defaults["tools_iso_follow"],
"tools_iso_isotype": self.app.defaults["tools_iso_isotype"],
"tools_iso_rest": self.app.defaults["tools_iso_rest"],
diff --git a/appTools/ToolFollow.py b/appTools/ToolFollow.py
new file mode 100644
index 00000000..a6bcda57
--- /dev/null
+++ b/appTools/ToolFollow.py
@@ -0,0 +1,712 @@
+# ##########################################################
+# FlatCAM: 2D Post-processing for Manufacturing #
+# File by: Marius Adrian Stanciu (c) #
+# Date: 5/25/2020 #
+# License: MIT Licence #
+# ##########################################################
+
+from PyQt5 import QtWidgets, QtCore, QtGui
+
+from appTool import AppTool
+from appGUI.GUIElements import RadioSet, FCButton, FCComboBox, FCLabel, FCComboBox2
+from appParsers.ParseGerber import Gerber
+
+from copy import deepcopy
+
+import numpy as np
+
+from shapely.ops import unary_union
+from shapely.geometry import MultiPolygon, Polygon, MultiLineString, LineString, LinearRing, Point
+
+from matplotlib.backend_bases import KeyEvent as mpl_key_event
+
+import logging
+import gettext
+import appTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+ _ = gettext.gettext
+
+log = logging.getLogger('base')
+
+
+class ToolFollow(AppTool, Gerber):
+
+ optimal_found_sig = QtCore.pyqtSignal(float)
+
+ def __init__(self, app):
+ self.app = app
+ self.decimals = self.app.decimals
+
+ AppTool.__init__(self, app)
+ Gerber.__init__(self, steps_per_circle=self.app.defaults["gerber_circle_steps"])
+
+ # #############################################################################
+ # ######################### Tool GUI ##########################################
+ # #############################################################################
+ self.ui = FollowUI(layout=self.layout, app=self.app)
+ self.toolName = self.ui.toolName
+
+ # disconnect flags
+ self.area_sel_disconnect_flag = False
+
+ self.first_click = False
+ self.cursor_pos = None
+ self.mouse_is_dragging = False
+
+ self.mm = None
+ self.mp = None
+ self.mr = None
+ self.kp = None
+
+ self.sel_rect = []
+
+ # store here the points for the "Polygon" area selection shape
+ self.points = []
+ # set this as True when in middle of drawing a "Polygon" area selection shape
+ # it is made False by first click to signify that the shape is complete
+ self.poly_drawn = False
+
+ # Signals
+ self.ui.selectmethod_radio.activated_custom.connect(self.ui.on_selection)
+ self.ui.generate_geometry_button.clicked.connect(self.on_generate_geometry_click)
+
+ def install(self, icon=None, separator=None, **kwargs):
+ AppTool.install(self, icon, separator, shortcut='', **kwargs)
+
+ def run(self, toggle=True):
+ self.app.defaults.report_usage("ToolFollow()")
+ log.debug("ToolFOllow().run() was launched ...")
+
+ 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])
+
+ AppTool.run(self)
+ self.set_tool_ui()
+
+ self.app.ui.notebook.setTabText(2, _("Follow Tool"))
+
+ def set_tool_ui(self):
+ self.units = self.app.defaults['units'].upper()
+ self.ui.selectmethod_radio.set_value('all') # _("All")
+ self.ui.area_shape_radio.set_value('square')
+
+ self.sel_rect[:] = []
+ self.points = []
+ self.poly_drawn = False
+ self.area_sel_disconnect_flag = False
+
+ def on_generate_geometry_click(self):
+ obj_name = self.ui.object_combo.currentText()
+
+ # Get source object.
+ try:
+ obj = self.app.collection.get_by_name(obj_name)
+ except Exception as e:
+ self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
+ return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
+
+ if obj is None:
+ self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
+ return
+
+ formatted_name = obj_name.rpartition('.')[0]
+ if formatted_name == '':
+ formatted_name = obj_name
+ outname = '%s_follow' % formatted_name
+
+ select_method = self.ui.selectmethod_radio.get_value()
+ if select_method == 'all': # _("All")
+ self.follow_all(obj, outname)
+ else:
+ self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
+
+ if self.app.is_legacy is False:
+ 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)
+ else:
+ self.app.plotcanvas.graph_event_disconnect(self.app.mp)
+ self.app.plotcanvas.graph_event_disconnect(self.app.mm)
+ self.app.plotcanvas.graph_event_disconnect(self.app.mr)
+
+ self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
+ self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
+ self.kp = self.app.plotcanvas.graph_event_connect('key_press', self.on_key_press)
+
+ # disconnect flags
+ self.area_sel_disconnect_flag = True
+
+ def follow_all(self, obj, outname):
+ def job_thread(tool_obj):
+ tool_obj.follow_geo(obj, outname)
+
+ self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
+
+ def follow_area(self):
+ obj_name = self.ui.object_combo.currentText()
+
+ # Get source object.
+ try:
+ obj = self.app.collection.get_by_name(obj_name)
+ except Exception as e:
+ self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
+ return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
+
+ if obj is None:
+ self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(self.obj_name)))
+ return
+
+ formatted_name = obj_name.rpartition('.')[0]
+ if formatted_name == '':
+ formatted_name = obj_name
+ outname = '%s_follow' % formatted_name
+
+ def job_thread(tool_obj):
+ tool_obj.follow_geo_area(obj, outname)
+
+ self.app.worker_task.emit({'fcn': job_thread, 'params': [self]})
+
+ def follow_geo(self, followed_obj, outname):
+ """
+ Creates a geometry object "following" the gerber paths.
+
+ :param followed_obj: Gerber object for which to generate the follow geometry
+ :type followed_obj: AppObjects.FlatCAMGerber.GerberObject
+ :param outname: Nme of the resulting Geometry object
+ :type outname: str
+ :return: None
+ """
+
+ def follow_init(new_obj, app_obj):
+ if type(app_obj.defaults["geometry_cnctooldia"]) == float:
+ tools_list = [app_obj.defaults["geometry_cnctooldia"]]
+ else:
+ try:
+ temp_tools = app_obj.defaults["geometry_cnctooldia"].split(",")
+ tools_list = [
+ float(eval(dia)) for dia in temp_tools if dia != ''
+ ]
+ except Exception as e:
+ log.error("ToolFollow.follow_geo -> At least one tool diameter needed. -> %s" % str(e))
+ return 'fail'
+
+ # store here the default data for Geometry Data
+ new_data = {}
+
+ for opt_key, opt_val in app_obj.options.items():
+ if opt_key.find('geometry' + "_") == 0:
+ oname = opt_key[len('geometry') + 1:]
+ new_data[oname] = app_obj.options[opt_key]
+ if opt_key.find('tools_mill' + "_") == 0:
+ oname = opt_key[len('tools_mill') + 1:]
+ new_data[oname] = app_obj.options[opt_key]
+
+ # Propagate options
+ new_obj.options["cnctooldia"] = app_obj.defaults["geometry_cnctooldia"]
+ new_obj.solid_geometry = deepcopy(followed_obj.follow_geometry)
+ new_obj.tools = {
+ 1: {
+ 'tooldia': app_obj.dec_format(float(tools_list[0]), self.decimals),
+ 'offset': 'Path',
+ 'offset_value': 0.0,
+ 'type': 'Rough',
+ 'tool_type': 'C1',
+ 'data': deepcopy(new_data),
+ 'solid_geometry': new_obj.solid_geometry
+ }
+ }
+
+ ret = self.app.app_obj.new_object("geometry", outname, follow_init)
+ if ret == 'fail':
+ self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed to create Follow Geometry."))
+ else:
+ self.app.inform.emit("[success] %s" % _("Done."))
+
+ def follow_geo_area(self, followed_obj, outname):
+ """
+ Creates a geometry object "following" the gerber paths.
+
+ :param followed_obj: Gerber object for which to generate the follow geometry
+ :type followed_obj: AppObjects.FlatCAMGerber.GerberObject
+ :param outname: Nme of the resulting Geometry object
+ :type outname: str
+ :return: None
+ """
+
+ def follow_init(new_obj, app_obj):
+ if type(app_obj.defaults["geometry_cnctooldia"]) == float:
+ tools_list = [app_obj.defaults["geometry_cnctooldia"]]
+ else:
+ try:
+ temp_tools = app_obj.defaults["geometry_cnctooldia"].split(",")
+ tools_list = [
+ float(eval(dia)) for dia in temp_tools if dia != ''
+ ]
+ except Exception as e:
+ log.error("ToolFollow.follow_geo -> At least one tool diameter needed. -> %s" % str(e))
+ return 'fail'
+
+ # store here the default data for Geometry Data
+ new_data = {}
+
+ for opt_key, opt_val in app_obj.options.items():
+ if opt_key.find('geometry' + "_") == 0:
+ oname = opt_key[len('geometry') + 1:]
+ new_data[oname] = app_obj.options[opt_key]
+ if opt_key.find('tools_mill' + "_") == 0:
+ oname = opt_key[len('tools_mill') + 1:]
+ new_data[oname] = app_obj.options[opt_key]
+
+ # Propagate options
+ new_obj.options["cnctooldia"] = app_obj.defaults["geometry_cnctooldia"]
+
+ target_geo = unary_union(followed_obj.follow_geometry)
+ area_follow = target_geo.intersection(deepcopy(unary_union(self.sel_rect)))
+ self.sel_rect[:] = []
+ self.points = []
+
+ new_obj.solid_geometry = deepcopy(area_follow)
+ new_obj.tools = {
+ 1: {
+ 'tooldia': app_obj.dec_format(float(tools_list[0]), self.decimals),
+ 'offset': 'Path',
+ 'offset_value': 0.0,
+ 'type': 'Rough',
+ 'tool_type': 'C1',
+ 'data': deepcopy(new_data),
+ 'solid_geometry': new_obj.solid_geometry
+ }
+ }
+
+ ret = self.app.app_obj.new_object("geometry", outname, follow_init)
+ if ret == 'fail':
+ self.app.inform.emit("[ERROR_NOTCL] %s" % _("Failed to create Follow Geometry."))
+ else:
+ self.app.inform.emit("[success] %s" % _("Done."))
+
+ # To be called after clicking on the plot.
+ def on_mouse_release(self, event):
+ if self.app.is_legacy is False:
+ event_pos = event.pos
+ # event_is_dragging = event.is_dragging
+ right_button = 2
+ else:
+ event_pos = (event.xdata, event.ydata)
+ # event_is_dragging = self.app.plotcanvas.is_dragging
+ right_button = 3
+
+ try:
+ x = float(event_pos[0])
+ y = float(event_pos[1])
+ except TypeError:
+ return
+
+ event_pos = (x, y)
+
+ shape_type = self.ui.area_shape_radio.get_value()
+
+ curr_pos = self.app.plotcanvas.translate_coords(event_pos)
+ if self.app.grid_status():
+ curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
+
+ x1, y1 = curr_pos[0], curr_pos[1]
+
+ # do paint single only for left mouse clicks
+ if event.button == 1:
+ if shape_type == "square":
+ if not self.first_click:
+ self.first_click = True
+ self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the area."))
+
+ self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
+ if self.app.grid_status():
+ self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
+ else:
+ self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
+ self.app.delete_selection_shape()
+
+ x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
+ pt1 = (x0, y0)
+ pt2 = (x1, y0)
+ pt3 = (x1, y1)
+ pt4 = (x0, y1)
+
+ new_rectangle = Polygon([pt1, pt2, pt3, pt4])
+ self.sel_rect.append(new_rectangle)
+
+ # add a temporary shape on canvas
+ self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
+
+ self.first_click = False
+ return
+ else:
+ self.points.append((x1, y1))
+
+ if len(self.points) > 1:
+ self.poly_drawn = True
+ self.app.inform.emit(_("Click on next Point or click right mouse button to complete ..."))
+
+ return ""
+ elif event.button == right_button and self.mouse_is_dragging is False:
+
+ shape_type = self.ui.area_shape_radio.get_value()
+
+ if shape_type == "square":
+ self.first_click = False
+ else:
+ # if we finish to add a polygon
+ if self.poly_drawn is True:
+ try:
+ # try to add the point where we last clicked if it is not already in the self.points
+ last_pt = (x1, y1)
+ if last_pt != self.points[-1]:
+ self.points.append(last_pt)
+ except IndexError:
+ pass
+
+ # we need to add a Polygon and a Polygon can be made only from at least 3 points
+ if len(self.points) > 2:
+ self.delete_moving_selection_shape()
+ pol = Polygon(self.points)
+ # do not add invalid polygons even if they are drawn by utility geometry
+ if pol.is_valid:
+ self.sel_rect.append(pol)
+ self.draw_selection_shape_polygon(points=self.points)
+ self.app.inform.emit(
+ _("Zone added. Click to start adding next zone or right click to finish."))
+
+ self.points = []
+ self.poly_drawn = False
+ return
+
+ self.delete_tool_selection_shape()
+
+ if self.app.is_legacy is False:
+ self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+ self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+ self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+ else:
+ self.app.plotcanvas.graph_event_disconnect(self.mr)
+ self.app.plotcanvas.graph_event_disconnect(self.mm)
+ self.app.plotcanvas.graph_event_disconnect(self.kp)
+
+ self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+ self.app.on_mouse_click_over_plot)
+ self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+ self.app.on_mouse_move_over_plot)
+ self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+ self.app.on_mouse_click_release_over_plot)
+
+ # disconnect flags
+ self.area_sel_disconnect_flag = False
+
+ if len(self.sel_rect) == 0:
+ return
+
+ self.follow_area()
+
+ # called on mouse move
+ def on_mouse_move(self, event):
+ shape_type = self.ui.area_shape_radio.get_value()
+
+ if self.app.is_legacy is False:
+ event_pos = event.pos
+ event_is_dragging = event.is_dragging
+ # right_button = 2
+ else:
+ event_pos = (event.xdata, event.ydata)
+ event_is_dragging = self.app.plotcanvas.is_dragging
+ # right_button = 3
+
+ try:
+ x = float(event_pos[0])
+ y = float(event_pos[1])
+ except TypeError:
+ return
+
+ curr_pos = self.app.plotcanvas.translate_coords((x, y))
+
+ # detect mouse dragging motion
+ if event_is_dragging == 1:
+ self.mouse_is_dragging = True
+ else:
+ self.mouse_is_dragging = False
+
+ # update the cursor position
+ if self.app.grid_status():
+ # Update cursor
+ 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=self.app.cursor_color_3D,
+ edge_width=self.app.defaults["global_cursor_width"],
+ size=self.app.defaults["global_cursor_size"])
+
+ if self.cursor_pos is None:
+ self.cursor_pos = (0, 0)
+
+ self.app.dx = curr_pos[0] - float(self.cursor_pos[0])
+ self.app.dy = curr_pos[1] - float(self.cursor_pos[1])
+
+ # # update the positions on status bar
+ self.app.ui.position_label.setText(" X: %.4f "
+ "Y: %.4f " % (curr_pos[0], curr_pos[1]))
+ self.app.ui.rel_position_label.setText("Dx: %.4f Dy: "
+ "%.4f " % (self.app.dx, self.app.dy))
+
+ units = self.app.defaults["units"].lower()
+ self.app.plotcanvas.text_hud.text = \
+ 'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
+ self.app.dx, units, self.app.dy, units, curr_pos[0], units, curr_pos[1], units)
+
+ # draw the utility geometry
+ if shape_type == "square":
+ if self.first_click:
+ self.app.delete_selection_shape()
+ self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
+ coords=(curr_pos[0], curr_pos[1]))
+ else:
+ self.delete_moving_selection_shape()
+ self.draw_moving_selection_shape_poly(points=self.points, data=(curr_pos[0], curr_pos[1]))
+
+ def on_key_press(self, event):
+ # modifiers = QtWidgets.QApplication.keyboardModifiers()
+ # matplotlib_key_flag = False
+
+ # events out of the self.app.collection view (it's about Project Tab) are of type int
+ if type(event) is int:
+ key = event
+ # events from the GUI are of type QKeyEvent
+ elif type(event) == QtGui.QKeyEvent:
+ key = event.key()
+ elif isinstance(event, mpl_key_event): # MatPlotLib key events are trickier to interpret than the rest
+ # matplotlib_key_flag = True
+
+ key = event.key
+ key = QtGui.QKeySequence(key)
+
+ # check for modifiers
+ key_string = key.toString().lower()
+ if '+' in key_string:
+ mod, __, key_text = key_string.rpartition('+')
+ if mod.lower() == 'ctrl':
+ # modifiers = QtCore.Qt.ControlModifier
+ pass
+ elif mod.lower() == 'alt':
+ # modifiers = QtCore.Qt.AltModifier
+ pass
+ elif mod.lower() == 'shift':
+ # modifiers = QtCore.Qt.ShiftModifier
+ pass
+ else:
+ # modifiers = QtCore.Qt.NoModifier
+ pass
+ key = QtGui.QKeySequence(key_text)
+
+ # events from Vispy are of type KeyEvent
+ else:
+ key = event.key
+
+ if key == QtCore.Qt.Key_Escape or key == 'Escape':
+ if self.area_sel_disconnect_flag is True:
+ try:
+ if self.app.is_legacy is False:
+ self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
+ self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
+ self.app.plotcanvas.graph_event_disconnect('key_press', self.on_key_press)
+ else:
+ self.app.plotcanvas.graph_event_disconnect(self.mr)
+ self.app.plotcanvas.graph_event_disconnect(self.mm)
+ self.app.plotcanvas.graph_event_disconnect(self.kp)
+ except Exception as e:
+ log.debug("ToolFollow.on_key_press() _1 --> %s" % str(e))
+
+ self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
+ self.app.on_mouse_click_over_plot)
+ self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
+ self.app.on_mouse_move_over_plot)
+ self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
+ self.app.on_mouse_click_release_over_plot)
+ self.points = []
+ self.poly_drawn = False
+ self.sel_rect[:] = []
+
+ self.delete_moving_selection_shape()
+ self.delete_tool_selection_shape()
+
+
+class FollowUI:
+
+ toolName = _("Follow Tool")
+
+ def __init__(self, layout, app):
+ self.app = app
+ self.decimals = self.app.decimals
+ self.layout = layout
+
+ self.tools_frame = QtWidgets.QFrame()
+ self.tools_frame.setContentsMargins(0, 0, 0, 0)
+ self.layout.addWidget(self.tools_frame)
+ self.tools_box = QtWidgets.QVBoxLayout()
+ self.tools_box.setContentsMargins(0, 0, 0, 0)
+ self.tools_frame.setLayout(self.tools_box)
+
+ self.title_box = QtWidgets.QHBoxLayout()
+ self.tools_box.addLayout(self.title_box)
+
+ # ## Title
+ title_label = FCLabel("%s" % self.toolName)
+ title_label.setStyleSheet("""
+ QLabel
+ {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ """)
+ title_label.setToolTip(
+ _("Create a Geometry object with\n"
+ "toolpaths to cut around polygons.")
+ )
+
+ self.title_box.addWidget(title_label)
+
+ self.obj_combo_label = FCLabel('%s:' % _("GERBER"))
+ self.obj_combo_label.setToolTip(
+ _("Source object for following geometry.")
+ )
+
+ self.tools_box.addWidget(self.obj_combo_label)
+
+ # #############################################################################################################
+ # ################################ The object to be followed ##################################################
+ # #############################################################################################################
+ self.object_combo = FCComboBox()
+ self.object_combo.setModel(self.app.collection)
+ self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+ # self.object_combo.setCurrentIndex(1)
+ self.object_combo.is_last = True
+
+ self.tools_box.addWidget(self.object_combo)
+
+ separator_line = QtWidgets.QFrame()
+ separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ self.tools_box.addWidget(separator_line)
+
+ grid0 = QtWidgets.QGridLayout()
+ grid0.setColumnStretch(0, 0)
+ grid0.setColumnStretch(1, 1)
+ self.tools_box.addLayout(grid0)
+
+ # Parameter Title
+ self.param_title = FCLabel("%s:" % _("Parameters"))
+ grid0.addWidget(self.param_title, 0, 0, 1, 2)
+
+ # Polygon selection
+ selectlabel = FCLabel('%s:' % _('Selection'))
+ selectlabel.setToolTip(
+ _("Selection of area to be processed.\n"
+ "- 'All Polygons' - the process will start after click.\n"
+ "- 'Area Selection' - left mouse click to start selection of the area to be processed.")
+ )
+
+ self.selectmethod_radio = RadioSet([{'label': _("All"), 'value': 'all'},
+ {'label': _("Area Selection"), 'value': 'area'}])
+
+ grid0.addWidget(selectlabel, 2, 0)
+ grid0.addWidget(self.selectmethod_radio, 2, 1)
+
+ # Area Selection shape
+ self.area_shape_label = FCLabel('%s:' % _("Shape"))
+ self.area_shape_label.setToolTip(
+ _("The kind of selection shape used for area selection.")
+ )
+
+ self.area_shape_radio = RadioSet([{'label': _("Square"), 'value': 'square'},
+ {'label': _("Polygon"), 'value': 'polygon'}])
+
+ grid0.addWidget(self.area_shape_label, 4, 0)
+ grid0.addWidget(self.area_shape_radio, 4, 1)
+
+ self.area_shape_label.hide()
+ self.area_shape_radio.hide()
+
+ separator_line = QtWidgets.QFrame()
+ separator_line.setFrameShape(QtWidgets.QFrame.HLine)
+ separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
+ grid0.addWidget(separator_line, 6, 0, 1, 2)
+
+ self.generate_geometry_button = FCButton("%s" % _("Generate Geometry"))
+ self.generate_geometry_button.setIcon(QtGui.QIcon(self.app.resource_location + '/geometry32.png'))
+ self.generate_geometry_button.setStyleSheet("""
+ QPushButton
+ {
+ font-weight: bold;
+ }
+ """)
+ self.generate_geometry_button.setToolTip(_("Generate a 'Follow' geometry.\n"
+ "This means that it will cut through\n"
+ "the middle of the trace."))
+ self.tools_box.addWidget(self.generate_geometry_button)
+
+ self.tools_box.addStretch()
+
+ # ## Reset Tool
+ self.reset_button = FCButton(_("Reset Tool"))
+ self.reset_button.setIcon(QtGui.QIcon(self.app.resource_location + '/reset32.png'))
+ self.reset_button.setToolTip(
+ _("Will reset the tool parameters.")
+ )
+ self.reset_button.setStyleSheet("""
+ QPushButton
+ {
+ font-weight: bold;
+ }
+ """)
+ self.tools_box.addWidget(self.reset_button)
+ # ############################ FINSIHED GUI ###################################
+ # #############################################################################
+
+ def on_selection(self, val):
+ if val == 'area': # _("Area Selection")
+ self.area_shape_label.show()
+ self.area_shape_radio.show()
+ else: # All
+ self.area_shape_label.hide()
+ self.area_shape_radio.hide()
+
+ def confirmation_message(self, accepted, minval, maxval):
+ if accepted is False:
+ self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
+ self.decimals,
+ minval,
+ self.decimals,
+ maxval), False)
+ else:
+ self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
+
+ def confirmation_message_int(self, accepted, minval, maxval):
+ if accepted is False:
+ self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
+ (_("Edited value is out of range"), minval, maxval), False)
+ else:
+ self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
diff --git a/appTools/ToolIsolation.py b/appTools/ToolIsolation.py
index 907884c1..ddaedfe7 100644
--- a/appTools/ToolIsolation.py
+++ b/appTools/ToolIsolation.py
@@ -136,7 +136,6 @@ class ToolIsolation(AppTool, Gerber):
"tools_iso_overlap": self.ui.iso_overlap_entry,
"tools_iso_milling_type": self.ui.milling_type_radio,
"tools_iso_combine": self.ui.combine_passes_cb,
- "tools_iso_follow": self.ui.follow_cb,
"tools_iso_isotype": self.ui.iso_type_radio
}
@@ -145,7 +144,6 @@ class ToolIsolation(AppTool, Gerber):
"i_overlap": "tools_iso_overlap",
"i_milling_type": "tools_iso_milling_type",
"i_combine": "tools_iso_combine",
- "i_follow": "tools_iso_follow",
"i_iso_type": "tools_iso_isotype"
}
@@ -253,10 +251,6 @@ class ToolIsolation(AppTool, Gerber):
self.ui.iso_type_radio.set_value('full')
self.ui.iso_type_radio.hide()
- self.ui.follow_cb.set_value(False)
- self.ui.follow_cb.hide()
- self.ui.follow_label.hide()
-
self.ui.rest_cb.set_value(False)
self.ui.rest_cb.hide()
self.ui.forced_rest_iso_cb.hide()
@@ -280,10 +274,6 @@ class ToolIsolation(AppTool, Gerber):
self.ui.iso_type_radio.set_value(self.app.defaults["tools_iso_isotype"])
self.ui.iso_type_radio.show()
- self.ui.follow_cb.set_value(self.app.defaults["tools_iso_follow"])
- self.ui.follow_cb.show()
- self.ui.follow_label.show()
-
self.ui.rest_cb.set_value(self.app.defaults["tools_iso_rest"])
self.ui.rest_cb.show()
self.ui.forced_rest_iso_cb.show()
@@ -373,7 +363,6 @@ class ToolIsolation(AppTool, Gerber):
"tools_iso_passes": self.app.defaults["tools_iso_passes"],
"tools_iso_overlap": self.app.defaults["tools_iso_overlap"],
"tools_iso_milling_type": self.app.defaults["tools_iso_milling_type"],
- "tools_iso_follow": self.app.defaults["tools_iso_follow"],
"tools_iso_isotype": self.app.defaults["tools_iso_isotype"],
"tools_iso_rest": self.app.defaults["tools_iso_rest"],
@@ -1477,41 +1466,6 @@ class ToolIsolation(AppTool, Gerber):
self.app.worker_task.emit({'fcn': worker_task, 'params': [self.grb_obj]})
- def follow_geo(self, followed_obj, outname):
- """
- Creates a geometry object "following" the gerber paths.
-
- :param followed_obj: Gerber object for which to generate the follow geometry
- :type followed_obj: AppObjects.FlatCAMGerber.GerberObject
- :param outname: Nme of the resulting Geometry object
- :type outname: str
- :return: None
- """
-
- def follow_init(follow_obj, app_obj):
- # Propagate options
- follow_obj.options["cnctooldia"] = str(tooldia)
- follow_obj.solid_geometry = self.grb_obj.follow_geometry
- app_obj.inform.emit('[success] %s.' % _("Following geometry was generated"))
-
- # in the end toggle the visibility of the origin object so we can see the generated Geometry
- followed_obj.ui.plot_cb.set_value(False)
- follow_name = outname
-
- for tool in self.iso_tools:
- tooldia = self.iso_tools[tool]['tooldia']
- new_name = "%s_%.*f" % (follow_name, self.decimals, tooldia)
-
- follow_state = self.iso_tools[tool]['data']['tools_iso_follow']
- if follow_state:
- ret = self.app.app_obj.new_object("geometry", new_name, follow_init)
- if ret == 'fail':
- self.app.inform.emit("[ERROR_NOTCL] %s: %.*f" % (
- _("Failed to create Follow Geometry with tool diameter"), self.decimals, tooldia))
- else:
- self.app.inform.emit("[success] %s: %.*f" % (
- _("Follow Geometry was created with tool diameter"), self.decimals, tooldia))
-
def isolate_handler(self, isolated_obj):
"""
Creates a geometry object with paths around the gerber features.
@@ -1623,11 +1577,10 @@ class ToolIsolation(AppTool, Gerber):
for tool in sorted_tools:
tool_data = tools_storage[tool]['data']
- to_follow = tool_data['tools_iso_follow']
work_geo = geometry
if work_geo is None:
- work_geo = isolated_obj.follow_geometry if to_follow else isolated_obj.solid_geometry
+ work_geo = isolated_obj.solid_geometry
iso_t = {
'ext': 0,
@@ -1670,7 +1623,7 @@ class ToolIsolation(AppTool, Gerber):
mill_dir = 1 if milling_type == 'cl' else 0
iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
- follow=to_follow, nr_passes=i, prog_plot=prog_plot)
+ nr_passes=i, prog_plot=prog_plot)
if iso_geo == 'fail':
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
continue
@@ -1996,13 +1949,9 @@ class ToolIsolation(AppTool, Gerber):
tool_cut_type = tools_storage[tool]['type']
tool_data = tools_storage[tool]['data']
- to_follow = tool_data['tools_iso_follow']
-
- # TODO what to do when the iso2geo param is not None but the Follow cb is checked
- # for the case when limited area is used .... the follow geo should be clipped too
work_geo = geometry
if work_geo is None:
- work_geo = iso_obj.follow_geometry if to_follow else iso_obj.solid_geometry
+ work_geo = iso_obj.solid_geometry
iso_t = {
'ext': 0,
@@ -2040,7 +1989,7 @@ class ToolIsolation(AppTool, Gerber):
mill_dir = 1 if milling_type == 'cl' else 0
iso_geo = self.generate_envelope(iso_offset, mill_dir, geometry=work_geo, env_iso_type=iso_t,
- follow=to_follow, nr_passes=nr_pass, prog_plot=prog_plot)
+ nr_passes=nr_pass, prog_plot=prog_plot)
if iso_geo == 'fail':
self.app.inform.emit('[ERROR_NOTCL] %s' % _("Isolation geometry could not be generated."))
continue
@@ -2097,15 +2046,12 @@ class ToolIsolation(AppTool, Gerber):
if len(tools_storage) > 1:
geo_obj.multigeo = True
else:
- if to_follow:
- geo_obj.multigeo = False
- else:
- passes_no = 1
- for ky in tools_storage.keys():
- passes_no = float(tools_storage[ky]['data']['tools_iso_passes'])
- geo_obj.multigeo = True
- break
+ passes_no = 1
+ for ky in tools_storage.keys():
+ passes_no = float(tools_storage[ky]['data']['tools_iso_passes'])
geo_obj.multigeo = True
+ break
+ geo_obj.multigeo = True
# detect if solid_geometry is empty and this require list flattening which is "heavy"
# or just looking in the lists (they are one level depth) and if any is not empty
@@ -2789,7 +2735,7 @@ class ToolIsolation(AppTool, Gerber):
def poly2ints(poly):
return [interior for interior in poly.interiors]
- def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, follow=None, nr_passes=0,
+ def generate_envelope(self, offset, invert, geometry=None, env_iso_type=2, nr_passes=0,
prog_plot=False):
"""
Isolation_geometry produces an envelope that is going on the left of the geometry
@@ -2807,8 +2753,6 @@ class ToolIsolation(AppTool, Gerber):
:type geometry:
:param env_iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
:type env_iso_type: int
- :param follow: If the kind of isolation is a "follow" one
- :type follow: bool
:param nr_passes: Number of passes
:type nr_passes: int
:param prog_plot: Type of plotting: "normal" or "progressive"
@@ -2817,16 +2761,12 @@ class ToolIsolation(AppTool, Gerber):
:rtype: MultiPolygon or Polygon
"""
- if follow:
- geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, follow=follow, prog_plot=prog_plot)
- return geom
- else:
- try:
- geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type,
- passes=nr_passes, prog_plot=prog_plot)
- except Exception as e:
- log.debug('ToolIsolation.generate_envelope() --> %s' % str(e))
- return 'fail'
+ try:
+ geom = self.grb_obj.isolation_geometry(offset, geometry=geometry, iso_type=env_iso_type,
+ passes=nr_passes, prog_plot=prog_plot)
+ except Exception as e:
+ log.debug('ToolIsolation.generate_envelope() --> %s' % str(e))
+ return 'fail'
if invert:
try:
@@ -3091,9 +3031,9 @@ class IsoUI:
self.tools_box.addWidget(self.obj_combo_label)
- # ################################################
- # ##### The object to be copper cleaned ##########
- # ################################################
+ # #############################################################################################################
+ # ################################ The object to be isolated ##################################################
+ # #############################################################################################################
self.object_combo = FCComboBox()
self.object_combo.setModel(self.app.collection)
self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
@@ -3321,23 +3261,6 @@ class IsoUI:
self.grid3.addWidget(self.milling_type_label, 15, 0)
self.grid3.addWidget(self.milling_type_radio, 15, 1)
- # Follow
- self.follow_label = FCLabel('%s:' % _('Follow'))
- self.follow_label.setToolTip(
- _("Generate a 'Follow' geometry.\n"
- "This means that it will cut through\n"
- "the middle of the trace.")
- )
-
- self.follow_cb = FCCheckBox()
- self.follow_cb.setToolTip(_("Generate a 'Follow' geometry.\n"
- "This means that it will cut through\n"
- "the middle of the trace."))
- self.follow_cb.setObjectName("i_follow")
-
- self.grid3.addWidget(self.follow_label, 16, 0)
- self.grid3.addWidget(self.follow_cb, 16, 1)
-
# Isolation Type
self.iso_type_label = FCLabel('%s:' % _('Isolation Type'))
self.iso_type_label.setToolTip(
diff --git a/appTools/ToolPaint.py b/appTools/ToolPaint.py
index 73ed5bac..0f4648a6 100644
--- a/appTools/ToolPaint.py
+++ b/appTools/ToolPaint.py
@@ -68,7 +68,6 @@ class ToolPaint(AppTool, Gerber):
self.tooldia_list = []
self.tooldia = None
- self.sel_rect = None
self.o_name = None
self.overlap = None
self.connect = None
@@ -2406,7 +2405,7 @@ class ToolPaint(AppTool, Gerber):
return self.flat_geometry
- # this is were heavy lifting is done and creating the geometry to be painted
+ # this is where heavy lifting is done and creating the geometry to be painted
target_geo = MultiPolygon(obj.solid_geometry)
if obj.kind == 'gerber':
# I don't do anything here, like buffering when the Gerber is loaded without buffering????!!!!
diff --git a/appTools/__init__.py b/appTools/__init__.py
index 924b9a45..aab619fa 100644
--- a/appTools/__init__.py
+++ b/appTools/__init__.py
@@ -19,6 +19,7 @@ from appTools.ToolCutOut import CutOut
from appTools.ToolNCC import NonCopperClear
from appTools.ToolPaint import ToolPaint
from appTools.ToolIsolation import ToolIsolation
+from appTools.ToolFollow import ToolFollow
from appTools.ToolDrilling import ToolDrilling
from appTools.ToolMilling import ToolMilling
diff --git a/app_Main.py b/app_Main.py
index 47d00f3e..dbc3d684 100644
--- a/app_Main.py
+++ b/app_Main.py
@@ -551,7 +551,7 @@ class App(QtCore.QObject):
# ----------------------------------------- WARNING --------------------------------------------------------
# Preprocessors need to be loaded before the Preferences Manager builds the Preferences
- # That's because the number of preprocessors can vary and here the comboboxes are populated
+ # That's because the number of preprocessors can vary and here the combobox is populated
# -----------------------------------------------------------------------------------------------------------
# a dictionary that have as keys the name of the preprocessor files and the value is the class from
@@ -718,7 +718,6 @@ class App(QtCore.QObject):
alignment=Qt.AlignBottom | Qt.AlignLeft,
color=QtGui.QColor("gray"))
start_plot_time = time.time() # debug
- self.plotcanvas = None
self.app_cursor = None
self.hover_shapes = None
@@ -726,7 +725,9 @@ class App(QtCore.QObject):
self.log.debug("Setting up canvas: %s" % str(self.defaults["global_graphic_engine"]))
# setup the PlotCanvas
- self.on_plotcanvas_setup()
+ self.plotcanvas = self.on_plotcanvas_setup()
+ if self.plotcanvas == 'fail':
+ return
end_plot_time = time.time()
self.used_time = end_plot_time - start_plot_time
@@ -1031,6 +1032,7 @@ class App(QtCore.QObject):
self.ncclear_tool = None
self.paint_tool = None
self.isolation_tool = None
+ self.follow_tool = None
self.drilling_tool = None
self.optimal_tool = None
@@ -1401,7 +1403,6 @@ class App(QtCore.QObject):
# Canvas Context Menu
self.connect_canvas_context_signals()
-
# Notebook tab clicking
self.ui.notebook.tabBarClicked.connect(self.on_properties_tab_click)
@@ -1874,6 +1875,10 @@ class App(QtCore.QObject):
self.isolation_tool.install(icon=QtGui.QIcon(self.resource_location + '/iso_16.png'), pos=self.ui.menutool,
before=self.sub_tool.menuAction, separator=True)
+ self.follow_tool = ToolFollow(self)
+ self.follow_tool.install(icon=QtGui.QIcon(self.resource_location + '/follow32.png'), pos=self.ui.menutool,
+ before=self.sub_tool.menuAction, separator=True)
+
self.drilling_tool = ToolDrilling(self)
self.drilling_tool.install(icon=QtGui.QIcon(self.resource_location + '/extract_drill32.png'),
pos=self.ui.menutool, before=self.sub_tool.menuAction, separator=True)
@@ -2155,6 +2160,8 @@ class App(QtCore.QObject):
self.ui.ncc_btn.triggered.connect(lambda: self.ncclear_tool.run(toggle=True))
self.ui.paint_btn.triggered.connect(lambda: self.paint_tool.run(toggle=True))
self.ui.isolation_btn.triggered.connect(lambda: self.isolation_tool.run(toggle=True))
+ self.ui.follow_btn.triggered.connect(lambda: self.follow_tool.run(toggle=True))
+
self.ui.drill_btn.triggered.connect(lambda: self.drilling_tool.run(toggle=True))
self.ui.panelize_btn.triggered.connect(lambda: self.panelize_tool.run(toggle=True))
@@ -7733,10 +7740,10 @@ class App(QtCore.QObject):
if self.is_legacy is True or modifier == QtCore.Qt.ControlModifier:
self.is_legacy = True
self.defaults["global_graphic_engine"] = "2D"
- self.plotcanvas = PlotCanvasLegacy(plot_container, self)
+ plotcanvas = PlotCanvasLegacy(plot_container, self)
else:
try:
- self.plotcanvas = PlotCanvas(plot_container, self)
+ plotcanvas = PlotCanvas(plot_container, self)
except Exception as er:
msg_txt = traceback.format_exc()
self.log.debug("App.on_plotcanvas_setup() failed -> %s" % str(er))
@@ -7749,25 +7756,25 @@ class App(QtCore.QObject):
return 'fail'
# So it can receive key presses
- self.plotcanvas.native.setFocus()
+ plotcanvas.native.setFocus()
if self.is_legacy is False:
pan_button = 2 if self.defaults["global_pan_button"] == '2' else 3
# Set the mouse button for panning
- self.plotcanvas.view.camera.pan_button_setting = pan_button
+ plotcanvas.view.camera.pan_button_setting = pan_button
- self.mm = self.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot)
- self.mp = self.plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot)
- self.mr = self.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release_over_plot)
- self.mdc = self.plotcanvas.graph_event_connect('mouse_double_click', self.on_mouse_double_click_over_plot)
+ self.mm = plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move_over_plot)
+ self.mp = plotcanvas.graph_event_connect('mouse_press', self.on_mouse_click_over_plot)
+ self.mr = plotcanvas.graph_event_connect('mouse_release', self.on_mouse_click_release_over_plot)
+ self.mdc = plotcanvas.graph_event_connect('mouse_double_click', self.on_mouse_double_click_over_plot)
# Keys over plot enabled
- self.kp = self.plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
+ self.kp = plotcanvas.graph_event_connect('key_press', self.ui.keyPressEvent)
if self.defaults['global_cursor_type'] == 'small':
- self.app_cursor = self.plotcanvas.new_cursor()
+ self.app_cursor = plotcanvas.new_cursor()
else:
- self.app_cursor = self.plotcanvas.new_cursor(big=True)
+ self.app_cursor = plotcanvas.new_cursor(big=True)
if self.ui.grid_snap_btn.isChecked():
self.app_cursor.enabled = True
@@ -7775,11 +7782,13 @@ class App(QtCore.QObject):
self.app_cursor.enabled = False
if self.is_legacy is False:
- self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
+ self.hover_shapes = ShapeCollection(parent=plotcanvas.view.scene, layers=1)
else:
# will use the default Matplotlib axes
self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
+ return plotcanvas
+
def on_zoom_fit(self):
"""
Callback for zoom-fit request. This can be either from the corresponding
diff --git a/camlib.py b/camlib.py
index 59291d06..4181d271 100644
--- a/camlib.py
+++ b/camlib.py
@@ -398,7 +398,7 @@ class ApertureMacro:
nrings = val[5]
cross_th = val[6]
cross_len = val[7]
- angle = val[8]
+ # angle = val[8]
r = dia / 2 - thickness / 2
result = Point((x, y)).buffer(r).exterior.buffer(thickness / 2.0)
@@ -441,7 +441,7 @@ class ApertureMacro:
dout = val[2]
din = val[3]
t = val[4]
- angle = val[5]
+ # angle = val[5]
ring = Point((x, y)).buffer(dout / 2.0).difference(Point((x, y)).buffer(din / 2.0))
hline = LineString([(x - dout / 2.0, y), (x + dout / 2.0, y)]).buffer(t / 2.0, cap_style=3)
@@ -1036,8 +1036,7 @@ class Geometry(object):
#
# return self.flat_geometry, self.flat_geometry_rtree
- def isolation_geometry(self, offset, geometry=None, iso_type=2, corner=None, follow=None, passes=0,
- prog_plot=False):
+ def isolation_geometry(self, offset, geometry=None, iso_type=2, corner=None, passes=0, prog_plot=False):
"""
Creates contours around geometry at a given
offset distance.
@@ -1048,7 +1047,6 @@ class Geometry(object):
:param iso_type: type of isolation, can be 0 = exteriors or 1 = interiors or 2 = both (complete)
:param corner: type of corner for the isolation:
0 = round; 1 = square; 2= beveled (line that connects the ends)
- :param follow: whether the geometry to be isolated is a follow_geometry
:param passes: current pass out of possible multiple passes for which the isolation is done
:param prog_plot: type of plotting: "normal" or "progressive"
:return: The buffered geometry.
@@ -1061,9 +1059,6 @@ class Geometry(object):
geo_iso = []
- if follow:
- return geometry
-
if geometry:
working_geo = geometry
else:
@@ -1123,7 +1118,7 @@ class Geometry(object):
ret_geo = self.get_exteriors(geo_iso)
elif iso_type == 1:
self.app.proc_container.update_view_text(' %s' % _("Get Interiors"))
- ret_geo = self.get_interiors(geo_iso)
+ ret_geo = self.get_interiors(geo_iso)
else:
log.debug("Geometry.isolation_geometry() --> Type of isolation not supported")
return "fail"
@@ -1186,8 +1181,8 @@ class Geometry(object):
merged_lines = linemerge(geos_lines)
geos = geos_polys
try:
- for l in merged_lines:
- geos.append(l)
+ for ml in merged_lines:
+ geos.append(ml)
except TypeError:
geos.append(merged_lines)
@@ -1264,8 +1259,8 @@ class Geometry(object):
merged_lines = linemerge(geos_lines)
geos = geos_polys
- for l in merged_lines:
- geos.append(l)
+ for ml in merged_lines:
+ geos.append(ml)
# Add to object
if self.solid_geometry is None:
@@ -2402,7 +2397,6 @@ class Geometry(object):
except AttributeError:
self.app.inform.emit('[ERROR_NOTCL] %s %s' % (_("Failed."), _("No object is selected.")))
-
self.app.proc_container.new_text = ''
def rotate(self, angle, point):
@@ -3661,7 +3655,7 @@ class CNCjob(Geometry):
geo_storage = {}
for geo in temp_solid_geometry:
- if not geo is None:
+ if geo is not None:
geo_storage[geo.coords[0]] = geo
locations = list(geo_storage.keys())
@@ -3862,7 +3856,7 @@ class CNCjob(Geometry):
if self.z_cut == 'fail':
return 'fail'
# multidepth use this
- old_zcut = deepcopy(self.z_cut)
+ # old_zcut = deepcopy(self.z_cut)
# XY_toolchange parameter
try:
@@ -4183,8 +4177,8 @@ class CNCjob(Geometry):
drill.y
)
optimized_path.append(unoptimized_coords)
- # #########################################################################################################
- # #########################################################################################################
+ # #####################################################################################################
+ # #####################################################################################################
# Only if there are locations to drill
if not optimized_path:
@@ -5290,19 +5284,19 @@ class CNCjob(Geometry):
self.z_cut = float(z_cut) if z_cut else None
self.z_move = float(z_move) if z_move is not None else None
- self.feedrate = float(feedrate) if feedrate else self.app.defaults["geometry_feedrate"]
+ self.feedrate = float(feedrate) if feedrate else self.app.defaults["geometry_feedrate"]
self.z_feedrate = float(feedrate_z) if feedrate_z is not None else self.app.defaults["geometry_feedrate_z"]
self.feedrate_rapid = float(feedrate_rapid) if feedrate_rapid else self.app.defaults["geometry_feedrate_rapid"]
self.spindlespeed = int(spindlespeed) if spindlespeed != 0 else None
self.spindledir = spindledir
self.dwell = dwell
- self.dwelltime = float(dwelltime) if dwelltime else self.app.defaults["geometry_dwelltime"]
+ self.dwelltime = float(dwelltime) if dwelltime else self.app.defaults["geometry_dwelltime"]
- self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"]
- self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"]
+ self.startz = float(startz) if startz is not None else self.app.defaults["geometry_startz"]
+ self.z_end = float(endz) if endz is not None else self.app.defaults["geometry_endz"]
- self.xy_end = re.sub('[()\[\]]', '', str(endxy)) if endxy else self.app.defaults["geometry_endxy"]
+ self.xy_end = re.sub('[()\[\]]', '', str(endxy)) if endxy else self.app.defaults["geometry_endxy"]
if self.xy_end and self.xy_end != '':
self.xy_end = [float(eval(a)) for a in self.xy_end.split(",")]
@@ -6937,8 +6931,7 @@ class CNCjob(Geometry):
else:
dd = d / 2
other = dim ^ 1
- return (line[0][dim] + dd * sign, line[0][other] + \
- dd * (line[1][other] - line[0][other]) / d)
+ return line[0][dim] + dd * sign, line[0][other] + dd * (line[1][other] - line[0][other]) / d
return None
# recursively breaks down a given line until it is within the
@@ -7545,16 +7538,16 @@ class CNCjob(Geometry):
cmaxx = -np.Inf
cmaxy = -np.Inf
- for k in obj:
- if type(k) is dict:
- for key in k:
- minx_, miny_, maxx_, maxy_ = bounds_rec(k[key])
+ for oo in obj:
+ if type(oo) is dict:
+ for key in oo:
+ minx_, miny_, maxx_, maxy_ = bounds_rec(oo[key])
cminx = min(cminx, minx_)
cminy = min(cminy, miny_)
cmaxx = max(cmaxx, maxx_)
cmaxy = max(cmaxy, maxy_)
else:
- minx_, miny_, maxx_, maxy_ = bounds_rec(k)
+ minx_, miny_, maxx_, maxy_ = bounds_rec(oo)
cminx = min(cminx, minx_)
cminy = min(cminy, miny_)
cmaxx = max(cmaxx, maxx_)
@@ -7744,7 +7737,7 @@ class CNCjob(Geometry):
temp_gcode += line
lines.close()
- header_stop = False
+ # header_stop = False
return temp_gcode
if self.multitool is False:
diff --git a/defaults.py b/defaults.py
index 818b1fa3..e2fc9572 100644
--- a/defaults.py
+++ b/defaults.py
@@ -415,7 +415,6 @@ class FlatCAMDefaults:
"tools_iso_passes": 1,
"tools_iso_overlap": 10,
"tools_iso_milling_type": "cl",
- "tools_iso_follow": False,
"tools_iso_isotype": "full",
"tools_iso_rest": False,