def elliptic_arc_(self, rx, ry, angle, clockwise, cy): """ Plot an elliptic arc. """ xrot = 90 orig_angle = angle angle = angle % 360 if angle == 0 and orig_angle != 0: angle = 360 if angle > 180.0: large_arc = 1 else: large_arc = 0 if clockwise: sweep_flag = 0 else: sweep_flag = 1 if angle <= 90: ysign = -1 else: ysign = 1 cx = 0 #cy = 0 #x, y = 0, ry x, y = 0, cy + ry if clockwise: theta = angle + 90 else: theta = -angle + 90 theta_rad = deg2rad(theta) cost = math.cos(theta_rad) sint = math.sin(theta_rad) xd = cx - rx * cost yd = cy + ry * sint if not clockwise: x, y = rotate_coords(cx, cy, x, y, 180) xd, yd = rotate_coords(cx, cy, xd, yd, 180) component = self.screen.drawing.path() command = "M {} {}".format(x, y) component.push(command) command = "A {} {} {} {} {} {} {}".format(abs(ry), abs(rx), xrot, large_arc, sweep_flag, xd, yd) component.push(command) return component, (xd, yd)
def ext_ellipse(self, major, minor, angle=360, clockwise=True): """ Extension method added to turtle instance. """ backend = self.backend orig_heading = self.heading() theta = backend.cartesian_heading(orig_heading) orig_pos = self.pos() i = orig_pos[0] j = orig_pos[1] angle_count = int(angle) pos_fn = lambda frm, to, count, i: (to * i + frm * (count - i - 1)) / (count - 1) if clockwise: start_angle = 90 p = functools.partial(pos_fn, start_angle, start_angle - angle + 1, angle_count) angles = list(map(p, range(angle_count))) else: start_angle = -90 p = functools.partial(pos_fn, start_angle, start_angle + angle - 1, angle_count) angles = list(map(p, range(angle_count))) half_major = major / 2 half_minor = minor / 2 coords = [rotate_coords(0, 0, half_major * math.cos(deg2rad(alpha)), half_minor * math.sin(deg2rad(alpha)), theta) for alpha in angles] if not clockwise: xsign = -1 ysign = 1 else: xsign = 1 ysign = -1 i = half_minor * math.sin(deg2rad(theta)) * xsign + i j = half_minor * math.cos(deg2rad(theta)) * ysign + j coords = [(i + x, j + y) for x, y in coords] self.pu() coord = coords[0] self.setpos(*coord) self.pd() for x, y in coords: self.setpos(x, y) if clockwise: turtle_heading = backend.turtle_heading_from_cartesian_heading(theta - angle) else: turtle_heading = backend.turtle_heading_from_cartesian_heading(theta + angle) self.setheading(turtle_heading)
def ellipse_simulation_(self, major, minor, angle=360, clockwise=True): """ Simulate an ellipse using many straight segments. Used in conjunction with fills and masks so that the result minimizes and gaps. """ orig_heading = self._heading theta = orig_heading orig_pos = self._pos i = orig_pos[0] j = orig_pos[1] angle_count = int(angle) pos_fn = lambda frm, to, count, i: (to * i + frm * (count - i - 1)) / (count - 1) if clockwise: start_angle = 90 p = functools.partial(pos_fn, start_angle, start_angle - angle + 1, angle_count) angles = list(map(p, range(angle_count))) else: start_angle = -90 p = functools.partial(pos_fn, start_angle, start_angle + angle - 1, angle_count) angles = list(map(p, range(angle_count))) half_major = major / 2 half_minor = minor / 2 coords = [ rotate_coords(0, 0, half_major * math.cos(deg2rad(alpha)), half_minor * math.sin(deg2rad(alpha)), theta) for alpha in angles ] if not clockwise: xsign = -1 ysign = 1 else: xsign = 1 ysign = -1 i = half_minor * math.sin(deg2rad(theta)) * xsign + i j = half_minor * math.cos(deg2rad(theta)) * ysign + j coords = [(i + x, j + y) for x, y in coords] coord = coords[0] self._line_to(coord[0], coord[1], no_stroke=True) for x, y in coords: self._line_to(x, y, no_stroke=True)
def write(self, text, move=False, align='left', font=('Arial', 8, 'normal')): """ Write text to the image. """ if move: raise errors.LogoError( "Moving the turtle to the end of the text is not supported by the SVG Turtle back end." ) x, y = self._pos x, y = rotate_coords(0, 0, y, x, -90) txt_obj = self.screen.drawing.text(text, insert=(x, y)) txt_obj['fill'] = self._pencolor txt_obj['text-anchor'] = self._text_alignments[align] font_face, font_size, font_weight = font txt_obj['style'] = "font-family:{};font-weight:{};".format( font_face, font_weight) txt_obj['font-size'] = "{}pt".format(font_size) txt_obj['transform'] = "matrix(0 1 1 0 0 0) rotate(90)" self._components.append(txt_obj)
def cartesian2svg(x, y): return rotate_coords(0, 0, y, x, -90)
def svg2cartesian(x, y): return rotate_coords(0, 0, y, x, 90)
def ellipse(self, major, minor, angle=360, clockwise=True): """ Plot an ellipse or an arc of an ellipse with axes of length `major` and `minor`. The arc will start at the turtle current position. The final position will be located `angle` degrees from the line that joins the starting position and the center of the ellipse in a direction determined by `clockwise`. """ x, y = self._pos heading = self._heading rx = major / 2 ry = minor / 2 ps = self._pensize # Assume current position is center of ellipse, then translate. if clockwise: yoff = -ry else: yoff = ry # Choose component to use. if angle != 0 and (angle % 360 == 0): component = self.screen.drawing.ellipse((0, yoff), (rx, ry)) xd, yd = x, y else: component, (xd, yd) = self.elliptic_arc_(rx, ry, angle, clockwise, yoff) xdp, ydp = xd, yd xdp, ydp = rotate_coords(0, 0, xdp, ydp, heading) xdp = xdp + x ydp = ydp + y #self._line_to(xdp, ydp, no_stroke=True) self.ellipse_simulation_(major, minor, angle, clockwise) if clockwise: self._heading = heading - angle else: self._heading = heading + angle # Component needs to be oriented and translated. transform = "translate({} {}) rotate({})".format(x, y, heading) component['transform'] = transform component['stroke'] = self._pencolor component['stroke-width'] = self._pensize component['fill-opacity'] = 1 component['class'] = 'no-fill' if self._fill_mode == 'unfill': self.add_hole_component_(component) elif self._fill_mode == 'fill': self._filled_components.append(component) component['class'] = 'fill' component['fill-opacity'] = 1 else: component['class'] = 'no-fill' component['fill-opacity'] = 0 self._components.append(component) # Compute bounds. # 1) Compute the center. cx = 0 cy = yoff cx, cy = rotate_coords(0, 0, cx, cy, heading) cx += x cy += y ps = self._pensize max_radius = max(abs(rx), abs(ry)) west = cx - max_radius east = cx + max_radius north = -(cy + max_radius) south = -(cy - max_radius) self._adjust_bounds(east - ps, north - ps) self._adjust_bounds(west + ps, south + ps)