Creating POV Sequences.

2.7K140

Intro: Creating POV Sequences.

This instructable will focus on creating sequences that can be used to send to a LED interface.

For programming the template we've used NodeBox 3, an application that focuses on generative design and datavizualisation but is in fact a multipurpose toolkit that can be used in other contexts too. NodeBox is a free, open source functional programming language which comes with a modular interface. For more information on the software, go to their documentation files or download it.

Arduino Uno was used to control the shift registers and the potentiometer.

done by me and internep.


STEP 1: Hardware Interface.

The interface uses an Arduino board and two shift register modules (ghielectronics_maxo-module).

An excellent tutorial on using shift registers can be found on the arduino pages.

Ours consist of 56 green LED's -so our drawing can have 56 pixels in height or in width. The interface also has a small potentiometer that enables us to adjust the timedelay.

STEP 2: A Simple Template.

Nodebox is a great tool for visualising stuff. Let's start with a simple example that visualises an ellipse on the interface. Key is that we have 56 LED's. Below is the description of a simple example but it might be easier to download the baseReader.zip which is included and open it in Nodebox allowing you to see it step by step..

the grid

Create a grid node with the row parameter set to 56. This will be one vertical line of our image. Next set the columns parameter to 25. Change the dimensions of it to width 120 and height 300. It will result in a grid of points.

This grid of points is sorted on the Y axis. We have to change it so that the first 56 points are the first column of the grid, the second set of 56 points the second column in the grid and so on.

Create a sort node and connect grid1 to the shapes port. Change the order parameter to X.


the shape

The next thing we need is a shape.
Create an ellipse node and leave it at it's default parameters.


the overlap

The idea is that we will try to figure out which points of the grid overlap the ellipse. The ones that do have to be stored as 1, the ones that don't have to be stored as 0. This is not a build-in function, but NodeBox allows us to use our own code as well.

Open a text editor and enter

def cook(shape, points):
if shape.contains(points.x, points.y):
return 1
else:
return 0

save it as a python file called check.py. Now that we have a function to call for, it can be implemented into NodeBox. First we need to import the python file in the program.

* Go to File >> Code Libraries. It will open a new small window with in the upper right corner a '+' sign.
* Press '+' and select Python from the Python / Closure option. Browse to check.py
* If you can see a reference to it you can close the window.

The python file is imported now and the function in it can be called for from a node.

Every node calls for a function, you can find out which one by selecting a node and then go to the metadata tab on top of the port/parameters window. It will open a new window with specifications of the node and its ports. If you go to 'Settings' you can find out what function it refers to. In the case of a rectangle it will say: corevector/rect, meaning that it will call the rect function in the corevector file. To call the cook function in checkImage.py i will have to call checkImage/cook.

I need a node where i can send a shape and a list of points to and which returns a list (of 0 and 1). We can use the filter node to start with since it already imports a shape.

* Create a filter node.
* Select the node and go to the Metadata tab.
* Go to Node >> Settings. Enter 'check/cook' in the 'Function' option.

We still need a new port. For the moment the node can only receive a shape.

* In the Metadata window: click the '+' button on the lower left corner.
* Enter a port name: call it 'points'.
* Select 'point' from the Type menu and press 'OK'.

Now we have two input ports, a black one for our shape (the ellipse) and a blue one for our points (for our sorted grid).

Connect sort1 to the points port of the new filter node and connect ellipse1 to the shape port of the filter node. It should return a list of 0 and 1 which we can use to create a color.


visualise it

Create an rgb color node and change the range to 1. Connect filter1 to the green port. Now we can visualise in NodeBox what will be on the interface later.

 * Create a rect node of 5 by 5.
 * Create a translate node and connect the rect to it's shape parameter and sort1 to it's translate parameter.
 * Create a colorise node and connect translate to it. Connect the rob node to the fill parameter of this colorise node and render it. The ellipse should appear. (see the screenshot on top of this step)



into hex.

First step is to create pairs of 8, then to write them into hexadecimal value and then output it to a file.

* Create a slice node. Set the amount parameter to 8 and connect filter1 to the list port. The start index will allow us to read 8 values and we want to do that over a step of 8. Create a subnetwork for it and call it eight_bit. Publish the start index parameter so it's accessible from the root.

