3D Printer in Minecraft

3D Printing in Minecraft

I recently purchased a 3D printer for my classroom. Watching the machine precisely extrude the molten plastic into intricate shapes is a hypnotic and mesmerizing experience. The process of printing a 3D file involves taking a 3D model, typically in .obj or .stl format and "slicing" it into .gcode, the ancient language of mills and heavy machinery.

Image result for 3D printer There are numerous programs that are able to convert an .stl or .obj file into gcode. I used slic3r, which is intended as a go-between for 3D printing. Since I am not 3D printing, I had to adjust some of the parameters to suit what I was doing (getting Minecraft to behave as a 3D printer). This exercise helped me to better understand how 3D printers work and allowed me to import some intricate designs into Minecraft that would have otherwise been very difficult to build by hand.

Using slic3r, I was able to generate gcode (machine instructions) for a 3D printer. I tweaked some of the settings to make the printer behave more like Minecraft. For instance, I changed all the measurements to 1mm as that was what I thought would represent 1 block unit.

Slicr.png

I used m_bergman's Reims Cathedral Kitset from https://www.thingiverse.com/thing:35798 for the 3D model.

One tip is to use 90° fill angle to maximize speed in Minecraft (which can only fill at 90° angles). See below:

slicr2.png

This is an excerpt of gcode:

; generated by Slic3r 1.2.9 on 2018-04-14 at 13:30:01

; external perimeters extrusion width = 1.00mm
; perimeters extrusion width = 1.05mm
; infill extrusion width = 1.05mm
; solid infill extrusion width = 1.05mm
; top infill extrusion width = 1.05mm

M107
M104 S200 ; set temperature
G28 ; home all axes
G1 Z5 F5000 ; lift nozzle

M109 S200 ; wait for temperature to be reached
G21 ; set units to millimeters
G90 ; use absolute coordinates
M82 ; use absolute distances for extrusion
G92 E0
G1 Z0.350 F60.000
G1 X50.353 Y22.513 F60.000
G1 X57.003 Y12.013 E3.46104 F1800.000
G1 X58.547 Y10.317 E4.09970
G1 X60.577 Y9.249 E4.73836
G1 X62.600 Y8.932 E5.30861
G1 X137.600 Y8.932 E26.19400
G1 X139.859 Y9.329 E26.83266
G1 X141.847 Y10.473 E27.47132 F1800.000
G1 X143.197 Y12.013 E28.04157
G1 X149.847 Y22.513 E31.50262
G1 X150.846 Y25.439 E32.36384
G1 X162.346 Y148.214 E66.70287
G1 X162.375 Y148.832 E66.87511
G1 X162.375 Y210.432 E84.02898
G1 X162.246 Y211.732 E84.39255
G1 X157.746 Y234.232 E90.78225
G1 X157.183 Y235.880 E91.26732
G1 X149.026 Y252.299 E96.37272
G1 X148.637 Y252.978 E96.59069
G1 X143.685 Y260.547 E99.10944
G1 X143.333 Y261.036 E99.27708
G1 X140.219 Y264.957 E100.67168
G1 X134.956 Y271.090 E102.92209
G1 X134.316 Y271.739 E103.17594
G1 X129.275 Y276.196 E105.04954
G1 X128.455 Y276.814 E105.33552
G1 X108.874 Y289.330 E111.80708
G1 X107.273 Y290.075 E112.29890

It continues for 201468 lines of code total. The excerpt here will draw the Notre-Dame Cathedral of Reims. My approach for this project is similar to the approach used for drawing a photo into Minecraft:

Step 1: A Python script reads the gcode file, parses the relevant information, and exports this to an array of integers.

Step 2: The array of integers is pasted into a Microsoft MakeCode script and the MakeCode script translates the array into a series of build actions.

In order to make sense of this file it is helpful to know a little about how gcode works. After setting up my slic3r to treat the extruder width as 1mm and the workplace as 100mm (to keep the number of digits consistent), the resulting gcode is made up of a few relevant pieces of information:

1) the X and Y coordinates of the G1 moves. These are straight line moves from the last stated point. 2) the Z coordinate (height) 3) moves made by the extruder that do not extrude material ("pen up" moves)

The following link was very helpful to me in understanding which codes do what: https://nraynaud.github.io/webgcode/

I ended up adding coordinates to the array that contained an E, or extrude instruction and coordinates with an F were used as an indicator to "lift the pen." My "lift the pen" code was 999. This was used in the MakeCode script to instruct Minecraft to "lift the pen."

In [3]:
import math

