ignorance setup
[pyxelmap.git] / pyxelmap.py
blob66fd74e0ec6e2ab564d57efc7826b7e8d7c8e858
1 #!/usr/bin/env python
2 import sys, time, math, argparse, os, os.path
3 import PIL
4 from overviewer_core import world
7 def main():
8 parser = argparse.ArgumentParser(description='Anvil 2d map generator.')
9 parser.add_argument("--grid", type=int, metavar="BLOCKS", dest="gridStep",
10 help="Grid marks step")
11 parser.add_argument("-f", "--filter", metavar="NAME", type=str, choices=chunkFilters.keys(), default="all",
12 help="Chunk filtering method")
13 parser.add_argument("-F", "--blockfilter", metavar="NAME", type=str, choices=blockFilters.keys(),
14 help="Block filtering method")
15 parser.add_argument("-g", "--arg", metavar="VALUE", type=int, action="append", default = [],
16 help="Chunk selection parameters")
17 parser.add_argument("-i", "--in", dest="worldPath", metavar="PATH", type=str,
18 help="Source world directory")
19 parser.add_argument("-o", "--out", dest="destPath", metavar="PATH", type=str, default=".",
20 help="Output directory")
21 parser.add_argument("-r", "--region", metavar="NAME", type=str, default="overworld",
22 help="Region name")
23 parser.add_argument("-v", "--verbose", action="store_true", default=False,
24 help="Print some progress info")
25 args = parser.parse_args()
27 try:
28 srcWorld = world.World(args.worldPath)
29 srcRegion = srcWorld.get_regionset(args.region)
30 except Exception, ex:
31 sys.exit("Error opening world")
33 if not os.access(args.destPath, os.W_OK):
34 sys.exit("Unable to write at %s" % args.destPath)
36 if args.verbose:
37 print "Preparing chunks"
38 # filter out chunks
40 if not args.blockfilter:
41 blockFilter = None
42 myChunks = [i for i in srcRegion.iterate_chunks() if chunkFilters[args.filter](i[0], i[1], i[2], args.arg)]
43 else:
44 blockFilter = blockFilters[args.blockfilter]
45 myChunks = [i for i in srcRegion.iterate_chunks() if chunkBlockFilter(i[0], i[1], blockFilter, args.arg)]
47 # find out northernmost, easternmost... chunks
48 xChunks, yChunks, mtimes = zip(*myChunks)
49 limitMinX, limitMinY, limitMaxX, limitMaxY = min(xChunks), min(yChunks), max(xChunks), max(yChunks)
50 # resulting image dimensions
51 dstSize = limitMinX * -16 + limitMaxX * 16 + 16, limitMinY * -16 + limitMaxY * 16 + 16
52 dstXoffset, dstYoffset = 0, 0 # helper to move map around
54 if args.verbose:
55 print "Image size: %sx%s" % dstSize
57 # canvas
58 dstImg = PIL.Image.new("RGB", dstSize)
59 dstDraw = PIL.ImageDraw.Draw(dstImg)
61 axisStep = args.gridStep
62 cnt = 0
63 start = time.time()
64 cleaner = ""
65 for chx, chy, chmtime in myChunks:
66 cnt += 1
67 if args.verbose and not cnt % 10:
68 # show some progress
69 msg = "Chunk: \t%s,%s \t%s/%s \tETA %d/%ds " % \
70 (chx, chy, cnt, len(myChunks), time.time() - start, (time.time() - start) * len(myChunks) / cnt)
71 sys.stdout.write("\r"+cleaner+"\r"+msg)
72 cleaner = " "*len(msg.expandtabs())
73 sys.stdout.flush() # if you do not like this, better turn off whole of verbosity
75 chunk = srcRegion.get_chunk(chx, chy)
76 for y,x in chunkWalker():
77 # world coords
78 realX = (chx - limitMinX ) * 16 + x + dstXoffset
79 realY = (chy - limitMinY ) * 16 + y + dstYoffset
80 # image coords
81 mapX = chx * 16 + x
82 mapY = chy * 16 + y
83 if blockFilter and not blockFilter(mapX, mapY, args.arg): continue
85 #if chx, x, chy, y == 0,0,0,0: # mark world bellybutton # NOTE: slowdown
86 #dstDraw.point((realX, realY), fill=(0, 0, 0))
87 #continue
89 # axes
90 #if (not mapX % axisStep or not mapY % axisStep) and not x+y & 3: # dotted axis
91 # TODO: draw in separate pass to save some time
92 if axisStep and (((mapX+3) % axisStep < 7 and not mapY % axisStep) or ((mapY+3) % axisStep < 7 and not mapX % axisStep)): # cross axis
93 dstDraw.point((realX, realY), fill=(255 - 30*abs((mapX+mapY+3)%axisStep-3) , 0, 0))
94 continue
96 # core job, find highest block and draw it
97 bid, prevbid = 0, 0
98 watermode = 0
99 #print
100 for bid, prevbid, height in FallThru(chunk, x, y):
101 if bid in blockId2color:
102 #print bid, prevbid
103 if bid in (8,9, 79):
104 if not watermode:
105 watermode = bid
106 continue
107 color, layer = getColor(bid, height)
108 if layer < 100:
109 #if realX == 0 or realY == 0: color = 0, 0, 0
110 if watermode:
111 color, garb = getColor(watermode, height)
112 dstDraw.point((realX, realY), fill=color)
113 # TODO: for debuging block ids
114 if color == (238, 130, 238):
115 print "\nunknown block id at %d/%d/%d : %s" % (realX, height, realY, bid)
116 break
117 if args.verbose:
118 sys.stdout.write("\r"+cleaner+"\rChunk: %d done in %ds\n"% (cnt, time.time() - start))
120 del dstDraw
121 dstImg.save(args.destPath, "PNG")
124 chunkFilters = {
125 "all": lambda x,y,t,a: True,
126 "square": lambda x,y,t,a: abs(x) < a[0] and abs(y) < a[0],
127 "rect": lambda x,y,t,a: abs(x) < a[0] and abs(y) < a[1],
128 "circle": lambda x,y,t,a: math.sqrt(x**2 + y**2) < a[0],
129 "test": lambda x,y,t,a: x == 0 and y == 0,
130 "single": lambda x,y,t,a: x == a[0] and y == a[1],
133 # special case, args will be block filter function
134 chunkBlockFilter = lambda x,y,f,a: \
135 f(x*16,y*16, a) or \
136 f(x*16+16,y*16, a) or \
137 f(x*16,y*16+16, a) or \
138 f(x*16+16,y*16+16, a) # anything within given block filter
140 blockFilters = {
141 "all": None,
142 "circle": lambda x,y,a: math.sqrt(x**2 + y**2) < a[0],
145 planks = (160, 128, 0) # carpentry is kinda ubiquitous
147 # key - block id
148 # value tuple - color triple, layer, height tint flag
149 blockId2color = {
150 1: ((128, 128, 128), 0, True), # stone
151 2: ((51, 102, 0), 0, True), # grass
152 3: ((102, 51, 0), 0, True), # dirt
153 4: ((160, 160, 160), 10, True), # cobblestone
154 5: (planks, 10, True), # planks
155 7: ((0, 0, 0), 0, True), # bedrock
156 8: ((0, 77, 153), 0, True), # water
157 9: ((0, 77, 153), 0, True), # water
158 10: ((255, 51, 51), 0, True), # lavavv
159 11: ((255, 51, 51), 0, True), # lava
160 12: ((160, 160, 80), 0, True), # sand
161 13: ((153, 51, 153), 0, True), # gravel
162 14: ((255, 255, 153), 2, False), # gold ore
163 15: ((218, 188, 166), 2, False), # iron ore
164 16: ((0, 0, 0), 2, False), # coal ore
165 17: ((102, 51, 0), 3, False), # wood
166 18: ((0, 153, 0), 3, False), # leaves
167 21: ((0, 0, 153), 2, False), # lazuli ore
168 22: ((0, 0, 204), 10, False), # lazuli block
169 24: ((204, 204, 0), 10, True), # sandstone
170 27: ((200, 200, 212), 10, True), # power rail
171 28: ((200, 200, 212), 10, True), # switch rail
172 35: ((224, 224, 224), 10, True), # wool
173 42: ((200, 200, 212), 10, False), # iron block
174 45: ((153, 0, 0), 10, True), # red bricks
175 60: ((255, 153, 51), 10, True), # irigated dirt
176 66: ((200, 200, 212), 10, True), # rails
177 73: ((153, 0, 0), 0, False), # redstone ore
178 74: ((153, 0, 0), 0, False), # redstone ore
179 78: ((204, 204, 204), 0, True), # snow
180 79: ((153, 204, 255), 0, True), # ice
181 80: ((204, 204, 204), 0, True), # snow
182 91: ((204, 102, 0), 3, False), # pumpkin
183 103: ((128, 255, 0), 3, False), # melon
184 126: (planks, 10, True), # wood slab
185 128: ((204, 204, 0), 10, True), # sandstone stairs
186 134: (planks, 10, True), # wood
187 135: (planks, 10, True), # wood
188 136: (planks, 10, True), # wood
190 unknown = (238, 130, 238), 20, False
193 def getColor(bid, height):
194 """Decide pixel color for block of given type in given height"""
195 color, layer, tint = blockId2color.get(bid, unknown)
196 #color = blockId2color.get(bid, unknown)
197 if tint:
198 heightPart = (height) / 64.0
199 #return tuple([int(i * heightPart) for i in color]), layer
200 return (int(color[0] * heightPart), int(color[1] * heightPart), int(color[2] * heightPart)), layer
201 else:
202 return color, layer
205 def FallThru(chunk, x, y):
206 """Iterate blocks top to bottom"""
207 height = len(chunk["Sections"]) * 16
208 prev = 0
209 for sect in reversed(chunk["Sections"]):
210 for layer in reversed(sect["Blocks"]):
211 cur = layer[y,x]
212 yield cur, prev, height
213 prev = cur
214 height -= 1
217 def chunkWalker():
218 """Iterate 16x16 matrix"""
219 for i in range(16):
220 for j in range(16):
221 yield j,i
224 if __name__ == "__main__":
225 main()