Пример #1
0
def generate_version_py():
    try:
        outstr = ""
        outstr += "VERSION=%r\n" % util.findGitVersion()
        outstr += "HASH=%r\n" % util.findGitHash()
        outstr += "BUILD_DATE=%r\n" % time.asctime()
        outstr += "BUILD_PLATFORM=%r\n" % platform.processor()
        outstr += "BUILD_OS=%r\n" % platform.platform()
        f = open("overviewer_core/overviewer_version.py", "w")
        f.write(outstr)
        f.close()
    except Exception:
        print("WARNING: failed to build overviewer_version file")
Пример #2
0
def generate_version_py():
    try:
        outstr = ""
        outstr += "VERSION=%r\n" % util.findGitVersion()
        outstr += "HASH=%r\n" % util.findGitHash()
        outstr += "BUILD_DATE=%r\n" % time.asctime()
        outstr += "BUILD_PLATFORM=%r\n" % platform.processor()
        outstr += "BUILD_OS=%r\n" % platform.platform()
        f = open("overviewer_core/overviewer_version.py", "w")
        f.write(outstr)
        f.close()
    except:
        print "WARNING: failed to build overviewer_version file"
Пример #3
0
def main():
    # bootstrap the logger with defaults
    logger.configure()

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    #avail_rendermodes = c_overviewer.get_render_modes()
    avail_north_dirs = [
        'lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto'
    ]

    # Parse for basic options
    parser = OptionParser(usage=helptext, add_help_option=False)
    parser.add_option("-h",
                      "--help",
                      dest="help",
                      action="store_true",
                      help="show this help message and exit")
    parser.add_option("-c",
                      "--config",
                      dest="config",
                      action="store",
                      help="Specify the config file to use.")
    parser.add_option(
        "-p",
        "--processes",
        dest="procs",
        action="store",
        type="int",
        help=
        "The number of local worker processes to spawn. Defaults to the number of CPU cores your computer has"
    )

    parser.add_option("--pid",
                      dest="pid",
                      action="store",
                      help="Specify the pid file to use.")
    # Options that only apply to the config-less render usage
    parser.add_option(
        "--rendermodes",
        dest="rendermodes",
        action="store",
        help=
        "If you're not using a config file, specify which rendermodes to render with this option. This is a comma-separated list."
    )

    # Useful one-time render modifiers:
    parser.add_option("--forcerender",
                      dest="forcerender",
                      action="store_true",
                      help="Force re-rendering the entire map.")
    parser.add_option("--check-tiles",
                      dest="checktiles",
                      action="store_true",
                      help="Check each tile on disk and re-render old tiles")
    parser.add_option(
        "--no-tile-checks",
        dest="notilechecks",
        action="store_true",
        help=
        "Only render tiles that come from chunks that have changed since the last render (the default)"
    )

    # Useful one-time debugging options:
    parser.add_option(
        "--check-terrain",
        dest="check_terrain",
        action="store_true",
        help=
        "Tries to locate the texture files. Useful for debugging texture problems."
    )
    parser.add_option("-V",
                      "--version",
                      dest="version",
                      help="Displays version information and then exits",
                      action="store_true")
    parser.add_option(
        "--update-web-assets",
        dest='update_web_assets',
        action="store_true",
        help=
        "Update web assets. Will *not* render tiles or update overviewerConfig.js"
    )

    # Log level options:
    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.")
    parser.add_option(
        "--simple-output",
        dest="simple",
        action="store_true",
        default=False,
        help="Use a simple output format, with no colors or progress bars")

    # create a group for "plugin exes" (the concept of a plugin exe is only loosly defined at this point)
    exegroup = OptionGroup(
        parser, "Other Scripts",
        "These scripts may accept different arguments than the ones listed above"
    )
    exegroup.add_option("--genpoi",
                        dest="genpoi",
                        action="store_true",
                        help="Runs the genPOI script")
    exegroup.add_option("--skip-scan",
                        dest="skipscan",
                        action="store_true",
                        help="When running GenPOI, don't scan for entities")

    parser.add_option_group(exegroup)

    options, args = parser.parse_args()

    # first thing to do is check for stuff in the exegroup:
    if options.genpoi:
        # remove the "--genpoi" option from sys.argv before running genPI
        sys.argv.remove("--genpoi")
        #sys.path.append(".")
        g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
        g.genPOI.main()
        return 0
    if options.help:
        parser.print_help()
        return 0

    # re-configure the logger now that we've processed the command line options
    logger.configure(logging.INFO + 10 * options.quiet - 10 * options.verbose,
                     verbose=options.verbose > 0,
                     simple=options.simple)

    ##########################################################################
    # This section of main() runs in response to any one-time options we have,
    # such as -V for version reporting
    if options.version:
        print("Minecraft Overviewer %s" % util.findGitVersion()),
        print("(%s)" % util.findGitHash()[:7])
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print("built on %s" % overviewer_version.BUILD_DATE)
            if options.verbose > 0:
                print("Build machine: %s %s" %
                      (overviewer_version.BUILD_PLATFORM,
                       overviewer_version.BUILD_OS))
                print("Read version information from %r" %
                      overviewer_version.__file__)
        except ImportError:
            print("(build info not found)")
        if options.verbose > 0:
            print("Python executable: %r" % sys.executable)
            print(sys.version)
        return 0

    if options.pid:
        if os.path.exists(options.pid):
            try:
                with open(options.pid, 'r') as fpid:
                    pid = int(fpid.read())
                    if util.pid_exists(pid):
                        print("Already running (pid exists) - exiting..")
                        return 0
            except (IOError, ValueError):
                pass
        with open(options.pid, "w") as f:
            f.write(str(os.getpid()))
    # if --check-terrain was specified, but we have NO config file, then we cannot
    # operate on a custom texture path.  we do terrain checking with a custom texture
    # pack later on, after we've parsed the config file
    if options.check_terrain and not options.config:
        import hashlib
        from overviewer_core.textures import Textures
        tex = Textures()

        logging.info("Looking for a few common texture files...")
        try:
            f = tex.find_file(
                "assets/minecraft/textures/blocks/sandstone_top.png",
                verbose=True)
            f = tex.find_file("assets/minecraft/textures/blocks/grass_top.png",
                              verbose=True)
            f = tex.find_file(
                "assets/minecraft/textures/blocks/diamond_ore.png",
                verbose=True)
            f = tex.find_file(
                "assets/minecraft/textures/blocks/planks_acacia.png",
                verbose=True)
        except IOError:
            logging.error("Could not find any texture files.")
            return 1

        return 0

    # if no arguments are provided, print out a helpful message
    if len(args) == 0 and not options.config:
        # 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")
            print("\n")
            print(
                "For a quick-start guide on Windows, visit the following URL:\n"
            )
            print(
                "http://docs.overviewer.org/en/latest/win_tut/windowsguide/\n")

        else:
            # more helpful message for users who know what they're doing
            logging.error(
                "You must either specify --config or give me a world directory and output directory"
            )
            parser.print_help()
            list_worlds()
        return 1

    ##########################################################################
    # This section does some sanity checking on the command line options passed
    # in. It checks to see if --config was given that no worldname/destdir were
    # given, and vice versa
    if options.config and args:
        print()
        print(
            "If you specify --config, you need to specify the world to render as well as"
        )
        print("the destination in the config file, not on the command line.")
        print("Put something like this in your config file:")
        print("worlds['myworld'] = %r" % args[0])
        print("outputdir = %r" %
              (args[1] if len(args) > 1 else "/path/to/output"))
        print()
        logging.error(
            "Cannot specify both --config AND a world + output directory on the command line."
        )
        parser.print_help()
        return 1

    if not options.config and len(args) < 2:
        logging.error(
            "You must specify both the world directory and an output directory"
        )
        parser.print_help()
        return 1
    if not options.config and len(args) > 2:
        # it's possible the user has a space in one of their paths but didn't
        # properly escape it attempt to detect this case
        for start in range(len(args)):
            if not os.path.exists(args[start]):
                for end in range(start + 1, len(args) + 1):
                    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]))
                        return 1
        logging.error("Too many command line arguments")
        parser.print_help()
        return 1

    #########################################################################
    # These two halfs of this if statement unify config-file mode and
    # command-line mode.
    mw_parser = configParser.MultiWorldParser()

    if not options.config:
        # No config file mode.
        worldpath, destdir = map(os.path.expanduser, args)
        logging.debug("Using %r as the world directory", worldpath)
        logging.debug("Using %r as the output directory", destdir)

        mw_parser.set_config_item("worlds", {'world': worldpath})
        mw_parser.set_config_item("outputdir", destdir)

        rendermodes = ['lighting']
        if options.rendermodes:
            rendermodes = options.rendermodes.replace("-", "_").split(",")

        # Now for some good defaults
        renders = util.OrderedDict()
        for rm in rendermodes:
            renders["world-" + rm] = {
                "world": "world",
                "title": "Overviewer Render (%s)" % rm,
                "rendermode": rm,
            }
        mw_parser.set_config_item("renders", renders)

    else:
        if options.rendermodes:
            logging.error(
                "You cannot specify --rendermodes if you give a config file. Configure your rendermodes in the config file instead"
            )
            parser.print_help()
            return 1

        # Parse the config file
        try:
            mw_parser.parse(os.path.expanduser(options.config))
        except configParser.MissingConfigException as e:
            # this isn't a "bug", so don't print scary traceback
            logging.error(str(e))
            util.nice_exit(1)

    # Add in the command options here, perhaps overriding values specified in
    # the config
    if options.procs:
        mw_parser.set_config_item("processes", options.procs)

    # Now parse and return the validated config
    try:
        config = mw_parser.get_validated_config()
    except Exception as ex:
        if options.verbose:
            logging.exception(
                "An error was encountered with your configuration. See the info below."
            )
        else:  # no need to print scary traceback! just
            logging.error("An error was encountered with your configuration.")
            logging.error(str(ex))
        return 1

    if options.check_terrain:  # we are already in the "if configfile" branch
        logging.info("Looking for a few common texture files...")
        for render_name, render in config['renders'].iteritems():
            logging.info("Looking at render %r", render_name)

            # find or create the textures object
            texopts = util.dict_subset(render, ["texturepath"])

            tex = textures.Textures(**texopts)
            f = tex.find_file(
                "assets/minecraft/textures/blocks/sandstone_top.png",
                verbose=True)
            f = tex.find_file("assets/minecraft/textures/blocks/grass_top.png",
                              verbose=True)
            f = tex.find_file(
                "assets/minecraft/textures/blocks/diamond_ore.png",
                verbose=True)
            f = tex.find_file(
                "assets/minecraft/textures/blocks/planks_oak.png",
                verbose=True)
        return 0

    ############################################################
    # Final validation steps and creation of the destination directory
    logging.info("Welcome to Minecraft Overviewer!")
    logging.debug("Current log level: {0}".format(logging.getLogger().level))

    # Override some render configdict options depending on one-time command line
    # modifiers
    if (bool(options.forcerender) + bool(options.checktiles) +
            bool(options.notilechecks)) > 1:
        logging.error(
            "You cannot specify more than one of --forcerender, " +
            "--check-tiles, and --no-tile-checks. These options conflict.")
        parser.print_help()
        return 1
    if options.forcerender:
        logging.info("Forcerender mode activated. ALL tiles will be rendered")
        for render in config['renders'].itervalues():
            render['renderchecks'] = 2
    elif options.checktiles:
        logging.info("Checking all tiles for updates manually.")
        for render in config['renders'].itervalues():
            render['renderchecks'] = 1
    elif options.notilechecks:
        logging.info("Disabling all tile mtime checks. Only rendering tiles " +
                     "that need updating since last render")
        for render in config['renders'].itervalues():
            render['renderchecks'] = 0

    if not config['renders']:
        logging.error(
            "You must specify at least one render in your config file. See the docs if you're having trouble"
        )
        return 1

    #####################
    # Do a few last minute things to each render dictionary here
    for rname, render in config['renders'].iteritems():
        # Convert render['world'] to the world path, and store the original
        # in render['worldname_orig']
        try:
            worldpath = config['worlds'][render['world']]
        except KeyError:
            logging.error(
                "Render %s's world is '%s', but I could not find a corresponding entry in the worlds dictionary.",
                rname, render['world'])
            return 1
        render['worldname_orig'] = render['world']
        render['world'] = worldpath

        # If 'forcerender' is set, change renderchecks to 2
        if render.get('forcerender', False):
            render['renderchecks'] = 2

        # check if overlays are set, if so, make sure that those renders exist
        if render.get('overlay', []) != []:
            for x in render.get('overlay'):
                if x != rname:
                    try:
                        renderLink = config['renders'][x]
                    except KeyError:
                        logging.error(
                            "Render %s's overlay is '%s', but I could not find a corresponding entry in the renders dictionary.",
                            rname, x)
                        return 1
                else:
                    logging.error("Render %s's overlay contains itself.",
                                  rname)
                    return 1

    destdir = config['outputdir']
    if not destdir:
        logging.error(
            "You must specify the output directory in your config file.")
        logging.error("e.g. outputdir = '/path/to/outputdir'")
        return 1
    if not os.path.exists(destdir):
        try:
            os.mkdir(destdir)
        except OSError:
            logging.exception("Could not create the output directory.")
            return 1

    ########################################################################
    # Now we start the actual processing, now that all the configuration has
    # been gathered and validated
    # create our asset manager... ASSMAN
    assetMrg = assetmanager.AssetManager(destdir,
                                         config.get('customwebassets', None))

    # If we've been asked to update web assets, do that and then exit
    if options.update_web_assets:
        assetMrg.output_noconfig()
        logging.info("Web assets have been updated")
        return 0

    # The changelist support.
    changelists = {}
    for render in config['renders'].itervalues():
        if 'changelist' in render:
            path = render['changelist']
            if path not in changelists:
                out = open(path, "w")
                logging.debug("Opening changelist %s (%s)", out, out.fileno())
                changelists[path] = out
            else:
                out = changelists[path]
            render['changelist'] = out.fileno()

    tilesets = []

    # saves us from creating the same World object over and over again
    worldcache = {}
    # same for textures
    texcache = {}

    # Set up the cache objects to use
    caches = []
    caches.append(cache.LRUCache(size=100))
    if config.get("memcached_host", False):
        caches.append(cache.Memcached(config['memcached_host']))
    # TODO: optionally more caching layers here

    renders = config['renders']
    for render_name, render in renders.iteritems():
        logging.debug("Found the following render thing: %r", render)

        # find or create the world object
        try:
            w = worldcache[render['world']]
        except KeyError:
            w = world.World(render['world'])
            worldcache[render['world']] = w

        # find or create the textures object
        texopts = util.dict_subset(
            render, ["texturepath", "bgcolor", "northdirection"])
        texopts_key = tuple(texopts.items())
        if texopts_key not in texcache:
            tex = textures.Textures(**texopts)
            logging.info("Generating textures...")
            tex.generate()
            logging.debug("Finished generating textures")
            texcache[texopts_key] = tex
        else:
            tex = texcache[texopts_key]

        try:
            logging.debug("Asking for regionset %r" % render['dimension'][1])
            rset = w.get_regionset(render['dimension'][1])
        except IndexError:
            logging.error(
                "Sorry, I can't find anything to render!  Are you sure there are .mca files in the world directory?"
            )
            return 1
        if rset == None:  # indicates no such dimension was found:
            logging.error(
                "Sorry, you requested dimension '%s' for %s, but I couldn't find it",
                render['dimension'][0], render_name)
            return 1

        #################
        # Apply any regionset transformations here

        # Insert a layer of caching above the real regionset. Any world
        # tranformations will pull from this cache, but their results will not
        # be cached by this layer. This uses a common pool of caches; each
        # regionset cache pulls from the same underlying cache object.
        rset = world.CachedRegionSet(rset, caches)

        # If a crop is requested, wrap the regionset here
        if "crop" in render:
            rset = world.CroppedRegionSet(rset, *render['crop'])

        # If this is to be a rotated regionset, wrap it in a RotatedRegionSet
        # object
        if (render['northdirection'] > 0):
            rset = world.RotatedRegionSet(rset, render['northdirection'])
        logging.debug("Using RegionSet %r", rset)

        ###############################
        # Do the final prep and create the TileSet object

        # create our TileSet from this RegionSet
        tileset_dir = os.path.abspath(os.path.join(destdir, render_name))

        # only pass to the TileSet the options it really cares about
        render[
            'name'] = render_name  # perhaps a hack. This is stored here for the asset manager
        tileSetOpts = util.dict_subset(render, [
            "name", "imgformat", "renderchecks", "rerenderprob", "bgcolor",
            "defaultzoom", "imgquality", "optimizeimg", "rendermode",
            "worldname_orig", "title", "dimension", "changelist", "showspawn",
            "overlay", "base", "poititle", "maxzoom", "showlocationmarker",
            "minzoom"
        ])
        tileSetOpts.update({"spawn": w.find_true_spawn()
                            })  # TODO find a better way to do this
        tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts,
                               tileset_dir)
        tilesets.append(tset)

    # Do tileset preprocessing here, before we start dispatching jobs
    logging.info("Preprocessing...")
    for ts in tilesets:
        ts.do_preprocessing()

    # Output initial static data and configuration
    assetMrg.initialize(tilesets)

    # multiprocessing dispatcher
    if config['processes'] == 1:
        dispatch = dispatcher.Dispatcher()
    else:
        dispatch = dispatcher.MultiprocessingDispatcher(
            local_procs=config['processes'])
    dispatch.render_all(tilesets, config['observer'])
    dispatch.close()

    assetMrg.finalize(tilesets)

    for out in changelists.itervalues():
        logging.debug("Closing %s (%s)", out, out.fileno())
        out.close()

    if config['processes'] == 1:
        logging.debug("Final cache stats:")
        for c in caches:
            logging.debug("\t%s: %s hits, %s misses", c.__class__.__name__,
                          c.hits, c.misses)
    if options.pid:
        os.remove(options.pid)

    return 0
