Plotter modules

Plotter

A base class for the BrachioGraph and PantoGraph subclasses.

This class provides all the interfaces you’ll need to use with the plotter in normal use, with the exception of __init__().

All the classes, including this base class, can be instantiated without any arguments and will work for testing.

For testing with turtle graphics, you will need to use one one of the sublasses.

Plotter.__init__(virtual: bool = False, turtle: bool = False, turtle_coarseness=None, bounds: tuple = [- 10, 5, 10, 15], servo_1_parked_pw: int = 1500, servo_2_parked_pw: int = 1500, servo_1_degree_ms: float = - 10, servo_2_degree_ms: float = 10, servo_1_parked_angle: float = 0, servo_2_parked_angle: float = 0, hysteresis_correction_1: float = 0, hysteresis_correction_2: float = 0, servo_1_angle_pws: tuple = (), servo_2_angle_pws: tuple = (), servo_1_angle_pws_bidi: tuple = (), servo_2_angle_pws_bidi: tuple = (), pw_up: int = 1500, pw_down: int = 1100, wait: Optional[float] = None, resolution: Optional[float] = None)
Parameters
  • virtual (bool) – A virtual plotter will run in software only, and doesn’t expect any attached hardware. This allows work and development on a machine other than a Raspberry Pi, and to run automated tests.

  • turtle (bool) – Produces a graphical representation of the plotter and its behaviour using Python turtle graphics, as well as or instead of a physical plotter.

  • turtle_coarseness (float or None) – For use with turtle; a factor, in degrees, to represent the resolution of the servos by rounding values. Defaults to 1˚ if not specified.

  • bounds (list or tuple) – Four numbers, indicating the area that the plotter should treat as its available area for drawing in. The numbers represent, in order the left, top, right and bottom boundaries. Defaults to usable values in the default subclass definitions.

  • servo_1_parked_pw (int) – The pulse-width of servo 1 when parked.

  • servo_2_parked_pw (int) – The pulse-width of servo 2 when parked.

  • servo_1_degree_ms (float) – Milliseconds pulse-width difference per degree of movement.

  • servo_2_degree_ms (float) – Milliseconds pulse-width difference per degree of movement.

  • servo_1_parked_angle (float) – The arm angle in the parked position.

  • servo_2_parked_angle (float) – The arm angle in the parked position.

  • hysteresis_correction_1 (float) – Servo 1 hysteresis error compensation.

  • hysteresis_correction_2 (float) – Servo 2 hysteresis error compensation.

  • servo_1_angle_pws (tuple) – Pulse-widths for various angles of servo 1.

  • servo_2_angle_pws (tuple) – Pulse-widths for various angles of servo 2.

  • servo_1_angle_pws_bidi (tuple) – Pulse-widths for various angles of servo 1, collected in both clockwise and anti-clockwise directions. This is introduced in the tutorial.

  • servo_2_angle_pws_bidi (tuple) – Pulse-widths for various angles of servo 2, collected in both clockwise and anti-clockwise directions.

  • pw_up (int) – The pulse-width for the pen’s up position.

  • pw_down (int) – The pulse-width for the pen’s down position.

  • wait (float or None) – A time in seconds that the plotter will rest after making a movement. If not specified, defaults to 0.1, or 0 for a virtual-only plotter.

  • resolution (float or None) – A distance in centimetres. When drawing between two points, any line longer than resolution will be broken down into a series of points no more than resolution apart. This allows the plotter to approximate straight lines by drawing a series of shorter curved lines (all the lines the plotter naturally draws are curved). If not specified, defaults to 1.

In all the methods below, arguments that are also atrributes of the plotter class need only be used to override those values (which is generally not required).

Plotting

Plotter.plot_file(filename='', wait=None, resolution=None, bounds=None)

Plots and image encoded as JSON lines in filename. Passes the lines in the supplied JSON file to plot_lines().

Plotter.plot_lines(lines=[], wait=None, resolution=None, rotate=False, flip=False, bounds=None)

Passes each segment of each line in lines to draw_line()

Drawing according to x/y values

Plotter.box(bounds=None, wait=None, resolution=None, repeat=1, reverse=False)

Draw a box marked out by the bounds.

Plotter.test_pattern(bounds=None, lines=4, wait=None, resolution=None, repeat=1, reverse=False, both=False)
Plotter.vertical_lines(bounds=None, lines=4, wait=None, resolution=None, repeat=1, reverse=False, both=False)
Plotter.horizontal_lines(bounds=None, lines=4, wait=None, resolution=None, repeat=1, reverse=False, both=False)
Plotter.draw_line(start=(0, 0), end=(0, 0), wait=None, resolution=None, both=False)

Draws a line between two points

Plotter.xy(x=None, y=None, wait=None, resolution=None, draw=False)

Moves the pen to the xy position; optionally draws while doing it. None for x or y means that the pen will not be moved in that dimension.

Drawing according to servo angle values

Plotter.move_angles(angle_1=None, angle_2=None, wait=None, resolution=None, draw=False)

