def circle_arc_(self, radius, angle, theta, xcenter, ycenter): """ Create a circular arc component and return it. """ x, y = self._pos rx = radius ry = radius xrot = 0 if abs(angle) > 180.0: large_arc = 1 else: large_arc = 0 if angle < 0: sweep_flag = 1 else: sweep_flag = 0 theta = (theta - 180 + angle) xdest = xcenter + math.cos(deg2rad(theta)) * radius ydest = ycenter + math.sin(deg2rad(theta)) * radius component = self.screen.drawing.path() command = "M {} {}".format(x, y) component.push(command) command = "A {} {} {} {} {} {} {}".format(abs(radius), abs(radius), xrot, large_arc, sweep_flag, xdest, ydest) component.push(command) return component
def circle(self, radius, angle, steps=None): """ The center of the circle with be `radius` units to 90 degrees left of the turtle's current heading. The turtle will trace out an arc that sweeps out `angle` degrees. """ x, y = self._pos theta = (self._heading + 90) % 360 xcenter = x + math.cos(deg2rad(theta)) * radius ycenter = y + math.sin(deg2rad(theta)) * radius self._adjust_bounds(xcenter - radius, ycenter - radius) self._adjust_bounds(xcenter + radius, ycenter + radius) if steps is None and angle != 0 and (angle % 360 == 0): component = self.screen.drawing.circle((xcenter, ycenter), radius) elif steps is None: component = self.circle_arc_(radius, angle, theta, xcenter, ycenter) else: self.regular_polygon_(radius, steps, angle, xcenter, ycenter) return component['stroke'] = self._pencolor component['stroke-width'] = self._pensize if self._fill_mode == 'unfill': self.add_hole_component_(component) elif self._fill_mode == 'fill': self._filled_components.append(component) else: component['class'] = 'no-fill' component['fill-opacity'] = 0 self._components.append(component)
def regular_polygon_(self, radius, sides, angle, xcenter, ycenter): """ Add the coordinates of a regular polygon (or segments of it) to the primary component. """ heading = self._heading step_angle = angle / sides angle = abs(angle) alpha = heading angle_offset = 0 for n in range(sides + 1): angle_offset = step_angle * n theta = deg2rad(alpha + angle_offset) x = xcenter + radius * math.cos(theta) y = ycenter + radius * math.sin(theta) if n == 0: no_stroke = True else: no_stroke = False self._line_to(x, y, no_stroke=no_stroke) if abs(angle_offset) >= angle: break if angle == 360: polyline = self._get_current_polyline() polyline['stroke-linecap'] = 'round'
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 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)