#!/usr/bin/env python
################################################################################
# Copyright (C) The Qt Company Ltd.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#   * Neither the name of The Qt Company Ltd, nor the names of its contributors
#     may be used to endorse or promote products derived from this software
#     without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
################################################################################

import os
import locale
import sys
import getopt
import subprocess
import re
import shutil
from glob import glob

import common

ignoreErrors = False
debug_build = False
encoding = locale.getdefaultlocale()[1]

def usage():
    print("Usage: %s <existing_qtcreator_binary> [qmake_path]" % os.path.basename(sys.argv[0]))

def which(program):
    def is_exe(fpath):
        return os.path.exists(fpath) and os.access(fpath, os.X_OK)

    fpath = os.path.dirname(program)
    if fpath:
        if is_exe(program):
            return program
        if common.is_windows_platform():
            if is_exe(program + ".exe"):
                return program  + ".exe"
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file
            if common.is_windows_platform():
                if is_exe(exe_file + ".exe"):
                    return exe_file  + ".exe"

    return None

def is_debug(fpath):
    # match all Qt Core dlls from Qt4, Qt5beta2 and Qt5rc1 and later
    # which all have the number at different places
    coredebug = re.compile(r'Qt[1-9]?Core[1-9]?d[1-9]?.dll')
    # bootstrap exception
    if coredebug.search(fpath):
        return True
    output = subprocess.check_output(['dumpbin', '/imports', fpath])
    return coredebug.search(output.decode(encoding)) != None

def op_failed(details = None):
    if details != None:
        print(details)
    if ignoreErrors == False:
        print("Error: operation failed!")
        sys.exit(2)
    else:
        print("Error: operation failed, but proceeding gracefully.")

def is_ignored_windows_file(use_debug, basepath, filename):
    ignore_patterns = ['.lib', '.pdb', '.exp', '.ilk']
    if use_debug:
        ignore_patterns.extend(['libEGL.dll', 'libGLESv2.dll'])
    else:
        ignore_patterns.extend(['libEGLd.dll', 'libGLESv2d.dll'])
    for ip in ignore_patterns:
        if filename.endswith(ip):
            return True
    if filename.endswith('.dll'):
        filepath = os.path.join(basepath, filename)
        if use_debug != is_debug(filepath):
            return True
    return False

def ignored_qt_lib_files(path, filenames):
    if not common.is_windows_platform():
        return []
    return [fn for fn in filenames if is_ignored_windows_file(debug_build, path, fn)]

def copy_qt_libs(target_qt_prefix_path, qt_bin_dir, qt_libs_dir, qt_plugin_dir, qt_import_dir, qt_qml_dir, plugins, imports):
    print("copying Qt libraries...")

    if common.is_windows_platform():
        libraries = glob(os.path.join(qt_libs_dir, '*.dll'))
    else:
        libraries = glob(os.path.join(qt_libs_dir, '*.so.*'))

    if common.is_windows_platform():
        lib_dest = os.path.join(target_qt_prefix_path)
    else:
        lib_dest = os.path.join(target_qt_prefix_path, 'lib')

    if not os.path.exists(lib_dest):
        os.makedirs(lib_dest)

    if common.is_windows_platform():
        libraries = [lib for lib in libraries if not is_ignored_windows_file(debug_build, '', lib)]

    for library in libraries:
        print(library, '->', lib_dest)
        if os.path.islink(library):
            linkto = os.readlink(library)
            try:
                os.symlink(linkto, os.path.join(lib_dest, os.path.basename(library)))
            except OSError:
                op_failed("Link already exists!")
        else:
            shutil.copy(library, lib_dest)

    print("Copying plugins:", plugins)
    for plugin in plugins:
        target = os.path.join(target_qt_prefix_path, 'plugins', plugin)
        if (os.path.exists(target)):
            shutil.rmtree(target)
        pluginPath = os.path.join(qt_plugin_dir, plugin)
        if (os.path.exists(pluginPath)):
            print('{0} -> {1}'.format(pluginPath, target))
            common.copytree(pluginPath, target, ignore=ignored_qt_lib_files, symlinks=True)

    print("Copying imports:", imports)
    for qtimport in imports:
        target = os.path.join(target_qt_prefix_path, 'imports', qtimport)
        if (os.path.exists(target)):
            shutil.rmtree(target)
        import_path = os.path.join(qt_import_dir, qtimport)
        if os.path.exists(import_path):
            print('{0} -> {1}'.format(import_path, target))
            common.copytree(import_path, target, ignore=ignored_qt_lib_files, symlinks=True)

    if (os.path.exists(qt_qml_dir)):
        print("Copying qt quick 2 imports")
        target = os.path.join(target_qt_prefix_path, 'qml')
        if (os.path.exists(target)):
            shutil.rmtree(target)
        print('{0} -> {1}'.format(qt_qml_dir, target))
        common.copytree(qt_qml_dir, target, ignore=ignored_qt_lib_files, symlinks=True)

    print("Copying qtdiag")
    bin_dest = target_qt_prefix_path if common.is_windows_platform() else os.path.join(target_qt_prefix_path, 'bin')
    qtdiag_src = os.path.join(qt_bin_dir, 'qtdiag.exe' if common.is_windows_platform() else 'qtdiag')
    if not os.path.exists(bin_dest):
        os.makedirs(bin_dest)
    shutil.copy(qtdiag_src, bin_dest)

def add_qt_conf(target_path, qt_prefix_path):
    qtconf_filepath = os.path.join(target_path, 'qt.conf')
    prefix_path = os.path.relpath(qt_prefix_path, target_path).replace('\\', '/')
    print('Creating qt.conf in "{0}":'.format(qtconf_filepath))
    f = open(qtconf_filepath, 'w')
    f.write('[Paths]\n')
    f.write('Prefix={0}\n'.format(prefix_path))
    f.write('Binaries={0}\n'.format('bin' if common.is_linux_platform() else '.'))
    f.write('Libraries={0}\n'.format('lib' if common.is_linux_platform() else '.'))
    f.write('Plugins=plugins\n')
    f.write('Imports=imports\n')
    f.write('Qml2Imports=qml\n')
    f.close()

def copy_translations(install_dir, qt_tr_dir):
    translations = glob(os.path.join(qt_tr_dir, '*.qm'))
    tr_dir = os.path.join(install_dir, 'share', 'qtcreator', 'translations')

    print("copying translations...")
    for translation in translations:
        print(translation, '->', tr_dir)
        shutil.copy(translation, tr_dir)

def copyPreservingLinks(source, destination):
    if os.path.islink(source):
        linkto = os.readlink(source)
        destFilePath = destination
        if os.path.isdir(destination):
            destFilePath = os.path.join(destination, os.path.basename(source))
        os.symlink(linkto, destFilePath)
    else:
        shutil.copy(source, destination)

def deploy_libclang(install_dir, llvm_install_dir, chrpath_bin):
    # contains pairs of (source, target directory)
    deployinfo = []
    resourcesource = os.path.join(llvm_install_dir, 'lib', 'clang')
    if common.is_windows_platform():
        clangbindirtarget = os.path.join(install_dir, 'bin', 'clang', 'bin')
        if not os.path.exists(clangbindirtarget):
            os.makedirs(clangbindirtarget)
        clanglibdirtarget = os.path.join(install_dir, 'bin', 'clang', 'lib')
        if not os.path.exists(clanglibdirtarget):
            os.makedirs(clanglibdirtarget)
        deployinfo.append((os.path.join(llvm_install_dir, 'bin', 'libclang.dll'),
                           os.path.join(install_dir, 'bin')))
        deployinfo.append((os.path.join(llvm_install_dir, 'bin', 'clang.exe'),
                           clangbindirtarget))
        deployinfo.append((os.path.join(llvm_install_dir, 'bin', 'clang-cl.exe'),
                           clangbindirtarget))
        deployinfo.append((os.path.join(llvm_install_dir, 'bin', 'clangd.exe'),
                           clangbindirtarget))


from __future__ import unicode_literals

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

from django.core.urlresolvers import reverse

#from django.contrib.auth.models import User
from djangae.contrib.gauth.datastore.models import GaeDatastoreUser
from djangae import fields

from .tools.modified_fields import modified_field as mf

# Create your models here.

