Skip to content

Commit

Permalink
first real push
Browse files Browse the repository at this point in the history
  • Loading branch information
codys committed Jul 13, 2011
1 parent 316c12f commit 695866c
Show file tree
Hide file tree
Showing 42 changed files with 9,738 additions and 0 deletions.
22 changes: 22 additions & 0 deletions README
@@ -0,0 +1,22 @@
Minecraft.Print() is a python library for converting defined regions of Minecraft levels to a 3D Printable format.

Includes pymclevel.
Requires numpy (https://github.com/numpy/numpy).

In game:
Before running Minecraft.Print() you will need to define the space you wish to print by placing two markers, in-game. The marker configuration is a diamond block, followed by a gold block, followed by an iron block in vertical order (ascending or descending). The coordinates of the diamond block will be used as the marker.

Running Minecraft.Print():
Run "python run.py LEVEL_NAME OUTPUT_NAME" - replacing LEVEL_NAME and and OUTPUT_NAME with the level you wish to process and the desired output file name. The script will then run and save the STL file in the same directory.

Viewing resulting file:
There are many free programs for viewing STL files. One example: http://meshlab.sourceforge.net

Printing:
If you don't have a 3D Printer of your own, to print to, there are online services that will allow you to upload an STL file and order a print. Two random ones: cloudfab.com and shapeways.com

Show off your creation:
We figure you already know how to do this.

Sharing:
So here's the deal. We want to see what awesome stuff you guys are going to print. So drop us a line at hi@minecraftprint.com and let us know how it went. Also, if you have something really awesome, consider putting the model up on thingiverse.com for others to print.
192 changes: 192 additions & 0 deletions minecraft_print.py
@@ -0,0 +1,192 @@
import pymclevel.mclevel as mclevel
import sys
import os
from pymclevel.box import BoundingBox
import numpy
from numpy import zeros, bincount
import logging
import itertools
import traceback
import shlex
import operator
import codecs

from math import floor
import readline

class UsageError(RuntimeError): pass
class BlockMatchError(RuntimeError): pass
class PlayerNotFound(RuntimeError): pass

class MinecraftPrint:

def __init__(self, level, output):
self.level_name = level
self.output_name = output + '.stl'

#The list of goodness and markers
#Format: [[chunk_x, chunk_z, block_x, block_z, block_y]]
self.markers = []

self.chunk_positions = []
self.num_chunks = 0
self.chunk_counter = 0

self.diamond_check = []
self.object_array = []

#Data value for each block type
self.diamond = 57
self.gold = 41
self.iron = 42

def generate(self):
self.find_marked_area()
self.copy_marked_area()
self.generate_stl()

def find_marked_area(self):
self.world = mclevel.loadWorld(self.level_name)

#Load chunks and determine minimize indexes
self.chunk_positions = list(self.world.allChunks)
self.num_chunks = len(self.chunk_positions)

#Just a little user feedback on the progress
print "Processing level: " + self.level_name
print "Scanning level for markers..."
self.chunk_counter = 1

self.find_markers()

print '100%'

def find_markers(self):
#Iterate through chunks, looking for block combination
for x_pos, z_pos in self.chunk_positions:
#User feedback
if self.chunk_counter % 10 == 0:
print str(self.chunk_counter/float(self.num_chunks)*100) + "%"
self.chunk_counter += 1

chunk = self.world.getChunk(x_pos, z_pos)
#Does this chunk have a diamond block?

diamond_check = numpy.where(chunk.Blocks == self.diamond)
if len(diamond_check[0]) > 0:
for dx, dz, dy in zip(diamond_check[0], diamond_check[1], diamond_check[2]):

#We found diamond, but is it a marker (diamond, gold, and iron in asc or desc vertical order)
#If so, define the diamond block coordinates as the marker and remove the marker from the map
if dy > 1 and chunk.Blocks[dx, dz, dy - 1] == self.gold and chunk.Blocks[dx, dz, dy - 2] == self.iron:
self.markers.append([x_pos, z_pos, dx, dz, dy])
chunk.Blocks[dx, dz, dy] = 0
chunk.Blocks[dx, dz, dy - 1] = 0
chunk.Blocks[dx, dz, dy - 2] = 0
elif dy < 126 and chunk.Blocks[dx, dz, dy + 1] == self.gold and chunk.Blocks[dx, dz, dy + 2] == self.iron:
self.markers.append([x_pos, z_pos, dx, dz, dy])
chunk.Blocks[dx, dz, dy] = 0
chunk.Blocks[dx, dz, dy + 1] = 0
chunk.Blocks[dx, dz, dy + 2] = 0

def copy_marked_area(self):
#Now we have the markers. Time to get serious
if len(self.markers) == 2:
print "Congrats, looks like we have two markers"
print "..."
print "Capturing marked area... this may take a minute..."

#Calculate x_min and x_max
if self.markers[0][0] < self.markers[1][0]:
x_min = [self.markers[0][0], self.markers[0][2]]
x_max = [self.markers[1][0], self.markers[1][2]]
elif self.markers[0][0] > self.markers[1][0]:
x_min = [self.markers[1][0], self.markers[1][2]]
x_max = [self.markers[0][0], self.markers[0][2]]
else:
x_min = [self.markers[0][0], min(self.markers[0][2], self.markers[1][2])]
x_max = [self.markers[0][0], max(self.markers[0][2], self.markers[1][2])]

#Calculate z_min and z_max
if self.markers[0][1] < self.markers[1][1]:
z_min = [self.markers[0][1], self.markers[0][3]]
z_max = [self.markers[1][1], self.markers[1][3]]
elif self.markers[0][1] > self.markers[1][1]:
z_min = [self.markers[1][1], self.markers[1][3]]
z_max = [self.markers[0][1], self.markers[0][3]]
else:
z_min = [self.markers[0][1], min(self.markers[0][3], self.markers[1][3])]
z_max = [self.markers[0][1], max(self.markers[0][3], self.markers[1][3])]

#Calculate y_min and y_max
y_min = min(self.markers[0][4], self.markers[1][4])
y_max = max(self.markers[0][4], self.markers[1][4])

#Construct an array to fit the object
self.object_array = [[[0 for z in xrange((z_max[0] - z_min[0] + 1) * 16)] for y in xrange(y_max - y_min + 1)] for x in xrange((x_max[0] - x_min[0] + 1) * 16)]

#Copy marked blocks to object_array
for x_pos in range(x_min[0], x_max[0] + 1):
for z_pos in range(z_min[0], z_max[0] + 1):
chunk = self.world.getChunk(x_pos, z_pos)
for x in range(16):
for z in range(16):
for y in range(y_max - y_min + 1):
if (x_pos == x_min[0] and x < x_min[1]) or (x_pos == x_max[0] and x > x_max[1]):
block_type = 0
elif (z_pos == z_min[0] and z < z_min[1]) or (z_pos == z_max[0] and z > z_max[1]):
block_type = 0
else:
block_type = chunk.Blocks[x, z, y_min + y]
#print str((16 * (x_pos + offsetx)) + x) + ", " + str(y) + ", " + str((16 * (z_pos + offsetz)) + z)
self.object_array[(16 * (x_pos -x_min[0])) + x][y][(16 * (z_pos - z_min[0])) + z] = block_type

else:
print "Freak out! There are somehow more or less than 2 markers!"

def generate_stl(self):
"""Generate STL file"""
filename = self.output_name

width = len(self.object_array)
height = len(self.object_array[0])
depth = len(self.object_array[0][0])

str_o = "solid Minecraft\n";
str_e = " endloop\n endfacet\n"
str_s = " facet normal %d %d %d\n outer loop\n"
str_v = " vertex %d %d %d\n"

print "start"

f=open(filename, 'w')
f.write(str_o)
for x in range(width):
print str(x/float(width)*100) + "%"
for y in range(height):
for z in range(depth):
if self.object_array[x][y][z] > 0:
if x==0 or self.object_array[x-1][y][z]<=0:
f.write("".join([str_s%(-1,0,0),str_v%(x,z+1,y), str_v%(x,z,y+1),str_v%(x,z+1,y+1),str_e]))
f.write("".join([str_s%(-1,0,0),str_v%(x,z+1,y), str_v%(x,z,y),str_v%(x,z,y+1),str_e]))
if x==width-1 or self.object_array[x+1][y][z]<=0:
f.write("".join([str_s%(1,0,0),str_v%(x+1,z+1,y), str_v%(x+1,z+1,y+1),str_v%(x+1,z,y+1),str_e]))
f.write("".join([str_s%(1,0,0),str_v%(x+1,z+1,y), str_v%(x+1,z,y+1),str_v%(x+1,z,y),str_e]))
if (z==0) or self.object_array[x][y][z-1]<=0:
f.write("".join([str_s%(0,0,-1),str_v%(x,z,y), str_v%(x+1,z,y+1),str_v%(x,z,y+1),str_e]))
f.write("".join([str_s%(0,0,-1),str_v%(x,z,y), str_v%(x+1,z,y),str_v%(x+1,z,y+1),str_e]))
if (z==depth-1) or self.object_array[x][y][z+1]<=0:
f.write("".join([str_s%(0,0,1),str_v%(x,z+1,y), str_v%(x,z+1,y+1),str_v%(x+1,z+1,y+1),str_e]))
f.write("".join([str_s%(0,0,1),str_v%(x,z+1,y), str_v%(x+1,z+1,y+1),str_v%(x+1,z+1,y),str_e]))
if (y==0) or self.object_array[x][y-1][z]<=0:
f.write("".join([str_s%(0,-1,0),str_v%(x+1,z,y), str_v%(x,z+1,y),str_v%(x+1,z+1,y),str_e]))
f.write("".join([str_s%(0,-1,0),str_v%(x+1,z,y), str_v%(x,z,y),str_v%(x,z+1,y),str_e]))
if (y==height-1) or self.object_array[x][y+1][z]<=0:
f.write("".join([str_s%(0,1,0),str_v%(x+1,z,y+1), str_v%(x+1,z+1,y+1),str_v%(x,z+1,y+1),str_e]))
f.write("".join([str_s%(0,1,0),str_v%(x+1,z,y+1), str_v%(x,z+1,y+1),str_v%(x,z,y+1),str_e]))

f.write("endsolid Minecraft\n")
print "100%"
f.close()

print "Done!"
15 changes: 15 additions & 0 deletions pymclevel/.gitignore
@@ -0,0 +1,15 @@
# git-ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
*.pyc
*.pyo
*~

.project
.pydevproject

#profiling/outputs
*.log
13 changes: 13 additions & 0 deletions pymclevel/LICENSE.txt
@@ -0,0 +1,13 @@
Copyright (c) 2010 David Rio Vierra

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
127 changes: 127 additions & 0 deletions pymclevel/README.txt
@@ -0,0 +1,127 @@
Python library for reading Minecraft levels.

Can read Alpha levels, Indev levels, and Creative levels (with help).

Includes a command-line client (mce.py)

Requires numpy.

Read mclevel.py to get started.

See LICENSE.txt for licensing terms.



mce.py is a command-line editor for SMP maps. It can be used interactively from a terminal, accept editing commands on standard input, or run a single editing command from the shell.

Sample usage:

$ python mce.py

Usage:

Block commands:
clone <sourcePoint> <sourceSize> <destPoint>
fill <blockType> [ <point> <size> ]
replace <blockType> [with] <newBlockType> [ <point> <size> ]

export <filename> <sourcePoint> <sourceSize>
import <filename> <destPoint>

analyze

Player commands:
player [ <player> [ <point> ] ]
spawn [ <point> ]

Entity commands:
removeEntities [ <EntityID> ]

Chunk commands:
createChunks <point> <size>
deleteChunks <point> <size>
prune <point> <size>
relight [ <point> <size> ]

World commands:
degrief

Editor commands:
save
reload
load <filename> | <world number>
quit

Informational:
blocks [ <block name> | <block ID> ]
help [ <command> ]

Points and sizes are space-separated triplets of numbers ordered X Y Z.
X is position north-south, increasing southward.
Y is position up-down, increasing upward.
Z is position east-west, increasing westward.

A player's name can be used as a point - it will use the
position of the player's head. Use the keyword 'delta' after
the name to specify a point near the player.

Example:
codewarrior delta 0 5 0

This refers to a point 5 blocks above codewarrior's head.


Please enter world number or path to world folder: 4
INFO:Identifying C:\Users\Rio\AppData\Roaming\.minecraft\saves\World4\level.dat
INFO:Detected Infdev level.dat
INFO:Saved 0 chunks
INFO:Scanning for chunks...
INFO:Found 6288 chunks.
World4> fill 20 Player delta -10 0 -10 20 20 20

Filling with Glass
Filled 8000 blocks.
World4> player Player

Player Player: [87.658381289724858, 54.620000004768372, 358.64257283335115]
World4> player Player Player delta 0 25 0

Moved player Player to (87.658381289724858, 79.620000004768372, 358.642572833351
15)
World4> save

INFO:Asked to light 6 chunks
INFO:Batch 1/1
INFO:Lighting 20 chunks
INFO:Dispersing light...
INFO:BlockLight Pass 0: 20 chunks
INFO:BlockLight Pass 1: 2 chunks
INFO:BlockLight Pass 2: 0 chunks
INFO:BlockLight Pass 3: 0 chunks
INFO:BlockLight Pass 4: 0 chunks
INFO:BlockLight Pass 5: 0 chunks
INFO:BlockLight Pass 6: 0 chunks
INFO:BlockLight Pass 7: 0 chunks
INFO:BlockLight Pass 8: 0 chunks
INFO:BlockLight Pass 9: 0 chunks
INFO:BlockLight Pass 10: 0 chunks
INFO:BlockLight Pass 11: 0 chunks
INFO:BlockLight Pass 12: 0 chunks
INFO:BlockLight Pass 13: 0 chunks
INFO:SkyLight Pass 0: 20 chunks
INFO:SkyLight Pass 1: 22 chunks
INFO:SkyLight Pass 2: 17 chunks
INFO:SkyLight Pass 3: 9 chunks
INFO:SkyLight Pass 4: 7 chunks
INFO:SkyLight Pass 5: 2 chunks
INFO:SkyLight Pass 6: 0 chunks
INFO:SkyLight Pass 7: 0 chunks
INFO:SkyLight Pass 8: 0 chunks
INFO:SkyLight Pass 9: 0 chunks
INFO:SkyLight Pass 10: 0 chunks
INFO:SkyLight Pass 11: 0 chunks
INFO:SkyLight Pass 12: 0 chunks
INFO:SkyLight Pass 13: 0 chunks
INFO:Completed in 0:00:02.024000, 0:00:00.337333 per chunk
INFO:Saved 20 chunks
World4>
2 changes: 2 additions & 0 deletions pymclevel/__init__.py
@@ -0,0 +1,2 @@
from mclevel import *
import items

0 comments on commit 695866c

Please sign in to comment.