diff --git a/FlatCAMObj.py b/FlatCAMObj.py
index 39dbd8b9..83678a70 100644
--- a/FlatCAMObj.py
+++ b/FlatCAMObj.py
@@ -1172,7 +1172,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
aperture = self.ui.apertures_table.item(row, 1).text()
# self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
- self.plot_apertures(color='#FD6A02', marked_aperture=aperture, visible=True)
+ self.plot_apertures(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
else:
self.marked_rows.append(False)
@@ -1209,7 +1209,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
if mark_all:
for aperture in self.apertures:
# self.plot_apertures(color='#2d4606bf', marked_aperture=aperture, visible=True)
- self.plot_apertures(color='#FD6A02', marked_aperture=aperture, visible=True)
+ self.plot_apertures(color=self.app.defaults['global_sel_draw_color'], marked_aperture=aperture, visible=True)
else:
self.clear_plot_apertures()
diff --git a/README.md b/README.md
index 37999b7b..9813648a 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,11 @@ CAD program, and create G-Code for Isolation routing.
=================================================
+11.04.2019
+
+- changed the color of the marked apertures to the global_selection_color
+- Gerber Editor: added Transformation Tool and Rotation key shortcut
+
10.04.2019
- Gerber Editor: added Add Track and Add Region functions
diff --git a/flatcamEditors/FlatCAMGeoEditor.py b/flatcamEditors/FlatCAMGeoEditor.py
index 07639a18..56a42993 100644
--- a/flatcamEditors/FlatCAMGeoEditor.py
+++ b/flatcamEditors/FlatCAMGeoEditor.py
@@ -2653,34 +2653,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app = app
self.canvas = app.plotcanvas
- self.app.ui.geo_add_circle_menuitem.triggered.connect(lambda: self.select_tool('circle'))
- self.app.ui.geo_add_arc_menuitem.triggered.connect(lambda: self.select_tool('arc'))
- self.app.ui.geo_add_rectangle_menuitem.triggered.connect(lambda: self.select_tool('rectangle'))
- self.app.ui.geo_add_polygon_menuitem.triggered.connect(lambda: self.select_tool('polygon'))
- self.app.ui.geo_add_path_menuitem.triggered.connect(lambda: self.select_tool('path'))
- self.app.ui.geo_add_text_menuitem.triggered.connect(lambda: self.select_tool('text'))
- self.app.ui.geo_paint_menuitem.triggered.connect(self.on_paint_tool)
- self.app.ui.geo_buffer_menuitem.triggered.connect(self.on_buffer_tool)
- self.app.ui.geo_transform_menuitem.triggered.connect(self.on_transform_tool)
-
- self.app.ui.geo_delete_menuitem.triggered.connect(self.on_delete_btn)
- self.app.ui.geo_union_menuitem.triggered.connect(self.union)
- self.app.ui.geo_intersection_menuitem.triggered.connect(self.intersection)
- self.app.ui.geo_subtract_menuitem.triggered.connect(self.subtract)
- self.app.ui.geo_cutpath_menuitem.triggered.connect(self.cutpath)
- self.app.ui.geo_copy_menuitem.triggered.connect(lambda: self.select_tool('copy'))
-
- self.app.ui.geo_union_btn.triggered.connect(self.union)
- self.app.ui.geo_intersection_btn.triggered.connect(self.intersection)
- self.app.ui.geo_subtract_btn.triggered.connect(self.subtract)
- self.app.ui.geo_cutpath_btn.triggered.connect(self.cutpath)
- self.app.ui.geo_delete_btn.triggered.connect(self.on_delete_btn)
-
- self.app.ui.geo_move_menuitem.triggered.connect(self.on_move)
- self.app.ui.geo_cornersnap_menuitem.triggered.connect(self.on_corner_snap)
-
- self.transform_complete.connect(self.on_transform_complete)
-
## Toolbar events and properties
self.tools = {
"select": {"button": self.app.ui.geo_select_btn,
@@ -2814,15 +2786,43 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.snap_max_dist_entry.textChanged.connect(
lambda: entry2option("snap_max", self.app.ui.snap_max_dist_entry))
- # store the status of the editor so the Delete at object level will not work until the edit is finished
- self.editor_active = False
-
# if using Paint store here the tool diameter used
self.paint_tooldia = None
self.paint_tool = PaintOptionsTool(self.app, self)
self.transform_tool = TransformEditorTool(self.app, self)
+ self.app.ui.geo_add_circle_menuitem.triggered.connect(lambda: self.select_tool('circle'))
+ self.app.ui.geo_add_arc_menuitem.triggered.connect(lambda: self.select_tool('arc'))
+ self.app.ui.geo_add_rectangle_menuitem.triggered.connect(lambda: self.select_tool('rectangle'))
+ self.app.ui.geo_add_polygon_menuitem.triggered.connect(lambda: self.select_tool('polygon'))
+ self.app.ui.geo_add_path_menuitem.triggered.connect(lambda: self.select_tool('path'))
+ self.app.ui.geo_add_text_menuitem.triggered.connect(lambda: self.select_tool('text'))
+ self.app.ui.geo_paint_menuitem.triggered.connect(self.on_paint_tool)
+ self.app.ui.geo_buffer_menuitem.triggered.connect(self.on_buffer_tool)
+ self.app.ui.geo_transform_menuitem.triggered.connect(self.transform_tool.run)
+
+ self.app.ui.geo_delete_menuitem.triggered.connect(self.on_delete_btn)
+ self.app.ui.geo_union_menuitem.triggered.connect(self.union)
+ self.app.ui.geo_intersection_menuitem.triggered.connect(self.intersection)
+ self.app.ui.geo_subtract_menuitem.triggered.connect(self.subtract)
+ self.app.ui.geo_cutpath_menuitem.triggered.connect(self.cutpath)
+ self.app.ui.geo_copy_menuitem.triggered.connect(lambda: self.select_tool('copy'))
+
+ self.app.ui.geo_union_btn.triggered.connect(self.union)
+ self.app.ui.geo_intersection_btn.triggered.connect(self.intersection)
+ self.app.ui.geo_subtract_btn.triggered.connect(self.subtract)
+ self.app.ui.geo_cutpath_btn.triggered.connect(self.cutpath)
+ self.app.ui.geo_delete_btn.triggered.connect(self.on_delete_btn)
+
+ self.app.ui.geo_move_menuitem.triggered.connect(self.on_move)
+ self.app.ui.geo_cornersnap_menuitem.triggered.connect(self.on_corner_snap)
+
+ self.transform_complete.connect(self.on_transform_complete)
+
+ # store the status of the editor so the Delete at object level will not work until the edit is finished
+ self.editor_active = False
+
def pool_recreated(self, pool):
self.shapes.pool = pool
self.tool_shape.pool = pool
@@ -2856,8 +2856,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.app.ui.geo_edit_toolbar.setDisabled(False)
self.app.ui.geo_edit_toolbar.setVisible(True)
- self.app.ui.grb_edit_toolbar.setDisabled(False)
- self.app.ui.grb_edit_toolbar.setVisible(True)
+
self.app.ui.snap_toolbar.setDisabled(False)
# prevent the user to change anything in the Selected Tab while the Geo Editor is active
@@ -2937,7 +2936,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
self.canvas.vis_connect('mouse_move', self.on_canvas_move)
self.canvas.vis_connect('mouse_release', self.on_canvas_click_release)
-
def disconnect_canvas_event_handlers(self):
self.canvas.vis_disconnect('mouse_press', self.on_canvas_click)
@@ -3089,10 +3087,6 @@ class FlatCAMGeoEditor(QtCore.QObject):
paint_tool = PaintOptionsTool(self.app, self)
paint_tool.run()
- def on_transform_tool(self):
- transform_tool = TransformEditorTool(self.app, self)
- transform_tool.run()
-
def on_tool_select(self, tool):
"""
Behavior of the toolbar. Tool initialization.
diff --git a/flatcamEditors/FlatCAMGrbEditor.py b/flatcamEditors/FlatCAMGrbEditor.py
index c3078292..3d9e6075 100644
--- a/flatcamEditors/FlatCAMGrbEditor.py
+++ b/flatcamEditors/FlatCAMGrbEditor.py
@@ -12,7 +12,7 @@ import copy
from camlib import *
from flatcamGUI.GUIElements import FCEntry, FCComboBox, FCTable, FCDoubleSpinner, LengthEntry, RadioSet, \
- SpinBoxDelegate, EvalEntry
+ SpinBoxDelegate, EvalEntry, EvalEntry2, FCInputDialog, FCButton, OptionalInputSection, FCCheckBox
from flatcamEditors.FlatCAMGeoEditor import FCShapeTool, DrawTool, DrawToolShape, DrawToolUtilityShape, FlatCAMGeoEditor
from FlatCAMObj import FlatCAMGerber
from FlatCAMTool import FlatCAMTool
@@ -242,9 +242,9 @@ class FCScale(FCShapeTool):
if self.draw_app.app.ui.splitter.sizes()[0] == 0:
self.draw_app.app.ui.splitter.setSizes([1, 1])
- self.activate()
+ self.activate_scale()
- def activate(self):
+ def activate_scale(self):
self.draw_app.hide_tool('all')
self.draw_app.scale_tool_frame.show()
@@ -254,7 +254,7 @@ class FCScale(FCShapeTool):
pass
self.draw_app.scale_button.clicked.connect(self.on_scale_click)
- def deactivate(self):
+ def deactivate_scale(self):
self.draw_app.scale_button.clicked.disconnect()
self.complete = True
self.draw_app.select_tool("select")
@@ -262,7 +262,7 @@ class FCScale(FCShapeTool):
def on_scale_click(self):
self.draw_app.on_scale()
- self.deactivate()
+ self.deactivate_scale()
class FCBuffer(FCShapeTool):
@@ -279,9 +279,9 @@ class FCBuffer(FCShapeTool):
if self.draw_app.app.ui.splitter.sizes()[0] == 0:
self.draw_app.app.ui.splitter.setSizes([1, 1])
- self.activate()
+ self.activate_buffer()
- def activate(self):
+ def activate_buffer(self):
self.draw_app.hide_tool('all')
self.draw_app.buffer_tool_frame.show()
@@ -291,7 +291,7 @@ class FCBuffer(FCShapeTool):
pass
self.draw_app.buffer_button.clicked.connect(self.on_buffer_click)
- def deactivate(self):
+ def deactivate_buffer(self):
self.draw_app.buffer_button.clicked.disconnect()
self.complete = True
self.draw_app.select_tool("select")
@@ -299,7 +299,7 @@ class FCBuffer(FCShapeTool):
def on_buffer_click(self):
self.draw_app.on_buffer()
- self.deactivate()
+ self.deactivate_buffer()
class FCApertureMove(FCShapeTool):
@@ -492,6 +492,20 @@ class FCApertureSelect(DrawTool):
return ""
+class FCTransform(FCShapeTool):
+ def __init__(self, draw_app):
+ FCShapeTool.__init__(self, draw_app)
+ self.name = 'transformation'
+
+ # self.shape_buffer = self.draw_app.shape_buffer
+ self.draw_app = draw_app
+ self.app = draw_app.app
+
+ self.start_msg = _("Shape transformations ...")
+ self.origin = (0, 0)
+ self.draw_app.transform_tool.run()
+
+
class FlatCAMGrbEditor(QtCore.QObject):
draw_shape_idx = -1
@@ -759,6 +773,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
"constructor": FCScale},
"copy": {"button": self.app.ui.aperture_copy_btn,
"constructor": FCApertureCopy},
+ "transform": {"button": self.app.ui.grb_transform_btn,
+ "constructor": FCTransform},
"move": {"button": self.app.ui.aperture_move_btn,
"constructor": FCApertureMove},
}
@@ -794,32 +810,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
# this var will store the state of the toolbar before starting the editor
self.toolbar_old_state = False
- # Signals
- self.buffer_button.clicked.connect(self.on_buffer)
- self.scale_button.clicked.connect(self.on_scale)
-
- self.app.ui.delete_drill_btn.triggered.connect(self.on_delete_btn)
- self.name_entry.returnPressed.connect(self.on_name_activate)
-
- self.aptype_cb.currentIndexChanged[str].connect(self.on_aptype_changed)
-
- self.addaperture_btn.clicked.connect(self.on_aperture_add)
- self.delaperture_btn.clicked.connect(self.on_aperture_delete)
- self.apertures_table.cellPressed.connect(self.on_row_selected)
-
- self.app.ui.grb_add_pad_menuitem.triggered.connect(self.on_pad_add)
- self.app.ui.grb_add_track_menuitem.triggered.connect(self.on_track_add)
- self.app.ui.grb_add_region_menuitem.triggered.connect(self.on_region_add)
-
- self.app.ui.grb_add_buffer_menuitem.triggered.connect(self.on_buffer)
- self.app.ui.grb_add_scale_menuitem.triggered.connect(self.on_scale)
-
- self.app.ui.grb_copy_menuitem.triggered.connect(self.on_copy_button)
- self.app.ui.grb_delete_menuitem.triggered.connect(self.on_delete_btn)
-
- self.app.ui.grb_move_menuitem.triggered.connect(self.on_move_button)
-
-
# Init GUI
self.apdim_lbl.hide()
self.apdim_entry.hide()
@@ -889,6 +879,34 @@ class FlatCAMGrbEditor(QtCore.QObject):
def entry2option(option, entry):
self.options[option] = float(entry.text())
+ self.transform_tool = TransformEditorTool(self.app, self)
+
+ # Signals
+ self.buffer_button.clicked.connect(self.on_buffer)
+ self.scale_button.clicked.connect(self.on_scale)
+
+ self.app.ui.delete_drill_btn.triggered.connect(self.on_delete_btn)
+ self.name_entry.returnPressed.connect(self.on_name_activate)
+
+ self.aptype_cb.currentIndexChanged[str].connect(self.on_aptype_changed)
+
+ self.addaperture_btn.clicked.connect(self.on_aperture_add)
+ self.delaperture_btn.clicked.connect(self.on_aperture_delete)
+ self.apertures_table.cellPressed.connect(self.on_row_selected)
+
+ self.app.ui.grb_add_pad_menuitem.triggered.connect(self.on_pad_add)
+ self.app.ui.grb_add_track_menuitem.triggered.connect(self.on_track_add)
+ self.app.ui.grb_add_region_menuitem.triggered.connect(self.on_region_add)
+
+ self.app.ui.grb_add_buffer_menuitem.triggered.connect(self.on_buffer)
+ self.app.ui.grb_add_scale_menuitem.triggered.connect(self.on_scale)
+ self.app.ui.grb_transform_menuitem.triggered.connect(self.transform_tool.run)
+
+ self.app.ui.grb_copy_menuitem.triggered.connect(self.on_copy_button)
+ self.app.ui.grb_delete_menuitem.triggered.connect(self.on_delete_btn)
+
+ self.app.ui.grb_move_menuitem.triggered.connect(self.on_move_button)
+
# store the status of the editor so the Delete at object level will not work until the edit is finished
self.editor_active = False
@@ -1256,7 +1274,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
self.apdim_entry.hide()
self.apsize_entry.setReadOnly(False)
- def activate(self):
+ def activate_grb_editor(self):
self.connect_canvas_event_handlers()
# init working objects
@@ -1294,7 +1312,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
# Tell the App that the editor is active
self.editor_active = True
- def deactivate(self):
+ def deactivate_grb_editor(self):
self.disconnect_canvas_event_handlers()
self.clear()
self.app.ui.grb_edit_toolbar.setDisabled(True)
@@ -1394,8 +1412,8 @@ class FlatCAMGrbEditor(QtCore.QObject):
:return: None
"""
- self.deactivate()
- self.activate()
+ self.deactivate_grb_editor()
+ self.activate_grb_editor()
# create a reference to the source object
self.gerber_obj = orig_grb_obj
@@ -1445,9 +1463,6 @@ class FlatCAMGrbEditor(QtCore.QObject):
except ValueError:
break
- for k, v in self.gerber_obj.apertures.items():
- print(k, v)
-
for apid in self.gerber_obj.apertures:
self.grb_plot_promises.append(apid)
self.app.worker_task.emit({'fcn': job_thread, 'params': [self, apid]})
@@ -2251,4 +2266,972 @@ class FlatCAMGrbEditor(QtCore.QObject):
if tool_name == 'scale' or tool_name == 'all':
self.scale_tool_frame.hide()
- self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
\ No newline at end of file
+ self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+
+class TransformEditorTool(FlatCAMTool):
+ """
+ Inputs to specify how to paint the selected polygons.
+ """
+
+ toolName = _("Transform Tool")
+ rotateName = _("Rotate")
+ skewName = _("Skew/Shear")
+ scaleName = _("Scale")
+ flipName = _("Mirror (Flip)")
+ offsetName = _("Offset")
+
+ def __init__(self, app, draw_app):
+ FlatCAMTool.__init__(self, app)
+
+ self.app = app
+ self.draw_app = draw_app
+
+ self.transform_lay = QtWidgets.QVBoxLayout()
+ self.layout.addLayout(self.transform_lay)
+ ## Title
+ title_label = QtWidgets.QLabel("%s" % (_('Editor %s') % self.toolName))
+ title_label.setStyleSheet("""
+ QLabel
+ {
+ font-size: 16px;
+ font-weight: bold;
+ }
+ """)
+ self.transform_lay.addWidget(title_label)
+
+ self.empty_label = QtWidgets.QLabel("")
+ self.empty_label.setFixedWidth(50)
+
+ self.empty_label1 = QtWidgets.QLabel("")
+ self.empty_label1.setFixedWidth(70)
+ self.empty_label2 = QtWidgets.QLabel("")
+ self.empty_label2.setFixedWidth(70)
+ self.empty_label3 = QtWidgets.QLabel("")
+ self.empty_label3.setFixedWidth(70)
+ self.empty_label4 = QtWidgets.QLabel("")
+ self.empty_label4.setFixedWidth(70)
+ self.transform_lay.addWidget(self.empty_label)
+
+ ## Rotate Title
+ rotate_title_label = QtWidgets.QLabel("%s" % self.rotateName)
+ self.transform_lay.addWidget(rotate_title_label)
+
+ ## Layout
+ form_layout = QtWidgets.QFormLayout()
+ self.transform_lay.addLayout(form_layout)
+ form_child = QtWidgets.QHBoxLayout()
+
+ self.rotate_label = QtWidgets.QLabel(_("Angle:"))
+ self.rotate_label.setToolTip(
+ _("Angle for Rotation action, in degrees.\n"
+ "Float number between -360 and 359.\n"
+ "Positive numbers for CW motion.\n"
+ "Negative numbers for CCW motion.")
+ )
+ self.rotate_label.setFixedWidth(50)
+
+ self.rotate_entry = FCEntry()
+ # self.rotate_entry.setFixedWidth(60)
+ self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+
+ self.rotate_button = FCButton()
+ self.rotate_button.set_value(_("Rotate"))
+ self.rotate_button.setToolTip(
+ _("Rotate the selected shape(s).\n"
+ "The point of reference is the middle of\n"
+ "the bounding box for all selected shapes.")
+ )
+ self.rotate_button.setFixedWidth(60)
+
+ form_child.addWidget(self.rotate_entry)
+ form_child.addWidget(self.rotate_button)
+
+ form_layout.addRow(self.rotate_label, form_child)
+
+ self.transform_lay.addWidget(self.empty_label1)
+
+ ## Skew Title
+ skew_title_label = QtWidgets.QLabel("%s" % self.skewName)
+ self.transform_lay.addWidget(skew_title_label)
+
+ ## Form Layout
+ form1_layout = QtWidgets.QFormLayout()
+ self.transform_lay.addLayout(form1_layout)
+ form1_child_1 = QtWidgets.QHBoxLayout()
+ form1_child_2 = QtWidgets.QHBoxLayout()
+
+ self.skewx_label = QtWidgets.QLabel(_("Angle X:"))
+ self.skewx_label.setToolTip(
+ _("Angle for Skew action, in degrees.\n"
+ "Float number between -360 and 359.")
+ )
+ self.skewx_label.setFixedWidth(50)
+ self.skewx_entry = FCEntry()
+ self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.skewx_entry.setFixedWidth(60)
+
+ self.skewx_button = FCButton()
+ self.skewx_button.set_value(_("Skew X"))
+ self.skewx_button.setToolTip(
+ _("Skew/shear the selected shape(s).\n"
+ "The point of reference is the middle of\n"
+ "the bounding box for all selected shapes."))
+ self.skewx_button.setFixedWidth(60)
+
+ self.skewy_label = QtWidgets.QLabel(_("Angle Y:"))
+ self.skewy_label.setToolTip(
+ _("Angle for Skew action, in degrees.\n"
+ "Float number between -360 and 359.")
+ )
+ self.skewy_label.setFixedWidth(50)
+ self.skewy_entry = FCEntry()
+ self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.skewy_entry.setFixedWidth(60)
+
+ self.skewy_button = FCButton()
+ self.skewy_button.set_value(_("Skew Y"))
+ self.skewy_button.setToolTip(
+ _("Skew/shear the selected shape(s).\n"
+ "The point of reference is the middle of\n"
+ "the bounding box for all selected shapes."))
+ self.skewy_button.setFixedWidth(60)
+
+ form1_child_1.addWidget(self.skewx_entry)
+ form1_child_1.addWidget(self.skewx_button)
+
+ form1_child_2.addWidget(self.skewy_entry)
+ form1_child_2.addWidget(self.skewy_button)
+
+ form1_layout.addRow(self.skewx_label, form1_child_1)
+ form1_layout.addRow(self.skewy_label, form1_child_2)
+
+ self.transform_lay.addWidget(self.empty_label2)
+
+ ## Scale Title
+ scale_title_label = QtWidgets.QLabel("%s" % self.scaleName)
+ self.transform_lay.addWidget(scale_title_label)
+
+ ## Form Layout
+ form2_layout = QtWidgets.QFormLayout()
+ self.transform_lay.addLayout(form2_layout)
+ form2_child_1 = QtWidgets.QHBoxLayout()
+ form2_child_2 = QtWidgets.QHBoxLayout()
+
+ self.scalex_label = QtWidgets.QLabel(_("Factor X:"))
+ self.scalex_label.setToolTip(
+ _("Factor for Scale action over X axis.")
+ )
+ self.scalex_label.setFixedWidth(50)
+ self.scalex_entry = FCEntry()
+ self.scalex_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.scalex_entry.setFixedWidth(60)
+
+ self.scalex_button = FCButton()
+ self.scalex_button.set_value(_("Scale X"))
+ self.scalex_button.setToolTip(
+ _("Scale the selected shape(s).\n"
+ "The point of reference depends on \n"
+ "the Scale reference checkbox state."))
+ self.scalex_button.setFixedWidth(60)
+
+ self.scaley_label = QtWidgets.QLabel(_("Factor Y:"))
+ self.scaley_label.setToolTip(
+ _("Factor for Scale action over Y axis.")
+ )
+ self.scaley_label.setFixedWidth(50)
+ self.scaley_entry = FCEntry()
+ self.scaley_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.scaley_entry.setFixedWidth(60)
+
+ self.scaley_button = FCButton()
+ self.scaley_button.set_value(_("Scale Y"))
+ self.scaley_button.setToolTip(
+ _("Scale the selected shape(s).\n"
+ "The point of reference depends on \n"
+ "the Scale reference checkbox state."))
+ self.scaley_button.setFixedWidth(60)
+
+ self.scale_link_cb = FCCheckBox()
+ self.scale_link_cb.set_value(True)
+ self.scale_link_cb.setText(_("Link"))
+ self.scale_link_cb.setToolTip(
+ _("Scale the selected shape(s)\n"
+ "using the Scale Factor X for both axis."))
+ self.scale_link_cb.setFixedWidth(50)
+
+ self.scale_zero_ref_cb = FCCheckBox()
+ self.scale_zero_ref_cb.set_value(True)
+ self.scale_zero_ref_cb.setText(_("Scale Reference"))
+ self.scale_zero_ref_cb.setToolTip(
+ _("Scale the selected shape(s)\n"
+ "using the origin reference when checked,\n"
+ "and the center of the biggest bounding box\n"
+ "of the selected shapes when unchecked."))
+
+ form2_child_1.addWidget(self.scalex_entry)
+ form2_child_1.addWidget(self.scalex_button)
+
+ form2_child_2.addWidget(self.scaley_entry)
+ form2_child_2.addWidget(self.scaley_button)
+
+ form2_layout.addRow(self.scalex_label, form2_child_1)
+ form2_layout.addRow(self.scaley_label, form2_child_2)
+ form2_layout.addRow(self.scale_link_cb, self.scale_zero_ref_cb)
+ self.ois_scale = OptionalInputSection(self.scale_link_cb, [self.scaley_entry, self.scaley_button],
+ logic=False)
+
+ self.transform_lay.addWidget(self.empty_label3)
+
+ ## Offset Title
+ offset_title_label = QtWidgets.QLabel("%s" % self.offsetName)
+ self.transform_lay.addWidget(offset_title_label)
+
+ ## Form Layout
+ form3_layout = QtWidgets.QFormLayout()
+ self.transform_lay.addLayout(form3_layout)
+ form3_child_1 = QtWidgets.QHBoxLayout()
+ form3_child_2 = QtWidgets.QHBoxLayout()
+
+ self.offx_label = QtWidgets.QLabel(_("Value X:"))
+ self.offx_label.setToolTip(
+ _("Value for Offset action on X axis.")
+ )
+ self.offx_label.setFixedWidth(50)
+ self.offx_entry = FCEntry()
+ self.offx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.offx_entry.setFixedWidth(60)
+
+ self.offx_button = FCButton()
+ self.offx_button.set_value(_("Offset X"))
+ self.offx_button.setToolTip(
+ _("Offset the selected shape(s).\n"
+ "The point of reference is the middle of\n"
+ "the bounding box for all selected shapes.\n")
+ )
+ self.offx_button.setFixedWidth(60)
+
+ self.offy_label = QtWidgets.QLabel(_("Value Y:"))
+ self.offy_label.setToolTip(
+ _("Value for Offset action on Y axis.")
+ )
+ self.offy_label.setFixedWidth(50)
+ self.offy_entry = FCEntry()
+ self.offy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.offy_entry.setFixedWidth(60)
+
+ self.offy_button = FCButton()
+ self.offy_button.set_value(_("Offset Y"))
+ self.offy_button.setToolTip(
+ _("Offset the selected shape(s).\n"
+ "The point of reference is the middle of\n"
+ "the bounding box for all selected shapes.\n")
+ )
+ self.offy_button.setFixedWidth(60)
+
+ form3_child_1.addWidget(self.offx_entry)
+ form3_child_1.addWidget(self.offx_button)
+
+ form3_child_2.addWidget(self.offy_entry)
+ form3_child_2.addWidget(self.offy_button)
+
+ form3_layout.addRow(self.offx_label, form3_child_1)
+ form3_layout.addRow(self.offy_label, form3_child_2)
+
+ self.transform_lay.addWidget(self.empty_label4)
+
+ ## Flip Title
+ flip_title_label = QtWidgets.QLabel("%s" % self.flipName)
+ self.transform_lay.addWidget(flip_title_label)
+
+ ## Form Layout
+ form4_layout = QtWidgets.QFormLayout()
+ form4_child_hlay = QtWidgets.QHBoxLayout()
+ self.transform_lay.addLayout(form4_child_hlay)
+ self.transform_lay.addLayout(form4_layout)
+ form4_child_1 = QtWidgets.QHBoxLayout()
+
+ self.flipx_button = FCButton()
+ self.flipx_button.set_value(_("Flip on X"))
+ self.flipx_button.setToolTip(
+ _("Flip the selected shape(s) over the X axis.\n"
+ "Does not create a new shape.")
+ )
+ self.flipx_button.setFixedWidth(60)
+
+ self.flipy_button = FCButton()
+ self.flipy_button.set_value(_("Flip on Y"))
+ self.flipy_button.setToolTip(
+ _("Flip the selected shape(s) over the X axis.\n"
+ "Does not create a new shape.")
+ )
+ self.flipy_button.setFixedWidth(60)
+
+ self.flip_ref_cb = FCCheckBox()
+ self.flip_ref_cb.set_value(True)
+ self.flip_ref_cb.setText(_("Ref Pt"))
+ self.flip_ref_cb.setToolTip(
+ _("Flip the selected shape(s)\n"
+ "around the point in Point Entry Field.\n"
+ "\n"
+ "The point coordinates can be captured by\n"
+ "left click on canvas together with pressing\n"
+ "SHIFT key. \n"
+ "Then click Add button to insert coordinates.\n"
+ "Or enter the coords in format (x, y) in the\n"
+ "Point Entry field and click Flip on X(Y)")
+ )
+ self.flip_ref_cb.setFixedWidth(50)
+
+ self.flip_ref_label = QtWidgets.QLabel(_("Point:"))
+ self.flip_ref_label.setToolTip(
+ _("Coordinates in format (x, y) used as reference for mirroring.\n"
+ "The 'x' in (x, y) will be used when using Flip on X and\n"
+ "the 'y' in (x, y) will be used when using Flip on Y.")
+ )
+ self.flip_ref_label.setFixedWidth(50)
+ self.flip_ref_entry = EvalEntry2("(0, 0)")
+ self.flip_ref_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ # self.flip_ref_entry.setFixedWidth(60)
+
+ self.flip_ref_button = FCButton()
+ self.flip_ref_button.set_value(_("Add"))
+ self.flip_ref_button.setToolTip(
+ _("The point coordinates can be captured by\n"
+ "left click on canvas together with pressing\n"
+ "SHIFT key. Then click Add button to insert.")
+ )
+ self.flip_ref_button.setFixedWidth(60)
+
+ form4_child_hlay.addStretch()
+ form4_child_hlay.addWidget(self.flipx_button)
+ form4_child_hlay.addWidget(self.flipy_button)
+
+ form4_child_1.addWidget(self.flip_ref_entry)
+ form4_child_1.addWidget(self.flip_ref_button)
+
+ form4_layout.addRow(self.flip_ref_cb)
+ form4_layout.addRow(self.flip_ref_label, form4_child_1)
+ self.ois_flip = OptionalInputSection(self.flip_ref_cb,
+ [self.flip_ref_entry, self.flip_ref_button], logic=True)
+
+ self.transform_lay.addStretch()
+
+ ## Signals
+ self.rotate_button.clicked.connect(self.on_rotate)
+ self.skewx_button.clicked.connect(self.on_skewx)
+ self.skewy_button.clicked.connect(self.on_skewy)
+ self.scalex_button.clicked.connect(self.on_scalex)
+ self.scaley_button.clicked.connect(self.on_scaley)
+ self.offx_button.clicked.connect(self.on_offx)
+ self.offy_button.clicked.connect(self.on_offy)
+ self.flipx_button.clicked.connect(self.on_flipx)
+ self.flipy_button.clicked.connect(self.on_flipy)
+ self.flip_ref_button.clicked.connect(self.on_flip_add_coords)
+
+ self.rotate_entry.returnPressed.connect(self.on_rotate)
+ self.skewx_entry.returnPressed.connect(self.on_skewx)
+ self.skewy_entry.returnPressed.connect(self.on_skewy)
+ self.scalex_entry.returnPressed.connect(self.on_scalex)
+ self.scaley_entry.returnPressed.connect(self.on_scaley)
+ self.offx_entry.returnPressed.connect(self.on_offx)
+ self.offy_entry.returnPressed.connect(self.on_offy)
+
+ self.set_tool_ui()
+
+ def run(self):
+ self.app.report_usage("Geo Editor Transform Tool()")
+ FlatCAMTool.run(self)
+ self.set_tool_ui()
+
+ # if the splitter us hidden, display it
+ if self.app.ui.splitter.sizes()[0] == 0:
+ self.app.ui.splitter.setSizes([1, 1])
+
+ self.app.ui.notebook.setTabText(2, _("Transform Tool"))
+
+ def install(self, icon=None, separator=None, **kwargs):
+ FlatCAMTool.install(self, icon, separator, shortcut='ALT+T', **kwargs)
+
+ def set_tool_ui(self):
+ ## Initialize form
+ if self.app.defaults["tools_transform_rotate"]:
+ self.rotate_entry.set_value(self.app.defaults["tools_transform_rotate"])
+ else:
+ self.rotate_entry.set_value(0.0)
+
+ if self.app.defaults["tools_transform_skew_x"]:
+ self.skewx_entry.set_value(self.app.defaults["tools_transform_skew_x"])
+ else:
+ self.skewx_entry.set_value(0.0)
+
+ if self.app.defaults["tools_transform_skew_y"]:
+ self.skewy_entry.set_value(self.app.defaults["tools_transform_skew_y"])
+ else:
+ self.skewy_entry.set_value(0.0)
+
+ if self.app.defaults["tools_transform_scale_x"]:
+ self.scalex_entry.set_value(self.app.defaults["tools_transform_scale_x"])
+ else:
+ self.scalex_entry.set_value(1.0)
+
+ if self.app.defaults["tools_transform_scale_y"]:
+ self.scaley_entry.set_value(self.app.defaults["tools_transform_scale_y"])
+ else:
+ self.scaley_entry.set_value(1.0)
+
+ if self.app.defaults["tools_transform_scale_link"]:
+ self.scale_link_cb.set_value(self.app.defaults["tools_transform_scale_link"])
+ else:
+ self.scale_link_cb.set_value(True)
+
+ if self.app.defaults["tools_transform_scale_reference"]:
+ self.scale_zero_ref_cb.set_value(self.app.defaults["tools_transform_scale_reference"])
+ else:
+ self.scale_zero_ref_cb.set_value(True)
+
+ if self.app.defaults["tools_transform_offset_x"]:
+ self.offx_entry.set_value(self.app.defaults["tools_transform_offset_x"])
+ else:
+ self.offx_entry.set_value(0.0)
+
+ if self.app.defaults["tools_transform_offset_y"]:
+ self.offy_entry.set_value(self.app.defaults["tools_transform_offset_y"])
+ else:
+ self.offy_entry.set_value(0.0)
+
+ if self.app.defaults["tools_transform_mirror_reference"]:
+ self.flip_ref_cb.set_value(self.app.defaults["tools_transform_mirror_reference"])
+ else:
+ self.flip_ref_cb.set_value(False)
+
+ if self.app.defaults["tools_transform_mirror_point"]:
+ self.flip_ref_entry.set_value(self.app.defaults["tools_transform_mirror_point"])
+ else:
+ self.flip_ref_entry.set_value((0, 0))
+
+ def template(self):
+ if not self.fcdraw.selected:
+ self.app.inform.emit(_("[WARNING_NOTCL] Transformation cancelled. No shape selected."))
+ return
+
+ self.draw_app.select_tool("select")
+ self.app.ui.notebook.setTabText(2, "Tools")
+ self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+
+ self.app.ui.splitter.setSizes([0, 1])
+
+ def on_rotate(self, sig=None, val=None):
+ if val:
+ value = val
+ else:
+ try:
+ value = float(self.rotate_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ value = float(self.rotate_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Rotate, "
+ "use a number."))
+ return
+ self.app.worker_task.emit({'fcn': self.on_rotate_action,
+ 'params': [value]})
+ # self.on_rotate_action(value)
+ return
+
+ def on_flipx(self):
+ # self.on_flip("Y")
+ axis = 'Y'
+ self.app.worker_task.emit({'fcn': self.on_flip,
+ 'params': [axis]})
+ return
+
+ def on_flipy(self):
+ # self.on_flip("X")
+ axis = 'X'
+ self.app.worker_task.emit({'fcn': self.on_flip,
+ 'params': [axis]})
+ return
+
+ def on_flip_add_coords(self):
+ val = self.app.clipboard.text()
+ self.flip_ref_entry.set_value(val)
+
+ def on_skewx(self, sig=None, val=None):
+ if val:
+ value = val
+ else:
+ try:
+ value = float(self.skewx_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ value = float(self.skewx_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Skew X, "
+ "use a number."))
+ return
+
+ # self.on_skew("X", value)
+ axis = 'X'
+ self.app.worker_task.emit({'fcn': self.on_skew,
+ 'params': [axis, value]})
+ return
+
+ def on_skewy(self, sig=None, val=None):
+ if val:
+ value = val
+ else:
+ try:
+ value = float(self.skewy_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ value = float(self.skewy_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Skew Y, "
+ "use a number."))
+ return
+
+ # self.on_skew("Y", value)
+ axis = 'Y'
+ self.app.worker_task.emit({'fcn': self.on_skew,
+ 'params': [axis, value]})
+ return
+
+ def on_scalex(self, sig=None, val=None):
+ if val:
+ xvalue = val
+ else:
+ try:
+ xvalue = float(self.scalex_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ xvalue = float(self.scalex_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Scale X, "
+ "use a number."))
+ return
+
+ # scaling to zero has no sense so we remove it, because scaling with 1 does nothing
+ if xvalue == 0:
+ xvalue = 1
+ if self.scale_link_cb.get_value():
+ yvalue = xvalue
+ else:
+ yvalue = 1
+
+ axis = 'X'
+ point = (0, 0)
+ if self.scale_zero_ref_cb.get_value():
+ self.app.worker_task.emit({'fcn': self.on_scale,
+ 'params': [axis, xvalue, yvalue, point]})
+ # self.on_scale("X", xvalue, yvalue, point=(0,0))
+ else:
+ # self.on_scale("X", xvalue, yvalue)
+ self.app.worker_task.emit({'fcn': self.on_scale,
+ 'params': [axis, xvalue, yvalue]})
+
+ return
+
+ def on_scaley(self, sig=None, val=None):
+ xvalue = 1
+ if val:
+ yvalue = val
+ else:
+ try:
+ yvalue = float(self.scaley_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ yvalue = float(self.scaley_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Scale Y, "
+ "use a number."))
+ return
+
+ # scaling to zero has no sense so we remove it, because scaling with 1 does nothing
+ if yvalue == 0:
+ yvalue = 1
+
+ axis = 'Y'
+ point = (0, 0)
+ if self.scale_zero_ref_cb.get_value():
+ self.app.worker_task.emit({'fcn': self.on_scale,
+ 'params': [axis, xvalue, yvalue, point]})
+ # self.on_scale("Y", xvalue, yvalue, point=(0,0))
+ else:
+ # self.on_scale("Y", xvalue, yvalue)
+ self.app.worker_task.emit({'fcn': self.on_scale,
+ 'params': [axis, xvalue, yvalue]})
+
+ return
+
+ def on_offx(self, sig=None, val=None):
+ if val:
+ value = val
+ else:
+ try:
+ value = float(self.offx_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ value = float(self.offx_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Offset X, "
+ "use a number."))
+ return
+
+ # self.on_offset("X", value)
+ axis = 'X'
+ self.app.worker_task.emit({'fcn': self.on_offset,
+ 'params': [axis, value]})
+ return
+
+ def on_offy(self, sig=None, val=None):
+ if val:
+ value = val
+ else:
+ try:
+ value = float(self.offy_entry.get_value())
+ except ValueError:
+ # try to convert comma to decimal point. if it's still not working error message and return
+ try:
+ value = float(self.offy_entry.get_value().replace(',', '.'))
+ except ValueError:
+ self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered for Offset Y, "
+ "use a number."))
+ return
+
+ # self.on_offset("Y", value)
+ axis = 'Y'
+ self.app.worker_task.emit({'fcn': self.on_offset,
+ 'params': [axis, value]})
+ return
+
+ def on_rotate_action(self, num):
+ shape_list = self.draw_app.selected
+ xminlist = []
+ yminlist = []
+ xmaxlist = []
+ ymaxlist = []
+
+ if not shape_list:
+ self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to rotate!"))
+ return
+ else:
+ with self.app.proc_container.new(_("Appying Rotate")):
+ try:
+ # first get a bounding box to fit all
+ for sha in shape_list:
+ xmin, ymin, xmax, ymax = sha.bounds()
+ xminlist.append(xmin)
+ yminlist.append(ymin)
+ xmaxlist.append(xmax)
+ ymaxlist.append(ymax)
+
+ # get the minimum x,y and maximum x,y for all objects selected
+ xminimal = min(xminlist)
+ yminimal = min(yminlist)
+ xmaximal = max(xmaxlist)
+ ymaximal = max(ymaxlist)
+
+ self.app.progress.emit(20)
+
+ for sel_sha in shape_list:
+ px = 0.5 * (xminimal + xmaximal)
+ py = 0.5 * (yminimal + ymaximal)
+
+ sel_sha.rotate(-num, point=(px, py))
+ self.draw_app.plot_all()
+ # self.draw_app.add_shape(DrawToolShape(sel_sha.geo))
+
+ # self.draw_app.transform_complete.emit()
+
+ self.app.inform.emit(_("[success] Done. Rotate completed."))
+
+ self.app.progress.emit(100)
+
+ except Exception as e:
+ self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, rotation movement was not executed.") % str(e))
+ return
+
+ def on_flip(self, axis):
+ shape_list = self.draw_app.selected
+ xminlist = []
+ yminlist = []
+ xmaxlist = []
+ ymaxlist = []
+
+ if not shape_list:
+ self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to flip!"))
+ return
+ else:
+ with self.app.proc_container.new(_("Applying Flip")):
+ try:
+ # get mirroring coords from the point entry
+ if self.flip_ref_cb.isChecked():
+ px, py = eval('{}'.format(self.flip_ref_entry.text()))
+ # get mirroing coords from the center of an all-enclosing bounding box
+ else:
+ # first get a bounding box to fit all
+ for sha in shape_list:
+ xmin, ymin, xmax, ymax = sha.bounds()
+ xminlist.append(xmin)
+ yminlist.append(ymin)
+ xmaxlist.append(xmax)
+ ymaxlist.append(ymax)
+
+ # get the minimum x,y and maximum x,y for all objects selected
+ xminimal = min(xminlist)
+ yminimal = min(yminlist)
+ xmaximal = max(xmaxlist)
+ ymaximal = max(ymaxlist)
+
+ px = 0.5 * (xminimal + xmaximal)
+ py = 0.5 * (yminimal + ymaximal)
+
+ self.app.progress.emit(20)
+
+ # execute mirroring
+ for sha in shape_list:
+ if axis is 'X':
+ sha.mirror('X', (px, py))
+ self.app.inform.emit(_('[success] Flip on the Y axis done ...'))
+ elif axis is 'Y':
+ sha.mirror('Y', (px, py))
+ self.app.inform.emit(_('[success] Flip on the X axis done ...'))
+ self.draw_app.plot_all()
+
+ # self.draw_app.add_shape(DrawToolShape(sha.geo))
+ #
+ # self.draw_app.transform_complete.emit()
+
+ self.app.progress.emit(100)
+
+ except Exception as e:
+ self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, Flip action was not executed.") % str(e))
+ return
+
+ def on_skew(self, axis, num):
+ shape_list = self.draw_app.selected
+ xminlist = []
+ yminlist = []
+
+ if not shape_list:
+ self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to shear/skew!"))
+ return
+ else:
+ with self.app.proc_container.new(_("Applying Skew")):
+ try:
+ # first get a bounding box to fit all
+ for sha in shape_list:
+ xmin, ymin, xmax, ymax = sha.bounds()
+ xminlist.append(xmin)
+ yminlist.append(ymin)
+
+ # get the minimum x,y and maximum x,y for all objects selected
+ xminimal = min(xminlist)
+ yminimal = min(yminlist)
+
+ self.app.progress.emit(20)
+
+ for sha in shape_list:
+ if axis is 'X':
+ sha.skew(num, 0, point=(xminimal, yminimal))
+ elif axis is 'Y':
+ sha.skew(0, num, point=(xminimal, yminimal))
+ self.draw_app.plot_all()
+
+ # self.draw_app.add_shape(DrawToolShape(sha.geo))
+ #
+ # self.draw_app.transform_complete.emit()
+
+ self.app.inform.emit(_('[success] Skew on the %s axis done ...') % str(axis))
+ self.app.progress.emit(100)
+
+ except Exception as e:
+ self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, Skew action was not executed.") % str(e))
+ return
+
+ def on_scale(self, axis, xfactor, yfactor, point=None):
+ shape_list = self.draw_app.selected
+ xminlist = []
+ yminlist = []
+ xmaxlist = []
+ ymaxlist = []
+
+ if not shape_list:
+ self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to scale!"))
+ return
+ else:
+ with self.app.proc_container.new(_("Applying Scale")):
+ try:
+ # first get a bounding box to fit all
+ for sha in shape_list:
+ xmin, ymin, xmax, ymax = sha.bounds()
+ xminlist.append(xmin)
+ yminlist.append(ymin)
+ xmaxlist.append(xmax)
+ ymaxlist.append(ymax)
+
+ # get the minimum x,y and maximum x,y for all objects selected
+ xminimal = min(xminlist)
+ yminimal = min(yminlist)
+ xmaximal = max(xmaxlist)
+ ymaximal = max(ymaxlist)
+
+ self.app.progress.emit(20)
+
+ if point is None:
+ px = 0.5 * (xminimal + xmaximal)
+ py = 0.5 * (yminimal + ymaximal)
+ else:
+ px = 0
+ py = 0
+
+ for sha in shape_list:
+ sha.scale(xfactor, yfactor, point=(px, py))
+ self.draw_app.plot_all()
+
+ # self.draw_app.add_shape(DrawToolShape(sha.geo))
+ #
+ # self.draw_app.transform_complete.emit()
+
+ self.app.inform.emit(_('[success] Scale on the %s axis done ...') % str(axis))
+ self.app.progress.emit(100)
+ except Exception as e:
+ self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, Scale action was not executed.") % str(e))
+ return
+
+ def on_offset(self, axis, num):
+ shape_list = self.draw_app.selected
+ xminlist = []
+ yminlist = []
+
+ if not shape_list:
+ self.app.inform.emit(_("[WARNING_NOTCL] No shape selected. Please Select a shape to offset!"))
+ return
+ else:
+ with self.app.proc_container.new(_("Applying Offset")):
+ try:
+ # first get a bounding box to fit all
+ for sha in shape_list:
+ xmin, ymin, xmax, ymax = sha.bounds()
+ xminlist.append(xmin)
+ yminlist.append(ymin)
+
+ # get the minimum x,y and maximum x,y for all objects selected
+ xminimal = min(xminlist)
+ yminimal = min(yminlist)
+ self.app.progress.emit(20)
+
+ for sha in shape_list:
+ if axis is 'X':
+ sha.offset((num, 0))
+ elif axis is 'Y':
+ sha.offset((0, num))
+ self.draw_app.plot_all()
+
+ # self.draw_app.add_shape(DrawToolShape(sha.geo))
+ #
+ # self.draw_app.transform_complete.emit()
+
+ self.app.inform.emit(_('[success] Offset on the %s axis done ...') % str(axis))
+ self.app.progress.emit(100)
+
+ except Exception as e:
+ self.app.inform.emit(_("[ERROR_NOTCL] Due of %s, Offset action was not executed.") % str(e))
+ return
+
+ def on_rotate_key(self):
+ val_box = FCInputDialog(title=_("Rotate ..."),
+ text=_('Enter an Angle Value (degrees):'),
+ min=-359.9999, max=360.0000, decimals=4,
+ init_val=float(self.app.defaults['tools_transform_rotate']))
+ val_box.setWindowIcon(QtGui.QIcon('share/rotate.png'))
+
+ val, ok = val_box.get_value()
+ if ok:
+ self.on_rotate(val=val)
+ self.app.inform.emit(
+ _("[success] Geometry shape rotate done...")
+ )
+ return
+ else:
+ self.app.inform.emit(
+ _("[WARNING_NOTCL] Geometry shape rotate cancelled...")
+ )
+
+ def on_offx_key(self):
+ units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
+
+ val_box = FCInputDialog(title=_("Offset on X axis ..."),
+ text=(_('Enter a distance Value (%s):') % str(units)),
+ min=-9999.9999, max=10000.0000, decimals=4,
+ init_val=float(self.app.defaults['tools_transform_offset_x']))
+ val_box.setWindowIcon(QtGui.QIcon('share/offsetx32.png'))
+
+ val, ok = val_box.get_value()
+ if ok:
+ self.on_offx(val=val)
+ self.app.inform.emit(
+ _("[success] Geometry shape offset on X axis done..."))
+ return
+ else:
+ self.app.inform.emit(
+ _("[WARNING_NOTCL] Geometry shape offset X cancelled..."))
+
+ def on_offy_key(self):
+ units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().lower()
+
+ val_box = FCInputDialog(title=_("Offset on Y axis ..."),
+ text=(_('Enter a distance Value (%s):') % str(units)),
+ min=-9999.9999, max=10000.0000, decimals=4,
+ init_val=float(self.app.defaults['tools_transform_offset_y']))
+ val_box.setWindowIcon(QtGui.QIcon('share/offsety32.png'))
+
+ val, ok = val_box.get_value()
+ if ok:
+ self.on_offx(val=val)
+ self.app.inform.emit(
+ _("[success] Geometry shape offset on Y axis done..."))
+ return
+ else:
+ self.app.inform.emit(
+ _("[WARNING_NOTCL] Geometry shape offset Y cancelled..."))
+
+ def on_skewx_key(self):
+ val_box = FCInputDialog(title=_("Skew on X axis ..."),
+ text=_('Enter an Angle Value (degrees):'),
+ min=-359.9999, max=360.0000, decimals=4,
+ init_val=float(self.app.defaults['tools_transform_skew_x']))
+ val_box.setWindowIcon(QtGui.QIcon('share/skewX.png'))
+
+ val, ok = val_box.get_value()
+ if ok:
+ self.on_skewx(val=val)
+ self.app.inform.emit(
+ _("[success] Geometry shape skew on X axis done..."))
+ return
+ else:
+ self.app.inform.emit(
+ _("[WARNING_NOTCL] Geometry shape skew X cancelled..."))
+
+ def on_skewy_key(self):
+ val_box = FCInputDialog(title=_("Skew on Y axis ..."),
+ text=_('Enter an Angle Value (degrees):'),
+ min=-359.9999, max=360.0000, decimals=4,
+ init_val=float(self.app.defaults['tools_transform_skew_y']))
+ val_box.setWindowIcon(QtGui.QIcon('share/skewY.png'))
+
+ val, ok = val_box.get_value()
+ if ok:
+ self.on_skewx(val=val)
+ self.app.inform.emit(
+ _("[success] Geometry shape skew on Y axis done..."))
+ return
+ else:
+ self.app.inform.emit(
+ _("[WARNING_NOTCL] Geometry shape skew Y cancelled..."))
diff --git a/flatcamGUI/FlatCAMGUI.py b/flatcamGUI/FlatCAMGUI.py
index fd4b23d3..d1ab188c 100644
--- a/flatcamGUI/FlatCAMGUI.py
+++ b/flatcamGUI/FlatCAMGUI.py
@@ -472,6 +472,9 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
_('Buffer\tB'))
self.grb_add_scale_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/scale32.png'),
_('Scale\tS'))
+ self.grb_transform_menuitem = self.grb_editor_menu.addAction(
+ QtGui.QIcon('share/transform.png'),_( "Transform\tALT+R")
+ )
self.grb_editor_menu.addSeparator()
self.grb_copy_menuitem = self.grb_editor_menu.addAction(QtGui.QIcon('share/copy32.png'), _('Copy\tC'))
@@ -692,6 +695,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete"))
+ self.grb_transform_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/transform.png'),
+ _("Transformations"))
self.grb_edit_toolbar.addSeparator()
self.aperture_move_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move"))
@@ -1784,6 +1789,8 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.aperture_copy_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/copy32.png'), _("Copy"))
self.aperture_delete_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/trash32.png'),
_("Delete"))
+ self.grb_transform_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/transform.png'),
+ _("Transformations"))
self.grb_edit_toolbar.addSeparator()
self.aperture_move_btn = self.grb_edit_toolbar.addAction(QtGui.QIcon('share/move32.png'), _("Move"))
@@ -2268,7 +2275,7 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.geo_editor.delete_selected()
self.app.geo_editor.replot()
- # Move
+ # Rotate
if key == QtCore.Qt.Key_Space or key == 'Space':
self.app.geo_editor.transform_tool.on_rotate_key()
@@ -2484,6 +2491,10 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.on_toggle_notebook()
return
+ # Rotate
+ if key == QtCore.Qt.Key_Space or key == 'Space':
+ self.app.grb_editor.transform_tool.on_rotate_key()
+
# Switch to Project Tab
if key == QtCore.Qt.Key_1 or key == '1':
self.app.grb_editor.launched_from_shortcuts = True
@@ -2515,7 +2526,6 @@ class FlatCAMGUI(QtWidgets.QMainWindow):
self.app.inform.emit(_("[WARNING_NOTCL] Cancelled. Nothing selected to copy."))
return
-
# Scale Tool
if key == QtCore.Qt.Key_B or key == 'B':
self.app.grb_editor.launched_from_shortcuts = True