Esempio n. 1
0
    def calc_bounding_box(self):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        """

        Ps = Point(x=self.O.x - self.r, y=self.O.y - self.r)
        Pe = Point(x=self.O.x + self.r, y=self.O.y + self.r)

        # Do the calculation only for arcs have positiv extend => switch angles
        if self.ext >= 0:
            s_ang = self.s_ang
            e_ang = self.e_ang
        elif self.ext < 0:
            s_ang = self.e_ang
            e_ang = self.s_ang

        # If the positive X Axis is crossed
        if not (self.wrap(s_ang, 0) >= self.wrap(e_ang, 1)):
            Pe.x = max(self.Ps.x, self.Pe.x)

        # If the positive Y Axis is crossed
        if not (self.wrap(s_ang - pi / 2, 0) >= self.wrap(e_ang - pi / 2, 1)):
            Pe.y = max(self.Ps.y, self.Pe.y)

        # If the negative X Axis is crossed
        if not (self.wrap(s_ang - pi, 0) >= self.wrap(e_ang - pi, 1)):
            Ps.x = min(self.Ps.x, self.Pe.x)

        # If the negative Y is crossed
        if not (self.wrap(s_ang - 1.5 * pi, 0) >= self.wrap(
                e_ang - 1.5 * pi, 1)):
            Ps.y = min(self.Ps.y, self.Pe.y)

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)
Esempio n. 2
0
    def __init__(self, parent=None):
        super(GLWidget, self).__init__(parent)

        self.shapes = Shapes([])
        self.orientation = 0
        self.wpZero = 0
        self.routearrows = []
        self.expprv = None

        self.isPanning = False
        self.isRotating = False
        self.isMultiSelect = False
        self._lastPos = QPoint()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0
        self.scaleCorr = 1.0

        self.showPathDirections = False
        self.showDisabledPaths = False

        self.BB = BoundingBox()

        self.tol = 0
Esempio n. 3
0
    def calc_bounding_box(self):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        """
        Ps = Point(x=min(self.Ps.x, self.Pe.x), y=min(self.Ps.y, self.Pe.y))
        Pe = Point(x=max(self.Ps.x, self.Pe.x), y=max(self.Ps.y, self.Pe.y))

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)
Esempio n. 4
0
    def __init__(self):
        QGraphicsScene.__init__(self)

        self.shapes = []
        self.wpzero = None
        self.routearrows = []
        self.routetext = []
        self.expprv = None
        self.expcol = None
        self.expnr = 0

        self.showDisabledPaths = False

        self.BB = BoundingBox()
Esempio n. 5
0
    def calc_bounding_box(self):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        """

        Ps = Point(x=self.O.x - self.r, y=self.O.y - self.r)
        Pe = Point(x=self.O.x + self.r, y=self.O.y + self.r)

        # Do the calculation only for arcs have positiv extend => switch angles
        if self.ext >= 0:
            s_ang = self.s_ang
            e_ang = self.e_ang
        elif self.ext < 0:
            s_ang = self.e_ang
            e_ang = self.s_ang

        # If the positive X Axis is crossed
        if not(self.wrap(s_ang, 0) >= self.wrap(e_ang, 1)):
            Pe.x = max(self.Ps.x, self.Pe.x)

        # If the positive Y Axis is crossed
        if not(self.wrap(s_ang - pi / 2, 0) >= self.wrap(e_ang - pi / 2, 1)):
            Pe.y = max(self.Ps.y, self.Pe.y)

        # If the negative X Axis is crossed
        if not(self.wrap(s_ang - pi, 0) >= self.wrap(e_ang - pi, 1)):
            Ps.x = min(self.Ps.x, self.Pe.x)

        # If the negative Y is crossed
        if not(self.wrap(s_ang - 1.5 * pi, 0) >=
                self.wrap(e_ang - 1.5 * pi, 1)):
            Ps.y = min(self.Ps.y, self.Pe.y)

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)
Esempio n. 6
0
    def __init__(self, parent=None):
        super(GLWidget, self).__init__(parent)

        self.shapes = Shapes([])
        self.orientation = 0
        self.wpZero = 0
        self.routearrows = []
        self.expprv = None

        self.isPanning = False
        self.isRotating = False
        self.isMultiSelect = False
        self._lastPos = QPoint()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0
        self.scaleCorr = 1.0

        self.showPathDirections = False
        self.showDisabledPaths = False

        self.BB = BoundingBox()

        self.tol = 0
Esempio n. 7
0
    def calc_bounding_box(self, radius=1):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        @param radius: The Radius of the HoleGeo to be used for BoundingBox
        """

        Ps = Point(x=self.Ps.x - radius, y=self.Ps.y - radius)
        Pe = Point(x=self.Ps.x + radius, y=self.Ps.y + radius)

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)
Esempio n. 8
0
    def resetAll(self):
        # the wpzero is currently generated "last"
        if self.wpZero > 0:
            GL.glDeleteLists(self.orientation + 1,
                             self.wpZero - self.orientation)
        self.shapes = Shapes([])
        self.wpZero = 0
        self.delete_opt_paths()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0

        self.BB = BoundingBox()

        self.update()
Esempio n. 9
0
    def resetAll(self):
        # the wpzero is currently generated "last"
        if self.wpZero > 0:
            GL.glDeleteLists(self.orientation + 1, self.wpZero - self.orientation)
        self.shapes = Shapes([])
        self.wpZero = 0
        self.delete_opt_paths()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0

        self.BB = BoundingBox()

        self.update()
Esempio n. 10
0
class MyGraphicsScene(QGraphicsScene):
    """
    This is the Canvas used to print the graphical interface of dxf2gcode.
    The Scene is rendered into the previously defined mygraphicsView class.
    All performed plotting functions should be defined here.
    @sideeffect: None
    """
    def __init__(self):
        QGraphicsScene.__init__(self)

        self.shapes = []
        self.wpzero = None
        self.routearrows = []
        self.routetext = []
        self.expprv = None
        self.expcol = None
        self.expnr = 0

        self.showDisabledPaths = False

        self.BB = BoundingBox()

    def tr(self, string_to_translate):
        """
        Translate a string using the QCoreApplication translation framework
        @param string_to_translate: a unicode string
        @return: the translated unicode string if it was possible to translate
        """
        return text_type(
            QtCore.QCoreApplication.translate('MyGraphicsScene',
                                              string_to_translate))

    def plotAll(self, shapes):
        """
        Instance is called by the Main Window after the defined file is loaded.
        It generates all ploting functionality. The parameters are generally
        used to scale or offset the base geometry (by Menu in GUI).
        """
        for shape in shapes:
            self.paint_shape(shape)
            self.addItem(shape)
            self.shapes.append(shape)
        self.draw_wp_zero()
        self.update()

    def repaint_shape(self, shape):
        # setParentItem(None) might let it crash, hence we rely on the garbage collector
        shape.stmove.hide()
        shape.starrow.hide()
        shape.enarrow.hide()
        del shape.stmove
        del shape.starrow
        del shape.enarrow
        self.paint_shape(shape)
        if not shape.isSelected():
            shape.stmove.hide()
            shape.starrow.hide()
            shape.enarrow.hide()

    def paint_shape(self, shape):
        """
        Create all plotting related parts of one shape.
        @param shape: The shape to be plotted.
        """
        start, start_ang = shape.get_start_end_points(True, True)
        shape.path = QPainterPath()
        shape.path.moveTo(start.x, -start.y)
        drawHorLine = lambda caller, start, end: shape.path.lineTo(
            end.x, -end.y)
        drawVerLine = lambda caller, start: None  # Not used in 2D mode
        shape.make_path(drawHorLine, drawVerLine)

        self.BB = self.BB.joinBB(shape.BB)

        shape.stmove = self.createstmove(shape)
        shape.starrow = self.createstarrow(shape)
        shape.enarrow = self.createenarrow(shape)
        shape.stmove.setParentItem(shape)
        shape.starrow.setParentItem(shape)
        shape.enarrow.setParentItem(shape)

    def draw_wp_zero(self):
        """
        This function is called while the drawing of all items is done. It plots
        the WPZero to the Point x=0 and y=0. This item will be enabled or
        disabled to be shown or not.
        """
        self.wpzero = WpZero(QtCore.QPointF(0, 0))
        self.addItem(self.wpzero)

    def createstarrow(self, shape):
        """
        This function creates the Arrows at the end point of a shape when the
        shape is selected.
        @param shape: The shape for which the Arrow shall be created.
        """

        length = 20
        start, start_ang = shape.get_start_end_points_physical(True, True)
        arrow = Arrow(startp=start,
                      length=length,
                      angle=start_ang,
                      color=QColor(50, 200, 255),
                      pencolor=QColor(50, 100, 255))
        return arrow

    def createenarrow(self, shape):
        """
        This function creates the Arrows at the end point of a shape when the
        shape is selected.
        @param shape: The shape for which the Arrow shall be created.
        """
        length = 20
        end, end_ang = shape.get_start_end_points_physical(False, True)
        arrow = Arrow(startp=end,
                      length=length,
                      angle=end_ang,
                      color=QColor(0, 245, 100),
                      pencolor=QColor(0, 180, 50),
                      startarrow=False)
        return arrow

    def createstmove(self, shape):
        """
        This function creates the Additional Start and End Moves in the plot
        window when the shape is selected
        @param shape: The shape for which the Move shall be created.
        """
        stmove = StMoveGUI(shape)
        return stmove

    def delete_opt_paths(self):
        """
        This function deletes all the plotted export routes.
        """
        # removeItem might let it crash, hence we rely on the garbage collector
        while self.routearrows:
            item = self.routearrows.pop()
            item.hide()
            del item

        while self.routetext:
            item = self.routetext.pop()
            item.hide()
            del item

    def addexproutest(self):
        self.expprv = Point(g.config.vars.Plane_Coordinates['axis1_start_end'],
                            g.config.vars.Plane_Coordinates['axis2_start_end'])
        self.expcol = QtCore.Qt.darkRed

    def addexproute(self, exp_order, layer_nr):
        """
        This function initialises the Arrows of the export route order and its numbers.
        """
        for shape_nr in range(len(exp_order)):
            shape = self.shapes[exp_order[shape_nr]]
            st = self.expprv
            en = None
            # if hasattr(shape.geos[0], "type") and shape.geos[0].type is "Circle":
            #     en,self.expprv = shape.geos[0].O,shape
            # else:
            en, self.expprv = shape.get_start_end_points_physical()
            if (hasattr(shape.geos[0], "type")
                    and shape.geos[0].type is "Circle"):
                self.expcol = QtCore.Qt.darkGreen
            self.routearrows.append(
                Arrow(startp=en,
                      endp=st,
                      color=self.expcol,
                      pencolor=self.expcol))

            self.expcol = QtCore.Qt.darkGray

            self.routetext.append(
                RouteText(text=("%s,%s" % (layer_nr, shape_nr + 1)),
                          startp=en))
            # self.routetext[-1].ItemIgnoresTransformations

            self.addItem(self.routearrows[-1])
            self.addItem(self.routetext[-1])

    def addexprouteen(self):
        st = self.expprv
        en = Point(g.config.vars.Plane_Coordinates['axis1_start_end'],
                   g.config.vars.Plane_Coordinates['axis2_start_end'])
        self.expcol = QtCore.Qt.darkRed

        self.routearrows.append(
            Arrow(startp=en, endp=st, color=self.expcol, pencolor=self.expcol))

        self.addItem(self.routearrows[-1])

    def setShowDisabledPaths(self, flag):
        """
        This function is called by the Main Menu and is passed from Main to
        MyGraphicsView to the Scene. It performs the showing or hiding
        of enabled/disabled shapes.

        @param flag: This flag is true if hidden paths shall be shown
        """
        self.showDisabledPaths = flag

        for shape in self.shapes:
            if flag and shape.isDisabled():
                shape.show()
            elif not flag and shape.isDisabled():
                shape.hide()
