sapeck-FinalWIP

I am using the Rotrics arm to make an image/drawing modifier. My prompt is the robot answering “this is what you should add.” Some of the responses will be humorous (doodling on faces, arrows to move things, etc.) and others will be more constructive (parallel offsetting existing lines, adding doodles inspired by parts of the drawing, etc.).

Inspiration:

  • The Useless Box: https://www.youtube.com/watch?v=aqAUmgE3WyM
  • Art Sqool: https://artsqool.cool/
  • Google Quick Draw: https://quickdraw.withgoogle.com/

To start, I am working on a robotic L.H.O.O.Q. machine. Duchamp’s original piece is a mustache on a print of the Mona Lisa.

Image source: https://en.wikipedia.org/wiki/L.H.O.O.Q.#/media/File:Marcel_Duchamp,_1919,_L.H.O.O.Q.jpg

I have so far completed the facial recognition and am working on the pen and surface location mapping. Then I will draw a mustache on every face in the frame using the Rotrics robot arm. This is how I will produce the class postcards as well. I also want to try putting an actual head into the frame.

The location tags are implemented with April Tags and the face tracking uses Media Pipe Face Mesh. Face Mesh seems to require proportionally large faces within the frame (optimized for selfie cameras), so I have to limit the camera frame to 640 px by 480 px. The April tags seem to get more accurate with larger resolution, but not too much more accurate at this range.

My current goal is mustaches, but time permitting I want to also expand it to draw other features on not just faces. For example detecting contours and drawing offsets, filling in shapes, etc.

sapeck-Proposal

I want to use the Rotrics arm to make an image/drawing modifier. My prompt is the robot answering “this is what you should have done.” Some of the responses will be humorous (doodling on faces, crossing out sections, etc.) and others will be more constructive (parallel offsetting existing lines, adding doodles inspired by parts of the drawing, etc.). I will mount a camera above the robot, and the arm will move to modify multiple drawings at a time (lined up in front of the sliding rail). I will add some placement markers to the arm and/or drawings to help the robot easily find and register them. I will use a plethora of existing algorithms for identifying contours, faces, bodies, doodles, etc. as well as some of my own.

Inspiration:

    • The Useless Box: https://www.youtube.com/watch?v=aqAUmgE3WyM
    • Art Sqool: https://artsqool.cool/
    • Google Quick Draw: https://quickdraw.withgoogle.com/

Rough Timeline

    • 11/3 Proposal
    • 11/10 Collection of algorithms for detecting parts/sketches, working with webcam
    • 11/17 Page detection, generate and visualize drawings on pages
    • 11/29 Mapping robot to pages/drawings, drawing on pages
    • 12/1 Fine tuning

 

sapeck-TilingPattern

Code:

IN;DF;PS4;

PT.7;

