The kind of plotter we are making a version of is called many things like "Polargraph", "vertical plotter", "drawbot", "wall plotter" etc. They are more or less all orperating on the same principle namely a couple of stepper motors extending or retracting a known amount of wire - which in turn makes the pen, attached to a hanging "gondola" move in an xy space defined by two polar coordinate systems rather than the normal cartesian coordinate system used in "normal" plotters. Contrary to an cartesian plotter, the polargraph is easily scalable just by altering the distance between the two steppers and the length of string available.
The math behind is a little more complicated because of the need to convert the cartesian coordinates of a "drawing" to the polar coordinated used by the "drawing robot".
There is a nice list on Makerblok with links to a lot of other Polargraphs/Drawbots here
Geometrically the math is simple enough - based on Phytagoras because the two point where the wire/string pivots and the place of the gondola describes simple triangles.
Marginally Clever (who calls their drawbot "Makelangelo") has a nice description (and a better drawing) of the math behind a polargraph here.
The length of string dispensed by the steppers depend on the amount of steps per revolution as well as the circumference of the bobbins which carry the strings. In our version the steppers are offset from the bobbins - but with a 1:1 gear ratio so that doesn't have to be delt with in the math.
We use the Fab Inventory steppers (PF35T-48L4) from Nippon Pulse. The datasheet for these can be found at jameco.com. They are only 48 steps per revolution (i.e. 7.5 degrees per step) but the electronics and code we use allow for microstepping so the resolution can be increased "in software" to give the necessary precision.
In our prototype the bobbins have a diameter of 15,75mm and they are spaced 45 centimeters apart - but this distance is further reduced with tacs so the "distance between steppers" are 360 milimeters.
Remembering the relation of the circumference to the radius (C=2*PI*r) gives us an alteration in string length of just above 1 mm per full step.
When winding string onto or off the bobbins - the circumference is altered. But since we use a very thin and strong fishing line (aka "Fireline"; a non-monofilament line) with a thickness of only 0,13mm with almost 2 kg break strength, almost no stretch and almost no "shape memory" - this alteration is considered negligible with respect to the needed precision.
As we are puny humans standing on the shoulders of giants we cobbled code together from Marginally Clevers drawbot (linked to above) and "Keerbot's" c-code: here
Since we need only to draw fixed "hardcoded" graphics (a small bell icon) in known positions on the drawing surface, our solution is even simpler.
The Keerbot's c-code version looks like this:
#include “stdio.h”
#include “math.h”
#define UPR ‘q’ // here ro define the key.
#define UPL 3
#define DOWNR 5
#define DOWNL 7
#define UP_CURSOR 9
#define DOWN_CURSOR 4
#define swap(a,b) t=a;a=b;b=t;int main(int argc,char* argv[])
{
if(argc != 5 || strcmp(argv[1],”read_lines”))
{
printf(“error in command !! \n should be: %s read_lines points_file serial_out_file distanse_between_nails\n”,argv[0]);
return -1;
}
FILE* f = fopen(argv[2],”r”);
FILE* fout = fopen(argv[3],”w”);
double nail_dis = atoi(argv[4]);
int last_rl = 0,last_rr = 0;
while(!feof(f))
{
int x1,y1,x2,y2,t;
int num = fscanf(f,”%d %d %d %d\n”,&x1,&y1,&x2,&y2);
if(num != 4)
{
printf(“error in line cordinates format !! should be lines of 1 y1 x2 y2\n”);
return -1;
}
int r_start = sqrt(x1*x1 + y1*y1);
int r_end = sqrt(x2*x2 + y2*y2);
if(r_start > r_end)
{
swap(r_start,r_end);
swap(x1,x2);
swap(y1,y2);
}
fprintf(fout,”%d”,UP_CURSOR);
double l_x = x1,l_y = y1;
for(int r = r_start;r < r_end; ++r)
{
double dx = x2 – x1;
double dy = y2 – y1;
double dr = sqrt(dx*dx + dy*dy);
double D = x1*y2 – x2*y1;double cross_x1 = (D*dy + dx*sqrt(r*r*dr*dr – D*D))/(dr*dr);
double cross_y1 = y1 + dy*(cross_x1 – x1)/dx;double cross_x2 = (D*dy – dx*sqrt(r*r*dr*dr – D*D))/(dr*dr);
double cross_y2 = y1 + dy*(cross_x2 – x1)/dx;if((cross_x1 – l_x)*(cross_x1 – l_x) + (cross_y1 – l_y)*(cross_y1 – l_y) <
(cross_x2 – l_x)*(cross_x2 – l_x) + (cross_y2 – l_y)*(cross_y2 – l_y))
l_x = cross_x1,l_y = cross_y1;
else
l_x = cross_x2,l_y = cross_y2;int rl = (int)sqrt(l_x*l_x + l_y*l_y);
int rr = (int)sqrt((l_x – nail_dis)*(l_x – nail_dis) + l_y*l_y);
for(;last_rl < rl;++last_rl)
fprintf(fout,”%d”,UPL);
for(;last_rl > rl;–last_rl)
fprintf(fout,”%d”,DOWNL);for(;last_rr < rr;++last_rr)
fprintf(fout,”%d”,UPR);
for(;last_rr > rr;–last_rr)
fprintf(fout,”%d”,DOWNR);
if(r == r_start)
fprintf(fout,”%d”,DOWN_CURSOR);
}
}
return 0;
}
We had some problems compiling this, but eventually Maurice "De Programmer" came up with this:
#include#include #include /* atoi */ #define UPR 1 // here ro define the key. // motor 1 #define UPL 3 // motor 2 #define DOWNR 5 //motor 1 #define DOWNL 7 // motor2 #define UP_CURSOR 9 // servo #define DOWN_CURSOR 4 // servo #define swap(a,b) t=a;a=b;b=t; float arrx[2] = {30.0f,1.0f}; float arry[2] = {1.0,30.0f}; double nail_dis = 30.00; int last_rl = 0; int last_rr = 0; double t; int r; int j; double cartesianStep = 0.1; double machineStep=0.5; // length of minimum change in rope with one step of machine // double nail_dis = 360.00; // int last_rl = 0; // int last_rr = 0; int main(){ int i=0; int r_start = sqrt(arrx[i]*arrx[i] + arry[i]*arry[i]); // distance of hypothenusa printf("r_start: %d \n",r_start); int r_end = sqrt(arrx[i+1]*arrx[i+1] + arry[i+1]*arry[i+1]); // distance of new hypothenusa printf("r_end: %d \n",r_end); double x1 = arrx[i]; printf("x1 %0.2f \n",x1); double x2 = arrx[i+1]; printf("x2 %0.2f \n",x2); double y1 = arry[i]; printf("y1 %0.2f \n",y1); double y2 = arry[i+1]; printf("y2 %0.2f \n",y2); // double changeInx = x2 - x1; // change in x // printf("changeXLeft: %0.2f\n", changeInx); // double changeIny = y2 - y1; //change in y // printf("changeY: %0.2f\n", changeIny); // double changeInxRight = (nail_dis-x2) - (nail_dis-x1); // change in x //printf("changeXRight: %0.2f\n", changeInxRight); // double relativeSpeed = fabs(changeInx)/fabs(changeIny); double oldleftwirelength = sqrt(x1*x1+y1*y1); double oldrightwirelength = sqrt((nail_dis-x1)*(nail_dis-x1)+y1*y1); int totalCartesianSteps = fabs(x2-x1)/cartesianStep; printf("totalCartesianSteps: %d",totalCartesianSteps); for (j =0;j <=totalCartesianSteps;j++){ double changeInx = x2 - x1; // change in x // printf("changeXLeft: %0.2f\n", changeInx); double changeIny = y2 - y1; //change in y //printf("changeY: %0.2f\n", changeIny); double changeInxRight = (nail_dis-x2) - (nail_dis-x1); // change in x // printf("changeXRight: %0.2f\n", changeInxRight); double relativeSpeed = fabs(changeInx)/fabs(changeIny); // move cartesian x and y with cartesianstep if (changeInx>0) { x1 = x1 + cartesianStep; // printf("x1-: ,%0.2f\n",x1); } else {x1 = x1 - cartesianStep; // printf("x1+: ,%0.2f\n",x1); } // if cartesian x is negative then negative step else positive step // move y in ratio to x // if cartesian y is negative then negative steo else positive step if (changeIny>0) {y1 = y1 + (cartesianStep/relativeSpeed); // printf("y1-: ,%0.2f\n",y1); } else {y1 = y1 - (cartesianStep/relativeSpeed); // printf("y1+: ,%0.2f\n",y1); } double newleftwirelength = sqrt(x1*x1+y1*y1); double newrightwirelength = sqrt((nail_dis-x1)*(nail_dis-x1)+y1*y1); if(newleftwirelength>oldleftwirelength){ if(fabs(newleftwirelength-oldleftwirelength) > machineStep){ printf(" ReleaseLeft\n"); oldleftwirelength = newleftwirelength; } } if(newleftwirelength machineStep){ printf(" PullLeft\n"); oldleftwirelength = newleftwirelength; } } if(newrightwirelength>oldrightwirelength){ if(fabs(newrightwirelength-oldrightwirelength) > machineStep){ printf(" ReleaseRight\n"); oldrightwirelength = newrightwirelength; } } if(newrightwirelength machineStep){ printf(" PullRight\n"); oldrightwirelength = newrightwirelength; } } // printf("oldleftwirelength: %0.2f:\n", oldleftwirelength); // printf("newleftwirelength: %0.2f:\n", newleftwirelength); // printf("oldrightwirelength: %0.2f:\n", oldrightwirelength); // printf("newrightwirelength: %0.2f:\n", newrightwirelength); // calculate move in leftrope // move in leftrope is bigger than machinestep, make machine step } }
The Keerbot has the steppers on the gondola itself and does not take into account the circumference of bobbins which our solution has to. So our stringlength is modified by the ratio of circumference to steps.
For reference the more involved Arduino-code for the Makelangelo (Marginally Clever) looks like: this.