Textured 3D Printing: How to explore it yourself.

This blog post aims to give the essential details for other people to recreate some research I have done at CFPR.  When I started my employment as a Research Fellow I was given unlimited access to a Rostock Max FDM 3D printer to cut my teeth.  I developed an intimate understanding of the machine, software and materials, and with a playful attitude, I developed unusual surface textures such as this:






After constructing the 3D printer kit I began to experiment in getting the machine to work to the expected standard (smooth surface finish, fast print times).  Soon enough various 'failures' were cropping up in the printed objects.  In a slight of lateral thinking, I wondered if the failures were consistently reproducible - and thus born was the idea that the FDM 3D printer was capable of producing a variety of unusual surface textures if it was controlled in unusual ways.  I keep a box of 3D print failures which are inspirational for new printing methods.  I mention this in a short video here; the idea has become quite a meme in our research group.  Suffice to say, by ignoring the 'rules' or sensibilities of 3D printing, there seems to be a rich set of expressive qualities to explore, and also hidden functional properties.

Using a printer in 'unusual ways' meant getting rid of CAD models and slicing algorithms as these are too automated and encapsulate someone else's idea of how to 3D print.  By writing GCode it is possible to develop printing methods for texture from the point of view of how the material can be expressively played with.  

This blog post should allow you to recreate this initial study, to explore and play with the 3D printer and material without CAD and slicing algorithms in the way.  Don't expect things to work first time - try to enjoy the process of discovery.  As a disclaimer, I can't guarantee that the described methods will not damage your machine, environment or person - so be cautious and take responsibility for your actions.

I have been sitting on this research for a couple of years for various reasons, so it is good to finally write about it and put it in the public domain.  From the initial foray, I developed the conceptual and academic ideas behind what I was doing through many discussions with Peter Walters, Stephen Hoskins and Adrian Geisow.  There is an academic paper on the subject available here (sorry for the paywall).  In particular, Adrian Geisow took the technical foundation and began to write a new CAD user interface to specify textures - so the two of us ended up bouncing technical ideas between us quite rapidly.  

It is also worthwhile noting that there are a few other people investigating similar things.  Project Silkworm have used the Rhino Grasshopper plugin to weave FDM filament in way similar to spirographs; Peetersm on Thingiverse exploits printing into free-air to force a nice effect through CAD and slicing; artist Shane Hope subverts FDM 3D printing machines in paintly ways; most recently researchers at Carnegie Mellon University have used FDM to print hairs with NinjaFlex filament.

The methods I present below should be quite accessible and reproducable.  The following are necessary: 
  • You have an FDM 3D printer that communicates via GCode (not a proprietary protocol, such as a the newer Makerbot use).
  • You have a computer capable of running Processing to explore the presented code and to generate your own GCode files.
  • As a caution (not mandatory), your 3D Printer firmware has coordinate limits so that GCode commands can not drive the machine outside the working envelope or below the print bed, or raise temperatures dangerously.  If you can't ensure this, be ready to hit the reset button if your machine looks like it is about to do something it shouldn't.
  • You have a way of sending GCode to the 3D printer.  Software like RepetierHost allows you to import and transmit GCode.
  • Your printer places the co-ordinate origin (0,0,0) at the centre of the build platform, not at a corner.  If not, you will need to modify the code to offset all produced GCode coordinates to something central.
Enjoy.


Process Overview

Using the 3D Printer to manipulate the plastic material into expressive textures requires a calibration relative to your machine.  Each machine will be unique.  

To calibrate, you need to the machine to print something easy and consistent so you can study how the extruded material is deposited and make adjustments.  The easiest way to do this is print a cylinder on a spiral toolpath.  This means getting the nozzle to move in circles and slowly ascend in the z axis.  It is the most controlled way for your printer to operate.  

Once a cylinder can be printed consistently, you can begin to explore how altering the extrusion rate changes the surface texture, as well as how deviations to the nozzle movement can create texture, and eventually how manipulations of object geometry can also be used.  The two key variables are extrusion rate and nozzle velocity.

Printing Cylinders and GCode

