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 __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 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 __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 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 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)
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()
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()
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
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)
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
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()
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()