* Create a count node, connect filter1 to its list port. This outputs the number of values in the list.
Create a range node and connect count to it's end port. Set the step parameter to 8. This returns a list 0, 8, 16, 24, 32, .. We can use these values as the start index of our eightbit node. Connect it.

* Go in the subnetwork and create 8 slice nodes. Each one of them has 1 as amount parameter. The first one has start index 0, the second one 1, .. Connect them all to the first slice node (with the amount of 8).

* Create 3 concatenate nodes. Connect all slice nodes (with amount 1) to them and see results like 00000000, 00000000, 00000000, 00011111, ..

Open a text editor and enter

import math
def tohex(n):
return hex(int(n,2))


save it as tohex.py

import it into NodeBox by using the code libraries option.

Create a new node which is a base node to be extended for custom nodes. Go to the metadata and change it's function to 'tohex/tohex'. Create a new port, call it bit and put the type to String.
Connect the eight bit node to it and render it. The result should be hex values like 0x0, 0x3, 0xff, …

It's important to know how many 'packages of eight' there are so create a new count node. Connect the to hex node to it's list parameter.


to arduino

Now let's output it to a file. Let's use a csv format.

Open a text editor and enter:

import csv
def writetocsv(hexes,name):
list = []
csvfile = file('/Users/Desktop/'+name+'.csv', 'w') # this should refer to a folder on your computer
writer = csv.writer(csvfile, delimiter=",")
for hex in hexes:
list.append(hex)
writer.writerow(list)
csvfile.close()
return hexes

save it as toarduino.py. Create a new base node again, change it's function to 'toarduino/writetocsv' and create two new parameters. Both have a String widget but the first one has a list as range, while the second one has value as range. Call the first one hexes and the second one name. We will send the output of the hex node to the hexes port and enter a filename in the name parameter.


arduino code

below is the arduino code started from Tom Igoe's tutorial on the Arduino playground. The shifter function runs through all the bytes and has 3 arguments. ( sequence array, timedelay, amount). byte oval[175] stores the information from NodeBox.
The potPin refers to a potentiometer thats enables us to control the timedelay on the interface.

--

//**************************************************************//
// code started from this arduino playground example:
//  Name    : shiftOutCode,  Hello World                               
//  Author  : Carlyn Maw, Tom Igoe,  David A. Mellis
//  Date    : 25 Oct,  2006   
//  Modified: 23 Mar 2010                                
//  Version : 2.0                                            
//  Notes   : Code for using a 74HC595 Shift Register           //
//          : to count from 0 to 255                          
//****************************************************************

int latchPin = 8;
int clockPin = 12;
int potPin = A0;
int dataPin = 11;

byte oval[175] = {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x3,0xff,0xc0,0x0,0x0,0x0,0x0,0x7,
0xff,0xe0,0x0,0x0,0x0,0x0,0x7,0xff,0xe0,0x0,0x0,0x0,0x0,0xf,0xff,0xf0,0x0,0x0,0x0,0x0,0xf,
0xff,0xf0,0x0,0x0,0x0,0x0,0x1f,0xff,0xf8,0x0,0x0,0x0,0x0,0x1f,0xff,0xf8,0x0,0x0,0x0,0x0,0x1f,
0xff,0xf8,0x0,0x0,0x0,0x0,0x1f,0xff,0xf8,0x0,0x0,0x0,0x0,0x1f,0xff,0xf8,0x0,0x0,0x0,0x0,0x1f,
0xff,0xf8,0x0,0x0,0x0,0x0,0x1f,0xff,0xf8,0x0,0x0,0x0,0x0,0xf,0xff,0xf0,0x0,0x0,0x0,0x0,0xf,
0xff,0xf0,0x0,0x0,0x0,0x0,0x7,0xff,0xe0,0x0,0x0,0x0,0x0,0x7,0xff,0xe0,0x0,0x0,0x0,0x0,0x3,
0xff,0xc0,0x0,0x0,0x0,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0};