SP3;PU1293,1707;FT10,100;WG1000,270,45;
SP3;PU1293,1707;FT10,100;WG1000,270,45;
SP3;PU1293,2707;FT10,100;WG1000,270,45;
SP3;PU1293,2707;FT10,100;WG1000,270,45;
SP3;PU1293,3707;FT10,100;WG1000,270,45;
SP3;PU1293,3707;FT10,100;WG1000,270,45;
SP3;PU1293,4707;FT10,100;WG1000,270,45;
SP3;PU1293,4707;FT10,100;WG1000,270,45;
SP3;PU1293,5707;FT10,100;WG1000,270,45;
SP3;PU1293,5707;FT10,100;WG1000,270,45;
SP3;PU2707,1707;FT10,100;WG1000,90,45;
SP3;PU2707,1707;FT10,100;WG1000,90,45;
SP3;PU2707,2707;FT10,100;WG1000,90,45;
SP3;PU2707,2707;FT10,100;WG1000,90,45;
SP3;PU2707,3707;FT10,100;WG1000,90,45;
SP3;PU2707,3707;FT10,100;WG1000,90,45;
SP3;PU2707,4707;FT10,100;WG1000,90,45;
SP3;PU2707,4707;FT10,100;WG1000,90,45;
SP3;PU2707,5707;FT10,100;WG1000,90,45;
SP3;PU2707,5707;FT10,100;WG1000,90,45;
SP3;PU2707,2707;FT10,100;WG1000,270,45;
SP3;PU2707,2707;FT10,100;WG1000,270,45;
SP3;PU2707,1707;FT10,100;WG1000,270,45;
SP3;PU2707,1707;FT10,100;WG1000,270,45;
SP3;PU2707,3707;FT10,100;WG1000,270,45;
SP3;PU2707,3707;FT10,100;WG1000,270,45;
SP3;PU2707,4707;FT10,100;WG1000,270,45;
SP3;PU2707,4707;FT10,100;WG1000,270,45;
SP3;PU2707,5707;FT10,100;WG1000,270,45;
SP3;PU2707,5707;FT10,100;WG1000,270,45;
SP3;PU4121,1707;FT10,100;WG1000,90,45;
SP3;PU4121,1707;FT10,100;WG1000,90,45;
SP3;PU4121,2707;FT10,100;WG1000,90,45;
SP3;PU4121,2707;FT10,100;WG1000,90,45;
SP3;PU4121,3707;FT10,100;WG1000,90,45;
SP3;PU4121,3707;FT10,100;WG1000,90,45;
SP3;PU4121,4707;FT10,100;WG1000,90,45;
SP3;PU4121,4707;FT10,100;WG1000,90,45;
SP3;PU4121,5707;FT10,100;WG1000,90,45;
SP3;PU4121,5707;FT10,100;WG1000,90,45;
SP3;PU4121,2707;FT10,100;WG1000,270,45;
SP3;PU4121,2707;FT10,100;WG1000,270,45;
SP3;PU4121,1707;FT10,100;WG1000,270,45;
SP3;PU4121,1707;FT10,100;WG1000,270,45;
SP3;PU4121,3707;FT10,100;WG1000,270,45;
SP3;PU4121,3707;FT10,100;WG1000,270,45;
SP3;PU4121,4707;FT10,100;WG1000,270,45;
SP3;PU4121,4707;FT10,100;WG1000,270,45;
SP3;PU4121,5707;FT10,100;WG1000,270,45;
SP3;PU4121,5707;FT10,100;WG1000,270,45;
SP3;PU5536,1707;FT10,100;WG1000,90,45;
SP3;PU5536,1707;FT10,100;WG1000,90,45;
SP3;PU5536,2707;FT10,100;WG1000,90,45;
SP3;PU5536,2707;FT10,100;WG1000,90,45;
SP3;PU5536,3707;FT10,100;WG1000,90,45;
SP3;PU5536,3707;FT10,100;WG1000,90,45;
SP3;PU5536,4707;FT10,100;WG1000,90,45;
SP3;PU5536,4707;FT10,100;WG1000,90,45;
SP3;PU5536,5707;FT10,100;WG1000,90,45;
SP3;PU5536,5707;FT10,100;WG1000,90,45;
SP3;PU5536,2707;FT10,100;WG1000,270,45;
SP3;PU5536,2707;FT10,100;WG1000,270,45;
SP3;PU5536,1707;FT10,100;WG1000,270,45;
SP3;PU5536,1707;FT10,100;WG1000,270,45;
SP3;PU5536,3707;FT10,100;WG1000,270,45;
SP3;PU5536,3707;FT10,100;WG1000,270,45;
SP3;PU5536,4707;FT10,100;WG1000,270,45;
SP3;PU5536,4707;FT10,100;WG1000,270,45;
SP3;PU5536,5707;FT10,100;WG1000,270,45;
SP3;PU5536,5707;FT10,100;WG1000,270,45;
SP1;PU2000,1000;FT10,100;WG1000,90,45;
SP1;PU2000,2000;FT10,100;WG1000,90,45;
SP1;PU2000,3000;FT10,100;WG1000,90,45;
SP1;PU2000,4000;FT10,100;WG1000,90,45;
SP1;PU2000,5000;FT10,100;WG1000,90,45;
SP1;PU2000,2414;FT10,100;WG1000,270,45;
SP1;PU2000,3414;FT10,100;WG1000,270,45;
SP1;PU2000,4414;FT10,100;WG1000,270,45;
SP1;PU2000,5414;FT10,100;WG1000,270,45;
SP1;PU2000,6414;FT10,100;WG1000,270,45;
SP1;PU3414,1000;FT10,100;WG1000,90,45;
SP1;PU3414,2000;FT10,100;WG1000,90,45;
SP1;PU3414,3000;FT10,100;WG1000,90,45;
SP1;PU3414,4000;FT10,100;WG1000,90,45;
SP1;PU3414,5000;FT10,100;WG1000,90,45;
SP1;PU3414,3414;FT10,100;WG1000,270,45;
SP1;PU3414,2414;FT10,100;WG1000,270,45;
SP1;PU3414,4414;FT10,100;WG1000,270,45;
SP1;PU3414,5414;FT10,100;WG1000,270,45;
SP1;PU3414,6414;FT10,100;WG1000,270,45;
SP1;PU4828,1000;FT10,100;WG1000,90,45;
SP1;PU4828,2000;FT10,100;WG1000,90,45;
SP1;PU4828,3000;FT10,100;WG1000,90,45;
SP1;PU4828,4000;FT10,100;WG1000,90,45;
SP1;PU4828,5000;FT10,100;WG1000,90,45;
SP1;PU4828,3414;FT10,100;WG1000,270,45;
SP1;PU4828,2414;FT10,100;WG1000,270,45;
SP1;PU4828,4414;FT10,100;WG1000,270,45;
SP1;PU4828,5414;FT10,100;WG1000,270,45;
SP1;PU4828,6414;FT10,100;WG1000,270,45;
SP1;PU6243,1000;FT10,100;WG1000,90,45;
SP1;PU6243,2000;FT10,100;WG1000,90,45;
SP1;PU6243,3000;FT10,100;WG1000,90,45;
SP1;PU6243,4000;FT10,100;WG1000,90,45;
SP1;PU6243,5000;FT10,100;WG1000,90,45;
SP2;PU1293,1707;FT10,100;EW1000,270,45;
SP2;PU1293,1707;FT10,100;EW1000,270,45;
SP2;PU1293,2707;FT10,100;EW1000,270,45;
SP2;PU1293,2707;FT10,100;EW1000,270,45;
SP2;PU1293,3707;FT10,100;EW1000,270,45;
SP2;PU1293,3707;FT10,100;EW1000,270,45;
SP2;PU1293,4707;FT10,100;EW1000,270,45;
SP2;PU1293,4707;FT10,100;EW1000,270,45;
SP2;PU1293,5707;FT10,100;EW1000,270,45;
SP2;PU1293,5707;FT10,100;EW1000,270,45;
SP2;PU2707,1707;FT10,100;EW1000,90,45;
SP2;PU2707,1707;FT10,100;EW1000,90,45;
SP2;PU2707,2707;FT10,100;EW1000,90,45;
SP2;PU2707,2707;FT10,100;EW1000,90,45;
SP2;PU2707,3707;FT10,100;EW1000,90,45;
SP2;PU2707,3707;FT10,100;EW1000,90,45;
SP2;PU2707,4707;FT10,100;EW1000,90,45;
SP2;PU2707,4707;FT10,100;EW1000,90,45;
SP2;PU2707,5707;FT10,100;EW1000,90,45;
SP2;PU2707,5707;FT10,100;EW1000,90,45;
SP2;PU2707,2707;FT10,100;EW1000,270,45;
SP2;PU2707,2707;FT10,100;EW1000,270,45;
SP2;PU2707,1707;FT10,100;EW1000,270,45;
SP2;PU2707,1707;FT10,100;EW1000,270,45;
SP2;PU2707,3707;FT10,100;EW1000,270,45;
SP2;PU2707,3707;FT10,100;EW1000,270,45;
SP2;PU2707,4707;FT10,100;EW1000,270,45;
SP2;PU2707,4707;FT10,100;EW1000,270,45;
SP2;PU2707,5707;FT10,100;EW1000,270,45;
SP2;PU2707,5707;FT10,100;EW1000,270,45;
SP2;PU4121,1707;FT10,100;EW1000,90,45;
SP2;PU4121,1707;FT10,100;EW1000,90,45;
SP2;PU4121,2707;FT10,100;EW1000,90,45;
SP2;PU4121,2707;FT10,100;EW1000,90,45;
SP2;PU4121,3707;FT10,100;EW1000,90,45;
SP2;PU4121,3707;FT10,100;EW1000,90,45;
SP2;PU4121,4707;FT10,100;EW1000,90,45;
SP2;PU4121,4707;FT10,100;EW1000,90,45;
SP2;PU4121,5707;FT10,100;EW1000,90,45;
SP2;PU4121,5707;FT10,100;EW1000,90,45;
SP2;PU4121,2707;FT10,100;EW1000,270,45;
SP2;PU4121,2707;FT10,100;EW1000,270,45;
SP2;PU4121,1707;FT10,100;EW1000,270,45;
SP2;PU4121,1707;FT10,100;EW1000,270,45;
SP2;PU4121,3707;FT10,100;EW1000,270,45;
SP2;PU4121,3707;FT10,100;EW1000,270,45;
SP2;PU4121,4707;FT10,100;EW1000,270,45;
SP2;PU4121,4707;FT10,100;EW1000,270,45;
SP2;PU4121,5707;FT10,100;EW1000,270,45;
SP2;PU4121,5707;FT10,100;EW1000,270,45;
SP2;PU5536,1707;FT10,100;EW1000,90,45;
SP2;PU5536,1707;FT10,100;EW1000,90,45;
SP2;PU5536,2707;FT10,100;EW1000,90,45;
SP2;PU5536,2707;FT10,100;EW1000,90,45;
SP2;PU5536,3707;FT10,100;EW1000,90,45;
SP2;PU5536,3707;FT10,100;EW1000,90,45;
SP2;PU5536,4707;FT10,100;EW1000,90,45;
SP2;PU5536,4707;FT10,100;EW1000,90,45;
SP2;PU5536,5707;FT10,100;EW1000,90,45;
SP2;PU5536,5707;FT10,100;EW1000,90,45;
SP2;PU5536,2707;FT10,100;EW1000,270,45;
SP2;PU5536,2707;FT10,100;EW1000,270,45;
SP2;PU5536,1707;FT10,100;EW1000,270,45;
SP2;PU5536,1707;FT10,100;EW1000,270,45;
SP2;PU5536,3707;FT10,100;EW1000,270,45;
SP2;PU5536,3707;FT10,100;EW1000,270,45;
SP2;PU5536,4707;FT10,100;EW1000,270,45;
SP2;PU5536,4707;FT10,100;EW1000,270,45;
SP2;PU5536,5707;FT10,100;EW1000,270,45;
SP2;PU5536,5707;FT10,100;EW1000,270,45;
SP2;PU2000,1000;FT10,100;EW1000,90,45;
SP2;PU2000,1000;FT10,100;EW1000,90,45;
SP2;PU2000,2000;FT10,100;EW1000,90,45;
SP2;PU2000,2000;FT10,100;EW1000,90,45;
SP2;PU2000,3000;FT10,100;EW1000,90,45;
SP2;PU2000,3000;FT10,100;EW1000,90,45;
SP2;PU2000,4000;FT10,100;EW1000,90,45;
SP2;PU2000,4000;FT10,100;EW1000,90,45;
SP2;PU2000,5000;FT10,100;EW1000,90,45;
SP2;PU2000,5000;FT10,100;EW1000,90,45;
SP2;PU2000,2414;FT10,100;EW1000,270,45;
SP2;PU2000,2414;FT10,100;EW1000,270,45;
SP2;PU2000,3414;FT10,100;EW1000,270,45;
SP2;PU2000,3414;FT10,100;EW1000,270,45;
SP2;PU2000,4414;FT10,100;EW1000,270,45;
SP2;PU2000,4414;FT10,100;EW1000,270,45;
SP2;PU2000,5414;FT10,100;EW1000,270,45;
SP2;PU2000,5414;FT10,100;EW1000,270,45;
SP2;PU2000,6414;FT10,100;EW1000,270,45;
SP2;PU2000,6414;FT10,100;EW1000,270,45;
SP2;PU3414,1000;FT10,100;EW1000,90,45;
SP2;PU3414,1000;FT10,100;EW1000,90,45;
SP2;PU3414,2000;FT10,100;EW1000,90,45;
SP2;PU3414,2000;FT10,100;EW1000,90,45;
SP2;PU3414,3000;FT10,100;EW1000,90,45;
SP2;PU3414,3000;FT10,100;EW1000,90,45;
SP2;PU3414,4000;FT10,100;EW1000,90,45;
SP2;PU3414,4000;FT10,100;EW1000,90,45;
SP2;PU3414,5000;FT10,100;EW1000,90,45;
SP2;PU3414,5000;FT10,100;EW1000,90,45;
SP2;PU3414,3414;FT10,100;EW1000,270,45;
SP2;PU3414,3414;FT10,100;EW1000,270,45;
SP2;PU3414,2414;FT10,100;EW1000,270,45;
SP2;PU3414,2414;FT10,100;EW1000,270,45;
SP2;PU3414,4414;FT10,100;EW1000,270,45;
SP2;PU3414,4414;FT10,100;EW1000,270,45;
SP2;PU3414,5414;FT10,100;EW1000,270,45;
SP2;PU3414,5414;FT10,100;EW1000,270,45;
SP2;PU3414,6414;FT10,100;EW1000,270,45;
SP2;PU3414,6414;FT10,100;EW1000,270,45;
SP2;PU4828,1000;FT10,100;EW1000,90,45;
SP2;PU4828,1000;FT10,100;EW1000,90,45;
SP2;PU4828,2000;FT10,100;EW1000,90,45;
SP2;PU4828,2000;FT10,100;EW1000,90,45;
SP2;PU4828,3000;FT10,100;EW1000,90,45;
SP2;PU4828,3000;FT10,100;EW1000,90,45;
SP2;PU4828,4000;FT10,100;EW1000,90,45;
SP2;PU4828,4000;FT10,100;EW1000,90,45;
SP2;PU4828,5000;FT10,100;EW1000,90,45;
SP2;PU4828,5000;FT10,100;EW1000,90,45;
SP2;PU4828,3414;FT10,100;EW1000,270,45;
SP2;PU4828,3414;FT10,100;EW1000,270,45;
SP2;PU4828,2414;FT10,100;EW1000,270,45;
SP2;PU4828,2414;FT10,100;EW1000,270,45;
SP2;PU4828,4414;FT10,100;EW1000,270,45;
SP2;PU4828,4414;FT10,100;EW1000,270,45;
SP2;PU4828,5414;FT10,100;EW1000,270,45;
SP2;PU4828,5414;FT10,100;EW1000,270,45;
SP2;PU4828,6414;FT10,100;EW1000,270,45;
SP2;PU4828,6414;FT10,100;EW1000,270,45;
SP2;PU6243,1000;FT10,100;EW1000,90,45;
SP2;PU6243,1000;FT10,100;EW1000,90,45;
SP2;PU6243,2000;FT10,100;EW1000,90,45;
SP2;PU6243,2000;FT10,100;EW1000,90,45;
SP2;PU6243,3000;FT10,100;EW1000,90,45;
SP2;PU6243,3000;FT10,100;EW1000,90,45;
SP2;PU6243,4000;FT10,100;EW1000,90,45;
SP2;PU6243,4000;FT10,100;EW1000,90,45;
SP2;PU6243,5000;FT10,100;EW1000,90,45;
SP2;PU6243,5000;FT10,100;EW1000,90,45;

