updated and cleaned up overviewer.py
- moved c_overviewer version checks into __init__.py - bare_console support (mostly) moved into util, slightly more sane - removed and updated a ton of old code
This commit is contained in:
329
overviewer.py
329
overviewer.py
@@ -18,25 +18,13 @@
|
||||
import platform
|
||||
import sys
|
||||
|
||||
# quick version check
|
||||
if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6):
|
||||
print "Sorry, the Overviewer requires at least Python 2.6 to run"
|
||||
if sys.version_info[0] >= 3:
|
||||
print "and will not run on Python 3.0 or later"
|
||||
sys.exit(1)
|
||||
|
||||
isBareConsole = False
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
try:
|
||||
import ctypes
|
||||
GetConsoleProcessList = ctypes.windll.kernel32.GetConsoleProcessList
|
||||
num = GetConsoleProcessList(ctypes.byref(ctypes.c_int(0)), ctypes.c_int(1))
|
||||
if (num == 1):
|
||||
isBareConsole = True
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
@@ -44,110 +32,13 @@ import subprocess
|
||||
import multiprocessing
|
||||
import time
|
||||
import logging
|
||||
from overviewer_core import util
|
||||
|
||||
def doExit(msg=None, code=1, wait=None, consoleMsg=True):
|
||||
'''Exits Overviewer. If `wait` is None, the default
|
||||
will be true is 'isBareConsole' is true'''
|
||||
global isBareConsole
|
||||
if msg:
|
||||
print msg
|
||||
|
||||
if wait == None:
|
||||
if isBareConsole:
|
||||
if consoleMsg:
|
||||
print "\n"
|
||||
print "The Overviewer is a console program. Please open a Windows command prompt"
|
||||
print "first and run Overviewer from there. Further documentation is available at"
|
||||
print "http://docs.overviewer.org/\n"
|
||||
print "Press [Enter] to close this window."
|
||||
raw_input()
|
||||
else:
|
||||
if wait:
|
||||
if consoleMsg:
|
||||
print "\n"
|
||||
print "The Overviewer is a console program. Please open a Windows command prompt"
|
||||
print "first and run Overviewer from there. Further documentation is available at"
|
||||
print "http://docs.overviewer.org/\n"
|
||||
print "Press [Enter] to close this window."
|
||||
raw_input()
|
||||
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
this_dir = util.get_program_path()
|
||||
|
||||
# make sure the c_overviewer extension is available
|
||||
try:
|
||||
from overviewer_core import c_overviewer
|
||||
except ImportError:
|
||||
## if this is a frozen windows package, the following error messages about
|
||||
## building the c_overviewer extension are not appropriate
|
||||
if hasattr(sys, "frozen"):
|
||||
print "Something has gone wrong importing the c_overviewer extension. Please"
|
||||
print "make sure the 2008 and 2010 redistributable packages from Microsoft"
|
||||
print "are installed."
|
||||
doExit()
|
||||
|
||||
|
||||
## try to find the build extension
|
||||
ext = os.path.join(this_dir, "overviewer_core", "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so"))
|
||||
if os.path.exists(ext):
|
||||
print "Something has gone wrong importing the c_overviewer extension. Please"
|
||||
print "make sure it is up-to-date (clean and rebuild)"
|
||||
doExit()
|
||||
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print ""
|
||||
print "You need to compile the c_overviewer module to run Minecraft Overviewer."
|
||||
print "Run `python setup.py build`, or see the README for details."
|
||||
doExit()
|
||||
|
||||
from overviewer_core import textures
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
pass # we don't bother with a compat test since it should always be in sync
|
||||
elif "extension_version" in dir(c_overviewer):
|
||||
# check to make sure the binary matches the headers
|
||||
if os.path.exists(os.path.join(this_dir, "overviewer_core", "src", "overviewer.h")):
|
||||
with open(os.path.join(this_dir, "overviewer_core", "src", "overviewer.h")) as f:
|
||||
lines = f.readlines()
|
||||
lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines)
|
||||
if lines:
|
||||
l = lines[0]
|
||||
if int(l.split()[2].strip()) != c_overviewer.extension_version():
|
||||
print "Please rebuild your c_overviewer module. It is out of date!"
|
||||
doExit(code=1, consoleMsg=True)
|
||||
else:
|
||||
print "Please rebuild your c_overviewer module. It is out of date!"
|
||||
doExit()
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
from overviewer_core import util
|
||||
from overviewer_core import textures
|
||||
from overviewer_core import optimizeimages, world
|
||||
#from overviewer_core import googlemap
|
||||
from overviewer_core import configParser, tileset, assetmanager, dispatcher
|
||||
|
||||
# definitions of built-in custom modes
|
||||
# usually because what used to be a mode became an option
|
||||
# for example, night mode
|
||||
builtin_custom_rendermodes = {}
|
||||
# 'night' : {
|
||||
# 'parent' : 'lighting',
|
||||
# 'label' : 'Night',
|
||||
# 'description' : 'like "lighting", except at night',
|
||||
# 'options' : {'night' : True}
|
||||
# },
|
||||
|
||||
# 'smooth-night' : {
|
||||
# 'parent' : 'smooth-lighting',
|
||||
# 'label' : 'Smooth Night',
|
||||
# 'description' : 'like "lighting", except smooth and at night',
|
||||
# 'options' : {'night' : True}
|
||||
# },
|
||||
# }
|
||||
|
||||
helptext = """
|
||||
%prog [OPTIONS] <World # / Name / Path to World> <tiles dest dir>"""
|
||||
|
||||
@@ -195,7 +86,6 @@ def configure_logger(loglevel=logging.INFO, verbose=False):
|
||||
logger.setLevel(loglevel)
|
||||
|
||||
def main():
|
||||
|
||||
# bootstrap the logger with defaults
|
||||
configure_logger()
|
||||
|
||||
@@ -216,7 +106,6 @@ def main():
|
||||
#parser.add_option("--forcerender", dest="forcerender", help="Force re-rendering the entire map (or the given regionlist). Useful for re-rendering without deleting it.", action="store_true")
|
||||
#parser.add_option("--stochastic-render", dest="stochastic_render", help="Rerender a non-updated tile randomly, with the given probability (between 0 and 1). Useful for incrementally updating a map with a new mode.", type="float", advanced=True, default=0.0, metavar="PROBABILITY")
|
||||
#parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by ',', ':', or '/'. Use --list-rendermodes to list them all.", type="choice", required=True, default=avail_rendermodes[0], listify=True)
|
||||
parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.")
|
||||
#parser.add_option("--rendermode-options", dest="rendermode_options", default={}, advanced=True, help="Used to specify options for different rendermodes. Only useful in a settings.py file")
|
||||
#parser.add_option("--custom-rendermodes", dest="custom_rendermodes", default={}, advanced=True, help="Used to define custom rendermodes. Only useful in a settings.py file")
|
||||
#parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.", advanced=True )
|
||||
@@ -253,59 +142,54 @@ def main():
|
||||
print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS)
|
||||
except ImportError:
|
||||
print "(build info not found)"
|
||||
pass
|
||||
doExit(code=0, consoleMsg=False)
|
||||
|
||||
# setup c_overviewer rendermode customs / options
|
||||
for mode in builtin_custom_rendermodes:
|
||||
c_overviewer.add_custom_render_mode(mode, builtin_custom_rendermodes[mode])
|
||||
# TODO allow custom rendermodes to be specified in a settings.py file
|
||||
#for mode in options.custom_rendermodes:
|
||||
# c_overviewer.add_custom_render_mode(mode, options.custom_rendermodes[mode])
|
||||
#for mode in options.rendermode_options:
|
||||
# c_overviewer.set_render_mode_options(mode, options.rendermode_options[mode])
|
||||
|
||||
|
||||
|
||||
if options.list_rendermodes:
|
||||
list_rendermodes()
|
||||
doExit(code=0, consoleMsg=False)
|
||||
return 0
|
||||
|
||||
if options.check_terrain:
|
||||
import hashlib
|
||||
from overviewer_core.textures import _find_file
|
||||
if options.textures_path:
|
||||
textures._find_file_local_path = options.textures_path
|
||||
from overviewer_core.textures import Textures
|
||||
# TODO custom textures path?
|
||||
tex = Textures()
|
||||
|
||||
try:
|
||||
f = _find_file("terrain.png", verbose=True)
|
||||
f = tex.find_file("terrain.png", verbose=True)
|
||||
except IOError:
|
||||
logging.error("Could not find the file terrain.png")
|
||||
doExit(code=1, consoleMsg=False)
|
||||
return 1
|
||||
|
||||
h = hashlib.sha1()
|
||||
h.update(f.read())
|
||||
logging.info("Hash of terrain.png file is: `%s`", h.hexdigest())
|
||||
doExit(code=0, consoleMsg=False)
|
||||
return 0
|
||||
|
||||
if options.display_config:
|
||||
# just display the config file and exit
|
||||
parser.display_config()
|
||||
return 0
|
||||
|
||||
# TODO remove advanced help? needs discussion
|
||||
#if options.advanced_help:
|
||||
# parser.advanced_help()
|
||||
# doExit(code=0, consoleMsg=False)
|
||||
|
||||
# TODO right now, we will not let users specify worlds to render on the command line.
|
||||
# TODO in the future, we need to also let worlds be specified on the command line
|
||||
|
||||
# if no arguments are provided, print out a helpful message
|
||||
if len(args) == 0:
|
||||
# first provide an appropriate error for bare-console users
|
||||
# that don't provide any options
|
||||
if util.is_bare_console():
|
||||
print "\n"
|
||||
print "The Overviewer is a console program. Please open a Windows command prompt"
|
||||
print "first and run Overviewer from there. Further documentation is available at"
|
||||
print "http://docs.overviewer.org/\n"
|
||||
else:
|
||||
# more helpful message for users who know what they're doing
|
||||
logging.error("You need to give me your world number or directory")
|
||||
parser.print_help()
|
||||
list_worlds()
|
||||
return 1
|
||||
|
||||
# for multiworld, we must specify the *outputdir* on the command line
|
||||
if len(args) == 1:
|
||||
elif len(args) == 1:
|
||||
logging.debug("Using %r as the output_directory", args[0])
|
||||
destdir = os.path.expanduser(args[0])
|
||||
|
||||
elif len(args) < 1:
|
||||
logging.error("You need to give me your world number or directory")
|
||||
parser.print_help()
|
||||
list_worlds()
|
||||
doExit(code=1, consoleMsg=True)
|
||||
elif len(args) == 2: # TODO support this usecase
|
||||
worlddir = os.path.expanduser(args[0])
|
||||
destdir = os.path.expanduser(args[1])
|
||||
@@ -319,14 +203,7 @@ def main():
|
||||
if os.path.exists(" ".join(args[start:end])):
|
||||
logging.warning("It looks like you meant to specify \"%s\" as your world dir or your output\n\
|
||||
dir but you forgot to put quotes around the directory, since it contains spaces." % " ".join(args[start:end]))
|
||||
doExit(code=1, consoleMsg=False)
|
||||
|
||||
|
||||
if options.display_config:
|
||||
# just display the config file and exit
|
||||
parser.display_config()
|
||||
doExit(code=0, consoleMsg=False)
|
||||
|
||||
return 1
|
||||
|
||||
# TODO regionlists are per-world
|
||||
#if options.regionlist:
|
||||
@@ -363,11 +240,11 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
|
||||
# except IOError as e:
|
||||
# logging.error("Unable to open file %s to use for changelist." % options.changelist)
|
||||
# logging.error("I/O Error: %s" % e.strerror)
|
||||
# doExit(code=1, consoleMsg=False)
|
||||
# return 1
|
||||
|
||||
#if options.changelist_format != "auto" and not options.changelist:
|
||||
# logging.error("changelist_format specified without changelist.")
|
||||
# doExit(code=1, consoleMsg=False)
|
||||
# return 1
|
||||
#if options.changelist_format == "auto":
|
||||
# options.changelist_format = "relative"
|
||||
|
||||
@@ -382,7 +259,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
|
||||
tex.generate()
|
||||
except IOError, e:
|
||||
logging.error(str(e))
|
||||
doExit(code=1, consoleMsg=False)
|
||||
return 1
|
||||
|
||||
# look at our settings.py file
|
||||
mw_parser = configParser.MultiWorldParser("settings.py")
|
||||
@@ -429,127 +306,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
|
||||
dispatch.close()
|
||||
|
||||
assetMrg.finalize(tilesets)
|
||||
|
||||
sys.exit("early abort")
|
||||
|
||||
# First do world-level preprocessing. This scans the world hierarchy, reads
|
||||
# in the region files and caches chunk modified times, and determines the
|
||||
# chunk bounds (max and min in both dimensions)
|
||||
w = world.World(worlddir)
|
||||
rset = w.get_regionsets()[0] # use the default
|
||||
if north_direction == 'auto':
|
||||
# TODO get this from the asset manager # north_direction = w.persistentData['north_direction']
|
||||
options.north_direction = north_direction
|
||||
|
||||
# TODO deal with changed north direction
|
||||
|
||||
# A couple other things we need to figure out about the world:
|
||||
w.get_regionsets()[0].determine_bounds()
|
||||
# w.find_true_spawn()
|
||||
|
||||
logging.info("Rendering the following tilesets: %s", ",".join(options.rendermode))
|
||||
|
||||
bgcolor = (int(options.bg_color[1:3],16),
|
||||
int(options.bg_color[3:5],16),
|
||||
int(options.bg_color[5:7],16),
|
||||
0)
|
||||
|
||||
# Create the quadtrees. There is one quadtree per rendermode requested, and
|
||||
# therefore, per output directory hierarchy of tiles. Each quadtree
|
||||
# individually computes its depth and size. The constructor computes the
|
||||
# depth of the tree, while the go() method re-arranges tiles if the current
|
||||
# depth differs from the computed depth.
|
||||
q = []
|
||||
qtree_args = {'depth' : options.zoom,
|
||||
'imgformat' : imgformat,
|
||||
'imgquality' : options.imgquality,
|
||||
'optimizeimg' : optimizeimg,
|
||||
'bgcolor' : bgcolor,
|
||||
'forcerender' : options.forcerender,
|
||||
'rerender_prob' : options.stochastic_render
|
||||
}
|
||||
for rendermode in options.rendermode:
|
||||
if rendermode == 'normal':
|
||||
qtree = quadtree.QuadtreeGen(rset, destdir, rendermode=rendermode, tiledir='tiles', **qtree_args)
|
||||
else:
|
||||
qtree = quadtree.QuadtreeGen(rset, destdir, rendermode=rendermode, **qtree_args)
|
||||
q.append(qtree)
|
||||
|
||||
# Make sure the quadtrees are the correct depth
|
||||
for qtree in q:
|
||||
qtree.check_depth()
|
||||
|
||||
# create the distributed render
|
||||
r = rendernode.RenderNode(q, options)
|
||||
# for the pool_initializer
|
||||
r.builtin_custom_rendermodes = builtin_custom_rendermodes
|
||||
|
||||
# write out the map and web assets
|
||||
m = googlemap.MapGen(q, configInfo=options)
|
||||
m.go(options.procs)
|
||||
|
||||
# render the tiles!
|
||||
r.go(options.procs)
|
||||
|
||||
# finish up the map
|
||||
m.finalize()
|
||||
|
||||
if options.changelist:
|
||||
changed=[]
|
||||
for tile in r.rendered_tiles:
|
||||
if options.changelist_format=="absolute":
|
||||
tile=os.path.abspath(tile)
|
||||
changed.append(tile)
|
||||
for zl in range(q[0].p - 1):
|
||||
tile=os.path.dirname(tile)
|
||||
changed.append("%s.%s" % (tile, imgformat))
|
||||
#Quick and nasty way to remove duplicate entries
|
||||
changed=list(set(changed))
|
||||
changed.sort()
|
||||
for path in changed:
|
||||
changefile.write("%s\n" % path)
|
||||
changefile.close()
|
||||
|
||||
def list_rendermodes():
|
||||
"Prints out a pretty list of supported rendermodes"
|
||||
|
||||
def print_mode_tree(line_max, mode, prefix='', last=False):
|
||||
"Prints out a mode tree for the given mode, with an indent."
|
||||
|
||||
try:
|
||||
info = c_overviewer.get_render_mode_info(mode)
|
||||
except ValueError:
|
||||
info = {}
|
||||
|
||||
print prefix + '+-', mode,
|
||||
|
||||
if 'description' in info:
|
||||
print " " * (line_max - len(prefix) - len(mode) - 2),
|
||||
print info['description']
|
||||
else:
|
||||
print
|
||||
|
||||
children = c_overviewer.get_render_mode_children(mode)
|
||||
for child in children:
|
||||
child_last = (child == children[-1])
|
||||
if last:
|
||||
child_prefix = ' '
|
||||
else:
|
||||
child_prefix = '| '
|
||||
print_mode_tree(line_max, child, prefix=prefix + child_prefix, last=child_last)
|
||||
|
||||
avail_rendermodes = c_overviewer.get_render_modes()
|
||||
line_lengths = {}
|
||||
parent_modes = []
|
||||
for mode in avail_rendermodes:
|
||||
inherit = c_overviewer.get_render_mode_inheritance(mode)
|
||||
if not inherit[0] in parent_modes:
|
||||
parent_modes.append(inherit[0])
|
||||
line_lengths[mode] = 2 * len(inherit) + 1 + len(mode)
|
||||
|
||||
line_length = max(line_lengths.values())
|
||||
for mode in parent_modes:
|
||||
print_mode_tree(line_length, mode, last=(mode == parent_modes[-1]))
|
||||
return 0
|
||||
|
||||
def list_worlds():
|
||||
"Prints out a brief summary of saves found in the default directory"
|
||||
@@ -582,18 +339,14 @@ def list_worlds():
|
||||
size = "%.2fMB" % (info['SizeOnDisk'] / 1024. / 1024.)
|
||||
print formatString % (name, size, playstamp, timestamp)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
multiprocessing.freeze_support()
|
||||
try:
|
||||
main()
|
||||
ret = main()
|
||||
util.exit(ret)
|
||||
except Exception, e:
|
||||
print e
|
||||
if e.args[0] == "Exiting":
|
||||
logging.info("Exiting...")
|
||||
doExit(code=0, wait=False)
|
||||
logging.exception("""An error has occurred. This may be a bug. Please let us know!
|
||||
See http://docs.overviewer.org/en/latest/index.html#help
|
||||
|
||||
This is the error that occurred:""")
|
||||
doExit(code=1, consoleMsg=False)
|
||||
util.exit(1)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# Code to check to make sure c_overviewer is built and working
|
||||
#
|
||||
|
||||
import os.path
|
||||
import platform
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import util
|
||||
|
||||
def check_c_overviewer():
|
||||
"""Check to make sure c_overviewer works and is up-to-date. Prints
|
||||
out a helpful error and returns 1 if something's wrong, returns 0
|
||||
otherwise.
|
||||
"""
|
||||
root_dir = util.get_program_path()
|
||||
# make sure the c_overviewer extension is available
|
||||
try:
|
||||
import c_overviewer
|
||||
except ImportError:
|
||||
## if this is a frozen windows package, the following error messages about
|
||||
## building the c_overviewer extension are not appropriate
|
||||
if hasattr(sys, "frozen") and platform.system() == 'Windows':
|
||||
print "Something has gone wrong importing the c_overviewer extension. Please"
|
||||
print "make sure the 2008 and 2010 redistributable packages from Microsoft"
|
||||
print "are installed."
|
||||
return 1
|
||||
|
||||
## try to find the build extension
|
||||
ext = os.path.join(root_dir, "overviewer_core", "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so"))
|
||||
if os.path.exists(ext):
|
||||
traceback.print_exc()
|
||||
print ""
|
||||
print "Something has gone wrong importing the c_overviewer extension. Please"
|
||||
print "make sure it is up-to-date (clean and rebuild)"
|
||||
return 1
|
||||
|
||||
print "You need to compile the c_overviewer module to run Minecraft Overviewer."
|
||||
print "Run `python setup.py build`, or see the README for details."
|
||||
return 1
|
||||
|
||||
#
|
||||
# make sure it's up-to-date
|
||||
#
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
pass # we don't bother with a compat test since it should always be in sync
|
||||
elif "extension_version" in dir(c_overviewer):
|
||||
# check to make sure the binary matches the headers
|
||||
if os.path.exists(os.path.join(root_dir, "overviewer_core", "src", "overviewer.h")):
|
||||
with open(os.path.join(root_dir, "overviewer_core", "src", "overviewer.h")) as f:
|
||||
lines = f.readlines()
|
||||
lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines)
|
||||
if lines:
|
||||
l = lines[0]
|
||||
if int(l.split()[2].strip()) != c_overviewer.extension_version():
|
||||
print "Please rebuild your c_overviewer module. It is out of date!"
|
||||
return 1
|
||||
else:
|
||||
print "Please rebuild your c_overviewer module. It is out of date!"
|
||||
return 1
|
||||
|
||||
# all good!
|
||||
return 0
|
||||
|
||||
# only check the module if we're not setup.py
|
||||
if not sys.argv[0].endswith("setup.py"):
|
||||
ret = check_c_overviewer()
|
||||
if ret > 0:
|
||||
util.exit(ret)
|
||||
|
||||
@@ -21,6 +21,7 @@ import imp
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import platform
|
||||
from subprocess import Popen, PIPE
|
||||
import logging
|
||||
from cStringIO import StringIO
|
||||
@@ -88,6 +89,33 @@ def findGitVersion():
|
||||
except Exception:
|
||||
return "unknown"
|
||||
|
||||
def is_bare_console():
|
||||
"""Returns true if Overviewer is running in a bare console in
|
||||
Windows, that is, if overviewer wasn't started in a cmd.exe
|
||||
session.
|
||||
"""
|
||||
if platform.system() == 'Windows':
|
||||
try:
|
||||
import ctypes
|
||||
GetConsoleProcessList = ctypes.windll.kernel32.GetConsoleProcessList
|
||||
num = GetConsoleProcessList(ctypes.byref(ctypes.c_int(0)), ctypes.c_int(1))
|
||||
if (num == 1):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
def exit(ret=0):
|
||||
"""Drop-in replacement for sys.exit that will automatically detect
|
||||
bare consoles and wait for user input before closing.
|
||||
"""
|
||||
if ret and is_bare_console():
|
||||
print
|
||||
print "Press [Enter] to close this window."
|
||||
raw_input()
|
||||
sys.exit(ret)
|
||||
|
||||
# http://docs.python.org/library/itertools.html
|
||||
def roundrobin(iterables):
|
||||
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
|
||||
|
||||
Reference in New Issue
Block a user