Преглед изворни кода

- modified the pull request to include along the flipX, flipY commands
also the Rotate, SkewX and SkewY commands. Fix for issue #235

All perform the same in regard of multiple object selection.

Marius Stanciu пре 7 година
родитељ
комит
74104ec19f
7 измењених фајлова са 346 додато и 11 уклоњено
  1. 125 1
      FlatCAMApp.py
  2. 10 0
      FlatCAMGUI.py
  3. 69 10
      GUIElements.py
  4. 142 0
      camlib.py
  5. BIN
      share/rotate.png
  6. BIN
      share/skewX.png
  7. BIN
      share/skewY.png

+ 125 - 1
FlatCAMApp.py

@@ -35,6 +35,7 @@ from FlatCAMCommon import LoudDict
 from FlatCAMShell import FCShell
 from FlatCAMDraw import FlatCAMDraw
 from FlatCAMProcess import *
+from GUIElements import FCInputDialog
 from MeasurementTool import Measurement
 from DblSidedTool import DblSidedTool
 import tclCommands
@@ -538,6 +539,9 @@ class App(QtCore.QObject):
         self.ui.menuoptions_transfer_p2o.triggered.connect(self.on_options_project2object)
         self.ui.menuoptions_transform_flipx.triggered.connect(self.on_flipx)
         self.ui.menuoptions_transform_flipy.triggered.connect(self.on_flipy)
+        self.ui.menuoptions_transform_skewx.triggered.connect(self.on_skewx)
+        self.ui.menuoptions_transform_skewy.triggered.connect(self.on_skewy)
+        self.ui.menuoptions_transform_rotate.triggered.connect(self.on_rotate)
         self.ui.menuviewdisableall.triggered.connect(self.disable_plots)
         self.ui.menuviewdisableother.triggered.connect(lambda: self.disable_plots(except_current=True))
         self.ui.menuviewenable.triggered.connect(self.enable_all_plots)
@@ -1501,7 +1505,7 @@ class App(QtCore.QObject):
             warningbox.setText(msg)
             warningbox.setWindowTitle("Warning ...")
             warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
-            warningbox.setStandardButtons(QtGUi.QMessageBox.Ok)
+            warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
             warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
             warningbox.exec_()
         else:
@@ -1569,6 +1573,126 @@ class App(QtCore.QObject):
                 obj.plot()
             self.inform.emit('Flipped on the Y axis ...')
 