void setup()
{
    pinMode(dataPin,  OUTPUT);       
    pinMode(latchPin,  OUTPUT);
    pinMode(clockPin,  OUTPUT);
    pinMode(potPin,  INPUT);
    Serial.begin(9600);
}

void shifter(byte seq1[],  float timeDelay, int len){
for (int x = 0; x < len; x++)        
    {
        digitalWrite(latchPin,  LOW);            //start sturen via latchpin
        if(x%7 == 0){
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x]);        
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+1]); 
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+2]);     
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+3]);       
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+4]); 
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+5]);     
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+6]); 
        shiftOut(dataPin,  clockPin,  MSBFIRST,  seq1[x+7]);  
        }
        digitalWrite(latchPin,  HIGH);           //stop versturen
        delay(timeDelay);
    }
    delay(timeDelay*20);
}


void loop() {
    float temp = analogRead(potPin);
    float timeDelay = mapped(temp,  0, 1023,  0,  25);

    shifter(oval, timeDelay, 175);
}

float mapped(float x,  float in_min,  float in_max, float out_min, float out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

another image

Now we have a basic template which we can use to create other images. Changing the ellipse to another shape creates a new array. You can create a single character of an alphabet. etc.

STEP 3: The Template Can Handle Any Path.

complexity.

NodeBox allows to create more complex shapes like funny characters and invaders.

In addition there is a zip file containing a similar procedure for reading in an image. It works with an other python file that does a simple edge detection procedure. See the zip file for more.

from javax.imageio import ImageIO
from java.io import File
from nodebox.graphics import Point
from math import sqrt
import os
from os.path import abspath

def pixel(c,c1,c2,edge):
    r,g,b = c[0],c[1],c[2]
    r1,g1,b1 = c1[0],c1[1],c1[2]
    r2,g2,b2 = c2[0],c2[1],c2[2]
    if (sqrt((r-r1)*(r-r1)+(g-g1)*(g-g1)+(b-b1)*(b-b1))>= edge) or (sqrt((r-r2)*(r-r2)+(g-g2)*(g-g2)+(b-b2)*(b-b2))>= edge):
        return True
    else:
        return False


def cook(foto, sens,sg):
    f = File(abspath(foto))
    bi = ImageIO.read(f)
    raster = bi.raster
    w = raster.width
    h = raster.height
    seg = sg
    all = []
    for i in xrange(0,w,seg):
        for j in xrange(0,h,seg):
            c = bi.raster.getPixel(i,j,[0.0,0.0,0.0])
            try:
                c1 = bi.raster.getPixel(i+1,j,[0.0,0.0,0.0])
                c2 = bi.raster.getPixel(i,j+1,[0.0,0.0,0.0])
            except:
                pass
            if pixel(c,c1,c2,sens):
                all.append(Point(i,j))
    return all

STEP 4: Building Blocks.

the centipede.

I've done a previous instructable on NodeBox and cnc engraving where i've created a centipede like creature. Fun thing is to create building blocks in a similar way then create a sequence in arduino with the different parts.

byte leg[91] = {0x80,0x0,0x0,0x3c,..};
byte leg1[91] = {0x0,0x0,0x0,0x3c,..};
byte leg2[91] = {0x0,0x0,0x0,0x3c,..};
byte leg3[91] = {0x0,0x0,0x0,0x3c,..};
byte leg4[91] = {0x0,0x0,0x0,0x3c,..};

byte blank[280] = {0x0,0x0,0x0,0x0,..};

byte head[420] = {0x40,0x0,0x30,0x44,..};
byte tail[280] = {0x0,0x0,0x1,0xf0,0x0,..};

byte *pickAleg[5] = {leg, leg1, leg2, leg3, leg4};

// first we start with a blank, then a head section, 19 legs and a tail..

void loop() {
    float temp = analogRead(potPin);
    float timeDelay = mapped(temp,  0, 1023,  0,  25);

    shifter(blank, timeDelay, 280);
    shifter(head, timeDelay, 420);
    for(int i = 0; i < 19;i++){
    shifter(pickAleg[int(random(5))], timeDelay, 91);
    }
    shifter(tail, timeDelay, 420);
    shifter(blank, timeDelay, 280);
}