Printing cylinders and GCode are both quite easy.  There is a list of GCodes here.   Don't be put off by how many GCodes there are.  The most important in this case is G1, as this instructs the machine to move.  It takes the form of: 
G1 X Y Z F E,
such as G1 X12.33 Y-25.00 Z0.15 F3000 E1.2.  The G1 is the command instruction (G1 equals move), and the other parts are simply the coordinates in XYZ, the speed of movement (Feedrate), and the quantity of material to extrude.  If you have set coordinate limits in your 3D printer firmware, you can safely send bad coordinates to your machine and it will not destroy itself.  Likewise, 3D printer firmware can also be set to have a limit on extrusion rate, feedrate and temperatures.  If these are set, sending GCode to your machine is quite safe.  If you don't know if you have these limits in place, be ready to hit reset should something strange happen.

To print a cylinder we need to create a list of coordinates, and transform these into a list of G1 GCodes.  You can imagine being the nozzle of the machine and needing to be told to progressively walk around the perimeter of the cylinder, step by step.

A cylinder can be described by a long list of X Y and Z locations in 3D space.  When 3D printing, Z starts at 0 and needs to move upwards (ascend), so that material is placed on top of previous layers of material.  A cylinder can be thought of as a stack of circles.  So to print a cylinder we need to only describe a circle as a list of X and Y locations, and also move up a bit every time we finish drawing the circle.

Circles:

The X and Y points for a circle exist on the circumference.  An X and Y location on the circumference can be calculated using the centre of the circle, outward by a distance called the radius, and at an angle between 0 and 360 degrees.  Our cylinder has a fixed radius, and we can fix the origin, so we only need to progressively change the angle to draw the circle we need.

Example psuedocode to create coordinates for a circle:
radius = 10;
for( angle = 0; angle <= (2*PI); angle += 0.6 ) {
    x = radius * cos( angle );
    y = radius * sin( angle );
}
In the above example, a list of 10 XY points would be created.  This is because the angle value starts at 0, increments by 0.6 on each loop, up to 2*PI (6.28/0.6).  Why 2*PI?  2*PI is equivalent to 360degrees, and is the value in radians.  It is enough to know that computers generally use radians for angles, such as the sin and cos functions.  Why x = radius * cos( angle ) and y = radius * sin( angle )?  These are from trigonometry (remember Soh Cah Toa?), and some explanation can be found here, here, and here

Working in millimetres; we calculate that a circle with a radius of 10 has a circumference of 62.83.  If we have created 10 points in our list of XY coordinates, each movement of the printer nozzle will be 6.28mm in distance (the circumference divided by the number of list points).  This would create a circle drawn with very visible flat edges.  The printer will move the coordinates in the list, it will not smooth things out for you.


Therefore, the amount we increment the angle by controls how large each movement step of the nozzle will be.  Smaller angle increments will result in smaller movement steps, and a longer list of points to traverse.  To create a consistent print for calibration we want to control the movement step regardless of the radius of the cylinder we are printing.  We can add to the example code to fix the movement step distance:

radius = 10;
circumference = 2 * PI * radius;
move_step = 0.2;
num_steps = circumference / move_step;
angle_increment = (2*PI) / num_steps; 
for( angle = 0; angle <= 2*PI; angle += angle_increment ) {
    x = radius * cos( angle );
    y = radius * sin( angle );
}

Reading through the above example we set the radius to 10mm, calculate the circumference of the circle, set the movement step to 0.2mm, calculate how many steps there will be within the circumference, and then calculate the angular increment by dividing 2*PI (360degrees, a complete circle) by the number of movement steps.  

Following the calculation through, the angular increment is 0.02 radians, generating 314 steps of 0.2mm movement.  To the human eye this will be a very smooth circle.  

This is an important step for later, as we might change the radius of the cylinder as we print, but want to keep a nozzle movement step of 0.2mm.  If we change the radius, the angular increment would adjust to keep 0.2mm of movement.

Incrementing Z:

Most often, FDM machines increment the Z height layer by layer.  This is because the objects printed have a complex perimeter and often infill, so it is simplest to print a cross section of the object and then move up a layer.  For our calibration and texture exploration, this would create a sudden change of behaviour from the printer as it changed layer height.  It is actually quite common for a 'seam' to appear where the printer changes layer height.  It is possible to slowly increment the Z height so that there is never a single instance of z height transition (slic3r has this as the 'vase' feature).  This is easiest on objects with only a perimeter, such as the cylinder we are printing.  

