syntax-highlighter

Showing posts with label making. Show all posts
Showing posts with label making. Show all posts

Sunday, May 26, 2013

Smooth Feedrate Envelopes for Motion Control, Part II

In the previous post, I derived equations for smooth feedrate control of stepper motors, claiming that by using a smoother feedrate envelope that the motors could be driven faster with less chance of skipping steps and losing position.

In this post, I demonstrate this with a real stepper motor and show that it actually does work: using the envelopes does actually prevent the motors from losing steps.  My test setup is a single NEMA 17 stepper, driven by one of my A4988 driver breakouts, which is controlled by an Arduino sketch running on the Arduino Due.  I'm using half-stepping on the motors, driven by a 200KHz timer interrupt step callback which decides whether or not to step based on the interpolated supplied delays for the start and end of each move.  The move itself approximates a square wave, first accelerating from a slow feedrate, then performing a constant speed portion, then decelerating back to the initial feedrate.

The video below shows the stepper being driven using a constant acceleration profile, which causes the kinks in the feedrate graph in my previous post. You can clearly see it moving around on the table and stalling frequently before it reaches the top speed.


In contrast, here is the result using the third-order cubic feedrate envelope for the same set of moves. The stepper is easily able to handle the top speed and jerks around considerably less on the table.  Of course this comes at a price, a higher pulse-frequency must be used to resolve the acceleration profile.


You can get the code I used for this from following link: https://sites.google.com/site/jamesgregson/tmp/linear_move.zip, it includes a multi-axis DDA implementation suitable for use with timer-interrupts as well as the code for evaluating the feedrate envelopes.

Thursday, May 23, 2013

Smooth Feedrate Envelopes for Motion Control

When running motors it is desirable to run them as smoothly as possible to minimize vibrations and possible missed steps. This is why controllers for 3D printers and CNC machines typically incorporate some notion of acceleration rather than instantly switching from one feedrate to another.  Often this is done with a simple ramp of the feedrate, i.e. using a constant acceleration profile.

As an example, consider a machine starting at feedrate $f_0$ and then performing a very long linear move at a constant feedrate $f_1$. For this example, If $f(t)$ is the machine feedrate over time and $a$ is a constant acceleration, this profile would be defined mathematically as follows:

\begin{equation}
f(t) = \begin{cases}
f_0      & \mbox{if } t < t_0 \\
f_0 + a (t - t_0) \hspace{0.5cm} & \mbox{if } 0 \leq t - t_0 \leq \frac{f_1-f_0}{a} \\
f_1 & \mbox{otherwise}
\end{cases}
\end{equation}

Graphically, here's a plot of the feedrate over time for a move starting at rest at $t=0$ and accelerating up to a feedrate of 2 over one unit of time:


The problem with a constant acceleration profile is that there are sharp kinks in the feedrate plotted over time.  These kinks imply instantaneous changes in acceleration, which in turn imply infinite forces for infinitely short periods of time.  Of course, there is no mechanical way to produce these forces, so what actually happens is the machine overshoots very slightly and averages the forces out over a short time. For low feedrates with a light machine this actually works okay, but for a heavy machine at high-feedrates, the overshoot can be more than a motor step which causes the machine to lose position.  In an open-loop design, once the machine loses position, it never recovers and in all likelihood, the part is ruined.

There are a few ways to address this problem:
  • Use lower feedrates
  • Use higher torque motors
  • Use a closed-loop control scheme, e.g. with encoders on the motors
  • Make the acceleration smooth
The first is clearly not an option because it wastes time and feedrates may be chosen specifically for valid reasons such as minimizing local part heating or reducing machining time.  In an ideal world we'd do the remaining three items, but options two and three are expensive, particularly for hobby gear.  However the fourth option can be tackled in firmware with minimal hardware overhead. 

In order to smoothly transition between accelerations we can simply use a different curve to interpolate the feedrates.  The conditions needed are that the feedrates match the desired rates at the beginning and end of the curve and that the slope of the feedrate curves (i.e. the acceleration) is zero at the endpoints.  In between the endpoints we want the curve to be smooth.

The one of the simplest classes of functions that meet these requirements are cubic polynomials.  These are defined by four coefficients $a$, $b$, $c$ and $d$ using the following equation, where $\tau$ is the fraction of the total time spent accelerating:

\begin{equation}
f(\tau) = a \tau^3 + b \tau^2 + c \tau + d
\end{equation}

We now want to solve for the coefficients needed to reproduce the move.  There are four coefficients so we need four equations.  Two come from the requirement that we match the feedrates at the curve endpoints:

\begin{eqnarray}
f(\tau=0) = a 0^3 + b 0^2 + c 0 + d &=& f_0 \\
f(\tau=1) = a 1^3 + b 1^2 + c 1 + d &=& f_1
\end{eqnarray}

From these, we see that $d=f_0$ and $a+b+c=f_1-f_0$. The remaining two equations can be found using the requirements that the slope of the feedrate curve is zero at the endpoints. To enforce these constraints we need the derivative of the cubic function:

\begin{equation}
f'(\tau) = 3 a \tau^2 + 2 b \tau + c
\end{equation}

The constraints can now be enforced by requiring that:

\begin{eqnarray}
f'(\tau=0) = 3 a 0^2 + 2 b 0 + c &=& 0 \\
f'(\tau=1) = 3 a 1^2 + 2 b 1 + c &=& 0 
\end{eqnarray}

These equations make it clear that $c=0$ and $3 a + 2 b = 0$. Combining these with the previous conditions leaves two equations and two unknowns:

\begin{eqnarray}
a + b &=& f_1 - f_0 \\
3 a + 2b &=& 0
\end{eqnarray}

So $a = -\frac{2 b}{3}$ which means that $b = 3 (f_1-f_0)$ and $a = -2 (f_1 - f_0)$. This gives the following equation for the interpolating curve:

\begin{equation}
f(\tau) = -2(f_1-f_0)\tau^3 + 3(f_1-f_0)\tau^2 + f_0
\end{equation}

The only remaining thing is to define $\tau$ in terms of $t$.  This is a simple linear interpolation from the start of the acceleration $t_0$ to the end of the acceleration $t_1=\frac{f_1-f_0}{a}$:

\begin{equation}
\tau = \frac{t-t_0}{t_1-t_0} = \frac{t-t_0}{\frac{f_1-f_0}{a}-t_0}
\end{equation}