Пример #4
0
def main():
    # bootstrap the logger with defaults
    logger.configure()

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    #avail_rendermodes = c_overviewer.get_render_modes()
    avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto']

    # Parse for basic options
    parser = OptionParser(usage=helptext, add_help_option=False)
    parser.add_option("-h", "--help", dest="help", action="store_true",
            help="show this help message and exit")
    parser.add_option("-c", "--config", dest="config", action="store", help="Specify the config file to use.")
    parser.add_option("-p", "--processes", dest="procs", action="store", type="int",
            help="The number of local worker processes to spawn. Defaults to the number of CPU cores your computer has")

    # Options that only apply to the config-less render usage
    parser.add_option("--rendermodes", dest="rendermodes", action="store",
            help="If you're not using a config file, specify which rendermodes to render with this option. This is a comma-separated list.")

    # Useful one-time render modifiers:
    parser.add_option("--forcerender", dest="forcerender", action="store_true",
            help="Force re-rendering the entire map.")
    parser.add_option("--check-tiles", dest="checktiles", action="store_true",
            help="Check each tile on disk and re-render old tiles")
    parser.add_option("--no-tile-checks", dest="notilechecks", action="store_true",
            help="Only render tiles that come from chunks that have changed since the last render (the default)")

    # Useful one-time debugging options:
    parser.add_option("--check-terrain", dest="check_terrain", action="store_true",
            help="Prints the location and hash of terrain.png, useful for debugging terrain.png problems")
    parser.add_option("-V", "--version", dest="version",
            help="Displays version information and then exits", action="store_true")
    parser.add_option("--update-web-assets", dest='update_web_assets', action="store_true",
            help="Update web assets. Will *not* render tiles or update overviewerConfig.js")

    # Log level options:
    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.")
    parser.add_option("--simple-output", dest="simple", action="store_true", default=False,
            help="Use a simple output format, with no colors or progress bars")

    # create a group for "plugin exes" (the concept of a plugin exe is only loosly defined at this point)
    exegroup = OptionGroup(parser, "Other Scripts",
            "These scripts may accept different arguments than the ones listed above")
    exegroup.add_option("--genpoi", dest="genpoi", action="store_true",
            help="Runs the genPOI script")
    exegroup.add_option("--skip-scan", dest="skipscan", action="store_true",
            help="When running GenPOI, don't scan for entities")

    parser.add_option_group(exegroup)

    options, args = parser.parse_args()

    # first thing to do is check for stuff in the exegroup:
    if options.genpoi:
        # remove the "--genpoi" option from sys.argv before running genPI
        sys.argv.remove("--genpoi")
        #sys.path.append(".")
        g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
        g.genPOI.main()
        return 0
    if options.help:
        parser.print_help()
        return 0

    # re-configure the logger now that we've processed the command line options
    logger.configure(logging.INFO + 10*options.quiet - 10*options.verbose,
                     verbose=options.verbose > 0,
                     simple=options.simple)

    ##########################################################################
    # This section of main() runs in response to any one-time options we have,
    # such as -V for version reporting
    if options.version:
        print("Minecraft Overviewer %s" % util.findGitVersion()),
        print("(%s)" % util.findGitHash()[:7])
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print("built on %s" % overviewer_version.BUILD_DATE)
            if options.verbose > 0:
                print("Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS))
        except ImportError:
            print("(build info not found)")
        return 0

    # if --check-terrain was specified, but we have NO config file, then we cannot
    # operate on a custom texture path.  we do terrain checking with a custom texture
    # pack later on, after we've parsed the config file
    if options.check_terrain and not options.config:
        import hashlib
        from overviewer_core.textures import Textures
        tex = Textures()

        logging.info("Looking for a few common texture files...")
        try:
            f = tex.find_file("textures/blocks/stone.png", verbose=True)
            f = tex.find_file("textures/blocks/tallgrass.png", verbose=True)
            f = tex.find_file("textures/blocks/oreDiamond.png", verbose=True)
            f = tex.find_file("textures/blocks/wood.png", verbose=True)
        except IOError:
            logging.error("Could not find the file stone.png")
            return 1

        return 0

    # if no arguments are provided, print out a helpful message
    if len(args) == 0 and not options.config:
        # 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")
            print("\n")
            print("For a quick-start guide on Windows, visit the following URL:\n")
            print("http://docs.overviewer.org/en/latest/win_tut/windowsguide/\n")

        else:
            # more helpful message for users who know what they're doing
            logging.error("You must either specify --config or give me a world directory and output directory")
            parser.print_help()
            list_worlds()
        return 1

    ##########################################################################
    # This section does some sanity checking on the command line options passed
    # in. It checks to see if --config was given that no worldname/destdir were
    # given, and vice versa
    if options.config and args:
        print()
        print("If you specify --config, you need to specify the world to render as well as")
        print("the destination in the config file, not on the command line.")
        print("Put something like this in your config file:")
        print("worlds['myworld'] = %r" % args[0])
        print("outputdir = %r" % (args[1] if len(args) > 1 else "/path/to/output"))
        print()
        logging.error("Cannot specify both --config AND a world + output directory on the command line.")
        parser.print_help()
        return 1

    if not options.config and len(args) < 2:
        logging.error("You must specify both the world directory and an output directory")
        parser.print_help()
        return 1
    if not options.config and len(args) > 2:
        # it's possible the user has a space in one of their paths but didn't
        # properly escape it attempt to detect this case
        for start in range(len(args)):
            if not os.path.exists(args[start]):
                for end in range(start+1, len(args)+1):
                    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]))
                        return 1
        logging.error("Too many command line arguments")
        parser.print_help()
        return 1

    #########################################################################
    # These two halfs of this if statement unify config-file mode and
    # command-line mode.
    mw_parser = configParser.MultiWorldParser()

    if not options.config:
        # No config file mode.
        worldpath, destdir = map(os.path.expanduser, args)
        logging.debug("Using %r as the world directory", worldpath)
        logging.debug("Using %r as the output directory", destdir)

        mw_parser.set_config_item("worlds", {'world': worldpath})
        mw_parser.set_config_item("outputdir", destdir)

        rendermodes = ['lighting']
        if options.rendermodes:
            rendermodes = options.rendermodes.replace("-","_").split(",")

        # Now for some good defaults
        renders = util.OrderedDict()
        for rm in rendermodes:
            renders["world-" + rm] = {
                    "world": "world",
                    "title": "Overviewer Render (%s)" % rm,
                    "rendermode": rm,
                    }
        mw_parser.set_config_item("renders", renders)

    else:
        if options.rendermodes:
            logging.error("You cannot specify --rendermodes if you give a config file. Configure your rendermodes in the config file instead")
            parser.print_help()
            return 1

        # Parse the config file
        try:
            mw_parser.parse(os.path.expanduser(options.config))
        except configParser.MissingConfigException as e:
            # this isn't a "bug", so don't print scary traceback
            logging.error(str(e))
            util.nice_exit(1)

    # Add in the command options here, perhaps overriding values specified in
    # the config
    if options.procs:
        mw_parser.set_config_item("processes", options.procs)

    # Now parse and return the validated config
    try:
        config = mw_parser.get_validated_config()
    except Exception as ex:
        if options.verbose:
            logging.exception("An error was encountered with your configuration. See the info below.")
        else: # no need to print scary traceback! just
            logging.error("An error was encountered with your configuration.")
            logging.error(str(ex))
        return 1

    if options.check_terrain: # we are already in the "if configfile" branch
        logging.info("Looking for a few common texture files...")
        for render_name, render in config['renders'].iteritems():
            logging.info("Looking at render %r", render_name)

            # find or create the textures object
            texopts = util.dict_subset(render, ["texturepath"])

            tex = textures.Textures(**texopts)
            f = tex.find_file("textures/blocks/stone.png", verbose=True)
            f = tex.find_file("textures/blocks/tallgrass.png", verbose=True)
            f = tex.find_file("textures/blocks/oreDiamond.png", verbose=True)
            f = tex.find_file("textures/blocks/wood.png", verbose=True)
        return 0

    ############################################################
    # Final validation steps and creation of the destination directory
    logging.info("Welcome to Minecraft Overviewer!")
    logging.debug("Current log level: {0}".format(logging.getLogger().level))

    # Override some render configdict options depending on one-time command line
    # modifiers
    if (
            bool(options.forcerender) +
            bool(options.checktiles) +
            bool(options.notilechecks)
            ) > 1:
        logging.error("You cannot specify more than one of --forcerender, "+
        "--check-tiles, and --no-tile-checks. These options conflict.")
        parser.print_help()
        return 1
    if options.forcerender:
        logging.info("Forcerender mode activated. ALL tiles will be rendered")
        for render in config['renders'].itervalues():
            render['renderchecks'] = 2
    elif options.checktiles:
        logging.info("Checking all tiles for updates manually.")
        for render in config['renders'].itervalues():
            render['renderchecks'] = 1
    elif options.notilechecks:
        logging.info("Disabling all tile mtime checks. Only rendering tiles "+
        "that need updating since last render")
        for render in config['renders'].itervalues():
            render['renderchecks'] = 0

    if not config['renders']:
        logging.error("You must specify at least one render in your config file. See the docs if you're having trouble")
        return 1

    #####################
    # Do a few last minute things to each render dictionary here
    for rname, render in config['renders'].iteritems():
        # Convert render['world'] to the world path, and store the original
        # in render['worldname_orig']
        try:
            worldpath = config['worlds'][render['world']]
        except KeyError:
            logging.error("Render %s's world is '%s', but I could not find a corresponding entry in the worlds dictionary.",
                    rname, render['world'])
            return 1
        render['worldname_orig'] = render['world']
        render['world'] = worldpath

        # If 'forcerender' is set, change renderchecks to 2
        if render.get('forcerender', False):
            render['renderchecks'] = 2

        # check if overlays are set, if so, make sure that those renders exist
        if render.get('overlay', []) != []:
            for x in render.get('overlay'):
                if x != rname:
                    try:
                        renderLink = config['renders'][x]
                    except KeyError:
                        logging.error("Render %s's overlay is '%s', but I could not find a corresponding entry in the renders dictionary.",
                                rname, x)
                        return 1
                else:
                    logging.error("Render %s's overlay contains itself.", rname)
                    return 1

    destdir = config['outputdir']
    if not destdir:
        logging.error("You must specify the output directory in your config file.")
        logging.error("e.g. outputdir = '/path/to/outputdir'")
        return 1
    if not os.path.exists(destdir):
        try:
            os.mkdir(destdir)
        except OSError:
            logging.exception("Could not create the output directory.")
            return 1

    ########################################################################
    # Now we start the actual processing, now that all the configuration has
    # been gathered and validated
    # create our asset manager... ASSMAN
    assetMrg = assetmanager.AssetManager(destdir, config.get('customwebassets', None))

    # If we've been asked to update web assets, do that and then exit 
    if options.update_web_assets:
        assetMrg.output_noconfig()
        logging.info("Web assets have been updated")
        return 0

    # The changelist support.
    changelists = {}
    for render in config['renders'].itervalues():
        if 'changelist' in render:
            path = render['changelist']
            if path not in changelists:
                out = open(path, "w")
                logging.debug("Opening changelist %s (%s)", out, out.fileno())
                changelists[path] = out
            else:
                out = changelists[path]
            render['changelist'] = out.fileno()

    tilesets = []

    # saves us from creating the same World object over and over again
    worldcache = {}
    # same for textures
    texcache = {}

    # Set up the cache objects to use
    caches = []
    caches.append(cache.LRUCache(size=100))
    if config.get("memcached_host", False):
        caches.append(cache.Memcached(config['memcached_host']))
    # TODO: optionally more caching layers here

    renders = config['renders']
    for render_name, render in renders.iteritems():
        logging.debug("Found the following render thing: %r", render)

        # find or create the world object
        try:
            w = worldcache[render['world']]
        except KeyError:
            w = world.World(render['world'])
            worldcache[render['world']] = w

        # find or create the textures object
        texopts = util.dict_subset(render, ["texturepath", "bgcolor", "northdirection"])
        texopts_key = tuple(texopts.items())
        if texopts_key not in texcache:
            tex = textures.Textures(**texopts)
            tex.generate()
            texcache[texopts_key] = tex
        else:
            tex = texcache[texopts_key]
    
        try:
            rset = w.get_regionset(render['dimension'][1])
        except IndexError:
            logging.error("Sorry, I can't find anything to render!  Are you sure there are .mca files in the world directory?")
            return 1
        if rset == None: # indicates no such dimension was found:
            logging.error("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'][0], render_name)
            return 1

        #################
        # Apply any regionset transformations here

        # Insert a layer of caching above the real regionset. Any world
        # tranformations will pull from this cache, but their results will not
        # be cached by this layer. This uses a common pool of caches; each
        # regionset cache pulls from the same underlying cache object.
        rset = world.CachedRegionSet(rset, caches)

        # If a crop is requested, wrap the regionset here
        if "crop" in render:
            rset = world.CroppedRegionSet(rset, *render['crop'])

        # If this is to be a rotated regionset, wrap it in a RotatedRegionSet
        # object
        if (render['northdirection'] > 0):
            rset = world.RotatedRegionSet(rset, render['northdirection'])
        logging.debug("Using RegionSet %r", rset)

        ###############################
        # Do the final prep and create the TileSet object

        # create our TileSet from this RegionSet
        tileset_dir = os.path.abspath(os.path.join(destdir, render_name))

        # only pass to the TileSet the options it really cares about
        render['name'] = render_name # perhaps a hack. This is stored here for the asset manager
        tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "defaultzoom", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist", "showspawn", "overlay", "base", "poititle", "maxzoom"])
        tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this
        tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
        tilesets.append(tset)

    # Do tileset preprocessing here, before we start dispatching jobs
    for ts in tilesets:
        ts.do_preprocessing()

    # Output initial static data and configuration
    assetMrg.initialize(tilesets)

    # multiprocessing dispatcher
    if config['processes'] == 1:
        dispatch = dispatcher.Dispatcher()
    else:
        dispatch = dispatcher.MultiprocessingDispatcher(
            local_procs=config['processes'])
    dispatch.render_all(tilesets, config['observer'])
    dispatch.close()

    assetMrg.finalize(tilesets)

    for out in changelists.itervalues():
        logging.debug("Closing %s (%s)", out, out.fileno())
        out.close()

    if config['processes'] == 1:
        logging.debug("Final cache stats:")
        for c in caches:
            logging.debug("\t%s: %s hits, %s misses", c.__class__.__name__, c.hits, c.misses)

    return 0
Пример #5
0
setup_kwargs['cmdclass'] = {}
setup_kwargs['options'] = {}

#
# metadata
#

# Utility function to read the README file.  
# Used for the long_description.  It's nice, because now 1) we have a top level
# README file and 2) it's easier to type in the README file than to put a raw
# string in below ...
def read(fname):
    return open(os.path.join(os.path.dirname(__file__), fname)).read()

setup_kwargs['name'] = 'Minecraft-Overviewer'
setup_kwargs['version'] = util.findGitVersion()
setup_kwargs['description'] = 'Generates large resolution images of a Minecraft map.'
setup_kwargs['url'] = 'http://overviewer.org/'
setup_kwargs['author'] = 'Andrew Brown'
setup_kwargs['author_email'] = '*****@*****.**'
setup_kwargs['license'] = 'GNU General Public License v3'
setup_kwargs['long_description'] = read('README.rst')

# top-level files that should be included as documentation
doc_files = ['COPYING.txt', 'README.rst', 'CONTRIBUTORS.rst', 'sample.settings.py']

# helper to create a 'data_files'-type sequence recursively for a given dir
def recursive_data_files(src, dest=None):
    if dest is None:
        dest = src
    
def main():
    # bootstrap the logger with defaults
    logger.configure()

    if os.name == "posix":
        if os.geteuid() == 0:
            logging.warning("You are running Overviewer as root. "
                            "It is recommended that you never do this, "
                            "as it is dangerous for your system. If you are running "
                            "into permission errors, fix your file/directory "
                            "permissions instead. Overviewer does not need access to "
                            "critical system resources and therefore does not require "
                            "root access.")
        try:
            with open("/etc/redhat-release", "r") as release_f:
                rel_contents = release_f.read()
                try:
                    major_rel = re.search(r'\d(\.\d+)?', rel_contents).group(0).split('.')[0]
                    if major_rel == "6":
                        logging.warning(
                            "We will be dropping support for this release of your distribution "
                            "soon. Please upgrade as soon as possible, or you will not receive "
                            "future Overviewer updates.")
                except AttributeError:
                    pass
        except IOError:
            pass

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto']

    # Parse for basic options
    parser = ArgumentParser(usage=helptext)
    parser.add_argument("-c", "--config", dest="config", action="store",
                        help="Specify the config file to use.")
    parser.add_argument("-p", "--processes", dest="procs", action="store", type=int,
                        help="The number of local worker processes to spawn. Defaults to the "
                        "number of CPU cores your computer has.")

    parser.add_argument("--pid", dest="pid", action="store", help="Specify the pid file to use.")
    # Options that only apply to the config-less render usage
    parser.add_argument("--rendermodes", dest="rendermodes", action="store",
                        help="If you're not using a config file, specify which rendermodes to "
                        "render with this option. This is a comma-separated list.")
    parser.add_argument("world", nargs='?',
                        help="Path or name of the world you want to render.")
    parser.add_argument("output", nargs='?',
                        help="Output directory for the rendered map.")

    # Useful one-time render modifiers:
    render_modifiers = parser.add_mutually_exclusive_group()
    render_modifiers.add_argument("--forcerender", dest="forcerender", action="store_true",
                                  help="Force re-render the entire map.")
    render_modifiers.add_argument("--check-tiles", dest="checktiles", action="store_true",
                                  help="Check each tile on disk and re-render old tiles.")
    render_modifiers.add_argument("--no-tile-checks", dest="notilechecks", action="store_true",
                                  help="Only render tiles that come from chunks that have changed "
                                  "since the last render (the default).")

    # Useful one-time debugging options:
    parser.add_argument("--check-terrain", dest="check_terrain", action="store_true",
                        help="Try to locate the texture files. Useful for debugging texture"
                        " problems.")
    parser.add_argument("-V", "--version", dest="version",
                        help="Display version information and then exits.", action="store_true")
    parser.add_argument("--check-version", dest="checkversion",
                        help="Fetch information about the latest version of Overviewer.",
                        action="store_true")
    parser.add_argument("--update-web-assets", dest='update_web_assets', action="store_true",
                        help="Update web assets. Will *not* render tiles or update "
                        "overviewerConfig.js.")

    # Log level options:
    parser.add_argument("-q", "--quiet", dest="quiet", action="count", default=0,
                        help="Print less output. You can specify this option multiple times.")
    parser.add_argument("-v", "--verbose", dest="verbose", action="count", default=0,
                        help="Print more output. You can specify this option multiple times.")
    parser.add_argument("--simple-output", dest="simple", action="store_true", default=False,
                        help="Use a simple output format, with no colors or progress bars.")

    # create a group for "plugin exes"
    # (the concept of a plugin exe is only loosely defined at this point)
    exegroup = parser.add_argument_group("Other Scripts", "These scripts may accept different "
                                         "arguments than the ones listed above.")
    exegroup.add_argument("--genpoi", dest="genpoi", action="store_true",
                          help="Run the genPOI script.")
    exegroup.add_argument("--skip-scan", dest="skipscan", action="store_true",
                          help="When running GenPOI, don't scan for entities.")
    exegroup.add_argument("--skip-players", dest="skipplayers", action="store_true",
                          help="When running GenPOI, don't scan player data.")

    args, unknowns = parser.parse_known_args()

    # Check for possible shell quoting issues
    if len(unknowns) > 0 and args.world and args.output:
        possible_mistakes = []
        for i in range(len(unknowns) + 1):
            possible_mistakes.append(" ".join([args.world, args.output] + unknowns[:i]))
            possible_mistakes.append(" ".join([args.output] + unknowns[:i]))
        for mistake in possible_mistakes:
            if os.path.exists(mistake):
                logging.warning("Looks like you tried to make me use {0} as an argument, but "
                                "forgot to quote the argument correctly. Try using \"{0}\" "
                                "instead if the spaces are part of the path.".format(mistake))
                parser.error("Too many arguments.")
        parser.error("Too many arguments.")

    # first thing to do is check for stuff in the exegroup:
    if args.genpoi:
        # remove the "--genpoi" option from sys.argv before running genPI
        sys.argv.remove("--genpoi")
        g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
        g.genPOI.main()
        return 0

    # re-configure the logger now that we've processed the command line options
    logger.configure(logging.INFO + 10 * args.quiet - 10 * args.verbose,
                     verbose=args.verbose > 0, simple=args.simple)

    ##########################################################################
    # This section of main() runs in response to any one-time options we have,
    # such as -V for version reporting
    if args.version:
        print("Minecraft Overviewer %s" % util.findGitVersion() +
              " (%s)" % util.findGitHash()[:7])
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print("built on %s" % overviewer_version.BUILD_DATE)
            if args.verbose > 0:
                print("Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM,
                                                overviewer_version.BUILD_OS))
                print("Read version information from %r" % overviewer_version.__file__)
        except ImportError:
            print("(build info not found)")
        if args.verbose > 0:
            print("Python executable: %r" % sys.executable)
            print(sys.version)
        if not args.checkversion:
            return 0
    if args.checkversion:
        print("Currently running Minecraft Overviewer %s" % util.findGitVersion() +
              " (%s)" % util.findGitHash()[:7])
        try:
            from urllib import request
            import json
            latest_ver = json.loads(request.urlopen("http://overviewer.org/download.json")
                                    .read())['src']
            print("Latest version of Minecraft Overviewer %s (%s)" % (latest_ver['version'],
                                                                      latest_ver['commit'][:7]))
            print("See https://overviewer.org/downloads for more information.")
        except Exception:
            print("Failed to fetch latest version info.")
            if args.verbose > 0:
                import traceback
                traceback.print_exc()
            else:
                print("Re-run with --verbose for more details.")
            return 1
        return 0

    if args.pid:
        if os.path.exists(args.pid):
            try:
                with open(args.pid, 'r') as fpid:
                    pid = int(fpid.read())
                    if util.pid_exists(pid):
                        print("Overviewer is already running (pid exists) - exiting.")
                        return 0
            except (IOError, ValueError):
                pass
        with open(args.pid, "w") as f:
            f.write(str(os.getpid()))
    # if --check-terrain was specified, but we have NO config file, then we cannot
    # operate on a custom texture path.  we do terrain checking with a custom texture
    # pack later on, after we've parsed the config file
    if args.check_terrain and not args.config:
        import hashlib
        from overviewer_core.textures import Textures
        tex = Textures()

        logging.info("Looking for a few common texture files...")
        try:
            f = tex.find_file("assets/minecraft/textures/block/sandstone_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/grass_block_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/diamond_ore.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/acacia_planks.png", verbose=True)
            # 1.16
            f = tex.find_file("assets/minecraft/textures/block/ancient_debris_top.png",
                              verbose=True)
        except IOError:
            logging.error("Could not find any texture files.")
            return 1

        return 0

    # if no arguments are provided, print out a helpful message
    if not (args.world and args.output) and not args.config:
        # 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")
            print("\n")
            print("For a quick-start guide on Windows, visit the following URL:\n")
            print("http://docs.overviewer.org/en/latest/win_tut/windowsguide/\n")

        else:
            # more helpful message for users who know what they're doing
            logging.error("You must either specify --config or give me a world directory "
                          "and output directory.")
            parser.print_help()
            list_worlds()
        return 1

    ##########################################################################
    # This section does some sanity checking on the command line options passed
    # in. It checks to see if --config was given that no worldname/destdir were
    # given, and vice versa
    if args.config and (args.world and args.output):
        print()
        print("If you specify --config, you need to specify the world to render as well as "
              "the destination in the config file, not on the command line.")
        print("Put something like this in your config file:")
        print("worlds['myworld'] = %r" % args[0])
        print("outputdir = %r" % (args[1] if len(args) > 1 else "/path/to/output"))
        print()
        logging.error("You cannot specify both --config AND a world + output directory on the "
                      "command line.")
        parser.print_help()
        return 1

    if not args.config and (args.world or args.output) and not (args.world and args.output):
        logging.error("You must specify both the world directory and an output directory")
        parser.print_help()
        return 1

    #########################################################################
    # These two halfs of this if statement unify config-file mode and
    # command-line mode.
    mw_parser = config_parser.MultiWorldParser()

    if not args.config:
        # No config file mode.
        worldpath, destdir = map(os.path.expanduser, [args.world, args.output])
        logging.debug("Using %r as the world directory", worldpath)
        logging.debug("Using %r as the output directory", destdir)

        mw_parser.set_config_item("worlds", {'world': worldpath})
        mw_parser.set_config_item("outputdir", destdir)

        rendermodes = ['lighting']
        if args.rendermodes:
            rendermodes = args.rendermodes.replace("-", "_").split(",")

        # Now for some good defaults
        renders = OrderedDict()
        for rm in rendermodes:
            renders["world-" + rm] = {
                "world": "world",
                "title": "Overviewer Render (%s)" % rm,
                "rendermode": rm,
            }
        mw_parser.set_config_item("renders", renders)

    else:
        if args.rendermodes:
            logging.error("You cannot specify --rendermodes if you give a config file. "
                          "Configure your rendermodes in the config file instead.")
            parser.print_help()
            return 1

        # Parse the config file
        try:
            mw_parser.parse(os.path.expanduser(args.config))
        except config_parser.MissingConfigException as e:
            # this isn't a "bug", so don't print scary traceback
            logging.error(str(e))
            util.nice_exit(1)

    # Add in the command options here, perhaps overriding values specified in
    # the config
    if args.procs:
        mw_parser.set_config_item("processes", args.procs)

    # Now parse and return the validated config
    try:
        config = mw_parser.get_validated_config()
    except Exception as ex:
        if args.verbose:
            logging.exception("An error was encountered with your configuration. "
                              "See the information below.")
        else:   # no need to print scary traceback!
            logging.error("An error was encountered with your configuration.")
            logging.error(str(ex))
        return 1

    if args.check_terrain:   # we are already in the "if configfile" branch
        logging.info("Looking for a few common texture files...")
        for render_name, render in config['renders'].items():
            logging.info("Looking at render %r.", render_name)

            # find or create the textures object
            texopts = util.dict_subset(render, ["texturepath"])

            tex = textures.Textures(**texopts)
            f = tex.find_file("assets/minecraft/textures/block/sandstone_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/grass_block_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/diamond_ore.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/oak_planks.png", verbose=True)
        return 0

    ############################################################
    # Final validation steps and creation of the destination directory
    logging.info("Welcome to Minecraft Overviewer version %s (%s)!" % (util.findGitVersion(), util.findGitHash()[:7]))
    logging.debug("Current log level: {0}.".format(logging.getLogger().level))

    def set_renderchecks(checkname, num):
        for name, render in config['renders'].items():
            if render.get('renderchecks', 0) == 3:
                logging.warning(checkname + " ignoring render " + repr(name) + " since it's "
                                "marked as \"don't render\".")
            else:
                render['renderchecks'] = num

    if args.forcerender:
        logging.info("Forcerender mode activated. ALL tiles will be rendered.")
        set_renderchecks("forcerender", 2)
    elif args.checktiles:
        logging.info("Checking all tiles for updates manually.")
        set_renderchecks("checktiles", 1)
    elif args.notilechecks:
        logging.info("Disabling all tile mtime checks. Only rendering tiles "
                     "that need updating since last render.")
        set_renderchecks("notilechecks", 0)

    if not config['renders']:
        logging.error("You must specify at least one render in your config file. Check the "
                      "documentation at http://docs.overviewer.org if you're having trouble.")
        return 1

    #####################
    # Do a few last minute things to each render dictionary here
    for rname, render in config['renders'].items():
        # Convert render['world'] to the world path, and store the original
        # in render['worldname_orig']
        try:
            worldpath = config['worlds'][render['world']]
        except KeyError:
            logging.error("Render %s's world is '%s', but I could not find a corresponding entry "
                          "in the worlds dictionary.", rname, render['world'])
            return 1
        render['worldname_orig'] = render['world']
        render['world'] = worldpath

        # If 'forcerender' is set, change renderchecks to 2
        if render.get('forcerender', False):
            render['renderchecks'] = 2

        # check if overlays are set, if so, make sure that those renders exist
        if render.get('overlay', []) != []:
            for x in render.get('overlay'):
                if x != rname:
                    try:
                        renderLink = config['renders'][x]
                    except KeyError:
                        logging.error("Render %s's overlay is '%s', but I could not find a "
                                      "corresponding entry in the renders dictionary.", rname, x)
                        return 1
                else:
                    logging.error("Render %s's overlay contains itself.", rname)
                    return 1

    destdir = config['outputdir']
    if not destdir:
        logging.error("You must specify the output directory in your config file.")
        logging.error("e.g. outputdir = '/path/to/outputdir'")
        return 1
    if not os.path.exists(destdir):
        try:
            os.mkdir(destdir)
        except OSError:
            logging.exception("Could not create the output directory.")
            return 1

    ########################################################################
    # Now we start the actual processing, now that all the configuration has
    # been gathered and validated
    # create our asset manager... ASSMAN
    assetMrg = assetmanager.AssetManager(destdir, config.get('customwebassets', None))

    # If we've been asked to update web assets, do that and then exit
    if args.update_web_assets:
        assetMrg.output_noconfig()
        logging.info("Web assets have been updated.")
        return 0

    # The changelist support.
    changelists = {}
    for render in config['renders'].values():
        if 'changelist' in render:
            path = render['changelist']
            if path not in changelists:
                out = open(path, "w")
                logging.debug("Opening changelist %s (%s).", out, out.fileno())
                changelists[path] = out
            else:
                out = changelists[path]
            render['changelist'] = out.fileno()

    tilesets = []

    # saves us from creating the same World object over and over again
    worldcache = {}
    # same for textures
    texcache = {}

    # Set up the cache objects to use
    caches = []
    caches.append(cache.LRUCache(size=100))
    # TODO: optionally more caching layers here

    renders = config['renders']
    for render_name, render in renders.items():
        logging.debug("Found the following render thing: %r", render)

        # find or create the world object
        try:
            w = worldcache[render['world']]
        except KeyError:
            try:
                w = world.World(render['world'])
            except CorruptNBTError as e:
                logging.error("Failed to open world %r.", render['world'])
                raise e
            except world.UnsupportedVersion as e:
                for ln in str(e).split('\n'):
                    logging.error(ln)
                sys.exit(1)

            worldcache[render['world']] = w

        # find or create the textures object
        texopts = util.dict_subset(render, ["texturepath", "bgcolor", "northdirection"])
        texopts_key = tuple(texopts.items())
        if texopts_key not in texcache:
            tex = textures.Textures(**texopts)
            logging.info("Generating textures...")
            tex.generate()
            logging.debug("Finished generating textures.")
            texcache[texopts_key] = tex
        else:
            tex = texcache[texopts_key]

        try:
            logging.debug("Asking for regionset %r." % render['dimension'][1])
            rset = w.get_regionset(render['dimension'][1])
        except IndexError:
            logging.error("Sorry, I can't find anything to render!  Are you sure there are .mca "
                          "files in the world directory of %s?" % render['world'])
            return 1
        if rset is None:    # indicates no such dimension was found
            logging.warning("Sorry, you requested dimension '%s' for %s, but I couldn't find it.",
                         render['dimension'][0], render_name)
            continue

        #################
        # Apply any regionset transformations here

        # Insert a layer of caching above the real regionset. Any world
        # tranformations will pull from this cache, but their results will not
        # be cached by this layer. This uses a common pool of caches; each
        # regionset cache pulls from the same underlying cache object.
        rset = world.CachedRegionSet(rset, caches)

        # If a crop is requested, wrap the regionset here
        if "crop" in render:
            rsets = []
            for zone in render['crop']:
                rsets.append(world.CroppedRegionSet(rset, *zone))
        else:
            rsets = [rset]

        # If this is to be a rotated regionset, wrap it in a RotatedRegionSet
        # object
        if (render['northdirection'] > 0):
            newrsets = []
            for r in rsets:
                r = world.RotatedRegionSet(r, render['northdirection'])
                newrsets.append(r)
            rsets = newrsets

        ###############################
        # Do the final prep and create the TileSet object

        # create our TileSet from this RegionSet
        tileset_dir = os.path.abspath(os.path.join(destdir, render_name))

        # only pass to the TileSet the options it really cares about
        render['name'] = render_name    # perhaps a hack. This is stored here for the asset manager
        tileSetOpts = util.dict_subset(render, [
            "name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "defaultzoom",
            "imgquality", "imglossless", "optimizeimg", "rendermode", "worldname_orig", "title",
            "dimension", "changelist", "showspawn", "overlay", "base", "poititle", "maxzoom",
            "showlocationmarker", "minzoom", "center"])
        tileSetOpts.update({"spawn": w.find_true_spawn()})  # TODO find a better way to do this
        for rset in rsets:
            tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
            tilesets.append(tset)

    # If none of the requested dimenstions exist, tilesets will be empty
    if not tilesets:
        logging.error("There are no tilesets to render! There's nothing to do, so exiting.")
        return 1

    # Do tileset preprocessing here, before we start dispatching jobs
    logging.info("Preprocessing...")
    for ts in tilesets:
        ts.do_preprocessing()

    # Output initial static data and configuration
    assetMrg.initialize(tilesets)

    # multiprocessing dispatcher
    if config['processes'] == 1:
        dispatch = dispatcher.Dispatcher()
    else:
        dispatch = dispatcher.MultiprocessingDispatcher(
            local_procs=config['processes'])
    dispatch.render_all(tilesets, config['observer'])
    dispatch.close()

    assetMrg.finalize(tilesets)

    for out in changelists.values():
        logging.debug("Closing %s (%s).", out, out.fileno())
        out.close()

    if config['processes'] == 1:
        logging.debug("Final cache stats:")
        for c in caches:
            logging.debug("\t%s: %s hits, %s misses", c.__class__.__name__, c.hits, c.misses)
    if args.pid:
        os.remove(args.pid)

    logging.info("Your render has been written to '%s', open index.html to view it." % destdir)

    return 0