A slowly incrementing Z co-ordinate, combined with our circle co-ordinates, would effectively create a corkscrew toolpath, or a spiral toolpath.  Each time we complete a revolution of the circle, we want to have moved up in the Z axis by at least the height of the material deposited.  Therefore, our Z increment is also related to the angle when drawing the circle.

Extending the previous example pseudocode:

radius = 10;
circumference = 2 * PI * radius;
move_step = 0.2;
num_steps = circumference / move_step;
angle_increment = (2*PI) / num_steps;  
layer_height = 0.15;
z_increment = layer_height / num_steps;
z = 0; 
for( angle = 0; angle <= 2*PI; angle += angle_increment ) {
    x = radius * cos( angle );
    y = radius * sin( angle );
    z += z_increment;
}

In the above example, we set a layer height to 0.15mm.  This is the theoretical height of a single layer of material.  This value will change depending on how fast you extrude material, as well as whether your nozzle is smearing material as it prints the object.  In fact, setting this value low is quite tolerant because the hot nozzle will push or smear obstructing material out of the way.  However setting this value too high  will result in the nozzle raising too quickly, and you will extrude plastic into free air.  This value should be close to what you would use ordinarily for your 3D printer layer height.

To raise a layer height after one full circle, the layer height is divided by the number of steps (num_steps) to traverse the circumference which we previously calculated.  This creates the z increment value.  Within the for-loop this value is accumulated by the z variable.  There are more intelligent ways to do this, relating to angular position, but simply accumulating will suffice for now.  
The example pseudocode can be extended again to loop for the height of an entire object, ascending through the Z axis:

z = 0;
object_height = 100; 
while( z < object_height ) { 
    radius = 10;
    circumference = 2 * PI * radius;
    move_step = 0.2;
    num_steps = circumference / move_step;
    angle_increment = (2*PI) / num_steps;   
    layer_height = 0.15;
    z_increment = layer_height / num_steps;
 
    for( angle = 0; angle <= 2*PI; angle += angle_increment ) {
        x = radius * cos( angle );
        y = radius * sin( angle );
        z += z_increment;
    } 
}

Importantly, we set the variable z to 0 to begin, and set a limit of z via object_height = 100mm. 

Creating GCode:

To create GCode we will migrate our pseudocode to Processing.  We need to do is convert our variables X Y and Z into a text string, and write this to a text file.  We also need to add some start up routine for the 3D printer to heat up etc.  

In Processing, formatting numeric variables into a text string is easy.  Here is an example:
float x = 1.2;
float y = -10.5;
float z = 12.23;
float e = 2; 
String command; 
command  =  "G1 ";
command += "X";
command +=  nf( x, 0,2);
command += " Y";
command += nf( y, 0, 2);
command += " Z";
command += nf( z, 0, 2);
command += " F3000";
command += " E";
command +=  nf( e, 0,2 );
Importantly, the nf() function formats our floating point number into a text String with a set precision - without this, the number of digits is too long, and you'll overflow your 3D printers command buffer.  The printer will act very strange if that happens.  Also make sure you add white space (as above) to separate out the components of the command string.  

Here is code that will produce a printable cylinder via Processing:



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Processing method to write text to disc.
PrintWriter gcode_file;


