- in Geometry Editor, in Circle Tool added UI, cursor data, radius projection and ability to add ellipses

This commit is contained in:
Marius Stanciu
2022-04-16 16:01:39 +03:00
committed by Marius
parent e399c6cddc
commit 4d496e4539
11 changed files with 449 additions and 13 deletions

View File

@@ -18,13 +18,14 @@ from camlib import distance, arc, three_point_circle, Geometry, AppRTreeStorage,
from appGUI.GUIElements import *
from appGUI.VisPyVisuals import ShapeCollection
from appEditors.plugins.GeoBufferPlugin import BufferSelectionTool
from appEditors.plugins.GeoPaintPlugin import PaintOptionsTool
from appEditors.plugins.GeoTextPlugin import TextInputTool
from appEditors.plugins.GeoTransformationPlugin import TransformEditorTool
from appEditors.plugins.GeoPathPlugin import PathEditorTool
from appEditors.plugins.GeoSimplificationPlugin import SimplificationTool
from appEditors.plugins.GeoRectanglePlugin import RectangleEditorTool
from appEditors.geo_plugins.GeoBufferPlugin import BufferSelectionTool
from appEditors.geo_plugins.GeoPaintPlugin import PaintOptionsTool
from appEditors.geo_plugins.GeoTextPlugin import TextInputTool
from appEditors.geo_plugins.GeoTransformationPlugin import TransformEditorTool
from appEditors.geo_plugins.GeoPathPlugin import PathEditorTool
from appEditors.geo_plugins.GeoSimplificationPlugin import SimplificationTool
from appEditors.geo_plugins.GeoRectanglePlugin import RectangleEditorTool
from appEditors.geo_plugins.GeoCirclePlugin import CircleEditorTool
from vispy.geometry import Rect
@@ -455,6 +456,9 @@ class FCCircle(FCShapeTool):
self.name = 'circle'
self.draw_app = draw_app
self.app = self.draw_app.app
self.plugin_name = _("Circle")
self.storage = self.draw_app.storage
try:
QtGui.QGuiApplication.restoreOverrideCursor()
@@ -463,6 +467,13 @@ class FCCircle(FCShapeTool):
self.cursor = QtGui.QCursor(QtGui.QPixmap(self.draw_app.app.resource_location + '/aero_circle_geo.png'))
QtGui.QGuiApplication.setOverrideCursor(self.cursor)
if self.app.use_3d_engine:
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.circle_tool = CircleEditorTool(self.app, self.draw_app, plugin_name=self.plugin_name)
self.circle_tool.run()
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
self.draw_app.app.inform.emit(_("Click on Center point ..."))
@@ -475,7 +486,36 @@ class FCCircle(FCShapeTool):
pass
self.draw_app.app.jump_signal.connect(lambda x: self.draw_app.update_utility_geometry(data=x))
modifiers = QtWidgets.QApplication.keyboardModifiers()
if modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier:
# deselect all shapes
self.draw_app.selected = []
for ____ in self.storage.get_objects():
try:
__, closest_shape = self.storage.nearest(point)
# select closes shape
self.draw_app.selected.append(closest_shape)
except StopIteration:
return ""
if self.draw_app.selected:
self.draw_app.plot_all()
self.circle_tool.mode = 'change'
sel_shape_geo = self.draw_app.selected[-1].geo
geo_bounds = sel_shape_geo.bounds
# assuming that the default setting for anchor is center
origin_x_sel_geo = geo_bounds[0] + ((geo_bounds[2] - geo_bounds[0]) / 2)
self.circle_tool.ui.x_entry.set_value(origin_x_sel_geo)
origin_y_sel_geo = geo_bounds[1] + ((geo_bounds[3] - geo_bounds[1]) / 2)
self.circle_tool.ui.y_entry.set_value(origin_y_sel_geo)
self.draw_app.app.inform.emit(
_("Click on Center point to add a new circle or Apply to change the selection."))
return
self.circle_tool.mode = 'add'
self.points.append(point)
self.circle_tool.ui.x_entry.set_value(point[0])
self.circle_tool.ui.y_entry.set_value(point[1])
if len(self.points) == 1:
self.draw_app.app.inform.emit(_("Click on Perimeter point to complete ..."))
@@ -496,6 +536,117 @@ class FCCircle(FCShapeTool):
return None
def draw_cursor_data(self, pos=None, delete=False):
if pos is None:
pos = self.draw_app.snap_x, self.draw_app.snap_y
if delete:
if self.draw_app.app.use_3d_engine:
self.draw_app.app.plotcanvas.text_cursor.parent = None
self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None
return
# font size
qsettings = QtCore.QSettings("Open Source", "FlatCAM")
if qsettings.contains("hud_font_size"):
fsize = qsettings.value('hud_font_size', type=int)
else:
fsize = 8
x = pos[0]
y = pos[1]
try:
length = abs(np.sqrt((pos[0] - self.points[-1][0]) ** 2 + (pos[1] - self.points[-1][1]) ** 2))
except IndexError:
length = self.draw_app.app.dec_format(0.0, self.draw_app.app.decimals)
units = self.draw_app.app.app_units.lower()
x_dec = str(self.draw_app.app.dec_format(x, self.draw_app.app.decimals)) if x else '0.0'
y_dec = str(self.draw_app.app.dec_format(y, self.draw_app.app.decimals)) if y else '0.0'
length_dec = str(self.draw_app.app.dec_format(length, self.draw_app.app.decimals)) if length else '0.0'
l1_txt = 'X: %s [%s]' % (x_dec, units)
l2_txt = 'Y: %s [%s]' % (y_dec, units)
l3_txt = 'L: %s [%s]' % (length_dec, units)
cursor_text = '%s\n%s\n\n%s' % (l1_txt, l2_txt, l3_txt)
if self.draw_app.app.use_3d_engine:
new_pos = self.draw_app.app.plotcanvas.translate_coords_2((x, y))
x, y, __, ___ = self.draw_app.app.plotcanvas.translate_coords((new_pos[0]+30, new_pos[1]))
# text
self.draw_app.app.plotcanvas.text_cursor.font_size = fsize
self.draw_app.app.plotcanvas.text_cursor.text = cursor_text
self.draw_app.app.plotcanvas.text_cursor.pos = x, y
self.draw_app.app.plotcanvas.text_cursor.anchors = 'left', 'top'
if self.draw_app.app.plotcanvas.text_cursor.parent is None:
self.draw_app.app.plotcanvas.text_cursor.parent = self.draw_app.app.plotcanvas.view.scene
def on_key(self, key):
# Jump to coords
if key == QtCore.Qt.Key.Key_J or key == 'J':
self.draw_app.app.on_jump_to()
if key in [str(i) for i in range(10)] + ['.', ',', '+', '-', '/', '*'] or \
key in [QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_0, QtCore.Qt.Key.Key_1, QtCore.Qt.Key.Key_2,
QtCore.Qt.Key.Key_3, QtCore.Qt.Key.Key_4, QtCore.Qt.Key.Key_5, QtCore.Qt.Key.Key_6,
QtCore.Qt.Key.Key_7, QtCore.Qt.Key.Key_8, QtCore.Qt.Key.Key_9, QtCore.Qt.Key.Key_Minus,
QtCore.Qt.Key.Key_Plus, QtCore.Qt.Key.Key_Comma, QtCore.Qt.Key.Key_Period,
QtCore.Qt.Key.Key_Slash, QtCore.Qt.Key.Key_Asterisk]:
if self.draw_app.app.mouse[0] != self.points[-1][0] or (
self.draw_app.app.mouse[1] != self.points[-1][1] and
self.circle_tool.ui.radius_link_btn.isChecked()):
try:
# VisPy keys
if self.circle_tool.radius_x == 0.0:
self.circle_tool.radius_x = str(key.name)
else:
self.circle_tool.radius_x = str(self.circle_tool.radius_x) + str(key.name)
except AttributeError:
# Qt keys
if self.circle_tool.radius_x == 0.0:
self.circle_tool.radius_x = chr(key)
else:
self.circle_tool.radius_x = str(self.circle_tool.radius_x) + chr(key)
if self.draw_app.app.mouse[1] != self.points[-1][1] or (
self.draw_app.app.mouse[0] != self.points[-1][0] and
self.circle_tool.ui.radius_link_btn.isChecked()):
try:
# VisPy keys
if self.circle_tool.radius_y == 0.0:
self.circle_tool.radius_y = str(key.name)
else:
self.circle_tool.radius_y = str(self.circle_tool.radius_y) + str(key.name)
except AttributeError:
# Qt keys
if self.circle_tool.radius_y == 0.0:
self.circle_tool.radius_y = chr(key)
else:
self.circle_tool.radius_y = str(self.circle_tool.radius_y) + chr(key)
if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter:
new_radius_x, new_radius_y = self.circle_tool.radius_x, self.circle_tool.radius_y
if self.circle_tool.ui.radius_link_btn.isChecked():
if self.circle_tool.radius_x == 0:
return _("Failed.")
else:
if self.circle_tool.radius_x == 0 or self.circle_tool.radius_y == 0:
return _("Failed.")
new_pt = (
new_radius_x + self.circle_tool.ui.x_entry.get_value(),
self.circle_tool.ui.y_entry.get_value()
)
self.points.append(new_pt)
self.make()
self.draw_app.on_shape_complete()
self.draw_app.select_tool("select")
return "Done."
def make(self):
try:
QtGui.QGuiApplication.restoreOverrideCursor()
@@ -506,18 +657,28 @@ class FCCircle(FCShapeTool):
p2 = self.points[1]
radius = distance(p1, p2)
circle_shape = Point(p1).buffer(radius, int(self.steps_per_circ / 4)).exterior
self.geometry = DrawToolShape(circle_shape)
self.complete = True
self.draw_app.app.jump_signal.disconnect()
self.geometry.data['type'] = _('Circle')
self.complete = True
self.draw_cursor_data(delete=True)
try:
self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError):
pass
self.draw_app.app.inform.emit('[success] %s' % _("Done."))
def clean_up(self):
self.draw_app.selected = []
self.draw_app.plot_all()
if self.draw_app.app.use_3d_engine:
self.draw_app.app.plotcanvas.text_cursor.parent = None
self.draw_app.app.plotcanvas.view.camera.zoom_callback = lambda *args: None
try:
self.draw_app.app.jump_signal.disconnect()
except (TypeError, AttributeError):
@@ -953,9 +1114,9 @@ class FCRectangle(FCShapeTool):
except AttributeError:
# Qt keys
if self.rect_tool.width == 0:
self.rect_tool.length = chr(key)
self.rect_tool.width = chr(key)
else:
self.rect_tool.length = str(self.rect_tool.width) + chr(key)
self.rect_tool.width = str(self.rect_tool.width) + chr(key)
if key == 'Enter' or key == QtCore.Qt.Key.Key_Return or key == QtCore.Qt.Key.Key_Enter:
new_x, new_y = self.points[-1][0], self.points[-1][1]