+    def on_rotate(self, preset=None):
+        obj_list = self.collection.get_selected()
+        xminlist = []
+        yminlist = []
+        xmaxlist = []
+        ymaxlist = []
+
+        if not obj_list:
+            self.inform.emit("WARNING: No object selected.")
+            msg = "Please Select an object to rotate!"
+            warningbox = QtGui.QMessageBox()
+            warningbox.setText(msg)
+            warningbox.setWindowTitle("Warning ...")
+            warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
+            warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
+            warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
+            warningbox.exec_()
+        else:
+            if preset is not None:
+                rotatebox = FCInputDialog()
+                num, ok = rotatebox.get_value(title='Transform', message='Enter the Angle value',
+                                              min=-360, max=360, decimals=3)
+            else:
+                num = preset
+                ok = True
+
+            if ok:
+                for sel_obj in obj_list:
+                    # first get a bounding box to fit all
+                    for obj in obj_list:
+                        xmin, ymin, xmax, ymax = obj.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)
+
+                    sel_obj.rotate(-num, point=(px, py))
+                    sel_obj.plot()
+                self.inform.emit('Object was rotated ...')
+
+    def on_skewx(self):
+        obj_list = self.collection.get_selected()
+        xminlist = []
+        yminlist = []
+
+        if not obj_list:
+            self.inform.emit("WARNING: No object selected.")
+            msg = "Please Select an object to skew/shear!"
+            warningbox = QtGui.QMessageBox()
+            warningbox.setText(msg)
+            warningbox.setWindowTitle("Warning ...")
+            warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
+            warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
+            warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
+            warningbox.exec_()
+        else:
+            skewxbox = FCInputDialog()
+            num, ok = skewxbox.get_value(title='Transform', message='Enter the Angle value',
+                                          min=-360, max=360, decimals=3)
+            if ok:
+                # first get a bounding box to fit all
+                for obj in obj_list:
+                    xmin, ymin, xmax, ymax = obj.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)
+
+                for obj in obj_list:
+                    obj.skew(num, 0, point=(xminimal, yminimal))
+                    obj.plot()
+                self.inform.emit('Object was skewed on X axis ...')
+
+    def on_skewy(self):
+        obj_list = self.collection.get_selected()
+        xminlist = []
+        yminlist = []
+
+
+        if not obj_list:
+            self.inform.emit("WARNING: No object selected.")
+            msg = "Please Select an object to skew/shear!"
+            warningbox = QtGui.QMessageBox()
+            warningbox.setText(msg)
+            warningbox.setWindowTitle("Warning ...")
+            warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
+            warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
+            warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
+            warningbox.exec_()
+        else:
+            skewybox = FCInputDialog()
+            num, ok = skewybox.get_value(title='Transform', message='Enter the Angle value',
+                                         min=-360, max=360, decimals=3)
+            if ok:
+                # first get a bounding box to fit all
+                for obj in obj_list:
+                    xmin, ymin, xmax, ymax = obj.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)
+
+                for obj in obj_list:
+                    obj.skew(0, num, point=(xminimal, yminimal))
+                    obj.plot()
+                self.inform.emit('Object was skewed on Y axis ...')
+
     def on_delete(self):
         """
         Delete the currently selected FlatCAMObjs.

+ 10 - 0
FlatCAMGUI.py

@@ -119,6 +119,16 @@ class FlatCAMGUI(QtGui.QMainWindow):
                                                                                 "Flip Selection on &X axis")
         self.menuoptions_transform_flipy = self.menuoptions_transform.addAction(QtGui.QIcon('share/flipy.png'),
                                                                                 "Flip Selection on &Y axis")
+        # Separator
+        self.menuoptions_transform.addSeparator()
+        self.menuoptions_transform_skewx = self.menuoptions_transform.addAction(QtGui.QIcon('share/skewx.png'),
+                                                                                "&Skew Selection on X axis")
+        self.menuoptions_transform_skewy = self.menuoptions_transform.addAction(QtGui.QIcon('share/skewy.png'),
+                                                                                "S&kew Selection on Y axis")
+        # Separator
+        self.menuoptions_transform.addSeparator()
+        self.menuoptions_transform_rotate = self.menuoptions_transform.addAction(QtGui.QIcon('share/rotate.png'),
+                                                                                 "&Rotate Selection")
 
         ### View ###
         self.menuview = self.menu.addMenu('&View')

+ 69 - 10
GUIElements.py

@@ -80,13 +80,15 @@ class LengthEntry(QtGui.QLineEdit):
         self.readyToEdit = True
 
     def mousePressEvent(self, e, Parent=None):
-        super(LengthEntry, self).mousePressEvent(e)  # required to deselect on 2e click
+        # required to deselect on 2nd click
+        super(LengthEntry, self).mousePressEvent(e)
         if self.readyToEdit:
             self.selectAll()
             self.readyToEdit = False
 
     def focusOutEvent(self, e):
-        super(LengthEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+        # required to remove cursor on focusOut
+        super(LengthEntry, self).focusOutEvent(e)
         self.deselect()
         self.readyToEdit = True
 
@@ -126,13 +128,15 @@ class FloatEntry(QtGui.QLineEdit):
         self.readyToEdit = True
 
     def mousePressEvent(self, e, Parent=None):
-        super(FloatEntry, self).mousePressEvent(e)  # required to deselect on 2e click
+        # required to deselect on 2nd click
+        super(FloatEntry, self).mousePressEvent(e)
         if self.readyToEdit:
             self.selectAll()
             self.readyToEdit = False
 
     def focusOutEvent(self, e):
-        super(FloatEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+        # required to remove cursor on focusOut
+        super(FloatEntry, self).focusOutEvent(e)
         self.deselect()
         self.readyToEdit = True
 
@@ -166,13 +170,15 @@ class IntEntry(QtGui.QLineEdit):
         self.readyToEdit = True
 
     def mousePressEvent(self, e, Parent=None):
-        super(IntEntry, self).mousePressEvent(e)  # required to deselect on 2e click
+        # required to deselect on 2nd click
+        super(IntEntry, self).mousePressEvent(e)
         if self.readyToEdit:
             self.selectAll()
             self.readyToEdit = False
 
     def focusOutEvent(self, e):
-        super(IntEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+        # required to remove cursor on focusOut
+        super(IntEntry, self).focusOutEvent(e)
         self.deselect()
         self.readyToEdit = True
 
@@ -199,13 +205,15 @@ class FCEntry(QtGui.QLineEdit):
         self.readyToEdit = True
 
     def mousePressEvent(self, e, Parent=None):
-        super(FCEntry, self).mousePressEvent(e)  # required to deselect on 2e click
+        # required to deselect on 2nd click
+        super(FCEntry, self).mousePressEvent(e)
         if self.readyToEdit:
             self.selectAll()
             self.readyToEdit = False
 
     def focusOutEvent(self, e):
-        super(FCEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+        # required to remove cursor on focusOut
+        super(FCEntry, self).focusOutEvent(e)
         self.deselect()
         self.readyToEdit = True
 
@@ -222,13 +230,15 @@ class EvalEntry(QtGui.QLineEdit):
         self.readyToEdit = True
 
     def mousePressEvent(self, e, Parent=None):
-        super(EvalEntry, self).mousePressEvent(e)  # required to deselect on 2e click
+        # required to deselect on 2nd click
+        super(EvalEntry, self).mousePressEvent(e)
         if self.readyToEdit:
             self.selectAll()
             self.readyToEdit = False
 
     def focusOutEvent(self, e):
-        super(EvalEntry, self).focusOutEvent(e)  # required to remove cursor on focusOut
+        # required to remove cursor on focusOut
+        super(EvalEntry, self).focusOutEvent(e)
         self.deselect()
         self.readyToEdit = True
 
@@ -275,6 +285,55 @@ class FCTextArea(QtGui.QPlainTextEdit):
     def get_value(self):
         return str(self.toPlainText())
 
+class FCInputDialog(QtGui.QInputDialog):
+    def __init__(self, parent=None, ok=False, val=None):
+        super(FCInputDialog, self).__init__(parent)
+        self.allow_empty = ok
+        self.empty_val = val
+        self.readyToEdit = True
+
+    def mousePressEvent(self, e, Parent=None):
+        # required to deselect on 2nd click
+        super(FCInputDialog, self).mousePressEvent(e)
+        if self.readyToEdit:
+            self.selectAll()
+            self.readyToEdit = False
+
+    def focusOutEvent(self, e):
+        # required to remove cursor on focusOut
+        super(FCInputDialog, self).focusOutEvent(e)
+        self.deselect()
+        self.readyToEdit = True
+
+    def get_value(self, title=None, message=None, min=None, max=None, decimals=None):
+        if title is None:
+            title = "FlatCAM action"
+        if message is None:
+            message = "Please enter the value: "
+        if min is None:
+            min = 0.0
+        if max is None:
+            max = 100.0
+        if decimals is None:
+            decimals = 1
+        self.val,self.ok = self.getDouble(self, title, message, min=min,
+                                                      max=max, decimals=decimals)
+        return [self.val,self.ok]
+
+    def set_value(self, val):
+        pass
+
+
+class FCButton(QtGui.QPushButton):
+    def __init__(self, parent=None):
+        super(FCButton, self).__init__(parent)
+
+    def get_value(self):
+        return self.isChecked()
+
+    def set_value(self, val):
+        self.setText(str(val))
+
 
 class VerticalScrollArea(QtGui.QScrollArea):
     """