SP0;IN;

sapeck-Hatching

The first method plays with the vsketch point drawing method and drawing many circles at close offsets to create a new stroke. The second method is the same point but on a Perlin grid. The third method is the built-in vsketch hatching method. The fourth method was an experiment in using occult and many layers of circles. Each layer has occult run individually, with circles added randomly to each layer. The best surprise was with how well the circle-stroke (the first method) works. The Axidraw is extremely precise and able to draw these tiny circles. The very close, overlapped circles look like a new stroke. I was also surprised how vpype optimized the circles into some longer strokes in the Perlin grid.

sapeck-LostrittoReading

I found it interesting how he separates drawing, computing, and digital images. The concept of the drawing being additive and uneditable makes sense, but that a computer image is not only additive and uneditable is an implied but not always easily seen difference. It is not easily seen because we cannot make another iteration of a drawing as fast as we can make another iteration of a digital image. Sure, the plotter can make repeated drawings, but they are not actually all the same and they take time. You now have to take care into each edit or iteration, or allow for the mishaps and mistakes and unintentional parts to drive the work.

The chapter talks a lot from an architecture perspective.  An example in that field is that people don’t sketch on paper as much anymore. But the definition of sketching and drafting as drawing has been blurred. This blur exists with plotting graphics tools as well: we can visualize a plot on a screen, and we can do our best to make that visualization accurate, but there must be an understanding of how the physical medium works as well to make it worthwhile. Otherwise, plots will look like they were just printed.