Plotting this for the same parameters as before gives a smooth, kink-free curve that considerably reduces the time-rate-of-change of acceleration:


In the post-to-come I will demonstrate applying this to a real stepper motor being driven aggressively.  Although seemingly complicated, for a cost of only a few operations per step, it is possible to switch from the linear acceleration profile to the cubic one derived here and get considerably smoother operation.

Sunday, March 24, 2013

Setting up the WiFly RN-XV with a Teensy 3.0

A recent order from Sparkfun arrived, including a 3.3V Serial LCD and a Roving Networks RN-XV WiFly module.  The RN-XV module is intended to be a drop-in replacement for an XBee, except that it operates over WiFi.  At about $35, it is just about the cheapest way to make your project wireless enabled.

The module is 3.3V, meaning some form of level shifting is needed with a 5V system like an Arduino.  You can use this module with an Arduino via an XBee shield pretty easily.  However it is even easier to use with the 3.3V Teensy 3.0 ARM board, provided you have a breakout for small-pitch XBee module footprint.  The Teensy is also nice for this application because it has multiple serial ports, so you don't need to use the SoftwareSerial library, or program the board, then disconnect to use the wireless.

Setting everything up was pretty easy once I knew what to do, but this post summarizes the process should I ever need to do it again.

The setup that I am using is shown below:


Only four connections are needed once you're set up, 3.3V, GND and two data connections.  DOUT from the Teensy3.0 Serial3 connects to DIN of the RN-XV and DIN from the Teensy to DOUT to the RN-XV.  This makes the module operate as just a serial port, making it pretty easy to interface with. The remaining orange wire connects the Serial LCD display, more on this later.

To get started, I found it was easiest to set the RN-XV in ad-hoc mode. This can be done by connecting pin 8 to 3.3V and will cause the module to create its own wireless network.  When this happens you will see the status LEDs blinking green, orange and red; they're doing it, but you can't really see in this picture.  Note the additional green wire to 3.3V connected to the 8th pin.


You can then look for the network. On a Mac it's pretty easy, it just shows up in the list of networks in the status bar:


The WiFly shows up towards the bottom as WiFly-GSX-a8 or something similar.  If you connect to this network, you can then telnet to the module using the IP address: 169.254.1.1, port 2000.  The module should then respond with a *HELLO* string, at which point you type $$$ to enter command mode.  Command mode allows you to set up the module for your network.


When the module is ready, it will respond with the CMD message to indicate that you're in command mode.  To set up your network you can issue the commands:


set wlan phrase (password);
set lan ssid (your network name);
save
reboot

You can also issue commands to assign a static IP address to the module, but I didn't do this.  For more information, see this excellent introduction http://www.tinkerfailure.com/2012/02/setting-up-the-wifly-rn-xv/

I found that sometimes the module would respond with a confirmation and sometimes would not. I repeated the process a few times in the hopes that some combination would stick.  After this process, remove the power and and connection from pin 8 to 3.3V.  This will cause the device to try to connect to your wireless network.

You should now be able to telnet to the device, but this time with your computer and it connected to your normal WiFi network rather than the ad-hoc network that the device creates.  However first you need to find the IP address of the module.  To do this, I went into my router configuration page:


Conveniently the WiFly module had an entry: 192.168.1.106. Depending on your router, you should be able to set up a specific IP address for the router to assign to the module based on the MAC address.  However my POS router does not allow this.

I could then telnet to the module's IP address, again using port 2000.  This module responds with the same *HELLO* prompt, indicating that everything was successful and the module is on the network and communicating.

With the connections above the Teensy should now see the module as just another serial port.  To test this, I attached the Serial LCD and uploaded the following code to the Teensy:

#include<stdio.h>

void setup(){
  Serial.begin(9600);
  Serial2.begin(9600);
  Serial3.begin(9600);
}

void write_lines( const char *L0, const char *L1 ){
  
  Serial2.write( 0xFE );
  Serial2.write( 0x01 );
  delay(10);
  Serial2.write( 0xFE );
  Serial2.write( 128 );
  delay(10);
  Serial2.print( L0 );
  Serial2.write( 0xFE );
  Serial2.write( 192 );
  delay(10);
  Serial2.print( L1 );
}


void loop(){
  if( Serial3.available() ){
    char L0[17];
    char L1[17];
    int pos = 0;

    L0[0] = '\0';
    L1[0] = '\0';

    while( Serial3.available() ){
      char c = Serial3.read();
      if( c == '\n' ){
        pos = 0;
        Serial.print('\n');
      } else if( c == '\r' ){
        
      } else {
        if( pos < 16 ){
          L0[pos] = c;
          pos++;
          L0[pos] = '\0';
        } else if( pos < 32 ){
          L1[pos-16] = c; 
          pos++;  
          L1[pos-16] = '\0'; 
        }
        Serial.print( (char)c );
      }
    }
    write_lines( L0, L1 );
  }
  delay(100);
}

My LCD is a 2x16 character display.  The code above just polls for available data on the third serial port and, when a newline is encountered, prints it out onto the display.  Lo and behold, after the following session:

Jamess-MacBook-Pro:~ jgregson$ telnet 192.168.1.106 2000
Trying 192.168.1.106...
Connected to 192.168.1.106.
Escape character is '^]'.
*HELLO*
This is James

The result on the display is below:


Hooray! An utterly useless internet thingy!




Wednesday, March 13, 2013

Reducing Warping/Shrinkage in Large 3D Prints

I've been doing a bit more 3D printing lately and having problems with large 3D prints warping due to uneven cooling. Most of the prints I work on are fairly large, roughly 10x8x4 cm or larger, so this can be a big problem, particularly if the dimensions change enough to mess up the fit of parts. Basically the problem is that as layers cool, they contract and pull off the build-surface. Generally I'm printing PLA on painter's tape, so this will just pull up the tape. An example is shown in the photo below:


You can see how the tape is pulled up at the near edge; this is about 2mm of shrinkage which is not too bad. But this is also a relatively small part, perhaps 5x5x4 cm, and the problem gets worse as the parts get bigger.  A partial fix is to use a heated bed, but the Gen6 electronics for my printer do not support controlling a bed.

However a more practical solution was suggested by another member of the Vancouver Hack Space.  He's working on a very large printer for very large prints; I highly suggest visiting his site ottersoft.ca if you're interested.  Anyway, his suggestion is to space the part up from the bed using some dummy geometry and then print with support material.  The support material forms less connected layers than the object, which gives it more...'give' when printing. Here's an example:


The little block is some dummy geometry added to lower the bottom of the geometry about 1.5 cm. When printed with support, the extra flex of the support lets the object stay attached to the bed without the terrible shrinking.


Part of this seems to be the extra flex, but I suspect that another part is less direct contact with the large metal build-plate, which acts like a heat sink, so the whole part cools more uniformly.


After pulling it off the build plate, you can see that the support material on both sides is in contact with the table, indicating little to no warping.  And this is a big part, roughly 10x8x3 cm.


The downside is that you now have to deal with the support material. If this is the same as the print material, it can be difficult to clean off without leaving a nasty surface finish or changing the part accuracy. For example, filing the support out of the horizontal holes without accidentally changing the hole diameter.  But once done carefully, the part is remarkably accurate: the height as measured at all four corners only differs by about 0.15 mm (with a layer height of 0.3 mm), and the holes are actually round and not elliptical.

I wish that I could take credit for this trick, but that belongs to Loial (ottersoft.ca) of VHS (hackspace.ca)

Sunday, February 10, 2013

Prototype 5-Axis CNC Board

Since I'm working on some CNC firmware, I thought I needed a test platform.  I didn't want to rip-apart the CNC so I decided to build a new 5-axis board using the new Pololu stepper drivers. These are nice little cheap boards and their advantage over the ubiquitous A4988 drivers is that they don't need cooling up to 1.5A per phase.  This makes things way easier, since those tiny little heatsinks for the A4988 chips are scattered all over my apartment, double-sided tape all linted up so they won't stick, just waiting to be stepped on.  They hurt even more than stepping on a Lego block, little buggers.

Anyway, I figured I should just go to five axes, which would allow me to control the 3D printer eventually (1X, 1Y, 2Z, 1E).  So I built a protoboard test board:


I laid out the board and soldered it up.  It was pretty easy except for having to drill a few extra holes in the power-rails to get the spacing I wanted.  After electrically testing everything I decided not to blow all 5 drivers simultaneously on a meticulously copied wiring error, opting instead for only two.

The MCU controlling the whole mess is an Arduino Uno.  I have the step pins on PortD and the direction pins on PortB, which allows fast bitwise operations to be used for stepping.  While picking up the stuff for the prototype board I also bought a proto-screw-shield, wanting a more resilient mounting option for the Uno:





I have the early version of my CNC firmware loaded onto the Arduino.  After firing up the serial terminal, I had the thing running!



Not super crazy, but still rewarding nonetheless.

CNC Firmware running on Arduino Uno

In an earlier post I described some prototype CNC firmware that I had gotten running on the Teensy3.0.  The Teensy3.0 is a 32-bit ARM board and is pretty nice, but I wanted to see if the code would also run on an Arduino Uno.  This is a much more restrictive platform; only 2k of ram and 32k of program space.  However, after a bit of porting of the hardware layer, it compiles and seem to run no problem.  Here's a screen-capture from a (brief) session, the pulse-ticks stuff is debugging the motion control module.


Note that this is running the full GCode interpreter, with spec-compliant command sorting and an 8-move lookahead buffer.  The step timer is running off a 2KHz Timer1 interrupt.  I'm hoping to increase this to 8+ KHz, at least on other platforms, which should make smoother motion control possible, but at the moment I'm just happy that my ANSI C + POSIX code that I've developed on on a 64bit quad core machine with 16Gb ram ports easily to an 8bit system with 2k or ram and only interrupts after changing less than 50 hardware-specific lines of code.

I've written the code this way for just this reason; by keeping all the hardware specific stuff isolated in a hardware abstraction layer I'm hoping to make a code base that stands the test of time, from small embedded platforms to boards like the Raspberry Pi, to full-blown PCs.  So far so good, it's running on an 8bit AVR, a 32 bit embedded ARM and a full X86_64 PC.

Wednesday, January 30, 2013

Start of CNC Firmware Running on the Teensy 3.0

I've been intermittently working on some CNC firmware with the goal of getting it to run on everything from an Arduino to a standard PC.  It's a fun project because it's very resource constrained but involves a number of different parts, like GCode parsing, asynchcronous programming, portability and motion control for non-Cartesian machines.

In the interests of portability I've been writing the various components in plain ANSI C (except the comments, I like my double slashes!).  By doing this I expect the code to be portable across a wide variety of platforms, from AVRs to the Propoeller child to ARM to PCs, with minimal effort.

My hopes are that the firmware will conform to the NIST GCode specification as closely as is manageable.  That said, some sacrifices will have to made as the spec calls for around 20k of addressable space for expressions, which exceeds the total RAM of Arduinos and even the Teensy 3.0.  However I hope to get mostly spec-compliant in terms of order of operations, expressions (if not the addressable space) and features.  Where possible I am also allowing the specific capabilities to be determined by preprocessor macros.

I've done a bit of work on the GCode interpreter, getting some test code together that parses GCode expressions (this led to my Mathematical Expression Parser in C) as well as an arbitrary dimension DDA implementation.  To date these have been just simple tests, nothing actually running on real hardware.  I've also done some early tests on doing feedrate optimization and

However today I made some good progress on actually getting some real firmware started.  I got my early GCode interpreter stripped down and abstracted out all the hardware-specific stuff into a hardware layer.  I then wrote the hardware abstraction layer for the Teensy 3.0 version of the Arduino environment and fired up the code, using the following string to test with:


N1229.0(This is a comment)G01x110.0   y330.0 M7 F20.0(MSG: another comment)

And it worked!


The screenshot above shows the GCode interpreter code loaded up in the Arduino environment, the Teensy loader application that programs the Teensy and the serial output of the parser. Note that this is not just reading the commands linearly, it is fully parsing the input, splitting it into distinct commands, sorting the commands by operation precedence (as described in the NIST spec) and finally calling back to the Arduino sketch with each command.

Parsing the string and calling back takes about 250 us (the remaining time taken is up by serial communications) so more than fast enough to fill up a lookahead buffer.  Total RAM used is about 5k, so this won't run on an Uno but does run handily on the Teensy 3.0. Ditto goes for the flash at 33Kb currently. This is pretty hefty compared to other firmware, but I think portability and cleanness makes up for that.

The next steps will be to get the kinematic/motion control stuff fleshed out.  I think I should be able to use by DDA implementation running with the Teensy timers pretty easily.  Then it's just tweaking and adding features...

