- added possibility of changing the Axis color and created a new parameter for that in Preferences
- some refactoring - in a FCColorEntry GUI element, setting a color in the Color dialog will trigger the editingFinished signal therefore propagating the changes
This commit is contained in:
818
appPlugins/ToolFollow.py
Normal file
818
appPlugins/ToolFollow.py
Normal file
@@ -0,0 +1,818 @@
|
||||
# ##########################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# File by: Marius Adrian Stanciu (c) #
|
||||
# Date: 11/12/2020 #
|
||||
# License: MIT Licence #
|
||||
# ##########################################################
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from appTool import AppTool
|
||||
from appGUI.GUIElements import RadioSet, FCButton, FCComboBox, FCLabel
|
||||
from appParsers.ParseGerber import Gerber
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
import numpy as np
|
||||
|
||||
from shapely.ops import unary_union
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
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.pluginName = self.ui.pluginName
|
||||
|
||||
# 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.level.toggled.connect(self.on_level_changed)
|
||||
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
|
||||
if self.app.ui.splitter.sizes()[0] == 0:
|
||||
self.app.ui.splitter.setSizes([1, 1])
|
||||
|
||||
# if the Tool Tab is hidden display it, else hide it but only if the objectName is the same
|
||||
found_idx = None
|
||||
for idx in range(self.app.ui.notebook.count()):
|
||||
if self.app.ui.notebook.widget(idx).objectName() == "plugin_tab":
|
||||
found_idx = idx
|
||||
break
|
||||
# show the Tab
|
||||
if not found_idx:
|
||||
self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin"))
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
|
||||
|
||||
try:
|
||||
if self.app.ui.plugin_scroll_area.widget().objectName() == self.pluginName and found_idx:
|
||||
# if the Tool Tab is not focused, focus on it
|
||||
if not self.app.ui.notebook.currentWidget() is self.app.ui.plugin_tab:
|
||||
# focus on Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.plugin_tab)
|
||||
else:
|
||||
# else remove the Tool Tab
|
||||
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
|
||||
self.app.ui.notebook.removeTab(2)
|
||||
|
||||
# if there are no objects loaded in the app then hide the Notebook widget
|
||||
if not self.app.collection.get_list():
|
||||
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"))
|
||||
|
||||
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
|
||||
|
||||
# Show/Hide Advanced Options
|
||||
app_mode = self.app.defaults["global_app_level"]
|
||||
self.change_level(app_mode)
|
||||
|
||||
def change_level(self, level):
|
||||
"""
|
||||
|
||||
:param level: application level: either 'b' or 'a'
|
||||
:type level: str
|
||||
:return:
|
||||
"""
|
||||
|
||||
if level == 'a':
|
||||
self.ui.level.setChecked(True)
|
||||
else:
|
||||
self.ui.level.setChecked(False)
|
||||
self.on_level_changed(self.ui.level.isChecked())
|
||||
|
||||
def on_level_changed(self, checked):
|
||||
if not checked:
|
||||
self.ui.level.setText('%s' % _('Beginner'))
|
||||
self.ui.level.setStyleSheet("""
|
||||
QToolButton
|
||||
{
|
||||
color: green;
|
||||
}
|
||||
""")
|
||||
|
||||
# Add Tool section
|
||||
self.ui.select_label.hide()
|
||||
self.ui.selectmethod_radio.hide()
|
||||
self.ui.param_title.hide()
|
||||
self.ui.separator_line.hide()
|
||||
else:
|
||||
self.ui.level.setText('%s' % _('Advanced'))
|
||||
self.ui.level.setStyleSheet("""
|
||||
QToolButton
|
||||
{
|
||||
color: red;
|
||||
}
|
||||
""")
|
||||
|
||||
# Add Tool section
|
||||
self.ui.select_label.show()
|
||||
self.ui.selectmethod_radio.show()
|
||||
self.ui.param_title.show()
|
||||
self.ui.separator_line.show()
|
||||
|
||||
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(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["tools_mill_tooldia"]) == float:
|
||||
tools_list = [app_obj.defaults["tools_mill_tooldia"]]
|
||||
else:
|
||||
try:
|
||||
temp_tools = app_obj.defaults["tools_mill_tooldia"].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 in app_obj.options:
|
||||
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_') == 0:
|
||||
new_data[opt_key] = app_obj.options[opt_key]
|
||||
|
||||
try:
|
||||
__ = iter(followed_obj.follow_geometry)
|
||||
except TypeError:
|
||||
followed_obj.follow_geometry = [followed_obj.follow_geometry]
|
||||
|
||||
follow_geo = [
|
||||
g for g in followed_obj.follow_geometry if g and not g.is_empty and g.is_valid and
|
||||
g.geom_type != 'Point'
|
||||
]
|
||||
|
||||
if not follow_geo:
|
||||
self.app.log.warning("ToolFollow.follow_geo() -> Empty Follow Geometry")
|
||||
return 'fail'
|
||||
|
||||
# Propagate options
|
||||
new_obj.options["tools_mill_tooldia"] = app_obj.defaults["tools_mill_tooldia"]
|
||||
new_obj.solid_geometry = follow_geo
|
||||
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
|
||||
}
|
||||
}
|
||||
for d in new_data:
|
||||
print(d, new_data[d])
|
||||
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):
|
||||
new_obj.multigeo = True
|
||||
|
||||
if type(app_obj.defaults["tools_mill_tooldia"]) == float:
|
||||
tools_list = [app_obj.defaults["tools_mill_tooldia"]]
|
||||
else:
|
||||
try:
|
||||
temp_tools = app_obj.defaults["tools_mill_tooldia"].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_') == 0:
|
||||
new_data[opt_key] = app_obj.options[opt_key]
|
||||
|
||||
# Propagate options
|
||||
new_obj.options["tools_mill_tooldia"] = app_obj.defaults["tools_mill_tooldia"]
|
||||
new_data["tools_mill_tooldia"] = app_obj.defaults["tools_mill_tooldia"]
|
||||
|
||||
target_geo = unary_union(followed_obj.follow_geometry)
|
||||
area_follow = target_geo.intersection(deepcopy(unary_union(self.sel_rect)))
|
||||
self.sel_rect[:] = []
|
||||
self.points = []
|
||||
|
||||
try:
|
||||
__ = iter(area_follow)
|
||||
except TypeError:
|
||||
area_follow = [area_follow]
|
||||
|
||||
cleaned_area_follow = [g for g in area_follow if not g.is_empty and g.is_valid and g.geom_type != 'Point']
|
||||
|
||||
new_obj.solid_geometry = deepcopy(cleaned_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)
|
||||
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(" <b>X</b>: %.4f "
|
||||
"<b>Y</b>: %.4f " % (curr_pos[0], curr_pos[1]))
|
||||
self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f <b>Dy</b>: "
|
||||
"%.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.error("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:
|
||||
|
||||
pluginName = _("Follow")
|
||||
|
||||
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.pluginName)
|
||||
title_label.setStyleSheet("""
|
||||
QLabel
|
||||
{
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
""")
|
||||
title_label.setToolTip(
|
||||
_("Create a Geometry object with\n"
|
||||
"toolpaths to cut through the middle of polygons.")
|
||||
)
|
||||
|
||||
self.title_box.addWidget(title_label)
|
||||
|
||||
# App Level label
|
||||
self.level = QtWidgets.QToolButton()
|
||||
self.level.setToolTip(
|
||||
_(
|
||||
"BASIC is suitable for a beginner. Many parameters\n"
|
||||
"are hidden from the user in this mode.\n"
|
||||
"ADVANCED mode will make available all parameters.\n\n"
|
||||
"To change the application LEVEL, go to:\n"
|
||||
"Edit -> Preferences -> General and check:\n"
|
||||
"'APP. LEVEL' radio button."
|
||||
)
|
||||
)
|
||||
self.level.setCheckable(True)
|
||||
self.title_box.addWidget(self.level)
|
||||
|
||||
self.obj_combo_label = FCLabel('<b>%s</b>:' % _("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("<b>%s:</b>" % _("Parameters"))
|
||||
grid0.addWidget(self.param_title, 0, 0, 1, 2)
|
||||
|
||||
# Polygon selection
|
||||
self.select_label = FCLabel('%s:' % _('Selection'))
|
||||
self.select_label.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(self.select_label, 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()
|
||||
|
||||
self.separator_line = QtWidgets.QFrame()
|
||||
self.separator_line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
grid0.addWidget(self.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)
|
||||
# ############################ FINISHED 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)
|
||||
Reference in New Issue
Block a user