How to use Processing in Android mode with the IOIO board

March 04, 2012

A little while ago I was asked whether it would be possible to control hardware from a phone with the IOIO board from code written in Processing’s Android mode.  Turns out you can – making it quite easy to develop Android apps that can control hardware in the real world – which is awesome.  I didn’t find documentation on how to do this elsewhere online, so I thought I should post the gist of it here for posterity.

This is the IOIO board:

CC BY-NC-SA from Sparkfun.com

 

1.  Download and install the Android SDK.  This link will send you to the download page, which also has a guide worth reading.

2.  Download and install Processing 2.0a4 or later (or the latest version – alpha releases are coming pretty frequently) – support for Android in Processing 1.5.1 no longer works.

2.5. Before trying anything with the IOIO board, you may want to make sure that Processing in Android mode works – plenty can go wrong just getting this set up. I highly recommend reading the Processing in Android wiki page (if you haven’t already) before going too far.

3.  Download this version (ioio.zip) of Ytai’s IOIO library (equivalent to 3.11), exported in .jar format for use in Processing.

4.  Unzip ioio.zip and install it as you would a Processing library (inside a folder called ‘libraries’ in your Processing sketch folder).

5.  Open processing – You should see ‘ioio’ as an option under the ‘sketch’ -> ‘import library’ option

6. The import statements should like this:

import ioio.lib.util.*;
import ioio.lib.impl.*;
import ioio.lib.api.*;
import ioio.lib.api.exception.*;

7.  Go here: http://code.google.com/p/apwidgets/downloads/list  and download the latest (and awesome!) APWidgets library, and put it in the same libraries folder that you put the ioio library.  (this is optional, but if you want any GUI elements like the buttons or checkboxes used in the examples below, you’ll want it).

8. There are a few basic things to remember when doing Android development in Processing for the IOIO board.

  • EVERY sketch you write for the IOIO board needs to have ‘internet’ permissions.  To enable this, go to the ‘Android’ menu in Processing, select ‘Sketch Permissions’, and scroll down until you see the ‘INTERNET’ checkbox, check it, and save.
  • Theory wise, the IOIO functions need to be put in their own thread – if you’re not familiar with threads in Processing, this post from Dan Shiffman is a great place to start.
  • Usage of basic functions from the IOIO library is exactly as it appears in most of Ytai’s examples, but examples (like his HelloIOIO example) that use the AbstractIOIOActivity don’t work.  That’s because we can’t make or use a separate Android activity straight from Processing.
9.  Example 1: Turning on and off the onboard LED.  For this you’ll want a Processing sketch with two tabs (optional, but cleaner) – one for your Processing stuff, and one for our IOIO thread class.
Here’s the first tab:
/* IOIO Test 2 -
 * Toggling the onboard LED on the IOIO board and changing rectangle size through Processing in Android mode
 * by Ben Leduc-Mills
 * This code is Beerware - feel free to reuse and credit me, and if it helped you out and we meet someday, buy me a beer.
 */
 
//import apwdidgets
import apwidgets.*;
 
//import ioio
import ioio.lib.util.*;
import ioio.lib.impl.*;
import ioio.lib.api.*;
import ioio.lib.api.exception.*;
 
//make a widget container and a button
APWidgetContainer widgetContainer;
APButton button1;
 
//our rectangle size
int rectSize = 100;
 
//boolean to turn the light on or off
boolean lightOn = false;
 
//create a IOIO instance
IOIO ioio = IOIOFactory.create();
 
//create a thread for our IOIO code
myIOIOThread thread1; 
 
void setup() {
 
  //instantiate our thread
  thread1 = new myIOIOThread("thread1", 100);
  //start our thread
  thread1.start();
 
  size(480, 800);
  smooth();
  noStroke();
  fill(255);
  rectMode(CENTER);     //This sets all rectangles to draw from the center point
 
  //create new container for widgets
  widgetContainer = new APWidgetContainer(this); 
 
  //create new button from x- and y-pos. and label. size determined by text content
  button1 = new APButton(10, 10, "Toggle LED"); 
 
  //place button in container
  widgetContainer.addWidget(button1);
}
 