Thursday, January 10, 2013

Periodic Interrupt Timers on the Teensy 3.0 (Freescale MK20DX128)

I recently ordered a Teensy 3.0 and today it finally arrived!  It definitely is teensy.  Anyway, after soldering on headers and popping it in a breadboard, I got it running with the blink example.  It took some time, but my issue was downloading directly from the Teensy loader page (which does not support Teensy3.0) rather than from the PJRC Teensy forums. To be fair, the Teensy loader page does have a notice at the top about this, but I missed it.

After getting the software, I was able to write a standard Arduino sketch to blink the LED connected to pin 13.  The code is below:

void setup(){
   pinMode(13, OUTPUT);
}

void loop(){
   digitalWrite( 13, HIGH );
   delay( 100 );
   digitalRead( 13, LOW );
   delay( 100 )
}

It's pretty nice that the Teensy3 works like a regular Arduino, but with more pins, at a higher clock rate, in 32 bits and with heaps of extra peripherals.  However my end goal is to use the board as a CNC controller for my ongoing firmware project.  This will make extensive use of timed interrupts to control steppers, so I thought I'd try to get timers working.  Of course, the Teensy3 is not a Atmel uC, so everything changes at this point and, with the help of this forum post to get started, I had to dive into the manual (available from here) for the Freescale MK20DX128 that the Teensy3 is based upon.

According to the forum post, the timer to use is one of the (4?) Periodic Interrupt Timers (PITs).  As expected, these have a number of control registers. Registers listed with an [N], e.g. PIT_LDVAL[N] should have an appropriate timer index substituted, like PIT_LDVAL2.  Here are the registers:
  • SIM_SCGC6 - Enables/disables clock used by PIT timers, not exactly clear on the details, set to SIM_SCGC6_PIT in the forum post example.
  • PIT_MCR - Enables and disables the PIT timers. Writing zero enables the timers and writing 1 disables them.
  • PIT_LDVAL[N] - Sets the timer count value.  Apparently the timer runs at 50MHz, so toggling timer 2 every second should set PIT_LDVAL2 to 0x2fa080 (hex for 50,000,000).  Visually, this appears to be around a second.
  • PIT_TCTRL[N] - Bit zero (TEN in the manual) enables (set to 1) or disables (set to zero) the timer. Bit one (TIE in the manual) enables (set to 1) or disables (set to zero) interrupts that can be generated by the timer.
  • PIT_TFLG[N] - Flag to indicate timer waiting.  Set to one to start timer and at the end of every called interrupt routine, otherwise interrupts will stop. 
Finally, interupts must be enabled. Again I'm not clear on the details, but calling NVIC_ENABLE_IRQ( IRC_PIT_CH[N] ) results in the interupt "void pit[N]_isr(void){}" being called.  Although it seems like the chip should have four timers, I only succeeded in getting timers 0, 1, and 2 working properly with interrupts, testing with index 3 gave a linker error in the Arduino software.

Anyway, here's the code for my tests:

#define TIE 0x2
#define TEN 0x1

void pit0_isr(void){
  digitalWrite( 13, !digitalRead(13) );
  PIT_TFLG0 = 1; 
}

void setup(){
  pinMode(13,OUTPUT);
  SIM_SCGC6 |= SIM_SCGC6_PIT;
  PIT_MCR = 0x00;
  NVIC_ENABLE_IRQ(IRQ_PIT_CH0);
  PIT_LDVAL0 = 0x2faf080;
  PIT_TCTRL0 = TIE;
  PIT_TCTRL0 |= TEN;
  PIT_TFLG0 |= 1;
}

void loop(){
  delay(2000);
}

Hope this helps someone get up to speed, and perhaps serves as a reference for me later on.

Saturday, December 22, 2012

Test Wax Castings with Pourable OOGOO

My early experiments with casting focused on OOGOO, a low-cost casting material made from cornstarch, silicone caulking and food coloring.  By adding Xylene (or in my case, lighterfluid) the putty-like consistency can be thinned to a pouring consistency.  The result is a very cheap molding fluid, with the drawback that the molds appear to shrink considerably over the course of several days.  However, since the stuff sets quite quickly and is relatively cheap, it should be no problem to let the molds set for a few hours and then immediately cast some parts.  To this end, I decided to try the process out with wax, as a kind of dry run.  The two molds (after a wax casting) are below:



The molds were still quite soft when I tried this and I didn't mix enough silicone to properly cover the parts so they deformed under their own weight a bit.  However the results were pretty decent, the white parts were the printed masters and the green were cast with candle wax.


It's evident on the smaller gear that some air bubbles were caught in the gear teeth.  I could probably improve this by brushing the part with mold material first.  If I were casting actual parts I would also add patterns for the bolt holes. 

I'm not sure that this process is all that practical for actually making useful parts mostly because the molds are not dimensionally stable for more than a day or so.  But it does help to get a feel for the process and challenges.  These parts are already miles ahead of the previous resin casting I made despite being considerably more detailed.  This leads me to believe that it's a good idea to use single-piece, open-topped molds when possible since it allows air to escape.  With resin, it should also allow me to poke about in the concavities to dislodge any trapped bubbles.

Wednesday, December 19, 2012

Parametrically Designed Gearboxes

Using my Python CSG and Gear libraries, I've been able to start parametrically designing parts.  This can sort of be done with OpenSCAD, but the lack of proper variables and functions makes it difficult.  Recently I tried designing a full gearbox.  The script is shown below: it's a bit messy, but you get the idea:


import gears
import pyPolyCSG as csg

def make_clamp_hub( B ):
    thickness = 9
    hub = csg.cylinder( B/2.0+6, thickness, True )
    hub = hub + csg.cylinder( 4.0, B+10, True ).rotate( 90.0, 0.0, 0.0 ).translate( B/2+3, 0, 0 )
    hub = hub - csg.cylinder( 2.0, 100, True ).rotate( 90.0, 0.0, 0.0 ).translate( B/2+3, 0, 0 )
    hub = hub - csg.box( B/2+6, thickness, 2, True ).translate( B/2+3.5, 0, 0 )
    return hub.rotate(90,0,0).translate( 0, 0, thickness/2-0.01 )