View File

@@ -0,0 +1,270 @@
from appTool import *
fcTranslate.apply_language('strings')
if '_' not in builtins.__dict__:
_ = gettext.gettext
class CircleEditorTool(AppTool):
"""
Simple input for buffer distance.
"""
def __init__(self, app, draw_app, plugin_name):
AppTool.__init__(self, app)
self.draw_app = draw_app
self.decimals = app.decimals
self._mode = 'add'
self.ui = CircleEditorUI(layout=self.layout, circle_class=self)
self.ui.pluginName = plugin_name
self.connect_signals_at_init()
self.set_tool_ui()
def connect_signals_at_init(self):
# Signals
self.ui.add_button.clicked.connect(self.on_execute)
def run(self):
self.app.defaults.report_usage("Geo Editor CircleTool()")
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, self.ui.pluginName)
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, self.ui.pluginName)
def set_tool_ui(self):
# Init appGUI
self.ui.x_entry.set_value(0)
self.ui.y_entry.set_value(0)
self.ui.radius_x_entry.set_value(0)
self.ui.radius_y_entry.set_value(0)
self.ui.angle_entry.set_value(0)
self.ui.radius_link_btn.setChecked(True)
self.ui.on_link_checked(True)
def on_tab_close(self):
self.draw_app.select_tool("select")
self.app.ui.notebook.callback_on_close = lambda: None
def on_execute(self):
if self.mode == 'add':
self.app.log.info("CircleEditorTool.on_add() -> adding a Circle shape")
self.on_add()
else:
self.app.log.info("RectangleEditorTool.on_add() -> modifying a Circle shape")
self.draw_app.delete_selected()
self.on_add()
self.draw_app.app.inform.emit(_("Click on Center point ..."))
def on_add(self):
origin_x = self.ui.x_entry.get_value()
origin_y = self.ui.y_entry.get_value()
radius_x = self.ui.radius_x_entry.get_value()
radius_y = self.ui.radius_y_entry.get_value()
angle = self.ui.angle_entry.get_value()
is_circle = True if self.ui.radius_link_btn.isChecked() else False
if radius_x == 0.0 or radius_y == 0.0:
self.app.inform.emit('[WARNING_NOTCL] %s' % _("Failed."))
return
if is_circle:
geo = Point((origin_x, origin_y)).buffer(radius_x)
else: # 'ellipse'
circle_geo = Point((origin_x, origin_y)).buffer(1)
geo = scale(circle_geo, radius_x, radius_y)
if angle != 0:
geo = rotate(geo, -angle)
added_shapes = self.draw_app.add_shape(geo.exterior)
for added_shape in added_shapes:
added_shape.data['type'] = _("Circle")
self.draw_app.plot_all()
def on_clear(self):
self.set_tool_ui()
@property
def mode(self):
return self._mode
@mode.setter
def mode(self, val):
self._mode = val
if self._mode == 'add':
# remove selections when adding a new rectangle
self.draw_app.selected = []
self.ui.add_button.set_value(_("Add"))
self.ui.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
else:
self.ui.add_button.set_value(_("Apply"))
self.ui.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/apply32.png'))
@property
def radius_x(self):
return self.ui.radius_x_entry.get_value()
@radius_x.setter
def radius_x(self, val):
self.ui.radius_x_entry.set_value(val)
@property
def radius_y(self):
return self.ui.radius_y_entry.get_value()
@radius_y.setter
def radius_y(self, val):
self.ui.radius_y_entry.set_value(val)
def hide_tool(self):
self.ui.circle_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 CircleEditorUI:
pluginName = _("Circle")
def __init__(self, layout, circle_class):
self.circle_class = circle_class
self.decimals = self.circle_class.app.decimals
self.app = self.circle_class.app
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.circle_frame = QtWidgets.QFrame()
self.circle_frame.setContentsMargins(0, 0, 0, 0)
self.layout.addWidget(self.circle_frame)
self.circle_tool_box = QtWidgets.QVBoxLayout()
self.circle_tool_box.setContentsMargins(0, 0, 0, 0)
self.circle_frame.setLayout(self.circle_tool_box)
# Grid Layout
grid0 = GLay(v_spacing=5, h_spacing=3)
self.circle_tool_box.addLayout(grid0)
# Position
self.pos_lbl = FCLabel('<b>%s</b>' % _("Position"))
grid0.addWidget(self.pos_lbl, 0, 0, 1, 3)
# X Pos
self.x_lbl = FCLabel('%s:' % _("X"))
self.x_entry = FCDoubleSpinner()
self.x_entry.set_precision(self.decimals)
self.x_entry.set_range(-10000.0000, 10000.0000)
grid0.addWidget(self.x_lbl, 2, 0)
grid0.addWidget(self.x_entry, 2, 1, 1, 2)
# Y Pos
self.y_lbl = FCLabel('%s:' % _("Y"))
self.y_entry = FCDoubleSpinner()
self.y_entry.set_precision(self.decimals)
self.y_entry.set_range(-10000.0000, 10000.0000)
grid0.addWidget(self.y_lbl, 4, 0)
grid0.addWidget(self.y_entry, 4, 1, 1, 2)
# Radius X
self.radius_x_lbl = FCLabel('%s X:' % _("Radius"))
self.radius_x_entry = FCDoubleSpinner()
self.radius_x_entry.set_precision(self.decimals)
self.radius_x_entry.set_range(0.0000, 10000.0000)
grid0.addWidget(self.radius_x_lbl, 6, 0)
grid0.addWidget(self.radius_x_entry, 6, 1)
# Radius Y
self.radius_y_lbl = FCLabel('%s Y:' % _("Radius"))
self.radius_y_entry = FCDoubleSpinner()
self.radius_y_entry.set_precision(self.decimals)
self.radius_y_entry.set_range(0.0000, 10000.0000)
grid0.addWidget(self.radius_y_lbl, 7, 0)
grid0.addWidget(self.radius_y_entry, 7, 1)
# Angle
self.angle_lbl = FCLabel('%s:' % _("Angle"))
self.angle_entry = FCDoubleSpinner()
self.angle_entry.set_precision(self.decimals)
self.angle_entry.set_range(0.0000, 360.0000)
grid0.addWidget(self.angle_lbl, 8, 0)
grid0.addWidget(self.angle_entry, 8, 1)
# Radius link
self.radius_link_btn = QtWidgets.QToolButton()
self.radius_link_btn.setIcon(QtGui.QIcon(self.app.resource_location + '/link32.png'))
self.radius_link_btn.setSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.radius_link_btn.setCheckable(True)
grid0.addWidget(self.radius_link_btn, 6, 2, 3, 1)
# Buttons
self.add_button = FCButton(_("Add"))
self.add_button.setIcon(QtGui.QIcon(self.app.resource_location + '/plus16.png'))
grid0.addWidget(self.add_button, 18, 0, 1, 3)
self.layout.addStretch(1)
# Note
self.note_lbl = FCLabel('<b>%s</b>' % _("Note"))
self.layout.addWidget(self.note_lbl)
self.note_description_lbl = FCLabel('%s' % _("Shift + click to select a shape for modification."))
self.layout.addWidget(self.note_description_lbl)
# Signals
self.radius_link_btn.clicked.connect(self.on_link_checked)
def on_link_checked(self, checked):
if checked:
self.radius_x_lbl.set_value('%s:' % _("Radius"))
self.radius_y_lbl.setDisabled(True)
self.radius_y_entry.setDisabled(True)
self.radius_y_entry.set_value(self.radius_x_entry.get_value())
self.angle_lbl.setDisabled(True)
self.angle_entry.setDisabled(True)
else:
self.radius_x_lbl.set_value('%s X:' % _("Radius"))
self.radius_y_lbl.setDisabled(False)
self.radius_y_entry.setDisabled(False)
self.angle_lbl.setDisabled(False)
self.angle_entry.setDisabled(False)