moved quadtree.py to mtime-based update checking, and added a stub direct-to-tile renderer
This commit is contained in:
7
gmap.py
7
gmap.py
@@ -137,9 +137,10 @@ def main():
|
|||||||
w.go(options.procs)
|
w.go(options.procs)
|
||||||
|
|
||||||
# Now generate the tiles
|
# Now generate the tiles
|
||||||
#q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg)
|
# TODO chunklist, render type (night, lighting, spawn)
|
||||||
#q.write_html(options.skipjs)
|
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg)
|
||||||
#q.go(options.procs)
|
q.write_html(options.skipjs)
|
||||||
|
q.go(options.procs)
|
||||||
|
|
||||||
def delete_all(tiledir):
|
def delete_all(tiledir):
|
||||||
# Delete all /hash/ files in the tile dir.
|
# Delete all /hash/ files in the tile dir.
|
||||||
|
|||||||
169
quadtree.py
169
quadtree.py
@@ -31,6 +31,7 @@ from time import gmtime, strftime, sleep
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
import nbt
|
||||||
from optimizeimages import optimize_image
|
from optimizeimages import optimize_image
|
||||||
import composite
|
import composite
|
||||||
|
|
||||||
@@ -424,9 +425,11 @@ class QuadtreeGen(object):
|
|||||||
chunklist = []
|
chunklist = []
|
||||||
for row in xrange(rowstart-16, rowend+1):
|
for row in xrange(rowstart-16, rowend+1):
|
||||||
for col in xrange(colstart, colend+1):
|
for col in xrange(colstart, colend+1):
|
||||||
c = self.world.chunkmap.get((col, row), None)
|
# return (col, row, chunkx, chunky, regionpath)
|
||||||
if c:
|
chunkx, chunky = self.world.unconvert_coords(col, row)
|
||||||
chunklist.append((col, row, c))
|
c = self.world.get_region_path(chunkx, chunky)
|
||||||
|
if os.path.exists(c):
|
||||||
|
chunklist.append((col, row, chunkx, chunky, c))
|
||||||
return chunklist
|
return chunklist
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
@@ -436,68 +439,56 @@ def render_innertile(dest, name, imgformat, optimizeimg):
|
|||||||
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) + "." + imgformat
|
imgpath = os.path.join(dest, name) + "." + imgformat
|
||||||
hashpath = os.path.join(dest, name) + ".hash"
|
|
||||||
|
|
||||||
if name == "base":
|
if name == "base":
|
||||||
q0path = os.path.join(dest, "0." + imgformat)
|
q0path = os.path.join(dest, "0." + imgformat)
|
||||||
q1path = os.path.join(dest, "1." + imgformat)
|
q1path = os.path.join(dest, "1." + imgformat)
|
||||||
q2path = os.path.join(dest, "2." + imgformat)
|
q2path = os.path.join(dest, "2." + imgformat)
|
||||||
q3path = os.path.join(dest, "3." + imgformat)
|
q3path = os.path.join(dest, "3." + imgformat)
|
||||||
q0hash = os.path.join(dest, "0.hash")
|
|
||||||
q1hash = os.path.join(dest, "1.hash")
|
|
||||||
q2hash = os.path.join(dest, "2.hash")
|
|
||||||
q3hash = os.path.join(dest, "3.hash")
|
|
||||||
else:
|
else:
|
||||||
q0path = os.path.join(dest, name, "0." + imgformat)
|
q0path = os.path.join(dest, name, "0." + imgformat)
|
||||||
q1path = os.path.join(dest, name, "1." + imgformat)
|
q1path = os.path.join(dest, name, "1." + imgformat)
|
||||||
q2path = os.path.join(dest, name, "2." + imgformat)
|
q2path = os.path.join(dest, name, "2." + imgformat)
|
||||||
q3path = os.path.join(dest, name, "3." + imgformat)
|
q3path = os.path.join(dest, name, "3." + imgformat)
|
||||||
q0hash = os.path.join(dest, name, "0.hash")
|
|
||||||
q1hash = os.path.join(dest, name, "1.hash")
|
|
||||||
q2hash = os.path.join(dest, name, "2.hash")
|
|
||||||
q3hash = os.path.join(dest, name, "3.hash")
|
|
||||||
|
|
||||||
# Check which ones exist
|
# Check which ones exist
|
||||||
if not os.path.exists(q0hash):
|
if not os.path.exists(q0path):
|
||||||
q0path = None
|
q0path = None
|
||||||
q0hash = None
|
if not os.path.exists(q1path):
|
||||||
if not os.path.exists(q1hash):
|
|
||||||
q1path = None
|
q1path = None
|
||||||
q1hash = None
|
if not os.path.exists(q2path):
|
||||||
if not os.path.exists(q2hash):
|
|
||||||
q2path = None
|
q2path = None
|
||||||
q2hash = None
|
if not os.path.exists(q3path):
|
||||||
if not os.path.exists(q3hash):
|
|
||||||
q3path = None
|
q3path = None
|
||||||
q3hash = None
|
|
||||||
|
|
||||||
# do they all not exist?
|
# do they all not exist?
|
||||||
if not (q0path or q1path or q2path or q3path):
|
if not (q0path or q1path or q2path or q3path):
|
||||||
if os.path.exists(imgpath):
|
if os.path.exists(imgpath):
|
||||||
os.unlink(imgpath)
|
os.unlink(imgpath)
|
||||||
if os.path.exists(hashpath):
|
|
||||||
os.unlink(hashpath)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Now check the hashes
|
# check the mtimes
|
||||||
hasher = hashlib.md5()
|
try:
|
||||||
if q0hash:
|
tile_mtime = os.path.getmtime(imgpath)
|
||||||
hasher.update(open(q0hash, "rb").read())
|
needs_rerender = False
|
||||||
if q1hash:
|
|
||||||
hasher.update(open(q1hash, "rb").read())
|
# remove non-existant paths
|
||||||
if q2hash:
|
components = [q0path, q1path, q2path, q3path]
|
||||||
hasher.update(open(q2hash, "rb").read())
|
components = filter(lambda p: p != None, components)
|
||||||
if q3hash:
|
|
||||||
hasher.update(open(q3hash, "rb").read())
|
for mtime in [os.path.getmtime(path) for path in components]:
|
||||||
if os.path.exists(hashpath):
|
if mtime > tile_mtime:
|
||||||
oldhash = open(hashpath, "rb").read()
|
needs_rerender = True
|
||||||
else:
|
break
|
||||||
oldhash = None
|
|
||||||
newhash = hasher.digest()
|
# quit now if we don't need rerender
|
||||||
|
if not needs_rerender:
|
||||||
if newhash == oldhash:
|
return
|
||||||
# Nothing to do
|
except OSError:
|
||||||
return
|
# one of our mtime calls failed, so we'll continue
|
||||||
|
pass
|
||||||
|
|
||||||
|
#logging.debug("writing out innertile {0}".format(imgpath))
|
||||||
|
|
||||||
# Create the actual image now
|
# Create the actual image now
|
||||||
img = Image.new("RGBA", (384, 384), (38,92,255,0))
|
img = Image.new("RGBA", (384, 384), (38,92,255,0))
|
||||||
@@ -538,10 +529,6 @@ def render_innertile(dest, name, imgformat, optimizeimg):
|
|||||||
if optimizeimg:
|
if optimizeimg:
|
||||||
optimize_image(imgpath, imgformat, optimizeimg)
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
with open(hashpath, "wb") as hashout:
|
|
||||||
hashout.write(newhash)
|
|
||||||
|
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg):
|
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg):
|
||||||
"""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
|
||||||
@@ -549,8 +536,8 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
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
|
||||||
will render the other half)
|
will render the other half)
|
||||||
|
|
||||||
chunks is a list of (col, row, filename) of chunk images that are relevant
|
chunks is a list of (col, row, chunkx, chunky, filename) of chunk
|
||||||
to this call
|
images that are relevant to this call (with their associated regions)
|
||||||
|
|
||||||
The image is saved to path+".ext" and a hash is saved to path+".hash"
|
The image is saved to path+".ext" and a hash is saved to path+".hash"
|
||||||
|
|
||||||
@@ -592,15 +579,20 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
|
|
||||||
# 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"
|
# TODO remove hash files?
|
||||||
imgpath = path + "." + imgformat
|
imgpath = path + "." + imgformat
|
||||||
|
|
||||||
|
# first, remove chunks from `chunks` that don't actually exist in
|
||||||
|
# their region files
|
||||||
|
def chunk_exists(chunk):
|
||||||
|
_, _, chunkx, chunky, region = chunk
|
||||||
|
return nbt.load_from_region(region, chunkx, chunky) != None
|
||||||
|
chunks = filter(chunk_exists, chunks)
|
||||||
|
|
||||||
if not chunks:
|
if not chunks:
|
||||||
# No chunks were found in this tile
|
# No chunks were found in this tile
|
||||||
if os.path.exists(imgpath):
|
if os.path.exists(imgpath):
|
||||||
os.unlink(imgpath)
|
os.unlink(imgpath)
|
||||||
if os.path.exists(hashpath):
|
|
||||||
os.unlink(hashpath)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create the directory if not exists
|
# Create the directory if not exists
|
||||||
@@ -615,60 +607,44 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
import errno
|
import errno
|
||||||
if e.errno != errno.EEXIST:
|
if e.errno != errno.EEXIST:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
imghash = hashlib.md5()
|
# check chunk mtimes to see if they are newer
|
||||||
for col, row, chunkfile in chunks:
|
try:
|
||||||
# Get the hash of this image and add it to our hash for this tile
|
tile_mtime = os.path.getmtime(imgpath)
|
||||||
imghash.update(
|
needs_rerender = False
|
||||||
os.path.basename(chunkfile).split(".")[4]
|
for col, row, chunkx, chunky, regionfile in chunks:
|
||||||
)
|
# check region file mtime first
|
||||||
digest = imghash.digest()
|
if os.path.getmtime(regionfile) <= tile_mtime:
|
||||||
|
continue
|
||||||
if os.path.exists(hashpath):
|
|
||||||
oldhash = open(hashpath, 'rb').read()
|
# checking chunk mtime
|
||||||
else:
|
with open(regionfile, 'rb') as regionfile:
|
||||||
oldhash = None
|
region = nbt.MCRFileReader(regionfile)
|
||||||
|
if region.get_chunk_timestamp(chunkx, chunky) > time_mtime:
|
||||||
if digest == oldhash:
|
needs_rerender = True
|
||||||
# All the chunks for this tile have not changed according to the hash
|
if needs_rerender:
|
||||||
return
|
break
|
||||||
|
|
||||||
|
# if after all that, we don't need a rerender, return
|
||||||
|
if not needs_rerender:
|
||||||
|
return None
|
||||||
|
except OSError:
|
||||||
|
# couldn't get tile mtime, skip check
|
||||||
|
pass
|
||||||
|
|
||||||
|
#logging.debug("writing out worldtile {0}".format(imgpath))
|
||||||
|
|
||||||
# Compile this image
|
# Compile this image
|
||||||
tileimg = Image.new("RGBA", (width, height), (38,92,255,0))
|
tileimg = Image.new("RGBA", (width, height), (38,92,255,0))
|
||||||
|
|
||||||
# col colstart will get drawn on the image starting at x coordinates -(384/2)
|
# col colstart will get drawn on the image starting at x coordinates -(384/2)
|
||||||
# row rowstart will get drawn on the image starting at y coordinates -(192/2)
|
# row rowstart will get drawn on the image starting at y coordinates -(192/2)
|
||||||
for col, row, chunkfile in chunks:
|
for col, row, chunkx, chunky, regionfile in chunks:
|
||||||
try:
|
|
||||||
chunkimg = Image.open(chunkfile)
|
|
||||||
chunkimg.load()
|
|
||||||
except Exception, e:
|
|
||||||
# If for some reason the chunk failed to load (perhaps a previous
|
|
||||||
# run was canceled and the file was only written half way,
|
|
||||||
# corrupting it), then this could error.
|
|
||||||
# Since we have no easy way of determining how this chunk was
|
|
||||||
# generated, we need to just ignore it.
|
|
||||||
logging.warning("Could not open chunk '{0}' ({1})".format(chunkfile,e))
|
|
||||||
try:
|
|
||||||
# Remove the file so that the next run will re-generate it.
|
|
||||||
os.unlink(chunkfile)
|
|
||||||
except OSError, e:
|
|
||||||
import errno
|
|
||||||
# Ignore if file doesn't exist, another task could have already
|
|
||||||
# removed it.
|
|
||||||
if e.errno != errno.ENOENT:
|
|
||||||
logging.warning("Could not remove chunk '{0}'!".format(chunkfile))
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
logging.warning("Removed the corrupt file")
|
|
||||||
|
|
||||||
logging.warning("You will need to re-run the Overviewer to fix this chunk")
|
|
||||||
continue
|
|
||||||
|
|
||||||
xpos = -192 + (col-colstart)*192
|
xpos = -192 + (col-colstart)*192
|
||||||
ypos = -96 + (row-rowstart)*96
|
ypos = -96 + (row-rowstart)*96
|
||||||
|
|
||||||
composite.alpha_over(tileimg, chunkimg.convert("RGB"), (xpos, ypos), chunkimg)
|
# TODO draw chunks!
|
||||||
|
#composite.alpha_over(tileimg, chunkimg.convert("RGB"), (xpos, ypos), chunkimg)
|
||||||
|
|
||||||
# Save them
|
# Save them
|
||||||
tileimg.save(imgpath)
|
tileimg.save(imgpath)
|
||||||
@@ -676,9 +652,6 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
if optimizeimg:
|
if optimizeimg:
|
||||||
optimize_image(imgpath, imgformat, optimizeimg)
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
with open(hashpath, "wb") as hashout:
|
|
||||||
hashout.write(digest)
|
|
||||||
|
|
||||||
class FakeResult(object):
|
class FakeResult(object):
|
||||||
def __init__(self, res):
|
def __init__(self, res):
|
||||||
self.res = res
|
self.res = res
|
||||||
|
|||||||
29
world.py
29
world.py
@@ -38,16 +38,6 @@ This module has routines for extracting information about available worlds
|
|||||||
base36decode = functools.partial(int, base=36)
|
base36decode = functools.partial(int, base=36)
|
||||||
cached = collections.defaultdict(dict)
|
cached = collections.defaultdict(dict)
|
||||||
|
|
||||||
|
|
||||||
def _convert_coords(chunkx, chunky):
|
|
||||||
"""Takes a coordinate (chunkx, chunky) where chunkx and chunky are
|
|
||||||
in the chunk coordinate system, and figures out the row and column
|
|
||||||
in the image each one should be. Returns (col, row)."""
|
|
||||||
|
|
||||||
# columns are determined by the sum of the chunk coords, rows are the
|
|
||||||
# difference (TODO: be able to change direction of north)
|
|
||||||
return (chunkx + chunky, chunky - chunkx)
|
|
||||||
|
|
||||||
def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'):
|
def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'):
|
||||||
'''
|
'''
|
||||||
Convert an integer to a base36 string.
|
Convert an integer to a base36 string.
|
||||||
@@ -117,6 +107,23 @@ class World(object):
|
|||||||
|
|
||||||
return os.path.join(self.worlddir, chunkFile)
|
return os.path.join(self.worlddir, chunkFile)
|
||||||
|
|
||||||
|
def convert_coords(self, chunkx, chunky):
|
||||||
|
"""Takes a coordinate (chunkx, chunky) where chunkx and chunky are
|
||||||
|
in the chunk coordinate system, and figures out the row and column
|
||||||
|
in the image each one should be. Returns (col, row)."""
|
||||||
|
|
||||||
|
# columns are determined by the sum of the chunk coords, rows are the
|
||||||
|
# difference (TODO: be able to change direction of north)
|
||||||
|
# change this function, and you MUST change unconvert_coords
|
||||||
|
return (chunkx + chunky, chunky - chunkx)
|
||||||
|
|
||||||
|
def unconvert_coords(self, col, row):
|
||||||
|
"""Undoes what convert_coords does. Returns (chunkx, chunky)."""
|
||||||
|
|
||||||
|
# col + row = chunky + chunky => (col + row)/2 = chunky
|
||||||
|
# col - row = chunkx + chunkx => (col - row)/2 = chunkx
|
||||||
|
return ((col - row) / 2, (col + row) / 2)
|
||||||
|
|
||||||
def findTrueSpawn(self):
|
def findTrueSpawn(self):
|
||||||
"""Adds the true spawn location to self.POI. The spawn Y coordinate
|
"""Adds the true spawn location to self.POI. The spawn Y coordinate
|
||||||
is almost always the default of 64. Find the first air block above
|
is almost always the default of 64. Find the first air block above
|
||||||
@@ -181,7 +188,7 @@ class World(object):
|
|||||||
# Translate chunks to our diagonal coordinate system
|
# Translate chunks to our diagonal coordinate system
|
||||||
mincol = maxcol = minrow = maxrow = 0
|
mincol = maxcol = minrow = maxrow = 0
|
||||||
for chunkx, chunky in [(minx, miny), (minx, maxy), (maxx, miny), (maxx, maxy)]:
|
for chunkx, chunky in [(minx, miny), (minx, maxy), (maxx, miny), (maxx, maxy)]:
|
||||||
col, row = _convert_coords(chunkx, chunky)
|
col, row = self.convert_coords(chunkx, chunky)
|
||||||
mincol = min(mincol, col)
|
mincol = min(mincol, col)
|
||||||
maxcol = max(maxcol, col)
|
maxcol = max(maxcol, col)
|
||||||
minrow = min(minrow, row)
|
minrow = min(minrow, row)
|
||||||
|
|||||||
Reference in New Issue
Block a user