- in Geometry Editor, for Path tool, added UI that close on end of the Path tool action; it displays the projected length which now is kept for as long as it is wanted, allowing for path automation in case of repetitive lengths

This commit is contained in:
Marius Stanciu
2022-04-14 14:01:01 +03:00
committed by Marius
parent ff954c4f62
commit 53c92306dd
5 changed files with 226 additions and 47 deletions

View File

@@ -12,6 +12,7 @@ CHANGELOG for FlatCAM Evo beta
- in Geometry Editor added a new feature. While drawing a 'Path' now the user can project a direction by moving the mouse cursor in a certain direction and after that by typing a number or an arithmetic simple expression, a line segment will be drawn in that direction with the specified length from the last point - in Geometry Editor added a new feature. While drawing a 'Path' now the user can project a direction by moving the mouse cursor in a certain direction and after that by typing a number or an arithmetic simple expression, a line segment will be drawn in that direction with the specified length from the last point
- in Geometry Editor for the Path tool but only when using the 3D engine graphic mode, the mouse cursor is followed by position data - in Geometry Editor for the Path tool but only when using the 3D engine graphic mode, the mouse cursor is followed by position data
- in Geometry Editor for the Path tool fixed an issue with path projection when changing the grid size while the Path tool is active - in Geometry Editor for the Path tool fixed an issue with path projection when changing the grid size while the Path tool is active
- in Geometry Editor, for Path tool, added UI that close on end of the Path tool action; it displays the projected length which now is kept for as long as it is wanted, allowing for path automation in case of repetitive lengths
13.04.2022 13.04.2022

View File