def make_gear( pressure_angle, pitch, teeth, thickness, bore ):
    px, py = gears.gears_make_gear( pressure_angle, teeth, pitch )
    coords = []
    for i in range( 0, len(px) ):
        coords.append( ( px[i], py[i] ) )
    gear = csg.extrusion( coords, thickness ) + make_clamp_hub( bore ).translate( 0, 0, thickness )
    gear = gear - csg.cylinder( bore/2.0, thickness*100, True ).rotate( 90.0, 0.0, 0.0 )
    return gear

pressure_angle = 20.0
pitch          = 0.8
N1             = 12
B1             = 8.0+0.7
T1             = 10.0

N2             = 36
B2             = 5.0+0.7
T2             = 5.0

backlash       = 1.0

dp1 = gears.gears_pitch_diameter( pressure_angle, N1, pitch )
dp2 = gears.gears_pitch_diameter( pressure_angle, N2, pitch )
do1 = gears.gears_outer_diameter( pressure_angle, N1, pitch )
do2 = gears.gears_outer_diameter( pressure_angle, N2, pitch )
dc  = ( dp1 + dp2 )/2.0 + backlash

gear1 = make_gear( pressure_angle, pitch, N1, T1, B1 )
gear1.save_mesh("gear_%gdeg_P%g_%d_tooth.obj" % ( pressure_angle, pitch, N1 ))

gear2 = make_gear( pressure_angle, pitch, N2, T2, B2 )
gear2.save_mesh("gear_%gdeg_P%g_%d_tooth.obj" % ( pressure_angle, pitch, N2 ))


def hole_xy( x, y, radius, height ):
    return csg.cylinder( radius, height, True ).rotate( 90.0, 0.0, 0.0 ).translate( x, y, height/2.0 )

B1s        = 10.0
B1p        = 22
B2p        = 10

box_pad    = 5.0
screw_diam = 4.0
box_thick  = 4.0
nema_offset  = 31.0/2.0

box_height = max( (do1, do2 ) ) + box_pad*2.0
box_width  = max( ( dc + (max( ( B1p, do1 ) )+do2)/2.0 + box_pad*2.0, nema_offset+dc+dp1/2 + screw_diam*2.0 + box_pad*2.0 ) )
screw_x_off = box_width/2.0-screw_diam
screw_y_off = box_height/2.0-screw_diam

g1_coords = ( box_pad+max((do1,B1p))/2.0, box_height/2.0 )
g2_coords = ( g1_coords[0]+dc, g1_coords[1] )

plate = csg.box( box_width, box_height, box_thick )
plate = plate - hole_xy( box_pad, box_pad, screw_diam/2.0, box_thick*2.0 )
plate = plate - hole_xy( box_width-box_pad, box_pad, screw_diam/2.0, box_thick*2.0 )
plate = plate - hole_xy( box_width-box_pad, box_height-box_pad, screw_diam/2.0, box_thick*2.0 )
plate = plate - hole_xy( box_pad, box_height-box_pad, screw_diam/2.0, box_thick*2.0 )


plate = plate + hole_xy( g1_coords[0], g1_coords[1], B1p/2.0+4.0, box_thick ).translate( 0, 0, box_thick/2+3 )
plate = plate - hole_xy( g1_coords[0], g1_coords[1], B1p/2.0, box_thick ).translate( 0, 0, box_thick/2+3 )
plate = plate - hole_xy( g1_coords[0], g1_coords[1], B1s/2.0, box_thick*2.0 )

plate = plate - hole_xy( g2_coords[0], g2_coords[1], B2p/2.0, box_thick*2.0 )
plate = plate - hole_xy( g2_coords[0]-nema_offset, g2_coords[1]-nema_offset, screw_diam/2.0, box_thick*2.0 )
plate = plate - hole_xy( g2_coords[0]+nema_offset, g2_coords[1]-nema_offset, screw_diam/2.0, box_thick*2.0 )
plate = plate - hole_xy( g2_coords[0]+nema_offset, g2_coords[1]+nema_offset, screw_diam/2.0, box_thick*2.0 )
plate = plate - hole_xy( g2_coords[0]-nema_offset, g2_coords[1]+nema_offset, screw_diam/2.0, box_thick*2.0 )
#plate = plate + gear1.translate( g1_coords[0], g1_coords[1], -10.0 )
#plate = plate + gear2.translate( g2_coords[0], g2_coords[1], -10.0 )

plate.save_mesh( "gearbox_plate.obj" )

This generates two gears and two gearbox plates that are mounted together using M3 screws.  The printed gearbox is shown below, although I'm missing some bearings and hardware.  Each plate has a recess into which a 608ZZ bearing can be press-fit.  Depending on the orientation it can either serve as a fixed or floating end.


I hope to use gearboxes like this to increase the speed that I can drive leadscrews at for my CNC project, which currently has pretty limited feedrates.  This would allow the use of cheap hardware store threaded rods, rather than expensive leadscrews, while still being a self-locking drive.

Update on Pourable OOGOO

Just for completeness, after waiting a few days/weeks the pourable OOGOO that I made using standard OOGOO and lighter fluid did actually set up well.  However it also shrank quite a bit, so I doubt it will be effective for casting.

An End to Captive Nuts

I've been using 3D printed gears and timing pulleys for a while now, but have been very disappointed with the captive nuts that are used by most scripts available on Thingiverse. Generally I've found that the material being printed isn't stiff enough to allow the set screws to be tightened enough to secure to the shaft without deforming. This causes the pulley or gear to deform out of true.

To get around this I've started using clamping hubs.  They're not too much larger than the captive nut hubs but deform uniformly so the pulley/gear run true.


The picture above shows an example gear.  I've used an M3 screw in the clamping hub.  It ends up fixing very securely to the 8mm shaft and has the additional advantage of not marking the shaft.

I printed the gear above after using my Python Involute Gear Script to generate the involute profile, followed by the Python Constructive Solid Geometry Library to generate the hub and 3D model. The full source of the script that I used is shown below:

import gears
import pyPolyCSG as csg

def make_clamp_hub( B ):
    thickness = 9
    hub = csg.cylinder( B/2.0+6, thickness, True )
    hub = hub + csg.cylinder( 4.0, B+10, True ).rotate( 90.0, 0.0, 0.0 ).translate( B/2+3, 0, 0 )
    hub = hub - csg.cylinder( 2.0, 100, True ).rotate( 90.0, 0.0, 0.0 ).translate( B/2+3, 0, 0 )
    hub = hub - csg.box( B/2+6, thickness, 2, True ).translate( B/2+3.5, 0, 0 )
    return hub.rotate(90,0,0).translate( 0, 0, thickness/2-0.01 )

