Skip to content

Making Of

Overview of Techniques

  1. Fabrication
    1. laser cutting
    2. 3d printing
    3. kinematics
    4. machine build
  2. Electronics
    1. switch - between camera mode and human eye mode.
    2. turntable - with speed control.
    3. high-frequency flashing light to let human eyes to simulatie camera's shutter angle.
  3. Programming
    1. speed control
    2. light control - differnt modes and binking frequencies
  4. Parametrical Design
    1. procedural setup in Cinema 4D
  5. Documentation
    1. the process
    2. 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

  1. 6 in. Square Lazy-Susan Turntable with 400 lb. Load Rating
  2. 18 in. x 24 in. x .220 in. Acrylic Sheet
  3. 775 DC Motor DC 12V - 24V
  4. L298N Motor Drive Controller Board
  5. WS2812B RGB 5050SMD Individual Addressable 60Pixels/m LED Strip (total number of LED pixels should be no less than 336)
  6. 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.

Alt text

Alt text

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.

Alt text

I exported each single sheet to a PDF, and opened the PDF in Adobe Acrobat, and print it to PLS6.150D with Actual Size.

Alt text

Alt text

In UCP, the setting is:

Alt text

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.

LC_ARCYLIC 0.22_003.las

LC_ARCYLIC 0.093_001.las

Alt text

Alt text

Machine Build

Alt text

Alt text

Alt text

Alt text

Motor and Gear Drives

Alt text

Alt text

Alt text

Alt text

Eletronics

D21E18A Pin Out Diagram

Schematic

Alt text

Board

Alt text

Production

Alt text

Alt text

Alt text

Alt text

PNG Images for Milling

Note: Please download the original PNG images at the bottom of the this page for milling machine.

Trace

Alt text

Cut

Alt text

Motor Control

With a potentiometer.

Alt text

LED

Alt text

Test

Alt text

Programming

Bootloading for D21E18A

Detailed steps can be found at Leo's Github and MTM.

The following code is used for these two interactions:

  1. Motor speed control
  2. LED graphics (including blinking frequency)
C
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

Alt text

I imported the STL file into Prusa Slicer, generated the G-Code, and sent to the printer.

Alt text

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.

Alt text

Alt text

Then I imported the ABC file back to Cinema 4D, and exported it as an OBJ sequence.

Alt text

Alt text

Alt text

Alt text

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).

Alt text

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:

Text Only
;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]

Alt text

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.

Alt text

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.


Resources

⬇️ Download Project Files and Assets