@python_2_unicode_compatible
class Character(models.Model):
    """
    This is the main model for character sheets
    """

    LAWFUL_GOOD = 'LG'
    NEUTRAL_GOOD = 'NG'
    CHAOTIC_GOOD = 'CG'
    LAWFUL_NEUTRAL = 'LN'
    TRUE_NEUTRAL = 'TN'
    CHAOTIC_NEUTRAL = 'CN'
    LAWFUL_EVIL = 'LE'
    NEUTRAL_EVIL = 'NE'
    CHAOTIC_EVIL = 'CE'
    ALIGNMENT_CHOICES = (
        (LAWFUL_GOOD, 'Lawful Good'),
        (NEUTRAL_GOOD, 'Nuetral Good'),
        (CHAOTIC_GOOD, 'Chaotic Good'),
        (LAWFUL_NEUTRAL, 'Lawful Neutral'),
        (TRUE_NEUTRAL, 'True Neutral'),
        (CHAOTIC_NEUTRAL, 'Chaotic Neutral'),
        (LAWFUL_EVIL, 'Lawful Evil'),
        (NEUTRAL_EVIL, 'Neutral Evil'),
        (CHAOTIC_EVIL, 'Chaotic Evil'),
    )

    user = models.ForeignKey(GaeDatastoreUser, blank=True, null=True, related_name='user')
    users_with_access = fields.RelatedSetField(GaeDatastoreUser, blank=True, related_name='access_user')

    # Char Details
    player_name = models.CharField(max_length=100, blank=True)
    character_name = models.CharField(max_length=100)
    background = models.CharField(max_length=200, blank=True)
    race = models.CharField(max_length=200, blank=True)
    alignment = models.CharField(max_length=2, choices=ALIGNMENT_CHOICES, default=TRUE_NEUTRAL)
    experience_points = models.IntegerField(null=True, blank=True)
    max_hit_points = models.IntegerField(null=True, blank=True)
    current_hit_points = models.IntegerField(null=True, blank=True)
    temporary_hit_points = models.IntegerField(null=True, blank=True)
    armor_class = models.IntegerField(null=True, blank=True)
    speed = models.IntegerField(null=True, blank=True, default=25)
    inspiration = models.IntegerField(null=True, blank=True)
    proficiency_bonus = models.IntegerField(null=True, blank=True)

    # Hit Dice
    hit_dice_total = models.IntegerField(null=True, blank=True)
    hit_dice = models.CharField(max_length=200, blank=True)

    # Death Saves - I may switch this to a many to many field, but for now
    # I don't think it's worth it.
    death_save_success_1 = models.BooleanField(default=False)
    death_save_success_2 = models.BooleanField(default=False)
    death_save_success_3 = models.BooleanField(default=False)
    death_save_failure_1 = models.BooleanField(default=False)
    death_save_failure_2 = models.BooleanField(default=False)
    death_save_failure_3 = models.BooleanField(default=False)

    # Char Description
    age = models.IntegerField(null=True, blank=True)
    height = models.CharField(max_length=200, blank=True)
    weight = models.IntegerField(null=True, blank=True)
    eyes = models.CharField(max_length=200, blank=True)
    skin = models.CharField(max_length=200, blank=True)
    hair = models.CharField(max_length=200, blank=True)
    character_appearance = models.TextField(blank=True)
    character_backstory = models.TextField(blank=True)
    allies_and_organizations = models.TextField(blank=True)
    additional_features_and_traits = models.TextField(blank=True)

    treasure = models.TextField(blank=True) # Not sure if i want this to be a TextField

    # Main Stats
    strength = models.IntegerField(null=True, blank=True)
    dexterity = models.IntegerField(null=True, blank=True)
    constitution = models.IntegerField(null=True, blank=True)
    intelligence = models.IntegerField(null=True, blank=True)
    wisdom = models.IntegerField(null=True, blank=True)
    charisma = models.IntegerField(null=True, blank=True)

    # Skills
    acrobatics = models.BooleanField(default=False)
    animal_handling = models.BooleanField(default=False)
    arcana = models.BooleanField(default=False)
    athletics = models.BooleanField(default=False)
    deception = models.BooleanField(default=False)
    history = models.BooleanField(default=False)
    insight = models.BooleanField(default=False)
    intimidation = models.BooleanField(default=False)
    investigation = models.BooleanField(default=False)
    medicine = models.BooleanField(default=False)
    nature = models.BooleanField(default=False)
    perception = models.BooleanField(default=False)
    performance = models.BooleanField(default=False)
    persuasion = models.BooleanField(default=False)
    religion = models.BooleanField(default=False)
    sleight_of_hand = models.BooleanField(default=False)
    stealth = models.BooleanField(default=False)
    survival = models.BooleanField(default=False)

    # Saving Throws
    strength_save = models.BooleanField(default=False)
    dexterity_save = models.BooleanField(default=False)
    constitution_save = models.BooleanField(default=False)
    intelligence_save = models.BooleanField(default=False)
    wisdom_save = models.BooleanField(default=False)
    charisma_save = models.BooleanField(default=False)

    personality_traits = models.TextField(blank=True)
    ideals = models.TextField(blank=True)
    bonds = models.TextField(blank=True)
    flaws = models.TextField(blank=True)

    features_and_traits = models.TextField(blank=True)
    other_proficiencies_and_languages = models.TextField(blank=True)

    # Spell Casting Saves
    spell_casting_class = models.CharField(max_length=50, blank=True)
    spellcasting_ability = models.CharField(max_length=50, blank=True)
    spell_save_dc = models.IntegerField(null=True, blank=True)
    spell_attack_bonus = models.IntegerField(null=True, blank=True)

    @property
    def strength_modifier(self):
        return (self.strength - 10) / 2

    @property
    def dexterity_modifier(self):
        return (self.dexterity - 10) / 2

    @property
    def constitution_modifier(self):
        return (self.constitution - 10) / 2

    @property
    def intelligence_modifier(self):
        return (self.intelligence - 10) / 2

    @property
    def wisdom_modifier(self):
        return (self.wisdom - 10) / 2

    @property
    def charisma_modifier(self):
        return (self.charisma - 10) / 2

    @property
    def strength_save_modifier(self):
        return mf(self.strength_save, self.strength_modifier, 2)

    @property
    def dexterity_save_modifier(self):
        return mf(self.dexterity_save, self.dexterity_modifier, 2)

    @property
    def constitution_save_modifier(self):
        return mf(self.constitution_save, self.constitution_modifier, 2)

    @property
    def intelligence_save_modifier(self):
        return mf(self.intelligence_save, self.intelligence_modifier, 2)

    @property
    def wisdom_save_modifier(self):
        return mf(self.wisdom_save, self.wisdom_modifier, 2)

    @property
    def charisma_save_modifier(self):
        return mf(self.charisma_save, self.charisma_modifier, 2)

    @property
    def acrobatics_modifier(self):
        return mf(self.acrobatics, self.dexterity_modifier, 2)

    @property
    def animal_handling_modifier(self):
        return mf(self.animal_handling, self.wisdom_modifier, 2)

    @property
    def arcana_modifier(self):
        return mf(self.arcana, self.intelligence_modifier, 2)

    @property
    def athletics_modifier(self):
        return mf(self.athletics, self.strength_modifier, 2)

    @property
    def deception_modifier(self):
        return mf(self.deception, self.charisma_modifier, 2)

    @property
    def history_modifier(self):
        return mf(self.history, self.intelligence_modifier, 2)

    @property
    def insight_modifier(self):
        return mf(self.insight, self.wisdom_modifier, 2)

    @property
    def intimidation_modifier(self):
        return mf(self.intimidation, self.charisma_modifier, 2)

    @property
    def investigation_modifier(self):
        return mf(self.investigation, self.intelligence_modifier, 2)

    @property
    def medicine_modifier(self):
        return mf(self.medicine, self.wisdom_modifier, 2)

    @property
    def nature_modifier(self):
        return mf(self.nature, self.intelligence_modifier, 2)

    @property
    def perception_modifier(self):
        return mf(self.perception, self.wisdom_modifier, 2)

    @property
    def performance_modifier(self):
        return mf(self.performance, self.charisma_modifier, 2)

    @property
    def persuasion_modifier(self):
        return mf(self.persuasion, self.charisma_modifier, 2)

    @property
    def religion_modifier(self):
        return mf(self.religion, self.intelligence_modifier, 2)

    @property
    def sleight_of_hand_modifier(self):
        return mf(self.sleight_of_hand, self.dexterity_modifier, 2)

    @property
    def stealth_modifier(self):
        return mf(self.stealth, self.dexterity_modifier, 2)

    @property
    def survival_modifier(self):
        return mf(self.survival, self.wisdom_modifier, 2)

    @property
    def initiative(self):
        return self.dexterity_modifier

    @property
    def passive_perception(self):
        return self.perception_modifier + 10

    def __str__(self):
        return self.character_name

    def get_absolute_url(self):
        return reverse('detail', kwargs={'pk': self.pk})

    class Meta:
        verbose_name_plural = "Characters"

class CharacterClass(models.Model):
    """
    Character Class models, Many to One field for character classes
    """
    character = models.ForeignKey(Character)
    char_class = models.CharField(max_length=200)
    level = models.IntegerField(null=True, blank=True)

    class Meta:
        verbose_name_plural = "Character Classes"

class Equipment(models.Model):
    character = models.ForeignKey(Character)
    value = models.CharField(max_length=50, blank=True)
    name = models.CharField(max_length=100)
    description = models.CharField(max_length=500)
    quantity = models.IntegerField()
    weight = models.IntegerField(null=True, blank=True)

    class Meta:
        verbose_name_plural = "Equipment"

class AttacksAndSpellcasting(models.Model):


import copy
from enum import Enum
import logging
import hexgrid
from catan import boardbuilder, states
from catan.pieces import PieceType, Piece

class Board(object):
    """
    class Board represents a catan board. It has tiles, ports, and pieces.

    A Board has pieces, which is a dictionary mapping (hexgrid.TYPE, coord) -> Piece.

    Use #place_piece, #move_piece, and #remove_piece to manage pieces on the board.

    Use #get_pieces to get all the pieces at a particular coordinate of the allowed types.
    """
    def __init__(self, board=None, terrain=None, numbers=None, ports=None, pieces=None, players=None):
        """
        Create a new board. Creation will be delegated to module boardbuilder.

        :param terrain: terrain option, boardbuilder.Opt
        :param numbers: numbers option, boardbuilder.Opt
        :param ports: ports option, boardbuilder.Opt
        :param pieces: pieces option, boardbuilder.Opt
        :param players: players option, boardbuilder.Opt
        """
        self.tiles = list()
        self.ports = list()
        self.state = states.BoardState(self)
        self.pieces = dict()

        self.opts = dict()
        if board is not None:
            self.opts['board'] = board
        if terrain is not None:
            self.opts['terrain'] = terrain
        if numbers is not None:
            self.opts['numbers'] = numbers
        if ports is not None:
            self.opts['ports'] = ports
        if pieces is not None:
            self.opts['pieces'] = pieces
        if players is not None:
            self.opts['players'] = players

        self.reset()
        self.observers = set()

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = object.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            if k == 'observers':
                setattr(result, k, set(v))
            else:
                setattr(result, k, copy.deepcopy(v, memo))
        return result

    def restore(self, board):
        """
        Restore this Board object to match the properties and state of the given Board object
        :param board: properties to restore to the current (self) Board
        """
        self.tiles = board.tiles
        self.ports = board.ports

        self.state = board.state
        self.state.board = self

        self.pieces = board.pieces
        self.opts = board.opts
        self.observers = board.observers

        self.notify_observers()

    def notify_observers(self):
        for obs in self.observers:
            obs.notify(self)

    def lock(self):
        self.state = states.BoardStateLocked(self)
        for port in self.ports.copy():
            if port.type == PortType.none:
                self.ports.remove(port)
        self.notify_observers()

    def unlock(self):
        self.state = states.BoardStateModifiable(self)

    def reset(self, board=None, terrain=None, numbers=None, ports=None, pieces=None, players=None):
        opts = self.opts.copy()
        if board is not None:
            opts['board'] = board
        if terrain is not None:
            opts['terrain'] = terrain
        if numbers is not None:
            opts['numbers'] = numbers
        if ports is not None:
            opts['ports'] = ports
        if pieces is not None:
            opts['pieces'] = pieces
        if players is not None:
            opts['players'] = players
        boardbuilder.reset(self, opts=opts)

    def can_place_piece(self, piece, coord):
        if piece.type == PieceType.road:
            logging.warning('"Can place road" not yet implemented')
            return True
        elif piece.type == PieceType.settlement:
            logging.warning('"Can place settlement" not yet implemented')
            return True
        elif piece.type == PieceType.city:
            logging.warning('"Can place city" not yet implemented')
            return True
        elif piece.type == PieceType.robber:
            logging.warning('"Can place robber" not yet implemented')
            return True
        else:
            logging.debug('Can\'t place piece={} on coord={}'.format(
                piece.value, hex(coord)
            ))
            return self.pieces.get(coord) is None

    def place_piece(self, piece, coord):
        if not self.can_place_piece(piece, coord):
            logging.critical('ILLEGAL: Attempted to place piece={} on coord={}'.format(
                piece.value, hex(coord)
            ))
        logging.debug('Placed piece={} on coord={}'.format(
            piece, hex(coord)
        ))
        hex_type = self._piece_type_to_hex_type(piece.type)
        self.pieces[(hex_type, coord)] = piece

    def move_piece(self, piece, from_coord, to_coord):
        from_index = (self._piece_type_to_hex_type(piece.type), from_coord)
        if from_index not in self.pieces:
            logging.warning('Attempted to move piece={} which was NOT on the board'.format(from_index))
            return
        self.place_piece(piece, to_coord)
        self.remove_piece(piece, from_coord)

    def remove_piece(self, piece, coord):
        index = (self._piece_type_to_hex_type(piece.type), coord)
        try:
            self.pieces.pop(index)
            logging.debug('Removed piece={}'.format(index))
        except ValueError:
            logging.critical('Attempted to remove piece={} which was NOT on the board'.format(index))

    def get_pieces(self, types=tuple(), coord=None):
        if coord is None:
            logging.critical('Attempted to get_piece with coord={}'.format(coord))
            return Piece(None, None)
        indexes = set((self._piece_type_to_hex_type(t), coord) for t in types)
        pieces = [self.pieces[idx] for idx in indexes if idx in self.pieces]
        if len(pieces) == 0:
            #logging.warning('Found zero pieces at {}'.format(indexes))
            pass
        elif len(pieces) == 1:
            logging.debug('Found one piece at {}: {}'.format(indexes, pieces[0]))
        elif len(pieces) > 1:
            logging.debug('Found {} pieces at {}: {}'.format(len(pieces), indexes, coord, pieces))
        return pieces

    def get_port_at(self, tile_id, direction):
        """
        If no port is found, a new none port is made and added to self.ports.

        Returns the port.

        :param tile_id:
        :param direction:
        :return: Port
        """
        for port in self.ports:
            if port.tile_id == tile_id and port.direction == direction:
                return port
        port = Port(tile_id, direction, PortType.none)
        self.ports.append(port)
        return port

    def _piece_type_to_hex_type(self, piece_type):
        if piece_type in (PieceType.road, ):
            return hexgrid.EDGE
        elif piece_type in (PieceType.settlement, PieceType.city):
            return hexgrid.NODE
        elif piece_type in (PieceType.robber, ):
            return hexgrid.TILE
        else:
            logging.critical('piece type={} has no corresponding hex type. Returning None'.format(piece_type))
            return None

    def cycle_hex_type(self, tile_id):
        if self.state.modifiable():
            tile = self.tiles[tile_id - 1]
            next_idx = (list(Terrain).index(tile.terrain) + 1) % len(Terrain)
            next_terrain = list(Terrain)[next_idx]
            tile.terrain = next_terrain
        else:
            logging.debug('Attempted to cycle terrain on tile={} on a locked board'.format(tile_id))
        self.notify_observers()

    def cycle_hex_number(self, tile_id):
        if self.state.modifiable():
            tile = self.tiles[tile_id - 1]
            next_idx = (list(HexNumber).index(tile.number) + 1) % len(HexNumber)
            next_hex_number = list(HexNumber)[next_idx]
            tile.number = next_hex_number
        else:
            logging.debug('Attempted to cycle number on tile={} on a locked board'.format(tile_id))
        self.notify_observers()

    def cycle_port_type(self, tile_id, direction):
        if self.state.modifiable():
            port = self.get_port_at(tile_id, direction)
            port.type = PortType.next_ui(port.type)
        else:
            logging.debug('Attempted to cycle port on coord=({},{}) on a locked board'.format(tile_id, direction))
        self.notify_observers()

    def rotate_ports(self):
        """
        Rotates the ports 90 degrees. Useful when using the default port setup but the spectator is watching
        at a "rotated" angle from "true north".
        """
        for port in self.ports:
            port.tile_id = ((port.tile_id + 1) % len(hexgrid.coastal_tile_ids())) + 1
            port.direction = hexgrid.rotate_direction(hexgrid.EDGE, port.direction, ccw=True)
        self.notify_observers()

    def set_terrain(self, terrain):
        self.tiles = [Tile(tile.tile_id, t, tile.number) for t, tile in zip(terrain, self.tiles)]

    def set_numbers(self, numbers):
        self.tiles = [Tile(tile.tile_id, tile.terrain, n) for n, tile in zip(numbers, self.tiles)]

    def set_ports(self, ports):
        self.ports = ports