sapeck-LineExercises

A: Dashed Line

/*
* sapeck_03_A_DashedLine.pde
* A dashed line between the center and the mouse
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/

int DASH_INTERVAL = 20;

void setup() {
  size(640, 480);
}

void draw() {
  background(255, 255, 255);
  
  
  float d = dist(width/2, height/2, mouseX, mouseY);
  float num = d/DASH_INTERVAL;
  for (int i = 0; i <= num; i += 2) {
    float x1 = lerp(width/2, mouseX, i/num);
    float y1 = lerp(height/2, mouseY, i/num);
    float x2 = lerp(width/2, mouseX, min(1.0, (i+1)/num));
    float y2 = lerp(height/2, mouseY, min(1.0, (i+1)/num));
    line(x1, y1, x2, y2);
  }
}

void mouseClicked() {
  saveFrame("sapeck_03_A_DashedLine_####.png");
}

B: Living Line

/*
* sapeck_03_B_LivingLine.pde
* A mouse trail
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/

int NUM_POSITIONS = 100;

// Two 2D arrays
ArrayList x_positions = new ArrayList();
ArrayList y_positions = new ArrayList();

// One 2D Array
ArrayList<Integer[]> xy_positions = new ArrayList<Integer[]>();

// One Array of Objects (PVectors)
ArrayList pv_positions = new ArrayList();

void setup() {
  size(640, 480);
}

void mouseMoved() {
  // If there are 100 points recorded, remove the first one
  // This is a FIFO list, so the items are appended to the end
  // and removed from the beginning.
  if (x_positions.size() >= 100) {
    x_positions.remove(0);
    y_positions.remove(0);
    xy_positions.remove(0);
    pv_positions.remove(0);
  }
  
  // Add the new location
  x_positions.add(mouseX);
  y_positions.add(mouseY);
  Integer[] xy = { mouseX, mouseY };
  xy_positions.add(xy);
  PVector pv = new PVector(mouseX, mouseY);
  pv_positions.add(pv);
}

void draw() {
  background(255, 255, 255);
  
  beginShape();
  for (int i = 0; i < pv_positions.size(); i++) {
    // Loop over each item in the list and add a point
    // to the line
    PVector pv = pv_positions.get(i);
    vertex(pv.x, pv.y);
  }
  endShape();
}

void mouseClicked() {
  saveFrame("sapeck_03_B_LivingLine_####.png");
}

C: Spicy Line

/*
* sapeck_03_C_SpicyLine.pde
* A mouse trail that gets spicy
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/

int NUM_POSITIONS = 100;
float RANDOMNESS_START = 1;
float RANDOMNESS_RATE = 0.008;

// One Array of Objects (PVectors)
ArrayList pv_positions = new ArrayList();

float randomness = RANDOMNESS_START;

void setup() {
  size(640, 480);
}

void mouseMoved() {
  // Increase the randomness over time of movements
  randomness += RANDOMNESS_RATE;
  
  // If there are 100 points recorded, remove the first one
  // This is a FIFO list, so the items are appended to the end
  // and removed from the beginning.
  if (pv_positions.size() >= 100) pv_positions.remove(0);
  
  // Add the new location
  float x = mouseX + random(-1 * randomness, randomness);
  float y = mouseY + random(-1 * randomness, randomness);
  PVector pv = new PVector(x, y);
  pv_positions.add(pv);
}

void draw() {
  background(255, 255, 255);
  
  beginShape();
  for (int i = 0; i < pv_positions.size(); i++) {
    // Loop over each item in the list and add a point
    // to the curve
    PVector pv = pv_positions.get(i);
    vertex(pv.x, pv.y);
  }
  endShape();
}

void mouseClicked() {
  saveFrame("sapeck_03_C_SpicyLine_####.png");
}

D: One Circle, Two Ways

Sine and Cosine Points

/*
* sapeck_03_D_OneCircleTwoWays_SINCOSPoints.pde
* A circle made from sine and cosine points
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/

int NUM_POINTS = 20;
float CIRCLE_RADIUS = 100;

// One Array of Objects (PVectors)
PVector pv_positions[] = new PVector[NUM_POINTS];

void setup() {
  size(640, 480);
  
  for (int i = 0; i < NUM_POINTS; i++) {
    float x = CIRCLE_RADIUS * cos(i * (TWO_PI / NUM_POINTS)) + (width / 2); 
    float y = CIRCLE_RADIUS * sin(i * (TWO_PI / NUM_POINTS)) + (height / 2); 
    pv_positions[i] = new PVector(x, y);
  }
}

void draw() {
  background(255, 255, 255);
  
  beginShape();
  for (int i = 0; i < NUM_POINTS; i++) {
    // Loop over each item in the list and add a point
    // to the curve
    PVector pv = pv_positions[i];
    vertex(pv.x, pv.y);
  }
  endShape(CLOSE);
}

void mouseClicked() {
  saveFrame("sapeck_03_D_OneCircleTwoWays_SINCOSPoints_####.png");
}

Cosine and Square Root

/*
* sapeck_03_D_OneCircleTwoWays_SQRT.pde
* A circle made from cosine and square root
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/

int NUM_POINTS = 20;
float CIRCLE_RADIUS = 100;

// One Array of Objects (PVectors)
PVector pv_positions[] = new PVector[NUM_POINTS];

float CIRCLE_RADIUS_SQ = pow(CIRCLE_RADIUS, 2);

void setup() {
  size(640, 480);
  
  for (int i = 0; i < NUM_POINTS; i++) { float x = CIRCLE_RADIUS * cos(i * (TWO_PI / NUM_POINTS)); float x_sq = pow(x, 2); float y = sqrt(CIRCLE_RADIUS_SQ - x_sq); float x_aligned = x + (width / 2); float y_aligned = (height / 2) + y; if (i > NUM_POINTS / 2) y_aligned -= 2 * y;
    pv_positions[i] = new PVector(x_aligned, y_aligned);
  }
}

void draw() {
  background(255, 255, 255);
  
  beginShape();
  for (int i = 0; i < NUM_POINTS; i++) {
    // Loop over each item in the list and add a point
    // to the curve
    PVector pv = pv_positions[i];
    vertex(pv.x, pv.y);
  }
  endShape(CLOSE);
}

void mouseClicked() {
  saveFrame("sapeck_03_D_OneCircleTwoWays_SQRT_####.png");
}

E: Spiral

/*
* sapeck_03_E_Spiral.pde
* A spiral made from a circle with increasing radius
* Originally created by sapeck 2021-09-14
* CMU 60-428 F21 Drawing With Machines
*/

