Making Of
Overview of Techniques
- Fabrication
- laser cutting
- 3d printing
- kinematics
- machine build
- Electronics
- switch - between camera mode and human eye mode.
- turntable - with speed control.
- high-frequency flashing light to let human eyes to simulatie camera's shutter angle.
- Programming
- speed control
- light control - differnt modes and binking frequencies
- Parametrical Design
- procedural setup in Cinema 4D
- Documentation
- the process
- the final recorded animation video
Animation Calculation
Before 3d modelling, I need to calculate how many model “frames” do I need for each circle. So I made the following equation for easy calculation:
N = 60/R * F
- N: number of model “frames” in a full circle
- R: turntable revolutions per minute
- F: frame rate, how many frames per second, for both the animation and the video recording devices.
Schedule
Week 1121-1127
- overall 3D design
- test motor control
- test flashing light control
Week 1128-1204
- test motor control
- test flashing light control
- debveloping the paramteircal template
Week 1205-1211
- building the device body
- PCB design
- making animation models with various materials and methods: 3d printing, laser cutting, PCB milling, etc.
Week 1212-1218
- PCB production
- assembly machine parts
- shooting videos
Week 1219-1220
- updating website
- editing presentation slides
- editing videos
- final presentation
Inventory List
- 6 in. Square Lazy-Susan Turntable with 400 lb. Load Rating
- 18 in. x 24 in. x .220 in. Acrylic Sheet
- 775 DC Motor DC 12V - 24V
- L298N Motor Drive Controller Board
- WS2812B RGB 5050SMD Individual Addressable 60Pixels/m LED Strip (total number of LED pixels should be no less than 336)
- M4 screw, nut, washers
Process
Part 1: Machine
Mechanics & Machine Design
Before modeling, I first decided to use 0.220 in thick acrylic sheet as the main material, combined with metal rods and screws as the main way of fastening. I built the model in Rhino according to the actual size, and at the same time left a certain tolerance between the components. I then made laser cut files of the acrylic sheet. At this time, I also clarified the length of the main metal parts required.
Sourcing Materials
I drove to Home Depot and bought the acrylic sheets and metal rods and matching screw caps. Even though the lab has screw nutss of various sizes, I chose to buy a couple of the large and long ones for design considerations and the ability to act as a base support at the same time.
Laser Cutting
I installed ULP (Universal Laser Systems Control Panel) on my laptop for customized control of score, inside cut, and outside cut.
The laser cutter we used is PLS6.150D.
I layed out the cutting components in Rhino.
I exported each single sheet to a PDF, and opened the PDF in Adobe Acrobat, and print it to PLS6.150D
with Actual Size
.
In UCP, the setting is:
The order in which the colors correspond is:
- Blue: Score
- Red: Inside cut
- Magenta: Outside cut
Such setting is saved as a local file so that I don't need to manully set every time.
Machine Build
Motor and Gear Drives
Eletronics
D21E18A Pin Out Diagram
Schematic
Board
Production
PNG Images for Milling
Note: Please download the original PNG images at the bottom of the this page for milling machine.
Trace
Cut
Motor Control
With a potentiometer.
LED
Test
Programming
Bootloading for D21E18A
Detailed steps can be found at Leo's Github and MTM.
The following code is used for these two interactions:
- Motor speed control
- LED graphics (including blinking frequency)
int ena = 8;
int in1 = 9;
int in2 = 10;
int sc = A1; //speed controlling Potentiometer
int mspeed = 0; // motor speed, the variable resistor value will be stored in this variable
#include <FastLED.h>
// How many leds in your strip?
#define NUM_LEDS 288
// For led chips like WS2812, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
// Clock pin only needed for SPI based chipsets when not using hardware SPI
#define DATA_PIN 11
// #define CLOCK_PIN 13
// Define the array of leds
CRGB leds[NUM_LEDS];
#define LED_TYPE WS2811
#define COLOR_ORDER GRB
#define BRIGHTNESS 96
#define FRAMES_PER_SECOND 120
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(ena, OUTPUT);
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
pinMode(sc, INPUT);
analogWrite(ena, 0);
// // Uncomment/edit one of the following lines for your leds arrangement.
// // ## Clockless types ##
// FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); // GRB ordering is assume
// // FastLED.addLeds<SM16703, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<TM1829, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<TM1812, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<TM1809, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<TM1804, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<TM1803, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<UCS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<UCS1903B, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<UCS1904, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<UCS2903, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// // FastLED.addLeds<WS2852, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// // FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// // FastLED.addLeds<GS1903, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<SK6812, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// // FastLED.addLeds<SK6822, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<APA106, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<PL9823, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<SK6822, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<WS2813, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<APA104, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<WS2811_400, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<GE8822, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<GW6205, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<GW6205_400, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<LPD1886, DATA_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<LPD1886_8BIT, DATA_PIN, RGB>(leds, NUM_LEDS);
// // ## Clocked (SPI) types ##
// // FastLED.addLeds<LPD6803, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// // FastLED.addLeds<LPD8806, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
// // FastLED.addLeds<WS2801, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<WS2803, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<SM16716, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
// // FastLED.addLeds<P9813, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// // FastLED.addLeds<DOTSTAR, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// // FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
// // FastLED.addLeds<SK9822, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS); // BGR ordering is typical
delay(3000); // 3 second delay for recovery
// tell FastLED about the LED strip configuration
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
//FastLED.addLeds<LED_TYPE,DATA_PIN,CLK_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
// set master brightness control
FastLED.setBrightness(BRIGHTNESS);
}
void loop() {
mspeed = analogRead(sc);
mspeed = map(mspeed, 0, 1023, 0, 255);
analogWrite(ena, mspeed);
// forward
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
// // fill_solid(leds, NUM_LEDS, CRGB::White);
// for(int n = 0; n < NUM_LEDS; n++){
// // Turn the LED on, then pause
// leds[n] = CRGB::Red;
// FastLED.show();
// delay(50);
// // Now turn the LED off, then pause
// leds[n] = CRGB::Black;
// FastLED.show();
// // delay(50);
// // }
// // RED Green Blue
// for (int i = 0; i < NUM_LEDS; i++)
// leds[i] = CRGB(255, 0, 0);
// FastLED.show();
// delay(50);
// for (int i = 0; i < NUM_LEDS; i++)
// leds[i] = CRGB::Black;
// FastLED.show();
// delay(50);
// Call the current pattern function once, updating the 'leds' array
gPatterns[gCurrentPatternNumber]();
// send the 'leds' array out to the actual LED strip
FastLED.show();
// insert a delay to keep the framerate modest
FastLED.delay(1000/FRAMES_PER_SECOND);
// do some periodic updates
EVERY_N_MILLISECONDS( 20 ) { gHue++; } // slowly cycle the "base color" through the rainbow
EVERY_N_SECONDS( 10 ) { nextPattern(); } // change patterns periodically
}
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
void nextPattern()
{
// add one to the current pattern number, and wrap around at the end
gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);
}
void rainbow()
{
// FastLED's built-in rainbow generator
fill_rainbow( leds, NUM_LEDS, gHue, 7);
}
void rainbowWithGlitter()
{
// built-in FastLED rainbow, plus some random sparkly glitter
rainbow();
addGlitter(80);
}
void addGlitter( fract8 chanceOfGlitter)
{
if( random8() < chanceOfGlitter) {
leds[ random16(NUM_LEDS) ] += CRGB::White;
}
}
void confetti()
{
// random colored speckles that blink in and fade smoothly
fadeToBlackBy( leds, NUM_LEDS, 10);
int pos = random16(NUM_LEDS);
leds[pos] += CHSV( gHue + random8(64), 200, 255);
}
void sinelon()
{
// a colored dot sweeping back and forth, with fading trails
fadeToBlackBy( leds, NUM_LEDS, 20);
int pos = beatsin16( 13, 0, NUM_LEDS-1 );
leds[pos] += CHSV( gHue, 255, 192);
}
void bpm()
{
// colored stripes pulsing at a defined Beats-Per-Minute (BPM)
uint8_t BeatsPerMinute = 62;
CRGBPalette16 palette = PartyColors_p;
uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
for( int i = 0; i < NUM_LEDS; i++) { //9948
leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10));
}
}
void juggle() {
// eight colored dots, weaving in and out of sync with each other
fadeToBlackBy( leds, NUM_LEDS, 20);
uint8_t dothue = 0;
for( int i = 0; i < 8; i++) {
leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255);
dothue += 32;
}
}
Part 2: Templates
1. Cup (Merged Frames)
I developed a Cinema 4D Template so users can easily change the parameters to create their own "animate cup" model.
I exported it as a STL file
I imported the STL file into Prusa Slicer, generated the G-Code, and sent to the printer.
2. Single Animate Frames
I started with download a dancing motion capture file from Mixamo, an attach spheres to it.
Animation preview:
With such digital animation sequence, I first exported it as an Alembic (.abc) file containing all frames in a single file.
Then I imported the ABC file back to Cinema 4D, and exported it as an OBJ sequence.
Then I followed the same 3d printing process to print all these models. To save time and test the effect, I first printed models with even frames, such as 0, 2, 4, 6, ..., 58 (aka. frame step of 2).
Note: Adding frame numbers on bottom of the model will help to keep track of each frame.
Part 3: Experiences
Turntable
I used the Blackmagic Pocket Cinema Camera (2013) for shooting. It can be shot with any camera, as long as making sure the camera's shutter angle is set to minimum in order to remove motion blur as much as possibile (similar to how the film projector works).
3D Printing Layers (Bonus)
I embedded motion frames as 3d printing layers, so the timelapse of 3d printing can be controlled to express certain animation. I hacked the G-Code file so after each single layer, the printer moves to a certain location, and triggers the camera controller to take a photo. Hundreds of photos are combined as frames to create such animated "HTMAA" text effect.
In Prusa Slicer, I inject the following code after each layer of printing:
;AFTER_LAYER_CHANGE
G1 X5 Y205 F{travel_speed*60} ;Move away from the print
G4 S0 ;Wait for move to finish
G4 P500 ;Wait for 500ms
;[layer_z]
Then, I precisely put a camera controller in front of the printer, so that every time the printer's nozzle moves to the set location mentioned above, gthe plate will hit the controller to trigger the camera taking a photo.
Finally I combined these photos in After Effects, and created the printing animation in the beginning.
With such workflow and setup, I am able to embedd any video or animation sequence into the 3d printing layers.