void setup() {

  // Command string to write out
  String command;
  
  // GCode variables
  float x, y, z, e, f;
  
  // Object height and z variables
  float object_height;
  float z_increment;
  
  // Object radius traversal variables.
  float radius;
  float circumference;
  float move_step;
  float num_steps;
  float layer_height;
  float angle_increment;
  float angle;
  
  // Printing key variables.
  // Alter these to achieve a consistent
  // print quality.
  float e_rate = 0.012;  
  float f_rate = 300;
  
  // create new gcode file in sketchbook folder
  gcode_file = createWriter("test.gcode");

  addStartUpRoutine();

  z = 0; 
  e = 0;
  object_height = 100; 
  while ( z < object_height ) {
    
    radius = 10;
    circumference = 2 * PI * radius;
    move_step = 0.2;
    num_steps = circumference / move_step;
    angle_increment = (2*PI) / num_steps;   
   
    layer_height = 0.15;
    z_increment = layer_height / num_steps;

    for ( angle = 0; angle <= 2*PI; angle += angle_increment ) {
      
      x = radius * cos( angle );
      y = radius * sin( angle );
      
      command = "G1 ";
      command  =  "G1 ";
      command += "X";
      command +=  nf( x, 0,3);
      command += " Y";
      command += nf( y, 0, 3);
      command += " Z";
      command += nf( z, 0, 3);
      command += " F";
      command += nf( f_rate, 0, 0);
      command += " E";
      command +=  nf( e, 0,3 );
      
      // Write command to the file
      gcode_file.println( command );
      
      // Increment z and e after the last move
      z += z_increment;
      e += e_rate;
    }
  }
  
  // Home the printer at the end.
  gcode_file.println("G28");
  
  // Ensure the data is written to disk
  gcode_file.flush();
  gcode_file.close();
  
  // Finish.
  exit();
}


// These commands were extracted from a standard
// GCode file from Slic3r set up for the Rostock
// Find similar for your machine. 
void addStartUpRoutine() {
  // Set units to mm
  gcode_file.println("G21");
  
  // Heat the build platform    
  gcode_file.println("M190 S75");
 
  // Heat the extruder
  gcode_file.println("M104 S185");

  // Home printer
  gcode_file.println("G28");

  // Send to ready position
  gcode_file.println("G1 Z300 F3000");

  // Wait for extrduer to come up
  gcode_file.println("M109 S185");

  // Use absolute coordinates
  gcode_file.println("G90");

  // Set extrusion to zero
  gcode_file.println("G92 E0");
 
  // Use absolute distance for extrusion
  gcode_file.println("M82");
}


There are several additions to this code to make it '3D printable'.  Most importantly, a start up routine is added at the bottom.  This routine contains the commands to bring the printer up to temperature, to set the origin coordinates, and to set the measurement units.  Your start up code will be similar but you should check with your machine.  The best way to do this, is to look at the GCode for an object you have successfully printed previously.

The other additions are e_rate and f_rate.  To achieve a consistent print, I recommend leaving the f_rate at a low value such as 300.  If modify this value you should notice the movement speed of your printer changing.  Leave it set, and instead alter the e_rate to increase or decrease the extrusion rate.  Aim to get a solid extrusion.  Once this is achieved, you can increase the f_rate, and then adjust the e_rate again.  By experimenting in this way, you should begin to understand how e_rate and f_rate relate to each other.

Depending on your 3D printer, you may also need to experiment with the layer_height variable to get each successive layer to stack up nicely.

One last thing: CRC Line Number Checks

Since we want our 3D printer to operate consistently, it might be worth adding in line number checking to ensure that no commands are skipped by the printer.  I didn't include this in the previous example to make it simpler, but also because it may not be supported on all machines.  Here is the modified code to include the line number check.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Processing method to write text to disc.
PrintWriter gcode_file;


void setup() {

  // Command string to write out
  String command;
  
  // GCode variables
  float x, y, z, e, f;
  
  // Object height and z variables
  float object_height;
  float z_increment;
  
  // Object radius traversal variables.
  float radius;
  float circumference;
  float move_step;
  float num_steps;
  float layer_height;
  float angle_increment;
  float angle;
  
  // Printing key variables.
  // Alter these to achieve a consistent
  // print quality.
  float e_rate = 0.012;  
  float f_rate = 300;
  
  // Current line number
  int line;
  
  
  // create new gcode file in sketchbook folder
  gcode_file = createWriter("test.gcode");

  addStartUpRoutine();

  line = 1;
  z = 0; 
  e = 0;
  object_height = 100; 
  while ( z < object_height ) {
    
    radius = 10;
    circumference = 2 * PI * radius;
    move_step = 0.2;
    num_steps = circumference / move_step;
    angle_increment = (2*PI) / num_steps;   
   
    layer_height = 0.15;
    z_increment = layer_height / num_steps;

    for ( angle = 0; angle <= 2*PI; angle += angle_increment ) {
      
      x = radius * cos( angle );
      y = radius * sin( angle );
      
      command = "G1 ";
      command  =  "G1 ";
      command += "X";
      command +=  nf( x, 0,3);
      command += " Y";
      command += nf( y, 0, 3);
      command += " Z";
      command += nf( z, 0, 3);
      command += " F";
      command += nf( f_rate, 0, 0);
      command += " E";
      command +=  nf( e, 0,3 );
      
      // This will add the CRC line check.
      command = addLineCheck( command, line );
      
      // Write command to the file
      gcode_file.println( command );
      
      // Increment z and e after the last move
      z += z_increment;
      e += e_rate;
      
      // Increment the GCode line number
      line++;
    }
  }
  
  // Home the printer at the end.
  gcode_file.println("G28");
  
  // Ensure the data is written to disk
  gcode_file.flush();
  gcode_file.close();
  
  // Finish.
  exit();
}