def make_gear( pressure_angle, pitch, teeth, thickness, bore ):
    px, py = gears.gears_make_gear( pressure_angle, teeth, pitch )
    coords = []
    for i in range( 0, len(px) ):
        coords.append( ( px[i], py[i] ) )
    gear = csg.extrusion( coords, thickness ) + make_clamp_hub( bore ).translate( 0, 0, thickness )
    gear = gear - csg.cylinder( bore/2.0, thickness*100, True ).rotate( 90.0, 0.0, 0.0 )
    return gear

pressure_angle = 20.0
pitch          = 0.8
N1             = 12
B1             = 8.0+0.7
T1             = 10.0

gear1 = make_gear( pressure_angle, pitch, N1, T1, B1 )
gear1.save_mesh("gear_%gdeg_P%g_%d_tooth.obj" % ( pressure_angle, pitch, N1 ))

I've found that being able to use Python is much more convenient than OpenSCAD, mostly because its possible to define (real) variables and functions/classes.  As a result I've pretty much switched to using the Python CSG library from OpenSCAD.

Sunday, December 2, 2012

Improving part surface finish and accuracy for casting

3D printing is great, but RepRap style printers generally don't produce great surface quality, while also having problems reproducing exact dimensions.  Most people address the second issue by filing their parts to fit, but I wanted to see if I could simply print test parts and adjust the dimensions a bit.


This seems to work pretty well, the part shown above had the post mounting holes pattern negatives (the pegs in the above photo) off by about 1.2 mm.  So I just subtracted that from the initial diameter and regenerated the part with my Python CSG library and, to my surprise, the diameters came out correct to within 2 thou.  The same goes for the horizontally oriented cylinder, one simple iteration of correction produced the correct diameters within a very small tolerance.  I've also found that printing external perimeters at 20 mm/s while printing everything else at the maximum reliable 80 mm/s feedrate for my RepRap gives the best surface finish and fastest printing time of all the options I've tried.

To improve the surface finish of the parts I tried filling the surface with Bondo body-filler to fill in the filament marks.  I wasn't sure if the Bondo would dissolve the PLA, but it seems to be fine.  After a light surface sanding the final part had a very clean surface finish, with little to no filament marks and consistent, accurate diameters.


The pink in the above photo is the Bondo fairing compound.  You can get an idea of how thin the coating is since you can still see the dimension adjustments I'd marked on the part through the fairing.  This coating just fills in the small gaps between the filament and allows the surface to be quickly sanded smooth.  This should dramatically improve the surface finish of cast parts.

Spending a bit more time on the patterns, embedding positives of the cores to be used in casting (the vertical pegs and horizontal cylinder above) and moving to an open-topped casting method should improve the quality of my cast parts considerably.  By using an open mold, I'm hoping that the air-bubbles that ruined the previous part can be popped at the exposed resin surface easily.  I've also remove sharp concave corners in the pattern, by filleting the relevant edges, in the hopes of improving these features.  Finally the time spent finishing the pattern should pay off in spades, since it will reduce cleanup of each cast part by the same amount.

Pourable OOGOO

Recently I have developed an interest in using resin casting to produce small numbers (several dozen at most) of high-quality plastic parts.  The motivation for this is to be able to produce stiffer epoxy parts for my CNC quickly and at relatively low cost.  I've mostly been investigating a material called OOGOO, a Sugru 'substitute' made from silicone and cornstarch which is cheap, easy to get the raw ingredients for, and seems very well suited to casting resin parts, based on my experiments.  Below you can see the first ever resin casting I've made; I freely admit that it's a piece of junk, but turned out surprisingly well given the amount of knowledge I had about casting and is impressively strong compared the the 3D printed original.


However a drawback of OOGOO is that it basically forms a putty, so quite a bit of care is needed to make sure that you get good contact with the pattern in order to not leave voids in the mold.  People have tried making a pourable OOGOO using Xylene as a thinner (see comments in the OOGOO Instructable link above), but I can't seem to get Xylene in quantities less than about 4L, which is way too big a container to store in my apartment.

I've read in random forums that White Gas can be substituted for Xylene to thin OOGOO. 'White Gas' appear to be a catchall term and may or may not be lighter fluid depending on the brand and region where you live. So I decided to give it a go with lighter fluid to see i) if it thinned OOGOO to the point where it could be poured and ii) if the thinned OOGOO would ever set up properly.  The results, at least at the early stages, are 'yes' and 'maybe' respectively.  I'm hoping that a bit of time will change this to 'yes' and 'yes'.

After mixing a lot of lighter-fluid into the OOGOO, I definitely obtained something pourable.  I have no idea how much, I was simply winging it, but I would guess two parts lighter-fluid to one part each of silicone and corn-starch.  I poured it over a scrap PLA printed part, not knowing if the lighter-fluid would dissolve it:


At this (estimated) ratio, it was still quite thick, probably comparable to a cold molasses and so some coaxing was required to get it onto the part.  However it slowly settled on the part and settled around it.  I used PAM cooking spray as a release agent, again hearing on a random forum that it worked well.


Next time I would skip the release agent since I don't think the OOGOO sticks particularly well to the PLA printed part, and wherever it contacted the part but other OOGOO flowed over it the two bits of fluid would not adhere well.


Detail transfer from the part was excellent, but the OOGOO remained very, very soft after about an hour.  I'm hoping that this is simply because the majority of the solvent (lighter fluid) hasn't evaporated yet, and that once it does the mold will be more rigid.  At the moment the material is not stiff and tear-resistant enough to be usefully used as a mold, it will simply get destroyed when demolding the first few parts.

The bits left inside the jar that I mixed the thinned OOGOO appear to be much stiffer, so I'm hoping that a few days left to blow off the volatiles from the lighter-fluid will result in a stiffer material, but even several hours in the mold is still quite spongy.  I'll check it again after a few days and see if the mold is somewhat usable.  If so then I will probably start thinning my OOGOO.  I've also disccovered a tin of Xylene on the shelf in the parking garage, so I may steal a hundred mL or so, and if it works better, replace the tin with one I can dip into guilt-free from time to time.

Thursday, November 29, 2012

Improving 3D print quality, by doing the obvious

Some time ago I bought and assembled a RepRap Prusa with the intention of using it to prototype things.  It has saved me countless hours in prototyping my CNC but getting good quality prints out of it has been challenging at times.  Granted my standards for good have gone up even as I've developed a hunger to print as fast as possible.