with open("mc/reims.gcode", "r") as myfile:
    data = myfile.readlines()
    z = 0
    pix_array = []
    for line in data:
        if line and line[0:2] == 'G1':
            if line[3] == 'X':
                x = int(float(line[4:9]))
                if line[11] == 'Y':
                    y = int(float(line[12:17]))
                if line[12] == 'Y':
                    y = int(float(line[13:18]))
                if line[13] == 'Y':
                    y = int(float(line[14:19]))
                if "E" in line[18:22]:
                    pix_array.append(x)
                    pix_array.append(y)
                    pix_array.append(z)
                else:
                    pix_array.append(999)
            if line[3] == 'Z':
                z += 1
    print(pix_array[:200])
[999, 57, 12, 2, 58, 10, 2, 60, 9, 2, 62, 8, 2, 137, 8, 2, 139, 9, 2, 141, 10, 2, 143, 12, 2, 149, 22, 2, 150, 25, 2, 162, 148, 2, 162, 148, 2, 162, 210, 2, 162, 211, 2, 157, 234, 2, 157, 235, 2, 149, 252, 2, 148, 252, 2, 143, 260, 2, 143, 261, 2, 140, 264, 2, 134, 271, 2, 134, 271, 2, 129, 276, 2, 128, 276, 2, 108, 289, 2, 107, 290, 2, 105, 290, 2, 103, 290, 2, 96, 290, 2, 94, 290, 2, 92, 290, 2, 91, 289, 2, 71, 276, 2, 70, 276, 2, 65, 271, 2, 65, 271, 2, 59, 264, 2, 56, 261, 2, 56, 260, 2, 51, 252, 2, 51, 252, 2, 43, 235, 2, 42, 234, 2, 37, 211, 2, 37, 210, 2, 37, 170, 2, 37, 146, 2, 37, 146, 2, 49, 25, 2, 50, 22, 2, 999, 71, 210, 2, 73, 210, 2, 73, 232, 2, 79, 246, 2, 91, 256, 2, 109, 256, 2, 121, 246, 2, 126, 232, 2, 126, 210, 2, 128, 210, 2, 128, 209, 2, 141, 209, 2, 141, 231, 2, 138, 232, 2, 141, 240, 2]

First, initialize variable z to 0. The program reads the file line by line looking for lines that begin with G1. When it finds a line that fits this description, it looks for a number next to an X in the 4-9 position and a number next to a Y in the 12 or 13-17 position. In an array it stores these values as a string of integers in the order X, Y, Z, but only if it encounters an E in index 18-20. E stands for extrude. If it does not find an E in this position, the program assumes the following coordinate is for positioning and not for extruding material so it adds '999' to the array. This will be interpreted in our other script as the "pen up" function found in other programs (like Scratch). Below is the terminal output of the Python script. This array will be pasted into the MakeCode JavaScript editor.

The MakeCode portion iterates through the array and draws lines between the last point and the current point unless a 999 code is discovered. A ticker is used to determine whether the member of the array is an X, Y, or Z coordinate. Important to note is that in a 3D printer, 'up' is denoted as the Z axis but in Minecraft 'up' is denoted as the Y axis. When draw = 1, the program draws a line. When draw = 0, it sets the new x,y,z coordinates and returns draw to 1. This skips over "pen up" sections. For this reason, the third value of the Below is the entire Microsoft MakeCode JavaScript. I haven't included the array in the code below because it is very large:

let z1 = 0
let z2 = 0
let y1 = 0
let y2 = 0
let x1 = 0
let x2 = 0
let ticker = 0
let draw = 1
let list: number[] = []
player.onChat("run", function () {
    list = []
    for (let value of list) {
        if (value < 999) {
            if (ticker % 3 == 0) {
                x2 = x1
                x1 = value
            }
            if (ticker % 3 == 1) {
                y2 = y1
                y1 = value
            }
            if (ticker % 3 == 2) {
                z2 = z1
                z1 = value
                if (draw == 1) {
                    shapes.line(blocks.block(Block.PurpleGlazedTerracotta), positions.createWorld(x1, z1, y1), positions.createWorld(x2, z2, y2))
                } else {
                    x2 = x1
                    y2 = y1
                    z2 = z1
                    draw = 1
                }
            }
            ticker += 1
        } else {
            draw = 0
        }
        if (ticker % 120 == 0) {
            player.say(ticker.toString())
        }
    }

})

Below you can see the result:

cath1.png cath2.png cath3.png cath4.png cath5.png cath6.png cath7.png