int POINTS_PER_ROTATION = 10;
int NUM_POINTS = 50;
float RADIUS_GROWTH_PER_POINT = 4;

// One Array of Objects (PVectors)
PVector pv_positions[] = new PVector[NUM_POINTS];

float radius = 0;

void setup() {
  size(640, 480);
  
  for (int i = 0; i < NUM_POINTS; i++) {
    float theta = i * (TWO_PI / POINTS_PER_ROTATION);
    float x = radius * cos(theta) + (width / 2);
    float y = radius * sin(theta) + (height / 2);
    pv_positions[i] = new PVector(x, y);
    radius += RADIUS_GROWTH_PER_POINT;
  }
}

void draw() {
  background(255, 255, 255);
  
  beginShape();
  for (int i = 0; i < NUM_POINTS; i++) {
    // Loop over each item in the list and add a point
    // to the curve
    PVector pv = pv_positions[i];
    curveVertex(pv.x, pv.y);
  }
  endShape();
}

void mouseClicked() {
  saveFrame("sapeck_03_E_Spiral_####.png");
}

F: Parallel Polyline

For this one, I used vsketch, vpype, and shapely. It’s as simple as installing these Python packages and then running “vsk run” and it will bring a viewer, auto-reload your code, and save an SVG for you when you click the “LIKE!”button.