Пример #7
0
def main():
    # bootstrap the logger with defaults
    logger.configure()

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    #avail_rendermodes = c_overviewer.get_render_modes()
    avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto']

    # Parse for basic options
    parser = OptionParser(usage=helptext, add_help_option=False)
    parser.add_option("-h", "--help", dest="help", action="store_true",
            help="show this help message and exit")
    parser.add_option("-c", "--config", dest="config", action="store", help="Specify the config file to use.")
    parser.add_option("-p", "--processes", dest="procs", action="store", type="int",
            help="The number of local worker processes to spawn. Defaults to the number of CPU cores your computer has")

    parser.add_option("--pid", dest="pid", action="store", help="Specify the pid file to use.")
    # Options that only apply to the config-less render usage
    parser.add_option("--rendermodes", dest="rendermodes", action="store",
            help="If you're not using a config file, specify which rendermodes to render with this option. This is a comma-separated list.")

    # Useful one-time render modifiers:
    parser.add_option("--forcerender", dest="forcerender", action="store_true",
            help="Force re-rendering the entire map.")
    parser.add_option("--check-tiles", dest="checktiles", action="store_true",
            help="Check each tile on disk and re-render old tiles")
    parser.add_option("--no-tile-checks", dest="notilechecks", action="store_true",
            help="Only render tiles that come from chunks that have changed since the last render (the default)")

    # Useful one-time debugging options:
    parser.add_option("--check-terrain", dest="check_terrain", action="store_true",
            help="Tries to locate the texture files. Useful for debugging texture problems.")
    parser.add_option("-V", "--version", dest="version",
            help="Displays version information and then exits", action="store_true")
    parser.add_option("--update-web-assets", dest='update_web_assets', action="store_true",
            help="Update web assets. Will *not* render tiles or update overviewerConfig.js")

    # Log level options:
    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.")
    parser.add_option("--simple-output", dest="simple", action="store_true", default=False,
            help="Use a simple output format, with no colors or progress bars")

    # create a group for "plugin exes" (the concept of a plugin exe is only loosly defined at this point)
    exegroup = OptionGroup(parser, "Other Scripts",
            "These scripts may accept different arguments than the ones listed above")
    exegroup.add_option("--genpoi", dest="genpoi", action="store_true",
            help="Runs the genPOI script")
    exegroup.add_option("--skip-scan", dest="skipscan", action="store_true",
            help="When running GenPOI, don't scan for entities")

    parser.add_option_group(exegroup)

    options, args = parser.parse_args()

    # first thing to do is check for stuff in the exegroup:
    if options.genpoi:
        # remove the "--genpoi" option from sys.argv before running genPI
        sys.argv.remove("--genpoi")
        #sys.path.append(".")
        g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
        g.genPOI.main()
        return 0
    if options.help:
        parser.print_help()
        return 0

    # re-configure the logger now that we've processed the command line options
    logger.configure(logging.INFO + 10*options.quiet - 10*options.verbose,
                     verbose=options.verbose > 0,
                     simple=options.simple)

    ##########################################################################
    # This section of main() runs in response to any one-time options we have,
    # such as -V for version reporting
    if options.version:
        print("Minecraft Overviewer %s" % util.findGitVersion()),
        print("(%s)" % util.findGitHash()[:7])
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print("built on %s" % overviewer_version.BUILD_DATE)
            if options.verbose > 0:
                print("Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS))
        except ImportError:
            print("(build info not found)")
        return 0

    if options.pid:
        if os.path.exists(options.pid):
            try:
                with open(options.pid, 'r') as fpid:
                    pid = int(fpid.read())
                    if util.pid_exists(pid):
                        print("Already running (pid exists) - exiting..")
                        return 0
            except IOError, ValueError:
                pass
        with open(options.pid,"w") as f:
            f.write(str(os.getpid()))
Пример #8
0
#
# metadata
#


# Utility function to read the README file.
# Used for the long_description.  It's nice, because now 1) we have a top level
# README file and 2) it's easier to type in the README file than to put a raw
# string in below ...
def read(fname):
    return open(fname).read()


setup_kwargs['name'] = 'Minecraft-Overviewer'
setup_kwargs['version'] = util.findGitVersion()
setup_kwargs[
    'description'] = 'Generates large resolution images of a Minecraft map.'
setup_kwargs['url'] = 'http://overviewer.org/'
setup_kwargs['author'] = 'Andrew Brown'
setup_kwargs['author_email'] = '*****@*****.**'
setup_kwargs['license'] = 'GNU General Public License v3'
setup_kwargs['long_description'] = read('README.rst')

# top-level files that should be included as documentation
doc_files = [
    'COPYING.txt', 'README.rst', 'CONTRIBUTORS.rst', 'sample_config.py'
]


# helper to create a 'data_files'-type sequence recursively for a given dir
Пример #9
0
def main():

    # bootstrap the logger with defaults
    configure_logger()

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    avail_rendermodes = c_overviewer.get_render_modes()
    avail_north_dirs = [
        'lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto'
    ]

    parser = ConfigOptionParser(usage=helptext, config="settings.py")
    parser.add_option("-V",
                      "--version",
                      dest="version",
                      helptext="Displays version information and then exits",
                      action="store_true")
    parser.add_option(
        "-p",
        "--processes",
        dest="procs",
        helptext="How many worker processes to start. Default %s" % cpus,
        default=cpus,
        action="store",
        type="int")
    parser.add_option(
        "-z",
        "--zoom",
        dest="zoom",
        helptext=
        "Sets the zoom level manually instead of calculating it. This can be useful if you have outlier chunks that make your world too big. This value will make the highest zoom level contain (2**ZOOM)^2 tiles",
        action="store",
        type="int",
        advanced=True)
    parser.add_option(
        "--regionlist",
        dest="regionlist",
        helptext=
        "A file containing, on each line, a path to a regionlist to update. Instead of scanning the world directory for regions, it will just use this list. Normal caching rules still apply."
    )
    parser.add_option(
        "--forcerender",
        dest="forcerender",
        helptext=
        "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",
        helptext=
        "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",
        helptext=
        "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",
                      helptext="List available render modes and exit.",
                      commandLineOnly=True)
    parser.add_option(
        "--rendermode-options",
        dest="rendermode_options",
        default={},
        advanced=True,
        helptext=
        "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,
        helptext=
        "Used to define custom rendermodes.  Only useful in a settings.py file"
    )
    parser.add_option(
        "--imgformat",
        dest="imgformat",
        helptext=
        "The image output format to use. Currently supported: png(default), jpg.",
        advanced=True)
    parser.add_option(
        "--imgquality",
        dest="imgquality",
        default=95,
        helptext=
        "Specify the quality of image output when using imgformat=\"jpg\".",
        type="int",
        advanced=True)
    parser.add_option(
        "--bg-color",
        dest="bg_color",
        helptext=
        "Configures the background color for the GoogleMap output.  Specify in #RRGGBB format",
        advanced=True,
        type="string",
        default="#1A1A1A")
    parser.add_option(
        "--optimize-img",
        dest="optimizeimg",
        helptext=
        "If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+advdef and 3 for pngcrush-advdef with more aggressive settings. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%",
        advanced=True)
    parser.add_option(
        "--web-assets-hook",
        dest="web_assets_hook",
        helptext=
        "If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a MapGen object as its only argument.",
        action="store",
        metavar="FUNCTION",
        type="function",
        advanced=True)
    parser.add_option(
        "--web-assets-path",
        dest="web_assets_path",
        helptext=
        "Specifies a non-standard web_assets directory to use. Files here will overwrite the default web assets.",
        metavar="PATH",
        type="string",
        advanced=True)
    parser.add_option(
        "--textures-path",
        dest="textures_path",
        helptext=
        "Specifies a non-standard textures path, from which terrain.png and other textures are loaded.",
        metavar="PATH",
        type="string",
        advanced=True)
    parser.add_option(
        "--check-terrain",
        dest="check_terrain",
        helptext=
        "Prints the location and hash of terrain.png, useful for debugging terrain.png problems",
        action="store_true",
        advanced=False,
        commandLineOnly=True)
    parser.add_option(
        "-q",
        "--quiet",
        dest="quiet",
        action="count",
        default=0,
        helptext=
        "Print less output. You can specify this option multiple times.")
    parser.add_option(
        "-v",
        "--verbose",
        dest="verbose",
        action="count",
        default=0,
        helptext=
        "Print more output. You can specify this option multiple times.")
    parser.add_option("--skip-js",
                      dest="skipjs",
                      action="store_true",
                      helptext="Don't output marker.js or regions.js")
    parser.add_option("--no-signs",
                      dest="nosigns",
                      action="store_true",
                      helptext="Don't output signs to markers.js")
    parser.add_option(
        "--north-direction",
        dest="north_direction",
        action="store",
        helptext=
        "Specifies which corner of the screen north will point to. Defaults to whatever the current map uses, or lower-left for new maps. Valid options are: "
        + ", ".join(avail_north_dirs) + ".",
        type="choice",
        default="auto",
        choices=avail_north_dirs)
    parser.add_option(
        "--changelist",
        dest="changelist",
        action="store",
        helptext=
        "Output list of changed tiles to file. If the file exists, its contents will be overwritten.",
        advanced=True)
    parser.add_option(
        "--changelist-format",
        dest="changelist_format",
        action="store",
        helptext=
        "Output relative or absolute paths for --changelist. Only valid when --changelist is used",
        type="choice",
        default="auto",
        choices=["auto", "relative", "absolute"],
        advanced=True)
    parser.add_option(
        "--display-config",
        dest="display_config",
        action="store_true",
        helptext=
        "Display the configuration parameters, but don't render the map.  Requires all required options to be specified",
        commandLineOnly=True)
    #parser.add_option("--write-config", dest="write_config", action="store_true", helptext="Writes out a sample config file", commandLineOnly=True)

    options, args = parser.parse_args()

    # re-configure the logger now that we've processed the command line options
    configure_logger(logging.INFO + 10 * options.quiet - 10 * options.verbose,
                     options.verbose > 0)

    if options.version:
        print "Minecraft Overviewer %s" % util.findGitVersion(),
        print "(%s)" % util.findGitHash()[:7]
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print "built on %s" % overviewer_version.BUILD_DATE
            if options.verbose > 0:
                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])
    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])

    # Expand user dir in directories strings
    if options.textures_path:
        options.textures_path = os.path.expanduser(options.textures_path)
    if options.web_assets_path:
        options.web_assets_path = os.path.expanduser(options.web_assets_path)

    if options.list_rendermodes:
        list_rendermodes()
        doExit(code=0, consoleMsg=False)

    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

        try:
            f = _find_file("terrain.png", verbose=True)
        except IOError:
            logging.error("Could not find the file terrain.png")
            doExit(code=1, consoleMsg=False)

        h = hashlib.sha1()
        h.update(f.read())
        logging.info("Hash of terrain.png file is: `%s`", h.hexdigest())
        doExit(code=0, consoleMsg=False)

    if options.advanced_help:
        parser.advanced_help()
        doExit(code=0, consoleMsg=False)

    if 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)
    worlddir = os.path.expanduser(args[0])

    if len(args) > 2:
        # it's possible the user has a space in one of their paths but didn't properly escape it
        # attempt to detect this case
        for start in range(len(args)):
            if not os.path.exists(args[start]):
                for end in range(start + 1, len(args) + 1):
                    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 not os.path.exists(worlddir):
        # world given is either world number, or name
        worlds = world.get_worlds()

        # if there are no worlds found at all, exit now
        if not worlds:
            parser.print_help()
            logging.error("Invalid world path")
            doExit(code=1, consoleMsg=False)

        try:
            worldnum = int(worlddir)
            worlddir = worlds[worldnum]['path']
        except ValueError:
            # it wasn't a number or path, try using it as a name
            try:
                worlddir = worlds[worlddir]['path']
            except KeyError:
                # it's not a number, name, or path
                parser.print_help()
                logging.error("Invalid world name or path")
                doExit(code=1, consoleMsg=False)
        except KeyError:
            # it was an invalid number
            parser.print_help()
            logging.error("Invalid world number")
            doExit(code=1, consoleMsg=False)

    # final sanity check for worlddir
    if not os.path.exists(os.path.join(worlddir, 'level.dat')):
        logging.error("Invalid world path -- does not contain level.dat")
        doExit(code=1, consoleMsg=False)

    if len(args) < 2:
        logging.error("Where do you want to save the tiles?")
        doExit(code=1, consoleMsg=False)
    elif len(args) > 2:
        parser.print_help()
        logging.error("Sorry, you specified too many arguments")
        doExit(code=1, consoleMsg=False)

    destdir = os.path.expanduser(args[1])
    if options.display_config:
        # just display the config file and exit
        parser.display_config()
        doExit(code=0, consoleMsg=False)

    if options.regionlist:
        regionlist = map(str.strip, open(options.regionlist, 'r'))
    else:
        regionlist = None

    if options.imgformat:
        if options.imgformat not in ('jpg', 'png'):
            parser.error("Unknown imgformat!")
        else:
            imgformat = options.imgformat
    else:
        imgformat = 'png'

    if options.optimizeimg:
        optimizeimg = int(options.optimizeimg)
        optimizeimages.check_programs(optimizeimg)
    else:
        optimizeimg = None

    if options.north_direction:
        north_direction = options.north_direction
    else:
        north_direction = 'auto'

    if options.changelist:
        try:
            changefile = open(options.changelist, 'w+')
        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)

    if options.changelist_format != "auto" and not options.changelist:
        logging.error("changelist_format specified without changelist.")
        doExit(code=1, consoleMsg=False)
    if options.changelist_format == "auto":
        options.changelist_format = "relative"

    logging.info("Welcome to Minecraft Overviewer!")
    logging.debug("Current log level: {0}".format(logging.getLogger().level))

    useBiomeData = os.path.exists(os.path.join(worlddir, 'biomes'))
    if not useBiomeData:
        logging.info("Notice: Not using biome data for tinting")

    # make sure that the textures can be found
    try:
        textures.generate(path=options.textures_path)
    except IOError, e:
        logging.error(str(e))
        doExit(code=1, consoleMsg=False)