void draw() {
  background(#FF9900);
  rect(width/2, height/2, rectSize, rectSize);
}
 
//onClickWidget is called when a widget is clicked/touched
void onClickWidget(APWidget widget) {
 
  if (widget == button1) { //if it was button1 that was clicked
    //rectSize = 100; //set the smaller size
 
    if (lightOn == true) {
      lightOn = false;
      rectSize = 50;
    }
    else if (lightOn == false) {
      lightOn = true;
      rectSize = 100;
    }
  }
}
And here’s the IOIO thread class:
/* This is our thread class, it's a subclass of the standard thread class that comes with Processing
 * we're not really doing anything dramatic, just using the start and run methods to control our interactions with the IOIO board
 */
 
class myIOIOThread extends Thread {
 
  boolean running;  //is our thread running?
  String id; //in case we want to name our thread
  int wait; //how often our thread should run
  DigitalOutput led;  //DigitalOutput type for the onboard led
  int count; //if we wanted our thread to timeout, we could put a counter on it, I don't use it in this sketch
 
  //our constructor
  myIOIOThread(String s, int w) {
 
    id = s;
    wait = w;
    running = false;
    count = 0;
  }
 
  //override the start method
  void start() {
    running = true;
 
    //try connecting to the IOIO board, handle the case where we cannot or the connection is lost
    try {
      IOIOConnect();  //this function is down below and not part of the IOIO library
    }
    catch (ConnectionLostException e) {
    }
 
    //try setting our led pin to the onboard led, which has a constant 'LED_PIN' associated with it
    try {
      led = ioio.openDigitalOutput(IOIO.LED_PIN);
    }
    catch (ConnectionLostException e) {
    }
 
    //don't forget this
    super.start();
  }
 
  //start automatically calls run for you
  void run() {
 
    //while our sketch is running, keep track of the lightOn boolean, and turn on or off the led accordingly
    while (running) {
      //count++;
 
      //again, we have to catch a bad connection exception
      try {
        led.write(lightOn);
      }
      catch (ConnectionLostException e) {
      }
 
      try {
        sleep((long)(wait));
      }
      catch (Exception e) {
      }
    }
  }
 
 //often we may want to quit or stop or thread, so I include this here but I'm not using it in this sketch
  void quit() {
    running = false;
    ioio.disconnect();
    interrupt();
  }
 
//a simple little method to try connecting to the IOIO board
  void IOIOConnect() throws ConnectionLostException {
 
    try {
      ioio.waitForConnect();
    }
    catch (IncompatibilityException e) {
    }
  }
}

10.  The code should compile both with an emulator and on the phone, though obviously it won’t work through the emulator.  To send to the phone, plug your phone into the computer and select ‘sketch’ -> ‘run on device’.  Processing should detect, install, and launch the sketch on your device.  Yay!

11.  After the sketch is installed on your phone, you’ll have to unplug it from your computer, and plug it into your IOIO board.  Make SURE you have USB debugging activated.  Your phone should give some indication that it has a USB connection when you plug it in.

12.  You should see your sketch in along with the other apps – go ahead and launch it.  The button should toggle on and off the yellow LED on the IOIO board. Success! (Note: If it doesn’t work, try relaunching the app a few times – this fixed things for me a few times, though your milage may vary.)

13. Example 2: Toggling a real LED, and changing the color of a rectangle based on analog values from a potentiometer.

For this to work, you’ll need an LED properly (e.g., with a resistor of appropriate value) hooked up to pin 3 of your IOIO board, and a trim pot (or other analog sensor) hooked up to pin 37.

Main Tab:

/* IOIO Test 4 -
 * Toggling a real LED on pin 3, and reading values from a potentiometer on pin 37 through Processing in Android mode
 * by Ben Leduc-Mills
 * This code is Beerware - feel free to reuse and credit me, and if it helped you out and we meet someday, buy me a beer.
 */
 
import apwidgets.*;
 
import ioio.lib.util.*;
import ioio.lib.impl.*;
import ioio.lib.api.*;
import ioio.lib.api.exception.*;
 
APWidgetContainer widgetContainer;
APButton button1;
int rectSize = 100;
boolean lightOn = false;
 
IOIO ioio = IOIOFactory.create();
myIOIOThread thread1; 
 
void setup() {
  thread1 = new myIOIOThread("thread1", 100);
  thread1.start();
 
  size(480, 800);
  smooth();
  noStroke();
  fill(255);
  rectMode(CENTER);     //This sets all rectangles to draw from the center point
 
  widgetContainer = new APWidgetContainer(this);
  button1 = new APButton(10, 10, "Toggle LED");
  widgetContainer.addWidget(button1); //place button in container
}
 
void draw() {
  background(#FF9900);
  //change the fill value based on the analog read of our potentiometer
  fill(thread1.value * 100); //it's * 100 because we only get values from 0-1, so really this should be * 255, or use the map function
  rect(width/2, height/2, rectSize, rectSize);
}
 
//onClickWidget is called when a widget is clicked/touched
void onClickWidget(APWidget widget) {
 
  if (widget == button1) { //if it was button1 that was clicked
 
    if (lightOn == true) {
      lightOn = false;
      rectSize = 50;
    }
    else if (lightOn == false) {
      lightOn = true;
      rectSize = 100;
    }
  }
}

 

And our IOIO thread, with some slight changes:

 

class myIOIOThread extends Thread {
 
  boolean running;
  String id;
  int wait;
  DigitalOutput led;
  AnalogInput in;
  int count;
  int ledpin = 3; //pin for our led
  int potpin = 37; // pin for our potentiometer
  float value; //our analog values range from 0 to 1
 
  myIOIOThread(String s, int w) {
 
    id = s;
    wait = w;
    running = false;
    count = 0;
 
  }
 
  void start() {
    running = true;
 
    try {
      IOIOConnect();
    }
    catch (ConnectionLostException e) {
    }
 
    try {
 
      led = ioio.openDigitalOutput(ledpin);
      in = ioio.openAnalogInput(potpin);
    }
    catch (ConnectionLostException e) {
    }
 
    super.start();
  }
 
  void run() {
 
    while (running) {
      //count++;
      try {
        led.write(lightOn);
        value = in.read();
      }
      catch (ConnectionLostException e) {
      }
      catch (InterruptedException e) {
      }
 
      try {
        sleep((long)(wait));
      }
      catch (Exception e) {
      }
    }
  }
 
  void quit() {
    running = false;
    //led.close();
    ioio.disconnect();
    interrupt();
  }
 
  void IOIOConnect() throws ConnectionLostException {
 
    try {
      ioio.waitForConnect();
    }
    catch (IncompatibilityException e) {
    }
  }
}

 

14.  Upload to your phone as before, hook up your phone to the IOIO board, and launch the app. The button should turn on and off the LED, and the trim pot should change the color of the rectangle in the middle of the screen.  Huzzah!

Here are some pics: for the visual learners out there:

The circuit for the second example, without the phone plugged in

With the phone connected, and the led toggle on

 

With the led toggle off, and the potentiometer turned to make the square grey

 

Comments (0) | More: Code

DangerShield VJDJ

December 30, 2011

Since I’ve been asked to teach a course on Processing at SparkFun Electronics using the Danger Shield, I thought I should whip up something extra to show them a few of the things I won’t have time to go over in class (in it’s only 1 day, mind you). So, I decided to do a little vj/dj control with the sliders and buttons on the Danger Shield, making use of the Video and Audio (Minim) libraries in Processing. The Video below has no sound, but I’ve just tied some audio samples to the buttons, turning them into sample triggers. The video manipulation is abased off code by my former teacher at ITP, Dan Shiffman, except I’ve changed and added a few things to change the shape and rotation of the pixels in accordance with the different sliders on the Danger Shield.


Code for the processing side of the VJDJ can be found here.

Comments (0) | More: Projects

Dangershield VJDJ Code

NOTE – this code is for Processing, but was written in Eclipse with the help of the wonderful Proclipsing plugin, so you may need to make some adjustments if you’re running it straight from the Processing IDE.

/*
 * Danger Shield VJDJ
 * Using the Danger Shield from SparkFun to do live Video and Audio manipulation
 * by Ben Leduc-Mills - http://benatwork.cc
 * video code based off code by Dan Shiffman - http://shiffman.net
 * This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license)
 */
 
import processing.core.PApplet;
import processing.serial.Serial;
import processing.video.*;
import ddf.minim.*;
 
public class DangerShieldAV extends PApplet {
 
	Capture myCamera;
	MovieMaker mm;
 
	Minim minim;
	AudioSample kick;
	AudioSample snare;
 
	Serial usbPort;
	// Size of each cell in the grid
	int cellSize = 20;
	// Number of columns and rows in our system
	int cols, rows;
	//int[] sensors;
	String[] sensors;
	boolean firstContact = false;
 
	int slider1, slider2, slider3;
	int button1, button2, button3;
	int photoCell;
 
	public void setup() {
		size(800,600);
 
		minim = new Minim(this);
		kick = minim.loadSample("kick.wav", 2048);
		snare = minim.loadSample("snare.wav", 2048);
 
		myCamera = new Capture(this, width, height, 15);
		mm = new MovieMaker(this, width, height, "drawing.mov");
		cols = width / cellSize;
		rows = height / cellSize;
		colorMode(RGB, 255, 255, 255, 100);
 
		usbPort = new Serial (this, Serial.list( )[0], 9600);
		usbPort.bufferUntil ('\n');
		background(0);
	}
 
	public void draw() {
 
		doSounds();
 
		if (myCamera.available()) {
			myCamera.read();
			myCamera.loadPixels();
 
			// Begin loop for columns
			for (int i = 0; i < cols; i++) {
				// Begin loop for rows
				for (int j = 0; j < rows; j++) {
 
					int x = i*cellSize;
					int y = j*cellSize;
					int loc = (myCamera.width - x - 1) + y*myCamera.width; // Reversing x to mirror the image
 
					float r = red(myCamera.pixels[loc]);
					float g = green(myCamera.pixels[loc]);
					float b = blue(myCamera.pixels[loc]);
					// Make a new color with an alpha component
					int c = color(r, g, b, 55);
 
					// Code for drawing a single rect
					// Using translate in order for rotation to work properly
					pushMatrix();
					translate(x+cellSize/2, y+cellSize/2);
 
					// Rotation formula based on slider1 value
					rotate((float) (2 * PI * brightness(c) / slider1));
					rectMode(CENTER);
 
					fill(c);
					noStroke();
					// Width and height of rects are based on slider2 and slider3 values
					rect(0, 0, slider2/4, slider3/4);
					popMatrix();
				}
			}
		}
 
		mm.addFrame();
	}
 
	public void doSounds() {
 
		//if button1 is pressed, play kick sound
		if(button1 == 0) {
 
			kick.trigger();
 
		}
 
		//if button2 is pressed, play snare sound
		if(button2 == 0) {
 
			snare.trigger();
		}
	}
 
	public void keyPressed() {
		  if (key == ' ') {
		    // Finish the movie if space bar is pressed
		    mm.finish();
		    // Quit running the sketch once the file is written
		    exit();
		  }
		}
 
	public void stop()
	{
	  // always close Minim audio classes when you are done with them
	  kick.close();
	  snare.close();
	  minim.stop();
 
	  super.stop();
	}
 
	public void serialEvent ( Serial usbPort ) {
		String usbString = usbPort.readStringUntil ('\n');
		if (usbString != null) {
			usbString = trim(usbString);
 
			//serial handshake
			if (firstContact == false) {
				if (usbString.equals("Hello")) {
					usbPort.clear();
					firstContact = true;
					usbPort.write('A');
					println("contact");
				}
			}
			else {
 
				//we got something, put the sensor values in their own variables
				sensors = split(usbString, ',');
				for (int sensorNum = 0; sensorNum < sensors.length; sensorNum++) {
				}
 
				slider1 = Integer.parseInt(sensors[0]);
				button1 = Integer.parseInt(sensors[1]);
				slider2 = Integer.parseInt(sensors[2]);
				button2 = Integer.parseInt(sensors[3]);
				slider3 = Integer.parseInt(sensors[4]);
				button3 = Integer.parseInt(sensors[5]);
				photoCell = Integer.parseInt(sensors[7]);
 
				//request more data!
				usbPort.write("A");
			}
		}
	}
 
}
Comments (0) | More: Code

12.28.11 – New UCube Video!

December 28, 2011

This is an updated video showing some of the latest work on the UCube (mostly software-related). Production value is a little higher than last time, thanks to Final Cut.

Comments (4) | More: News

Celebrity Twitter Generator

December 18, 2011

The Celebrity Twitter Generator (CTE) collects tweets from 30 different celebrities, and creates a language model out of each one, thus allowing a front-end GUI to dynamically create a tweet in the style of a given celebrity. The program works by creating Markov Chains with a variable N-Gram size, which allows the user to adjust the coherency or accuracy of the generated tweet. As part of evaluating how well the CTE works, put 301 generated tweets (10 from each celebrity, expect 1 which had 11) onto mechanical turk and had 10 turkers for each tweet attempt to guess the author of the tweet (for a total of 3010 guesses). The system performed admirably, with 1933 out of 3010 guesses being correct (64.22%). Currently this lives as a Java applet, but will soon be on this website for your enjoyment. Update: Now online, converted to a processing app, check it out here: http://benatwork.cc/CTE/applet/index.html

Comments (0) | More: Projects

ioCane

The ioCane is a mobility aid for blind cane users that incorporates the use of ultrasonic sensors and computer vision algorithms with the Android mobile operating system, to pro- vide a plug-and-play solution for the visually impaired that has the potential to significantly enhance mobility and object avoidance with a minimal learning curve. The system functions by taking in readings from three separate ultrasonic sensors placed along the cane and sending the data to a circuit board built to interface with Android mobile devices. The board then sends the sensor data (via Bluetooth) to our ioCane application on the mobile phone, which determines a threshold indicating whether the user is close to hitting an object. If so, the application vibrates (increasing intensity with the proximity of the object) or chimes (3 different tones, dependent on the height of the object detected) to alert the user to avoid the object. In addition, the ioCane application runs a series of computer vision algorithms to detect and alert the user if specific objects of interest are approaching. The sensors and board can fit directly onto a user’s existing cane, are extremely lightweight (under 400 grams), and can run off battery power. In collaboration with Shashank Bharadwaj and Patrick Cromer.
UPDATE: the latest code is available HERE as a repo on bitbucket.

Comments (0) | More: Projects

12.15.11 – Update

December 15, 2011

Holy smokes. It’s been far too long. I’m finally done with finals so I have a teeny amount of time to update the website with all the stuff I’ve been working on.  I had an awesome summer at SparkFun, and I’m still there on occasion as an Education Outreach Coordinator.  In fact I’ll be teaching a class called ‘Processing the Danger Shield’ at SparkFun in Febuary, sign up here if you’re interested.  Also in the works is an Android/Electric Sheep/IOIO Board Class which should be awesome.  I’ll be updating the site in the next few weeks with project content, including some of the work I did at SparkFun over the summer, a sensor-augmented blind cane, a celebrity tweet generator, and more. Whew!

Comments (0) | More: News

4.18.11 Update: SparkFun Summer, IDC 2011

April 19, 2011

I’ve added a few newer projets to the website, ModelHome v2 (w/ Nick O’Brien) and SocialHealth (w/ Sears Merritt). I also have a few pieces of good news: This summer, I’ll be working at SparkFun Electronics – one of the big names in DIY/Open-Source Hardware and Electronics. I’ll be working with their education department creating and documenting beginning electronics projects, creating curricula for K-12 schools, and potentially helping out with SparkFun’s in-house classes. Needless to say, I’m very excited.
The other piece of good news: My first conference paper submission, with my advisor Mike Eisenberg, for IDC 2011 (Interaction Design and Children), an ACM SIGCHI conference, has been accepted. It covers the initial version of the UCube research, and is titled “The UCube: a Child-Friendly Device for Introductory Three-Dimensional Design”. Mike was very generous in putting me as first author, and I’m lucky and excited to have Mike and an accepted paper to a competitive conference in my first year. I’ll be traveling to Ann Arbor, Michigan in mid-June to present the paper.

Comments (2) | More: News

SocialHealth

SocialHealth, created with Sears Merritt, is a web and mobile app that tries to answer the question: “Is there a bug going around?”. We created the Android application as part of our Object Oriented Analysis & Design Course at CU Boulder. The Android app logs you in using the Facebook API, and communicates with the web app to display your location, the number of symptom reports in your area, and the number of symptom reports among your friends. You can also submit a new symptom report as well as view a Google Map that shows the symptom reports in your area.

Comments (0) | More: Projects

ModelHome v2

April 18, 2011

ModelHome version 2 is an interactive art piece in collaboration with Nick O’Brien. I programmed Nick’s Kinect to use user hand gestures to warp/distort/rotate/extrude/change the texture of an .obj model of a house that Nick designed. The type of distortion was dependent on the quadrant of the screen the Kinect thought your hand was in, and the degree/severity of the change was influenced by the distance from center. Made possible through Dan Shiffman’s OpenKinect library for Processing, and the ObjLoader library by SAITO and Matt Ditton.

Comments (0) | More: Projects

UCube v08 – now with OpenGL and Toxiclibs!

February 17, 2011

Significant code upgrade in UCube v08 – I switched graphics libraries from P3D to OpenGL, and — more importantly — stabilized the .stl file export by switching to toxiclibs – now pretty much any .stl file will be able to print straightaway, no sliceform errors, no adjustments necessary!  Plus the graphics just look a lot smoother.

Evidence:

UCUbe v08   
/* UCube v.08
 * 3d modeling input device and stl export
 * now using toxiclibs and opengl
 * Manual rotation, shape mode toggle, and export button
 * by Ben Leduc-Mills - 2.10.11
 */
 
import processing.opengl.*;
import newhull.*;
import java.awt.event.*;
import toxi.geom.*;
import toxi.geom.mesh.*;
import toxi.processing.*;
import controlP5.*;
import processing.serial.*;
 
import javax.media.opengl.GL;
 
ControlP5 controlP5;
//Nav3D nav; // camera controller
 
QuickHull3D hull = new QuickHull3D(); //init quickhull
Point3d[] points; //init Point3d array
Point3d[] savedPoints;
 
ToxiclibsSupport gfx;
Mesh3D mesh = new TriangleMesh();
Vec3D[] vectors;
 
Serial myPort; // the serial port
 
float rotX, rotY; //for manual roatation
int gridSize = 4; //size of grid (assumes all dimensions are equal)
int spacing = 50; //distance between points
int counter = 0; //wireframe toggle
String inString; //string of coordinates from arduino
String oldString;
boolean reDraw = true;
PFont myFont; //init font for text
 
PGraphicsOpenGL pgl;
GL gl;
 
void setup() {
 
  size(1400,850,OPENGL);
  frameRate(13);
  gfx=new ToxiclibsSupport(this); // initialize ToxiclibsSupport
  //background(255); 
 
  pgl = (PGraphicsOpenGL) g; //processing graphics object
  gl = pgl.beginGL(); //begin opengl
  gl.setSwapInterval(1); //set vertical sync on
  pgl.endGL(); //end opengl
 
  controlP5 = new ControlP5(this);
  controlP5.addButton("Mode", 0,100,120,80,19);
  controlP5.addButton("Export", 0,100,140,80,19);
 
  myFont = createFont("FFScala", 32);
  textFont(myFont);
 
  println(Serial.list()); // list available serial ports
  myPort = new Serial(this, Serial.list()[0], 19200);
  myPort.bufferUntil('\n');
 
} 
 
void draw() {
 
  //}
 
  //void serialEvent(Serial myPort) {
 
  if (myPort.available() > 0) {
  background(255);
  smooth();
  // because we want controlP5 to be drawn on top of everything
  // else we need to disable OpenGL's depth testing at the end
  // of draw(). that means we need to turn it on again here.
  hint(ENABLE_DEPTH_TEST);
 
  pushMatrix();
  //lights();
  translations();
  drawGrid();
  drawAxes();
 
  String inString = myPort.readStringUntil('\n');
  //String m1[] = match(inString, "[0-3]");
 
  //TODO: compare inString to oldString to see if coords changed
 
  //if a coordinate string is coming in from arduino
  if (inString != null) {
    //make acive points more visible
    strokeWeight(8);
    stroke(255, 0, 0); 
 
    if (inString != oldString) {
      reDraw = true;
      oldString = inString;
    }
 
    inString = trim(inString);
    //split string of mutliple coordinates into coordinate-sized chuncks
    String coord[] = split(inString, ';');
 
    //init point3d array equal to number of activated points
    points = new Point3d[coord.length-1]; 
 
    //put the xyz coordinates into the point3d array and draw them
    for(int p3d = 0; p3d < coord.length-1; p3d++) {
 
      int subCoord[] = int(split(coord[p3d], ','));
      points[p3d] = new Point3d(subCoord[0] * spacing, subCoord[1] * spacing, subCoord[2] * spacing);
      point(subCoord[0] * spacing, subCoord[1] * spacing, subCoord[2] * spacing );
    }
 
    if (counter%2 != 0) {
      drawHull();
    }
  } //end if inString!=null
  popMatrix();
  // turn off depth test so the controlP5 GUI draws correctly
  hint(DISABLE_DEPTH_TEST);
  }
}
 
public void controlEvent(ControlEvent theEvent) {
  println(theEvent.controller().name());
}
 
void mouseDragged() {
  float x1 = mouseX-pmouseX;
  float y1 = mouseY-pmouseY;
  rotX = (mouseY * -0.01);
  rotY = (mouseX * 0.01);
}
 
void translations() {
  translate(width/2, height/2);
  rotateX(rotX);
  rotateY(rotY);
}
 
void drawGrid() {
 
  //draw rest of grid
  //(spacing * (gridSize -1) * -1) /2 = center around 0
  int xpos = 0;
  int ypos = 0;
  int zpos = 0;
 
  for (int i = 0; i < gridSize; i++) {
    for (int j = 0; j < gridSize; j++) {
      for( int k = 0; k < gridSize; k++) {
        stroke(100);
        strokeWeight(2);
        point(xpos, ypos, zpos);
        //println(xpos + "," + ypos + "," + zpos);
        xpos += spacing;
      }
      xpos = 0;
      ypos += spacing;
    }
    xpos = 0;
    ypos = 0;
    zpos += spacing;
  }
}
 
void drawAxes() {
 
  stroke(255,0,255);
  line(0,0,0, 100,0,0);
  fill(255,0,255);
  text("X", 200, 0);
  stroke(0,255,0);
  line(0,0,0, 0,-100,0);
  fill(0,255,0);
  text("Y", 0, -200);
  stroke(0,0,255);
  line(0,0,0, 0,0,100);
  fill(0,0, 255);
  text("Z", 0, 0, 200);
  fill(0,0,0);
}
 
public void Mode(int theValue) {
  counter++;
  println(counter);
  drawHull();
}
 
public void Export(int theValue) {
  outputSTL();
}
 
void outputSTL() {
 
  TriangleMesh mySTL = new TriangleMesh();
 
  for(int i = 0; i < vectors.length; i+=3) {
 
    mesh.addFace(vectors[i], vectors[i+1], vectors[i+2]);
   // println(vectors[i] + " " + vectors[i+1] + " " + vectors[i+2]);
  }
 
  mySTL.addMesh(mesh);
  mySTL.saveAsSTL(selectOutput());
}
 
void drawHull() {
 
  int numPoints = points.length;
  //check that our hull is valid
 
  if(hull.myCheck(points, numPoints) == false) {
 
    //brute force inefficiency
    beginShape(TRIANGLE_STRIP);
    strokeWeight(1);
    fill(0);
 
    for (int j = 0; j < numPoints; j++) {
 
      float x = (float)points[j].x;
      float y = (float)points[j].y;
      float z = (float)points[j].z;
      vertex(x,y,z);
    }
 
    endShape(CLOSE);
  }
 
  else if (hull.myCheck(points, numPoints) == true) {  
 
    if(reDraw == true) {
      //print("redraw = true");
      hull.build(points);
      hull.triangulate();
      //get an array of the vertices so we can get the faces
      Point3d[] vertices = hull.getVertices();
      savedPoints = new Point3d[0];
      vectors = new Vec3D[0];
 
      beginShape(TRIANGLE_STRIP);
      strokeWeight(1);
      //noFill();
      int[][] faceIndices = hull.getFaces();
      for (int i = 0; i < faceIndices.length; i++) {
        for (int k = 0; k < faceIndices[i].length; k++) {
 
          //get points that correspond to each face
          Point3d pnt2 = vertices[faceIndices[i][k]];
          float x = (float)pnt2.x;
          float y = (float)pnt2.y;
          float z = (float)pnt2.z;
          vertex(x,y,z);
          Vec3D tempVect = new Vec3D(x,y,z);
          //println(x + "," + y + "," + z + " " + k);
          savedPoints = (Point3d[])append(savedPoints, pnt2);
          vectors = (Vec3D[])append(vectors, tempVect);
          //savedPoints[k] = new Point3d(pnt2);
          //println(savedPoints[k]);
 
          //println(x + "," + y + "," + z);
        }
      }
      endShape(CLOSE);
      reDraw = false;
    }
 
    else if(reDraw == false) {
      // print("redraw = false");
      beginShape(TRIANGLE_STRIP);
      strokeWeight(1);
      //noFill();
      for(int i = 0; i < savedPoints.length; i++) {
 
        float x = (float)savedPoints[i].x;
        float y = (float)savedPoints[i].y;
        float z = (float)savedPoints[i].z;
        vertex(x,y,z);
      }
      endShape(CLOSE);
    }
  }
}
Comments (2) | More: Code

UCube

February 07, 2011

The UCube (v.1.0) is the first attempt at producing a tangible input device that allows non-experts to model and create their own three-dimensional objects. By placing towers around a board and activating various switches, users can define the points or vertices of a shape they wish to model. The companion software allows them to rotate the shape along any axis, fill it in, and export it to a file type (.stl) that can be read by 3d printers – thus allowing novice users to ‘close the production loop’ from ideation, to modeling, and finally to fabrication.

Comments (0) | More: Projects

Newhull Library v1.0

February 02, 2011

In case there is anyone interested in using a robust convex hull algorithm in Processing, here’s a link to a library I made, a slightly modified version of the QuickHull3D library by John Lloyd (check his documentation here: http://www.cs.ubc.ca/~lloyd/java/quickhull3d.html).

Library: newhull

Just un-zip and stick it in your libraries folder in Processing.

The most significant functional difference is the addition of a boolean myCheck() function that can check the validity of your hull BEFORE you attempt to draw it to the screen, thus saving you from QuickHull3D’s exception errors that crash Processing.
Usage below, the first parameter being the Point3D array of points, the second being the number of points in your array. Returns true if your hull contains no colinear, coplanar, or coincident points.

  if (hull.myCheck(points, numPoints) == true) {

Simple complete example:

import newhull.*;
 
QuickHull3D hull = new QuickHull3D(); //init quickhull
Point3d[] points; //init Point3d array
 
void setup() {
 
  size(200,200,P3D);
  background(255); 
 
  //point array
  points = new Point3d[] {
    new Point3d (0.0,  0.0,  0.0),
    new Point3d (0.0,  0.0,  50.0),
    new Point3d (0.0,  50.0,  0.0),
    new Point3d (0.0,  50.0,  50.0),
    new Point3d (50.0,  0.0,  0.0),
    new Point3d (50.0,  0.0,  50.0),
    new Point3d (50.0,  50.0,  50.0),
    new Point3d (50.0,  50.0,  0.0),
  };
}  
 
void draw() {
  background(255);
  smooth();
  translate(width/2, height/2);
  rotateY(frameCount * 0.01); 
 
  int numPoints = points.length; 
 
  //check that our hull is valid
  if (hull.myCheck(points, numPoints) == true) {
 
    hull.build(points);  //build hull
    hull.triangulate();  //triangulate faces
    Point3d[] vertices = hull.getVertices();  //get vertices
 
    beginShape(TRIANGLE_STRIP);
    int[][] faceIndices = hull.getFaces();
    //run through faces (each point on each face), and draw them
    for (int i = 0; i < faceIndices.length; i++) {
      for (int k = 0; k < faceIndices[i].length; k++) {
 
        //get points that correspond to each face
        Point3d pnt2 = vertices[faceIndices[i][k]];
        float x = (float)pnt2.x;
        float y = (float)pnt2.y;
        float z = (float)pnt2.z;
        vertex(x,y,z);
      }
    }
    endShape(CLOSE);
  }
}

For some more detailed usage examples, check out the code section of this website under the UCube examples.

Comments (0) | More: Code

Laser Art Code

Below is some code I wrote up to make fun shapes to laser etch. It uses an new version of the convex hull library I’ve been writing called newhull (which I will release shortly), as well as the pdf export library that comes with processing. It provides an object class that sets a convex hull with inputs for number of points, minimum and maximum on each axis (x, y, and z) and stroke weight. It produces something like this (which you can export to .pdf, then open with Inkscape or Illustrator, save to .svg and send to the laser cutter).

Code Here:

/* Laser Art Shapes
 * Using newhull and pdf export
 * by Ben Leduc-Mills
*/
 
import newhull.*;
import processing.pdf.*;
 
MHull h1, h2, h3, h4, h5;
boolean doSave = false;
float rotX, rotY; //for manual roatation
 
public void setup() {
 
  size(900, 600, P3D);
  smooth();
  background(255);
  noFill();
  // (numPoints, xMin, xMax, yMin, yMax, zMin, zMax, strokeWeight);
  h1 = new MHull(50, -50, 50, -50, 50, -50, 50, 2);
  h2 = new MHull(100, -100, 100, -100, 100, -100, 100, 2);
  h3 = new MHull(500, -200, 200, -200, 200, -200, 200, 1.5);
  h4 = new MHull(1000, -300, 300, -300, 300, -300, 300, 1);
  h5 = new MHull(800, -500, 500, -500, 500, -500, 500, .5);
  h1.genPoints();
  h2.genPoints();
  h3.genPoints();
  h4.genPoints();
  h5.genPoints();
}
 
public void draw() {
 
  background(255);
 
  if (doSave) {
    PGraphicsPDF pdf = (PGraphicsPDF)beginRaw(PDF, selectOutput());
    // set default Illustrator stroke styles and paint background rect.
    pdf.strokeJoin(MITER);
    pdf.strokeCap(SQUARE);
    //pdf.fill(0);
    pdf.noStroke();
    pdf.rect(0,0, width,height);
  }
 
  pushMatrix();
  translations();
  //h1.display();
  h2.display();
  h3.display();
  h4.display();
  h5.display();
  popMatrix();
  if(doSave) {
    endRaw();
    doSave=false;
  }
 
}
 
class MHull {
 
  int numPoints;
  int xMax, xMin;
  int yMax, yMin;
  int zMax, zMin;
  float strokeW;
  Point3d[] points;
  QuickHull3D hull = new QuickHull3D();
 
  MHull(int inumPoints, int ixMin, int ixMax, int iyMin, int iyMax, int izMin, int izMax, float istrokeW) {
 
    numPoints = inumPoints;
    xMin = ixMin;
    xMax = ixMax;
    yMin = iyMin;
    yMax = iyMax;
    zMin = izMin;
    zMax = izMax;
    strokeW = istrokeW;
  }
 
  void genPoints() {
 
    points = new Point3d[numPoints];
 
    for (int i = 0; i < numPoints; i++) {
 
      int randX = int(random(xMin, xMax));
      int randY = int(random(yMin, yMax));
      int randZ = int(random(zMin, zMax));
      points[i] = new Point3d(randX, randY, randZ);
    }
  }
 
  void refresh() {
 
   genPoints();
 
  }
 
  void display() {
 
    if (hull.myCheck(points, numPoints) == true) {
 
      hull.build(points);
      println("build");
 
      //get an array of the vertices so we can get the faces
      Point3d[] vertices = hull.getVertices();
 
      beginShape(TRIANGLE_STRIP);
      strokeWeight(strokeW);
      noFill();
      int[][] faceIndices = hull.getFaces();
      for (int i = 0; i < faceIndices.length; i++) {
        for (int k = 0; k < faceIndices[i].length; k++) {
 
          //get points that correspond to each face
          Point3d pnt2 = vertices[faceIndices[i][k]];
          float x = (float)pnt2.x;
          float y = (float)pnt2.y;
          float z = (float)pnt2.z;
          vertex(x,y,z);
        }
      }
      endShape(CLOSE);
    }
  }
}
 
void mouseDragged() {
  float x1 = mouseX-pmouseX;
  float y1 = mouseY-pmouseY;
  rotX = (mouseY * -0.01);
  rotY = (mouseX * 0.01);
}
 
void translations() {
  translate(width/2, height/2, -700);
  rotateX(rotX);
  rotateY(rotY);
}
 
void keyPressed() {
  if (key == 's') {
    doSave=true;
  }
  if(key == '1') {
   h1.refresh(); 
  }
   if(key == '2') {
   h2.refresh(); 
  }
   if(key == '3') {
   h3.refresh(); 
  }
   if(key == '4') {
   h4.refresh(); 
  }
   if(key == '5') {
   h5.refresh(); 
  }
}
Comments (0) | More: Code

Laser Etchings

This is less of a ‘project’ and more of a collection. In order to make my apartment in Boulder a little less empty, I undertook to make some art using the laser cutter at the Craft Technology Lab. Pics below are a few of the results so far, etching into bass wood and heavy stock watercolor paper. Check back later as I’ll be adding more to the collection.

Comments (0) | More: Projects

11.14.11 – UCube Demo Video

January 15, 2011

Finally got together a (semi) decent video for the UCube. Comments and feedback are most welcome.

Comments (0) | More: News

1.12.11 – Laser Etched Art

January 13, 2011

So, in addition to my regular workload at the Craft Tech Lab, I’ve been staying late to do more ‘artistic’ work. I wrote up a couple of quick programs in processing, exported the results to .svg format, and sent the files to the laser cutter in the lab. Check out the results (etched into basswood and heavy-stock watercolor paper. The Banksy .svg is courtesy of F.A.T. lab, the rest are BALM originals.

Comments (0) | More: News

uCube code – no serial

December 15, 2010

/* UCube v.0x
 * 3d modeling input device and stl export
 * No Serial communication, ued for software feature building
 * by Ben Leduc-Mills
*/
import controlP5.*;
import unlekker.util.*;
import unlekker.geom.*;
import unlekker.data.*;
import ec.util.*;
import quickhull3d.*;
//import processing.serial.*;
 
//Serial myPort; // the serial port
ControlP5 controlP5;
PFont myFont; //init font for text
STL stl; //init STL object
QuickHull3D hull = new QuickHull3D(); //init quickhull
Point3d[] points; //init Point3d array
 
float rotX, rotY;
int gridSize = 4; //size of grid (assumes all dimensions are equal)
int spacing = 50; //distance between points
int counter = 0;
 
void setup() {
// String[] fontList = PFont.list();
// println(fontList);
 controlP5 = new ControlP5(this);
 controlP5.addButton("Export",0,100,100,80,19);
 controlP5.addButton("Mode", 0,100,120,80,19);
 myFont = createFont("FFScala", 32);
 textFont(myFont);
 
 size(1000, 750, P3D);
// println(Serial.list()); // list available serial ports
// myPort = new Serial(this, Serial.list()[0], 9600);
// myPort.bufferUntil('\n');
 background(255);  //set initial background color (just looks nicer on startup)
 
}
 
void draw() {
 
 background(255);
 
 pushMatrix();
 translations();
 drawGrid();
 drawAxes();
 
 String inString = "0,0,0;0,0,2;0,2,0;0,2,2;2,0,0;2,0,2;2,2,2;2,2,0;";
 //String inString = "0,1,0;1,0,3;0,1,0;0,2,3;2,0,0;1,0,3;2,3,3;1,3,0;";
 
 if (inString != null) {
 
  //make acive points more visible
  strokeWeight(8);
  stroke(255, 0, 0);
 
  //trim whitespace
  inString = trim(inString);
//  println(inString);
 
  //split string of mutliple coordinates into coordinate-sized chuncks
  String coord[] = split(inString, ';');
 
  //init point3d array equal to number of activated points
  points = new Point3d[coord.length-1];
 
  //put the xyz coordinates into the point3d array and draw them
  for(int p3d = 0; p3d < coord.length-1; p3d++) {
 
    int subCoord[] = int(split(coord[p3d], ','));
    points[p3d] = new Point3d(subCoord[0] * spacing, subCoord[1] * spacing, subCoord[2] * spacing);
    point(subCoord[0] * spacing, subCoord[1] * spacing, subCoord[2] * spacing );
  }
 
  if (counter%2 != 0) {
   showShape(); 
  }
 
  popMatrix();
 
 } //end if inString!=null
 //controlP5.draw();
} //end draw
 
public void controlEvent(ControlEvent theEvent) {
  println(theEvent.controller().name());
 
}
 
void mouseDragged() {
  float x1 = mouseX-pmouseX;
  float y1 = mouseY-pmouseY;
  rotX = (mouseY * -0.01);
  rotY = (mouseX * 0.01);
 
}
 
void translations() {
  translate(width/2, height/2);
  rotateX(rotX);
  rotateY(rotY);
}
 
void drawGrid() {
 
 //draw rest of grid
 int xpos = 0;
 int ypos = 0;
 int zpos = 0;
 
  for (int i = 0; i < gridSize; i++) {
     for (int j = 0; j < gridSize; j++) {
       for( int k = 0; k < gridSize; k++) {
             stroke(100);
             strokeWeight(2);
             point(xpos, ypos, zpos);
             xpos += spacing;
           }
       xpos = 0;
       ypos += spacing;   
         }
     xpos = 0;
     ypos = 0;
     zpos += spacing; 
     }  
}
 
void drawAxes() {
 
  stroke(255,0,255);
  line(0,0,0, 100,0,0);
  fill(255,0,255);
  text("X", 200, 0);
  stroke(0,255,0);
  line(0,0,0, 0,-100,0);
  fill(0,255,0);
  text("Y", 0, -200);
  stroke(0,0,255);
  line(0,0,0, 0,0,100);
  fill(0,0, 255);
  text("Z", 0, 0, 200);
  fill(0,0,0);
  //text("0,0,0", 0,0,0);
}
 
public void Export(int theValue) {
  outputSTL(); 
}
 
//function to output STL file
void outputSTL() {
 
  //make sure we have at least 4 points
  //(can't have a 3d shape with less)
  if (points.length > 3) {
 
    //compute the convex hull
    hull.build(points);
 
    //get an array of the vertices so we can get the faces
    Point3d[] vertices = hull.getVertices();
 
    //start writing the stl file - file should appear in your sketch folder
    stl=(STL)beginRaw("unlekker.data.STL","convexhull.stl");
 
    //different beginShape() modes may affect the shape produced
    beginShape(QUADS);
    //println ("Faces:");
    int[][] faceIndices = hull.getFaces();
    for (int i = 0; i < faceIndices.length; i++) {
      for (int k = 0; k < faceIndices[i].length; k++) {
 
        //get points that correspond to each face
        Point3d pnt2 = vertices[faceIndices[i][k]];
        float x = (float)pnt2.x;
        float y = (float)pnt2.y;
        float z = (float)pnt2.z;
        vertex(x,y,z);
      }
    }
 
    endShape(CLOSE);
    endRaw();
  }
}
 
public void Mode(int theValue) {
  counter++;
  println(counter);
  showShape(); 
}
 
void showShape() {
 
 if (points.length > 3) {
 
    //compute the convex hull
    hull.build(points);
 
    //get an array of the vertices so we can get the faces
    Point3d[] vertices = hull.getVertices();  
 
    //different beginShape() modes may affect the shape produced
    beginShape(QUADS);
    strokeWeight(1);
    //fill(100);
    //println ("Faces:");
    int[][] faceIndices = hull.getFaces();
    for (int i = 0; i < faceIndices.length; i++) {
      for (int k = 0; k < faceIndices[i].length; k++) {
 
        //get points that correspond to each face
        Point3d pnt2 = vertices[faceIndices[i][k]];
        float x = (float)pnt2.x;
        float y = (float)pnt2.y;
        float z = (float)pnt2.z;
        vertex(x,y,z);
      }
    }
 
    endShape(CLOSE);
  }
 
}
Comments (0) | More: Code

12.13.10 – SpeakJet and UCube Update

December 14, 2010

Last week I helped a few art students put together a text-to-speech setup using the SparkFun Voice Box board, the TTS-256 Speech to Text chip, and the SpeakJet synthesizer chip.  We’re not quite done working on it, but here are a few pics:

I’ve also been doing a lot of work improving the uCube, and finally have a brief video that shows the physical interaction:

You can match it up with what’s going on in the software here (couldn’t get the screengrab to be recognized by youtube):

ucube_demo

Better documentation forthcoming after finals are over.

Comments (0) | More: News

I’m published!

December 01, 2010

My first (semi) serious article on sustainable design for developing countries was just published at Elephant Journal.com check it out: http://bit.ly/fgzEKM

Comments (1) | More: News

Links for design and ICT4D

November 27, 2010

Here is a list of links I used to help research my article on sustainable design for developing countries (I will post a link to the artlice when it goes live). Thanks especially to Shagun Singh and John Dimatos for references and ideas.

In the article:
Question Box
Barefoot College
Map Kibera

Ideas and writings:
IDEO’s human centered design toolkit
Inveneo’s sustainability primer
TU Delft – Base of the Pyramid
Humanitarian Crowdsourcing
Humanitarianism vs. Imperialism
1million t-shirts response
DIY foreign aid
DIY foreign aid response

Projects, products, and companies:
AfriGadget
SIMbaLink
Digital Green
Mapunity
Ushahidi
Frontlinesms
Selco India
E-charkha
Delwara Community toilets
Chotokool Refrigerators
Swach Water purifiers
Philips Stoves
Vesel (Village eScience for Life)
Pulse Global Labs
Ground Lab
Window Farms

Comments (1) | More: News

11.16.10 – UCube Update

November 17, 2010

After spending most of the day on the laser cutter, inhaling what are undoubtedly toxic fumes, I have made some progress on the UCube. Nothing terribly exciting, but I did cut the rest of the Z-Axis poles, and improved the stability of the base board by adding some dowels, as well as another board layer to help keep the poles upright and give a better tactile response.

Some pics from today:
These first three are of one of the z-axis poles inside the laser cutter while being cut. The masking tape is to prevent smoke/burn marks from appearing on the acrylic (protip #1), and the wooden dowel is to prevent the laser from cutting through to the other side (protip #2).

I also finally submitted my application for the National Science Foundation’s Graduate Research Fellowship (NSF GRFP). It’s a prestigious award for graduate study in the sciences/engineering – they give you $30K a year for three years to basically pursue whatever research you want. Happy to be done with the essays, transcripts, and letters of recommendation, and back to staring at lasers all day!

Comments (0) | More: News

11.04.10

November 05, 2010

The president of the University of Colorado came to the Craft Technology Lab today, so everyone’s been working hard over the past week to get a short demo together. The not-enough-sleep feeling is not so good, but I did manage to get a bit done on the first version of the UCUbe. Not so sure how much the president understood, but my advisor seemed pretty impressed (which is no small feat).

Here are a few pics of the development and UCube v.01:

Those last two are pics of me taking apart my PS2 in order to try and fix the disc drive (won’t open or close properly).

It looks like I’ll be giving an Arduino workshop next week to a bunch of MFA students here at CU. Should be fun. I also have a few exciting collaborations in their early stages – one involving a home-economics lab and the other involving GPS tracking of art pieces. More soon.

Comments (5) | More: News

10.26.10 – UCube Z-Axis Cylinder

October 26, 2010

It’s a happy day, folks. I’ve got the first prototype Z-Axis for the UCube working. It took a lot of a error checking, conductive tape, soldering, and luck, but it seems to be holding together so far. Still need to clean up the wiring a little bit and design the end-caps/contacts between the Z-Axes and the smart board they’ll be placed on. Ah well, celebrate small victories I say.

Here are a few pics for proof:

Comments (1) | More: News

10.16.10

October 16, 2010

Work on the UCube is going ok, but slowly. I was able to purchase some more parts last week to help build out the first prototype. The problem I’m struggling with now is the most effective way to design the contacts between the z-axis poles and the smart-board. I believe I’ve found a solution by laser-etching circular grooves into the plexi of the smart-board, and filling them partially with conductive material that I can connect wires to. The grooves are spaced to be the with of header pins that will protrude slightly from the z-axis cylinder. Not that this is in any way comprehensible at the moment, but once it’s done, I’ll post pictures and it will make more sense.
In other news, I’m writing a paper for my computational theory class on deciding equivalence between two regular expressions. It’s about as dense as it sounds, but at the same time strangely interesting. The most efficient method for determining equivalence is still an actively researched question in the PSPACE-complete problem set. I won’t bore you with the details, but I did write this sentence yesterday:
“As an example, Braibant and Pous developed a set of algebraic tools for reasoning about binary relations, of which Kleene algebras are part of a subset of algebraic fragments with decidable equality.”
Which has to be one of the more obscure sentences I’ve ever written. What have I become?

I came up with a joke this week. It’s really bad:
So I went to see this documentary about high-rise window washers.
It was rated Squee-G 13.

Comments (5) | More: News

uCube v.01

October 08, 2010

Here’s the first version of the uCube code, which integrates the convexhull + stl code with taking input from the physical 4x4x4 cube.

You will need the libraries I mentioned in the convex hull example, plus the serial library. The Processing sketch is parsing a string of xyz coordinates being send from Arduino, of the form (x,y,z;x,y,z,;etc.).

/* UCube v.01
 * 3d modeling input device and stl export
 * by Ben Leduc-Mills
*/
 
import unlekker.util.*;
import unlekker.geom.*;
import unlekker.data.*;
import ec.util.*;
import quickhull3d.*;
import processing.serial.*;
 
Serial myPort; // the serial port
float buttonId; // the button id we get from arduino if a button is switched
STL stl; //init STL object
QuickHull3D hull = new QuickHull3D(); //init quickhull
Point3d[] points; //init Point3d array
 
int gridSize = 4; //size of grid (assumes all dimensions are equal
int spacing = 20; //distance between points 
 
void setup() {
 
 size(800, 600, P3D);
 println(Serial.list()); // list available serial ports
 myPort = new Serial(this, Serial.list()[0], 9600);
 myPort.bufferUntil('\n');
 background(255);  //set initial background color (just looks nicer on startup)
 
}
 
void draw() {
 
  translate(width/2, height/2 -50, 300);
  rotateY(frameCount * 0.01);
 
}
 
void serialEvent (Serial myPort) {
 
 background(255);
 int xpos = 0;
 int ypos = 0;
 int zpos = 0;
 
 //get the button ID
 String inString = myPort.readStringUntil('\n');
 
 if (inString != null) {
 
  //make acive points more visible
  strokeWeight(3);
 
  //trim whitespace
  inString = trim(inString);
 
  //split string of mutliple coordinates into coordinate-sized chuncks
  String coord[] = split(inString, ';');
 
  //init point3d array equal to number of activated points
  points = new Point3d[coord.length-1];
 
  //put the xyz coordinates into the point3d array and draw them
  for(int p3d = 0; p3d < coord.length-1; p3d++) {
 
    int subCoord[] = int(split(coord[p3d], ','));
 
    points[p3d] = new Point3d(subCoord[0] * spacing, subCoord[1] * spacing, subCoord[2] * spacing);
    point(subCoord[0] * spacing, subCoord[1] * spacing, subCoord[2] * spacing );
 
  }
 
  //draw rest of grid
  for (int i = 0; i < gridSize; i++) {
     for (int j = 0; j < gridSize; j++) {
       for( int k = 0; k < gridSize; k++) {
             strokeWeight(1);
             point(xpos, ypos, zpos);
             xpos += spacing;
           }
       xpos = 0;
       ypos += spacing;   
         }
     xpos = 0;
     ypos = 0;
     zpos += spacing; 
     }        
 
 } //end if inString!=null 
 
} //end SerialEvent
 
//if any key is pressed, output the STL file
void keyPressed() {
  outputSTL();
}
 
//function to output STL file
void outputSTL() {
 
  //make sure we have at least 4 points
  //(can't have a 3d shape with less)
  if (points.length > 3) {
 
    //compute the convex hull
    hull.build(points);
 
    //get an array of the vertices so we can get the faces
    Point3d[] vertices = hull.getVertices();
 
    //start writing the stl file - file should appear in your sketch folder
    stl=(STL)beginRaw("unlekker.data.STL","convexhull.stl");
 
    //different beginShape() modes may affect the shape produced
    beginShape(QUADS);
    //println ("Faces:");
    int[][] faceIndices = hull.getFaces();
    for (int i = 0; i < faceIndices.length; i++) {
      for (int k = 0; k < faceIndices[i].length; k++) {
 
        //get points that correspond to each face
        Point3d pnt2 = vertices[faceIndices[i][k]];
        float x = (float)pnt2.x;
        float y = (float)pnt2.y;
        float z = (float)pnt2.z;
        vertex(x,y,z);
      }
    }
 
    endShape(CLOSE);
    endRaw();
  }
}
Comments (0) | More: Code

Convex Hull + STL Export

October 06, 2010

Big breakthrough today on the way toward realizing the UCube. I finally got code working in Processing that takes an arbitrary number of 3d coordinates (in x,y,z format), finds the convex hull, and is able to export a file of the shape that can be read accurately by 3d printers.

First, what a relief. I had been working on getting a convex hull working for a few weeks now. Second, I’m standing on the shoulders of giants, as always. The code below makes use of two libraries: the quickhull3d package by John Lloyd, which I turned into a processing library, and the awesome unlekker library from Marius Watz, which allows the nice export to .stl capabilities.

Here’s the Processing version of the quickhull3d library I used/adapted. Just unpack it and drop it in your Processing->libraries folder. quickhull3d

The example below just takes a bunch of random points and displays something like this:

You could, of course, populate the array with the coordinates you wanted.

/* Convexhull to stl export
 * by Ben Leduc-Mills
 * 10.5.10
*/
 
import unlekker.util.*;
import unlekker.geom.*;
import unlekker.data.*;
import ec.util.*;
import quickhull3d.*;
 
//init STL object
STL stl;
//init quickhull
QuickHull3D hull = new QuickHull3D();
//init Point3d array
Point3d[] points;
//number of points
int numPoints = 20;
 
void setup() {
 
  size(400, 400, P3D);
 
  //populate point3d array with random xyz coordinates
  Point3d[] points = new Point3d[numPoints];
  for(int i = 0; i < numPoints; i++) {
    points[i] = new Point3d (random(50), random(50), random(50));
  }
  hull.build (points);
}
 
void draw() {
  background(255);
  translate(width/2, height/2 -50, 200);
  lights();
  rotateY(frameCount * 0.01);
 
  Point3d[] vertices = hull.getVertices();
 
  beginShape();
   //println ("Faces:");
   int[][] faceIndices = hull.getFaces();
   //print(faceIndices.length);
   for (int i = 0; i < faceIndices.length; i++) {
     for (int k = 0; k < faceIndices[i].length; k++) {
 
        //print (faceIndices[i][k] + " ");
 
        //get points that correspond to each face
        Point3d pnt2 = vertices[faceIndices[i][k]];
        //print(pnt2);
        float x = (float)pnt2.x;
        float y = (float)pnt2.y;
        float z = (float)pnt2.z;
        vertex(x,y,z);
       }
      //println ("");
    }
 
endShape();
}
 
//if any key is pressed, output the STL file
void keyPressed() {
  outputSTL();
}
 
//function to output STL file
void outputSTL() {
 
   stl=(STL)beginRaw("unlekker.data.STL","convexhull.stl");
 
   Point3d[] vertices = hull.getVertices();
 
   beginShape(QUADS);
   //println ("Faces:");
   int[][] faceIndices = hull.getFaces();
   //print(faceIndices.length);
   for (int i = 0; i < faceIndices.length; i++) {
     for (int k = 0; k < faceIndices[i].length; k++) {
 
        //print (faceIndices[i][k] + " ");
 
        Point3d pnt2 = vertices[faceIndices[i][k]];
        print(pnt2);
        float x = (float)pnt2.x;
        float y = (float)pnt2.y;
        float z = (float)pnt2.z;
        vertex(x,y,z);
       }
      //println ("");
    }
 
endShape(CLOSE);
endRaw();
}
Comments (0) | More: Code

10.04.10 – Maker Faire Wrap-Up

October 04, 2010

After taking a week to catch my breath (and do all the homework I’d been putting off), I’m recording the rest of Maker Faire NY and other news of the past week or so.

First off, Maker Faire was amazing. It was great to connect with so many talented, creative people. ITP (my alma mater) had a great showing, I was very proud. Besides having their own ‘ITP Cafe’ where different students and alumni gave talk on current projects, there were at least 15 other booths manned by ITP alums, including at least 3 editor’s choice awards: Windowfarms by Britta Riley, Open-Source Lion Tracking collars/Ground Lab (Benedetta Piantella and Justin Downs), and….SADbot! by Dustyn Roberts and yours truly.

Our Blue Ribbon for SADbot:

ITP Alum Matt Parker also won the ‘Create the Future’ Award (sponsored by Red Bull) for his amazing project Lumarca. Congrats Matt!

I had a chance to seek away to see a few talks – one on wearables/LilyPad Arduino by Leah Beuchley and Hannah Perner-Wilson of the High/Low Tech group at MIT media lab, and a really interesting talk by John Shimmel (of ITP) on assistive technology (specifically a modded-ps3 controller for an ms patient). I also caught a bit of Eric Rosenthal’s presentation of an Arduino-based CNC machine that ITP is planning to use for in-house circuit board etching. Color me jealous.

In other news, I’ve started breadboarding the 3d-geoboard, which I’ve now re-named the ‘UCube’. I’m using nearly all the inputs on an Arduino Mega (64 of them), so the breadboard is a mess. Took me 3 days to get this far:

Here’s a few more pics from Maker Faire:
Windowfarms:
ShopBot:
Arduino-Based CNC:
High-Low Tech:
Giant Claw Game:

Comments (0) | More: News

9.25.10 – Open Hardware Summit and Maker Faire Setup

September 25, 2010

It’s been a busy few days here in New York, and it promises to continue to be that way. After catching a red-eye Wednesday night from Denver, I landed at JFK early Thursday and headed straight to the Open Hardware Summit (on no sleep thanks to crying babies). The summit was excellent – much kudos to Ayah Bdeir and Alicia Gibb for doing such a great job. The right people were there, and a lot of them. Some great talks by Limor Fried, Leah Buechley, Amanda McDonald Crowley, (notice all the awesome Women!), plus some great panels on Open Hardware Law and how to get from a DIY setup to more serious production.

Today, Dusytn and I did most of our setup for Maker Faire this weekend. It was a little more time-consuming than we had hoped – it’s amazing how quickly one forgets the ins and outs of a past project. It was quite a scene with all the makers setting up – see pics below. They also fed us paella and gave us free booze, so it’s hard to call them anything but generous.

Dustyn setting up:

Marching Band:

Squid Car:

Arc-Attack (giant musical Tesla coils):

Also, SWAG:

Combined from Maker Faire and the Open Hardware Summit: 2 different issues of Make:Magazine, the Fashioning Technology Book, a Make water bottle, an O’Reilly notebook, kit from Evil Mad Scientist, Open Hardware Stamp, neon snap bracelet(!!!), Sparkfun stickers, Annoy-your-neighbor gadget from ThinkGeek, resistor bender tool, and 2 free tickets to the ny hall of science.

Comments (0) | More: News

9.21.10

September 22, 2010

A few trivially exciting bits of info: I received my ~$400 order from DigiKey today, to help make the first prototype of the 3D-Geoboard:

Pictured: A brand new Weller soldering station(!!!), 100ft ea. of red, black, and white hook-up wire, lead-free solder, a giant 2x 64 row breadboard, 200 10k Ohm resistors, 8 shift registers (CD4021BE), and 70 illuminated rocker switches. I can’t wait to start hooking it all up.

Also, in preparation for my trip to the Open Hardware Summit and Maker Faire in NYC this week, I decided to laser-cut some quick & dirty style business cards since I didn’t have any that had my current info on them. I used some white-core card stock which really makes the etching pop:

I’m also working on a library for Processing for easy implementation of a 3D convex hull algorithm. A few people have given me a head start, but going from 2 to 3 dimensions is a little tricky.

Comments (3) | More: News

9.16.10

September 17, 2010

SADbot got a shout-out today as part of a blog post on Alibre, the awesomely cheap 3D modeling software. Word on the street is that SADbot co-creator and Alibre extraordinaire Dustyn Roberts is going to be speaking at the Alibre booth at Maker Faire! Congrats Dustyn!

Alibre was actually instrumental in helping us construct the mirror arrays that reflected light into Eyebeam’s window gallery – by doing a scale model of Eyebeam, Dustyn was able to figure out what angle we had to hang the mirrors at in order to get light into the window (52 degrees, as it turns out). Well, I thought it was cool anyway.

Comments (2) | More: News

9.15.10

September 15, 2010

I engineered something today! Nothing fancy though. I was searching for a way to be able to make the z-axes of the 3d geoboard movable. For this to work, I had to separate the switches from their connections to power, ground, and the arduino, while making sure that whatever position they ended up in would display correctly on the computer. Turns out I could hook them in parallel (I think) using just 1 line back to power, and then separating the ground lines back to the breadboard, keeping the logic line and the resistor there. Add some conductive tape and some scrapwood, and voila!

Here’s a couple of pics (pardon the general ugliness and messy breadboarding) and a screenshot of the Processing sketch display:

Comments (0) | More: News