#!/usr/bin/env python3
#
# sapeck_03_F_ParallelPolyline.py
# Draw a teardrop-like shape with inner and outer offset lines
# Originally created by sapeck 2021-09-15
# CMU 60-428 F21 Drawing With Machines
#

from math import cos, sin

from shapely.geometry import LineString, MultiLineString
import vsketch

PI = 3.14159

class Sapeck03FParallelpolylineVskSketch(vsketch.SketchClass):
    CIRCLE_POINTS = 100
    RADIUS = 2

    def draw(self, vsk: vsketch.Vsketch) -> None:
        vsk.size("letter", landscape=False)
        vsk.scale("in")

        # This makes a teardrop-like shape
        points = [(0, 0)]
        for i in range(0, self.CIRCLE_POINTS):
          x = self.RADIUS * cos(i * (PI / self.CIRCLE_POINTS))
          y = self.RADIUS * sin(i * (PI / self.CIRCLE_POINTS)) + self.RADIUS + 1
          points.append((x, y))
        points.append((0, 0))
        
        # points is just an array of (x, y) points
        line = LineString(points)
        vsk.stroke(1)
        vsk.geometry(line)
        
        # Now make the inner and outer offsets
        vsk.stroke(2)
        offset0 = line.parallel_offset(1, 'left',  join_style=2, mitre_limit=10.0) # inner offset
        offset1 = line.parallel_offset(1, 'right', join_style=2, mitre_limit=10.0) # outer offset
        vsk.geometry(offset0)
        vsk.geometry(offset1)

    def finalize(self, vsk: vsketch.Vsketch) -> None:
        # We can automatically apply vpype settings here too!
        vsk.vpype("linemerge linesimplify reloop linesort")