@@ -22,6 +22,7 @@ from appEditors.plugins.GeoBufferPlugin import BufferSelectionTool
from appEditors.plugins.GeoPaintPlugin import PaintOptionsTool from appEditors.plugins.GeoPaintPlugin import PaintOptionsTool
from appEditors.plugins.GeoTextPlugin import TextInputTool from appEditors.plugins.GeoTextPlugin import TextInputTool
from appEditors.plugins.GeoTransformationPlugin import TransformEditorTool from appEditors.plugins.GeoTransformationPlugin import TransformEditorTool
from appEditors.plugins.GeoPathPlugin import PathEditorTool
from vispy.geometry import Rect from vispy.geometry import Rect
@@ -939,8 +940,8 @@ class FCPath(FCPolygon):
def __init__(self, draw_app): def __init__(self, draw_app):
FCPolygon.__init__(self, draw_app) FCPolygon.__init__(self, draw_app)
self.draw_app = draw_app self.draw_app = draw_app
self.interpolate_length = ''
self.name = 'path' self.name = 'path'
self.app = self.draw_app.app
try: try:
QtGui.QGuiApplication.restoreOverrideCursor() QtGui.QGuiApplication.restoreOverrideCursor()
@@ -952,6 +953,12 @@ class FCPath(FCPolygon):
self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data self.draw_app.app.plotcanvas.view.camera.zoom_callback = self.draw_cursor_data
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x)) self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
self.path_tool = PathEditorTool(self.app, self.draw_app)
self.path_tool.run()
self.app.ui.notebook.setTabText(2, _("Path"))
if self.draw_app.app.ui.splitter.sizes()[0] == 0:
self.draw_app.app.ui.splitter.setSizes([1, 1])
def make(self): def make(self):
self.geometry = DrawToolShape(LineString(self.points)) self.geometry = DrawToolShape(LineString(self.points))
self.name = 'path' self.name = 'path'
@@ -963,7 +970,6 @@ class FCPath(FCPolygon):
self.draw_app.in_action = False self.draw_app.in_action = False
self.complete = True self.complete = True
self.interpolate_length = ''
self.draw_app.app.jump_signal.disconnect() self.draw_app.app.jump_signal.disconnect()
self.geometry.data['type'] = _('Path') self.geometry.data['type'] = _('Path')
@@ -1050,20 +1056,31 @@ class FCPath(FCPolygon):
QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]: QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]:
try: try:
# VisPy keys # VisPy keys
self.interpolate_length += str(key.name) if self.path_tool.length == 0:
self.path_tool.length = str(key.name)
else:
self.path_tool.length = str(self.path_tool.length) + str(key.name)
except AttributeError: except AttributeError:
# Qt keys # Qt keys
self.interpolate_length += chr(key) if self.path_tool.length == 0:
self.path_tool.length = chr(key)
else:
self.path_tool.length = str(self.path_tool.length) + chr(key)
if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter: if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter:
if self.interpolate_length != '': if self.path_tool.length != 0:
target_length = self.interpolate_length.replace(',', '.') # target_length = self.interpolate_length.replace(',', '.')
try: # try:
target_length = eval(target_length) # target_length = eval(target_length)
except SyntaxError as err: # except SyntaxError as err:
ret = '%s: %s' % (str(err).capitalize(), self.interpolate_length) # ret = '%s: %s' % (str(err).capitalize(), self.interpolate_length)
self.interpolate_length = '' # self.interpolate_length = ''
return ret # return ret
target_length = self.path_tool.length
if target_length is None:
self.path_tool.length = 0.0
return _("Failed.")
first_pt = self.points[-1] first_pt = self.points[-1]
last_pt = self.draw_app.app.mouse last_pt = self.draw_app.app.mouse
@@ -1077,15 +1094,16 @@ class FCPath(FCPolygon):
self.clean_up() self.clean_up()
return '%s %s' % (_("Failed."), str(err)) return '%s %s' % (_("Failed."), str(err))
self.points.append((new_x, new_y)) if self.points[-1] != (new_x, new_y):
self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False) self.points.append((new_x, new_y))
if len(self.points) > 0: self.draw_app.app.on_jump_to(custom_location=(new_x, new_y), fit_center=False)
msg = '%s: %s. %s' % ( if len(self.points) > 0:
_("Projected"), str(self.interpolate_length), msg = '%s: %s. %s' % (
_("Click on next Point or click right mouse button to complete ...")) _("Projected"), str(self.path_tool.length),
self.draw_app.app.inform.emit(msg) _("Click on next Point or click right mouse button to complete ..."))
self.interpolate_length = '' self.draw_app.app.inform.emit(msg)
# return "Click on next point or hit ENTER to complete ..." # self.interpolate_length = ''
# return "Click on next point or hit ENTER to complete ..."
def clean_up(self): def clean_up(self):
self.draw_app.selected = [] self.draw_app.selected = []
@@ -1100,6 +1118,7 @@ class FCPath(FCPolygon):
self.draw_app.app.jump_signal.disconnect() self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError): except (TypeError, AttributeError):
pass pass
self.path_tool.on_tab_close()
class FCSelect(DrawTool): class FCSelect(DrawTool):
@@ -1121,6 +1140,12 @@ class FCSelect(DrawTool):
if self.draw_app.app.plotcanvas.text_cursor.parent: if self.draw_app.app.plotcanvas.text_cursor.parent:
self.draw_app.app.plotcanvas.text_cursor.parent = None self.draw_app.app.plotcanvas.text_cursor.parent = None
# make sure that the Tools tab is removed
try:
self.draw_app.app.ui.notebook.removeTab(2)
except Exception:
pass
def click_release(self, point): def click_release(self, point):
""" """
@@ -1609,7 +1634,7 @@ class FCBuffer(FCShapeTool):
self.origin = (0, 0) self.origin = (0, 0)
self.buff_tool = BufferSelectionTool(self.app, self.draw_app) self.buff_tool = BufferSelectionTool(self.app, self.draw_app)
self.buff_tool.run() self.buff_tool.run()
self.app.ui.notebook.setTabText(2, _("Buffer Tool")) self.app.ui.notebook.setTabText(2, _("Buffer"))
if self.draw_app.app.ui.splitter.sizes()[0] == 0: if self.draw_app.app.ui.splitter.sizes()[0] == 0:
self.draw_app.app.ui.splitter.setSizes([1, 1]) self.draw_app.app.ui.splitter.setSizes([1, 1])
self.activate() self.activate()
@@ -1620,19 +1645,19 @@ class FCBuffer(FCShapeTool):
return return
try: try:
buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value()) buffer_distance = float(self.buff_tool.ui.buffer_distance_entry.get_value())
except ValueError: except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return # try to convert comma to decimal point. if it's still not working error message and return
try: try:
buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value().replace(',', '.')) buffer_distance = float(self.buff_tool.ui.buffer_distance_entry.get_value().replace(',', '.'))
self.buff_tool.buffer_distance_entry.set_value(buffer_distance) self.buff_tool.ui.buffer_distance_entry.set_value(buffer_distance)
except ValueError: except ValueError:
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Buffer distance value is missing or wrong format. Add it and retry.")) _("Buffer distance value is missing or wrong format. Add it and retry."))
return return
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1 join_style = self.buff_tool.ui.buffer_corner_cb.currentIndex() + 1
ret_val = self.draw_app.buffer(buffer_distance, join_style) ret_val = self.draw_app.buffer(buffer_distance, join_style)
self.deactivate() self.deactivate()
@@ -1646,19 +1671,19 @@ class FCBuffer(FCShapeTool):
return return
try: try:
buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value()) buffer_distance = float(self.buff_tool.ui.buffer_distance_entry.get_value())
except ValueError: except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return # try to convert comma to decimal point. if it's still not working error message and return
try: try:
buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value().replace(',', '.')) buffer_distance = float(self.buff_tool.ui.buffer_distance_entry.get_value().replace(',', '.'))
self.buff_tool.buffer_distance_entry.set_value(buffer_distance) self.buff_tool.ui.buffer_distance_entry.set_value(buffer_distance)
except ValueError: except ValueError:
self.app.inform.emit('[WARNING_NOTCL] %s' % self.app.inform.emit('[WARNING_NOTCL] %s' %
_("Buffer distance value is missing or wrong format. Add it and retry.")) _("Buffer distance value is missing or wrong format. Add it and retry."))
return return
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1 join_style = self.buff_tool.ui.buffer_corner_cb.currentIndex() + 1
ret_val = self.draw_app.buffer_int(buffer_distance, join_style) ret_val = self.draw_app.buffer_int(buffer_distance, join_style)
self.deactivate() self.deactivate()
@@ -1672,19 +1697,19 @@ class FCBuffer(FCShapeTool):
return return
try: try:
buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value()) buffer_distance = float(self.buff_tool.ui.buffer_distance_entry.get_value())
except ValueError: except ValueError:
# try to convert comma to decimal point. if it's still not working error message and return # try to convert comma to decimal point. if it's still not working error message and return
try: try:
buffer_distance = float(self.buff_tool.buffer_distance_entry.get_value().replace(',', '.')) buffer_distance = float(self.buff_tool.ui.buffer_distance_entry.get_value().replace(',', '.'))
self.buff_tool.buffer_distance_entry.set_value(buffer_distance) self.buff_tool.ui.buffer_distance_entry.set_value(buffer_distance)
except ValueError: except ValueError:
self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' % self.draw_app.app.inform.emit('[WARNING_NOTCL] %s' %
_("Buffer distance value is missing or wrong format. Add it and retry.")) _("Buffer distance value is missing or wrong format. Add it and retry."))
return return
# the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment # the cb index start from 0 but the join styles for the buffer start from 1 therefore the adjustment
# I populated the combobox such that the index coincide with the join styles value (whcih is really an INT) # I populated the combobox such that the index coincide with the join styles value (whcih is really an INT)
join_style = self.buff_tool.buffer_corner_cb.currentIndex() + 1 join_style = self.buff_tool.ui.buffer_corner_cb.currentIndex() + 1
ret_val = self.draw_app.buffer_ext(buffer_distance, join_style) ret_val = self.draw_app.buffer_ext(buffer_distance, join_style)
# self.app.ui.notebook.setTabText(2, _("Tools")) # self.app.ui.notebook.setTabText(2, _("Tools"))
# self.draw_app.app.ui.splitter.setSizes([0, 1]) # self.draw_app.app.ui.splitter.setSizes([0, 1])