class Tile(object):
    """
    class Tile represents a hex tile on the catan board.

    It contains a tile identifier, a terrain type, and a number.
    """
    def __init__(self, tile_id, terrain, number):
        """
        :param tile_id: tile identifier, int, see module hexgrid
        :param terrain: Terrain
        :param number: HexNumber
        :return:
        """
        self.tile_id = tile_id
        self.terrain = terrain
        self.number = number

# Number of tiles on the catan board. This should probably be in module hexgrid.
NUM_TILES = 3+4+5+4+3

class Terrain(Enum):
    wood = 'wood'
    brick = 'brick'
    wheat = 'wheat'
    sheep = 'sheep'
    ore = 'ore'
    desert = 'desert'

    def __repr__(self):
        return self.value

    @staticmethod
    def from_short_form(char):
        if char == 'w':
            return Terrain.wood
        elif char == 'b':
            return Terrain.brick
        elif char == 'h':
            return Terrain.wheat


# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# Script copyright (C) Campbell Barton, Bastien Montagne
# Modified by Jonas Hauquier for python 2.7 compat and MakeHuman FBX export

import array
import datetime

from fbx_utils import *

# Units convertors!
convert_sec_to_ktime = units_convertor("second", "ktime")
convert_sec_to_ktime_iter = units_convertor_iter("second", "ktime")

convert_mm_to_inch = units_convertor("millimeter", "inch")

convert_rad_to_deg = units_convertor("radian", "degree")
convert_rad_to_deg_iter = units_convertor_iter("radian", "degree")

# ##### Templates #####
# TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?

def fbx_template_def_globalsettings(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict()
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"GlobalSettings", b"", props, nbr_users, [False])