Esempio n. 11
0
class ArcGeo(object):
    """
    Standard Geometry Item used for DXF Import of all geometries, plotting and
    G-Code export.
    """
    def __init__(self,
                 Ps=None,
                 Pe=None,
                 O=None,
                 r=1,
                 s_ang=None,
                 e_ang=None,
                 direction=1,
                 drag=False):
        """
        Standard Method to initialize the ArcGeo. Not all of the parameters are
        required to fully define a arc. e.g. Ps and Pe may be given or s_ang and
        e_ang
        @param Ps: The Start Point of the arc
        @param Pe: the End Point of the arc
        @param O: The center of the arc
        @param r: The radius of the arc
        @param s_ang: The Start Angle of the arc
        @param e_ang: the End Angle of the arc
        @param direction: The arc direction where 1 is in positive direction
        """

        self.Ps = Ps
        self.Pe = Pe
        self.O = O
        self.r = abs(r)
        self.s_ang = s_ang
        self.e_ang = e_ang
        self.drag = drag

        # Get the Circle center point with known Start and End Points
        if self.O is None:

            if self.Ps is not None and\
               self.Pe is not None and\
               direction is not None:

                arc = self.Pe.norm_angle(self.Ps) - pi / 2
                m = self.Pe.distance(self.Ps) / 2

                if abs(self.r - m) < g.config.fitting_tolerance:
                    lo = 0.0
                else:
                    lo = sqrt(pow(self.r, 2) - pow(m, 2))

                d = -1 if direction < 0 else 1

                self.O = self.Ps + (self.Pe - self.Ps) / 2
                self.O.y += lo * sin(arc) * d
                self.O.x += lo * cos(arc) * d

            # Compute center point
            elif self.s_ang is not None:
                self.O.x = self.Ps.x - self.r * cos(self.s_ang)
                self.O.y = self.Ps.y - self.r * sin(self.s_ang)
            else:
                logger.error(self.tr("Missing value for Arc Geometry"))

        # Calculate start and end angles
        if self.s_ang is None:
            self.s_ang = self.O.norm_angle(self.Ps)
        if self.e_ang is None:
            self.e_ang = self.O.norm_angle(self.Pe)

        self.ext = self.dif_ang(self.Ps, self.Pe, direction)

        self.length = self.r * abs(self.ext)

        self.calc_bounding_box()

        self.abs_geo = None

    def __deepcopy__(self, memo):
        return ArcGeo(deepcopy(self.Ps, memo), deepcopy(self.Pe, memo),
                      deepcopy(self.O, memo), deepcopy(self.r, memo),
                      deepcopy(self.s_ang, memo), deepcopy(self.e_ang, memo),
                      deepcopy(self.ext, memo))

    def __str__(self):
        """
        Standard method to print the object
        @return: A string
        """
        return ("\nArcGeo(Ps=Point(x=%s ,y=%s), \n" % (self.Ps.x, self.Ps.y)) + \
               ("Pe=Point(x=%s, y=%s),\n" % (self.Pe.x, self.Pe.y)) + \
               ("O=Point(x=%s, y=%s),\n" % (self.O.x, self.O.y)) + \
               ("s_ang=%s,e_ang=%s,\n" % (self.s_ang, self.e_ang)) + \
               ("r=%s, \n" % self.r) + \
               ("ext=%s)" % self.ext)

    def save_v1(self):
        return "\nArcGeo" +\
               "\nPs:  %s; s_ang: %0.5f" % (self.Ps.save_v1(), self.s_ang) +\
               "\nPe:  %s; e_ang: %0.5f" % (self.Pe.save_v1(), self.e_ang) +\
               "\nO:   %s; r: %0.3f" % (self.O.save_v1(), self.r) +\
               "\next: %0.5f; length: %0.5f" % (self.ext, self.length)

    def angle_between(self, min_ang, max_ang, angle):
        """
        Returns if the angle is in the range between 2 other angles
        @param min_ang: The starting angle
        @param parent: The end angel. Always in ccw direction from min_ang
        @return: True or False
        """
        if min_ang < 0.0:
            min_ang += 2 * pi

        while max_ang < min_ang:
            max_ang += 2 * pi

        while angle < min_ang:
            angle += 2 * pi

        return (min_ang < angle) and (angle <= max_ang)

    def calc_bounding_box(self):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        """

        Ps = Point(x=self.O.x - self.r, y=self.O.y - self.r)
        Pe = Point(x=self.O.x + self.r, y=self.O.y + self.r)

        # Do the calculation only for arcs have positiv extend => switch angles
        if self.ext >= 0:
            s_ang = self.s_ang
            e_ang = self.e_ang
        elif self.ext < 0:
            s_ang = self.e_ang
            e_ang = self.s_ang

        # If the positive X Axis is crossed
        if not (self.wrap(s_ang, 0) >= self.wrap(e_ang, 1)):
            Pe.x = max(self.Ps.x, self.Pe.x)

        # If the positive Y Axis is crossed
        if not (self.wrap(s_ang - pi / 2, 0) >= self.wrap(e_ang - pi / 2, 1)):
            Pe.y = max(self.Ps.y, self.Pe.y)

        # If the negative X Axis is crossed
        if not (self.wrap(s_ang - pi, 0) >= self.wrap(e_ang - pi, 1)):
            Ps.x = min(self.Ps.x, self.Pe.x)

        # If the negative Y is crossed
        if not (self.wrap(s_ang - 1.5 * pi, 0) >= self.wrap(
                e_ang - 1.5 * pi, 1)):
            Ps.y = min(self.Ps.y, self.Pe.y)

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)

    def dif_ang(self, Ps, Pe, direction):
        """
        Calculated the angle between Pe and Ps with respect to the origin
        @param Ps: the start Point of the arc
        @param Pe: the end Point of the arc
        @param direction: the direction of the arc
        @return: Returns the angle between -2* pi and 2 *pi for the arc,
        0 excluded - we got a complete circle
        """
        dif_ang = (self.O.norm_angle(Pe) - self.O.norm_angle(Ps)) % (-2 * pi)

        if direction > 0:
            dif_ang += 2 * pi
        elif dif_ang == 0:
            dif_ang = -2 * pi

        return dif_ang

    def distance(self, other):
        """
        Find the distance between 2 geometry elements. Possible is LineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        """
        Find the distance between 2 geometry elements. Possible is Point, LineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        from core.linegeo import LineGeo

        if isinstance(other, LineGeo):
            return other.distance_l_a(self)
        elif isinstance(other, Point):
            return self.distance_a_p(other)
        elif isinstance(other, ArcGeo):
            return self.distance_a_a(other)
        else:
            logger.error(self.tr("Unsupported geometry type: %s" %
                                 type(other)))

    def distance_a_a(self, other):
        """
        Find the distance between two arcs
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        # logger.error('Unsupported function')
        Pself = self.get_nearest_point(other)
        Pother = other.get_nearest_point(self)
        return Pself.distance(Pother)

    def distance_a_p(self, other):
        """
        Find the distance between a arc and a point
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        # The Pont is outside of the Arc
        if self.O.distance(other) > self.r:
            # If the Nearest Point is on Arc Segement it is the neares one.
            # logger.debug("Nearest Point is outside of arc")
            if self.PointAng_withinArc(other):
                return other.distance(
                    self.O.get_arc_point(self.O.norm_angle(other), r=self.r))
            elif other.distance(self.Ps) < other.distance(self.Pe):
                return other.distance(self.Ps)
            else:
                return other.distance(self.Pe)

        # logger.debug("Nearest Point is Inside of arc")
        # logger.debug("self.distance(other.Ps): %s, self.distance(other.Pe): %s" %(self.distance(other.Ps),self.distance(other.Pe)))
        # The Line may be inside of the ARc or cross it
        if other.distance(self.Ps) < other.distance(self.Pe):
            dis_min = other.distance(self.Ps)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
        else:
            dis_min = other.distance(self.Pe)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        if ((self.PointAng_withinArc(other))
                and abs(self.r - self.O.distance(other)) < dis_min):
            dis_min = abs(self.r - self.O.distance(other))
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        return dis_min

    def find_inter_point(self, other=[], type='TIP'):
        """
        Find the intersection between 2 geometry elements. Possible is CCLineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)        @return: a list of intersection points.
        """
        from core.linegeo import LineGeo

        if isinstance(other, LineGeo):
            IPoints = other.find_inter_point_l_a(self, type)
            return IPoints
        elif isinstance(other, ArcGeo):
            return self.find_inter_point_a_a(other, type)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def find_inter_point_a_a(self, other, type='TIP'):
        """
        Find the intersection between 2 ArcGeo elements. There can be only one
        intersection between 2 lines.
        @param other: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)
        @return: a list of intersection points.
        @todo: FIXME: The type of the intersection is not implemented up to now
        """
        O_dis = self.O.distance(other.O)

        # If self circle is surrounded by the other no intersection
        if (O_dis < abs(self.r - other.r)):
            return None

        # If other circle is surrounded by the self no intersection
        if (O_dis < abs(other.r - self.r)):
            return None

        # If The circles are to far away from each other no intersection possible
        if (O_dis > abs(other.r + self.r)):
            return None

        # If both circles have the same center and radius
        if abs(O_dis) == 0.0 and abs(self.r - other.r) == 0.0:
            Pi1 = Point(x=self.Ps.x, y=self.Ps.y)
            Pi2 = Point(x=self.Pe.x, y=self.Pe.y)

            return [Pi1, Pi2]
        # The following algorithm was found on :
        # http://www.sonoma.edu/users/w/wilsonst/Papers/Geometry/circles/default.htm

        root = ((pow(self.r + other.r, 2) - pow(O_dis, 2)) *
                (pow(O_dis, 2) - pow(other.r - self.r, 2)))

        # If the Line is a tangent the root is 0.0.
        if root <= 0.0:
            root = 0.0
        else:
            root = sqrt(root)

        xbase = (other.O.x + self.O.x) / 2 + \
        (other.O.x - self.O.x) * \
        (pow(self.r, 2) - pow(other.r, 2)) / (2 * pow(O_dis, 2))

        ybase = (other.O.y + self.O.y) / 2 + \
        (other.O.y - self.O.y) * \
        (pow(self.r, 2) - pow(other.r, 2)) / (2 * pow(O_dis, 2))

        Pi1 = Point(x=xbase + (other.O.y - self.O.y) / \
                          (2 * pow(O_dis, 2)) * root,
                    y=ybase - (other.O.x - self.O.x) / \
                    (2 * pow(O_dis, 2)) * root)

        Pi1.v1 = self.dif_ang(self.Ps, Pi1, self.ext) / self.ext
        Pi1.v2 = other.dif_ang(other.Ps, Pi1, other.ext) / other.ext

        Pi2 = Point(x=xbase - (other.O.y - self.O.y) / \
                         (2 * pow(O_dis, 2)) * root,
                    y=ybase + (other.O.x - self.O.x) / \
                    (2 * pow(O_dis, 2)) * root)

        Pi2.v1 = self.dif_ang(self.Ps, Pi2, self.ext) / self.ext
        Pi2.v2 = other.dif_ang(other.Ps, Pi2, other.ext) / other.ext

        if type == 'TIP':
            if ((Pi1.v1 >= 0.0 and Pi1.v1 <= 1.0 and Pi1.v2 > 0.0
                 and Pi1.v2 <= 1.0) and (Pi2.v1 >= 0.0 and Pi2.v1 <= 1.0
                                         and Pi2.v2 > 0.0 and Pi2.v2 <= 1.0)):
                if (root == 0):
                    return Pi1
                else:
                    return [Pi1, Pi2]
            elif (Pi1.v1 >= 0.0 and Pi1.v1 <= 1.0 and Pi1.v2 > 0.0
                  and Pi1.v2 <= 1.0):
                return Pi1
            elif (Pi2.v1 >= 0.0 and Pi2.v1 <= 1.0 and Pi2.v2 > 0.0
                  and Pi2.v2 <= 1.0):
                return Pi2
            else:
                return None
        elif type == "Ray":
            # If the root is zero only one solution and the line is a tangent.
            if root == 0:
                return Pi1
            else:
                return [Pi1, Pi2]
        else:
            logger.error("We should not be here")

    def get_nearest_point(self, other):
        """
        Get the nearest point on the arc to another geometry.
        @param other: The Line to be nearest to
        @return: The point which is the nearest to other
        """
        from core.linegeo import LineGeo

        if isinstance(other, LineGeo):
            return other.get_nearest_point_l_a(self, ret="arc")
        elif isinstance(other, ArcGeo):
            return self.get_nearest_point_a_a(other)
        elif isinstance(other, Point):
            return self.get_nearest_point_a_p(other)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def get_nearest_point_a_p(self, other):
        """
        Get the nearest point to a point lieing on the arc
        @param other: The Point to be nearest to
        @return: The point which is the nearest to other
        """
        if self.intersect(other):
            return other

        PPoint = self.O.get_arc_point(self.O.norm_angle(other), r=self.r)
        if self.intersect(PPoint):
            return PPoint
        elif self.Ps.distance(other) < self.Pe.distance(other):
            return self.Ps
        else:
            return self.Pe

    def get_nearest_point_a_a(self, other, ret="self"):
        """
        Get the nearest point to a line lieing on the line
        @param other: The Point to be nearest to
        @return: The point which is the nearest to other
        """
        if self.intersect(other):
            return self.find_inter_point_a_a(other)

        # The Arc is outside of the Arc
        # if other.O.distance(self.O)>(other.r+other.r):

        # If Nearest point is on both Arc Segments.
        if other.PointAng_withinArc(self.O) and self.PointAng_withinArc(
                other.O):
            if ret == "self":
                return self.O.get_arc_point(self.O.norm_angle(other.O),
                                            r=self.r)
            elif ret == "other":
                return other.O.get_arc_point(other.O.norm_angle(self.O),
                                             r=other.r)
        # If Nearest point is on self Arc Segment but not other
        elif self.PointAng_withinArc(other.O):
            if self.distance(other.Ps) < self.distance(other.Pe):
                if ret == "self":
                    return self.O.get_arc_point(self.O.norm_angle(other.Ps),
                                                r=self.r)
                elif ret == "other":
                    return other.Ps
            else:
                if ret == "self":
                    return self.O.get_arc_point(self.O.norm_angle(other.Pe),
                                                r=self.r)
                elif ret == "other":
                    return other.Pe
        # If Nearest point is on other Arc Segment but not self
        elif other.PointAng_withinArc(self.O):
            if other.distance(self.Ps) < other.distance(self.Pe):
                if ret == "self":
                    return self.Ps
                elif ret == "other":
                    return other.O.get_arc_point(other.O.norm_angle(self.Ps),
                                                 r=other.r)
            else:
                if ret == "self":
                    return self.Pe
                elif ret == "other":
                    return other.O.get_arc_point(other.O.norm_angle(self.Pe),
                                                 r=other.r)
        # If the min distance is not on any arc segemtn but other.Ps is nearer then other.Pe
        elif self.distance(other.Ps) < self.distance(other.Pe):
            if self.Ps.distance(other.Ps) < self.Pe.distance(other.Ps):
                if ret == "self":
                    return self.Ps
                elif ret == "other":
                    return other.Ps
            else:
                if ret == "self":
                    return self.Pe
                elif ret == "other":
                    return other.Ps
        else:
            if self.Ps.distance(other.Pe) < self.Pe.distance(other.Pe):
                if ret == "self":
                    return self.Ps
                elif ret == "other":
                    return other.Pe
            else:
                if ret == "self":
                    return self.Pe
                elif ret == "other":
                    return other.Pe

            #         #logger.debug("Nearest Point is Inside of arc")
            #         #logger.debug("self.distance(other.Ps): %s, self.distance(other.Pe): %s" %(self.distance(other.Ps),self.distance(other.Pe)))
            #         # The Line may be inside of the ARc or cross it
            #         if self.distance(other.Ps)<self.distance(other.Pe):
            #             Pnearest=self.get_nearest_point(other.Ps)
            #             Pnother=other.Ps
            #             dis_min=self.distance(other.Ps)
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #         else:
            #             Pnearest=self.get_nearest_point(other.Pe)
            #             Pnother=other.Pe
            #             dis_min=self.distance(other.Pe)
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #
            #         if ((other.PointAng_withinArc(self.Ps)) and
            #             abs(other.r-other.O.distance(self.Ps)) < dis_min):
            #
            #             Pnearest=self.Ps
            #             Pnother=other.O.get_arc_point(other.O.norm_angle(Pnearest),r=other.r)
            #             dis_min=abs(other.r-other.O.distance(self.Ps))
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #
            #         if ((other.PointAng_withinArc(self.Pe)) and
            #             abs((other.r-other.O.distance(self.Pe))) < dis_min):
            #             Pnearest=self.Pe
            #             Pnother=other.O.get_arc_point(other.O.norm_angle(Pnearest),r=other.r)
            #
            #             dis_min=abs(other.r-other.O.distance(self.Pe))
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #         if ret=="line":
            #             return Pnearest
            #         elif ret=="arc":
            #             return Pnother

    def get_point_from_start(self, i, segments):
        ang = self.s_ang + i * self.ext / segments
        return self.O.get_arc_point(ang, self.r)

    def get_start_end_points(self, start_point, angles=None):
        if start_point:
            if angles is None:
                return self.Ps
            elif angles:
                return self.Ps, self.s_ang + pi / 2 * self.ext / abs(self.ext)
            else:
                direction = (self.O - self.Ps).unit_vector()
                direction = -direction if self.ext >= 0 else direction
                return self.Ps, Point(-direction.y, direction.x)
        else:
            if angles is None:
                return self.Pe
            elif angles:
                return self.Pe, self.e_ang - pi / 2 * self.ext / abs(self.ext)
            else:
                direction = (self.O - self.Pe).unit_vector()
                direction = -direction if self.ext >= 0 else direction
                return self.Pe, Point(-direction.y, direction.x)

    def intersect(self, other):
        """
        Check if there is an intersection of two geometry elements
        @param, a second geometry which shall be checked for intersection
        @return: True if there is an intersection
        """
        # Do a raw check first with BoundingBox
        # logger.debug("self: %s, \nother: %s, \nintersect: %s" %(self,other,self.BB.hasintersection(other.BB)))
        # logger.debug("self.BB: %s \nother.BB: %s")

        # We need to test Point first cause it has no BB
        from core.linegeo import LineGeo

        if isinstance(other, Point):
            return self.intersect_a_p(other)
        elif not (self.BB.hasintersection(other.BB)):
            return False
        elif isinstance(other, LineGeo):
            return other.intersect_l_a(self)
        elif isinstance(other, ArcGeo):
            return self.intersect_a_a(other)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def intersect_a_a(self, other):
        """
        Check if there is an intersection of two arcs
        @param, a second arc which shall be checked for intersection
        @return: True if there is an intersection
        """
        inter = self.find_inter_point_a_a(other)
        return not (inter is None)

    def intersect_a_p(self, other):
        """
        Check if there is an intersection of an point and a arc
        @param, a second arc which shall be checked for intersection
        @return: True if there is an intersection
        """
        # No intersection possible if point is not within radius
        if not (abs(self.O.distance(other) - self.r) < abs):
            return False
        elif self.PointAng_withinArc(other):
            return True
        else:
            return False

    def isHit(self, caller, xy, tol):
        tol2 = tol**2
        segments = int(abs(degrees(self.ext)) // 3 + 1)
        Ps = self.O.get_arc_point(self.s_ang, self.r)
        for i in range(1, segments + 1):
            Pe = self.get_point_from_start(i, segments)
            if xy.distance2_to_line(Ps, Pe) <= tol2:
                return True
            Ps = Pe
        return False

    def make_abs_geo(self, parent=None):
        """
        Generates the absolute geometry based on itself and the parent. This
        is done for rotating and scaling purposes
        """
        Ps = self.Ps.rot_sca_abs(parent=parent)
        Pe = self.Pe.rot_sca_abs(parent=parent)
        O = self.O.rot_sca_abs(parent=parent)
        r = self.scaled_r(self.r, parent)

        direction = 1 if self.ext > 0.0 else -1

        if parent is not None and parent.sca[0] * parent.sca[1] < 0.0:
            direction *= -1

        self.abs_geo = ArcGeo(Ps=Ps, Pe=Pe, O=O, r=r, direction=direction)

    def make_path(self, caller, drawHorLine):
        segments = int(abs(degrees(self.ext)) // 3 + 1)
        Ps = self.O.get_arc_point(self.s_ang, self.r)

        for i in range(1, segments + 1):
            Pe = self.get_point_from_start(i, segments)
            drawHorLine(caller, Ps, Pe)
            Ps = Pe

    def PointAng_withinArc(self, Point):
        """
        Check if the angle defined by Point is within the span of the arc.
        @param Point: The Point which angle to be checked
        @return: True or False
        """
        if self.ext == 0.0:
            return False

        v = self.dif_ang(self.Ps, Point, self.ext) / self.ext
        return v >= 0.0 and v <= 1.0

    def reverse(self):
        """
        Reverses the direction of the arc (switch direction).
        """
        self.Ps, self.Pe = self.Pe, self.Ps
        self.s_ang, self.e_ang = self.e_ang, self.s_ang
        self.ext = -self.ext
        if self.abs_geo:
            self.abs_geo.reverse()

    def scaled_r(self, r, parent):
        """
        Scales the radius based on the scale given in its parents. This is done
        recursively.
        @param r: The radius which shall be scaled
        @param parent: The parent Entity (Instance: EntityContentClass)
        @return: The scaled radius
        """
        # Rekursive Schleife falls mehrfach verschachtelt.
        # Recursive loop if nested.
        if parent is not None:
            r *= parent.sca[0]
            r = self.scaled_r(r, parent.parent)

        return r

    def split_into_2geos(self, ipoint=Point()):
        """
        Splits the given geometry into 2 geometries. The
        geometry will be splitted at ipoint.
        @param ipoint: The Point where the intersection occures
        @return: A list of 2 ArcGeo's will be returned.
        """

        # Generate the 2 geometries and their bounding boxes.
        Arc1 = ArcGeo(Ps=self.Ps,
                      Pe=ipoint,
                      r=self.r,
                      O=self.O,
                      direction=self.ext)

        Arc2 = ArcGeo(Ps=ipoint,
                      Pe=self.Pe,
                      r=self.r,
                      O=self.O,
                      direction=self.ext)
        return [Arc1, Arc2]

    def toShortString(self):
        return "(%f, %f) -> (%f, %f)" % (self.Ps.x, self.Ps.y, self.Pe.x,
                                         self.Pe.y)

    def tr(self, string_to_translate):
        """
        Translate a string using the QCoreApplication translation framework
        @param string_to_translate: a unicode string
        @return: the translated unicode string if it was possible to translate
        """
        return text_type(
            QtCore.QCoreApplication.translate('ArcGeo', string_to_translate))

    def trim(self, Point, dir=1, rev_norm=False):
        """
        This instance is used to trim the geometry at the given point. The point
        can be a point on the offset geometry a perpendicular point on line will
        be used for trimming.
        @param Point: The point / perpendicular point for new Geometry
        @param dir: The direction in which the geometry will be kept (1  means the
        beginn will be trimmed)
        @param rev_norm: If the direction of the point is on the reversed side.
        """

        # logger.debug("I'm getting trimmed: %s, %s, %s, %s" % (self, Point, dir, rev_norm))
        newPoint = self.O.get_arc_point(self.O.norm_angle(Point), r=self.r)
        new_normal = newPoint.unit_vector(self.O, r=1)

        # logger.debug(newPoint)
        [Arc1, Arc2] = self.split_into_2geos(newPoint)

        if dir == -1:
            new_arc = Arc1
            if hasattr(self, "end_normal"):
                # new_arc.end_normal = self.end_normal
                # new_arc.start_normal = new_normal

                new_arc.end_normal = new_normal
                new_arc.start_normal = self.start_normal
            # logger.debug(new_arc)
            return new_arc
        else:
            new_arc = Arc2
            if hasattr(self, "end_normal"):
                # new_arc.end_normal = new_normal
                # new_arc.start_normal = self.start_normal

                new_arc.end_normal = self.end_normal
                new_arc.start_normal = new_normal
            # logger.debug(new_arc)
            return new_arc
        # return self

    def update_start_end_points(self, start_point, value):
        prv_dir = self.ext
        if start_point:
            self.Ps = value
            self.s_ang = self.O.norm_angle(self.Ps)
        else:
            self.Pe = value
            self.e_ang = self.O.norm_angle(self.Pe)

        self.ext = self.dif_ang(self.Ps, self.Pe, self.ext)

        if 2 * abs(((prv_dir - self.ext) + pi) % (2 * pi) - pi) >= pi:
            # seems very unlikely that this is what you want - the direction changed (too drastically)
            self.Ps, self.Pe = self.Pe, self.Ps
            self.s_ang, self.e_ang = self.e_ang, self.s_ang
            self.ext = self.dif_ang(self.Ps, self.Pe, prv_dir)

        self.length = self.r * abs(self.ext)

    def wrap(self, angle, isend=0):
        """
        Wrapes the given angle into a range between 0 and 2pi
        @param angle: The angle to be wraped
        @param isend: If the angle is the end angle or start angle, this makes a
        difference at 0 or 2pi.
        @return: Returns the angle between 0 and 2 *pi
        """
        wrap_angle = angle % (2 * pi)
        if isend and wrap_angle == 0.0:
            wrap_angle += 2 * pi
        elif wrap_angle == 2 * pi:
            wrap_angle -= 2 * pi

        return wrap_angle

    def Write_GCode(self, PostPro=None):
        """
        Writes the GCODE for an Arc.
        @param PostPro: The PostProcessor instance to be used
        @return: Returns the string to be written to a file.
        """
        Ps, s_ang = self.get_start_end_points(True, True)
        Pe, e_ang = self.get_start_end_points(False, True)

        O = self.O
        r = self.r
        IJ = O - Ps

        # If the radius of the element is bigger than the max, radius export the element as an line.
        if r > PostPro.vars.General["max_arc_radius"]:
            string = PostPro.lin_pol_xy(Ps, Pe)
        else:
            if self.ext > 0:
                string = PostPro.lin_pol_arc("ccw", Ps, Pe, s_ang, e_ang, r, O,
                                             IJ, self.ext)
            elif self.ext < 0 and PostPro.vars.General["export_ccw_arcs_only"]:
                string = PostPro.lin_pol_arc("ccw", Pe, Ps, e_ang, s_ang, r, O,
                                             O - Pe, self.ext)
            else:
                string = PostPro.lin_pol_arc("cw", Ps, Pe, s_ang, e_ang, r, O,
                                             IJ, self.ext)
        return string
Esempio n. 12
0
class LineGeo(object):
    """
    Standard Geometry Item used for DXF Import of all geometries, plotting and
    G-Code export.
    """
    def __init__(self, Ps, Pe):
        """
        Standard Method to initialize the LineGeo.
        @param Ps: The Start Point of the line
        @param Pe: the End Point of the line
        """
        self.Ps = Ps
        self.Pe = Pe
        self.length = self.Ps.distance(self.Pe)

        self.calc_bounding_box()

        self.abs_geo = None

    def __deepcopy__(self, memo):
        return LineGeo(deepcopy(self.Ps, memo),
                       deepcopy(self.Pe, memo))

    def __str__(self):
        """
        Standard method to print the object
        @return: A string
        """
        return ("\nLineGeo(Ps=Point(x=%s ,y=%s),\n" % (self.Ps.x, self.Ps.y)) + \
               ("Pe=Point(x=%s, y=%s))" % (self.Pe.x, self.Pe.y))

    def save_v1(self):
        return "\nLineGeo" +\
               "\nPs:     %s" % self.Ps.save_v1() +\
               "\nPe:     %s" % self.Pe.save_v1() +\
               "\nlength: %0.5f" % self.length

    def calc_bounding_box(self):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        """
        Ps = Point(x=min(self.Ps.x, self.Pe.x), y=min(self.Ps.y, self.Pe.y))
        Pe = Point(x=max(self.Ps.x, self.Pe.x), y=max(self.Ps.y, self.Pe.y))

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)

    def colinear(self, other):
        """
        Check if two lines with same point self.Pe==other.Ps are colinear. For Point
        it check if the point is colinear with the line self.
        @param other: the possibly colinear line
        """
        if isinstance(other, LineGeo):
            return ((self.Ps.ccw(self.Pe, other.Pe) == 0) and
                    (self.Ps.ccw(self.Pe, other.Ps) == 0))
        elif isinstance(other, Point):
            """
            Return true iff a, b, and c all lie on the same line."
            """
            return self.Ps.ccw(self.Pe, other) == 0
        else:
            logger.debug("Unsupported instance: %s" % type(other))

    def colinearoverlapping(self, other):
        """
        Check if the lines are colinear overlapping
        Ensure A<B, C<D, and A<=C (which you can do by simple swapping). Then:
        •if B<C, the segments are disjoint
        •if B=C, then the intersection is the single point B=C
        •if B>C, then the intersection is the segment [C, min(B, D)]
        @param other: The other line
        @return: True if they are overlapping
        """
        if not(self.colinear(other)):
            return False
        else:
            if self.Ps < self.Pe:
                A = self.Ps
                B = self.Pe
            else:
                A = self.Pe
                B = self.Ps
            if other.Ps < self.Pe:
                C = other.Ps
                D = other.Pe
            else:
                C = other.Pe
                D = other.Ps

            # Swap lines if required
            if not(A <= C):
                A, B, C, D = C, D, A, B

        if B < C:
            return False
        elif B == C:
            return False
        else:
            return True

    def colinearconnected(self, other):
        """
        Check if Lines are connected and colinear
        @param other: Another Line which will be checked
        """

        if not(self.colinear(other)):
            return False
        elif self.Ps == other.Ps:
            return True
        elif self.Pe == other.Ps:
            return True
        elif self.Ps == other.Pe:
            return True
        elif self.Pe == other.Pe:
            return True
        else:
            return False

    def distance(self, other=[]):
        """
        Find the distance between 2 geometry elements. Possible is CCLineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        from core.arcgeo import ArcGeo

        if isinstance(other, LineGeo):
            return self.distance_l_l(other)
        elif isinstance(other, Point):
            return self.distance_l_p(other)
        elif isinstance(other, ArcGeo):
            return self.distance_l_a(other)
        else:
            logger.error(self.tr("Unsupported geometry type: %s" % type(other)))

    def distance_l_l(self, other):
        """
        Find the distance between 2 ccLineGeo elements.
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """

        if self.intersect(other):
            return 0.0

        return min(self.distance_l_p(other.Ps),
                   self.distance_l_p(other.Pe),
                   other.distance_l_p(self.Ps),
                   other.distance_l_p(self.Pe))

    def distance_l_a(self, other):
        """
        Find the distance between 2 ccLineGeo elements.
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """

        if self.intersect(other):
            return 0.0

        # Get the nearest Point to the Center of the Arc
        POnearest = self.get_nearest_point_l_p(other.O)

        # The Line is outside of the Arc
        if other.O.distance(POnearest) > other.r:
            # If the Nearest Point is on Arc Segement it is the neares one.
            # logger.debug("Nearest Point is outside of arc")
            if other.PointAng_withinArc(POnearest):
                return POnearest.distance(other.O.get_arc_point(other.O.norm_angle(POnearest), r=other.r))
            elif self.distance(other.Ps) < self.distance(other.Pe):
                    return self.get_nearest_point(other.Ps).distance(other.Ps)
            else:
                    return self.get_nearest_point(other.Pe).distance(other.Pe)

        # logger.debug("Nearest Point is Inside of arc")
        # logger.debug("self.distance(other.Ps): %s, self.distance(other.Pe): %s" %(self.distance(other.Ps),self.distance(other.Pe)))
        # The Line may be inside of the ARc or cross it
        if self.distance(other.Ps) < self.distance(other.Pe):
            dis_min = self.distance(other.Ps)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
        else:
            dis_min = self.distance(other.Pe)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        if ((other.PointAng_withinArc(self.Ps)) and
            abs(other.r - other.O.distance(self.Ps)) < dis_min):
            dis_min = abs(other.r - other.O.distance(self.Ps))
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        if ((other.PointAng_withinArc(self.Pe)) and
            abs((other.r - other.O.distance(self.Pe))) < dis_min):
            dis_min = abs(other.r - other.O.distance(self.Pe))
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        return dis_min

    def distance_l_p(self, Point):
        """
        Find the shortest distance between CCLineGeo and Point elements.
        Algorithm acc. to
        http://notejot.com/2008/09/distance-from-Point-to-line-segment-in-2d/
        http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm
        @param Point: the Point
        @return: The shortest distance between the Point and Line
        """
        d = self.Pe - self.Ps
        v = Point - self.Ps

        t = d.dotProd(v)

        if t <= 0:
            # our Point is lying "behind" the segment
            # so end Point 1 is closest to Point and distance is length of
            # vector from end Point 1 to Point.
            return self.Ps.distance(Point)
        elif t >= d.dotProd(d):
            # our Point is lying "ahead" of the segment
            # so end Point 2 is closest to Point and distance is length of
            # vector from end Point 2 to Point.
            return self.Pe.distance(Point)
        else:
            # our Point is lying "inside" the segment
            # i.e.:a perpendicular from it to the line that contains the line
            # segment has an end Point inside the segment
            # logger.debug(v.dotProd(v))
            # logger.debug(d.dotProd(d))
            # logger.debug(v.dotProd(v) - (t*t)/d.dotProd(d))
            #logger.debug(d.dotProd(d))
            #logger.debug(v.dotProd(v) - (t * t) / d.dotProd(d))
            #logger.debug(eps)
            #logger.debug((v.dotProd(v) - (t * t) / d.dotProd(d))< eps)
            #logger.debug(((v.dotProd(v) - (t * t) / d.dotProd(d))< eps))

            if (v.dotProd(v) - (t * t) / d.dotProd(d)) < eps:
                return 0.0
            else:
                return sqrt(v.dotProd(v) - (t * t) / d.dotProd(d));

    def find_inter_point(self, other, type='TIP'):
        """
        Find the intersection between 2 Geo elements. There can be only one
        intersection between 2 lines. Returns also FIP which lay on the ray.
        @param other: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)
        @return: a list of intersection points.
        """
        from core.arcgeo import ArcGeo

        if isinstance(other, LineGeo):
            inter = self.find_inter_point_l_l(other, type)
            return inter
        elif isinstance(other, ArcGeo):
            inter = self.find_inter_point_l_a(other, type)
            return inter
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def find_inter_point_l_l(self, other, type="TIP"):
        """
        Find the intersection between 2 LineGeo elements. There can be only one
        intersection between 2 lines. Returns also FIP which lay on the ray.
        @param other: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)
        @return: a list of intersection points.
        """

        if self.colinear(other):
            return None

        elif type == 'TIP' and not(self.intersect(other)):

            return None


        dx1 = self.Pe.x - self.Ps.x
        dy1 = self.Pe.y - self.Ps.y

        dx2 = other.Pe.x - other.Ps.x
        dy2 = other.Pe.y - other.Ps.y

        dax = self.Ps.x - other.Ps.x
        day = self.Ps.y - other.Ps.y

        # Return nothing if one of the lines has zero length
        if (dx1 == 0 and dy1 == 0) or (dx2 == 0 and dy2 == 0):
            return None

        # If to avoid division by zero.
        try:
            if(abs(dx2) >= abs(dy2)):
                v1 = (day - dax * dy2 / dx2) / (dx1 * dy2 / dx2 - dy1)
                v2 = (dax + v1 * dx1) / dx2
            else:
                v1 = (dax - day * dx2 / dy2) / (dy1 * dx2 / dy2 - dx1)
                v2 = (day + v1 * dy1) / dy2
        except:
            return None

        return Point(x=self.Ps.x + v1 * dx1,
                          y=self.Ps.y + v1 * dy1)

    def find_inter_point_l_a(self, Arc, type="TIP"):
        """
        Find the intersection between 2 Geo elements. The intersection
        between a Line and a Arc is checked here. This function is also used
        in the Arc Class to check Arc -> Line Intersection (the other way around)
        @param Arc: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)
        @return: a list of intersection points.
        @todo: FIXME: The type of the intersection is not implemented up to now
        """

        Ldx = self.Pe.x - self.Ps.x
        Ldy = self.Pe.y - self.Ps.y

        # Mitternachtsformel zum berechnen der Nullpunkte der quadratischen
        # Gleichung
        a = pow(Ldx, 2) + pow(Ldy, 2)
        b = 2 * Ldx * (self.Ps.x - Arc.O.x) + 2 * Ldy * (self.Ps.y - Arc.O.y)
        c = pow(self.Ps.x - Arc.O.x, 2) + pow(self.Ps.y - Arc.O.y, 2) - pow(Arc.r, 2)
        root = pow(b, 2) - 4 * a * c

        # If the value under the sqrt is negative there is no intersection.
        if root < 0 or a == 0.0:
            return None

        v1 = (-b + sqrt(root)) / (2 * a)
        v2 = (-b - sqrt(root)) / (2 * a)

        Pi1 = Point(x=self.Ps.x + v1 * Ldx,
                       y=self.Ps.y + v1 * Ldy)

        Pi2 = Point(x=self.Ps.x + v2 * Ldx,
               y=self.Ps.y + v2 * Ldy)

        Pi1.v = Arc.dif_ang(Arc.Ps, Pi1, Arc.ext) / Arc.ext
        Pi2.v = Arc.dif_ang(Arc.Ps, Pi2, Arc.ext) / Arc.ext

        if type == 'TIP':
            if ((Pi1.v >= 0.0 and Pi1.v <= 1.0 and self.intersect(Pi1)) and
               (Pi1.v >= 0.0 and Pi2.v <= 1.0 and self.intersect(Pi2))):
                if (root == 0):
                    return Pi1
                else:
                    return [Pi1, Pi2]
            elif (Pi1.v >= 0.0 and Pi1.v <= 1.0 and self.intersect(Pi1)):
                return Pi1
            elif  (Pi1.v >= 0.0 and Pi2.v <= 1.0 and self.intersect(Pi2)):
                return Pi2
            else:
                return None
        elif type == "Ray":
            # If the root is zero only one solution and the line is a tangent.
            if(root == 0):
                return Pi1

            return [Pi1, Pi2]
        else:
            logger.error("We should not be here")

    def get_nearest_point(self, other):
        """
        Get the nearest point on a line to another line lieing on the line
        @param other: The Line to be nearest to
        @return: The point which is the nearest to other
        """
        from core.arcgeo import ArcGeo

        if isinstance(other, LineGeo):
            return self.get_nearest_point_l_l(other)
        elif isinstance(other, ArcGeo):
            return self.get_nearest_point_l_a(other)
        elif isinstance(other, Point):
            return self.get_nearest_point_l_p(other)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def get_nearest_point_l_l(self, other):
        """
        Get the nearest point on a line to another line lieing on the line
        @param other: The Line to be nearest to
        @return: The point which is the nearest to other
        """
        # logger.debug(self.intersect(other))
        if self.intersect(other):
            return self.find_inter_point_l_l(other)
        min_dis = self.distance(other)
        if min_dis == self.distance_l_p(other.Ps):
            return self.get_nearest_point_l_p(other.Ps)
        elif min_dis == self.distance_l_p(other.Pe):
            return self.get_nearest_point_l_p(other.Pe)
        elif min_dis == other.distance_l_p(self.Ps):
            return self.Ps
        elif min_dis == other.distance_l_p(self.Pe):
            return self.Pe
        else:
            logger.warning("No solution found")

    def get_nearest_point_l_a(self, other, ret="line"):
        """
        Get the nearest point to a line lieing on the line
        @param other: The Point to be nearest to
        @return: The point which is the nearest to other
        """
        if self.intersect(other):
            return self.find_inter_point_l_a(other)

        # Get the nearest Point to the Center of the Arc
        POnearest = self.get_nearest_point_l_p(other.O)

        # The Line is outside of the Arc
        if other.O.distance(POnearest) > other.r:
            # If the Nearest Point is on Arc Segement it is the neares one.
            # logger.debug("Nearest Point is outside of arc")
            if other.PointAng_withinArc(POnearest):
                if ret == "line":
                    return POnearest
                elif ret == "arc":
                    return other.O.get_arc_point(other.O.norm_angle(POnearest), r=other.r)
            elif self.distance(other.Ps) < self.distance(other.Pe):
                if ret == "line":
                    return self.get_nearest_point(other.Ps)
                elif ret == "arc":
                    return other.Ps
            else:
                if ret == "line":
                    return self.get_nearest_point(other.Pe)
                elif ret == "art":
                    return other.Pe

        # logger.debug("Nearest Point is Inside of arc")
        # logger.debug("self.distance(other.Ps): %s, self.distance(other.Pe): %s" %(self.distance(other.Ps),self.distance(other.Pe)))
        # The Line may be inside of the ARc or cross it
        if self.distance(other.Ps) < self.distance(other.Pe):
            Pnearest = self.get_nearest_point(other.Ps)
            Pnother = other.Ps
            dis_min = self.distance(other.Ps)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
        else:
            Pnearest = self.get_nearest_point(other.Pe)
            Pnother = other.Pe
            dis_min = self.distance(other.Pe)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        if ((other.PointAng_withinArc(self.Ps)) and
            abs(other.r - other.O.distance(self.Ps)) < dis_min):

            Pnearest = self.Ps
            Pnother = other.O.get_arc_point(other.O.norm_angle(Pnearest), r=other.r)
            dis_min = abs(other.r - other.O.distance(self.Ps))
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        if ((other.PointAng_withinArc(self.Pe)) and
            abs((other.r - other.O.distance(self.Pe))) < dis_min):
            Pnearest = self.Pe
            Pnother = other.O.get_arc_point(other.O.norm_angle(Pnearest), r=other.r)

            dis_min = abs(other.r - other.O.distance(self.Pe))
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
        if ret == "line":
            return Pnearest
        elif ret == "arc":
            return Pnother

    def get_nearest_point_l_p(self, other):
        """
        Get the nearest point to a point lieing on the line
        @param other: The Point to be nearest to
        @return: The point which is the nearest to other
        """
        if self.intersect(other):
            return other

        PPoint = self.perpedicular_on_line(other)

        if self.intersect(PPoint):
            return PPoint

        if self.Ps.distance(other) < self.Pe.distance(other):
            return self.Ps
        else:
            return self.Pe

    def get_start_end_points(self, start_point, angles=None):
        if start_point:
            if angles is None:
                return self.Ps
            elif angles:
                return self.Ps, self.Ps.norm_angle(self.Pe)
            else:
                return self.Ps, (self.Pe - self.Ps).unit_vector()
        else:
            if angles is None:
                return self.Pe
            elif angles:
                return self.Pe, self.Pe.norm_angle(self.Ps)
            else:
                return self.Pe, (self.Pe - self.Ps).unit_vector()

    def intersect(self, other):
        """
        Check if there is an intersection of two geometry elements
        @param, a second geometry which shall be checked for intersection
        @return: True if there is an intersection
        """
        # Do a raw check first with BoundingBox
        # logger.debug("self: %s, \nother: %s, \nintersect: %s" %(self,other,self.BB.hasintersection(other.BB)))
        # logger.debug("self.BB: %s \nother.BB: %s")
        # logger.debug(self.BB.hasintersection(other.BB))
        # We need to test Point first cause it has no BB
        from core.arcgeo import ArcGeo

        if isinstance(other, Point):
            return self.intersect_l_p(other)
        elif not(self.BB.hasintersection(other.BB)):
            return False
        elif isinstance(other, LineGeo):
            return self.intersect_l_l(other)
        elif isinstance(other, ArcGeo):
            return self.intersect_l_a(other)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def intersect_l_a(self, other):
        """
        Check if there is an intersection of the two line
        @param, a second line which shall be checked for intersection
        @return: True if there is an intersection
        """
        inter = self.find_inter_point_l_a(other)
        return not(inter is None)

    def intersect_l_l(self, other):
        """
        Check if there is an intersection of the two line
        @param, a second line which shall be checked for intersection
        @return: True if there is an intersection
        """
        A = self.Ps
        B = self.Pe
        C = other.Ps
        D = other.Pe
        return A.ccw(C, D) != B.ccw(C, D) and A.ccw(B, C) != A.ccw(B, D)

    def intersect_l_p(self, Point):
        """
        Check if Point is colinear and within the Line
        @param Point: A Point which will be checked
        @return: True if point Point intersects the line segment from Ps to Pe.
        Refer to http://stackoverflow.com/questions/328107/how-can-you-determine-a-point-is-between-two-other-points-on-a-line-segment
        """
        # (or the degenerate case that all 3 points are coincident)
        # logger.debug(self.colinear(Point))
        return (self.colinear(Point)
                and (self.within(self.Ps.x, Point.x, self.Pe.x)
                     if self.Ps.x != self.Pe.x else
                     self.within(self.Ps.y, Point.y, self.Pe.y)))

    def isHit(self, caller, xy, tol):
        return xy.distance2_to_line(self.Ps, self.Pe) <= tol**2

    def within(self, p, q, r):
        "Return true iff q is between p and r (inclusive)."
        return p <= q <= r or r <= q <= p

    def join_colinear_line(self, other):
        """
        Check if the two lines are colinear connected or inside of each other, in
        this case these lines will be joined to one common line, otherwise return
        both lines
        @param other: a second line
        @return: Return one or two lines
        """
        if self.colinearconnected(other)or self.colinearoverlapping(other):
            if self.Ps < self.Pe:
                newPs = min(self.Ps, other.Ps, other.Pe)
                newPe = max(self.Pe, other.Ps, other.Pe)
            else:
                newPs = max(self.Ps, other.Ps, other.Pe)
                newPe = min(self.Pe, other.Ps, other.Pe)
            return [LineGeo(newPs, newPe)]
        else:
            return [self, other]

    def make_abs_geo(self, parent=None):
        """
        Generates the absolute geometry based on itself and the parent. This
        is done for rotating and scaling purposes
        """
        Ps = self.Ps.rot_sca_abs(parent=parent)
        Pe = self.Pe.rot_sca_abs(parent=parent)

        self.abs_geo = LineGeo(Ps=Ps, Pe=Pe)

    def make_path(self, caller, drawHorLine):
        drawHorLine(caller, self.Ps, self.Pe)

    def perpedicular_on_line(self, other):
        """
        This function calculates the perpendicular point on a line (or ray of line)
        with the shortest distance to the point given with other
        @param other: The point to be perpendicular to
        @return: A point which is on line and perpendicular to Point other
        @see: http://stackoverflow.com/questions/1811549/perpendicular-on-a-line-from-a-given-point
        """
        # first convert line to normalized unit vector
        unit_vector = self.Ps.unit_vector(self.Pe)

        # translate the point and get the dot product
        lam = ((unit_vector.x * (other.x - self.Ps.x))
                + (unit_vector.y * (other.y - self.Ps.y)))
        return Point(x=(unit_vector.x * lam) + self.Ps.x,
                     y=(unit_vector.y * lam) + self.Ps.y)

    def reverse(self):
        """
        Reverses the direction of the arc (switch direction).
        """
        self.Ps, self.Pe = self.Pe, self.Ps
        if self.abs_geo:
            self.abs_geo.reverse()

    def split_into_2geos(self, ipoint=Point()):
        """
        Splits the given geometry into 2 not self intersection geometries. The
        geometry will be splitted between ipoint and Pe.
        @param ipoint: The Point where the intersection occures
        @return: A list of 2 CCLineGeo's will be returned if intersection is inbetween
        """
        # The Point where the geo shall be splitted
        if not(ipoint):
            return [self]
        elif self.intersect(ipoint):
            Li1 = LineGeo(Ps=self.Ps, Pe=ipoint)
            Li2 = LineGeo(Ps=ipoint, Pe=self.Pe)
            return [Li1, Li2]
        else:
            return [self]

    def to_short_string(self):
        return "(%f, %f) -> (%f, %f)" % (self.Ps.x, self.Ps.y, self.Pe.x, self.Pe.y)

    def trim(self, Point, dir=1, rev_norm=False):
        """
        This instance is used to trim the geometry at the given point. The point
        can be a point on the offset geometry a perpendicular point on line will
        be used for trimming.
        @param Point: The point / perpendicular point for new Geometry
        @param dir: The direction in which the geometry will be kept (1  means the
        being will be trimmed)
        """
        newPoint = self.perpedicular_on_line(Point)
        if dir == 1:
            new_line = LineGeo(newPoint, self.Pe)
            new_line.end_normal = self.end_normal
            new_line.start_normal = self.start_normal
            return new_line
        else:
            new_line = LineGeo(self.Ps, newPoint)
            new_line.end_normal = self.end_normal
            new_line.start_normal = self.start_normal
            return new_line

    def update_start_end_points(self, start_point, value):
        prv_ang = self.Ps.norm_angle(self.Pe)
        if start_point:
            self.Ps = value
        else:
            self.Pe = value
        new_ang = self.Ps.norm_angle(self.Pe)

        if 2 * abs(((prv_ang - new_ang) + pi) % (2 * pi) - pi) >= pi:
            # seems very unlikely that this is what you want - the direction changed (too drastically)
            self.Ps, self.Pe = self.Pe, self.Ps

        self.length = self.Ps.distance(self.Pe)

    def Write_GCode(self, PostPro):
        """
        Writes the GCODE for a Line.
        @param PostPro: The PostProcessor instance to be used
        @return: Returns the string to be written to a file.
        """
        Ps = self.get_start_end_points(True)
        Pe = self.get_start_end_points(False)
        return PostPro.lin_pol_xy(Ps, Pe)
Esempio n. 13
0
class ArcGeo(object):
    """
    Standard Geometry Item used for DXF Import of all geometries, plotting and
    G-Code export.
    """
    def __init__(self, Ps=None, Pe=None, O=None, r=1,
                 s_ang=None, e_ang=None, direction=1, drag=False):
        """
        Standard Method to initialize the ArcGeo. Not all of the parameters are
        required to fully define a arc. e.g. Ps and Pe may be given or s_ang and
        e_ang
        @param Ps: The Start Point of the arc
        @param Pe: the End Point of the arc
        @param O: The center of the arc
        @param r: The radius of the arc
        @param s_ang: The Start Angle of the arc
        @param e_ang: the End Angle of the arc
        @param direction: The arc direction where 1 is in positive direction
        """

        self.Ps = Ps
        self.Pe = Pe
        self.O = O
        self.r = abs(r)
        self.s_ang = s_ang
        self.e_ang = e_ang
        self.drag = drag

        # Get the Circle center point with known Start and End Points
        if self.O is None:

            if self.Ps is not None and\
               self.Pe is not None and\
               direction is not None:

                arc = self.Pe.norm_angle(self.Ps) - pi / 2
                m = self.Pe.distance(self.Ps) / 2

                if abs(self.r - m) < g.config.fitting_tolerance:
                    lo = 0.0
                else:
                    lo = sqrt(pow(self.r, 2) - pow(m, 2))

                d = -1 if direction < 0 else 1

                self.O = self.Ps + (self.Pe - self.Ps) / 2
                self.O.y += lo * sin(arc) * d
                self.O.x += lo * cos(arc) * d

            # Compute center point
            elif self.s_ang is not None:
                self.O.x = self.Ps.x - self.r * cos(self.s_ang)
                self.O.y = self.Ps.y - self.r * sin(self.s_ang)
            else:
                logger.error(self.tr("Missing value for Arc Geometry"))

        # Calculate start and end angles
        if self.s_ang is None:
            self.s_ang = self.O.norm_angle(self.Ps)
        if self.e_ang is None:
            self.e_ang = self.O.norm_angle(self.Pe)

        self.ext = self.dif_ang(self.Ps, self.Pe, direction)

        self.length = self.r * abs(self.ext)

        self.calc_bounding_box()


        self.abs_geo = None

    def __deepcopy__(self, memo):
        return ArcGeo(deepcopy(self.Ps, memo),
                      deepcopy(self.Pe, memo),
                      deepcopy(self.O, memo),
                      deepcopy(self.r, memo),
                      deepcopy(self.s_ang, memo),
                      deepcopy(self.e_ang, memo),
                      deepcopy(self.ext, memo))

    def __str__(self):
        """
        Standard method to print the object
        @return: A string
        """
        return ("\nArcGeo(Ps=Point(x=%s ,y=%s), \n" % (self.Ps.x, self.Ps.y)) + \
               ("Pe=Point(x=%s, y=%s),\n" % (self.Pe.x, self.Pe.y)) + \
               ("O=Point(x=%s, y=%s),\n" % (self.O.x, self.O.y)) + \
               ("s_ang=%s,e_ang=%s,\n" % (self.s_ang, self.e_ang)) + \
               ("r=%s, \n" % self.r) + \
               ("ext=%s)" % self.ext)

    def save_v1(self):
        return "\nArcGeo" +\
               "\nPs:  %s; s_ang: %0.5f" % (self.Ps.save_v1(), self.s_ang) +\
               "\nPe:  %s; e_ang: %0.5f" % (self.Pe.save_v1(), self.e_ang) +\
               "\nO:   %s; r: %0.3f" % (self.O.save_v1(), self.r) +\
               "\next: %0.5f; length: %0.5f" % (self.ext, self.length)

    def angle_between(self, min_ang, max_ang, angle):
        """
        Returns if the angle is in the range between 2 other angles
        @param min_ang: The starting angle
        @param parent: The end angel. Always in ccw direction from min_ang
        @return: True or False
        """
        if min_ang < 0.0:
            min_ang += 2 * pi

        while max_ang < min_ang:
            max_ang += 2 * pi

        while angle < min_ang:
            angle += 2 * pi

        return (min_ang < angle) and (angle <= max_ang)

    def calc_bounding_box(self):
        """
        Calculated the BoundingBox of the geometry and saves it into self.BB
        """

        Ps = Point(x=self.O.x - self.r, y=self.O.y - self.r)
        Pe = Point(x=self.O.x + self.r, y=self.O.y + self.r)

        # Do the calculation only for arcs have positiv extend => switch angles
        if self.ext >= 0:
            s_ang = self.s_ang
            e_ang = self.e_ang
        elif self.ext < 0:
            s_ang = self.e_ang
            e_ang = self.s_ang

        # If the positive X Axis is crossed
        if not(self.wrap(s_ang, 0) >= self.wrap(e_ang, 1)):
            Pe.x = max(self.Ps.x, self.Pe.x)

        # If the positive Y Axis is crossed
        if not(self.wrap(s_ang - pi / 2, 0) >= self.wrap(e_ang - pi / 2, 1)):
            Pe.y = max(self.Ps.y, self.Pe.y)

        # If the negative X Axis is crossed
        if not(self.wrap(s_ang - pi, 0) >= self.wrap(e_ang - pi, 1)):
            Ps.x = min(self.Ps.x, self.Pe.x)

        # If the negative Y is crossed
        if not(self.wrap(s_ang - 1.5 * pi, 0) >=
                self.wrap(e_ang - 1.5 * pi, 1)):
            Ps.y = min(self.Ps.y, self.Pe.y)

        self.BB = BoundingBox(Ps=Ps, Pe=Pe)

    def dif_ang(self, Ps, Pe, direction):
        """
        Calculated the angle between Pe and Ps with respect to the origin
        @param Ps: the start Point of the arc
        @param Pe: the end Point of the arc
        @param direction: the direction of the arc
        @return: Returns the angle between -2* pi and 2 *pi for the arc,
        0 excluded - we got a complete circle
        """
        dif_ang = (self.O.norm_angle(Pe) - self.O.norm_angle(Ps)) % (-2 * pi)

        if direction > 0:
            dif_ang += 2 * pi
        elif dif_ang == 0:
            dif_ang = -2 * pi

        return dif_ang

    def distance(self, other):
        """
        Find the distance between 2 geometry elements. Possible is LineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        """
        Find the distance between 2 geometry elements. Possible is Point, LineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        from core.linegeo import LineGeo

        if isinstance(other, LineGeo):
            return other.distance_l_a(self)
        elif isinstance(other, Point):
            return self.distance_a_p(other)
        elif isinstance(other, ArcGeo):
            return self.distance_a_a(other)
        else:
            logger.error(self.tr("Unsupported geometry type: %s" % type(other)))

    def distance_a_a(self, other):
        """
        Find the distance between two arcs
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        # logger.error('Unsupported function')
        Pself = self.get_nearest_point(other)
        Pother = other.get_nearest_point(self)
        return Pself.distance(Pother)

    def distance_a_p(self, other):
        """
        Find the distance between a arc and a point
        @param other: the instance of the 2nd geometry element.
        @return: The distance between the two geometries
        """
        # The Pont is outside of the Arc
        if self.O.distance(other) > self.r:
            # If the Nearest Point is on Arc Segement it is the neares one.
            # logger.debug("Nearest Point is outside of arc")
            if self.PointAng_withinArc(other):
                return other.distance(self.O.get_arc_point(self.O.norm_angle(other), r=self.r))
            elif other.distance(self.Ps) < other.distance(self.Pe):
                    return other.distance(self.Ps)
            else:
                    return other.distance(self.Pe)

        # logger.debug("Nearest Point is Inside of arc")
        # logger.debug("self.distance(other.Ps): %s, self.distance(other.Pe): %s" %(self.distance(other.Ps),self.distance(other.Pe)))
        # The Line may be inside of the ARc or cross it
        if other.distance(self.Ps) < other.distance(self.Pe):
            dis_min = other.distance(self.Ps)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
        else:
            dis_min = other.distance(self.Pe)
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        if ((self.PointAng_withinArc(other)) and
            abs(self.r - self.O.distance(other)) < dis_min):
            dis_min = abs(self.r - self.O.distance(other))
            # logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))

        return dis_min

    def find_inter_point(self, other=[], type='TIP'):
        """
        Find the intersection between 2 geometry elements. Possible is CCLineGeo
        and ArcGeo
        @param other: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)        @return: a list of intersection points.
        """
        from core.linegeo import LineGeo

        if isinstance(other, LineGeo):
            IPoints = other.find_inter_point_l_a(self, type)
            return IPoints
        elif isinstance(other, ArcGeo):
            return self.find_inter_point_a_a(other, type)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def find_inter_point_a_a(self, other, type='TIP'):
        """
        Find the intersection between 2 ArcGeo elements. There can be only one
        intersection between 2 lines.
        @param other: the instance of the 2nd geometry element.
        @param type: Can be "TIP" for True Intersection Point or "Ray" for
        Intersection which is in Ray (of Line)
        @return: a list of intersection points.
        @todo: FIXME: The type of the intersection is not implemented up to now
        """
        O_dis = self.O.distance(other.O)

        # If self circle is surrounded by the other no intersection
        if(O_dis < abs(self.r - other.r)):
            return None

        # If other circle is surrounded by the self no intersection
        if(O_dis < abs(other.r - self.r)):
            return None

        # If The circles are to far away from each other no intersection possible
        if (O_dis > abs(other.r + self.r)):
            return None

        # If both circles have the same center and radius
        if abs(O_dis) == 0.0 and abs(self.r - other.r) == 0.0:
            Pi1 = Point(x=self.Ps.x, y=self.Ps.y)
            Pi2 = Point(x=self.Pe.x, y=self.Pe.y)

            return [Pi1, Pi2]
        # The following algorithm was found on :
        # http://www.sonoma.edu/users/w/wilsonst/Papers/Geometry/circles/default.htm

        root = ((pow(self.r + other.r , 2) - pow(O_dis, 2)) *
                  (pow(O_dis, 2) - pow(other.r - self.r, 2)))

        # If the Line is a tangent the root is 0.0.
        if root <= 0.0:
            root = 0.0
        else:
            root = sqrt(root)

        xbase = (other.O.x + self.O.x) / 2 + \
        (other.O.x - self.O.x) * \
        (pow(self.r, 2) - pow(other.r, 2)) / (2 * pow(O_dis, 2))

        ybase = (other.O.y + self.O.y) / 2 + \
        (other.O.y - self.O.y) * \
        (pow(self.r, 2) - pow(other.r, 2)) / (2 * pow(O_dis, 2))

        Pi1 = Point(x=xbase + (other.O.y - self.O.y) / \
                          (2 * pow(O_dis, 2)) * root,
                    y=ybase - (other.O.x - self.O.x) / \
                    (2 * pow(O_dis, 2)) * root)

        Pi1.v1 = self.dif_ang(self.Ps, Pi1, self.ext) / self.ext
        Pi1.v2 = other.dif_ang(other.Ps, Pi1, other.ext) / other.ext

        Pi2 = Point(x=xbase - (other.O.y - self.O.y) / \
                         (2 * pow(O_dis, 2)) * root,
                    y=ybase + (other.O.x - self.O.x) / \
                    (2 * pow(O_dis, 2)) * root)

        Pi2.v1 = self.dif_ang(self.Ps, Pi2, self.ext) / self.ext
        Pi2.v2 = other.dif_ang(other.Ps, Pi2, other.ext) / other.ext


        if type == 'TIP':
            if ((Pi1.v1 >= 0.0 and Pi1.v1 <= 1.0 and Pi1.v2 > 0.0 and Pi1.v2 <= 1.0) and
               (Pi2.v1 >= 0.0 and Pi2.v1 <= 1.0 and Pi2.v2 > 0.0 and Pi2.v2 <= 1.0)):
                if (root == 0):
                    return Pi1
                else:
                    return [Pi1, Pi2]
            elif (Pi1.v1 >= 0.0 and Pi1.v1 <= 1.0 and Pi1.v2 > 0.0 and Pi1.v2 <= 1.0):
                return Pi1
            elif  (Pi2.v1 >= 0.0 and Pi2.v1 <= 1.0 and Pi2.v2 > 0.0 and Pi2.v2 <= 1.0):
                return Pi2
            else:
                return None
        elif type == "Ray":
            # If the root is zero only one solution and the line is a tangent.
            if root == 0:
                return Pi1
            else:
                return [Pi1, Pi2]
        else:
            logger.error("We should not be here")

    def get_nearest_point(self, other):
        """
        Get the nearest point on the arc to another geometry.
        @param other: The Line to be nearest to
        @return: The point which is the nearest to other
        """
        from core.linegeo import LineGeo

        if isinstance(other, LineGeo):
            return other.get_nearest_point_l_a(self, ret="arc")
        elif isinstance(other, ArcGeo):
            return self.get_nearest_point_a_a(other)
        elif isinstance(other, Point):
            return self.get_nearest_point_a_p(other)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def get_nearest_point_a_p(self, other):
        """
        Get the nearest point to a point lieing on the arc
        @param other: The Point to be nearest to
        @return: The point which is the nearest to other
        """
        if self.intersect(other):
            return other

        PPoint = self.O.get_arc_point(self.O.norm_angle(other), r=self.r)
        if self.intersect(PPoint):
            return PPoint
        elif self.Ps.distance(other) < self.Pe.distance(other):
            return self.Ps
        else:
            return self.Pe

    def get_nearest_point_a_a(self, other, ret="self"):
        """
        Get the nearest point to a line lieing on the line
        @param other: The Point to be nearest to
        @return: The point which is the nearest to other
        """
        if self.intersect(other):
            return self.find_inter_point_a_a(other)



        # The Arc is outside of the Arc
        # if other.O.distance(self.O)>(other.r+other.r):

        # If Nearest point is on both Arc Segments.
        if other.PointAng_withinArc(self.O) and self.PointAng_withinArc(other.O):
            if ret == "self":
                return self.O.get_arc_point(self.O.norm_angle(other.O), r=self.r)
            elif ret == "other":
                return other.O.get_arc_point(other.O.norm_angle(self.O), r=other.r)
        # If Nearest point is on self Arc Segment but not other
        elif self.PointAng_withinArc(other.O):
            if self.distance(other.Ps) < self.distance(other.Pe):
                if ret == "self":
                    return self.O.get_arc_point(self.O.norm_angle(other.Ps), r=self.r)
                elif ret == "other":
                    return other.Ps
            else:
                if ret == "self":
                    return self.O.get_arc_point(self.O.norm_angle(other.Pe), r=self.r)
                elif ret == "other":
                    return other.Pe
        # If Nearest point is on other Arc Segment but not self
        elif other.PointAng_withinArc(self.O):
            if other.distance(self.Ps) < other.distance(self.Pe):
                if ret == "self":
                    return self.Ps
                elif ret == "other":
                    return other.O.get_arc_point(other.O.norm_angle(self.Ps), r=other.r)
            else:
                if ret == "self":
                    return self.Pe
                elif ret == "other":
                    return other.O.get_arc_point(other.O.norm_angle(self.Pe), r=other.r)
        # If the min distance is not on any arc segemtn but other.Ps is nearer then other.Pe
        elif self.distance(other.Ps) < self.distance(other.Pe):
            if self.Ps.distance(other.Ps) < self.Pe.distance(other.Ps):
                if ret == "self":
                    return self.Ps
                elif ret == "other":
                    return other.Ps
            else:
                if ret == "self":
                    return self.Pe
                elif ret == "other":
                    return other.Ps
        else:
            if self.Ps.distance(other.Pe) < self.Pe.distance(other.Pe):
                if ret == "self":
                    return self.Ps
                elif ret == "other":
                    return other.Pe
            else:
                if ret == "self":
                    return self.Pe
                elif ret == "other":
                    return other.Pe

            #         #logger.debug("Nearest Point is Inside of arc")
            #         #logger.debug("self.distance(other.Ps): %s, self.distance(other.Pe): %s" %(self.distance(other.Ps),self.distance(other.Pe)))
            #         # The Line may be inside of the ARc or cross it
            #         if self.distance(other.Ps)<self.distance(other.Pe):
            #             Pnearest=self.get_nearest_point(other.Ps)
            #             Pnother=other.Ps
            #             dis_min=self.distance(other.Ps)
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #         else:
            #             Pnearest=self.get_nearest_point(other.Pe)
            #             Pnother=other.Pe
            #             dis_min=self.distance(other.Pe)
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #
            #         if ((other.PointAng_withinArc(self.Ps)) and
            #             abs(other.r-other.O.distance(self.Ps)) < dis_min):
            #
            #             Pnearest=self.Ps
            #             Pnother=other.O.get_arc_point(other.O.norm_angle(Pnearest),r=other.r)
            #             dis_min=abs(other.r-other.O.distance(self.Ps))
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #
            #         if ((other.PointAng_withinArc(self.Pe)) and
            #             abs((other.r-other.O.distance(self.Pe))) < dis_min):
            #             Pnearest=self.Pe
            #             Pnother=other.O.get_arc_point(other.O.norm_angle(Pnearest),r=other.r)
            #
            #             dis_min=abs(other.r-other.O.distance(self.Pe))
            #             #logger.debug("Pnearest: %s, distance: %s" %(Pnearest, dis_min))
            #         if ret=="line":
            #             return Pnearest
            #         elif ret=="arc":
            #             return Pnother

    def get_point_from_start(self, i, segments):
        ang = self.s_ang + i * self.ext / segments
        return self.O.get_arc_point(ang, self.r)

    def get_start_end_points(self, start_point, angles=None):
        if start_point:
            if angles is None:
                return self.Ps
            elif angles:
                return self.Ps, self.s_ang + pi/2 * self.ext / abs(self.ext)
            else:
                direction = (self.O - self.Ps).unit_vector()
                direction = -direction if self.ext >= 0 else direction
                return self.Ps, Point(-direction.y, direction.x)
        else:
            if angles is None:
                return self.Pe
            elif angles:
                return self.Pe, self.e_ang - pi/2 * self.ext / abs(self.ext)
            else:
                direction = (self.O - self.Pe).unit_vector()
                direction = -direction if self.ext >= 0 else direction
                return self.Pe, Point(-direction.y, direction.x)

    def intersect(self, other):
        """
        Check if there is an intersection of two geometry elements
        @param, a second geometry which shall be checked for intersection
        @return: True if there is an intersection
        """
        # Do a raw check first with BoundingBox
        # logger.debug("self: %s, \nother: %s, \nintersect: %s" %(self,other,self.BB.hasintersection(other.BB)))
        # logger.debug("self.BB: %s \nother.BB: %s")

        # We need to test Point first cause it has no BB
        from core.linegeo import LineGeo

        if isinstance(other, Point):
            return self.intersect_a_p(other)
        elif not(self.BB.hasintersection(other.BB)):
            return False
        elif isinstance(other, LineGeo):
            return other.intersect_l_a(self)
        elif isinstance(other, ArcGeo):
            return self.intersect_a_a(other)
        else:
            logger.error("Unsupported Instance: %s" % other.type)

    def intersect_a_a(self, other):
        """
        Check if there is an intersection of two arcs
        @param, a second arc which shall be checked for intersection
        @return: True if there is an intersection
        """
        inter = self.find_inter_point_a_a(other)
        return not(inter is None)

    def intersect_a_p(self, other):
        """
        Check if there is an intersection of an point and a arc
        @param, a second arc which shall be checked for intersection
        @return: True if there is an intersection
        """
        # No intersection possible if point is not within radius
        if not(abs(self.O.distance(other) - self.r) < abs):
            return False
        elif self.PointAng_withinArc(other):
            return True
        else:
            return False

    def isHit(self, caller, xy, tol):
        tol2 = tol**2
        segments = int(abs(degrees(self.ext)) // 3 + 1)
        Ps = self.O.get_arc_point(self.s_ang, self.r)
        for i in range(1, segments + 1):
            Pe = self.get_point_from_start(i, segments)
            if xy.distance2_to_line(Ps, Pe) <= tol2:
                return True
            Ps = Pe
        return False

    def make_abs_geo(self, parent=None):
        """
        Generates the absolute geometry based on itself and the parent. This
        is done for rotating and scaling purposes
        """
        Ps = self.Ps.rot_sca_abs(parent=parent)
        Pe = self.Pe.rot_sca_abs(parent=parent)
        O = self.O.rot_sca_abs(parent=parent)
        r = self.scaled_r(self.r, parent)

        direction = 1 if self.ext > 0.0 else -1

        if parent is not None and parent.sca[0] * parent.sca[1] < 0.0:
            direction *= -1

        self.abs_geo = ArcGeo(Ps=Ps, Pe=Pe, O=O, r=r, direction=direction)

    def make_path(self, caller, drawHorLine):
        segments = int(abs(degrees(self.ext)) // 3 + 1)
        Ps = self.O.get_arc_point(self.s_ang, self.r)

        for i in range(1, segments + 1):
            Pe = self.get_point_from_start(i, segments)
            drawHorLine(caller, Ps, Pe)
            Ps = Pe

    def PointAng_withinArc(self, Point):
        """
        Check if the angle defined by Point is within the span of the arc.
        @param Point: The Point which angle to be checked
        @return: True or False
        """
        if self.ext == 0.0:
            return False

        v = self.dif_ang(self.Ps, Point, self.ext) / self.ext
        return v >= 0.0 and v <= 1.0

    def reverse(self):
        """
        Reverses the direction of the arc (switch direction).
        """
        self.Ps, self.Pe = self.Pe, self.Ps
        self.s_ang, self.e_ang = self.e_ang, self.s_ang
        self.ext = -self.ext
        if self.abs_geo:
            self.abs_geo.reverse()

    def scaled_r(self, r, parent):
        """
        Scales the radius based on the scale given in its parents. This is done
        recursively.
        @param r: The radius which shall be scaled
        @param parent: The parent Entity (Instance: EntityContentClass)
        @return: The scaled radius
        """
        # Rekursive Schleife falls mehrfach verschachtelt.
        # Recursive loop if nested.
        if parent is not None:
            r *= parent.sca[0]
            r = self.scaled_r(r, parent.parent)

        return r

    def split_into_2geos(self, ipoint=Point()):
        """
        Splits the given geometry into 2 geometries. The
        geometry will be splitted at ipoint.
        @param ipoint: The Point where the intersection occures
        @return: A list of 2 ArcGeo's will be returned.
        """


        # Generate the 2 geometries and their bounding boxes.
        Arc1 = ArcGeo(Ps=self.Ps, Pe=ipoint, r=self.r,
                       O=self.O, direction=self.ext)

        Arc2 = ArcGeo(Ps=ipoint, Pe=self.Pe, r=self.r,
                       O=self.O, direction=self.ext)
        return [Arc1, Arc2]

    def toShortString(self):
        return "(%f, %f) -> (%f, %f)" % (self.Ps.x, self.Ps.y, self.Pe.x, self.Pe.y)

    def tr(self, string_to_translate):
        """
        Translate a string using the QCoreApplication translation framework
        @param string_to_translate: a unicode string
        @return: the translated unicode string if it was possible to translate
        """
        return text_type(QtCore.QCoreApplication.translate('ArcGeo',
                                                           string_to_translate))

    def trim(self, Point, dir=1, rev_norm=False):
        """
        This instance is used to trim the geometry at the given point. The point
        can be a point on the offset geometry a perpendicular point on line will
        be used for trimming.
        @param Point: The point / perpendicular point for new Geometry
        @param dir: The direction in which the geometry will be kept (1  means the
        beginn will be trimmed)
        @param rev_norm: If the direction of the point is on the reversed side.
        """

        # logger.debug("I'm getting trimmed: %s, %s, %s, %s" % (self, Point, dir, rev_norm))
        newPoint = self.O.get_arc_point(self.O.norm_angle(Point), r=self.r)
        new_normal = newPoint.unit_vector(self.O, r=1)

        # logger.debug(newPoint)
        [Arc1, Arc2] = self.split_into_2geos(newPoint)

        if dir == -1:
            new_arc = Arc1
            if hasattr(self, "end_normal"):
                # new_arc.end_normal = self.end_normal
                # new_arc.start_normal = new_normal

                new_arc.end_normal = new_normal
                new_arc.start_normal = self.start_normal
            # logger.debug(new_arc)
            return new_arc
        else:
            new_arc = Arc2
            if hasattr(self, "end_normal"):
                # new_arc.end_normal = new_normal
                # new_arc.start_normal = self.start_normal

                new_arc.end_normal = self.end_normal
                new_arc.start_normal = new_normal
            # logger.debug(new_arc)
            return new_arc
        # return self

    def update_start_end_points(self, start_point, value):
        prv_dir = self.ext
        if start_point:
            self.Ps = value
            self.s_ang = self.O.norm_angle(self.Ps)
        else:
            self.Pe = value
            self.e_ang = self.O.norm_angle(self.Pe)

        self.ext = self.dif_ang(self.Ps, self.Pe, self.ext)

        if 2 * abs(((prv_dir - self.ext) + pi) % (2 * pi) - pi) >= pi:
            # seems very unlikely that this is what you want - the direction changed (too drastically)
            self.Ps, self.Pe = self.Pe, self.Ps
            self.s_ang, self.e_ang = self.e_ang, self.s_ang
            self.ext = self.dif_ang(self.Ps, self.Pe, prv_dir)

        self.length = self.r * abs(self.ext)

    def wrap(self, angle, isend=0):
        """
        Wrapes the given angle into a range between 0 and 2pi
        @param angle: The angle to be wraped
        @param isend: If the angle is the end angle or start angle, this makes a
        difference at 0 or 2pi.
        @return: Returns the angle between 0 and 2 *pi
        """
        wrap_angle = angle % (2 * pi)
        if isend and wrap_angle == 0.0:
            wrap_angle += 2 * pi
        elif wrap_angle == 2 * pi:
            wrap_angle -= 2 * pi

        return wrap_angle

    def Write_GCode(self, PostPro=None):
        """
        Writes the GCODE for an Arc.
        @param PostPro: The PostProcessor instance to be used
        @return: Returns the string to be written to a file.
        """
        Ps, s_ang = self.get_start_end_points(True, True)
        Pe, e_ang = self.get_start_end_points(False, True)

        O = self.O
        r = self.r
        IJ = O - Ps

        # If the radius of the element is bigger than the max, radius export the element as an line.
        if r > PostPro.vars.General["max_arc_radius"]:
            string = PostPro.lin_pol_xy(Ps, Pe)
        else:
            if self.ext > 0:
                string = PostPro.lin_pol_arc("ccw", Ps, Pe, s_ang, e_ang, r, O, IJ,self.ext)
            elif self.ext < 0 and PostPro.vars.General["export_ccw_arcs_only"]:
                string = PostPro.lin_pol_arc("ccw", Pe, Ps, e_ang, s_ang, r, O, O - Pe,self.ext)
            else:
                string = PostPro.lin_pol_arc("cw", Ps, Pe, s_ang, e_ang, r, O, IJ,self.ext)
        return string
Esempio n. 14
0
class GLWidget(CanvasBase):
    CAM_LEFT_X = -0.5
    CAM_RIGHT_X = 0.5
    CAM_BOTTOM_Y = 0.5
    CAM_TOP_Y = -0.5
    CAM_NEAR_Z = -14.0
    CAM_FAR_Z = 14.0

    COLOR_BACKGROUND = QColor.fromHsl(160, 0, 255, 255)
    COLOR_NORMAL = QColor.fromCmykF(1.0, 0.5, 0.0, 0.0, 1.0)
    COLOR_SELECT = QColor.fromCmykF(0.0, 1.0, 0.9, 0.0, 1.0)
    COLOR_NORMAL_DISABLED = QColor.fromCmykF(1.0, 0.5, 0.0, 0.0, 0.25)
    COLOR_SELECT_DISABLED = QColor.fromCmykF(0.0, 1.0, 0.9, 0.0, 0.25)
    COLOR_ENTRY_ARROW = QColor.fromRgbF(0.0, 0.0, 1.0, 1.0)
    COLOR_EXIT_ARROW = QColor.fromRgbF(0.0, 1.0, 0.0, 1.0)
    COLOR_ROUTE = QColor.fromRgbF(0.5, 0.0, 0.0, 1.0)
    COLOR_STMOVE = QColor.fromRgbF(0.5, 0.0, 0.25, 1.0)
    COLOR_BREAK = QColor.fromRgbF(1.0, 0.0, 1.0, 0.7)
    COLOR_LEFT = QColor.fromHsl(134, 240, 130, 255)
    COLOR_RIGHT = QColor.fromHsl(186, 240, 130, 255)

    def __init__(self, parent=None):
        super(GLWidget, self).__init__(parent)

        self.shapes = Shapes([])
        self.orientation = 0
        self.wpZero = 0
        self.routearrows = []
        self.expprv = None

        self.isPanning = False
        self.isRotating = False
        self.isMultiSelect = False
        self._lastPos = QPoint()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0
        self.scaleCorr = 1.0

        self.showPathDirections = False
        self.showDisabledPaths = False

        self.BB = BoundingBox()

        self.tol = 0

    def tr(self, string_to_translate):
        """
        Translate a string using the QCoreApplication translation framework
        @param string_to_translate: a unicode string
        @return: the translated unicode string if it was possible to translate
        """
        return text_type(QCoreApplication.translate('GLWidget',
                                                    string_to_translate))

    def resetAll(self):
        # the wpzero is currently generated "last"
        if self.wpZero > 0:
            GL.glDeleteLists(self.orientation + 1, self.wpZero - self.orientation)
        self.shapes = Shapes([])
        self.wpZero = 0
        self.delete_opt_paths()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0

        self.BB = BoundingBox()

        self.update()

    def delete_opt_paths(self):
        if len(self.routearrows) > 0:
            GL.glDeleteLists(self.routearrows[0][2], len(self.routearrows))
            self.routearrows = []

    def addexproutest(self):
        self.expprv = Point3D(g.config.vars.Plane_Coordinates['axis1_start_end'],
                              g.config.vars.Plane_Coordinates['axis2_start_end'],
                              0)

    def addexproute(self, exp_order, layer_nr):
        """
        This function initialises the Arrows of the export route order and its numbers.
        """
        for shape_nr in range(len(exp_order)):
            shape = self.shapes[exp_order[shape_nr]]
            st = self.expprv
            en, self.expprv = shape.get_start_end_points_physical()
            en = en.to3D(shape.axis3_start_mill_depth)
            self.expprv = self.expprv.to3D(shape.axis3_mill_depth)

            self.routearrows.append([st, en, 0])

            # TODO self.routetext.append(RouteText(text=("%s,%s" % (layer_nr, shape_nr+1)), startp=en))

    def addexprouteen(self):
        st = self.expprv
        en = Point3D(g.config.vars.Plane_Coordinates['axis1_start_end'],
                     g.config.vars.Plane_Coordinates['axis2_start_end'],
                     0)

        self.routearrows.append([st, en, 0])
        for route in self.routearrows:
            route[2] = self.makeRouteArrowHead(route[0], route[1])

    def contextMenuEvent(self, event):
        if not self.isRotating:
            clicked, offset, _ = self.getClickedDetails(event)
            MyDropDownMenu(self, event.globalPos(), clicked, offset)

    def setXRotation(self, angle):
        self.rotX = self.normalizeAngle(angle)

    def setYRotation(self, angle):
        self.rotY = self.normalizeAngle(angle)

    def setZRotation(self, angle):
        self.rotZ = self.normalizeAngle(angle)

    def normalizeAngle(self, angle):
        return (angle - 180) % -360 + 180

    def mousePressEvent(self, event):
        if self.isPanning or self.isRotating:
            self.setCursor(Qt.ClosedHandCursor)
        elif event.button() == Qt.LeftButton:
            clicked, offset, tol = self.getClickedDetails(event)
            xyForZ = {}
            for shape in self.shapes:
                hit = False
                z = shape.axis3_start_mill_depth
                if z not in xyForZ:
                    xyForZ[z] = self.determineSelectedPosition(clicked, z, offset)
                hit |= shape.isHit(xyForZ[z], tol)

                if not hit:
                    z = shape.axis3_mill_depth
                    if z not in xyForZ:
                        xyForZ[z] = self.determineSelectedPosition(clicked, z, offset)
                    hit |= shape.isHit(xyForZ[z], tol)

                if self.isMultiSelect and shape.selected:
                    hit = not hit

                if hit != shape.selected:
                    g.window.TreeHandler.updateShapeSelection(shape, hit)

                shape.selected = hit

            self.update()
        self._lastPos = event.pos()

    def getClickedDetails(self, event):
        min_side = min(self.frameSize().width(), self.frameSize().height())
        clicked = Point((event.pos().x() - self.frameSize().width() / 2),
                        (event.pos().y() - self.frameSize().height() / 2)) / min_side / self.scale
        offset = Point3D(-self.posX, -self.posY, -self.posZ) / self.scale
        tol = 4 * self.scaleCorr / min_side / self.scale
        return clicked, offset, tol

    def determineSelectedPosition(self, clicked, forZ, offset):
        angleX = -radians(self.rotX)
        angleY = -radians(self.rotY)

        zv = forZ - offset.z
        clickedZ = ((zv + clicked.x * sin(angleY)) / cos(angleY) - clicked.y * sin(angleX)) / cos(angleX)
        sx, sy, sz = self.deRotate(clicked.x, clicked.y, clickedZ)
        return Point(sx + offset.x, - sy - offset.y)  #, sz + offset.z

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton or event.button() == Qt.RightButton:
            if self.isPanning:
                self.setCursor(Qt.OpenHandCursor)
            elif self.isRotating:
                self.setCursor(Qt.PointingHandCursor)

    def mouseMoveEvent(self, event):
        dx = event.pos().x() - self._lastPos.x()
        dy = event.pos().y() - self._lastPos.y()

        if self.isRotating:
            if event.buttons() == Qt.LeftButton:
                self.setXRotation(self.rotX - dy / 2)
                self.setYRotation(self.rotY + dx / 2)
            elif event.buttons() == Qt.RightButton:
                self.setXRotation(self.rotX - dy / 2)
                self.setZRotation(self.rotZ + dx / 2)
        elif self.isPanning:
            if event.buttons() == Qt.LeftButton:
                min_side = min(self.frameSize().width(), self.frameSize().height())
                dx, dy, dz = self.deRotate(dx, dy, 0)
                self.posX += dx / min_side
                self.posY += dy / min_side
                self.posZ += dz / min_side

        self._lastPos = event.pos()
        self.update()

    def wheelEvent(self, event):
        min_side = min(self.frameSize().width(), self.frameSize().height())
        x = (event.pos().x() - self.frameSize().width() / 2) / min_side
        y = (event.pos().y() - self.frameSize().height() / 2) / min_side
        s = 1.001 ** event.angleDelta().y()

        x, y, z = self.deRotate(x, y, 0)
        self.posX = (self.posX - x) * s + x
        self.posY = (self.posY - y) * s + y
        self.posZ = (self.posZ - z) * s + z
        self.scale *= s

        self.update()

    def rotate(self, x, y, z):
        angleZ = radians(self.rotZ)
        x, y, z = x*cos(angleZ) - y*sin(angleZ), x*sin(angleZ) + y*cos(angleZ), z
        angleY = radians(self.rotY)
        x, y, z = x*cos(angleY) + z*sin(angleY), y, -x*sin(angleY) + z*cos(angleY)
        angleX = radians(self.rotX)
        return x, y*cos(angleX) - z*sin(angleX), y*sin(angleX) + z*cos(angleX)

    def deRotate(self, x, y, z):
        angleX = -radians(self.rotX)
        x, y, z = x, y*cos(angleX) - z*sin(angleX), y*sin(angleX) + z*cos(angleX)
        angleY = -radians(self.rotY)
        x, y, z = x*cos(angleY) + z*sin(angleY), y, -x*sin(angleY) + z*cos(angleY)
        angleZ = -radians(self.rotZ)
        return x*cos(angleZ) - y*sin(angleZ), x*sin(angleZ) + y*cos(angleZ), z

    def getRotationVectors(self, orgRefVector, toRefVector):
        """
        Generate a rotation matrix such that toRefVector = matrix * orgRefVector
        @param orgRefVector: A 3D unit vector
        @param toRefVector: A 3D unit vector
        @return: 3 vectors such that matrix = [vx; vy; vz]
        """
        # based on:
        # http://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d

        if orgRefVector == toRefVector:
            return Point3D(1, 0, 0), Point3D(0, 1, 0), Point3D(0, 0, 1)

        v = orgRefVector.cross_product(toRefVector)
        mn = (1 - orgRefVector * toRefVector) / v.length_squared()

        vx = Point3D(1, -v.z, v.y) + mn * Point3D(-v.y**2 - v.z**2, v.x * v.y, v.x * v.z)
        vy = Point3D(v.z, 1, -v.x) + mn * Point3D(v.x * v.y, -v.x**2 - v.z**2, v.y * v.z)
        vz = Point3D(-v.y, v.x, 1) + mn * Point3D(v.x * v.z, v.y * v.z, -v.x**2 - v.y**2)

        return vx, vy, vz

    def initializeGL(self):
        logger.debug(self.tr("Using OpenGL version: %s") % GL.glGetString(GL.GL_VERSION).decode("utf-8"))
        self.setClearColor(GLWidget.COLOR_BACKGROUND)

        GL.glEnable(GL.GL_MULTISAMPLE)

        GL.glEnable(GL.GL_POLYGON_SMOOTH)
        GL.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_NICEST)
        # GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        GL.glShadeModel(GL.GL_SMOOTH)

        GL.glEnable(GL.GL_DEPTH_TEST)
        GL.glEnable(GL.GL_CULL_FACE)
        # GL.glEnable(GL.GL_LIGHTING)
        # GL.glEnable(GL.GL_LIGHT0)
        GL.glEnable(GL.GL_BLEND)
        GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
        # GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (0.5, 5.0, 7.0, 1.0))
        # GL.glEnable(GL.GL_NORMALIZE)

        self.drawOrientationArrows()

    def paintGL(self):
        # The last transformation you specify takes place first.
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glLoadIdentity()
        GL.glRotatef(self.rotX, 1.0, 0.0, 0.0)
        GL.glRotatef(self.rotY, 0.0, 1.0, 0.0)
        GL.glRotatef(self.rotZ, 0.0, 0.0, 1.0)
        GL.glTranslatef(self.posX, self.posY, self.posZ)
        GL.glScalef(self.scale, self.scale, self.scale)
        for shape in self.shapes.selected_iter():
            if not shape.disabled:
                self.setColor(GLWidget.COLOR_STMOVE)
                GL.glCallList(shape.drawStMove)
                self.setColor(GLWidget.COLOR_SELECT)
                GL.glCallList(shape.drawObject)
            elif self.showDisabledPaths:
                self.setColor(GLWidget.COLOR_SELECT_DISABLED)
                GL.glCallList(shape.drawObject)
        for shape in self.shapes.not_selected_iter():
            if not shape.disabled:
                if shape.parentLayer.isBreakLayer():
                    self.setColor(GLWidget.COLOR_BREAK)
                elif shape.cut_cor == 41:
                    self.setColor(GLWidget.COLOR_LEFT)
                elif shape.cut_cor == 42:
                    self.setColor(GLWidget.COLOR_RIGHT)
                else:
                    self.setColor(GLWidget.COLOR_NORMAL)
                GL.glCallList(shape.drawObject)
                if self.showPathDirections:
                    self.setColor(GLWidget.COLOR_STMOVE)
                    GL.glCallList(shape.drawStMove)
            elif self.showDisabledPaths:
                self.setColor(GLWidget.COLOR_NORMAL_DISABLED)
                GL.glCallList(shape.drawObject)

        # optimization route arrows
        self.setColor(GLWidget.COLOR_ROUTE)
        GL.glBegin(GL.GL_LINES)
        for route in self.routearrows:
            start = route[0]
            end = route[1]
            GL.glVertex3f(start.x, -start.y, start.z)
            GL.glVertex3f(end.x, -end.y, end.z)
        GL.glEnd()

        GL.glScalef(self.scaleCorr / self.scale, self.scaleCorr / self.scale, self.scaleCorr / self.scale)
        scaleArrow = self.scale / self.scaleCorr
        for route in self.routearrows:
            end = scaleArrow * route[1]
            GL.glTranslatef(end.x, -end.y, end.z)
            GL.glCallList(route[2])
            GL.glTranslatef(-end.x, end.y, -end.z)

        # direction arrows
        for shape in self.shapes:
            if shape.selected and (not shape.disabled or self.showDisabledPaths) or\
               self.showPathDirections and not shape.disabled:
                start, end = shape.get_start_end_points_physical()
                start = scaleArrow * start.to3D(shape.axis3_start_mill_depth)
                end = scaleArrow * end.to3D(shape.axis3_mill_depth)
                GL.glTranslatef(start.x, -start.y, start.z)
                GL.glCallList(shape.drawArrowsDirection[0])
                GL.glTranslatef(-start.x, start.y, -start.z)
                GL.glTranslatef(end.x, -end.y, end.z)
                GL.glCallList(shape.drawArrowsDirection[1])
                GL.glTranslatef(-end.x, end.y, -end.z)

        if self.wpZero > 0:
            GL.glCallList(self.wpZero)
        GL.glTranslatef(-self.posX / self.scaleCorr, -self.posY / self.scaleCorr, -self.posZ / self.scaleCorr)
        GL.glCallList(self.orientation)

    def resizeGL(self, width, height):
        GL.glViewport(0, 0, width, height)
        side = min(width, height)
        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        if width >= height:
            scale_x = width / height
            GL.glOrtho(GLWidget.CAM_LEFT_X * scale_x, GLWidget.CAM_RIGHT_X * scale_x,
                            GLWidget.CAM_BOTTOM_Y, GLWidget.CAM_TOP_Y,
                            GLWidget.CAM_NEAR_Z, GLWidget.CAM_FAR_Z)
        else:
            scale_y = height / width
            GL.glOrtho(GLWidget.CAM_LEFT_X, GLWidget.CAM_RIGHT_X,
                            GLWidget.CAM_BOTTOM_Y * scale_y, GLWidget.CAM_TOP_Y * scale_y,
                            GLWidget.CAM_NEAR_Z, GLWidget.CAM_FAR_Z)
        self.scaleCorr = 400 / side
        GL.glMatrixMode(GL.GL_MODELVIEW)

    def setClearColor(self, c):
        GL.glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF())

    def setColor(self, c):
        self.setColorRGBA(c.redF(), c.greenF(), c.blueF(), c.alphaF())

    def setColorRGBA(self, r, g, b, a):
        # GL.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, (r, g, b, a))
        GL.glColor4f(r, g, b, a)

    def plotAll(self, shapes):
        for shape in shapes:
            self.paint_shape(shape)
            self.shapes.append(shape)
        self.drawWpZero()

    def repaint_shape(self, shape):
        GL.glDeleteLists(shape.drawObject, 4)
        self.paint_shape(shape)

    def paint_shape(self, shape):
        shape.drawObject = self.makeShape(shape)  # 1 object
        shape.stmove = StMove(shape)
        shape.drawStMove = self.makeStMove(shape.stmove)  # 1 object
        shape.drawArrowsDirection = self.makeDirArrows(shape)  # 2 objects

    def makeShape(self, shape):
        genList = GL.glGenLists(1)
        GL.glNewList(genList, GL.GL_COMPILE)

        GL.glBegin(GL.GL_LINES)
        shape.make_path(self.drawHorLine, self.drawVerLine)
        GL.glEnd()

        GL.glEndList()

        self.BB = self.BB.joinBB(shape.BB)


        return genList

    def makeStMove(self, stmove):
        genList = GL.glGenLists(1)
        GL.glNewList(genList, GL.GL_COMPILE)

        GL.glBegin(GL.GL_LINES)
        stmove.make_path(self.drawHorLine, self.drawVerLine)
        GL.glEnd()

        GL.glEndList()

        return genList

    def drawHorLine(self, caller, Ps, Pe):
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_start_mill_depth)
        GL.glVertex3f(Pe.x, -Pe.y, caller.axis3_start_mill_depth)
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_mill_depth)
        GL.glVertex3f(Pe.x, -Pe.y, caller.axis3_mill_depth)

    def drawVerLine(self, caller, Ps):
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_start_mill_depth)
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_mill_depth)

    def drawOrientationArrows(self):

        rCone = 0.01
        rCylinder = 0.004
        zTop = 0.05
        zMiddle = 0.02
        zBottom = -0.03
        segments = 20

        arrow = GL.glGenLists(1)
        GL.glNewList(arrow, GL.GL_COMPILE)

        self.drawCone(Point(), rCone, zTop, zMiddle, segments)
        self.drawSolidCircle(Point(), rCone, zMiddle, segments)
        self.drawCylinder(Point(), rCylinder, zMiddle, zBottom, segments)
        self.drawSolidCircle(Point(), rCylinder, zBottom, segments)

        GL.glEndList()

        self.orientation = GL.glGenLists(1)
        GL.glNewList(self.orientation, GL.GL_COMPILE)

        self.setColorRGBA(0.0, 0.0, 1.0, 0.5)
        GL.glCallList(arrow)

        GL.glRotatef(90, 0, 1, 0)
        self.setColorRGBA(1.0, 0.0, 0.0, 0.5)
        GL.glCallList(arrow)

        GL.glRotatef(90, 1, 0, 0)
        self.setColorRGBA(0.0, 1.0, 0.0, 0.5)
        GL.glCallList(arrow)

        GL.glEndList()

    def drawWpZero(self):

        r = 0.02
        segments = 20  # must be a multiple of 4

        self.wpZero = GL.glGenLists(1)
        GL.glNewList(self.wpZero, GL.GL_COMPILE)

        self.setColorRGBA(0.2, 0.2, 0.2, 0.7)
        self.drawSphere(r, segments, segments // 4, segments, segments // 4)

        GL.glBegin(GL.GL_TRIANGLE_FAN)
        GL.glVertex3f(0, 0, 0)
        for i in range(segments // 4 + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r)
            # GL.glNormal3f(0, -1, 0)
            GL.glVertex3f(xy2.x, 0, xy2.y)
        for i in range(segments // 4 + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r)
            # GL.glNormal3f(-1, 0, 0)
            GL.glVertex3f(0, -xy2.y, -xy2.x)
        for i in range(segments // 4 + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r)
            # GL.glNormal3f(0, 0, 1)
            GL.glVertex3f(-xy2.y, xy2.x, 0)
        GL.glEnd()

        self.setColorRGBA(0.6, 0.6, 0.6, 0.5)
        self.drawSphere(r * 1.25, segments, segments, segments, segments)

        GL.glEndList()

    def drawSphere(self, r, lats, mlats, longs, mlongs):
        lats //= 2
        # based on http://www.cburch.com/cs/490/sched/feb8/index.html
        for i in range(mlats):
            lat0 = pi * (-0.5 + i / lats)
            z0 = r * sin(lat0)
            zr0 = r * cos(lat0)
            lat1 = pi * (-0.5 + (i + 1) / lats)
            z1 = r * sin(lat1)
            zr1 = r * cos(lat1)
            GL.glBegin(GL.GL_QUAD_STRIP)
            for j in range(mlongs + 1):
                lng = 2 * pi * j / longs
                x = cos(lng)
                y = sin(lng)

                GL.glNormal3f(x * zr0, y * zr0, z0)
                GL.glVertex3f(x * zr0, y * zr0, z0)
                GL.glNormal3f(x * zr1, y * zr1, z1)
                GL.glVertex3f(x * zr1, y * zr1, z1)
            GL.glEnd()

    def drawSolidCircle(self, origin, r, z, segments):
        GL.glBegin(GL.GL_TRIANGLE_FAN)
        # GL.glNormal3f(0, 0, -1)
        GL.glVertex3f(origin.x, -origin.y, z)
        for i in range(segments + 1):
            ang = -i * 2 * pi / segments
            xy2 = origin.get_arc_point(ang, r)
            GL.glVertex3f(xy2.x, -xy2.y, z)
        GL.glEnd()

    def drawCone(self, origin, r, zTop, zBottom, segments):
        GL.glBegin(GL.GL_TRIANGLE_FAN)
        GL.glVertex3f(origin.x, -origin.y, zTop)
        for i in range(segments + 1):
            ang = i * 2 * pi / segments
            xy2 = origin.get_arc_point(ang, r)

            # GL.glNormal3f(xy2.x, -xy2.y, zBottom)
            GL.glVertex3f(xy2.x, -xy2.y, zBottom)
        GL.glEnd()

    def drawCylinder(self, origin, r, zTop, zBottom, segments):
        GL.glBegin(GL.GL_QUAD_STRIP)
        for i in range(segments + 1):
            ang = i * 2 * pi / segments
            xy = origin.get_arc_point(ang, r)

            # GL.glNormal3f(xy.x, -xy.y, 0)
            GL.glVertex3f(xy.x, -xy.y, zTop)
            GL.glVertex3f(xy.x, -xy.y, zBottom)
        GL.glEnd()

    def makeDirArrows(self, shape):
        (start, start_dir), (end, end_dir) = shape.get_start_end_points_physical(None, False)

        startArrow = GL.glGenLists(1)
        GL.glNewList(startArrow, GL.GL_COMPILE)
        self.setColor(GLWidget.COLOR_ENTRY_ARROW)
        self.drawDirArrow(Point3D(), start_dir.to3D(), True)
        GL.glEndList()

        endArrow = GL.glGenLists(1)
        GL.glNewList(endArrow, GL.GL_COMPILE)
        self.setColor(GLWidget.COLOR_EXIT_ARROW)
        self.drawDirArrow(Point3D(), end_dir.to3D(), False)
        GL.glEndList()

        return startArrow, endArrow

    def drawDirArrow(self, origin, direction, startError):
        offset = 0.0 if startError else 0.05
        zMiddle = -0.02 + offset
        zBottom = -0.05 + offset
        rx, ry, rz = self.getRotationVectors(Point3D(0, 0, 1), direction)

        self.drawArrowHead(origin, rx, ry, rz, offset)

        GL.glBegin(GL.GL_LINES)
        zeroMiddle = Point3D(0, 0, zMiddle)
        GL.glVertex3f(zeroMiddle * rx + origin.x, -zeroMiddle * ry - origin.y, zeroMiddle * rz + origin.z)
        zeroBottom = Point3D(0, 0, zBottom)
        GL.glVertex3f(zeroBottom * rx + origin.x, -zeroBottom * ry - origin.y, zeroBottom * rz + origin.z)
        GL.glEnd()

    def makeRouteArrowHead(self, start, end):
        if end == start:
            direction = Point3D(0, 0, 1)
        else:
            direction = (end - start).unit_vector()
        rx, ry, rz = self.getRotationVectors(Point3D(0, 0, 1), direction)

        head = GL.glGenLists(1)
        GL.glNewList(head, GL.GL_COMPILE)
        self.drawArrowHead(Point3D(), rx, ry, rz, 0)
        GL.glEndList()
        return head

    def drawArrowHead(self, origin, rx, ry, rz, offset):
        r = 0.01
        segments = 10
        zTop = 0 + offset
        zBottom = -0.02 + offset

        GL.glBegin(GL.GL_TRIANGLE_FAN)
        zeroTop = Point3D(0, 0, zTop)
        GL.glVertex3f(zeroTop * rx + origin.x, -zeroTop * ry - origin.y, zeroTop * rz + origin.z)
        for i in range(segments + 1):
            ang = i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r).to3D(zBottom)
            GL.glVertex3f(xy2 * rx + origin.x, -xy2 * ry - origin.y, xy2 * rz + origin.z)
        GL.glEnd()

        GL.glBegin(GL.GL_TRIANGLE_FAN)
        zeroBottom = Point3D(0, 0, zBottom)
        GL.glVertex3f(zeroBottom * rx + origin.x, -zeroBottom * ry - origin.y, zeroBottom * rz + origin.z)
        for i in range(segments + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r).to3D(zBottom)
            GL.glVertex3f(xy2 * rx + origin.x, -xy2 * ry - origin.y, xy2 * rz + origin.z)
        GL.glEnd()

    def setShowPathDirections(self, flag):
        self.showPathDirections = flag

    def setShowDisabledPaths(self, flag=True):
        self.showDisabledPaths = flag

    def autoscale(self):
        # TODO currently only works correctly when object is not rotated
        if self.frameSize().width() >= self.frameSize().height():
            aspect_scale_x = self.frameSize().width() / self.frameSize().height()
            aspect_scale_y = 1
        else:
            aspect_scale_x = 1
            aspect_scale_y = self.frameSize().height() / self.frameSize().width()
        scaleX = (GLWidget.CAM_RIGHT_X - GLWidget.CAM_LEFT_X) * aspect_scale_x / (self.BB.Pe.x - self.BB.Ps.x)
        scaleY = (GLWidget.CAM_BOTTOM_Y - GLWidget.CAM_TOP_Y) * aspect_scale_y / (self.BB.Pe.y - self.BB.Ps.y)
        self.scale = min(scaleX, scaleY) * 0.95
        self.posX = ((GLWidget.CAM_LEFT_X + GLWidget.CAM_RIGHT_X) * 0.95 * aspect_scale_x
                     - (self.BB.Ps.x + self.BB.Pe.x) * self.scale) / 2
        self.posY = -((GLWidget.CAM_TOP_Y + GLWidget.CAM_BOTTOM_Y) * 0.95 * aspect_scale_y
                      - (self.BB.Pe.y + self.BB.Ps.y) * self.scale) / 2
        self.posZ = 0
        self.update()

    def topView(self):
        self.rotX = 0
        self.rotY = 0
        self.rotZ = 0
        self.update()

    def isometricView(self):
        self.rotX = -22
        self.rotY = -22
        self.rotZ = 0
        self.update()
Esempio n. 15
0
class GLWidget(CanvasBase):
    CAM_LEFT_X = -0.5
    CAM_RIGHT_X = 0.5
    CAM_BOTTOM_Y = 0.5
    CAM_TOP_Y = -0.5
    CAM_NEAR_Z = -14.0
    CAM_FAR_Z = 14.0

    COLOR_BACKGROUND = QColor.fromHsl(160, 0, 255, 255)
    COLOR_NORMAL = QColor.fromCmykF(1.0, 0.5, 0.0, 0.0, 1.0)
    COLOR_SELECT = QColor.fromCmykF(0.0, 1.0, 0.9, 0.0, 1.0)
    COLOR_NORMAL_DISABLED = QColor.fromCmykF(1.0, 0.5, 0.0, 0.0, 0.25)
    COLOR_SELECT_DISABLED = QColor.fromCmykF(0.0, 1.0, 0.9, 0.0, 0.25)
    COLOR_ENTRY_ARROW = QColor.fromRgbF(0.0, 0.0, 1.0, 1.0)
    COLOR_EXIT_ARROW = QColor.fromRgbF(0.0, 1.0, 0.0, 1.0)
    COLOR_ROUTE = QColor.fromRgbF(0.5, 0.0, 0.0, 1.0)
    COLOR_STMOVE = QColor.fromRgbF(0.5, 0.0, 0.25, 1.0)
    COLOR_BREAK = QColor.fromRgbF(1.0, 0.0, 1.0, 0.7)
    COLOR_LEFT = QColor.fromHsl(134, 240, 130, 255)
    COLOR_RIGHT = QColor.fromHsl(186, 240, 130, 255)

    def __init__(self, parent=None):
        super(GLWidget, self).__init__(parent)

        self.shapes = Shapes([])
        self.orientation = 0
        self.wpZero = 0
        self.routearrows = []
        self.expprv = None

        self.isPanning = False
        self.isRotating = False
        self.isMultiSelect = False
        self._lastPos = QPoint()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0
        self.scaleCorr = 1.0

        self.showPathDirections = False
        self.showDisabledPaths = False

        self.BB = BoundingBox()

        self.tol = 0

    def tr(self, string_to_translate):
        """
        Translate a string using the QCoreApplication translation framework
        @param string_to_translate: a unicode string
        @return: the translated unicode string if it was possible to translate
        """
        return text_type(
            QCoreApplication.translate('GLWidget', string_to_translate))

    def resetAll(self):
        # the wpzero is currently generated "last"
        if self.wpZero > 0:
            GL.glDeleteLists(self.orientation + 1,
                             self.wpZero - self.orientation)
        self.shapes = Shapes([])
        self.wpZero = 0
        self.delete_opt_paths()

        self.posX = 0.0
        self.posY = 0.0
        self.posZ = 0.0
        self.rotX = 0.0
        self.rotY = 0.0
        self.rotZ = 0.0
        self.scale = 1.0

        self.BB = BoundingBox()

        self.update()

    def delete_opt_paths(self):
        if len(self.routearrows) > 0:
            GL.glDeleteLists(self.routearrows[0][2], len(self.routearrows))
            self.routearrows = []

    def addexproutest(self):
        self.expprv = Point3D(
            g.config.vars.Plane_Coordinates['axis1_start_end'],
            g.config.vars.Plane_Coordinates['axis2_start_end'], 0)

    def addexproute(self, exp_order, layer_nr):
        """
        This function initialises the Arrows of the export route order and its numbers.
        """
        for shape_nr in range(len(exp_order)):
            shape = self.shapes[exp_order[shape_nr]]
            st = self.expprv
            en, self.expprv = shape.get_start_end_points_physical()
            en = en.to3D(shape.axis3_start_mill_depth)
            self.expprv = self.expprv.to3D(shape.axis3_mill_depth)

            self.routearrows.append([st, en, 0])

            # TODO self.routetext.append(RouteText(text=("%s,%s" % (layer_nr, shape_nr+1)), startp=en))

    def addexprouteen(self):
        st = self.expprv
        en = Point3D(g.config.vars.Plane_Coordinates['axis1_start_end'],
                     g.config.vars.Plane_Coordinates['axis2_start_end'], 0)

        self.routearrows.append([st, en, 0])
        for route in self.routearrows:
            route[2] = self.makeRouteArrowHead(route[0], route[1])

    def contextMenuEvent(self, event):
        if not self.isRotating:
            clicked, offset, _ = self.getClickedDetails(event)
            MyDropDownMenu(self, event.globalPos(), clicked, offset)

    def setXRotation(self, angle):
        self.rotX = self.normalizeAngle(angle)

    def setYRotation(self, angle):
        self.rotY = self.normalizeAngle(angle)

    def setZRotation(self, angle):
        self.rotZ = self.normalizeAngle(angle)

    def normalizeAngle(self, angle):
        return (angle - 180) % -360 + 180

    def mousePressEvent(self, event):
        if self.isPanning or self.isRotating:
            self.setCursor(Qt.ClosedHandCursor)
        elif event.button() == Qt.LeftButton:
            clicked, offset, tol = self.getClickedDetails(event)
            xyForZ = {}
            for shape in self.shapes:
                hit = False
                z = shape.axis3_start_mill_depth
                if z not in xyForZ:
                    xyForZ[z] = self.determineSelectedPosition(
                        clicked, z, offset)
                hit |= shape.isHit(xyForZ[z], tol)

                if not hit:
                    z = shape.axis3_mill_depth
                    if z not in xyForZ:
                        xyForZ[z] = self.determineSelectedPosition(
                            clicked, z, offset)
                    hit |= shape.isHit(xyForZ[z], tol)

                if self.isMultiSelect and shape.selected:
                    hit = not hit

                if hit != shape.selected:
                    g.window.TreeHandler.updateShapeSelection(shape, hit)

                shape.selected = hit

            self.update()
        self._lastPos = event.pos()

    def getClickedDetails(self, event):
        min_side = min(self.frameSize().width(), self.frameSize().height())
        clicked = Point(
            (event.pos().x() - self.frameSize().width() / 2),
            (event.pos().y() -
             self.frameSize().height() / 2)) / min_side / self.scale
        offset = Point3D(-self.posX, -self.posY, -self.posZ) / self.scale
        tol = 4 * self.scaleCorr / min_side / self.scale
        return clicked, offset, tol

    def determineSelectedPosition(self, clicked, forZ, offset):
        angleX = -radians(self.rotX)
        angleY = -radians(self.rotY)

        zv = forZ - offset.z
        clickedZ = ((zv + clicked.x * sin(angleY)) / cos(angleY) -
                    clicked.y * sin(angleX)) / cos(angleX)
        sx, sy, sz = self.deRotate(clicked.x, clicked.y, clickedZ)
        return Point(sx + offset.x, -sy - offset.y)  #, sz + offset.z

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton or event.button() == Qt.RightButton:
            if self.isPanning:
                self.setCursor(Qt.OpenHandCursor)
            elif self.isRotating:
                self.setCursor(Qt.PointingHandCursor)

    def mouseMoveEvent(self, event):
        dx = event.pos().x() - self._lastPos.x()
        dy = event.pos().y() - self._lastPos.y()

        if self.isRotating:
            if event.buttons() == Qt.LeftButton:
                self.setXRotation(self.rotX - dy / 2)
                self.setYRotation(self.rotY + dx / 2)
            elif event.buttons() == Qt.RightButton:
                self.setXRotation(self.rotX - dy / 2)
                self.setZRotation(self.rotZ + dx / 2)
        elif self.isPanning:
            if event.buttons() == Qt.LeftButton:
                min_side = min(self.frameSize().width(),
                               self.frameSize().height())
                dx, dy, dz = self.deRotate(dx, dy, 0)
                self.posX += dx / min_side
                self.posY += dy / min_side
                self.posZ += dz / min_side

        self._lastPos = event.pos()
        self.update()

    def wheelEvent(self, event):
        min_side = min(self.frameSize().width(), self.frameSize().height())
        x = (event.pos().x() - self.frameSize().width() / 2) / min_side
        y = (event.pos().y() - self.frameSize().height() / 2) / min_side
        s = 1.001**event.angleDelta().y()

        x, y, z = self.deRotate(x, y, 0)
        self.posX = (self.posX - x) * s + x
        self.posY = (self.posY - y) * s + y
        self.posZ = (self.posZ - z) * s + z
        self.scale *= s

        self.update()

    def rotate(self, x, y, z):
        angleZ = radians(self.rotZ)
        x, y, z = x * cos(angleZ) - y * sin(angleZ), x * sin(angleZ) + y * cos(
            angleZ), z
        angleY = radians(self.rotY)
        x, y, z = x * cos(angleY) + z * sin(angleY), y, -x * sin(
            angleY) + z * cos(angleY)
        angleX = radians(self.rotX)
        return x, y * cos(angleX) - z * sin(angleX), y * sin(angleX) + z * cos(
            angleX)

    def deRotate(self, x, y, z):
        angleX = -radians(self.rotX)
        x, y, z = x, y * cos(angleX) - z * sin(angleX), y * sin(
            angleX) + z * cos(angleX)
        angleY = -radians(self.rotY)
        x, y, z = x * cos(angleY) + z * sin(angleY), y, -x * sin(
            angleY) + z * cos(angleY)
        angleZ = -radians(self.rotZ)
        return x * cos(angleZ) - y * sin(angleZ), x * sin(angleZ) + y * cos(
            angleZ), z

    def getRotationVectors(self, orgRefVector, toRefVector):
        """
        Generate a rotation matrix such that toRefVector = matrix * orgRefVector
        @param orgRefVector: A 3D unit vector
        @param toRefVector: A 3D unit vector
        @return: 3 vectors such that matrix = [vx; vy; vz]
        """
        # based on:
        # http://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d

        if orgRefVector == toRefVector:
            return Point3D(1, 0, 0), Point3D(0, 1, 0), Point3D(0, 0, 1)

        v = orgRefVector.cross_product(toRefVector)
        mn = (1 - orgRefVector * toRefVector) / v.length_squared()

        vx = Point3D(
            1, -v.z,
            v.y) + mn * Point3D(-v.y**2 - v.z**2, v.x * v.y, v.x * v.z)
        vy = Point3D(
            v.z, 1,
            -v.x) + mn * Point3D(v.x * v.y, -v.x**2 - v.z**2, v.y * v.z)
        vz = Point3D(-v.y, v.x,
                     1) + mn * Point3D(v.x * v.z, v.y * v.z, -v.x**2 - v.y**2)

        return vx, vy, vz

    def initializeGL(self):
        logger.debug(
            self.tr("Using OpenGL version: %s") %
            GL.glGetString(GL.GL_VERSION).decode("utf-8"))
        self.setClearColor(GLWidget.COLOR_BACKGROUND)

        GL.glEnable(GL.GL_MULTISAMPLE)

        GL.glEnable(GL.GL_POLYGON_SMOOTH)
        GL.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_NICEST)
        # GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        GL.glShadeModel(GL.GL_SMOOTH)

        GL.glEnable(GL.GL_DEPTH_TEST)
        GL.glEnable(GL.GL_CULL_FACE)
        # GL.glEnable(GL.GL_LIGHTING)
        # GL.glEnable(GL.GL_LIGHT0)
        GL.glEnable(GL.GL_BLEND)
        GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
        # GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (0.5, 5.0, 7.0, 1.0))
        # GL.glEnable(GL.GL_NORMALIZE)

        self.drawOrientationArrows()

    def paintGL(self):
        # The last transformation you specify takes place first.
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glLoadIdentity()
        GL.glRotatef(self.rotX, 1.0, 0.0, 0.0)
        GL.glRotatef(self.rotY, 0.0, 1.0, 0.0)
        GL.glRotatef(self.rotZ, 0.0, 0.0, 1.0)
        GL.glTranslatef(self.posX, self.posY, self.posZ)
        GL.glScalef(self.scale, self.scale, self.scale)
        for shape in self.shapes.selected_iter():
            if not shape.disabled:
                self.setColor(GLWidget.COLOR_STMOVE)
                GL.glCallList(shape.drawStMove)
                self.setColor(GLWidget.COLOR_SELECT)
                GL.glCallList(shape.drawObject)
            elif self.showDisabledPaths:
                self.setColor(GLWidget.COLOR_SELECT_DISABLED)
                GL.glCallList(shape.drawObject)
        for shape in self.shapes.not_selected_iter():
            if not shape.disabled:
                if shape.parentLayer.isBreakLayer():
                    self.setColor(GLWidget.COLOR_BREAK)
                elif shape.cut_cor == 41:
                    self.setColor(GLWidget.COLOR_LEFT)
                elif shape.cut_cor == 42:
                    self.setColor(GLWidget.COLOR_RIGHT)
                else:
                    self.setColor(GLWidget.COLOR_NORMAL)
                GL.glCallList(shape.drawObject)
                if self.showPathDirections:
                    self.setColor(GLWidget.COLOR_STMOVE)
                    GL.glCallList(shape.drawStMove)
            elif self.showDisabledPaths:
                self.setColor(GLWidget.COLOR_NORMAL_DISABLED)
                GL.glCallList(shape.drawObject)

        # optimization route arrows
        self.setColor(GLWidget.COLOR_ROUTE)
        GL.glBegin(GL.GL_LINES)
        for route in self.routearrows:
            start = route[0]
            end = route[1]
            GL.glVertex3f(start.x, -start.y, start.z)
            GL.glVertex3f(end.x, -end.y, end.z)
        GL.glEnd()

        GL.glScalef(self.scaleCorr / self.scale, self.scaleCorr / self.scale,
                    self.scaleCorr / self.scale)
        scaleArrow = self.scale / self.scaleCorr
        for route in self.routearrows:
            end = scaleArrow * route[1]
            GL.glTranslatef(end.x, -end.y, end.z)
            GL.glCallList(route[2])
            GL.glTranslatef(-end.x, end.y, -end.z)

        # direction arrows
        for shape in self.shapes:
            if shape.selected and (not shape.disabled or self.showDisabledPaths) or\
               self.showPathDirections and not shape.disabled:
                start, end = shape.get_start_end_points_physical()
                start = scaleArrow * start.to3D(shape.axis3_start_mill_depth)
                end = scaleArrow * end.to3D(shape.axis3_mill_depth)
                GL.glTranslatef(start.x, -start.y, start.z)
                GL.glCallList(shape.drawArrowsDirection[0])
                GL.glTranslatef(-start.x, start.y, -start.z)
                GL.glTranslatef(end.x, -end.y, end.z)
                GL.glCallList(shape.drawArrowsDirection[1])
                GL.glTranslatef(-end.x, end.y, -end.z)

        if self.wpZero > 0:
            GL.glCallList(self.wpZero)
        GL.glTranslatef(-self.posX / self.scaleCorr,
                        -self.posY / self.scaleCorr,
                        -self.posZ / self.scaleCorr)
        GL.glCallList(self.orientation)

    def resizeGL(self, width, height):
        GL.glViewport(0, 0, width, height)
        side = min(width, height)
        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        if width >= height:
            scale_x = width / height
            GL.glOrtho(GLWidget.CAM_LEFT_X * scale_x,
                       GLWidget.CAM_RIGHT_X * scale_x, GLWidget.CAM_BOTTOM_Y,
                       GLWidget.CAM_TOP_Y, GLWidget.CAM_NEAR_Z,
                       GLWidget.CAM_FAR_Z)
        else:
            scale_y = height / width
            GL.glOrtho(GLWidget.CAM_LEFT_X, GLWidget.CAM_RIGHT_X,
                       GLWidget.CAM_BOTTOM_Y * scale_y,
                       GLWidget.CAM_TOP_Y * scale_y, GLWidget.CAM_NEAR_Z,
                       GLWidget.CAM_FAR_Z)
        self.scaleCorr = 400 / side
        GL.glMatrixMode(GL.GL_MODELVIEW)

    def setClearColor(self, c):
        GL.glClearColor(c.redF(), c.greenF(), c.blueF(), c.alphaF())

    def setColor(self, c):
        self.setColorRGBA(c.redF(), c.greenF(), c.blueF(), c.alphaF())

    def setColorRGBA(self, r, g, b, a):
        # GL.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, (r, g, b, a))
        GL.glColor4f(r, g, b, a)

    def plotAll(self, shapes):
        for shape in shapes:
            self.paint_shape(shape)
            self.shapes.append(shape)
        self.drawWpZero()

    def repaint_shape(self, shape):
        GL.glDeleteLists(shape.drawObject, 4)
        self.paint_shape(shape)

    def paint_shape(self, shape):
        shape.drawObject = self.makeShape(shape)  # 1 object
        shape.stmove = StMove(shape)
        shape.drawStMove = self.makeStMove(shape.stmove)  # 1 object
        shape.drawArrowsDirection = self.makeDirArrows(shape)  # 2 objects

    def makeShape(self, shape):
        genList = GL.glGenLists(1)
        GL.glNewList(genList, GL.GL_COMPILE)

        GL.glBegin(GL.GL_LINES)
        shape.make_path(self.drawHorLine, self.drawVerLine)
        GL.glEnd()

        GL.glEndList()

        self.BB = self.BB.joinBB(shape.BB)

        return genList

    def makeStMove(self, stmove):
        genList = GL.glGenLists(1)
        GL.glNewList(genList, GL.GL_COMPILE)

        GL.glBegin(GL.GL_LINES)
        stmove.make_path(self.drawHorLine, self.drawVerLine)
        GL.glEnd()

        GL.glEndList()

        return genList

    def drawHorLine(self, caller, Ps, Pe):
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_start_mill_depth)
        GL.glVertex3f(Pe.x, -Pe.y, caller.axis3_start_mill_depth)
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_mill_depth)
        GL.glVertex3f(Pe.x, -Pe.y, caller.axis3_mill_depth)

    def drawVerLine(self, caller, Ps):
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_start_mill_depth)
        GL.glVertex3f(Ps.x, -Ps.y, caller.axis3_mill_depth)

    def drawOrientationArrows(self):

        rCone = 0.01
        rCylinder = 0.004
        zTop = 0.05
        zMiddle = 0.02
        zBottom = -0.03
        segments = 20

        arrow = GL.glGenLists(1)
        GL.glNewList(arrow, GL.GL_COMPILE)

        self.drawCone(Point(), rCone, zTop, zMiddle, segments)
        self.drawSolidCircle(Point(), rCone, zMiddle, segments)
        self.drawCylinder(Point(), rCylinder, zMiddle, zBottom, segments)
        self.drawSolidCircle(Point(), rCylinder, zBottom, segments)

        GL.glEndList()

        self.orientation = GL.glGenLists(1)
        GL.glNewList(self.orientation, GL.GL_COMPILE)

        self.setColorRGBA(0.0, 0.0, 1.0, 0.5)
        GL.glCallList(arrow)

        GL.glRotatef(90, 0, 1, 0)
        self.setColorRGBA(1.0, 0.0, 0.0, 0.5)
        GL.glCallList(arrow)

        GL.glRotatef(90, 1, 0, 0)
        self.setColorRGBA(0.0, 1.0, 0.0, 0.5)
        GL.glCallList(arrow)

        GL.glEndList()

    def drawWpZero(self):

        r = 0.02
        segments = 20  # must be a multiple of 4

        self.wpZero = GL.glGenLists(1)
        GL.glNewList(self.wpZero, GL.GL_COMPILE)

        self.setColorRGBA(0.2, 0.2, 0.2, 0.7)
        self.drawSphere(r, segments, segments // 4, segments, segments // 4)

        GL.glBegin(GL.GL_TRIANGLE_FAN)
        GL.glVertex3f(0, 0, 0)
        for i in range(segments // 4 + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r)
            # GL.glNormal3f(0, -1, 0)
            GL.glVertex3f(xy2.x, 0, xy2.y)
        for i in range(segments // 4 + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r)
            # GL.glNormal3f(-1, 0, 0)
            GL.glVertex3f(0, -xy2.y, -xy2.x)
        for i in range(segments // 4 + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r)
            # GL.glNormal3f(0, 0, 1)
            GL.glVertex3f(-xy2.y, xy2.x, 0)
        GL.glEnd()

        self.setColorRGBA(0.6, 0.6, 0.6, 0.5)
        self.drawSphere(r * 1.25, segments, segments, segments, segments)

        GL.glEndList()

    def drawSphere(self, r, lats, mlats, longs, mlongs):
        lats //= 2
        # based on http://www.cburch.com/cs/490/sched/feb8/index.html
        for i in range(mlats):
            lat0 = pi * (-0.5 + i / lats)
            z0 = r * sin(lat0)
            zr0 = r * cos(lat0)
            lat1 = pi * (-0.5 + (i + 1) / lats)
            z1 = r * sin(lat1)
            zr1 = r * cos(lat1)
            GL.glBegin(GL.GL_QUAD_STRIP)
            for j in range(mlongs + 1):
                lng = 2 * pi * j / longs
                x = cos(lng)
                y = sin(lng)

                GL.glNormal3f(x * zr0, y * zr0, z0)
                GL.glVertex3f(x * zr0, y * zr0, z0)
                GL.glNormal3f(x * zr1, y * zr1, z1)
                GL.glVertex3f(x * zr1, y * zr1, z1)
            GL.glEnd()

    def drawSolidCircle(self, origin, r, z, segments):
        GL.glBegin(GL.GL_TRIANGLE_FAN)
        # GL.glNormal3f(0, 0, -1)
        GL.glVertex3f(origin.x, -origin.y, z)
        for i in range(segments + 1):
            ang = -i * 2 * pi / segments
            xy2 = origin.get_arc_point(ang, r)
            GL.glVertex3f(xy2.x, -xy2.y, z)
        GL.glEnd()

    def drawCone(self, origin, r, zTop, zBottom, segments):
        GL.glBegin(GL.GL_TRIANGLE_FAN)
        GL.glVertex3f(origin.x, -origin.y, zTop)
        for i in range(segments + 1):
            ang = i * 2 * pi / segments
            xy2 = origin.get_arc_point(ang, r)

            # GL.glNormal3f(xy2.x, -xy2.y, zBottom)
            GL.glVertex3f(xy2.x, -xy2.y, zBottom)
        GL.glEnd()

    def drawCylinder(self, origin, r, zTop, zBottom, segments):
        GL.glBegin(GL.GL_QUAD_STRIP)
        for i in range(segments + 1):
            ang = i * 2 * pi / segments
            xy = origin.get_arc_point(ang, r)

            # GL.glNormal3f(xy.x, -xy.y, 0)
            GL.glVertex3f(xy.x, -xy.y, zTop)
            GL.glVertex3f(xy.x, -xy.y, zBottom)
        GL.glEnd()

    def makeDirArrows(self, shape):
        (start, start_dir), (end,
                             end_dir) = shape.get_start_end_points_physical(
                                 None, False)

        startArrow = GL.glGenLists(1)
        GL.glNewList(startArrow, GL.GL_COMPILE)
        self.setColor(GLWidget.COLOR_ENTRY_ARROW)
        self.drawDirArrow(Point3D(), start_dir.to3D(), True)
        GL.glEndList()

        endArrow = GL.glGenLists(1)
        GL.glNewList(endArrow, GL.GL_COMPILE)
        self.setColor(GLWidget.COLOR_EXIT_ARROW)
        self.drawDirArrow(Point3D(), end_dir.to3D(), False)
        GL.glEndList()

        return startArrow, endArrow

    def drawDirArrow(self, origin, direction, startError):
        offset = 0.0 if startError else 0.05
        zMiddle = -0.02 + offset
        zBottom = -0.05 + offset
        rx, ry, rz = self.getRotationVectors(Point3D(0, 0, 1), direction)

        self.drawArrowHead(origin, rx, ry, rz, offset)

        GL.glBegin(GL.GL_LINES)
        zeroMiddle = Point3D(0, 0, zMiddle)
        GL.glVertex3f(zeroMiddle * rx + origin.x, -zeroMiddle * ry - origin.y,
                      zeroMiddle * rz + origin.z)
        zeroBottom = Point3D(0, 0, zBottom)
        GL.glVertex3f(zeroBottom * rx + origin.x, -zeroBottom * ry - origin.y,
                      zeroBottom * rz + origin.z)
        GL.glEnd()

    def makeRouteArrowHead(self, start, end):
        if end == start:
            direction = Point3D(0, 0, 1)
        else:
            direction = (end - start).unit_vector()
        rx, ry, rz = self.getRotationVectors(Point3D(0, 0, 1), direction)

        head = GL.glGenLists(1)
        GL.glNewList(head, GL.GL_COMPILE)
        self.drawArrowHead(Point3D(), rx, ry, rz, 0)
        GL.glEndList()
        return head

    def drawArrowHead(self, origin, rx, ry, rz, offset):
        r = 0.01
        segments = 10
        zTop = 0 + offset
        zBottom = -0.02 + offset

        GL.glBegin(GL.GL_TRIANGLE_FAN)
        zeroTop = Point3D(0, 0, zTop)
        GL.glVertex3f(zeroTop * rx + origin.x, -zeroTop * ry - origin.y,
                      zeroTop * rz + origin.z)
        for i in range(segments + 1):
            ang = i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r).to3D(zBottom)
            GL.glVertex3f(xy2 * rx + origin.x, -xy2 * ry - origin.y,
                          xy2 * rz + origin.z)
        GL.glEnd()

        GL.glBegin(GL.GL_TRIANGLE_FAN)
        zeroBottom = Point3D(0, 0, zBottom)
        GL.glVertex3f(zeroBottom * rx + origin.x, -zeroBottom * ry - origin.y,
                      zeroBottom * rz + origin.z)
        for i in range(segments + 1):
            ang = -i * 2 * pi / segments
            xy2 = Point().get_arc_point(ang, r).to3D(zBottom)
            GL.glVertex3f(xy2 * rx + origin.x, -xy2 * ry - origin.y,
                          xy2 * rz + origin.z)
        GL.glEnd()

    def setShowPathDirections(self, flag):
        self.showPathDirections = flag

    def setShowDisabledPaths(self, flag=True):
        self.showDisabledPaths = flag

    def autoscale(self):
        # TODO currently only works correctly when object is not rotated
        if self.frameSize().width() >= self.frameSize().height():
            aspect_scale_x = self.frameSize().width() / self.frameSize(
            ).height()
            aspect_scale_y = 1
        else:
            aspect_scale_x = 1
            aspect_scale_y = self.frameSize().height() / self.frameSize(
            ).width()
        scaleX = (GLWidget.CAM_RIGHT_X - GLWidget.CAM_LEFT_X
                  ) * aspect_scale_x / (self.BB.Pe.x - self.BB.Ps.x)
        scaleY = (GLWidget.CAM_BOTTOM_Y - GLWidget.CAM_TOP_Y
                  ) * aspect_scale_y / (self.BB.Pe.y - self.BB.Ps.y)
        self.scale = min(scaleX, scaleY) * 0.95
        self.posX = ((GLWidget.CAM_LEFT_X + GLWidget.CAM_RIGHT_X) * 0.95 *
                     aspect_scale_x -
                     (self.BB.Ps.x + self.BB.Pe.x) * self.scale) / 2
        self.posY = -((GLWidget.CAM_TOP_Y + GLWidget.CAM_BOTTOM_Y) * 0.95 *
                      aspect_scale_y -
                      (self.BB.Pe.y + self.BB.Ps.y) * self.scale) / 2
        self.posZ = 0
        self.update()

    def topView(self):
        self.rotX = 0
        self.rotY = 0
        self.rotZ = 0
        self.update()

    def isometricView(self):
        self.rotX = -22
        self.rotY = -22
        self.rotZ = 0
        self.update()