+ 142 - 0
camlib.py

@@ -1051,6 +1051,46 @@ class Geometry(object):
 
         self.solid_geometry = mirror_geom(self.solid_geometry)
 
+    def skew(self, angle_x=None, angle_y=None, point=None):
+        """
+        Shear/Skew the geometries of an object by angles along x and y dimensions.
+
+        Parameters
+        ----------
+        xs, ys : float, float
+            The shear angle(s) for the x and y axes respectively. These can be
+            specified in either degrees (default) or radians by setting
+            use_radians=True.
+
+        See shapely manual for more information:
+        http://toblerity.org/shapely/manual.html#affine-transformations
+        """
+        if angle_y is None:
+            angle_y = 0.0
+        if angle_x is None:
+            angle_x = 0.0
+        if point is None:
+            self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
+                                            origin=(0, 0))
+        else:
+            px, py = point
+            self.solid_geometry = affinity.skew(self.solid_geometry, angle_x, angle_y,
+                                                origin=(px, py))
+        return
+
+    def rotate(self, angle, point=None):
+        """
+        Rotate an object by a given angle around given coords (point)
+        :param angle:
+        :param point:
+        :return:
+        """
+        if point is None:
+            self.solid_geometry = affinity.rotate(self.solid_geometry, angle, origin='center')
+        else:
+            px, py = point
+            self.solid_geometry = affinity.rotate(self.solid_geometry, angle, origin=(px, py))
+        return
 
 class ApertureMacro:
     """
@@ -2869,6 +2909,60 @@ class Excellon(Geometry):
         # Recreate geometry
         self.create_geometry()
 
+    def skew(self, angle_x=None, angle_y=None, point=None):
+        """
+        Shear/Skew the geometries of an object by angles along x and y dimensions.
+        Tool sizes, feedrates an Z-plane dimensions are untouched.
+
+        Parameters
+        ----------
+        angle_x, angle_y: float, float
+            The shear angle(s) for the x and y axes respectively. These can be
+            specified in either degrees (default) or radians by setting
+            use_radians=True.
+        point: point of origin for skew, tuple of coordinates
+
+        See shapely manual for more information:
+        http://toblerity.org/shapely/manual.html#affine-transformations
+        """
+
+        if angle_y is None:
+            angle_y = 0.0
+        if angle_x is None:
+            angle_x = 0.0
+        if point is None:
+            # Drills
+            for drill in self.drills:
+                drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
+                                               origin=(0, 0))
+        else:
+            # Drills
+            px, py = point
+            for drill in self.drills:
+                drill['point'] = affinity.skew(drill['point'], angle_x, angle_y,
+                                               origin=(px, py))
+
+        self.create_geometry()
+
+    def rotate(self, angle, point=None):
+        """
+        Rotate the geometry of an object by an angle around the 'point' coordinates
+        :param angle:
+        :param point: point around which to rotate
+        :return:
+        """
+        if point is None:
+            # Drills
+            for drill in self.drills:
+                drill['point'] = affinity.rotate(drill['point'], angle, origin='center')
+        else:
+            # Drills
+            px, py = point
+            for drill in self.drills:
+                drill['point'] = affinity.rotate(drill['point'], angle, origin=(px, py))
+
+        self.create_geometry()
+
     def convert_units(self, units):
         factor = Geometry.convert_units(self, units)
 
@@ -3535,6 +3629,54 @@ class CNCjob(Geometry):
 
         self.create_geometry()
 
+    def skew(self, angle_x=None, angle_y=None, point=None):
+        """
+        Shear/Skew the geometries of an object by angles along x and y dimensions.
+
+        Parameters
+        ----------
+        angle_x, angle_y : float, float
+            The shear angle(s) for the x and y axes respectively. These can be
+            specified in either degrees (default) or radians by setting
+            use_radians=True.
+        point: tupple of coordinates . Origin for skew.
+
+        See shapely manual for more information:
+        http://toblerity.org/shapely/manual.html#affine-transformations
+        """
+
+        if angle_y is None:
+            angle_y = 0.0
+        if angle_x is None:
+            angle_x = 0.0
+        if point == None:
+            for g in self.gcode_parsed:
+                g['geom'] = affinity.skew(g['geom'], angle_x, angle_y,
+                                          origin=(0, 0))
+        else:
+            for g in self.gcode_parsed:
+                g['geom'] = affinity.skew(g['geom'], angle_x, angle_y,
+                                          origin=point)
+
+        self.create_geometry()
+
+    def rotate(self, angle, point=None):
+        """
+        Rotate the geometrys of an object by an given angle around the coordinates of the 'point'
+        :param angle:
+        :param point:
+        :return:
+        """
+        if point is None:
+            for g in self.gcode_parsed:
+                g['geom'] = affinity.rotate(g['geom'], angle, origin='center')
+        else:
+            px, py = point
+            for g in self.gcode_parsed:
+                g['geom'] = affinity.rotate(g['geom'], angle, origin=(px, py))
+
+        self.create_geometry()
+
     def export_svg(self, scale_factor=0.00):
         """
         Exports the CNC Job as a SVG Element