def fbx_template_def_null(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict((
        (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
        (b"Size", (100.0, "p_double", False)),
        (b"Look", (1, "p_enum", False)),  # Cross (0 is None, i.e. invisible?).
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"NodeAttribute", b"FbxNull", props, nbr_users, [False])

def fbx_template_def_bone(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict()
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"NodeAttribute", b"LimbNode", props, nbr_users, [False])

def fbx_template_def_geometry(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict((
        (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
        (b"BBoxMin", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"BBoxMax", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"Primary Visibility", (True, "p_bool", False)),
        (b"Casts Shadows", (True, "p_bool", False)),
        (b"Receive Shadows", (True, "p_bool", False)),
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"Geometry", b"FbxMesh", props, nbr_users, [False])

def fbx_template_def_material(scene, settings, override_defaults=None, nbr_users=0):
    # WIP...
    props = OrderedDict((
        (b"ShadingModel", ("Phong", "p_string", False)),
        (b"MultiLayer", (False, "p_bool", False)),
        # Lambert-specific.
        (b"EmissiveColor", ((0.0, 0.0, 0.0), "p_color", True)),
        (b"EmissiveFactor", (1.0, "p_number", True)),
        (b"AmbientColor", ((0.2, 0.2, 0.2), "p_color", True)),
        (b"AmbientFactor", (1.0, "p_number", True)),
        (b"DiffuseColor", ((0.8, 0.8, 0.8), "p_color", True)),
        (b"DiffuseFactor", (1.0, "p_number", True)),
        (b"TransparentColor", ((0.0, 0.0, 0.0), "p_color", True)),
        (b"TransparencyFactor", (0.0, "p_number", True)),
        (b"Opacity", (1.0, "p_number", True)),
        (b"NormalMap", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"Bump", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"BumpFactor", (1.0, "p_double", False)),
        (b"DisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
        (b"DisplacementFactor", (1.0, "p_double", False)),
        (b"VectorDisplacementColor", ((0.0, 0.0, 0.0), "p_color_rgb", False)),
        (b"VectorDisplacementFactor", (1.0, "p_double", False)),
        # Phong-specific.
        (b"SpecularColor", ((0.2, 0.2, 0.2), "p_color", True)),
        (b"SpecularFactor", (1.0, "p_number", True)),
        # Not sure about the name, importer uses this (but ShininessExponent for tex prop name!)
        # And in fbx exported by sdk, you have one in template, the other in actual material!!! :/
        # For now, using both.
        (b"Shininess", (20.0, "p_number", True)),
        (b"ShininessExponent", (20.0, "p_number", True)),
        (b"ReflectionColor", ((0.0, 0.0, 0.0), "p_color", True)),
        (b"ReflectionFactor", (1.0, "p_number", True)),
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"Material", b"FbxSurfacePhong", props, nbr_users, [False])

def fbx_template_def_texture_file(scene, settings, override_defaults=None, nbr_users=0):
    # WIP...
    # XXX Not sure about all names!
    props = OrderedDict((
        (b"TextureTypeUse", (0, "p_enum", False)),  # Standard.
        (b"AlphaSource", (2, "p_enum", False)),  # Black (i.e. texture's alpha), XXX name guessed!.
        (b"Texture alpha", (1.0, "p_double", False)),
        (b"PremultiplyAlpha", (True, "p_bool", False)),
        (b"CurrentTextureBlendMode", (1, "p_enum", False)),  # Additive...
        (b"CurrentMappingType", (0, "p_enum", False)),  # UV.
        (b"UVSet", ("default", "p_string", False)),  # UVMap name.
        (b"WrapModeU", (0, "p_enum", False)),  # Repeat.
        (b"WrapModeV", (0, "p_enum", False)),  # Repeat.
        (b"UVSwap", (False, "p_bool", False)),
        (b"Translation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"Rotation", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"Scaling", ((1.0, 1.0, 1.0), "p_vector_3d", False)),
        (b"TextureRotationPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        (b"TextureScalingPivot", ((0.0, 0.0, 0.0), "p_vector_3d", False)),
        # Not sure about those two...
        (b"UseMaterial", (False, "p_bool", False)),
        (b"UseMipMap", (False, "p_bool", False)),
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"Texture", b"FbxFileTexture", props, nbr_users, [False])

def fbx_template_def_pose(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict()
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"Pose", b"", props, nbr_users, [False])

def fbx_template_def_deformer(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict()
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"Deformer", b"", props, nbr_users, [False])

def fbx_template_def_animstack(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict((
        (b"Description", ("", "p_string", False)),
        (b"LocalStart", (0, "p_timestamp", False)),
        (b"LocalStop", (0, "p_timestamp", False)),
        (b"ReferenceStart", (0, "p_timestamp", False)),
        (b"ReferenceStop", (0, "p_timestamp", False)),
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"AnimationStack", b"FbxAnimStack", props, nbr_users, [False])

def fbx_template_def_animlayer(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict((
        (b"Weight", (100.0, "p_number", True)),
        (b"Mute", (False, "p_bool", False)),
        (b"Solo", (False, "p_bool", False)),
        (b"Lock", (False, "p_bool", False)),
        (b"Color", ((0.8, 0.8, 0.8), "p_color_rgb", False)),
        (b"BlendMode", (0, "p_enum", False)),
        (b"RotationAccumulationMode", (0, "p_enum", False)),
        (b"ScaleAccumulationMode", (0, "p_enum", False)),
        (b"BlendModeBypass", (0, "p_ulonglong", False)),
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"AnimationLayer", b"FbxAnimLayer", props, nbr_users, [False])

def fbx_template_def_animcurvenode(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict((
        (FBX_ANIM_PROPSGROUP_NAME.encode(), (None, "p_compound", False)),
    ))
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"AnimationCurveNode", b"FbxAnimCurveNode", props, nbr_users, [False])

def fbx_template_def_animcurve(scene, settings, override_defaults=None, nbr_users=0):
    props = OrderedDict()
    if override_defaults is not None:
        props.update(override_defaults)
    return FBXTemplate(b"AnimationCurve", b"", props, nbr_users, [False])

# ##### Generators for connection elements. #####

def elem_connection(elem, c_type, uid_src, uid_dst, prop_dst=None):
    e = elem_data_single_string(elem, b"C", c_type)
    e.add_int64(uid_src)
    e.add_int64(uid_dst)
    if prop_dst is not None:
        e.add_string(prop_dst)

# ##### FBX objects generators. #####

def fbx_data_element_custom_properties(props, bid):
    """
    Store custom properties of blender ID bid (any mapping-like object, in fact) into FBX properties props.
    """
    for k, v in bid.items():
        list_val = getattr(v, "to_list", lambda: None)()

        if isinstance(v, str):
            elem_props_set(props, "p_string", k.encode(), v, custom=True)
        elif isinstance(v, int):
            elem_props_set(props, "p_integer", k.encode(), v, custom=True)
        elif isinstance(v, float):
            elem_props_set(props, "p_double", k.encode(), v, custom=True)
        elif list_val and len(list_val) == 3:
            elem_props_set(props, "p_vector", k.encode(), list_val, custom=True)

def fbx_data_bindpose_element(objectsParent, key, id, count):
    """
    Helper, since bindpose are used by both meshes shape keys and armature bones...
    """
    # We assume bind pose for our bones are their "Editmode" pose...
    # All matrices are expected in global (world) space.
    fbx_pose = elem_data_single_int64(objectsParent, b"Pose", id)
    fbx_pose.add_string(fbx_name_class(key.encode()))
    fbx_pose.add_string(b"BindPose")

    elem_data_single_string(fbx_pose, b"Type", b"BindPose")
    elem_data_single_int32(fbx_pose, b"Version", FBX_POSE_BIND_VERSION)
    elem_data_single_int32(fbx_pose, b"NbPoseNodes", count)

    return fbx_pose

def fbx_data_pose_node_element(bindposeParent, key, id, bindmat):
    fbx_posenode = elem_empty(bindposeParent, b"PoseNode")
    elem_data_single_int64(fbx_posenode, b"Node", id)
    elem_data_single_float64_array(fbx_posenode, b"Matrix", bindmat.ravel(order='F'))  # Use column-major order

def fbx_data_mesh_element(objectsParent, key, id, properties, coord, fvert, vnorm, texco, fuv):
    geom = elem_data_single_int64(objectsParent, b"Geometry", id)  #get_fbx_uuid_from_key(key))
    geom.add_string(fbx_name_class(key.encode()))
    geom.add_string(b"Mesh")

    name = key.split('::')[1]

    props = elem_properties(geom)

    for name, ptype, value, animatable, custom in get_properties(properties):
        elem_props_set(props, ptype, name, value, animatable, custom)
        #fbx_data_element_custom_properties(props, me)

    # Vertex cos.
    t_co = array.array(data_types.ARRAY_FLOAT64, coord.reshape(-1))
    elem_data_single_float64_array(geom, b"Vertices", t_co)
    del t_co

    # Polygon indices.
    # Bitwise negate last index to mark end of polygon loop
    fvert_ = fvert.copy()
    if fvert.shape[1] == 3:
        #fvert_[:,2] = -1 - fvert_[:,2]
        fvert_[:,2] = ~fvert_[:,2]
    else:
        fvert_[:,3] = ~fvert_[:,3]
    import numpy as np
    t_pvi = array.array(data_types.ARRAY_INT32, fvert_.astype(np.int32).reshape(-1))
    elem_data_single_int32_array(geom, b"PolygonVertexIndex", t_pvi)

    #elem_data_single_int32_array(geom, b"Edges", t_eli)
    del t_pvi

    # Layers

    # Normals
    t_ln = array.array(data_types.ARRAY_FLOAT64, vnorm.reshape(-1))

    lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0)
    elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION)
    elem_data_single_string(lay_nor, b"Name", (name+"_Normal").encode())
    elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
    elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")

    elem_data_single_float64_array(lay_nor, b"Normals", t_ln)

    elem_data_single_int32_array(lay_nor, b"NormalsIndex", fvert.reshape(-1))
    del t_ln

    # TODO export tangents
    '''
    # tspace
    tspacenumber = len(me.uv_layers)
    if tspacenumber:
        t_ln = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
        # t_lnw = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops)
        for idx, uvlayer in enumerate(me.uv_layers):
            name = uvlayer.name
            me.calc_tangents(name)
            # Loop bitangents (aka binormals).
            # NOTE: this is not supported by importer currently.
            me.loops.foreach_get("bitangent", t_ln)
            lay_nor = elem_data_single_int32(geom, b"LayerElementBinormal", idx)
            elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_BINORMAL_VERSION)
            elem_data_single_string_unicode(lay_nor, b"Name", name)
            elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
            elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
            elem_data_single_float64_array(lay_nor, b"Binormals",
                                           chain(*nors_transformed_gen(t_ln, geom_mat_no)))
            # Binormal weights, no idea what it is.
            # elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)

            # Loop tangents.
            # NOTE: this is not supported by importer currently.
            me.loops.foreach_get("tangent", t_ln)
            lay_nor = elem_data_single_int32(geom, b"LayerElementTangent", idx)
            elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_TANGENT_VERSION)
            elem_data_single_string_unicode(lay_nor, b"Name", name)
            elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
            elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
            elem_data_single_float64_array(lay_nor, b"Tangents",
                                           chain(*nors_transformed_gen(t_ln, geom_mat_no)))
            # Tangent weights, no idea what it is.
            # elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)

        # del t_lnw
    '''

    # TODO export vertex colors
    '''
    # VertexColor Layers.
    vcolnumber = len(me.vertex_colors)
    if vcolnumber:
        def _coltuples_gen(raw_cols):
            return zip(*(iter(raw_cols),) * 3 + (_infinite_gen(1.0),))  # We need a fake alpha...

        t_lc = array.array(data_types.ARRAY_FLOAT64, (0.0,)) * len(me.loops) * 3
        for colindex, collayer in enumerate(me.vertex_colors):
            collayer.data.foreach_get("color", t_lc)
            lay_vcol = elem_data_single_int32(geom, b"LayerElementColor", colindex)
            elem_data_single_int32(lay_vcol, b"Version", FBX_GEOMETRY_VCOLOR_VERSION)
            elem_data_single_string_unicode(lay_vcol, b"Name", collayer.name)
            elem_data_single_string(lay_vcol, b"MappingInformationType", b"ByPolygonVertex")
            elem_data_single_string(lay_vcol, b"ReferenceInformationType", b"IndexToDirect")

            col2idx = tuple(set(_coltuples_gen(t_lc)))
            elem_data_single_float64_array(lay_vcol, b"Colors", chain(*col2idx))  # Flatten again...

            col2idx = {col: idx for idx, col in enumerate(col2idx)}
            elem_data_single_int32_array(lay_vcol, b"ColorIndex", (col2idx[c] for c in _coltuples_gen(t_lc)))
            del col2idx
        del t_lc
        del _coltuples_gen
    '''

    # UV layers.
    # Note: LayerElementTexture is deprecated since FBX 2011 - luckily!
    #       Textures are now only related to materials, in FBX!
    t_uv = array.array(data_types.ARRAY_FLOAT64, texco.reshape(-1))
    t_fuv = array.array(data_types.ARRAY_INT32, fuv.reshape(-1))
    uvindex = 0
    lay_uv = elem_data_single_int32(geom, b"LayerElementUV", uvindex)
    elem_data_single_int32(lay_uv, b"Version", FBX_GEOMETRY_UV_VERSION)
    elem_data_single_string_unicode(lay_uv, b"Name", (name+"_UV").encode())
    elem_data_single_string(lay_uv, b"MappingInformationType", b"ByPolygonVertex")
    elem_data_single_string(lay_uv, b"ReferenceInformationType", b"IndexToDirect")

    # TODO verify whether this crashes FBX converter as well (and needs a hack like fbx_mesh.writeUvs2)
    elem_data_single_float64_array(lay_uv, b"UV", t_uv)
    elem_data_single_int32_array(lay_uv, b"UVIndex", t_fuv)

    del t_fuv
    del t_uv

    # Face's materials.
    lay_mat = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
    elem_data_single_int32(lay_mat, b"Version", FBX_GEOMETRY_MATERIAL_VERSION)
    elem_data_single_string(lay_mat, b"Name", (name+"_Material").encode())

    elem_data_single_string(lay_mat, b"MappingInformationType", b"AllSame")
    elem_data_single_string(lay_mat, b"ReferenceInformationType", b"IndexToDirect")
    elem_data_single_int32_array(lay_mat, b"Materials", [0])

    # Layer TOC
    layer = elem_data_single_int32(geom, b"Layer", 0)
    elem_data_single_int32(layer, b"Version", FBX_GEOMETRY_LAYER_VERSION)

    lay_nor = elem_empty(layer, b"LayerElement")
    elem_data_single_string(lay_nor, b"Type", b"LayerElementNormal")
    elem_data_single_int32(lay_nor, b"TypedIndex", 0)

    # TODO tangents
    '''
    lay_binor = elem_empty(layer, b"LayerElement")
    elem_data_single_string(lay_binor, b"Type", b"LayerElementBinormal")
    elem_data_single_int32(lay_binor, b"TypedIndex", 0)
    lay_tan = elem_empty(layer, b"LayerElement")
    elem_data_single_string(lay_tan, b"Type", b"LayerElementTangent")
    elem_data_single_int32(lay_tan, b"TypedIndex", 0)
    '''

    # TODO vertex colors
    '''
    if vcolnumber:
        lay_vcol = elem_empty(layer, b"LayerElement")
        elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
        elem_data_single_int32(lay_vcol, b"TypedIndex", 0)
    '''

    lay_uv = elem_empty(layer, b"LayerElement")
    elem_data_single_string(lay_uv, b"Type", b"LayerElementUV")
    elem_data_single_int32(lay_uv, b"TypedIndex", 0)

    lay_mat = elem_empty(layer, b"LayerElement")
    elem_data_single_string(lay_mat, b"Type", b"LayerElementMaterial")
    elem_data_single_int32(lay_mat, b"TypedIndex", 0)

    # Shape keys
    #fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, tmpl, props)

def fbx_data_model_element(objectsParent, key, id, properties):
    mod = elem_data_single_int64(objectsParent, b"Model", id)
    mod.add_string(fbx_name_class(key.encode()))
    mod.add_string(b"Mesh")

    elem_data_single_int32(mod, b"Version", FBX_MODELS_VERSION)

    props = elem_properties(mod)
    for name, ptype, value, animatable, custom in get_properties(properties):
        elem_props_set(props, ptype, name, value, animatable, custom)

    elem_data_single_bool(mod, b"Shading", True)
    elem_data_single_string(mod, b"Culling", "CullingOff")

def fbx_data_texture_file_element(objectsParent, key, id, video_key, video_id, texpath, texpath_rel, properties_tex, properties_vid):
    """
    Write the (file) Texture data block.
    """
    # XXX All this is very fuzzy to me currently...
    #     Textures do not seem to use properties as much as they could.
    #     For now assuming most logical and simple stuff.

    fbx_vid = elem_data_single_int64(objectsParent, b"Video", video_id)
    fbx_vid.add_string(fbx_name_class(video_key.encode()))
    fbx_vid.add_string(b"Clip")

    props = elem_properties(fbx_vid)
    for name, ptype, value, animatable, custom in get_properties(properties_vid):
        elem_props_set(props, ptype, name, value, animatable, custom)

    elem_data_single_int32(fbx_vid, b"UseMipMap", 0)
    elem_data_single_string_unicode(fbx_vid, b"Filename", texpath)
    elem_data_single_string_unicode(fbx_vid, b"RelativeFilename", texpath_rel)

    fbx_tex = elem_data_single_int64(objectsParent, b"Texture", id)
    fbx_tex.add_string(fbx_name_class(key.encode()))
    fbx_tex.add_string(b"")

    elem_data_single_string(fbx_tex, b"Type", b"TextureVideoClip")
    elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION)
    elem_data_single_string(fbx_tex, b"TextureName", fbx_name_class(key.encode()))
    elem_data_single_string(fbx_tex, b"Media", video_key)
    elem_data_single_string_unicode(fbx_tex, b"FileName", texpath)
    elem_data_single_string_unicode(fbx_tex, b"RelativeFilename", texpath_rel)

    elem_data_single_int32(fbx_tex, b"Version", FBX_TEXTURE_VERSION)
    elem_data_single_float32_array(fbx_tex, b"ModelUVTranslation", [0,0])
    elem_data_single_float32_array(fbx_tex, b"ModelUVScaling", [1,1])
    elem_data_single_string(fbx_tex, b"Texture_Alpha_Source", "None")
    elem_data_single_int32_array(fbx_tex, b"Cropping", [0,0,0,0])

    props = elem_properties(fbx_tex)
    for name, ptype, value, animatable, custom in get_properties(properties_tex):
        elem_props_set(props, ptype, name, value, animatable, custom)

    # UseMaterial should always be ON imho.
    elem_props_set(props, "p_bool", b"UseMaterial", True)

def fbx_data_skeleton_bone_model(objectsParent, key, id, properties):
    # Bone "data".

    #id = get_fbx_uuid_from_key(boneDataKey)

    fbx_bo = elem_data_single_int64(objectsParent, b"Model", id)
    fbx_bo.add_string(fbx_name_class(key.encode()))
    #fbx_bo.add_string(fbx_name_class(boneName.encode(), b"NodeAttribute"))
    fbx_bo.add_string(b"LimbNode")
    elem_data_single_int32(fbx_bo, b"Version", FBX_MODELS_VERSION)
    elem_data_single_bool(fbx_bo, b"Shading", True)
    elem_data_single_string(fbx_bo, b"Culling", "CullingOff")

    props = elem_properties(fbx_bo)
    for name, ptype, value, animatable, custom in get_properties(properties):
        elem_props_set(props, ptype, name, value, animatable, custom)

def fbx_data_skeleton_bone_node(objectsParent, key, id, properties):
    # Bone "data".

    #id = get_fbx_uuid_from_key(boneDataKey)

    fbx_bo = elem_data_single_int64(objectsParent, b"NodeAttribute", id)
    fbx_bo.add_string(fbx_name_class(key.encode()))
    fbx_bo.add_string(b"LimbNode")
    elem_data_single_string(fbx_bo, b"TypeFlags", b"Skeleton")

    props = elem_properties(fbx_bo)
    for name, ptype, value, animatable, custom in get_properties(properties):
        elem_props_set(props, ptype, name, value, animatable, custom)

def fbx_data_skeleton_model(objectsParent, key, id, properties):
    # Skeleton null object (has no data).

    #id = get_fbx_uuid_from_key(boneDataKey)

    fbx_bo = elem_data_single_int64(objectsParent, b"Model", id)
    fbx_bo.add_string(fbx_name_class(key.encode()))
    fbx_bo.add_string(b"Null")
    elem_data_single_int32(fbx_bo, b"Version", FBX_MODELS_VERSION)
    elem_data_single_bool(fbx_bo, b"Shading", True)
    elem_data_single_string(fbx_bo, b"Culling", "CullingOff")

    props = elem_properties(fbx_bo)
    for name, ptype, value, animatable, custom in get_properties(properties):
        elem_props_set(props, ptype, name, value, animatable, custom)

def fbx_data_deformer(objectsParent, key, id, properties):
    fbx_skin = elem_data_single_int64(objectsParent, b"Deformer", id)
    fbx_skin.add_string(fbx_name_class(key))
    fbx_skin.add_string(b"Skin")

    props = elem_properties(fbx_skin)
    for name, ptype, value, animatable, custom in get_properties(properties):
        elem_props_set(props, ptype, name, value, animatable, custom)

    elem_data_single_int32(fbx_skin, b"Version", FBX_DEFORMER_SKIN_VERSION)
    elem_data_single_float64(fbx_skin, b"Link_DeformAcuracy", 50.0)  # Only vague idea what it is...

def fbx_data_subdeformer(objectsParent, key, id, indices, weights, bindmat, bindinv):
    # Create the cluster.
    fbx_clstr = elem_data_single_int64(objectsParent, b"Deformer", id)
    fbx_clstr.add_string(fbx_name_class(key))
    fbx_clstr.add_string(b"Cluster")

    elem_data_single_int32(fbx_clstr, b"Version", FBX_DEFORMER_CLUSTER_VERSION)
    # No idea what that user data might be...
    fbx_userdata = elem_data_single_string(fbx_clstr, b"UserData", b"")
    fbx_userdata.add_string(b"")
    elem_data_single_int32_array(fbx_clstr, b"Indexes", indices)
    elem_data_single_float64_array(fbx_clstr, b"Weights", weights)
    # Transform, TransformLink and TransformAssociateModel matrices...
    # They seem to be doublons of BindPose ones??? Have armature (associatemodel) in addition, though.
    # WARNING! Even though official FBX API presents Transform in global space,
    #          **it is stored in bone space in FBX data!** See:
    #          http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-
    #                 by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
    elem_data_single_float64_array(fbx_clstr, b"Transform", bindmat.ravel(order='F'))
    elem_data_single_float64_array(fbx_clstr, b"TransformLink", bindinv.ravel(order='F'))
    #elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm))

# ##### Top-level FBX elements generators. #####

def fbx_header_elements(root, config, time=None):
    """
    Write boiling code of FBX root.
    time is expected to be a datetime.datetime object, or None (using now() in this case).
    """
    import makehuman
    app_vendor = "MakeHuman.org"
    app_name = "MakeHuman"
    app_ver = makehuman.getVersionStr()
    # ##### Start of FBXHeaderExtension element.
    header_ext = elem_empty(root, b"FBXHeaderExtension")

    elem_data_single_int32(header_ext, b"FBXHeaderVersion", FBX_HEADER_VERSION)

    elem_data_single_int32(header_ext, b"FBXVersion", FBX_VERSION)

    # No encryption!
    elem_data_single_int32(header_ext, b"EncryptionType", 0)

    if time is None:
        time = datetime.datetime.now()
    elem = elem_empty(header_ext, b"CreationTimeStamp")
    elem_data_single_int32(elem, b"Version", 1000)
    elem_data_single_int32(elem, b"Year", time.year)
    elem_data_single_int32(elem, b"Month", time.month)
    elem_data_single_int32(elem, b"Day", time.day)
    elem_data_single_int32(elem, b"Hour", time.hour)
    elem_data_single_int32(elem, b"Minute", time.minute)
    elem_data_single_int32(elem, b"Second", time.second)
    elem_data_single_int32(elem, b"Millisecond", time.microsecond // 1000)

    elem_data_single_string_unicode(header_ext, b"Creator", "%s - %s" % (app_name, app_ver))

    # 'SceneInfo' seems mandatory to get a valid FBX file...
    # TODO use real values!
    # XXX Should we use scene.name.encode() here?
    scene_info = elem_data_single_string(header_ext, b"SceneInfo", fbx_name_class(b"GlobalInfo", b"SceneInfo"))
    scene_info.add_string(b"UserData")
    elem_data_single_string(scene_info, b"Type", b"UserData")
    elem_data_single_int32(scene_info, b"Version", FBX_SCENEINFO_VERSION)
    meta_data = elem_empty(scene_info, b"MetaData")
    elem_data_single_int32(meta_data, b"Version", FBX_SCENEINFO_VERSION)
    elem_data_single_string(meta_data, b"Title", b"")
    elem_data_single_string(meta_data, b"Subject", b"")
    elem_data_single_string(meta_data, b"Author", b"")
    elem_data_single_string(meta_data, b"Keywords", b"")
    elem_data_single_string(meta_data, b"Revision", b"")
    elem_data_single_string(meta_data, b"Comment", b"")

    props = elem_properties(scene_info)
    elem_props_set(props, "p_string_url", b"DocumentUrl", "/foobar.fbx")    # TODO set to current export filename?
    elem_props_set(props, "p_string_url", b"SrcDocumentUrl", "/foobar.fbx")
    original = elem_props_compound(props, b"Original")
    original("p_string", b"ApplicationVendor", app_vendor)
    original("p_string", b"ApplicationName", app_name)
    original("p_string", b"ApplicationVersion", app_ver)
    original("p_datetime", b"DateTime_GMT", "01/01/1970 00:00:00.000")
    original("p_string", b"FileName", "/foobar.fbx")
    lastsaved = elem_props_compound(props, b"LastSaved")
    lastsaved("p_string", b"ApplicationVendor", app_vendor)
    lastsaved("p_string", b"ApplicationName", app_name)
    lastsaved("p_string", b"ApplicationVersion", app_ver)
    lastsaved("p_datetime", b"DateTime_GMT", "01/01/1970 00:00:00.000")

    # ##### End of FBXHeaderExtension element.

    # FileID is replaced by dummy value currently...
    elem_data_single_bytes(root, b"FileId", b"FooBar")

    # CreationTime is replaced by dummy value currently, but anyway...
    elem_data_single_string_unicode(root, b"CreationTime",
                                    "{:04}-{:02}-{:02} {:02}:{:02}:{:02}:{:03}"
                                    "".format(time.year, time.month, time.day, time.hour, time.minute, time.second,
                                              time.microsecond * 1000))

    elem_data_single_string_unicode(root, b"Creator", "%s - %s" % (app_name, app_ver))

    # ##### Start of GlobalSettings element.
    global_settings = elem_empty(root, b"GlobalSettings")

    elem_data_single_int32(global_settings, b"Version", 1000)

    props = elem_properties(global_settings)

    mesh_orientation = getMeshOrientation(config)
    up_axis, front_axis, coord_axis = RIGHT_HAND_AXES[mesh_orientation]
    # Currently not sure about that, but looks like default unit of FBX is cm...
    #scale_factor = 10.0/config.scale  # MH scales the mesh coordinates, the scale factor is a constant
    scale_factor = 10
    elem_props_set(props, "p_integer", b"UpAxis", up_axis[0])
    elem_props_set(props, "p_integer", b"UpAxisSign", up_axis[1])
    elem_props_set(props, "p_integer", b"FrontAxis", front_axis[0])
    elem_props_set(props, "p_integer", b"FrontAxisSign", front_axis[1])
    elem_props_set(props, "p_integer", b"CoordAxis", coord_axis[0])
    elem_props_set(props, "p_integer", b"CoordAxisSign", coord_axis[1])
    elem_props_set(props, "p_integer", b"OriginalUpAxis", -1)
    elem_props_set(props, "p_integer", b"OriginalUpAxisSign", 1)
    elem_props_set(props, "p_double", b"UnitScaleFactor", scale_factor)
    elem_props_set(props, "p_double", b"OriginalUnitScaleFactor", scale_factor)
    elem_props_set(props, "p_color_rgb", b"AmbientColor", (0.0, 0.0, 0.0))
    elem_props_set(props, "p_string", b"DefaultCamera", "Producer Perspective")

    # Global timing data.
    elem_props_set(props, "p_enum", b"TimeMode", 0)
    elem_props_set(props, "p_timestamp", b"TimeSpanStart", 0)
    elem_props_set(props, "p_timestamp", b"TimeSpanStop", 46186158000)
    elem_props_set(props, "p_double", b"CustomFrameRate", -1)

    # ##### End of GlobalSettings element.

def fbx_documents_elements(root, name, id):
    """
    Write 'Document' part of FBX root.
    Seems like FBX support multiple documents, but until I find examples of such, we'll stick to single doc!
    time is expected to be a datetime.datetime object, or None (using now() in this case).
    """
    # ##### Start of Documents element.
    docs = elem_empty(root, b"Documents")

    elem_data_single_int32(docs, b"Count", 1)


"""
ROKET (erROr breaKdown Estimation Tool)

Computes the error breakdown during a COMPASS simulation
and saves it in a HDF5 file
Error contributors are bandwidth, tomography, noise, aliasing,
WFS non linearity, filtered modes and fitting
Saved file contained temporal buffers of those contributors
"""

import cProfile
import pstats as ps

import sys, os
import numpy as np
from shesha.supervisor.compassSupervisor import CompassSupervisor
from shesha.util.rtc_util import centroid_gain
from shesha.ao.tomo import create_nact_geom
from shesha.ao.basis import compute_btt, compute_cmat_with_Btt
import shesha.constants as scons
import time
import matplotlib.pyplot as pl
pl.ion()
import shesha.util.hdf5_util as h5u
import pandas
from scipy.sparse import csr_matrix

class Roket(CompassSupervisor):
    """
    ROKET class
    Inherits from CompassSupervisor
    """

    def __init__(self, str=None, N_preloop=1000, gamma=1.):
        """
        Initializes an instance of Roket class

        Args:
            str: (str): (optional) path to a parameter file
            N_preloop: (int): (optional) number of iterations before starting error breakdown estimation
            gamma: (float): (optional) centroid gain
        """
        super().__init__(str)
        self.N_preloop = N_preloop
        self.gamma = gamma

    def init_config(self):
        """
        Initializes the COMPASS simulation and the ROKET buffers
        """
        #super().init_config()
        self.iter_number = 0
        self.n = self.config.p_loop.niter + self.N_preloop
        #self.nfiltered = int(self.config.p_controllers[0].maxcond)
        self.nfiltered = 20
        #self.nactus = self.get_command(0).size
        self.nactus = self.rtc.get_command(0).size
        self.nslopes = self.rtc.get_slopes(0).size
        self.com = np.zeros((self.n, self.nactus), dtype=np.float32)
        self.noise_com = np.zeros((self.n, self.nactus), dtype=np.float32)
        self.noise_buf = np.zeros((self.n, self.nactus), dtype=np.float32)
        self.alias_wfs_com = np.copy(self.noise_com)
        self.ageom = np.copy(self.noise_com)
        self.alias_meas = np.zeros((self.n, self.nslopes), dtype=np.float32)
        self.wf_com = np.copy(self.noise_com)
        self.tomo_com = np.copy(self.noise_com)
        self.tomo_buf = np.copy(self.noise_com)
        self.trunc_com = np.copy(self.noise_com)
        self.trunc_buf = np.copy(self.noise_com)
        self.trunc_meas = np.copy(self.alias_meas)
        self.H_com = np.copy(self.noise_com)
        self.mod_com = np.copy(self.noise_com)
        self.bp_com = np.copy(self.noise_com)
        self.fit = np.zeros(self.n)
        self.psf_ortho = self.target.get_tar_image(0) * 0
        self.centroid_gain = 0
        self.centroid_gain2 = 0
        self.slopes = np.zeros((self.n, self.nslopes), dtype=np.float32)
        #gamma = 1.0
        self.config.p_loop.set_niter(self.n)
        #self.IFpzt = self.get_influ_basis_sparse(1)
        self.IFpzt = self.rtc._rtc.d_control[1].d_IFsparse.get_csr().astype(np.float32)
        #self.IFpzt.P = scipy.sparse.csr_matrix.transpose(self.IFpzt.get_csr())
        #self.TT = self.get_tt_influ_basis(1)
        self.TT = np.array(self.rtc._rtc.d_control[1].d_TT)

        self.Btt, self.P = compute_btt(self.IFpzt.T, self.TT)
        tmp = self.Btt.dot(self.Btt.T)
        self.rtc._rtc.d_control[1].load_Btt(tmp[:-2, :-2], tmp[-2:, -2:])
        compute_cmat_with_Btt(self.rtc._rtc, self.Btt, self.nfiltered)
        self.cmat = self.rtc.get_command_matrix(0)
        self.D = self.rtc.get_interaction_matrix(0)
        self.RD = np.dot(self.cmat, self.D)
        # self.gRD = np.identity(
        #         self.RD.
        #         shape[0]) - self.config.p_controllers[0].gain * self.gamma * self.RD
        self.gRD = self.config.p_controllers[0].gain * self.gamma * self.RD

        self.Nact = create_nact_geom(self.config.p_dms[0])
        self.delay = int(self.config.p_controllers[0].delay) + 1

    def loop_next(self, **kwargs):
        """
            function next
            Iterates the AO loop, with optional parameters

        :param move_atmos (bool): move the atmosphere for this iteration, default: True
        :param nControl (int): Controller number to use, default 0 (single control configurations)
        :param tar_trace (None or list[int]): list of targets to trace. None equivalent to all.
        :param wfs_trace (None or list[int]): list of WFS to trace. None equivalent to all.
        :param apply_control (bool): (optional) if True (default), apply control on DMs
        """
        self.next(apply_control=False)
        self.error_breakdown()
        self.rtc.apply_control(0)
        self.iter_number += 1

    def loop(self, monitoring_freq=100, **kwargs):
        """
        Performs the AO loop for n iterations

        Args:
            monitoring_freq: (int): (optional) Loop monitoring frequency [frames] in the terminal
        """
        print("-----------------------------------------------------------------")
        print("iter# | SE SR | LE SR | FIT SR | PH SR | ETR (s) | Framerate (Hz)")
        print("-----------------------------------------------------------------")
        t0 = time.time()
        for i in range(self.n):
            self.loop_next(**kwargs)
            if ((i + 1) % monitoring_freq == 0):
                framerate = (i + 1) / (time.time() - t0)
                # self.target.comp_tar_image(0)
                # self.target.comp_strehl(0)
                strehltmp = self.target.get_strehl(0)
                etr = (self.n - i) / framerate
                print("%d \t %.2f \t  %.2f\t %.2f \t %.2f \t    %.1f \t %.1f" %
                      (i + 1, strehltmp[0], strehltmp[1], np.exp(-strehltmp[2]),
                       np.exp(-strehltmp[3]), etr, framerate))
        t1 = time.time()

        print(" loop execution time:", t1 - t0, "  (", self.n, "iterations), ",
              (t1 - t0) / self.n, "(mean)  ", self.n / (t1 - t0), "Hz")

        #self.tar.comp_image(0)
        SRs = self.target.get_strehl(0)
        self.SR2 = np.exp(-SRs[3])
        self.SR = SRs[1]

    def error_breakdown(self):
        """
        Compute the error breakdown of the AO simulation. Returns the error commands of
        each contributors. Suppose no delay (for now) and only 2 controllers : the main one, controller #0, (specified on the parameter file)
        and the geometric one, controller #1 (automatically added if roket is asked in the parameter file)
        Commands are computed by applying the loop filter on various kind of commands : (see schema_simulation_budget_erreur_v2)

            - Ageom : Aliasing contribution on WFS direction
                Obtained by computing commands from DM orthogonal phase (projection + slopes_geom)

            - B : Projection on the target direction
                Obtained as the commmands output of the geometric controller

            - C : Wavefront
                Obtained by computing commands from DM parallel phase (RD*B)

            - E : Wavefront + aliasing + ech/trunc + tomo
                Obtained by performing the AO loop iteration without noise on the WFS

            - F : Wavefront + aliasing + tomo
                Obtained by performing the AO loop iteration without noise on the WFS and using phase deriving slopes

            - G : tomo

        Note : rtc.get_err returns to -CMAT.slopes
        """
        g = self.config.p_controllers[0].gain
        Dcom = self.rtc.get_command(0)
        Derr = self.rtc.get_err(0)
        self.com[self.iter_number, :] = Dcom
        tarphase = self.target.get_tar_phase(0)
        self.slopes[self.iter_number, :] = self.rtc.get_slopes(0)

        ###########################################################################
        ## Noise contribution
        ###########################################################################
        if (self.config.p_wfss[0].type == scons.WFSType.SH):
            ideal_img = np.array(self.wfs._wfs.d_wfs[0].d_binimg_notnoisy)
            binimg = np.array(self.wfs._wfs.d_wfs[0].d_binimg)
            if (self.config.p_centroiders[0].type == scons.CentroiderType.TCOG
                ):  # Select the same pixels with or without noise
                invalidpix = np.where(binimg <= self.config.p_centroiders[0].thresh)
                ideal_img[invalidpix] = 0
                self.rtc.set_centroider_threshold(0, -1e16)
            self.wfs._wfs.d_wfs[0].set_binimg(ideal_img, ideal_img.size)
        elif (self.config.p_wfss[0].type == scons.centroiderType.PYRHR):
            ideal_pyrimg = np.array(self.wfs._wfs.d_wfs[0].d_binimg_notnoisy)
            self.wfs._wfs.d_wfs[0].set_binimg(ideal_pyrimg, ideal_pyrimg.size)

        self.rtc.do_centroids(0)
        if (self.config.p_centroiders[0].type == scons.CentroiderType.TCOG):
            self.rtc.set_centroider_threshold(0, config.p_centroiders[0].thresh)

        self.rtc.do_control(0)
        E = self.rtc.get_err(0)
        E_meas = self.rtc.get_slopes(0)
        self.noise_buf[self.iter_number, :] = (Derr - E)
        # Apply loop filter to get contribution of noise on commands
        # if (self.iter_number + 1 < self.config.p_loop.niter):
        self.noise_com[self.iter_number, :] = self.noise_com[self.iter_number - 1, :] - self.gRD.dot(
                    self.noise_com[self.iter_number - self.delay, :]) + g * self.noise_buf[self.iter_number - self.delay, :]
        ###########################################################################
        ## Sampling/truncature contribution
        ###########################################################################
        self.rtc.do_centroids_geom(0)
        self.rtc.do_control(0)


#  ICE Revision: $Id$
"""Working with a solution directory"""

from PyFoam.Basics.Utilities import Utilities
from PyFoam.Basics.BasicFile import BasicFile
from PyFoam.Error import warning,error
from PyFoam import configuration as conf

from PyFoam.RunDictionary.TimeDirectory import TimeDirectory
from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile,WriteParameterFile

from PyFoam.Basics.DataStructures import DictProxy

from PyFoam.ThirdParty.six import print_

from os import listdir,path,mkdir,stat,environ
from platform import uname
from time import asctime
from stat import ST_CTIME
import tarfile,fnmatch,glob
import re,os

try:
    from os import getlogin
except ImportError:
    try:
        import PyFoam.ThirdParty.winhacks
    except ImportError:
        print_("Unable to import the getlogin function.")
        import sys
        sys.exit(-1)

class SolutionDirectory(Utilities):
    """Represents a solution directory

    In the solution directory subdirectories whose names are numbers
    are assumed to be solutions for a specific time-step"""

    def __init__(self,
                 name,
                 archive=None,
                 paraviewLink=True,
                 parallel=False,
                 addLocalConfig=False,
                 tolerant=False,
                 region=None):
        """:param name: Name of the solution directory
        :param archive: name of the directory where the lastToArchive-method
        should copy files, if None no archive is created. Deprecated as it was never used
        :param paraviewLink: Create a symbolic link controlDict.foam for paraview
        :param tolerant: do not fail for minor inconsistencies
        :param parallel: use the first processor-subdirectory for the authorative information
        :param region: Mesh region for multi-region cases"""

        self.name=path.abspath(name)
        self.archive=None
        if archive!=None:
            self.archive=path.join(name,archive)
            if not path.exists(self.archive):
                mkdir(self.archive)

        self.region=region
        self.backups=[]

        self.parallel=parallel
        self.tolerant=tolerant

        self.lastReread=0
        self.reread()

        self.dirPrefix=''
        if self.processorDirs() and parallel:
            self.dirPrefix = self.processorDirs()[0]

        self.essential=set([self.systemDir(),
                            self.constantDir()])

        # only add the initial directory if no template exists
        if not path.exists(path.join(self.name,"0.org")) and not self.initialDir() is None:
            self.addToClone(self.initialDir())

        # PyFoam-specific
        self.addToClone("PyFoamHistory")
        self.addToClone("customRegexp")
        self.addToClone("LocalConfigPyFoam")

        # this usually comes with the tutorials
        self.addToClone("Allclean")
        self.addToClone("Allrun")

        self.addToClone("*.ipynb")

        emptyFoamFile=path.join(self.name,path.basename(self.name)+".foam")
        if paraviewLink and not path.exists(emptyFoamFile):
            dummy=open(emptyFoamFile,"w") # equivalent to touch

        if addLocalConfig:
            self.addLocalConfig()

        # These are used by PrepareCase
        self.addToClone("*.org")
        self.addToClone("*.template")
        self.addToClone("*.sh")
        self.addToClone("*.parameters")
        self.addToClone("derivedParameters.py")
        self.addToClone("*.postTemplate")
        self.addToClone("*.finalTemplate")

        self.__postprocDirs=[]
        self.__postprocInfo={}
        self.addPostprocDir(".")
        self.addPostprocDir("postProcessing",fail=False)

    def regions(self):
        """Detect sub-region cases by looking through constant and finding
        directories with polyMesh-directories"""
        regions=[]
        for f in listdir(self.constantDir()):
            fName=path.join(self.constantDir(),f)
            if path.isdir(fName):
                pF=path.join(fName,"polyMesh")
                if path.exists(pF) and path.isdir(pF):
                    if self.region is None:
                        regions.append(f)
                    else:
                        # sub-regions. Do they even exist?
                        regions.append(path.join(self.region,f))
        return regions

    def setToParallel(self):
        """Use the parallel times instead of the serial.

        Used to reset the behaviour after it has been set by the constructor"""
        if self.parallel:
            warning(self.name,"is already in parallel mode")
        else:
            self.parallel=True
            if self.processorDirs():
                self.dirPrefix = self.processorDirs()[0]
            self.reread(force=True)

    def addLocalConfig(self):
        """Add the local configuration file of the case to the configuration"""
        fName=path.join(self.name,"LocalConfigPyFoam")
        if path.exists(fName):
            conf().addFile(fName)

    def __len__(self):
        self.reread()
        return len(self.times)

    def __contains__(self,item):
        self.reread()

        if self.timeName(item)!=None:
            return True
        else:
            return False

    def __getitem__(self,key):
        self.reread()

        ind=self.timeName(key)
        if ind==None:
            raise KeyError(key)
        else:
            return TimeDirectory(self.name, self.fullPath(ind), region=self.region)

    def __setitem__(self,key,value):
        self.reread()
        if type(key)!=str:
            raise TypeError(type(key),"of",key,"is not 'str'")

        if type(value)!=TimeDirectory:
            raise TypeError(type(value),"is not TimeDirectory")

        dest=TimeDirectory(self.name, self.fullPath(key), create=True,region=self.region)
        dest.copy(value)

        self.reread(force=True)

    def __delitem__(self,key):
        self.reread()
        nm=self.timeName(key)
        if nm==None:
            raise KeyError(key)

        self.rmtree(path.join(self.name, self.fullPath(nm)),ignore_errors=True)

        self.reread(force=True)

    def __iter__(self):
        self.reread()
        for key in self.times:
            yield TimeDirectory(self.name,
                                self.fullPath(key),
                                region=self.region,
                                tolerant=self.tolerant)

    def timeName(self,item,minTime=False):
        """Finds the name of a directory that corresponds with the given parameter
        :param item: the time that should be found
        :param minTime: search for the time with the minimal difference.
        Otherwise an exact match will be searched"""

        if type(item)==int:
            return self.times[item]
        else:
            ind=self.timeIndex(item,minTime)
            if ind==None:
                return None
            else:
                return self.times[ind]

    def timeIndex(self,item,minTime=False):
        """Finds the index of a directory that corresponds with the given parameter
        :param item: the time that should be found
        :param minTime: search for the time with the minimal difference.
        Otherwise an exact match will be searched"""
        self.reread()

        time=float(item)
        result=None

        if minTime:
            result=0
            for i in range(1,len(self.times)):
                if abs(float(self.times[result])-time)>abs(float(self.times[i])-time):
                    result=i
        else:
            for i in range(len(self.times)):
                t=self.times[i]
                if abs(float(t)-time)<1e-6:
                    if result==None:
                        result=i
                    elif abs(float(t)-time)<abs(float(self.times[result])-time):
                        result=i

        return result

    def fullPath(self,time):
        if self.dirPrefix:
            return path.join(self.dirPrefix, time)
        return time

    def isValid(self):
        """Checks whether this is a valid case directory by looking for
        the system- and constant-directories and the controlDict-file"""

        return len(self.missingFiles())==0

    def missingFiles(self):
        """Return a list of all the missing files and directories that
        are needed for a valid case"""
        missing=[]
        if not path.exists(self.systemDir()):
            missing.append(self.systemDir())


"""
An experimental support for curvilinear grid.
"""

from itertools import chain
from .grid_finder import GridFinder

from  .axislines import \
     AxisArtistHelper, GridHelperBase
from  .axis_artist import AxisArtist
from matplotlib.transforms import Affine2D, IdentityTransform
import numpy as np

from matplotlib.path import Path

class FixedAxisArtistHelper(AxisArtistHelper.Fixed):
    """
    Helper class for a fixed axis.
    """

    def __init__(self, grid_helper, side, nth_coord_ticks=None):
        """
        nth_coord = along which coordinate value varies.
         nth_coord = 0 ->  x axis, nth_coord = 1 -> y axis
        """

        super(FixedAxisArtistHelper, self).__init__( \
            loc=side,
            )

        self.grid_helper = grid_helper
        if nth_coord_ticks is None:
            nth_coord_ticks = self.nth_coord
        self.nth_coord_ticks = nth_coord_ticks

        self.side = side
        self._limits_inverted = False

    def update_lim(self, axes):
        self.grid_helper.update_lim(axes)

        if self.nth_coord == 0:
            xy1, xy2 = axes.get_ylim()
        else:
            xy1, xy2 = axes.get_xlim()

        if xy1 > xy2:
            self._limits_inverted = True
        else:
            self._limits_inverted = False

    def change_tick_coord(self, coord_number=None):
        if coord_number is None:
            self.nth_coord_ticks = 1 - self.nth_coord_ticks
        elif coord_number in [0, 1]:
            self.nth_coord_ticks = coord_number
        else:
            raise Exception("wrong coord number")

    def get_tick_transform(self, axes):
        return axes.transData

    def get_tick_iterators(self, axes):
        """tick_loc, tick_angle, tick_label"""

        g = self.grid_helper

        if self._limits_inverted:
            side = {"left":"right","right":"left",
                    "top":"bottom", "bottom":"top"}[self.side]
        else:
            side = self.side

        ti1 = g.get_tick_iterator(self.nth_coord_ticks, side)
        ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True)

        #ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, self.side, minor=True)

        return chain(ti1, ti2), iter([])

class FloatingAxisArtistHelper(AxisArtistHelper.Floating):

    def __init__(self, grid_helper, nth_coord, value, axis_direction=None):
        """
        nth_coord = along which coordinate value varies.
         nth_coord = 0 ->  x axis, nth_coord = 1 -> y axis
        """

        super(FloatingAxisArtistHelper, self).__init__(nth_coord,
                                                       value,
                                                       )
        self.value = value
        self.grid_helper = grid_helper
        self._extremes = None, None

        self._get_line_path = None # a method that returns a Path.
        self._line_num_points = 100 # number of points to create a line

    def set_extremes(self, e1, e2):
        self._extremes = e1, e2

    def update_lim(self, axes):
        self.grid_helper.update_lim(axes)

        x1, x2 = axes.get_xlim()
        y1, y2 = axes.get_ylim()
        grid_finder = self.grid_helper.grid_finder
        extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy,
                                              x1, y1, x2, y2)

        extremes = list(extremes)
        e1, e2 = self._extremes # ranges of other coordinates
        if self.nth_coord == 0:
            if e1 is not None:
                extremes[2] = max(e1, extremes[2])
            if e2 is not None:
                extremes[3] = min(e2, extremes[3])
        elif self.nth_coord == 1:
            if e1 is not None:
                extremes[0] = max(e1, extremes[0])
            if e2 is not None:
                extremes[1] = min(e2, extremes[1])

        grid_info = dict()
        lon_min, lon_max, lat_min, lat_max = extremes
        lon_levs, lon_n, lon_factor = \
                  grid_finder.grid_locator1(lon_min, lon_max)
        lat_levs, lat_n, lat_factor = \
                  grid_finder.grid_locator2(lat_min, lat_max)
        grid_info["extremes"] = extremes

        grid_info["lon_info"] = lon_levs, lon_n, lon_factor
        grid_info["lat_info"] = lat_levs, lat_n, lat_factor

        grid_info["lon_labels"] = grid_finder.tick_formatter1("bottom",
                                                              lon_factor,
                                                              lon_levs)

        grid_info["lat_labels"] = grid_finder.tick_formatter2("bottom",
                                                              lat_factor,
                                                              lat_levs)

        grid_finder = self.grid_helper.grid_finder

        #e1, e2 = self._extremes # ranges of other coordinates
        if self.nth_coord == 0:
            xx0 = np.linspace(self.value, self.value, self._line_num_points)
            yy0 = np.linspace(extremes[2], extremes[3], self._line_num_points)
            xx, yy = grid_finder.transform_xy(xx0, yy0)
        elif self.nth_coord == 1:
            xx0 = np.linspace(extremes[0], extremes[1], self._line_num_points)
            yy0 = np.linspace(self.value, self.value, self._line_num_points)
            xx, yy = grid_finder.transform_xy(xx0, yy0)

        grid_info["line_xy"] = xx, yy
        self.grid_info = grid_info

    def get_axislabel_transform(self, axes):
        return Affine2D() #axes.transData

    def get_axislabel_pos_angle(self, axes):

        extremes = self.grid_info["extremes"]

        if self.nth_coord == 0:
            xx0 = self.value
            yy0 = (extremes[2]+extremes[3])/2.
            dxx, dyy = 0., abs(extremes[2]-extremes[3])/1000.
        elif self.nth_coord == 1:
            xx0 = (extremes[0]+extremes[1])/2.
            yy0 = self.value
            dxx, dyy = abs(extremes[0]-extremes[1])/1000., 0.

        grid_finder = self.grid_helper.grid_finder
        xx1, yy1 = grid_finder.transform_xy([xx0], [yy0])

        trans_passingthrough_point = axes.transData + axes.transAxes.inverted()
        p = trans_passingthrough_point.transform_point([xx1[0], yy1[0]])

        if (0. <= p[0] <= 1.) and (0. <= p[1] <= 1.):
            xx1c, yy1c = axes.transData.transform_point([xx1[0], yy1[0]])
            xx2, yy2 = grid_finder.transform_xy([xx0+dxx], [yy0+dyy])
            xx2c, yy2c = axes.transData.transform_point([xx2[0], yy2[0]])

            return (xx1c, yy1c), np.arctan2(yy2c-yy1c, xx2c-xx1c)/np.pi*180.
        else:
            return None, None

    def get_tick_transform(self, axes):
        return IdentityTransform() #axes.transData

    def get_tick_iterators(self, axes):
        """tick_loc, tick_angle, tick_label, (optionally) tick_label"""

        grid_finder = self.grid_helper.grid_finder

        lat_levs, lat_n, lat_factor = self.grid_info["lat_info"]
        lat_levs = np.asarray(lat_levs)
        if lat_factor is not None:
            yy0 = lat_levs / lat_factor
            dy = 0.01 / lat_factor
        else:
            yy0 = lat_levs
            dy = 0.01

        lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
        lon_levs = np.asarray(lon_levs)
        if lon_factor is not None:
            xx0 = lon_levs / lon_factor
            dx = 0.01 / lon_factor
        else:
            xx0 = lon_levs
            dx = 0.01

        if None in self._extremes:
            e0, e1 = self._extremes
        else:
            e0, e1 = sorted(self._extremes)
        if e0 is None:
            e0 = -np.inf
        if e1 is None:
            e1 = np.inf

        if self.nth_coord == 0:
            mask = (e0 <= yy0) & (yy0 <= e1)
            #xx0, yy0 = xx0[mask], yy0[mask]
            yy0 = yy0[mask]
        elif self.nth_coord == 1:
            mask = (e0 <= xx0) & (xx0 <= e1)
            #xx0, yy0 = xx0[mask], yy0[mask]
            xx0 = xx0[mask]

        def transform_xy(x, y):
            x1, y1 = grid_finder.transform_xy(x, y)
            x2y2 = axes.transData.transform(np.array([x1, y1]).transpose())
            x2, y2 = x2y2.transpose()
            return x2, y2

        # find angles
        if self.nth_coord == 0:
            xx0 = np.empty_like(yy0)
            xx0.fill(self.value)

            xx1, yy1 = transform_xy(xx0, yy0)

            xx00 = xx0.copy()
            xx00[xx0+dx>e1] -= dx
            xx1a, yy1a = transform_xy(xx00, yy0)
            xx1b, yy1b = transform_xy(xx00+dx, yy0)

            xx2a, yy2a = transform_xy(xx0, yy0)
            xx2b, yy2b = transform_xy(xx0, yy0+dy)

            labels = self.grid_info["lat_labels"]
            labels = [l for l, m in zip(labels, mask) if m]

        elif self.nth_coord == 1:
            yy0 = np.empty_like(xx0)
            yy0.fill(self.value)

            xx1, yy1 = transform_xy(xx0, yy0)

            xx1a, yy1a = transform_xy(xx0, yy0)
            xx1b, yy1b = transform_xy(xx0, yy0+dy)

            xx00 = xx0.copy()
            xx00[xx0+dx>e1] -= dx
            xx2a, yy2a = transform_xy(xx00, yy0)
            xx2b, yy2b = transform_xy(xx00+dx, yy0)

            labels = self.grid_info["lon_labels"]
            labels = [l for l, m in zip(labels, mask) if m]

        def f1():
            dd = np.arctan2(yy1b-yy1a, xx1b-xx1a) # angle normal
            dd2 = np.arctan2(yy2b-yy2a, xx2b-xx2a) # angle tangent
            mm = ((yy1b-yy1a)==0.) & ((xx1b-xx1a)==0.) # mask where dd1 is not defined
            dd[mm] = dd2[mm]+3.14159/2.
            #dd = np.arctan2(yy2-yy1, xx2-xx1) # angle normal
            #dd2 = np.arctan2(yy3-yy1, xx3-xx1) # angle tangent
            #mm = ((yy2-yy1)==0.) & ((xx2-xx1)==0.) # mask where dd1 is not defined
            #dd[mm] = dd2[mm]+3.14159/2.

            #dd += 3.14159

            #dd = np.arctan2(xx2-xx1, angle_tangent-yy1)
            trans_tick = self.get_tick_transform(axes)
            tr2ax = trans_tick + axes.transAxes.inverted()
            for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
                c2 = tr2ax.transform_point((x, y))
                delta=0.00001
                if (0. -delta<= c2[0] <= 1.+delta) and \
                       (0. -delta<= c2[1] <= 1.+delta):
                    d1 = d/3.14159*180.
                    d2 = d2/3.14159*180.
                    yield [x, y], d1, d2, lab

        return f1(), iter([])

    def get_line_transform(self, axes):
        return axes.transData

    def get_line(self, axes):
        self.update_lim(axes)
        x, y = self.grid_info["line_xy"]

        if self._get_line_path is None:
            return Path(list(zip(x, y)))
        else:
            return self._get_line_path(axes, x, y)

class GridHelperCurveLinear(GridHelperBase):

    def __init__(self, aux_trans,
                 extreme_finder=None,
                 grid_locator1=None,
                 grid_locator2=None,
                 tick_formatter1=None,
                 tick_formatter2=None):
        """
        aux_trans : a transform from the source (curved) coordinate to
        target (rectilinear) coordinate. An instance of MPL's Transform
        (inverse transform should be defined) or a tuple of two callable
        objects which defines the transform and its inverse. The callables
        need take two arguments of array of source coordinates and
        should return two target coordinates:
          e.g., x2, y2 = trans(x1, y1)
        """
        super(GridHelperCurveLinear, self).__init__()

        self.grid_info = None
        self._old_values = None
        #self._grid_params = dict()
        self._aux_trans = aux_trans

        self.grid_finder = GridFinder(aux_trans,
                                      extreme_finder,
                                      grid_locator1,
                                      grid_locator2,
                                      tick_formatter1,
                                      tick_formatter2)

    def update_grid_finder(self, aux_trans=None, **kw):

        if aux_trans is not None:
            self.grid_finder.update_transform(aux_trans)

        self.grid_finder.update(**kw)
        self.invalidate()

    def _update(self, x1, x2, y1, y2):
        "bbox in 0-based image coordinates"
        # update wcsgrid

        if self.valid() and self._old_values == (x1, x2, y1, y2):
            return

        self._update_grid(x1, y1, x2, y2)

        self._old_values = (x1, x2, y1, y2)

        self._force_update = False

    def new_fixed_axis(self, loc,
                       nth_coord=None,
                       axis_direction=None,
                       offset=None,
                       axes=None):

        if axes is None:
            axes = self.axes

        if axis_direction is None:
            axis_direction = loc
        _helper = FixedAxisArtistHelper(self, loc,
                                        #nth_coord,
                                        nth_coord_ticks=nth_coord,
                                        )

        axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)

        return axisline

    def new_floating_axis(self, nth_coord,
                          value,
                          axes=None,
                          axis_direction="bottom"
                          ):

        if axes is None:
            axes = self.axes

        _helper = FloatingAxisArtistHelper( \
            self, nth_coord, value, axis_direction)

        axisline = AxisArtist(axes, _helper)

        #_helper = FloatingAxisArtistHelper(self, nth_coord,
        #                                   value,
        #                                   label_direction=label_direction,
        #                                   )

        #axisline = AxisArtistFloating(axes, _helper,
        #                              axis_direction=axis_direction)
        axisline.line.set_clip_on(True)
        axisline.line.set_clip_box(axisline.axes.bbox)
        #axisline.major_ticklabels.set_visible(True)
        #axisline.minor_ticklabels.set_visible(False)

        #axisline.major_ticklabels.set_rotate_along_line(True)
        #axisline.set_rotate_label_along_line(True)

        return axisline

    def _update_grid(self, x1, y1, x2, y2):

        self.grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2)

    def get_gridlines(self, which="major", axis="both"):
        grid_lines = []

        if axis in ["both", "x"]:
            for gl in self.grid_info["lon"]["lines"]:
                grid_lines.extend(gl)
        if axis in ["both", "y"]:
            for gl in self.grid_info["lat"]["lines"]:
                grid_lines.extend(gl)

        return grid_lines

    def get_tick_iterator(self, nth_coord, axis_side, minor=False):

        #axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side]
        angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side]
        #angle = [0, 90, 180, 270][axisnr]
        lon_or_lat = ["lon", "lat"][nth_coord]
        if not minor: # major ticks
            def f():
                for (xy, a), l in zip(self.grid_info[lon_or_lat]["tick_locs"][axis_side],
                                    self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
                    angle_normal = a
                    yield xy, angle_normal, angle_tangent, l
        else:
            def f():
                for (xy, a), l in zip(self.grid_info[lon_or_lat]["tick_locs"][axis_side],
                                    self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
                    angle_normal = a
                    yield xy, angle_normal, angle_tangent, ""
                #for xy, a, l in self.grid_info[lon_or_lat]["ticks"][axis_side]:
                #    yield xy, a, ""

        return f()

def test3():

    import numpy as np
    from matplotlib.transforms import Transform
    from matplotlib.path import Path

    class MyTransform(Transform):
        input_dims = 2
        output_dims = 2
        is_separable = False

        def __init__(self, resolution):
            """
            Create a new Aitoff transform.  Resolution is the number of steps
            to interpolate between each input line segment to approximate its
            path in curved Aitoff space.
            """
            Transform.__init__(self)
            self._resolution = resolution

        def transform(self, ll):
            x = ll[:, 0:1]
            y  = ll[:, 1:2]

            return np.concatenate((x, y-x), 1)

        transform.__doc__ = Transform.transform.__doc__

        transform_non_affine = transform
        transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__

        def transform_path(self, path):
            vertices = path.vertices
            ipath = path.interpolated(self._resolution)
            return Path(self.transform(ipath.vertices), ipath.codes)
        transform_path.__doc__ = Transform.transform_path.__doc__

        transform_path_non_affine = transform_path
        transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__

        def inverted(self):
            return MyTransformInv(self._resolution)
        inverted.__doc__ = Transform.inverted.__doc__

    class MyTransformInv(Transform):
        input_dims = 2
        output_dims = 2
        is_separable = False

        def __init__(self, resolution):
            Transform.__init__(self)
            self._resolution = resolution

        def transform(self, ll):
            x = ll[:, 0:1]
            y  = ll[:, 1:2]

            return np.concatenate((x, y+x), 1)
        transform.__doc__ = Transform.transform.__doc__

        def inverted(self):
            return MyTransform(self._resolution)
        inverted.__doc__ = Transform.inverted.__doc__

    import matplotlib.pyplot as plt
    fig = plt.figure(1)
    fig.clf()
    tr = MyTransform(1)
    grid_helper = GridHelperCurveLinear(tr)

    from mpl_toolkits.axes_grid1.parasite_axes import host_subplot_class_factory
    from .axislines import Axes

    SubplotHost = host_subplot_class_factory(Axes)

    ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)

    fig.add_subplot(ax1)

    ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal")
    ax1.parasites.append(ax2)
    ax2.plot([3, 6], [5.0, 10.])

    ax1.set_aspect(1.)
    ax1.set_xlim(0, 10)
    ax1.set_ylim(0, 10)

    ax1.grid(True)
    plt.draw()

def curvelinear_test2(fig):
    """
    polar projection, but in a rectangular box.
    """
    global ax1
    import numpy as np
    from . import  angle_helper
    from matplotlib.projections import PolarAxes
    from matplotlib.transforms import Affine2D

    from mpl_toolkits.axes_grid.parasite_axes import SubplotHost, \
         ParasiteAxesAuxTrans
    import matplotlib.cbook as cbook

    # PolarAxes.PolarTransform takes radian. However, we want our coordinate
    # system in degree
    tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()

    # polar projection, which involves cycle, and also has limits in
    # its coordinates, needs a special method to find the extremes
    # (min, max of the coordinate within the view).

    # 20, 20 : number of sampling points along x, y direction
    extreme_finder = angle_helper.ExtremeFinderCycle(20, 20,
                                                     lon_cycle = 360,
                                                     lat_cycle = None,
                                                     lon_minmax = None,
                                                     lat_minmax = (0, np.inf),
                                                     )

    grid_locator1 = angle_helper.LocatorDMS(5)
    # Find a grid values appropriate for the coordinate (degree,
    # minute, second).

    tick_formatter1 = angle_helper.FormatterDMS()
    # And also uses an appropriate formatter.  Note that,the
    # acceptable Locator and Formatter class is a bit different than
    # that of mpl's, and you cannot directly use mpl's Locator and
    # Formatter here (but may be possible in the future).

    grid_helper = GridHelperCurveLinear(tr,
                                        extreme_finder=extreme_finder,
                                        grid_locator1=grid_locator1,
                                        tick_formatter1=tick_formatter1
                                        )