View File

@@ -61,7 +61,7 @@ class BufferSelectionTool(AppTool):
# self.app.ui.notebook.callback_on_close = self.on_tab_close # self.app.ui.notebook.callback_on_close = self.on_tab_close
self.app.ui.notebook.setTabText(2, _("Buffer Tool")) self.app.ui.notebook.setTabText(2, _("Buffer"))
def set_tool_ui(self): def set_tool_ui(self):
# Init appGUI # Init appGUI
@@ -375,7 +375,7 @@ class BufferEditorUI:
# Buffer distance # Buffer distance
self.buffer_distance_entry = FCDoubleSpinner() self.buffer_distance_entry = FCDoubleSpinner()
self.buffer_distance_entry.set_precision(self.decimals) self.buffer_distance_entry.set_precision(self.decimals)
self.buffer_distance_entry.set_range(0.0000, 9910000.0000) self.buffer_distance_entry.set_range(0.0000, 10000.0000)
grid_buffer.addWidget(FCLabel('%s:' % _("Buffer distance")), 0, 0) grid_buffer.addWidget(FCLabel('%s:' % _("Buffer distance")), 0, 0)
grid_buffer.addWidget(self.buffer_distance_entry, 0, 1) grid_buffer.addWidget(self.buffer_distance_entry, 0, 1)

View File

