Merged in new logging, fixed markers, and minetracks
This commit is contained in:
12
README.rst
12
README.rst
@@ -100,7 +100,7 @@ it.
|
|||||||
|
|
||||||
The Overviewer will put a cached image for every chunk *directly in your world
|
The Overviewer will put a cached image for every chunk *directly in your world
|
||||||
directory by default*. If you do not like this behavior, you can specify
|
directory by default*. If you do not like this behavior, you can specify
|
||||||
another location with the --chunkdir option. See below for details.
|
another location with the --cachedir option. See below for details.
|
||||||
|
|
||||||
Options
|
Options
|
||||||
-------
|
-------
|
||||||
@@ -121,6 +121,12 @@ Options
|
|||||||
|
|
||||||
python gmap.py --cachedir=<chunk cache dir> <world> <output dir>
|
python gmap.py --cachedir=<chunk cache dir> <world> <output dir>
|
||||||
|
|
||||||
|
--imgformat=FORMAT
|
||||||
|
Set the output image format used for the tiles. The default is 'png',
|
||||||
|
but 'jpg' is also supported. Note that regardless of what you choose,
|
||||||
|
Overviewer will still use PNG for cached images to avoid recompression
|
||||||
|
artifacts.
|
||||||
|
|
||||||
-p PROCS, --processes=PROCS
|
-p PROCS, --processes=PROCS
|
||||||
Adding the "-p" option will utilize more cores during processing. This
|
Adding the "-p" option will utilize more cores during processing. This
|
||||||
can speed up rendering quite a bit. The default is set to the same
|
can speed up rendering quite a bit. The default is set to the same
|
||||||
@@ -209,6 +215,10 @@ render for my world from 85M to 67M.
|
|||||||
|
|
||||||
find /path/to/destination -name "*.png" -exec pngcrush {} {}.crush \; -exec mv {}.crush {} \;
|
find /path/to/destination -name "*.png" -exec pngcrush {} {}.crush \; -exec mv {}.crush {} \;
|
||||||
|
|
||||||
|
Or if you prefer a more parallel solution, try something like this::
|
||||||
|
|
||||||
|
find /path/to/destination -print0 | xargs -0 -n 1 -P <nr_procs> sh -c 'pngcrush $0 temp.$$ && mv temp.$$ $0'
|
||||||
|
|
||||||
If you're on Windows, I've gotten word that this command line snippet works
|
If you're on Windows, I've gotten word that this command line snippet works
|
||||||
provided pngout is installed and on your path. Note that the % symbols will
|
provided pngout is installed and on your path. Note that the % symbols will
|
||||||
need to be doubled up if this is in a batch file.
|
need to be doubled up if this is in a batch file.
|
||||||
|
|||||||
29
chunk.py
29
chunk.py
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
from itertools import izip, count
|
|
||||||
import os.path
|
import os.path
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
@@ -60,9 +59,14 @@ def get_skylight_array(level):
|
|||||||
"""
|
"""
|
||||||
return numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
|
return numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
|
||||||
|
|
||||||
|
def get_blockdata_array(level):
|
||||||
|
"""Returns the ancillary data from the 'Data' byte array. Data is packed
|
||||||
|
in a similar manner to skylight data"""
|
||||||
|
return numpy.frombuffer(level['Data'], dtype=numpy.uint8).reshape((16,16,64))
|
||||||
|
|
||||||
# This set holds blocks ids that can be seen through, for occlusion calculations
|
# This set holds blocks ids that can be seen through, for occlusion calculations
|
||||||
transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 50, 51, 52, 53,
|
transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53,
|
||||||
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 79, 83, 85])
|
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 79, 81, 83, 85])
|
||||||
|
|
||||||
def render_and_save(chunkfile, cachedir, cave=False):
|
def render_and_save(chunkfile, cachedir, cave=False):
|
||||||
"""Used as the entry point for the multiprocessing workers (since processes
|
"""Used as the entry point for the multiprocessing workers (since processes
|
||||||
@@ -238,6 +242,13 @@ class ChunkRenderer(object):
|
|||||||
blocks = blocks.copy()
|
blocks = blocks.copy()
|
||||||
blocks[skylight_expanded != 0] = 21
|
blocks[skylight_expanded != 0] = 21
|
||||||
|
|
||||||
|
blockData = get_blockdata_array(self.level)
|
||||||
|
blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
|
||||||
|
# Even elements get the lower 4 bits
|
||||||
|
blockData_expanded[:,:,::2] = blockData & 0x0F
|
||||||
|
# Odd elements get the upper 4 bits
|
||||||
|
blockData_expanded[:,:,1::2] = blockData >> 4
|
||||||
|
|
||||||
|
|
||||||
# Each block is 24x24
|
# Each block is 24x24
|
||||||
# The next block on the X axis adds 12px to x and subtracts 6px from y in the image
|
# The next block on the X axis adds 12px to x and subtracts 6px from y in the image
|
||||||
@@ -256,7 +267,17 @@ class ChunkRenderer(object):
|
|||||||
for z in xrange(128):
|
for z in xrange(128):
|
||||||
try:
|
try:
|
||||||
blockid = blocks[x,y,z]
|
blockid = blocks[x,y,z]
|
||||||
t = textures.blockmap[blockid]
|
|
||||||
|
# the following blocks don't have textures that can be pre-computed from the blockid
|
||||||
|
# alone. additional data is required.
|
||||||
|
# TODO torches, redstone torches, crops, ladders, stairs,
|
||||||
|
# levers, doors, buttons, and signs all need to be handled here (and in textures.py)
|
||||||
|
if blockid in (66,): ## minecart track
|
||||||
|
ancilData = blockData_expanded[x,y,z]
|
||||||
|
t = textures.generate_special_texture(blockid, ancilData)
|
||||||
|
|
||||||
|
else:
|
||||||
|
t = textures.blockmap[blockid]
|
||||||
if not t:
|
if not t:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
32
gmap.py
32
gmap.py
@@ -26,14 +26,16 @@ from optparse import OptionParser
|
|||||||
import re
|
import re
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
|
|
||||||
import world
|
import world
|
||||||
import quadtree
|
import quadtree
|
||||||
|
|
||||||
helptext = """
|
helptext = """
|
||||||
%prog [OPTIONS] <World # / Path to World> <tiles dest dir>
|
%prog [OPTIONS] <World # / Path to World> <tiles dest dir>
|
||||||
%prog -d <World # / Path to World / Path to cache dir> [tiles dest dir]
|
%prog -d <World # / Path to World / Path to cache dir> [tiles dest dir]"""
|
||||||
"""
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
@@ -46,6 +48,9 @@ def main():
|
|||||||
parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true")
|
parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true")
|
||||||
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
||||||
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
||||||
|
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.")
|
||||||
|
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
||||||
|
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
|
||||||
|
|
||||||
options, args = parser.parse_args()
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
@@ -85,12 +90,28 @@ def main():
|
|||||||
else:
|
else:
|
||||||
chunklist = None
|
chunklist = None
|
||||||
|
|
||||||
|
if options.imgformat:
|
||||||
|
if options.imgformat not in ('jpg','png'):
|
||||||
|
parser.error("Unknown imgformat!")
|
||||||
|
else:
|
||||||
|
imgformat = options.imgformat
|
||||||
|
else:
|
||||||
|
imgformat = 'png'
|
||||||
|
|
||||||
|
logging.getLogger().setLevel(
|
||||||
|
logging.getLogger().level + 10*options.quiet)
|
||||||
|
logging.getLogger().setLevel(
|
||||||
|
logging.getLogger().level - 10*options.verbose)
|
||||||
|
|
||||||
|
logging.info("Welcome to Minecraft Overviewer!")
|
||||||
|
logging.debug("Current log level: {0}".format(logging.getLogger().level))
|
||||||
|
|
||||||
# First generate the world's chunk images
|
# First generate the world's chunk images
|
||||||
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist)
|
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist)
|
||||||
w.go(options.procs)
|
w.go(options.procs)
|
||||||
|
|
||||||
# Now generate the tiles
|
# Now generate the tiles
|
||||||
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom)
|
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat)
|
||||||
q.go(options.procs)
|
q.go(options.procs)
|
||||||
|
|
||||||
def delete_all(worlddir, tiledir):
|
def delete_all(worlddir, tiledir):
|
||||||
@@ -102,7 +123,7 @@ def delete_all(worlddir, tiledir):
|
|||||||
for f in filenames:
|
for f in filenames:
|
||||||
if matcher.match(f):
|
if matcher.match(f):
|
||||||
filepath = os.path.join(dirpath, f)
|
filepath = os.path.join(dirpath, f)
|
||||||
print "Deleting {0}".format(filepath)
|
logging.info("Deleting {0}".format(filepath))
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
|
|
||||||
# Now delete all /hash/ files in the tile dir.
|
# Now delete all /hash/ files in the tile dir.
|
||||||
@@ -111,7 +132,7 @@ def delete_all(worlddir, tiledir):
|
|||||||
for f in filenames:
|
for f in filenames:
|
||||||
if f.endswith(".hash"):
|
if f.endswith(".hash"):
|
||||||
filepath = os.path.join(dirpath, f)
|
filepath = os.path.join(dirpath, f)
|
||||||
print "Deleting {0}".format(filepath)
|
logging.info("Deleting {0}".format(filepath))
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
|
|
||||||
def list_worlds():
|
def list_worlds():
|
||||||
@@ -132,4 +153,5 @@ def list_worlds():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
multiprocessing.freeze_support()
|
||||||
main()
|
main()
|
||||||
|
|||||||
100
quadtree.py
100
quadtree.py
@@ -23,9 +23,12 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
import util
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module has routines related to generating a quadtree of tiles
|
This module has routines related to generating a quadtree of tiles
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ def catch_keyboardinterrupt(func):
|
|||||||
try:
|
try:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print "Ctrl-C caught!"
|
logging.error("Ctrl-C caught!")
|
||||||
raise Exception("Exiting")
|
raise Exception("Exiting")
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
@@ -52,7 +55,7 @@ def catch_keyboardinterrupt(func):
|
|||||||
return newfunc
|
return newfunc
|
||||||
|
|
||||||
class QuadtreeGen(object):
|
class QuadtreeGen(object):
|
||||||
def __init__(self, worldobj, destdir, depth=None):
|
def __init__(self, worldobj, destdir, depth=None, imgformat=None):
|
||||||
"""Generates a quadtree from the world given into the
|
"""Generates a quadtree from the world given into the
|
||||||
given dest directory
|
given dest directory
|
||||||
|
|
||||||
@@ -62,6 +65,9 @@ class QuadtreeGen(object):
|
|||||||
minimum depth that contains all chunks is calculated and used.
|
minimum depth that contains all chunks is calculated and used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
assert(imgformat)
|
||||||
|
self.imgformat = imgformat
|
||||||
|
|
||||||
if depth is None:
|
if depth is None:
|
||||||
# Determine quadtree depth (midpoint is always 0,0)
|
# Determine quadtree depth (midpoint is always 0,0)
|
||||||
for p in xrange(15):
|
for p in xrange(15):
|
||||||
@@ -106,16 +112,18 @@ class QuadtreeGen(object):
|
|||||||
else:
|
else:
|
||||||
if not complete % 1000 == 0:
|
if not complete % 1000 == 0:
|
||||||
return
|
return
|
||||||
print "{0}/{1} tiles complete on level {2}/{3}".format(
|
logging.info("{0}/{1} tiles complete on level {2}/{3}".format(
|
||||||
complete, total, level, self.p)
|
complete, total, level, self.p))
|
||||||
|
|
||||||
def write_html(self, zoomlevel):
|
def write_html(self, zoomlevel, imgformat):
|
||||||
"""Writes out index.html"""
|
"""Writes out index.html"""
|
||||||
templatepath = os.path.join(os.path.split(__file__)[0], "template.html")
|
templatepath = os.path.join(util.get_program_path(), "template.html")
|
||||||
|
|
||||||
html = open(templatepath, 'r').read()
|
html = open(templatepath, 'r').read()
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
"{maxzoom}", str(zoomlevel))
|
"{maxzoom}", str(zoomlevel))
|
||||||
|
html = html.replace(
|
||||||
|
"{imgformat}", str(imgformat))
|
||||||
|
|
||||||
with open(os.path.join(self.destdir, "index.html"), 'w') as output:
|
with open(os.path.join(self.destdir, "index.html"), 'w') as output:
|
||||||
output.write(html)
|
output.write(html)
|
||||||
@@ -125,6 +133,12 @@ class QuadtreeGen(object):
|
|||||||
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
|
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
|
||||||
output.write("var markerData=%s" % json.dumps(self.world.POI))
|
output.write("var markerData=%s" % json.dumps(self.world.POI))
|
||||||
|
|
||||||
|
# Write a blank image
|
||||||
|
blank = Image.new("RGBA", (1,1))
|
||||||
|
tileDir = os.path.join(self.destdir, "tiles")
|
||||||
|
if not os.path.exists(tileDir): os.mkdir(tileDir)
|
||||||
|
blank.save(os.path.join(tileDir, "blank."+self.imgformat))
|
||||||
|
|
||||||
def _get_cur_depth(self):
|
def _get_cur_depth(self):
|
||||||
"""How deep is the quadtree currently in the destdir? This glances in
|
"""How deep is the quadtree currently in the destdir? This glances in
|
||||||
index.html to see what maxZoom is set to.
|
index.html to see what maxZoom is set to.
|
||||||
@@ -159,8 +173,8 @@ class QuadtreeGen(object):
|
|||||||
newdir = "new" + str(dirnum)
|
newdir = "new" + str(dirnum)
|
||||||
newdirpath = getpath(newdir)
|
newdirpath = getpath(newdir)
|
||||||
|
|
||||||
files = [str(dirnum)+".png", str(dirnum)+".hash", str(dirnum)]
|
files = [str(dirnum)+"."+self.imgformat, str(dirnum)+".hash", str(dirnum)]
|
||||||
newfiles = [str(newnum)+".png", str(newnum)+".hash", str(newnum)]
|
newfiles = [str(newnum)+"."+self.imgformat, str(newnum)+".hash", str(newnum)]
|
||||||
|
|
||||||
os.mkdir(newdirpath)
|
os.mkdir(newdirpath)
|
||||||
for f, newf in zip(files, newfiles):
|
for f, newf in zip(files, newfiles):
|
||||||
@@ -221,7 +235,7 @@ class QuadtreeGen(object):
|
|||||||
# (even if tilechunks is empty, render_worldtile will delete
|
# (even if tilechunks is empty, render_worldtile will delete
|
||||||
# existing images if appropriate)
|
# existing images if appropriate)
|
||||||
yield pool.apply_async(func=render_worldtile, args= (tilechunks,
|
yield pool.apply_async(func=render_worldtile, args= (tilechunks,
|
||||||
colstart, colend, rowstart, rowend, dest))
|
colstart, colend, rowstart, rowend, dest, self.imgformat))
|
||||||
|
|
||||||
def _apply_render_inntertile(self, pool, zoom):
|
def _apply_render_inntertile(self, pool, zoom):
|
||||||
"""Same as _apply_render_worltiles but for the inntertile routine.
|
"""Same as _apply_render_worltiles but for the inntertile routine.
|
||||||
@@ -233,7 +247,7 @@ class QuadtreeGen(object):
|
|||||||
dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path[:-1]))
|
dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path[:-1]))
|
||||||
name = str(path[-1])
|
name = str(path[-1])
|
||||||
|
|
||||||
yield pool.apply_async(func=render_innertile, args= (dest, name))
|
yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat))
|
||||||
|
|
||||||
def go(self, procs):
|
def go(self, procs):
|
||||||
"""Renders all tiles"""
|
"""Renders all tiles"""
|
||||||
@@ -245,12 +259,12 @@ class QuadtreeGen(object):
|
|||||||
curdepth = self._get_cur_depth()
|
curdepth = self._get_cur_depth()
|
||||||
if curdepth != -1:
|
if curdepth != -1:
|
||||||
if self.p > curdepth:
|
if self.p > curdepth:
|
||||||
print "Your map seemes to have expanded beyond its previous bounds."
|
logging.warning("Your map seemes to have expanded beyond its previous bounds.")
|
||||||
print "Doing some tile re-arrangements... just a sec..."
|
logging.warning( "Doing some tile re-arrangements... just a sec...")
|
||||||
for _ in xrange(self.p-curdepth):
|
for _ in xrange(self.p-curdepth):
|
||||||
self._increase_depth()
|
self._increase_depth()
|
||||||
elif self.p < curdepth:
|
elif self.p < curdepth:
|
||||||
print "Your map seems to have shrunk. Re-arranging tiles, just a sec..."
|
logging.warning("Your map seems to have shrunk. Re-arranging tiles, just a sec...")
|
||||||
for _ in xrange(curdepth - self.p):
|
for _ in xrange(curdepth - self.p):
|
||||||
self._decrease_depth()
|
self._decrease_depth()
|
||||||
|
|
||||||
@@ -260,17 +274,17 @@ class QuadtreeGen(object):
|
|||||||
else:
|
else:
|
||||||
pool = multiprocessing.Pool(processes=procs)
|
pool = multiprocessing.Pool(processes=procs)
|
||||||
|
|
||||||
self.write_html(self.p)
|
self.write_html(self.p, self.imgformat)
|
||||||
|
|
||||||
# Render the highest level of tiles from the chunks
|
# Render the highest level of tiles from the chunks
|
||||||
results = collections.deque()
|
results = collections.deque()
|
||||||
complete = 0
|
complete = 0
|
||||||
total = 4**self.p
|
total = 4**self.p
|
||||||
print "Rendering highest zoom level of tiles now."
|
logging.info("Rendering highest zoom level of tiles now.")
|
||||||
print "There are {0} tiles to render".format(total)
|
logging.info("There are {0} tiles to render".format(total))
|
||||||
print "There are {0} total levels to render".format(self.p)
|
logging.info("There are {0} total levels to render".format(self.p))
|
||||||
print "Don't worry, each level has only 25% as many tiles as the last."
|
logging.info("Don't worry, each level has only 25% as many tiles as the last.")
|
||||||
print "The others will go faster"
|
logging.info("The others will go faster")
|
||||||
for result in self._apply_render_worldtiles(pool):
|
for result in self._apply_render_worldtiles(pool):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
if len(results) > 10000:
|
if len(results) > 10000:
|
||||||
@@ -295,7 +309,7 @@ class QuadtreeGen(object):
|
|||||||
assert len(results) == 0
|
assert len(results) == 0
|
||||||
complete = 0
|
complete = 0
|
||||||
total = 4**zoom
|
total = 4**zoom
|
||||||
print "Starting level", level
|
logging.info("Starting level {0}".format(level))
|
||||||
for result in self._apply_render_inntertile(pool, zoom):
|
for result in self._apply_render_inntertile(pool, zoom):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
if len(results) > 10000:
|
if len(results) > 10000:
|
||||||
@@ -311,13 +325,13 @@ class QuadtreeGen(object):
|
|||||||
|
|
||||||
self.print_statusline(complete, total, level, True)
|
self.print_statusline(complete, total, level, True)
|
||||||
|
|
||||||
print "Done"
|
logging.info("Done")
|
||||||
|
|
||||||
pool.close()
|
pool.close()
|
||||||
pool.join()
|
pool.join()
|
||||||
|
|
||||||
# Do the final one right here:
|
# Do the final one right here:
|
||||||
render_innertile(os.path.join(self.destdir, "tiles"), "base")
|
render_innertile(os.path.join(self.destdir, "tiles"), "base", self.imgformat)
|
||||||
|
|
||||||
def _get_range_by_path(self, path):
|
def _get_range_by_path(self, path):
|
||||||
"""Returns the x, y chunk coordinates of this tile"""
|
"""Returns the x, y chunk coordinates of this tile"""
|
||||||
@@ -348,28 +362,28 @@ class QuadtreeGen(object):
|
|||||||
return chunklist
|
return chunklist
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_innertile(dest, name):
|
def render_innertile(dest, name, imgformat):
|
||||||
"""
|
"""
|
||||||
Renders a tile at os.path.join(dest, name)+".png" by taking tiles from
|
Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from
|
||||||
os.path.join(dest, name, "{0,1,2,3}.png")
|
os.path.join(dest, name, "{0,1,2,3}.png")
|
||||||
"""
|
"""
|
||||||
imgpath = os.path.join(dest, name) + ".png"
|
imgpath = os.path.join(dest, name) + "." + imgformat
|
||||||
hashpath = os.path.join(dest, name) + ".hash"
|
hashpath = os.path.join(dest, name) + ".hash"
|
||||||
|
|
||||||
if name == "base":
|
if name == "base":
|
||||||
q0path = os.path.join(dest, "0.png")
|
q0path = os.path.join(dest, "0." + imgformat)
|
||||||
q1path = os.path.join(dest, "1.png")
|
q1path = os.path.join(dest, "1." + imgformat)
|
||||||
q2path = os.path.join(dest, "2.png")
|
q2path = os.path.join(dest, "2." + imgformat)
|
||||||
q3path = os.path.join(dest, "3.png")
|
q3path = os.path.join(dest, "3." + imgformat)
|
||||||
q0hash = os.path.join(dest, "0.hash")
|
q0hash = os.path.join(dest, "0.hash")
|
||||||
q1hash = os.path.join(dest, "1.hash")
|
q1hash = os.path.join(dest, "1.hash")
|
||||||
q2hash = os.path.join(dest, "2.hash")
|
q2hash = os.path.join(dest, "2.hash")
|
||||||
q3hash = os.path.join(dest, "3.hash")
|
q3hash = os.path.join(dest, "3.hash")
|
||||||
else:
|
else:
|
||||||
q0path = os.path.join(dest, name, "0.png")
|
q0path = os.path.join(dest, name, "0." + imgformat)
|
||||||
q1path = os.path.join(dest, name, "1.png")
|
q1path = os.path.join(dest, name, "1." + imgformat)
|
||||||
q2path = os.path.join(dest, name, "2.png")
|
q2path = os.path.join(dest, name, "2." + imgformat)
|
||||||
q3path = os.path.join(dest, name, "3.png")
|
q3path = os.path.join(dest, name, "3." + imgformat)
|
||||||
q0hash = os.path.join(dest, name, "0.hash")
|
q0hash = os.path.join(dest, name, "0.hash")
|
||||||
q1hash = os.path.join(dest, name, "1.hash")
|
q1hash = os.path.join(dest, name, "1.hash")
|
||||||
q2hash = os.path.join(dest, name, "2.hash")
|
q2hash = os.path.join(dest, name, "2.hash")
|
||||||
@@ -434,13 +448,16 @@ def render_innertile(dest, name):
|
|||||||
img.paste(quad3, (192, 192))
|
img.paste(quad3, (192, 192))
|
||||||
|
|
||||||
# Save it
|
# Save it
|
||||||
img.save(imgpath)
|
if imgformat == 'jpg':
|
||||||
|
img.save(imgpath, quality=95, subsampling=0)
|
||||||
|
else: # png
|
||||||
|
img.save(imgpath)
|
||||||
with open(hashpath, "wb") as hashout:
|
with open(hashpath, "wb") as hashout:
|
||||||
hashout.write(newhash)
|
hashout.write(newhash)
|
||||||
|
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat):
|
||||||
"""Renders just the specified chunks into a tile and save it. Unlike usual
|
"""Renders just the specified chunks into a tile and save it. Unlike usual
|
||||||
python conventions, rowend and colend are inclusive. Additionally, the
|
python conventions, rowend and colend are inclusive. Additionally, the
|
||||||
chunks around the edges are half-way cut off (so that neighboring tiles
|
chunks around the edges are half-way cut off (so that neighboring tiles
|
||||||
@@ -449,7 +466,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
chunks is a list of (col, row, filename) of chunk images that are relevant
|
chunks is a list of (col, row, filename) of chunk images that are relevant
|
||||||
to this call
|
to this call
|
||||||
|
|
||||||
The image is saved to path+".png" and a hash is saved to path+".hash"
|
The image is saved to path+".ext" and a hash is saved to path+".hash"
|
||||||
|
|
||||||
If there are no chunks, this tile is not saved (if it already exists, it is
|
If there are no chunks, this tile is not saved (if it already exists, it is
|
||||||
deleted)
|
deleted)
|
||||||
@@ -490,7 +507,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
# Before we render any tiles, check the hash of each image in this tile to
|
# Before we render any tiles, check the hash of each image in this tile to
|
||||||
# see if it's changed.
|
# see if it's changed.
|
||||||
hashpath = path + ".hash"
|
hashpath = path + ".hash"
|
||||||
imgpath = path + ".png"
|
imgpath = path + "." + imgformat
|
||||||
|
|
||||||
if not chunks:
|
if not chunks:
|
||||||
# No chunks were found in this tile
|
# No chunks were found in this tile
|
||||||
@@ -545,8 +562,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
# corrupting it), then this could error.
|
# corrupting it), then this could error.
|
||||||
# Since we have no easy way of determining how this chunk was
|
# Since we have no easy way of determining how this chunk was
|
||||||
# generated, we need to just ignore it.
|
# generated, we need to just ignore it.
|
||||||
print "Error opening file", chunkfile
|
logging.warning("Could not open chunk '{0}' ({1})".format(chunkfile,e))
|
||||||
print "(Error was {0})".format(e)
|
|
||||||
try:
|
try:
|
||||||
# Remove the file so that the next run will re-generate it.
|
# Remove the file so that the next run will re-generate it.
|
||||||
os.unlink(chunkfile)
|
os.unlink(chunkfile)
|
||||||
@@ -555,12 +571,12 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
# Ignore if file doesn't exist, another task could have already
|
# Ignore if file doesn't exist, another task could have already
|
||||||
# removed it.
|
# removed it.
|
||||||
if e.errno != errno.ENOENT:
|
if e.errno != errno.ENOENT:
|
||||||
print "Could not remove the corrupt chunk!"
|
logging.warning("Could not remove chunk '{0}'!".format(chunkfile))
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
print "Removed the corrupt file"
|
logging.warning("Removed the corrupt file")
|
||||||
|
|
||||||
print "You will need to re-run the Overviewer to fix this chunk"
|
logging.warning("You will need to re-run the Overviewer to fix this chunk")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
xpos = -192 + (col-colstart)*192
|
xpos = -192 + (col-colstart)*192
|
||||||
|
|||||||
13
setup.py
Normal file
13
setup.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from distutils.core import setup
|
||||||
|
import py2exe
|
||||||
|
|
||||||
|
setup(console=['gmap.py'],
|
||||||
|
data_files=[('textures', ['textures/lava.png', 'textures/water.png']),
|
||||||
|
('', ['template.html'])],
|
||||||
|
zipfile = None,
|
||||||
|
options = {'py2exe': {
|
||||||
|
'bundle_files': 1,
|
||||||
|
}},
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var config = {
|
var config = {
|
||||||
path: 'tiles',
|
path: 'tiles',
|
||||||
fileExt: 'png',
|
fileExt: '{imgformat}',
|
||||||
tileSize: 384,
|
tileSize: 384,
|
||||||
defaultZoom: 1,
|
defaultZoom: 1,
|
||||||
maxZoom: {maxzoom},
|
maxZoom: {maxzoom},
|
||||||
cacheMinutes: 0, // Change this to have browsers automatically requiest new images every x minutes
|
cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes
|
||||||
debug: false
|
debug: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
120
textures.py
120
textures.py
@@ -23,6 +23,8 @@ import math
|
|||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageEnhance
|
from PIL import Image, ImageEnhance
|
||||||
|
|
||||||
|
import util
|
||||||
|
|
||||||
def _find_file(filename, mode="rb"):
|
def _find_file(filename, mode="rb"):
|
||||||
"""Searches for the given file and returns an open handle to it.
|
"""Searches for the given file and returns an open handle to it.
|
||||||
This searches the following locations in this order:
|
This searches the following locations in this order:
|
||||||
@@ -39,7 +41,7 @@ def _find_file(filename, mode="rb"):
|
|||||||
* The program dir / textures
|
* The program dir / textures
|
||||||
|
|
||||||
"""
|
"""
|
||||||
programdir = os.path.dirname(__file__)
|
programdir = util.get_program_path()
|
||||||
path = os.path.join(programdir, filename)
|
path = os.path.join(programdir, filename)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
@@ -92,13 +94,19 @@ def _get_terrain_image():
|
|||||||
def _split_terrain(terrain):
|
def _split_terrain(terrain):
|
||||||
"""Builds and returns a length 256 array of each 16x16 chunk of texture"""
|
"""Builds and returns a length 256 array of each 16x16 chunk of texture"""
|
||||||
textures = []
|
textures = []
|
||||||
|
(terrain_width, terrain_height) = terrain.size
|
||||||
|
texture_resolution = terrain_width / 16
|
||||||
for y in xrange(16):
|
for y in xrange(16):
|
||||||
for x in xrange(16):
|
for x in xrange(16):
|
||||||
left = x*16
|
left = x*texture_resolution
|
||||||
upper = y*16
|
upper = y*texture_resolution
|
||||||
right = left+16
|
right = left+texture_resolution
|
||||||
lower = upper+16
|
lower = upper+texture_resolution
|
||||||
region = terrain.crop((left,upper,right,lower))
|
region = terrain.transform(
|
||||||
|
(16, 16),
|
||||||
|
Image.EXTENT,
|
||||||
|
(left,upper,right,lower),
|
||||||
|
Image.BICUBIC)
|
||||||
textures.append(region)
|
textures.append(region)
|
||||||
|
|
||||||
return textures
|
return textures
|
||||||
@@ -106,15 +114,20 @@ def _split_terrain(terrain):
|
|||||||
# This maps terainids to 16x16 images
|
# This maps terainids to 16x16 images
|
||||||
terrain_images = _split_terrain(_get_terrain_image())
|
terrain_images = _split_terrain(_get_terrain_image())
|
||||||
|
|
||||||
def _transform_image(img):
|
def _transform_image(img, blockID=None):
|
||||||
"""Takes a PIL image and rotates it left 45 degrees and shrinks the y axis
|
"""Takes a PIL image and rotates it left 45 degrees and shrinks the y axis
|
||||||
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
if blockID in (81,): # cacti
|
||||||
# even number that can be split in half twice
|
# Resize to 15x15, since the cactus texture is a little smaller than the other textures
|
||||||
img = img.resize((17, 17), Image.BILINEAR)
|
img = img.resize((15, 15), Image.BILINEAR)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
||||||
|
# even number that can be split in half twice
|
||||||
|
img = img.resize((17, 17), Image.BILINEAR)
|
||||||
|
|
||||||
# Build the Affine transformation matrix for this perspective
|
# Build the Affine transformation matrix for this perspective
|
||||||
transform = numpy.matrix(numpy.identity(3))
|
transform = numpy.matrix(numpy.identity(3))
|
||||||
@@ -134,10 +147,19 @@ def _transform_image(img):
|
|||||||
newimg = img.transform((24,12), Image.AFFINE, transform)
|
newimg = img.transform((24,12), Image.AFFINE, transform)
|
||||||
return newimg
|
return newimg
|
||||||
|
|
||||||
def _transform_image_side(img):
|
def _transform_image_side(img, blockID):
|
||||||
"""Takes an image and shears it for the left side of the cube (reflect for
|
"""Takes an image and shears it for the left side of the cube (reflect for
|
||||||
the right side)"""
|
the right side)"""
|
||||||
|
|
||||||
|
if blockID in (44,): # step block
|
||||||
|
# make the top half transpartent
|
||||||
|
# (don't just crop img, since we want the size of
|
||||||
|
# img to be unchanged
|
||||||
|
mask = img.crop((0,8,16,16))
|
||||||
|
n = Image.new(img.mode, img.size, (38,92,255,0))
|
||||||
|
n.paste(mask,(0,0,16,8), mask)
|
||||||
|
img = n
|
||||||
|
|
||||||
# Size of the cube side before shear
|
# Size of the cube side before shear
|
||||||
img = img.resize((12,12))
|
img = img.resize((12,12))
|
||||||
|
|
||||||
@@ -151,20 +173,20 @@ def _transform_image_side(img):
|
|||||||
return newimg
|
return newimg
|
||||||
|
|
||||||
|
|
||||||
def _build_block(top, side, texID=None):
|
def _build_block(top, side, blockID=None):
|
||||||
"""From a top texture and a side texture, build a block image.
|
"""From a top texture and a side texture, build a block image.
|
||||||
top and side should be 16x16 image objects. Returns a 24x24 image
|
top and side should be 16x16 image objects. Returns a 24x24 image
|
||||||
|
|
||||||
"""
|
"""
|
||||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||||
|
|
||||||
top = _transform_image(top)
|
top = _transform_image(top, blockID)
|
||||||
|
|
||||||
if not side:
|
if not side:
|
||||||
img.paste(top, (0,0), top)
|
img.paste(top, (0,0), top)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
side = _transform_image_side(side)
|
side = _transform_image_side(side, blockID)
|
||||||
|
|
||||||
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
|
||||||
@@ -179,16 +201,29 @@ def _build_block(top, side, texID=None):
|
|||||||
otherside.putalpha(othersidealpha)
|
otherside.putalpha(othersidealpha)
|
||||||
|
|
||||||
## special case for non-block things
|
## special case for non-block things
|
||||||
if texID in (12,13,15,28,29,80,73): ## flowers, sapling, mushrooms, regular torch, reeds
|
# TODO once torches are handled by generate_special_texture, remove
|
||||||
|
# them from this list
|
||||||
|
if blockID in (37,38,6,39,40,50,83): ## flowers, sapling, mushrooms, regular torch, reeds
|
||||||
# instead of pasting these blocks at the cube edges, place them in the middle:
|
# instead of pasting these blocks at the cube edges, place them in the middle:
|
||||||
# and omit the top
|
# and omit the top
|
||||||
img.paste(side, (6,3), side)
|
img.paste(side, (6,3), side)
|
||||||
img.paste(otherside, (6,3), otherside)
|
img.paste(otherside, (6,3), otherside)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
img.paste(side, (0,6), side)
|
|
||||||
img.paste(otherside, (12,6), otherside)
|
if blockID in (81,): # cacti!
|
||||||
img.paste(top, (0,0), top)
|
img.paste(side, (2,6), side)
|
||||||
|
img.paste(otherside, (10,6), otherside)
|
||||||
|
img.paste(top, (0,2), top)
|
||||||
|
elif blockID in (44,): # half step
|
||||||
|
# shift each texture down 6 pixels
|
||||||
|
img.paste(side, (0,12), side)
|
||||||
|
img.paste(otherside, (12,12), otherside)
|
||||||
|
img.paste(top, (0,6), top)
|
||||||
|
else:
|
||||||
|
img.paste(side, (0,6), side)
|
||||||
|
img.paste(otherside, (12,6), otherside)
|
||||||
|
img.paste(top, (0,0), top)
|
||||||
|
|
||||||
# Manually touch up 6 pixels that leave a gap because of how the
|
# Manually touch up 6 pixels that leave a gap because of how the
|
||||||
# shearing works out. This makes the blocks perfectly tessellate-able
|
# shearing works out. This makes the blocks perfectly tessellate-able
|
||||||
@@ -233,7 +268,7 @@ def _build_blockimages():
|
|||||||
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
||||||
34, 20, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1,- 1, -1, -1, -1,
|
34, 20, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1,- 1, -1, -1, -1,
|
||||||
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
||||||
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 7, 8, 35,
|
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 5, 5, 7, 8, 35,
|
||||||
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
||||||
36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 1, 1, -1,
|
36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 1, 1, -1,
|
||||||
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
||||||
@@ -243,8 +278,11 @@ def _build_blockimages():
|
|||||||
]
|
]
|
||||||
|
|
||||||
# This maps block id to the texture that goes on the side of the block
|
# This maps block id to the texture that goes on the side of the block
|
||||||
|
if len(topids) != len(sideids):
|
||||||
|
raise Exception("mismatched lengths")
|
||||||
|
|
||||||
allimages = []
|
allimages = []
|
||||||
for toptextureid, sidetextureid in zip(topids, sideids):
|
for toptextureid, sidetextureid,blockID in zip(topids, sideids,range(len(topids))):
|
||||||
if toptextureid == -1 or sidetextureid == -1:
|
if toptextureid == -1 or sidetextureid == -1:
|
||||||
allimages.append(None)
|
allimages.append(None)
|
||||||
continue
|
continue
|
||||||
@@ -252,7 +290,9 @@ def _build_blockimages():
|
|||||||
toptexture = terrain_images[toptextureid]
|
toptexture = terrain_images[toptextureid]
|
||||||
sidetexture = terrain_images[sidetextureid]
|
sidetexture = terrain_images[sidetextureid]
|
||||||
|
|
||||||
img = _build_block(toptexture, sidetexture, toptextureid)
|
## _build_block needs to know about the block ID, not just the textures
|
||||||
|
## of the block or the texture ID
|
||||||
|
img = _build_block(toptexture, sidetexture, blockID)
|
||||||
|
|
||||||
allimages.append((img.convert("RGB"), img.split()[3]))
|
allimages.append((img.convert("RGB"), img.split()[3]))
|
||||||
|
|
||||||
@@ -283,3 +323,41 @@ def load_water():
|
|||||||
blockmap[10] = lavablock.convert("RGB"), lavablock
|
blockmap[10] = lavablock.convert("RGB"), lavablock
|
||||||
blockmap[11] = blockmap[10]
|
blockmap[11] = blockmap[10]
|
||||||
load_water()
|
load_water()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_special_texture(blockID, data):
|
||||||
|
"""Generates a special texture, such as a correctly facing minecraft track"""
|
||||||
|
#print "%s has ancillary data: %X" %(blockID, data)
|
||||||
|
# TODO torches, redstone torches, crops, ladders, stairs,
|
||||||
|
# levers, doors, buttons, and signs all need to be handled here (and in chunkpy)
|
||||||
|
if blockID == 66: # minetrack:
|
||||||
|
raw_straight = terrain_images[128]
|
||||||
|
raw_corner = terrain_images[112]
|
||||||
|
|
||||||
|
## use _transform_image to scale and shear
|
||||||
|
if data == 0:
|
||||||
|
track = _transform_image(raw_straight, blockID)
|
||||||
|
elif data == 6:
|
||||||
|
track = _transform_image(raw_corner, blockID)
|
||||||
|
elif data == 7:
|
||||||
|
track = _transform_image(raw_corner.rotate(270), blockID)
|
||||||
|
elif data == 8:
|
||||||
|
# flip
|
||||||
|
track = _transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90),
|
||||||
|
blockID)
|
||||||
|
elif data == 9:
|
||||||
|
track = _transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM),
|
||||||
|
blockID)
|
||||||
|
elif data == 1:
|
||||||
|
track = _transform_image(raw_straight.rotate(90), blockID)
|
||||||
|
else:
|
||||||
|
# TODO render carts that slop up or down
|
||||||
|
track = _transform_image(raw_straight, blockID)
|
||||||
|
|
||||||
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||||
|
img.paste(track, (0,12), track)
|
||||||
|
|
||||||
|
return (img.convert("RGB"), img.split()[3])
|
||||||
|
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
29
util.py
Normal file
29
util.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# This file is part of the Minecraft Overviewer.
|
||||||
|
#
|
||||||
|
# Minecraft Overviewer is free software: you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
# your option) any later version.
|
||||||
|
#
|
||||||
|
# Minecraft Overviewer is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Misc utility routines used by multiple files that don't belong anywhere else
|
||||||
|
"""
|
||||||
|
|
||||||
|
import imp
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def get_program_path():
|
||||||
|
if hasattr(sys, "frozen") or imp.is_frozen("__main__"):
|
||||||
|
return os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
return os.path.dirname(sys.argv[0])
|
||||||
19
world.py
19
world.py
@@ -18,9 +18,9 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import chunk
|
import chunk
|
||||||
import nbt
|
import nbt
|
||||||
@@ -171,8 +171,9 @@ class WorldRenderer(object):
|
|||||||
def go(self, procs):
|
def go(self, procs):
|
||||||
"""Starts the render. This returns when it is finished"""
|
"""Starts the render. This returns when it is finished"""
|
||||||
|
|
||||||
print "Scanning chunks"
|
logging.info("Scanning chunks")
|
||||||
raw_chunks = self._find_chunkfiles()
|
raw_chunks = self._find_chunkfiles()
|
||||||
|
logging.debug("Done scanning chunks")
|
||||||
|
|
||||||
# Translate chunks to our diagonal coordinate system
|
# Translate chunks to our diagonal coordinate system
|
||||||
mincol, maxcol, minrow, maxrow, chunks = _convert_coords(raw_chunks)
|
mincol, maxcol, minrow, maxrow, chunks = _convert_coords(raw_chunks)
|
||||||
@@ -206,9 +207,11 @@ class WorldRenderer(object):
|
|||||||
p = f.split(".")
|
p = f.split(".")
|
||||||
all_chunks.append((base36decode(p[1]), base36decode(p[2]),
|
all_chunks.append((base36decode(p[1]), base36decode(p[2]),
|
||||||
os.path.join(dirpath, f)))
|
os.path.join(dirpath, f)))
|
||||||
|
logging.debug((base36decode(p[1]), base36decode(p[2]),
|
||||||
|
os.path.join(dirpath, f)))
|
||||||
|
|
||||||
if not all_chunks:
|
if not all_chunks:
|
||||||
print "Error: No chunks found!"
|
logging.error("Error: No chunks found!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return all_chunks
|
return all_chunks
|
||||||
|
|
||||||
@@ -229,7 +232,7 @@ class WorldRenderer(object):
|
|||||||
results = {}
|
results = {}
|
||||||
if processes == 1:
|
if processes == 1:
|
||||||
# Skip the multiprocessing stuff
|
# Skip the multiprocessing stuff
|
||||||
print "Rendering chunks synchronously since you requested 1 process"
|
logging.debug("Rendering chunks synchronously since you requested 1 process")
|
||||||
for i, (col, row, chunkfile) in enumerate(chunks):
|
for i, (col, row, chunkfile) in enumerate(chunks):
|
||||||
if inclusion_set and (col, row) not in inclusion_set:
|
if inclusion_set and (col, row) not in inclusion_set:
|
||||||
# Skip rendering, just find where the existing image is
|
# Skip rendering, just find where the existing image is
|
||||||
@@ -243,9 +246,9 @@ class WorldRenderer(object):
|
|||||||
results[(col, row)] = result
|
results[(col, row)] = result
|
||||||
if i > 0:
|
if i > 0:
|
||||||
if 1000 % i == 0 or i % 1000 == 0:
|
if 1000 % i == 0 or i % 1000 == 0:
|
||||||
print "{0}/{1} chunks rendered".format(i, len(chunks))
|
logging.info("{0}/{1} chunks rendered".format(i, len(chunks)))
|
||||||
else:
|
else:
|
||||||
print "Rendering chunks in {0} processes".format(processes)
|
logging.debug("Rendering chunks in {0} processes".format(processes))
|
||||||
pool = multiprocessing.Pool(processes=processes)
|
pool = multiprocessing.Pool(processes=processes)
|
||||||
asyncresults = []
|
asyncresults = []
|
||||||
for col, row, chunkfile in chunks:
|
for col, row, chunkfile in chunks:
|
||||||
@@ -268,10 +271,10 @@ class WorldRenderer(object):
|
|||||||
results[(col, row)] = result.get()
|
results[(col, row)] = result.get()
|
||||||
if i > 0:
|
if i > 0:
|
||||||
if 1000 % i == 0 or i % 1000 == 0:
|
if 1000 % i == 0 or i % 1000 == 0:
|
||||||
print "{0}/{1} chunks rendered".format(i, len(asyncresults))
|
logging.info("{0}/{1} chunks rendered".format(i, len(asyncresults)))
|
||||||
|
|
||||||
pool.join()
|
pool.join()
|
||||||
print "Done!"
|
logging.info("Done!")
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user