Moves the servo motors to the specified angles step-by-step, calling set_angles() for each step. resolution refers to degrees of movement. None for one of the angles means that that servo will not move.

Pen-moving methods

Plotter.set_angles(angle_1=None, angle_2=None)

Moves the servo motors to the specified angles immediately. Relies upon getting accurate pulse-width values. None for one of the angles means that that servo will not move.

Calls set_pulse_widths().

Sets current_x, current_y.

Plotter.park()

Park the plotter.

Angles to pulse widths

A plotter needs to move its arms to the correct angles, by providing the appropriate pulse-width to each servo.

Plotter.angles_to_pw_1()
Plotter.angles_to_pw_2()

These methods - one for each servo - take the angle as an argument and return a pulse-width.

The methods themselves stand in for functions that do the actual calculation; which function is assigned to the angles_to_pw_1/angles_to_pw_2 attributes depends upon how much information is provided about the servos when the plotter is initialised.

Naive calculation

The default is to use “naive” functions (naive_angles_to_pulse_widths_1 and naive_angles_to_pulse_widths_2), that assume linearity (1˚ of movement corresponds to a 10µs change in pulse-width), will be used.

Plotter.naive_angles_to_pulse_widths_1(angle)

A rule-of-thumb calculation of pulse-width for the desired servo angle

Plotter.naive_angles_to_pulse_widths_2(angle)

A rule-of-thumb calculation of pulse-width for the desired servo angle

Sophisticated calculation

In practice the response of servos is not linear. If a series of pulse-width/angle values are supplied, then numpy (numpy.poly1d(numpy.polyfit)) will provide a polynomial funtion that matches the curve corresponding to those values.

Line processing

Plotter.analyse_lines(lines=[], rotate=False, bounds=None)

Analyses the co-ordinates in lines, and returns:

  • rotate: True if the image needs to be rotated by 90˚ in order to fit better

  • x_mid_point, y_mid_point: mid-points of the image

  • box_x_mid_point, box_y_mid_point: mid-points of the bounds

  • divider: the value by which we must divide all x and y so that they will fit safely inside the bounds.

lines is a tuple itself containing a number of tuples, each of which contains a number of 2-tuples:

[
    [
        [3, 4],                               # |
        [2, 4],                               # |
        [1, 5],  #  a single point in a line  # |  a list of points defining a line
        [3, 5],                               # |
        [3, 7],                               # |
    ],
    [            #  all the lines
        [...],
        [...],
    ],
    [
        [...],
        [...],
    ],
]
Plotter.rotate_and_scale_lines(lines=[], rotate=False, flip=False, bounds=None)

Rotates and scales the lines so that they best fit the available drawing bounds.

Physical control

Plotter.set_pulse_widths(pw_1=None, pw_2=None)

Applies the supplied pulse-width values to the servos, or pretends to, if we’re in virtual mode.

Plotter.get_pulse_widths()

Returns the actual pulse-widths values; if in virtual mode, returns the nominal values - i.e. the values that they might be.

Plotter.quiet(servos=[14, 15, 18])

Stop sending pulses to the servos, so that they are no longer energised (and so that they stop buzzing).

Manual driving

Plotter.drive()

Control the pulse-widths using the keyboard.

The controls are:

Exit

-10 µs

-1 µs

+ 10 µs

+ 1 µs

0

Servo 1

a

A

s

S

Servo 2

k

K

l

L

Plotter.drive_xy()

Control the x/y position using the keyboard.

The controls are:

Exit

-1 cm

-1 mm

+ 1 cm

+ 1 mm

0

Servo 1

a

A

s

S

Servo 2

k

K

l

L

Reporting

Plotter.status()

Provides a report of the plotter status. Subclasses should override this to report on their own status.

Trigonometry

Plotter.xy_to_angles(x=0, y=0)

Return the servo angles required to reach any x/y position. This is a dummy method in the base class; it needs to be overridden in a sub-class implementation.

Plotter.angles_to_xy(angle_1, angle_2)

Return the servo angles required to reach any x/y position. This is a dummy method in the base class; it needs to be overridden in a sub-class implementation.

BrachioGraph

BrachioGraph.__init__(virtual: bool = False, turtle: bool = False, turtle_coarseness=None, bounds: tuple = [- 8, 4, 6, 13], inner_arm: float = 8, outer_arm: float = 8, servo_1_parked_pw: int = 1500, servo_2_parked_pw: int = 1500, servo_1_degree_ms: int = - 10, servo_2_degree_ms: int = 10, servo_1_parked_angle: int = - 90, servo_2_parked_angle: int = 90, hysteresis_correction_1: int = 0, hysteresis_correction_2: int = 0, servo_1_angle_pws: tuple = [], servo_2_angle_pws: tuple = [], servo_1_angle_pws_bidi: tuple = [], servo_2_angle_pws_bidi: tuple = [], pw_up: int = 1500, pw_down: int = 1100, wait: Optional[float] = None, resolution: Optional[float] = None)

Parameters are as for the Plotter parent class, except for:

Parameters
  • inner_arm (float) – The length of the inner arm, in cm.

  • outer_arm (float) – The length of the outer arm, in cm.