Multi-Material 3D Printing With OpenSCAD, Cura and the Geeetech A20T

2023-07-23 3dprint / python
How to do multi material 3D printing with OpenSCAD and Cura and the Geeetech A20T

I’ve been messing around with my cheap ALDI 3D printer for a few years now and it’s time to upgrade!

I’ve put an order in for a Geeetech A20T which has about 12× the build volume of the old printer, and has three filaments feeding into a single extuder, letting it print designs in multiple materials and/or colours.

I’ve written separate articles on assembling and configuring the Geeetech A20T and First prints with the A20T, this article is all about the technical details of getting a multi-material workflow set up with OpenSCAD and Cura.

OpenSCAD

I mostly use OpenSCAD to design stuff and OpenSCAD support for multi materials is not good (#1041, #1608.)

It’d be great to fix that, but in the meantime I need a quick solution so I can play with my new toy.

Erik Nygren has made a good start with a method for selecting one part at a time using a multicolor() module which checks a variable current_color for each part.

I wanted to get something similar working with Ultimaker Cura and to make it work without having to save a whole lot of separate STL files.

What I ended up doing was just defining several parts arbitrarily called red and blue and green, and at the bottom of the file include some code to select between them:

material = 0;
if (material == 0) {
   red();
   green();
   blue();
} else if (material == 1) { 
   red();
} else if (material == 2) {
   green();
} else if (material == 3) {
   blue();
}

Then you can either alter the code to set material manually or call OpenSCAD with a parameter to generate a file directly:

opencad -D material=1 -o project.stl project.scad

It might seem counterintuitive, but because of the way OpenSCAD programs declare variables, the -D material=1 option can override the value set by the code material = 0;. O tempora, o mores!

I’d like to come up with a nicer mechanism for doing this in OpenSCAD. Ideally there would be a mechanism to read out the names of the top level modules, and a mechanism to select one module to render.

Intersecting Volumes

Cura 5.4 doesn’t handle intersecting volumes nicely at all, exporting weird alternating layers where the volumes intersect.

intersection1 layers behave weirdly when volumes intersect

So instead make sure you subtract layers from each other before emitting them. We alter our previous code like so:

material = 0;
if (material == 0) {
    red();
    green();
    blue();
} else if (material == 1) {
    difference() {
        red();
        green();
        blue();
    }
} else if (material == 2) {
    difference() {
        green();
        blue();
    }
} else if (material == 3) {
    blue();
} 

intersection2 layers behave better when the volumes don’t intersect.

Note that there are still extraneous interior walls though, see below for details.

UPDATE: Cura 5.4.0 seems to be able to work this out for itself if setting “Remove Mesh Intersection” is not set! Maybe that’s a bug. But perhaps this step is unneccesary.

Exporting as AMF

So far, I’ve used STL to get my models from OpenSCAD to Cura, but it’s pretty limited and a bit of a pain to work with. There are more modern formats available. I had a look at 3MF but it a mess of ZIPped XML and so instead I’ve been working with AMF.

AMF is a relatively simple XML format. It’s an “Open Standard”, but sadly it’s the kind of “Open Standard” which costs sixty-three bucks to download so instead we’re gonna go on guesswork and other people’s code. Just looking at the AMF file, you can tell that it contains a pretty simple hierarchy of <amf> -> <object> -> <mesh> -> <vertices> and <volume>s so it’s not hard to guess what everything does.

Then we can just iterate through the materials from the command line using the -D var=val command line option and get OpenSCAD to export each material to a different .amf file:

openscad -D material=1 -o temp1.amf project.scad
openscad -D material=2 -o temp2.amf project.scad
openscad -D material=3 -o temp3.amf project.scad

Since we can manipulate AMF files pretty easily we can then combine those files into one file which can be importing into Cura.

combine_amf.py:

#!/usr/bin/env python
import sys
import xml.etree.ElementTree as ET

# XXX should handle units other than millimeters
# XXX probably should keep per file metadata
# XXX possibly should define materials per input file
# XXX possibly could unify vertices and produce multiple volumes in a single object

xout = ET.Element('amf', attrib={'unit': 'millimeter'})

obj_id = 0

for fn in sys.argv[1:]:
    et = ET.parse(fn)
    root = et.getroot()
    assert root.tag == 'amf'
    assert root.get('unit') == 'millimeter'

    for obj in root.findall('object'):
        obj.set('id', str(obj_id))
        xout.append(obj)
        obj_id += 1

ET.ElementTree(xout).write(sys.stdout, encoding='unicode', xml_declaration=True)

Rather than doing these steps manually we can use a bash script:

mmexport.sh:

#!/bin/bash
set -eu
SOURCE=$1
TARGET=${2:-${SOURCE%.*}.amf}
TEMPDIR=`mktemp -d`
for MATERIAL in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
    echo -e "---\nEXPORTING MATERIAL $MATERIAL"
    openscad -D material=$MATERIAL -o $TEMPDIR/temp$MATERIAL.amf $SOURCE || echo "FAILED"
done
echo -e "---\nCOMBINING INTO $TARGET"
./combine_amf.py $TEMPDIR/temp*.amf > $TARGET
echo -e "---\nDONE\n"
rm -r $TEMPDIR

Some examples

I’ve uploaded some multi-material OpenSCAD examples which use these methods, although I’m still messing around trying to work out “the best way” …

Importing into Cura

Ultimaker Cura can import AMF files, and when we import a multi-object AMF file as generated by the script above it appears as a single “grouped” object. All the materials are locked together to prevent misalignment.

We can still select individual pieces within the group using Control-Click, and then assign them to different extruders manually, one at a time.

Beware, if you decide to ungroup the pieces they may become misaligned as Cura will “drop” each piece to touch the print bed. To prevent this, control-click each individual piece and make sure “Drop Down Model” is turned off on every one before ungrouping the pieces.

It sure is tedious selecting each of those pieces. It would be nice if there was a way to automate this. Maybe there is, in some other format like 3MF?

Printing on the Geeetech A20T

I’ve written a separate article on assembling and configuring the Geeetech A20T.

The A20T has three filaments feeding into a single extruder nozzle. Cura 5.4.0 has a built-in printer definition for the A20T which supports the three physical extruders.

The firmware can also do mixing and fading between the physical extruders, by configuring “virtual extruders”, each of which can have its own ratio of filaments. By default Cura only supports the three physical filament feeders, but an extra configuration file can be added to support up to eight (or maybe 16) in total.

You can’t edit the built-in printers but you can add new ones. I’m not sure why this has to be so damn tricky or whether there’s a better way to do all this, but I’ve been able to modify the existing definitions a bit to allow more extruders.

You can unpack these files into your resources directory (eg: $HOME/.local/share/cura/5.4/ on Linux, something like %APPDATA%\cura\5.4\ on Windows I think, which might be under C:\Users\YOUR USERNAME\AppData\Roaming\)

This one should give you a new “A20Tx8” printer with eight extruders:

A20T with eight extruders (A20Tx8) printer definition

… or an “A20Tx10” printer with ten extruders:

A20T with ten extruders (A20Tx10) printer definition

The Cura interface becomes pretty cumbersome with this many extruders though. I tried setting up different filament colours as well … so lots of generic PLA filaments with different colours in their names and different colours in the preview … but it didn’t work all that well as you need to assign one filament definition per extruder and you need to make them different colours, and unless you’ve got some connection between the colour of the filament in Cura and the colour of the mixed filament on the actual printer it’s just going to get confusing.

Setting up Mixing (in G-Code)

Virtual extruders are set up in the printer settings G-code using the M163 and M164 commands.

Note that G-code standardization is quite broken across different brands of printer and different firmwares. There’s multiple interpretations of even basic stuff like tool changes and lots of optional features which your printer may or may not support. If you’re using something other than a Geeetech printer the required G-code may be subtly different.

Marlin supports up to 16 “virtual tools” by default (see MIXING_VIRTUAL_TOOLS), numbered from 0 .. 15.
Each of those has a mapping to a different mix of the real tools, numbered (in my case) 0 through 2.

For example these commands select a 50/30/20 mix of the three filaments in the real extruders 0, 1 and 2, and assign that mix to a “virtual tool” 3:

M163 S0 P0.5
M163 S1 P0.3
M163 S2 P0.2
M164 S3

You can reassign virtual tools 0, 1 and 2 as well!

Once these virtual tools are set up, Cura will automatically use them for the different extruders using the tool change commands T0 .. T7. So by fiddling with the “Start G-Code” in the printer settings, you can set up several extra colour mixes so for example this sequence would set up tools 3 through 7 as mixes of tools 1 and 2 (the second and third filaments), so you could for example load white, red and blue and print in white and several shades of purple.

M163 S0 P0
M163 S1 P0.84
M163 S2 P0.16
M164 S3

M163 S0 P0
M163 S1 P0.67
M163 S2 P0.33
M164 S4

M163 S0 P0
M163 S1 P0.5
M163 S2 P0.5
M164 S5

M163 S0 P0
M163 S1 P0.33
M163 S2 P0.67
M164 S6

M163 S0 P0
M163 S1 P0.16
M163 S2 P0.84
M164 S7

Using M166 should let you assign a Z-gradient to a tool as well!

It’d be nice to define a Cura plugin to make this a bit more accessible. There’s a Colormix Plugin for two-into-one mixes, which might be a good start to work from. But the Cura post-processing plugin interface looks pretty seriously limiting. Oh, well, let’s wait and see.

Outstanding Issues with Multi-Material Printing in Cura

A Terrible Workaround For Interior Walls

Cura can already print infill in a separate extruder but it doesn’t know how to eliminate interior walls, which will be printed in the surface colour.

So this is a workaround using OpenSCAD to separate the hidden interior from the surface layers so they can be specified separately:

  1. Create a volume which is just a smidge smaller than the sum of your actual volume. There are smarter ways, such as having a working 3D offset command, but this probably works well enough:

    module everything() {
        red();
        green();
        blue();
    }
       
    module interior() {
        intersection() {
            translate([1,0,0]) everything();
            translate([-1,0,0]) everything();
            translate([0,1,0]) everything();
            translate([0,-1,0]) everything();
            translate([0,0,1]) everything();
            translate([0,0,-1]) everything();
        }
    }
    

    It’d be nice to have a 3D equivalent of offset or possibly a minkowski difference, but this is an approximation. If there’s holes in the corners, add more translations like translate([1,1,0]) everything(); but it gets very slow.

  2. Subtract this interior volume from every part:

    if (material == 1) {
        difference() {
            red();
            green();
            blue();
            interior();
        }
    }
    
  3. Also render the interior volume as its own material:

    } else if (material == 4) {
        interior();
    }
    
  4. Combine AMFs and set the interior volume as your infill material, with only a single wall, top and bottom layer. You can’t select the interior volume by clicking on it, but you can set the material for the whole grouped component to the infill material, and then change each of the visible components individually to other materials.

    The interior volume now takes up most of the space, with the other materials just forming a thin shell of 2-3 layers surrounding the outside.

    intersection3 layers with the internal volume removed

    or if your design works well this way, you could beef up the shell a little and leave the core out, just letting Cura add support where necessary.

This feature really belongs in Cura, not OpenSCAD, but doing it this way is expedient.

It’s quite possible that this is already an option somewhere in Cura’s extensive set of “Mesh Fixes”, “Special Modes” or “Experimental” settings. If you find it, let me know!

To Be Continued

Now it’s arrived I’ve written about assembling and configuring the A20T and I’ll keep working on the multi material stuff as I go.