if __name__ == "__main__":
    Sapeck03FParallelpolylineVskSketch.display()

G: Lines of Different Weights

#!/usr/bin/env python3
#
# sapeck_03_G_LinesOfDifferentWeights.py
# Draw different weight lines using offset curves
# Originally created by sapeck 2021-09-15
# CMU 60-428 F21 Drawing With Machines
#

from math import cos, sin

from shapely.geometry import LineString, MultiLineString
import vsketch

PI = 3.14159

class Sapeck03GLinesofdifferentweightsSketch(vsketch.SketchClass):
    CIRCLE_POINTS = 100
    RADIUS = 2
    OFFSET_AMOUNT = 0.0025
    OFFSET_NUM = 10

    def draw(self, vsk: vsketch.Vsketch) -> None:
        vsk.size("letter", landscape=False)
        vsk.scale("in")

        # This makes a teardrop-like shape
        points = [(0, 0)]
        for i in range(0, self.CIRCLE_POINTS):
          x = self.RADIUS * cos(i * (PI / self.CIRCLE_POINTS))
          y = self.RADIUS * sin(i * (PI / self.CIRCLE_POINTS)) + self.RADIUS + 1
          points.append((x, y))
        points.append((0, 0))
        
        # points is just an array of (x, y) points
        line = LineString(points)
        vsk.stroke(1)
        vsk.geometry(line)
        
        # Now make the inner offsets
        vsk.stroke(2)
        for i in range(0, self.OFFSET_NUM):
          offset = line.parallel_offset(i * self.OFFSET_AMOUNT, 'left',  join_style=2, mitre_limit=10.0) # inner offset
          vsk.geometry(offset)

    def finalize(self, vsk: vsketch.Vsketch) -> None:
        vsk.vpype("linemerge linesimplify reloop linesort")


if __name__ == "__main__":
    Sapeck03GLinesofdifferentweightsSketch.display()

H: Calligraphic Polyline

#!/usr/bin/env python3
#
# sapeck_03_H_CalligraphicPolyline.py
# Draw a calligraphic sine wave
# Originally created by sapeck 2021-09-15
# CMU 60-428 F21 Drawing With Machines
#

from math import cos, dist, sin

import numpy as np
from shapely.geometry import LineString, MultiLineString, Point
from shapely.ops import unary_union
import vsketch