Printing fast has worked for the CNC, which has large, fairly simple geometries.  It's nice to be able to print an axis end in two hours, rather than eight.  But when I try to print small, accurate objects I tend to run into issues, especially with PLA.  The biggest one has been that smooth surfaces end up ridgy, see for example the print in the image below:


That print is supposed to be a linear bushing with circular sections, but the print is garbage due to the ridges.  I tried a lot of things to reduce them, most focused on the mechanical rigidity of the printer.  I tightened everything up and added diagonal braces in the X-direction. I even 'floated' the entire printer by sitting it on soft foam blocks so that the entire printer could move as a rigid body when the bed and extruder moved rather than cushioning jerk with elastic deformation.  Amazingly enough, this simple idea CAN actually improve print quality for wobbly printers quite a bit, but did not fix my problem.

The focus on mechanics and dynamics is probably not surprising given my background, but it turned out to be misplaced.  What should have clued me in that the mechanics were not to blame is that everything got a lot worse with PLA, even when using the same travel/speed/acceleration settings that I had been using with ABS.

Eventually I did realize this and started looking for extruder/heat problems, measuring and adjusting temperatures and filament diameters, changing nominal nozzle diameters and so on.  None of these seemed to have any effect.

Finally I ran across a post (I don't remember where I found it), that said to just print perimeters slower.  This seemed crazy since i) the mechanics were sound ii) the extruder is perfectly happy running quite quickly with the beast of a stepper I have on it.  But I tried it and my prints instantly got better.


Here is a print with perimeter speed of 40 mm/s.  Although it's difficult to tell in the photo, the surface is actually quite a bit better than the bushing printed above using 80 mm/s perimeters. Finally here is the same print, but with 20 mm/s perimeters:


The difference in the photos is much more evident here and even more so in the real world.  Note that all prints were made with aggressive, active cooling.  So what gives? Why does the print quality improve when printing slower in PLA, even though the extruder and printer stiffness are the same that was used for ABS at much higher speeds?

I have a theory, taken from the post that recommended printing slower and combined with (my own) conjecture about the print material.  The theory is that communication lag between the host software and firmware causes the motion to stutter a bit, particularly on smooth curves that are discretized with many small segments.  With limited lookahead, the printer does a few segments very quickly and empties its buffer before the host can refill it.  This causes the printer to periodically, but regularly, stall.  Since the temperature of the extruder has to be kept high to print at high speeds, the PLA in the nozzle is runny and gobs out during these stalls, creating the surface ridges.  I suspect the surface imperfections are less noticeable with ABS because it generally is not as liquid as the PLA, allowing you to get away with higher feeds without noticing the buffer starvation.

I should be able to test this by changing the baudrate on the printer.  Doubling the send-rate should allow me to increase (ideally double) the feedrate, but will require recompiling the firmware to test.  When I get a chance to test the idea out, I'll post the results.

In hindsight, slowing things down was an obvious thing to try.  My Prusa bobs like a cork on the soft-feet at 80 mm/s and all of my experience on other 3D printers suggests that slower printing gives improved results.  But I wanted to have it all and kept increasing the feedrate.  However, if I am right about this, perhaps I can go back to printing at high feeds while keeping the high print quality.

Friday, November 9, 2012

First Resin Casting

Following up on my two previous posts on making Silicone molds from OOGOO, I actually managed to cast a part today.  I ended up with mixed, but promising results as you will see. 

I started by cutting risers and sprues into the mold from my previous post.  Resin is poured into the sprues and air/resin escapes through the risers.  After the pour has finished, both the sprue and riser serve as reservoirs of excess resin to counteract shrinkage of the poured resin as it cures.


I used West System epoxy as a casting resin, which it is not specifically intended for.  It is however locally available and relatively reasonably priced.  Epoxy is also one of the least objectionable resins, much less stinky than polyester resin and apparently has substantially less shrinkage.  It's also easy to get the proportions right if you buy the pump kit.  But on the downside, it's meant for making boats, not casting.  Oh well. 


I don't have any pictures of pouring the resin, since I didn't want to gum up my phone, but in the photo above you can see the mold with mounting hole rods inserted and sprue/riser filled with epoxy.  I used vaseline as a mold release, especially thick on the metal parts since I was concerned that the epoxy would bond to the posts and scrap not only the part but the mold as well.  After pouring I plugged both holes and tumbled the mold by hand, hoping to get epoxy into all the nooks and crannies.  I failed, but more on this later.  I was concerned initially since my excess resin hardened quite quickly, but the resin in the mold appeared to harden much slower, judging by how gummy the resin at the top of the sprue and riser was.  So I left it for about four hours and luckily everything seemed solid when I checked it.


Here's the bottom half of the mold removed.  Unfortunately I ripped off the bottom half a bit too vigorously and tore the center plug.  Anyway, it was only a first try.


Here you can see the part almost completely separated from the mold, the only bit remaining is in the central hole which is the bit that I accidentally ripped off.  The vaseline also worked really well as a release agent; a quick easy twist of the posts with some pliers and they slid out cleanly.  This photo also shows the pegs left by the sprue and riser.


I then cut off the sprue and riser and filed the surfaces quickly to clean up the filament marks from the mold.  The bottom half of the part turned out pretty well, with only a few small bubbles.  However the piece looks pretty terrible due to the residual rust from the central pegs being oxidized by the Oogoo acetic acid as well as the yellowish color of the resin, although the photo makes it look worse than it actually does.  I was pleased to see that the accuracy is excellent; after a light sanding of the central hole, the linear bushing I'm using slides in perfectly with no play, although it might need a little dab of glue to keep it seated while in use.


The top portion of the part did not turn out so well due to trapped air bubbles.  The chunks missing in the photo above are all due to trapped bubbles, some of which are over 5mm across.  That's quite big for this part, which is only about 40mm on a side.  The white flecks are dust from the quick filing that is caught in the open surface bubbles.  This part is good enough to be usable, but I will try to produce a better quality version.

I realize now that the way I designed the mold was not the best.  Rather than have the Ooogoo fill the central hole, I could have put a patterned blank in as was done for the mounting holes, making it easier to separate.  Additonally, since this part only needs a single true surface, it could have been case in a single-piece mold which would have allowed me to directly see which areas were not reached by resin and also to intervene.  Having this ability would allow me to avoid the spoiled corners that can be seen in the last photo.  I would also spend more time on the cosmetic details of the pattern since every minor surface flaw was transferred to the mold and then to the finished part.  While this doesn't effect the function of the part, it does annoy me.

As an experiment, I consider this to be a success.  Ugly though it may be, this part MUCH stronger than the original printed pattern.  It is also dimensionally accurate with good surface reproduction, leading me to believe that Ooogoo molds with epoxy resin is a viable method for producing small-run parts, if one that requires some practice.  I expect the next attempt will be considerably improved. 

Wednesday, November 7, 2012

A second attempt at mold making

With the success of the last attempt at Oogoo-based mold making, I decided to try it on one of the current CNC parts.  The last attempt went well, but there were areas that could be improved.  To that end I bought real food-coloring, to get more consistent coloring of the Oogoo.  This is not important just for appearance, but also to see if it is mixed well.  I also bought a small plastic box that would contain the pattern so that I could pack the mold-material into the box around the pattern and be able to apply some reasonable force locally to get surface contact without deforming the mold elsewhere.


Here you can see the part with the steel rods that I'm using as patterns for the mounting holes.  I then put this in the container and packed Ooogoo around it as tightly as possible.  Using the plastic box really improved this step, since I could pack down the Oogoo without separating the mold from the pattern somewhere else.


I then waited about 30 minutes for the material to set and become rubbery and then pulled it out of the box, slit the mold around the edge with a box-cutter.  Here you can see the semi-separated pattern:


I then pulled out the pattern rods, slit one side of the through-hole and pulled out the pattern.


The surface quality is excellent, you can see the individual filaments of the printed part, as well as some 'flashing' where the pattern rods for the mounting holes didn't exactly meet the center hole.


You can also see where the acetic acid rusted the mounting hole peg.  Not sure how to avoid this, it seems to happen even when the pegs are oiled with vegetable oil (to act as a a release agent).


The top of the mold also shows filament marks.  Perhaps next time I'll fill the surface a bit and sand it down to improve the surface quality.  Anyway, the next step will be to cut sprues and risers to get the resin in and out of the mold.  I hope to try out actually casting parts in the next few days once I get some resin.

My first attempt at mold-making

Having finally gotten the CNC up and running, I'm kind of interested in seeing how to manufacture it cheaply and quickly.  The design of the machine centers around 3D printed plastic parts that bolt to aluminum cheeseplates.  These printed parts are the weakest parts of the machine; they are great in the sense that you can build a mill without owning a mill, but bad because the strength simply isn't there.   They are also time-consuming to print: most parts take 30min each or so, meaning that to make a copy would take a couple days or so.  The partially assembled machine is shown below, everything white is 3D printed, and most of the parts are not actually visible.
 

Enter casting.  Cast parts can solve a few of these problems.  Cast parts are likely to be much stronger than the 3D prints because the parts will be solid material and less prone to delamination than the current parts.  They can also be made relatively quickly using epoxy or polyester resins, which set very quickly.  Not much faster than the individual prints take, but multiple parts can be cast in parallel, so an entire axis could be cast at once and be ready in a an hour or so, instead of closer to 8 right now.  Cast parts can be further strengthened by the addition of chopped glass fibers, yielding FRC or FRP (fiber-reinforced composite/plastic) parts, although this will add considerable complexity to the process and likely require vacuum-assisted resin-transfer molding.

A downside is that the casting materials and resins can be quite expensive.  Wanting to get my feet wet, I turned to OOGOO, a Sugru substitute made from Silicone caulking, corn-starch and food coloring (actually I used Mio water flavor to color the first batch).  Below you can see the first test, including all ingredients and the first part still en-molded.


It is important to get the right caulking.  You want clear 100% silicone caulking, with no anti-fungal additives, and preferably a brand that mentions that it will stink of acetic acid (or is 'acid-cure').  'Neutral-cure' is a no-no, as is acrylic caulking.  At first I tried GE Silicone II but this failed to properly set.  GE Silicone 1 is what I eventually ended up using with good results, which is good because it's about $4 per 300 mL, making it one of the cheapest caulks there.

I don't have photos during the process because it was astonishingly messy, but can say that I mixed roughly 2:1 cornstarch to silicone by volume after adding mixing a few drops of water and coloring to the silicone.  You don't have to add coloring, but it helps you tell if the stuff is well mixed once you add the cornstarch.  Mixing the cornstarch is hard work, particularly in quantity, but you want a consistently colored, pastry like result.  Basically as you mix, you want the stuff to kind of tear and crumble at first but eventually reform and blend together.

Be quick though, because with these ratios you have limited working time.  After about 10 minutes the stuff sets up into a putty that becomes harder to work with and by 20 minutes it's effectively rubber.  However you can always mix more and it will bond just fine to Oogoo that has already set.  With my first batch made, I globbed it onto one of the pre-redesign CNC parts, ever cautious that it would dissolve it to nothing or light on fire.  After about 30 minutes I cut open the mold with great trepidation:


I needn't have worried, the ABS part was just fine (as are PLA parts, by all appearances).  Separating the mold showed that the Oogoo is perfect for this sort of thing:



Here you can see the pattern still embedded in half of the mold and then fully separated.  I used vegetable oil as release agent on the pattern prior to making the mold; this worked well, but it is hard to get the Oogoo around the part without also rubbing oil on the Oogoo surfaces that should not have release agent on them while building the mold.  I will have to learn how to deal with this for the next part.

Dimensional tolerances appear to be quite good, there is a firm friction fit between the patten and mold material.  In the photo immediately above, you can see that I wasn't able to get the Oogoo all the way into the slot of the part, but this could be easily fixed with a handsaw on the finished part.


With the pattern removed, the rods can be reinserted into the mold to create the impression for the mounting holds.  Provided the casting resin has low-enough viscosity, it will then easily flow around these posts to create the mounting holes.   I found it interesting to see that the acetic acid produced by the curing caulking actually corroded the post patterns, which you can see above by the discoloration of the steel posts where they were in contact with the Oogoo and by the rust-stains on the Oogoo itself.

Anyway, this process seems like it has great promise.  It's easy to work with, very cheap (less than $8 per pound) and seems to give quite accurate results.  The final molds are predominately silicone too, which has high heat resistance for the eventual polyester resin casting (I have seen large amounts of resin actually auto-ignite due to the exothermic reaction as the resin 'kicks').  I intend to keep playing with this stuff and hopefully end up with a reasonable method for performing casting of printed parts in the near future.