Пример #10
0
def main():
    # bootstrap the logger with defaults
    logger.configure()

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    #avail_rendermodes = c_overviewer.get_render_modes()
    avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto']

    # Parse for basic options
    parser = OptionParser(usage=helptext, add_help_option=False)
    parser.add_option("-h", "--help", dest="help", action="store_true",
            help="show this help message and exit")
    parser.add_option("-c", "--config", dest="config", action="store", help="Specify the config file to use.")
    parser.add_option("-p", "--processes", dest="procs", action="store", type="int",
            help="The number of local worker processes to spawn. Defaults to the number of CPU cores your computer has")

    parser.add_option("--pid", dest="pid", action="store", help="Specify the pid file to use.")
    # Options that only apply to the config-less render usage
    parser.add_option("--rendermodes", dest="rendermodes", action="store",
            help="If you're not using a config file, specify which rendermodes to render with this option. This is a comma-separated list.")

    # Useful one-time render modifiers:
    parser.add_option("--forcerender", dest="forcerender", action="store_true",
            help="Force re-rendering the entire map.")
    parser.add_option("--check-tiles", dest="checktiles", action="store_true",
            help="Check each tile on disk and re-render old tiles")
    parser.add_option("--no-tile-checks", dest="notilechecks", action="store_true",
            help="Only render tiles that come from chunks that have changed since the last render (the default)")

    # Useful one-time debugging options:
    parser.add_option("--check-terrain", dest="check_terrain", action="store_true",
            help="Tries to locate the texture files. Useful for debugging texture problems.")
    parser.add_option("-V", "--version", dest="version",
            help="Displays version information and then exits", action="store_true")
    parser.add_option("--update-web-assets", dest='update_web_assets', action="store_true",
            help="Update web assets. Will *not* render tiles or update overviewerConfig.js")

    # Log level options:
    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.")
    parser.add_option("--simple-output", dest="simple", action="store_true", default=False,
            help="Use a simple output format, with no colors or progress bars")

    # create a group for "plugin exes" (the concept of a plugin exe is only loosly defined at this point)
    exegroup = OptionGroup(parser, "Other Scripts",
            "These scripts may accept different arguments than the ones listed above")
    exegroup.add_option("--genpoi", dest="genpoi", action="store_true",
            help="Runs the genPOI script")
    exegroup.add_option("--skip-scan", dest="skipscan", action="store_true",
            help="When running GenPOI, don't scan for entities")

    parser.add_option_group(exegroup)

    options, args = parser.parse_args()

    # first thing to do is check for stuff in the exegroup:
    if options.genpoi:
        # remove the "--genpoi" option from sys.argv before running genPI
        sys.argv.remove("--genpoi")
        #sys.path.append(".")
        g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
        g.genPOI.main()
        return 0
    if options.help:
        parser.print_help()
        return 0

    # re-configure the logger now that we've processed the command line options
    logger.configure(logging.INFO + 10*options.quiet - 10*options.verbose,
                     verbose=options.verbose > 0,
                     simple=options.simple)

    ##########################################################################
    # This section of main() runs in response to any one-time options we have,
    # such as -V for version reporting
    if options.version:
        print("Minecraft Overviewer %s" % util.findGitVersion()),
        print("(%s)" % util.findGitHash()[:7])
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print("built on %s" % overviewer_version.BUILD_DATE)
            if options.verbose > 0:
                print("Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS))
                print("Read version information from %r"% overviewer_version.__file__)
        except ImportError:
            print("(build info not found)")
        if options.verbose > 0:
            print("Python executable: %r" % sys.executable)
            print(sys.version)
        return 0

    if options.pid:
        if os.path.exists(options.pid):
            try:
                with open(options.pid, 'r') as fpid:
                    pid = int(fpid.read())
                    if util.pid_exists(pid):
                        print("Already running (pid exists) - exiting..")
                        return 0
            except IOError, ValueError:
                pass
        with open(options.pid,"w") as f:
            f.write(str(os.getpid()))
Пример #11
0
def main():
    # bootstrap the logger with defaults
    logger.configure()

    if os.name == "posix":
        if os.geteuid() == 0:
            logging.warning("You are running Overviewer as root. "
                            "It is recommended that you never do this, "
                            "as it is dangerous for your system. If you are running "
                            "into permission errors, fix your file/directory "
                            "permissions instead. Overviewer does not need access to "
                            "critical system resources and therefore does not require "
                            "root access.")

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1

    avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto']

    # Parse for basic options
    parser = ArgumentParser(usage=helptext)
    parser.add_argument("-c", "--config", dest="config", action="store",
                        help="Specify the config file to use.")
    parser.add_argument("-p", "--processes", dest="procs", action="store", type=int,
                        help="The number of local worker processes to spawn. Defaults to the "
                        "number of CPU cores your computer has.")

    parser.add_argument("--pid", dest="pid", action="store", help="Specify the pid file to use.")
    # Options that only apply to the config-less render usage
    parser.add_argument("--rendermodes", dest="rendermodes", action="store",
                        help="If you're not using a config file, specify which rendermodes to "
                        "render with this option. This is a comma-separated list.")
    parser.add_argument("world", nargs='?',
                        help="Path or name of the world you want to render.")
    parser.add_argument("output", nargs='?',
                        help="Output directory for the rendered map.")

    # Useful one-time render modifiers:
    render_modifiers = parser.add_mutually_exclusive_group()
    render_modifiers.add_argument("--forcerender", dest="forcerender", action="store_true",
                                  help="Force re-render the entire map.")
    render_modifiers.add_argument("--check-tiles", dest="checktiles", action="store_true",
                                  help="Check each tile on disk and re-render old tiles.")
    render_modifiers.add_argument("--no-tile-checks", dest="notilechecks", action="store_true",
                                  help="Only render tiles that come from chunks that have changed "
                                  "since the last render (the default).")

    # Useful one-time debugging options:
    parser.add_argument("--check-terrain", dest="check_terrain", action="store_true",
                        help="Try to locate the texture files. Useful for debugging texture"
                        " problems.")
    parser.add_argument("-V", "--version", dest="version",
                        help="Display version information and then exits.", action="store_true")
    parser.add_argument("--check-version", dest="checkversion",
                        help="Fetch information about the latest version of Overviewer.",
                        action="store_true")
    parser.add_argument("--update-web-assets", dest='update_web_assets', action="store_true",
                        help="Update web assets. Will *not* render tiles or update "
                        "overviewerConfig.js.")

    # Log level options:
    parser.add_argument("-q", "--quiet", dest="quiet", action="count", default=0,
                        help="Print less output. You can specify this option multiple times.")
    parser.add_argument("-v", "--verbose", dest="verbose", action="count", default=0,
                        help="Print more output. You can specify this option multiple times.")
    parser.add_argument("--simple-output", dest="simple", action="store_true", default=False,
                        help="Use a simple output format, with no colors or progress bars.")

    # create a group for "plugin exes"
    # (the concept of a plugin exe is only loosely defined at this point)
    exegroup = parser.add_argument_group("Other Scripts", "These scripts may accept different "
                                         "arguments than the ones listed above.")
    exegroup.add_argument("--genpoi", dest="genpoi", action="store_true",
                          help="Run the genPOI script.")
    exegroup.add_argument("--skip-scan", dest="skipscan", action="store_true",
                          help="When running GenPOI, don't scan for entities.")
    exegroup.add_argument("--skip-players", dest="skipplayers", action="store_true",
                          help="When running GenPOI, don't scan player data.")

    args, unknowns = parser.parse_known_args()

    # Check for possible shell quoting issues
    if len(unknowns) > 0:
        possible_mistakes = []
        for i in xrange(len(unknowns) + 1):
            possible_mistakes.append(" ".join([args.world, args.output] + unknowns[:i]))
            possible_mistakes.append(" ".join([args.output] + unknowns[:i]))
        for mistake in possible_mistakes:
            if os.path.exists(mistake):
                logging.warning("Looks like you tried to make me use {0} as an argument, but "
                                "forgot to quote the argument correctly. Try using \"{0}\" "
                                "instead if the spaces are part of the path.".format(mistake))
                parser.error("Too many arguments.")
        parser.error("Too many arguments.")

    # first thing to do is check for stuff in the exegroup:
    if args.genpoi:
        # remove the "--genpoi" option from sys.argv before running genPI
        sys.argv.remove("--genpoi")
        g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
        g.genPOI.main()
        return 0

    # re-configure the logger now that we've processed the command line options
    logger.configure(logging.INFO + 10 * args.quiet - 10 * args.verbose,
                     verbose=args.verbose > 0, simple=args.simple)

    ##########################################################################
    # This section of main() runs in response to any one-time options we have,
    # such as -V for version reporting
    if args.version:
        print("Minecraft Overviewer %s" % util.findGitVersion() +
              " (%s)" % util.findGitHash()[:7])
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print("built on %s" % overviewer_version.BUILD_DATE)
            if args.verbose > 0:
                print("Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM,
                                                overviewer_version.BUILD_OS))
                print("Read version information from %r" % overviewer_version.__file__)
        except ImportError:
            print("(build info not found)")
        if args.verbose > 0:
            print("Python executable: %r" % sys.executable)
            print(sys.version)
        if not args.checkversion:
            return 0
    if args.checkversion:
        print("Currently running Minecraft Overviewer %s" % util.findGitVersion() +
              " (%s)" % util.findGitHash()[:7])
        try:
            import urllib
            import json
            latest_ver = json.loads(urllib.urlopen("http://overviewer.org/download.json")
                                    .read())['src']
            print("Latest version of Minecraft Overviewer %s (%s)" % (latest_ver['version'],
                                                                      latest_ver['commit'][:7]))
            print("See https://overviewer.org/downloads for more information.")
        except Exception:
            print("Failed to fetch latest version info.")
            if args.verbose > 0:
                import traceback
                traceback.print_exc()
            else:
                print("Re-run with --verbose for more details.")
            return 1
        return 0

    if args.pid:
        if os.path.exists(args.pid):
            try:
                with open(args.pid, 'r') as fpid:
                    pid = int(fpid.read())
                    if util.pid_exists(pid):
                        print("Overviewer is already running (pid exists) - exiting.")
                        return 0
            except (IOError, ValueError):
                pass
        with open(args.pid, "w") as f:
            f.write(str(os.getpid()))
    # if --check-terrain was specified, but we have NO config file, then we cannot
    # operate on a custom texture path.  we do terrain checking with a custom texture
    # pack later on, after we've parsed the config file
    if args.check_terrain and not args.config:
        import hashlib
        from overviewer_core.textures import Textures
        tex = Textures()

        logging.info("Looking for a few common texture files...")
        try:
            f = tex.find_file("assets/minecraft/textures/block/sandstone_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/grass_block_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/diamond_ore.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/acacia_planks.png", verbose=True)
        except IOError:
            logging.error("Could not find any texture files.")
            return 1

        return 0

    # if no arguments are provided, print out a helpful message
    if not (args.world and args.output) and not args.config:
        # 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")
            print("\n")
            print("For a quick-start guide on Windows, visit the following URL:\n")
            print("http://docs.overviewer.org/en/latest/win_tut/windowsguide/\n")

        else:
            # more helpful message for users who know what they're doing
            logging.error("You must either specify --config or give me a world directory "
                          "and output directory.")
            parser.print_help()
            list_worlds()
        return 1

    ##########################################################################
    # This section does some sanity checking on the command line options passed
    # in. It checks to see if --config was given that no worldname/destdir were
    # given, and vice versa
    if args.config and (args.world and args.output):
        print()
        print("If you specify --config, you need to specify the world to render as well as "
              "the destination in the config file, not on the command line.")
        print("Put something like this in your config file:")
        print("worlds['myworld'] = %r" % args[0])
        print("outputdir = %r" % (args[1] if len(args) > 1 else "/path/to/output"))
        print()
        logging.error("You cannot specify both --config AND a world + output directory on the "
                      "command line.")
        parser.print_help()
        return 1

    if not args.config and (args.world or args.output) and not (args.world and args.output):
        logging.error("You must specify both the world directory and an output directory")
        parser.print_help()
        return 1

    #########################################################################
    # These two halfs of this if statement unify config-file mode and
    # command-line mode.
    mw_parser = configParser.MultiWorldParser()

    if not args.config:
        # No config file mode.
        worldpath, destdir = map(os.path.expanduser, [args.world, args.output])
        logging.debug("Using %r as the world directory", worldpath)
        logging.debug("Using %r as the output directory", destdir)

        mw_parser.set_config_item("worlds", {'world': worldpath})
        mw_parser.set_config_item("outputdir", destdir)

        rendermodes = ['lighting']
        if args.rendermodes:
            rendermodes = args.rendermodes.replace("-", "_").split(",")

        # Now for some good defaults
        renders = util.OrderedDict()
        for rm in rendermodes:
            renders["world-" + rm] = {
                "world": "world",
                "title": "Overviewer Render (%s)" % rm,
                "rendermode": rm,
            }
        mw_parser.set_config_item("renders", renders)

    else:
        if args.rendermodes:
            logging.error("You cannot specify --rendermodes if you give a config file. "
                          "Configure your rendermodes in the config file instead.")
            parser.print_help()
            return 1

        # Parse the config file
        try:
            mw_parser.parse(os.path.expanduser(args.config))
        except configParser.MissingConfigException as e:
            # this isn't a "bug", so don't print scary traceback
            logging.error(str(e))
            util.nice_exit(1)

    # Add in the command options here, perhaps overriding values specified in
    # the config
    if args.procs:
        mw_parser.set_config_item("processes", args.procs)

    # Now parse and return the validated config
    try:
        config = mw_parser.get_validated_config()
    except Exception as ex:
        if args.verbose:
            logging.exception("An error was encountered with your configuration. "
                              "See the information below.")
        else:   # no need to print scary traceback!
            logging.error("An error was encountered with your configuration.")
            logging.error(str(ex))
        return 1

    if args.check_terrain:   # we are already in the "if configfile" branch
        logging.info("Looking for a few common texture files...")
        for render_name, render in config['renders'].iteritems():
            logging.info("Looking at render %r.", render_name)

            # find or create the textures object
            texopts = util.dict_subset(render, ["texturepath"])

            tex = textures.Textures(**texopts)
            f = tex.find_file("assets/minecraft/textures/block/sandstone_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/grass_block_top.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/diamond_ore.png", verbose=True)
            f = tex.find_file("assets/minecraft/textures/block/oak_planks.png", verbose=True)
        return 0

    ############################################################
    # Final validation steps and creation of the destination directory
    logging.info("Welcome to Minecraft Overviewer!")
    logging.debug("Current log level: {0}.".format(logging.getLogger().level))

    def set_renderchecks(checkname, num):
        for name, render in config['renders'].iteritems():
            if render.get('renderchecks', 0) == 3:
                logging.warning(checkname + " ignoring render " + repr(name) + " since it's "
                                "marked as \"don't render\".")
            else:
                render['renderchecks'] = num

    if args.forcerender:
        logging.info("Forcerender mode activated. ALL tiles will be rendered.")
        set_renderchecks("forcerender", 2)
    elif args.checktiles:
        logging.info("Checking all tiles for updates manually.")
        set_renderchecks("checktiles", 1)
    elif args.notilechecks:
        logging.info("Disabling all tile mtime checks. Only rendering tiles "
                     "that need updating since last render.")
        set_renderchecks("notilechecks", 0)

    if not config['renders']:
        logging.error("You must specify at least one render in your config file. Check the "
                      "documentation at http://docs.overviewer.org if you're having trouble.")
        return 1

    #####################
    # Do a few last minute things to each render dictionary here
    for rname, render in config['renders'].iteritems():
        # Convert render['world'] to the world path, and store the original
        # in render['worldname_orig']
        try:
            worldpath = config['worlds'][render['world']]
        except KeyError:
            logging.error("Render %s's world is '%s', but I could not find a corresponding entry "
                          "in the worlds dictionary.", rname, render['world'])
            return 1
        render['worldname_orig'] = render['world']
        render['world'] = worldpath

        # If 'forcerender' is set, change renderchecks to 2
        if render.get('forcerender', False):
            render['renderchecks'] = 2

        # check if overlays are set, if so, make sure that those renders exist
        if render.get('overlay', []) != []:
            for x in render.get('overlay'):
                if x != rname:
                    try:
                        renderLink = config['renders'][x]
                    except KeyError:
                        logging.error("Render %s's overlay is '%s', but I could not find a "
                                      "corresponding entry in the renders dictionary.", rname, x)
                        return 1
                else:
                    logging.error("Render %s's overlay contains itself.", rname)
                    return 1

    destdir = config['outputdir']
    if not destdir:
        logging.error("You must specify the output directory in your config file.")
        logging.error("e.g. outputdir = '/path/to/outputdir'")
        return 1
    if not os.path.exists(destdir):
        try:
            os.mkdir(destdir)
        except OSError:
            logging.exception("Could not create the output directory.")
            return 1

    ########################################################################
    # Now we start the actual processing, now that all the configuration has
    # been gathered and validated
    # create our asset manager... ASSMAN
    assetMrg = assetmanager.AssetManager(destdir, config.get('customwebassets', None))

    # If we've been asked to update web assets, do that and then exit
    if args.update_web_assets:
        assetMrg.output_noconfig()
        logging.info("Web assets have been updated.")
        return 0

    # The changelist support.
    changelists = {}
    for render in config['renders'].itervalues():
        if 'changelist' in render:
            path = render['changelist']
            if path not in changelists:
                out = open(path, "w")
                logging.debug("Opening changelist %s (%s).", out, out.fileno())
                changelists[path] = out
            else:
                out = changelists[path]
            render['changelist'] = out.fileno()

    tilesets = []

    # saves us from creating the same World object over and over again
    worldcache = {}
    # same for textures
    texcache = {}

    # Set up the cache objects to use
    caches = []
    caches.append(cache.LRUCache(size=100))
    # TODO: optionally more caching layers here

    renders = config['renders']
    for render_name, render in renders.iteritems():
        logging.debug("Found the following render thing: %r", render)

        # find or create the world object
        try:
            w = worldcache[render['world']]
        except KeyError:
            try:
                w = world.World(render['world'])
            except CorruptNBTError as e:
                logging.error("Failed to open world %r.", render['world'])
                raise e
            except world.UnsupportedVersion as e:
                for ln in str(e).split('\n'):
                    logging.error(ln)
                sys.exit(1)

            worldcache[render['world']] = w

        # find or create the textures object
        texopts = util.dict_subset(render, ["texturepath", "bgcolor", "northdirection"])
        texopts_key = tuple(texopts.items())
        if texopts_key not in texcache:
            tex = textures.Textures(**texopts)
            logging.info("Generating textures...")
            tex.generate()
            logging.debug("Finished generating textures.")
            texcache[texopts_key] = tex
        else:
            tex = texcache[texopts_key]

        try:
            logging.debug("Asking for regionset %r." % render['dimension'][1])
            rset = w.get_regionset(render['dimension'][1])
        except IndexError:
            logging.error("Sorry, I can't find anything to render!  Are you sure there are .mca "
                          "files in the world directory?")
            return 1
        if rset is None:    # indicates no such dimension was found
            logging.warn("Sorry, you requested dimension '%s' for %s, but I couldn't find it.",
                         render['dimension'][0], render_name)
            continue

        #################
        # Apply any regionset transformations here

        # Insert a layer of caching above the real regionset. Any world
        # tranformations will pull from this cache, but their results will not
        # be cached by this layer. This uses a common pool of caches; each
        # regionset cache pulls from the same underlying cache object.
        rset = world.CachedRegionSet(rset, caches)

        # If a crop is requested, wrap the regionset here
        if "crop" in render:
            rsets = []
            for zone in render['crop']:
                rsets.append(world.CroppedRegionSet(rset, *zone))
        else:
            rsets = [rset]

        # If this is to be a rotated regionset, wrap it in a RotatedRegionSet
        # object
        if (render['northdirection'] > 0):
            newrsets = []
            for r in rsets:
                r = world.RotatedRegionSet(r, render['northdirection'])
                newrsets.append(r)
            rsets = newrsets

        ###############################
        # Do the final prep and create the TileSet object

        # create our TileSet from this RegionSet
        tileset_dir = os.path.abspath(os.path.join(destdir, render_name))

        # only pass to the TileSet the options it really cares about
        render['name'] = render_name    # perhaps a hack. This is stored here for the asset manager
        tileSetOpts = util.dict_subset(render, [
            "name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "defaultzoom",
            "imgquality", "imglossless", "optimizeimg", "rendermode", "worldname_orig", "title",
            "dimension", "changelist", "showspawn", "overlay", "base", "poititle", "maxzoom",
            "showlocationmarker", "minzoom"])
        tileSetOpts.update({"spawn": w.find_true_spawn()})  # TODO find a better way to do this
        for rset in rsets:
            tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
            tilesets.append(tset)

    # If none of the requested dimenstions exist, tilesets will be empty
    if not tilesets:
        logging.error("There are no tilesets to render! There's nothing to do, so exiting.")
        return 1

    # Do tileset preprocessing here, before we start dispatching jobs
    logging.info("Preprocessing...")
    for ts in tilesets:
        ts.do_preprocessing()

    # Output initial static data and configuration
    assetMrg.initialize(tilesets)

    # multiprocessing dispatcher
    if config['processes'] == 1:
        dispatch = dispatcher.Dispatcher()
    else:
        dispatch = dispatcher.MultiprocessingDispatcher(
            local_procs=config['processes'])
    dispatch.render_all(tilesets, config['observer'])
    dispatch.close()

    assetMrg.finalize(tilesets)

    for out in changelists.itervalues():
        logging.debug("Closing %s (%s).", out, out.fileno())
        out.close()

    if config['processes'] == 1:
        logging.debug("Final cache stats:")
        for c in caches:
            logging.debug("\t%s: %s hits, %s misses", c.__class__.__name__, c.hits, c.misses)
    if args.pid:
        os.remove(args.pid)

    logging.info("Your render has been written to '%s', open index.html to view it." % destdir)

    return 0