PI = 3.14159

class Sapeck03HCalligraphicpolylineSketch(vsketch.SketchClass):
    CIRCLE_POINTS = 1000
    RADIUS = 3
    #OFFSET_AMOUNT = 0.0025
    OFFSET_AMOUNT = 0.0025
    OFFSET_MULTIPLIER = 600
    BLEED_OVER = 1

    def draw(self, vsk: vsketch.Vsketch) -> None:
        vsk.size("letter", landscape=True)
        vsk.scale("in")

        # This makes a teardrop-like shape
        points = []
        for i in range(0, self.CIRCLE_POINTS):
          x = i * (2 * PI / self.CIRCLE_POINTS)
          y = self.RADIUS * sin(x)
          points.append((x, y))
        
        last = None
        for point_i in range(0, len(points) - 1):
          prev_point = points[len(points) - 1] if point_i == 0 else points[point_i - 1]
          point = points[point_i]
          next_point = points[0] if point_i == len(points) - 1 else points[point_i + 1]
          change = dist(point, next_point)
          if change == 0:
            continue
          weight = 1 / (change * self.OFFSET_MULTIPLIER) # width of line
          num_lines = int(change / self.OFFSET_AMOUNT)
          orig = LineString([point, next_point])
          for i in range(1, num_lines + self.BLEED_OVER):
            next_point_shortened = orig.interpolate(i / num_lines, normalized=True)
            orig_short = LineString([Point(point[0], point[1]), next_point_shortened])
            # find perpendicular https://stackoverflow.com/a/57072678
            left = orig_short.parallel_offset(weight / 2, 'left')
            right = orig_short.parallel_offset(weight / 2, 'right')
            c = left.boundary[1]
            d = right.boundary[0]  # note the different orientation for right offset
            last = d
            vsk.geometry(LineString([c, d]))
          
          

    def finalize(self, vsk: vsketch.Vsketch) -> None:
        vsk.vpype("linemerge linesimplify reloop linesort")


if __name__ == "__main__":
    Sapeck03HCalligraphicpolylineSketch.display()

sapeck-LineWalk

SVG:PNG (screenshot):

I began with a single curve and moving it in a circle around the center. I then modified the curve to go in and out of the circle to different radii. Next I flipped the in and out movement to be a back and forth movement. Lastly, I added some twist to every other point and exaggerated these twists and extensions.

This assignment pushed my ability to completely understand my code as I was writing it. When I hit a happy accident, I made sure to really think through how my change had affected the output.

/*
* sapeck_LineWalk.pde
* A drawing consisting of a single continuous line
* Originally created by sapeck 2021-09-07
* CMU 60-428 F21 Drawing With Machines
*/

import processing.svg.*;

float[] radii = { .5, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1 };
int MULTIPLIER = 30;
int OUTER = 10;
int OUTER_TWIST = 50;
int DIVISIONS = radii.length * MULTIPLIER;
int SMALLER_COORD = min(width, height);

void setup() {
size(1056, 816); // Letter: 11"x8.5" at 96 DPI.
noLoop();
}

void draw() {
background(255);
beginRecord(SVG, "sapeck-LineWalk.svg");

stroke(0);
noFill(); // Don't create duplicate shapes!

beginShape();

int current_radius = 0;

for (int i = 0; i < (2*DIVISIONS)+3; i++) {
float radius = OUTER;
if (i % 2 == 0) radius = radii[current_radius];
float offset = 0;
if (radius != OUTER) offset -= ((OUTER - radii[current_radius])/max(radii)) * MULTIPLIER*2*PI/DIVISIONS;
float theta = i*2*PI/DIVISIONS + offset;
float scale =(radii[current_radius]*SMALLER_COORD/2);
if (radii[current_radius] == max(radii)) theta -= OUTER_TWIST*2*PI/DIVISIONS;
float x = width/2 - scale*cos(theta);
float y = height/2 - scale*sin(theta);
curveVertex(x, y);
current_radius++;
if (current_radius == radii.length) current_radius = 0;
}

endShape(CLOSE);
endRecord();
saveFrame("sapeck-LineWalk.png");
}

 

sapeck-PlotterTwitter

The most interesting things to me were the the innovative uses and color mixing. One person was painting with watercolors and even cleaning the brush and switching colors! Another was using overlapping hatching of different colors to mix colors and create different views when up-close or far back (like a printer). The disappointing pieces were the ones that were less identifiable as being drawn by a plotters. Honestly, the things with straight lines that appear the same from  a laser printer don’t look all that special. Maybe with materials and inks/drawing implements that don’t look like a laser printer would be better.

Paul Rickards (@paulrickards) created a piece by playing with mixing CMYK colors in hatching patterns. The order of the layers and closeness of the hatching pattern affect the resulting color and effect.