// This routine is lifted from:
// http://reprap.org/wiki/G-code 
String addLineCheck( String in_cmd, int line ) {
  String out_cmd;
  
  int checksum;
  int i;
  
  out_cmd = "N" + line + " " + in_cmd + "*";

  checksum = 0;
  for ( i = 0; out_cmd.charAt(i) != '*' && i < out_cmd.length(); i++ ) {
    checksum = checksum ^ out_cmd.charAt(i);
  }
  checksum &= 0xff;

  // Add the checksum to the command
  out_cmd = out_cmd + checksum;
  
  return out_cmd;
}


// These commands were extracted from a standard
// GCode file from Slic3r set up for the Rostock
// Find similar for your machine. 
void addStartUpRoutine() {
  // Set units to mm
  gcode_file.println("G21");
  
  // Heat the build platform    
  gcode_file.println("M190 S75");
 
  // Heat the extruder
  gcode_file.println("M104 S185");

  // Home printer
  gcode_file.println("G28");

  // Send to ready position
  gcode_file.println("G1 Z300 F3000");

  // Wait for extrduer to come up
  gcode_file.println("M109 S185");

  // Use absolute coordinates
  gcode_file.println("G90");

  // Set extrusion to zero
  gcode_file.println("G92 E0");
 
  // Use absolute distance for extrusion
  gcode_file.println("M82");
}


Calibration Steps

Once you are able to print a consistent, smooth-walled cylinder with the above code you can aim to achieve a second and extreme mode of operation - under-extrusion.  I have noticed this effect on various 3D printing forums, and it is dismissed as a failure, called 'stringing'.  It happens either because slicing algorithms are not configured properly and the printer massively under-extrudes, or because the printer slightly over-extrudes when it shouldn't be printing at all (hence, it creates a string in free air).  However, by taking control of the 3D printer directly, we can recreate this phenomenon reliably and to good effect.

Ultimately you need to determine the corresponding e_rate and f_rate pairs for this mode.  The performance you are aiming for is shown below.


In the above animation, the printer nozzle can be see to deposit only a blob of hot plastic filament onto the object, which is then pulled by the movement of the nozzle. This both stretches out the adhered filament, creating an extremely fine linkage, and also removes (pulls) material from the inner chamber of the nozzle.  The nozzle chamber is only slowly refilled because of the low e_rate set in code.  It is this absence of material, and the slow movement of the hot nozzle, that manipulates the plastic into an interesting state.  This is possible because FDM printers heat a plastic into a transitional and dynamic state, and the deposition process is a very direct and manual.


To get this effect, it is easiest if you fix the f_rate low (e.g. 300) and then slowly decrease the e_rate until the nozzle only periodically extrudes a bead of plastic.  I have found that a cooling fan blowing across the build area improves the performance.  



In the above image, a standard rigid PLA plastic has been printed entirely with the fine, gauze like texture.  The fine linkages are far smaller in diameter than the aperture of the nozzle, and confer surprising flexibility to the printed object.  When printed as a sheet the material has a more fabric like quality, able to conform to surfaces and hold ripple.  It is even possible to control the angle and aggression of the striation by modifying the e_rate and f_rate relative to the move_step variable.  The deposition and accumulation of the bead of plastic is interdependent on the movement progression about the object perimeter.