@@ -0,0 +1,152 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class PathEditorTool(AppTool):
"""
Simple input for buffer distance.
"""
def __init__(self, app, draw_app):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self.ui = PathEditorUI(layout=self.layout, path_class=self)
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.clear_btn.clicked.connect(self.on_clear)
def disconnect_signals(self):
# Signals
try:
self.ui.clear_btn.clicked.disconnect()
except (TypeError, AttributeError):
pass
def run(self):
self.app.defaults.report_usage("Geo Editor ToolPath()")
AppTool.run(self)
# if the splitter us 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:
try:
self.app.ui.notebook.addTab(self.app.ui.plugin_tab, _("Plugin"))
except RuntimeError:
self.app.ui.plugin_tab = QtWidgets.QWidget()
self.app.ui.plugin_tab.setObjectName("plugin_tab")
self.app.ui.plugin_tab_layout = QtWidgets.QVBoxLayout(self.app.ui.plugin_tab)
self.app.ui.plugin_tab_layout.setContentsMargins(2, 2, 2, 2)
self.app.ui.plugin_scroll_area = VerticalScrollArea()
self.app.ui.plugin_tab_layout.addWidget(self.app.ui.plugin_scroll_area)
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)
# self.app.ui.notebook.callback_on_close = self.on_tab_close
self.app.ui.notebook.setTabText(2, _("Path"))
def set_tool_ui(self):
# Init appGUI
self.length = 0.0
def on_tab_close(self):
self.disconnect_signals()
self.hide_tool()
# self.app.ui.notebook.callback_on_close = lambda: None
def on_clear(self):
self.set_tool_ui()
@property
def length(self):
return self.ui.project_line_entry.get_value()
@length.setter
def length(self, val):
self.ui.project_line_entry.set_value(val)
def hide_tool(self):
self.ui.path_tool_frame.hide()
self.app.ui.notebook.setCurrentWidget(self.app.ui.properties_tab)
if self.draw_app.active_tool.name != 'select':
self.draw_app.select_tool("select")
class PathEditorUI:
pluginName = _("Path")
def __init__(self, layout, path_class):
self.path_class = path_class
self.decimals = self.path_class.app.decimals
self.layout = layout
# Title
title_label = FCLabel("%s" % ('Editor ' + self.pluginName))
title_label.setStyleSheet("""
QLabel
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout.addWidget(title_label)
# this way I can hide/show the frame
self.path_tool_frame = QtWidgets.QFrame()
self.path_tool_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.path_tool_frame)
self.path_tool_box = QtWidgets.QVBoxLayout()
self.path_tool_box.setContentsMargins(0, 0, 0, 0)
self.path_tool_frame.setLayout(self.path_tool_box)
# Grid Layout
grid_path = GLay(v_spacing=5, h_spacing=3)
self.path_tool_box.addLayout(grid_path)
# Project distance
self.project_line_lbl = FCLabel('%s:' % _("Length"))
self.project_line_entry = NumericalEvalEntry(border_color='#0069A9')
grid_path.addWidget(self.project_line_lbl, 0, 0)
grid_path.addWidget(self.project_line_entry, 0, 1)
# self.buffer_corner_lbl = FCLabel('%s:' % _("Buffer corner"))
# self.buffer_corner_lbl.setToolTip(
# _("There are 3 types of corners:\n"
# " - 'Round': the corner is rounded for exterior buffer.\n"
# " - 'Square': the corner is met in a sharp angle for exterior buffer.\n"
# " - 'Beveled': the corner is a line that directly connects the features meeting in the corner")
# )
# self.buffer_corner_cb = FCComboBox()
# self.buffer_corner_cb.addItem(_("Round"))
# self.buffer_corner_cb.addItem(_("Square"))
# self.buffer_corner_cb.addItem(_("Beveled"))
# grid_path.addWidget(self.buffer_corner_lbl, 2, 0)
# grid_path.addWidget(self.buffer_corner_cb, 2, 1)
self.clear_btn = FCButton(_("Clear"))
grid_path.addWidget(self.clear_btn, 4, 0, 1, 2)
self.layout.addStretch(1)

View File

@@ -3482,20 +3482,21 @@ class MainGUI(QtWidgets.QMainWindow):
# automatically make the selection tool active after completing current action # automatically make the selection tool active after completing current action
self.app.geo_editor.select_tool('select') self.app.geo_editor.select_tool('select')
return return
elif self.app.geo_editor.active_tool.name == 'path':
if self.app.geo_editor.active_tool.interpolate_length != '':
pass
else: else:
self.app.geo_editor.active_tool.click( if self.app.geo_editor.active_tool.name == 'path' and \
self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y)) self.app.geo_editor.active_tool.path_tool.length != 0.0:
pass
else:
self.app.geo_editor.active_tool.click(
self.app.geo_editor.snap(self.app.geo_editor.x, self.app.geo_editor.y))
self.app.geo_editor.active_tool.make() self.app.geo_editor.active_tool.make()
if self.app.geo_editor.active_tool.complete: if self.app.geo_editor.active_tool.complete:
self.app.geo_editor.on_shape_complete() self.app.geo_editor.on_shape_complete()
self.app.inform.emit('[success] %s' % _("Done.")) self.app.inform.emit('[success] %s' % _("Done."))
# automatically make the selection tool active after completing current action # automatically make the selection tool active after completing current action
self.app.geo_editor.select_tool('select') self.app.geo_editor.select_tool('select')
# Abort the current action # Abort the current action
if key == QtCore.Qt.Key.Key_Escape or key == 'Escape': if key == QtCore.Qt.Key.Key_Escape or key == 'Escape':