Пример #12
0
# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Overviewer'
copyright = u'2010-2011 the Overviewer Team'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = util.findGitVersion()
# The full version, including alpha/beta/rc tags.
release = version

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Пример #13
0
def main():

    # bootstrap the logger with defaults
    configure_logger()

    try:
        cpus = multiprocessing.cpu_count()
    except NotImplementedError:
        cpus = 1
    
    avail_rendermodes = c_overviewer.get_render_modes()
    avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right', 'auto']
    
    parser = ConfigOptionParser(usage=helptext, config="settings.py")
    parser.add_option("-V", "--version", dest="version", helptext="Displays version information and then exits", action="store_true")
    parser.add_option("-p", "--processes", dest="procs", helptext="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int")
    parser.add_option("-z", "--zoom", dest="zoom", helptext="Sets the zoom level manually instead of calculating it. This can be useful if you have outlier chunks that make your world too big. This value will make the highest zoom level contain (2**ZOOM)^2 tiles", action="store", type="int", advanced=True)
    parser.add_option("--regionlist", dest="regionlist", helptext="A file containing, on each line, a path to a regionlist to update. Instead of scanning the world directory for regions, it will just use this list. Normal caching rules still apply.")
    parser.add_option("--forcerender", dest="forcerender", helptext="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", helptext="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", helptext="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", helptext="List available render modes and exit.", commandLineOnly=True)
    parser.add_option("--rendermode-options", dest="rendermode_options", default={}, advanced=True, helptext="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, helptext="Used to define custom rendermodes.  Only useful in a settings.py file")
    parser.add_option("--imgformat", dest="imgformat", helptext="The image output format to use. Currently supported: png(default), jpg.", advanced=True )
    parser.add_option("--imgquality", dest="imgquality", default=95, helptext="Specify the quality of image output when using imgformat=\"jpg\".", type="int", advanced=True)
    parser.add_option("--bg-color", dest="bg_color", helptext="Configures the background color for the GoogleMap output.  Specify in #RRGGBB format", advanced=True, type="string", default="#1A1A1A")
    parser.add_option("--optimize-img", dest="optimizeimg", helptext="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+advdef and 3 for pngcrush-advdef with more aggressive settings. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%", advanced=True)
    parser.add_option("--web-assets-hook", dest="web_assets_hook", helptext="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a MapGen object as its only argument.", action="store", metavar="FUNCTION", type="function", advanced=True)
    parser.add_option("--web-assets-path", dest="web_assets_path", helptext="Specifies a non-standard web_assets directory to use. Files here will overwrite the default web assets.", metavar="PATH", type="string", advanced=True)
    parser.add_option("--textures-path", dest="textures_path", helptext="Specifies a non-standard textures path, from which terrain.png and other textures are loaded.", metavar="PATH", type="string", advanced=True)
    parser.add_option("--check-terrain", dest="check_terrain", helptext="Prints the location and hash of terrain.png, useful for debugging terrain.png problems", action="store_true", advanced=False, commandLineOnly=True)
    parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, helptext="Print less output. You can specify this option multiple times.")
    parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, helptext="Print more output. You can specify this option multiple times.")
    parser.add_option("--skip-js", dest="skipjs", action="store_true", helptext="Don't output marker.js or regions.js")
    parser.add_option("--no-signs", dest="nosigns", action="store_true", helptext="Don't output signs to markers.js")
    parser.add_option("--north-direction", dest="north_direction", action="store", helptext="Specifies which corner of the screen north will point to. Defaults to whatever the current map uses, or lower-left for new maps. Valid options are: " + ", ".join(avail_north_dirs) + ".", type="choice", default="auto", choices=avail_north_dirs)
    parser.add_option("--changelist", dest="changelist", action="store", helptext="Output list of changed tiles to file. If the file exists, its contents will be overwritten.",advanced=True)
    parser.add_option("--changelist-format", dest="changelist_format", action="store", helptext="Output relative or absolute paths for --changelist. Only valid when --changelist is used", type="choice", default="auto", choices=["auto", "relative","absolute"],advanced=True)
    parser.add_option("--display-config", dest="display_config", action="store_true", helptext="Display the configuration parameters, but don't render the map.  Requires all required options to be specified", commandLineOnly=True)
    #parser.add_option("--write-config", dest="write_config", action="store_true", helptext="Writes out a sample config file", commandLineOnly=True)

    options, args = parser.parse_args()

    # re-configure the logger now that we've processed the command line options
    configure_logger(logging.INFO + 10*options.quiet - 10*options.verbose,
            options.verbose > 0)

    if options.version:
        print "Minecraft Overviewer %s" % util.findGitVersion(),
        print "(%s)" % util.findGitHash()[:7]
        try:
            import overviewer_core.overviewer_version as overviewer_version
            print "built on %s" % overviewer_version.BUILD_DATE
            if options.verbose > 0:
                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])
    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])
    
    
    # Expand user dir in directories strings
    if options.textures_path:
        options.textures_path = os.path.expanduser(options.textures_path)
    if options.web_assets_path:
        options.web_assets_path = os.path.expanduser(options.web_assets_path)
        
    
    if options.list_rendermodes:
        list_rendermodes()
        doExit(code=0, consoleMsg=False)

    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

        try:
            f = _find_file("terrain.png", verbose=True)
        except IOError:
            logging.error("Could not find the file terrain.png")
            doExit(code=1, consoleMsg=False)

        h = hashlib.sha1()
        h.update(f.read())
        logging.info("Hash of terrain.png file is: `%s`", h.hexdigest())
        doExit(code=0, consoleMsg=False)
        
    if options.advanced_help:
        parser.advanced_help()
        doExit(code=0, consoleMsg=False)

    if 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)
    worlddir = os.path.expanduser(args[0])

    if len(args) > 2:
        # it's possible the user has a space in one of their paths but didn't properly escape it
        # attempt to detect this case
        for start in range(len(args)):
            if not os.path.exists(args[start]):
                for end in range(start+1, len(args)+1):
                    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 not os.path.exists(worlddir):
        # world given is either world number, or name
        worlds = world.get_worlds()
        
        # if there are no worlds found at all, exit now
        if not worlds:
            parser.print_help()
            logging.error("Invalid world path")
            doExit(code=1, consoleMsg=False)
        
        try:
            worldnum = int(worlddir)
            worlddir = worlds[worldnum]['path']
        except ValueError:
            # it wasn't a number or path, try using it as a name
            try:
                worlddir = worlds[worlddir]['path']
            except KeyError:
                # it's not a number, name, or path
                parser.print_help()
                logging.error("Invalid world name or path")
                doExit(code=1, consoleMsg=False)
        except KeyError:
            # it was an invalid number
            parser.print_help()
            logging.error("Invalid world number")
            doExit(code=1, consoleMsg=False)
    
    # final sanity check for worlddir
    if not os.path.exists(os.path.join(worlddir, 'level.dat')):
        logging.error("Invalid world path -- does not contain level.dat")
        doExit(code=1, consoleMsg=False)

    if len(args) < 2:
        logging.error("Where do you want to save the tiles?")
        doExit(code=1, consoleMsg=False)
    elif len(args) > 2:
        parser.print_help()
        logging.error("Sorry, you specified too many arguments")
        doExit(code=1, consoleMsg=False)


    destdir = os.path.expanduser(args[1])
    if options.display_config:
        # just display the config file and exit
        parser.display_config()
        doExit(code=0, consoleMsg=False)


    if options.regionlist:
        regionlist = map(str.strip, open(options.regionlist, 'r'))
    else:
        regionlist = None

    if options.imgformat:
        if options.imgformat not in ('jpg','png'):
            parser.error("Unknown imgformat!")
        else:
            imgformat = options.imgformat
    else:
        imgformat = 'png'

    if options.optimizeimg:
        optimizeimg = int(options.optimizeimg)
        optimizeimages.check_programs(optimizeimg)
    else:
        optimizeimg = None

    if options.north_direction:
        north_direction = options.north_direction
    else:
        north_direction = 'auto'

    if options.changelist:
        try:
            changefile = open(options.changelist,'w+')
        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)

    if options.changelist_format != "auto" and not options.changelist:
        logging.error("changelist_format specified without changelist.")
        doExit(code=1, consoleMsg=False)
    if options.changelist_format == "auto":
        options.changelist_format = "relative"

    logging.info("Welcome to Minecraft Overviewer!")
    logging.debug("Current log level: {0}".format(logging.getLogger().level))
       
    useBiomeData = os.path.exists(os.path.join(worlddir, 'biomes'))
    if not useBiomeData:
        logging.info("Notice: Not using biome data for tinting")
    
    # make sure that the textures can be found
    try:
        textures.generate(path=options.textures_path)
    except IOError, e:
        logging.error(str(e))
        doExit(code=1, consoleMsg=False)