Modulating Extrusion

Once you are able to print a cylinder with consistent and smooth walls, and you have also discovered the e_rate and f_rate values required for under-extrusion, it is possible to then modulate (vary) between the two to creative effect, shown in the animation below.


In the above example, the e_rate and f_rate of the printer is being changed based on the angular position about the cylinder and with reference to a grey-scale image.   Therefore, the angular position (rotation around origin) is used as the lookup for the x-coordinate within the image, and the Z height of the print progress is used as the lookup for the y-coordinate within the image.  The pixel colour value at this image location is used determine both e_rate and f_rate, between the under-extrusion and consistent extrusion values.  A black pixel creates a solid extrusion, and a white pixel creates and under-extrusion.  Values between are linearly interpolated.  Using an image as a reference in this way is similar to the process of UV mapping.  

This method can be used to create 3D printable textures that are similar to textile lace.  Two examples are shown below.  




Geometry Modulations:



In the above example the general form of the cylinder has changed.  Quite simply, the radius has been increased as the print progress has increased in z height.  To achieve a pleasing curvature I interpolated z through a gaussian function to produce the resultant radius.  

Similarly textures can be created by perturbing the geometry of the object.  In the image above, the nozzle not only followed the perimeter of the object (draw a circle) but also oscillated back-and-forth away from the origin.  This can be thought of as similar to a zig-zag stitch on a sewing machine.  This is shown in macro in the inset image above.  The e-rate on this print was set higher than the value for a consistent smooth surface, resulting in over-extrusion and a bobbled surface similar too woollens.


By oscillating the nozzle in a similar way, the plucked texture shown in the second image of this blog post was created.  To create the plucked texture, the e-rate was set lower and the f_rate set higher.  Determined values are not usefully described, since the textures are expressed through the discoverable qualities of your own machine.

Similar to the UV Mapping approach to modulate extrusion into lace, UV mapping can be used to create relief and emboss into the surface of 3D prints by perturbing the perimeter coordinates.  In this case, pixel values are used to determine the negative or positive offset to the radius at a given angular and z height position:




The above three objects were produced with Adrian Geisow and the interface software he developed in tandem.  

Radical Modulations

Lastly, if we ignore all the 'rules' and 'sensibilities' of 3D printing we can achieve textured effects that become highly stochastic.  The animation below shows the 3D printer extruding the hot filament into free air.  The filament is pulled away from the object centre and cools.  It is then snapped off by a quick movement of the nozzle.  At this point, the free-standing filament cools in a non-deterministic way and curls.  


This process can be used to create 'hairy' objects. These are perhaps the most interesting textures because it would be very difficult to CAD such a surface finish.  Whilst I can definitely program a CAD model to iteratively produce random hairs, it is unlikely that a 3D printer could reproduce them as they are modelled.  At best, it could approximate the hair, but natural dynamics would have final say.

Concluding Thoughts


I think the idea of un-CAD-able textures challenges the current conception of  3D printing and 3D printed art.  My contention is that the majority of 3D printed art is aesthetically based on complex or fine geometries that can be defined within a CAD environment.  The entire 3D print process is dominated by the top-down approach of CAD, followed by black-box algorithms and an automated 3D printer.  

I think this this top-down approach can be unsatisfying and lacking creatively because it does not make a sympathetic use of the printed material.  In conventional 3D printing the material is not exploited for its dynamic qualities.  Instead, materials are placed in discrete, controlled and precise ways.  This could be contrasted against the ceramicist on the potters-wheel, who has an intimate understanding of the material and intimate engagement with the process of fabrication.  They are able to push materials to their limits - and thereby exceed our expectations and excite.

However I am not suggesting current 3D printed art has lesser value.  I fully respect the high level of skill of a successful CAD drafts-person, especially their ability to anticipate the realisation of their work in 3D print.  I only wish to suggest that the 3D printer is ready to be re-assessed or appropriated as an artists implement, and fundamentally explored from the bottom-up as an expressive medium in it's own right.  The current level of automation hides a rich set of expressive qualities.  Moreover, the encapsulated sensibilities and automation almost certainly hide utilitarian and function properties too.