""" MeshLabXML layer functions """

#from . import mlx.FilterScript
#from meshlabxml import mlx.FilterScript
import meshlabxml as mlx
from . import util

def join(script, merge_visible=True, merge_vert=False, delete_layer=True,
         keep_unreferenced_vert=False):
    """ Flatten all or only the visible layers into a single new mesh.

    Transformations are preserved. Existing layers can be optionally
    deleted.

    Args:
        script: the mlx.FilterScript object or script filename to write
            the filter to.
        merge_visible (bool): merge only visible layers
        merge_vert (bool): merge the vertices that are duplicated among
            different layers. Very useful when the layers are spliced portions
            of a single big mesh.
        delete_layer (bool): delete all the merged layers. If all layers are
            visible only a single layer will remain after the invocation of
            this filter.
        keep_unreferenced_vert (bool): Do not discard unreferenced vertices
            from source layers. Necessary for point-only layers.

    Layer stack:
        Creates a new layer "Merged Mesh"
        Changes current layer to the new layer
        Optionally deletes all other layers

    MeshLab versions:
        2016.12
        1.3.4BETA

    Bugs:
        UV textures: not currently preserved, however will be in a future
            release. https://github.com/cnr-isti-vclab/meshlab/issues/128
        merge_visible: it is not currently possible to change the layer
            visibility from meshlabserver, however this will be possible
            in the future https://github.com/cnr-isti-vclab/meshlab/issues/123
    """
    filter_xml = ''.join([
        '  <filter name="Flatten Visible Layers">\n',
        '    <Param name="MergeVisible" ',
        'value="{}" '.format(str(merge_visible).lower()),
        'description="Merge Only Visible Layers" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="MergeVertices" ',
        'value="{}" '.format(str(merge_vert).lower()),
        'description="Merge duplicate vertices" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="DeleteLayer" ',
        'value="{}" '.format(str(delete_layer).lower()),
        'description="Delete Layers" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="AlsoUnreferenced" ',
        'value="{}" '.format(str(keep_unreferenced_vert).lower()),
        'description="Keep unreferenced vertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, mlx.FilterScript):
        script.add_layer('Merged Mesh')
        if delete_layer:
            # As it is not yet possible to change the layer visibility, all
            # layers will be deleted. This will be updated once layer
            # visibility is tracked.
            for i in range(script.last_layer()):
                script.del_layer(0)
    return None

def delete(script, layer_num=None):
    """ Delete layer

    Args:
        script: the mlx.FilterScript object or script filename to write
            the filter to.
        layer_num (int): the number of the layer to delete. Default is the
            current layer. Not supported on the file base API.

    Layer stack:
        Deletes a layer
        will change current layer if deleted layer is lower in the stack

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Delete Current Mesh"/>\n'
    if isinstance(script, mlx.FilterScript):
        if (layer_num is None) or (layer_num == script.current_layer()):
            util.write_filter(script, filter_xml)
            script.del_layer(script.current_layer())
        else:
            cur_layer = script.current_layer()
            change(script, layer_num)
            util.write_filter(script, filter_xml)
            if layer_num < script.current_layer():
                change(script, cur_layer - 1)
            else:
                change(script, cur_layer)
            script.del_layer(layer_num)
    else:
        util.write_filter(script, filter_xml)
    return None

def rename(script, label='blank', layer_num=None):
    """ Rename layer label

    Can be useful for outputting mlp files, as the output file names use
    the labels.

    Args:
        script: the mlx.FilterScript object or script filename to write
            the filter to.
        label (str): new label for the mesh layer
        layer_num (int): layer number to rename. Default is the
            current layer. Not supported on the file base API.

    Layer stack:
        Renames a layer

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Rename Current Mesh">\n',
        '    <Param name="newName" ',
        'value="{}" '.format(label),
        'description="New Label" ',
        'type="RichString" ',
        '/>\n',
        '  </filter>\n'])
    if isinstance(script, mlx.FilterScript):
        if (layer_num is None) or (layer_num == script.current_layer()):
            util.write_filter(script, filter_xml)
            script.layer_stack[script.current_layer()] = label
        else:
            cur_layer = script.current_layer()
            change(script, layer_num)
            util.write_filter(script, filter_xml)
            change(script, cur_layer)
            script.layer_stack[layer_num] = label
    else:
        util.write_filter(script, filter_xml)
    return None

def change(script, layer_num=None):
    """ Change the current layer by specifying the new layer number.

    Args:
        script: the mlx.FilterScript object or script filename to write
            the filter to.
        layer_num (int): the number of the layer to change to. Default is the
            last layer if script is a mlx.FilterScript object; if script is a
            filename the default is the first layer.

    Layer stack:
        Modifies current layer

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if layer_num is None:
        if isinstance(script, mlx.FilterScript):
            layer_num = script.last_layer()
        else:
            layer_num = 0
    filter_xml = ''.join([
        '  <filter name="Change the current layer">\n',
        '    <Param name="mesh" ',
        'value="{:d}" '.format(layer_num),
        'description="Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, mlx.FilterScript):
        script.set_current_layer(layer_num)
        #script.layer_stack[len(self.layer_stack) - 1] = layer_num
    return None

def duplicate(script, layer_num=None):
    """ Duplicate a layer.

    New layer label is '*_copy'.

    Args:
        script: the mlx.FilterScript object or script filename to write
            the filter to.
        layer_num (int): layer number to duplicate. Default is the
            current layer. Not supported on the file base API.

    Layer stack:
        Creates a new layer
        Changes current layer to the new layer

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Duplicate Current layer"/>\n'
    if isinstance(script, mlx.FilterScript):
        if (layer_num is None) or (layer_num == script.current_layer()):
            util.write_filter(script, filter_xml)
            script.add_layer('{}_copy'.format(script.layer_stack[script.current_layer()]), True)
        else:
            change(script, layer_num)
            util.write_filter(script, filter_xml)
            script.add_layer('{}_copy'.format(script.layer_stack[layer_num]), True)
    else:
        util.write_filter(script, filter_xml)
    return None

def split_parts(script, part_num=None, layer_num=None):
    """ Split current layer into many layers, one for each part (connected
        component)

    Mesh is split so that the largest part is the lowest named layer "CC 0"
    and the smallest part is the highest numbered "CC" layer.

    Args:
        script: the mlx.FilterScript object or script filename to write
            the filter to.
        part_num (int): the number of parts in the model. This is needed in
            order to properly create and manage the layer stack. Can be found
            with mlx.compute.measure_topology.
        layer_num (int): the number of the layer to split. Default is the
            current layer. Not supported on the file base API.

    Layer stack:
        Creates a new layer for each part named "CC 0", "CC 1", etc.
        Changes current layer to the last new layer

    MeshLab versions:
        2016.12
        1.3.4BETA

    Bugs:
        UV textures: not currently preserved, however will be in a future
            release. https://github.com/cnr-isti-vclab/meshlab/issues/127
    """
    filter_xml = '  <filter name="Split in Connected Components"/>\n'
    if isinstance(script, mlx.FilterScript):
        if (layer_num is not None) and (layer_num != script.current_layer()):
            change(script, layer_num)
        util.write_filter(script, filter_xml)
        if part_num is not None:
            for i in range(part_num):
                script.add_layer('CC {}'.format(i), True)
        else:
            script.add_layer('CC 0', True)
            print('Warning: the number of parts was not provided and cannot',
                  'be determined automatically. The layer stack is likely',
                  'incorrect!')
    else:
        util.write_filter(script, filter_xml)
    return None

def delete_lower(script, layer_num=None):
    """ Delete all layers below the specified one.

    Useful for MeshLab ver 2016.12, whcih will only output layer 0.
    """
    if layer_num is None:
        layer_num = script.current_layer()
    if layer_num != 0:
        change(script, 0)
    for i in range(layer_num):
        delete(script, 0)
    return None


#!/usr/bin/python3
"""Create and run MeshLab XML scripts

License:
    Copyright (C) 2016 Tim Ayres

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser 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
"""

import os
import sys
import inspect
import subprocess
import xml.etree.ElementTree as ET
import tempfile

from . import util
from . import layers
#from .layers import clean
from . import clean
from . import compute

# Global variables
ML_VERSION = '2016.12'
"""str: MeshLab version to target
"""

THIS_MODULEPATH = os.path.dirname(
    os.path.realpath(
        inspect.getsourcefile(
            lambda: 0)))

"""
Notes on meshlabserver filters - tested with 1.34Beta:
    Filter parameters may be specified in any order. Arguments within parameters
    may also be specified in any order.
    Required arguments (if applicable):
        value
        name
        type
        enum_cardinality
        min
        max
    Optional arguments (if applicable):
        enum_val#
        description
        tooltip
    Note that while min & max are required, if value is outside of min or max
    the filter will still work (tested with translate, may not work for all)
"""

class FilterScript(object):
    """

    begin - need file names. Need these first to have correct layers; need to know however
    many we're importing!
    layers = list of mesh layers, with name as the entry
    file_in
    mlp_in

    begin code

    filters = actual text of filters. This is a list with each filter in a separate entry
    end code
    use self.meshes.[len(self.meshes)]
    use cur_layer, tot_layer in strings and replace at the end? Sounds way to complicated
    for now; just be sure to provide input files to start!

    add run method?

    """
    def __init__(self, file_in=None, mlp_in=None, file_out=None, ml_version=ML_VERSION):
        self.ml_version = ml_version # MeshLab version
        self.filters = []
        self.layer_stack = [-1] # set current layer to -1
        self.opening = ['<!DOCTYPE FilterScript>\n<FilterScript>\n']
        self.closing = ['</FilterScript>\n']
        self.__stl_layers = []
        self.file_in = file_in
        self.mlp_in = mlp_in
        self.__no_file_in = False
        self.file_out = file_out
        self.geometry = None
        self.topology = None
        self.hausdorff_distance = None
        self.parse_geometry = False
        self.parse_topology = False
        self.parse_hausdorff = False
        # Process input files
        # Process project files first

        # TODO: test to make sure this works with "bunny"; should work fine!
        if self.mlp_in is not None:
            # make a list if it isn't already
            self.mlp_in = util.make_list(self.mlp_in)
            for val in self.mlp_in:
                tree = ET.parse(val)
                #root = tree.getroot()
                for elem in tree.iter(tag='MLMesh'):
                    filename = (elem.attrib['filename'])
                    fext = os.path.splitext(filename)[1][1:].strip().lower()
                    label = (elem.attrib['label'])
                    # add new mesh to the end of the mesh stack
                    self.add_layer(label)
                    #self.layer_stack.insert(self.last_layer() + 1, label)
                    # change current mesh
                    self.set_current_layer(self.last_layer())
                    #self.layer_stack[self.last_layer() + 1] = self.last_layer()
                    # If the mesh file extension is stl, change to that layer and
                    # run clean.merge_vert
                    if fext == 'stl':
                        self.__stl_layers.append(self.current_layer())
        # Process separate input files next
        if self.file_in is not None:
            # make a list if it isn't already
            self.file_in = util.make_list(self.file_in)
            for val in self.file_in:
                fext = os.path.splitext(val)[1][1:].strip().lower()
                label = os.path.splitext(val)[0].strip() # file prefix
                #print('layer_stack = %s' % self.layer_stack)
                #print('last_layer = %s' % self.last_layer())
                #print('current_layer = %s' % self.current_layer())

                # add new mesh to the end of the mesh stack
                self.add_layer(label)
                #self.layer_stack.insert(self.last_layer() + 1, label)
                # change current mesh
                self.set_current_layer(self.last_layer())
                #self.layer_stack[self.last_layer() + 1] = self.last_layer()
                # If the mesh file extension is stl, change to that layer and
                # run clean.merge_vert
                if fext == 'stl':
                    self.__stl_layers.append(self.current_layer())
        # If some input files were stl, we need to change back to the last layer
        # If the mesh file extension is stl, change to that layer and
        # run clean.merge_vert
        if self.__stl_layers:
            for layer in self.__stl_layers:
                if layer != self.current_layer():
                    layers.change(self, layer)
                clean.merge_vert(self)
            layers.change(self, self.last_layer()) # Change back to the last layer
        elif self.last_layer() == -1:
            # If no input files are provided, create a dummy file
            # with a single vertex and delete it first in the script.
            # This works around the fact that meshlabserver will
            # not run without an input file.
            # Layer stack is modified here; temp file
            # is created during run
            self.__no_file_in = True
            self.add_layer('DELETE_ME')
            layers.delete(self)

    def last_layer(self):
        """ Returns the index number of the last layer """
        #print('last layer = %s' % (len(self.layer_stack) - 2))
        return len(self.layer_stack) - 2

    def current_layer(self):
        """ Returns the index number of the current layer """
        return self.layer_stack[len(self.layer_stack) - 1]

    def set_current_layer(self, layer_num):
        """ Set the current layer to layer_num """
        self.layer_stack[len(self.layer_stack) - 1] = layer_num
        return None

    def add_layer(self, label, change_layer=True):
        """ Add new mesh layer to the end of the stack

        Args:
            label (str): new label for the mesh layer
            change_layer (bool): change to the newly created layer
        """
        self.layer_stack.insert(self.last_layer() + 1, label)
        if change_layer:
            self.set_current_layer(self.last_layer())
        return None

    def del_layer(self, layer_num):
        """ Delete mesh layer """
        del self.layer_stack[layer_num]
        # Adjust current layer if needed
        if layer_num < self.current_layer():
            self.set_current_layer(self.current_layer() - 1)
        return None

    def save_to_file(self, script_file):
        """ Save filter script to an mlx file """
        # TODO: rasie exception here instead?
        if not self.filters:
            print('WARNING: no filters to save to file!')
        script_file_descriptor = open(script_file, 'w')
        script_file_descriptor.write(''.join(self.opening + self.filters + self.closing))
        script_file_descriptor.close()

    def run_script(self, log=None, ml_log=None, mlp_out=None, overwrite=False,
                   file_out=None, output_mask=None, script_file=None, print_meshlabserver_output=True):
        """ Run the script
        """

        temp_script = False
        temp_ml_log = False

        if self.__no_file_in:
            # If no input files are provided, create a dummy file
            # with a single vertex and delete it first in the script.
            # This works around the fact that meshlabserver will
            # not run without an input file.
            temp_file_in_file = tempfile.NamedTemporaryFile(delete=False, suffix='.xyz', dir=os.getcwd())
            temp_file_in_file.write(b'0 0 0')
            temp_file_in_file.close()
            self.file_in = [temp_file_in_file.name]

        if not self.filters:
            script_file = None
        elif script_file is None:
            # Create temporary script file
            temp_script = True
            temp_script_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mlx')
            temp_script_file.close()
            self.save_to_file(temp_script_file.name)
            script_file = temp_script_file.name

        if (self.parse_geometry or self.parse_topology or self.parse_hausdorff) and (ml_log is None):
            # create temp ml_log
            temp_ml_log = True
            ml_log_file = tempfile.NamedTemporaryFile(delete=False, suffix='.txt')
            ml_log_file.close()
            ml_log = ml_log_file.name
        if file_out is None:
            file_out = self.file_out

        run(script=script_file, log=log, ml_log=ml_log,
            mlp_in=self.mlp_in, mlp_out=mlp_out, overwrite=overwrite,
            file_in=self.file_in, file_out=file_out, output_mask=output_mask, ml_version=self.ml_version,
            print_meshlabserver_output=print_meshlabserver_output)

        # Parse output
        # TODO: record which layer this is associated with?
        if self.parse_geometry:
            self.geometry = compute.parse_geometry(ml_log, log, print_output=print_meshlabserver_output)
        if self.parse_topology:
            self.topology = compute.parse_topology(ml_log, log, print_output=print_meshlabserver_output)
        if self.parse_hausdorff:
            self.hausdorff_distance = compute.parse_hausdorff(ml_log, log, print_output=print_meshlabserver_output)

        # Delete temp files
        if self.__no_file_in:
            os.remove(temp_file_in_file.name)
        if temp_script:
            os.remove(temp_script_file.name)
        if temp_ml_log:
            os.remove(ml_log_file.name)

def handle_error(program_name, cmd, log=None):
    """Subprocess program error handling

    Args:
        program_name (str): name of the subprocess program

    Returns:
        break_now (bool): indicate whether calling program should break out of loop

    """
    print('\nHouston, we have a problem.',
          '\n%s did not finish successfully. Review the log' % program_name,
          'file and the input file(s) to see what went wrong.')
    print('%s command: "%s"' % (program_name, cmd))
    if log is not None:
        print('log: "%s"' % log)
    print('Where do we go from here?')
    print(' r  - retry running %s (probably after' % program_name,
          'you\'ve fixed any problems with the input files)')
    print(' c  - continue on with the script (probably after',
          'you\'ve manually re-run and generated the desired',
          'output file(s)')
    print(' x  - exit, keeping the TEMP3D files and log')
    print(' xd - exit, deleting the TEMP3D files and log')
    while True:
        choice = input('Select r, c, x (default), or xd: ')
        if choice not in ('r', 'c', 'x', 'xd'):
            #print('Please enter a valid option.')
            choice = 'x'
        #else:
        break
    if choice == 'x':
        print('Exiting ...')
        sys.exit(1)
    elif choice == 'xd':
        print('Deleting TEMP3D* and log files and exiting ...')
        util.delete_all('TEMP3D*')
        if log is not None:
            os.remove(log)
        sys.exit(1)
    elif choice == 'c':
        print('Continuing on ...')
        break_now = True
    elif choice == 'r':
        print('Retrying %s cmd ...' % program_name)
        break_now = False
    return break_now

def run(script='TEMP3D_default.mlx', log=None, ml_log=None,
        mlp_in=None, mlp_out=None, overwrite=False, file_in=None,
        file_out=None, output_mask=None, cmd=None, ml_version=ML_VERSION,
        print_meshlabserver_output=True):
    """Run meshlabserver in a subprocess.

    Args:
        log (str): filename of the log file for meshlabxml. If not
            None, all meshlabserver stdout and stderr messages
            will be appended to this file.
        ml_log (str): filename of the log file output directly by
            meshlabserver.
        mlp_in (str or list): input meshlab project file. Can be a
            single filename or a list of filenames. Filenames will
            be loaded in the order given. All project files will
            be loaded before individual input files. If you want
            to load project and input files in a different order
            then you should use a custom cmd.
        mlp_out (str): output meshlab project file. Specify a
            single filename (meshlabserver accepts multiple output
            project filenames, however they will all be identical,
            so there is little use). When this option is used all
            layers will be saved as ply files.
        overwrite (bool): when specifying mlp_out, this determines
            whether any existing files will be overwritten (if
            True) or new filenames created (if False). If a new
            project file is created meshes will have '_out' added
            to their name.
        file_in (str or list): input mesh filename. Can be a single
            filename or a list of filenames. Filenames will be
            loaded in the order given. All project files will be
            loaded before individual input files. If you want to
            load project and input files in a different order then
            you should use a custom cmd.
        file_out (str or list): output mesh filename. Can be a
            single filename or a list of filenames. The current
            layer will be saved to this filename or filenames.
            Multiple filenames are useful for saving to multiple
            formats at the same time. Currently there is no way to
            output multiple layers except for saving a mlp project
            file.
        output_mask (str or list): output mask options for the
            output file. Values must include the flag, i.e. -m or
            -output_mask. If this is not provided for an output
            file then function "default_output_mask" is used to
            determine default values.
        script (str): the mlx filter script filename to execute.
        cmd (str): a full meshlabserver command line, such as
            "meshlabserver -input file.stl". If not None, this
            will override all other arguements except for log.
        print_meshlabserver_output (bool): Pass meshlabserver's output to stdout; useful for debugging.
                                           Only used if log is None.

    Notes:
        Meshlabserver can't handle spaces in paths or filenames (on Windows at least; haven't tested on other platforms). Enclosing the name in quotes or escaping the space has no effect.

    Returns:
        return code of meshlabserver process; 0 if successful
    """
    if cmd is None:
        cmd = 'meshlabserver'
        if ml_log is not None:
            # Initialize ml_log
            ml_log_file = open(ml_log, 'w')
            ml_log_file.close()
            cmd += ' -l %s' % ml_log
        if mlp_in is not None:
            # make a list if it isn't already
            mlp_in = util.make_list(mlp_in)
            for val in mlp_in:
                cmd += ' -p "%s"' % val
        if mlp_out is not None:
            cmd += ' -w %s' % mlp_out
            if overwrite:
                cmd += ' -v'
        if (mlp_in is None) and (file_in is None):
			# If no input files are provided use the default created by begin().
			# This works around the fact that meshlabserver will
			# not run without an input file.
            file_in = ['TEMP3D.xyz']
        if file_in is not None:
            # make a list if it isn't already
            file_in = util.make_list(file_in)
            for val in file_in:
                if val == 'bunny':
                    cmd += ' -i "%s"' % os.path.join(THIS_MODULEPATH, os.pardir,
                                                     'models', 'bunny_flat(1Z).ply')
                elif val == 'bunny_raw':
                    cmd += ' -i "%s"' % os.path.join(THIS_MODULEPATH, os.pardir,
                                                     'models', 'bunny_raw(-1250Y).ply')
                else:
                    cmd += ' -i "%s"' % val
        if file_out is not None:
            # make a list if it isn't already
            file_out = util.make_list(file_out)
            if output_mask is not None:
                output_mask = util.make_list(output_mask)
            else:
                output_mask = []
            for index, val in enumerate(file_out):
                cmd += ' -o "%s"' % val
                try:
                    cmd += ' %s' % output_mask[index]
                except IndexError:  # If output_mask can't be found use defaults
                    cmd += ' %s' % default_output_mask(val, ml_version=ml_version)
        if script is not None:
            cmd += ' -s "%s"' % script
    if log is not None:
        log_file = open(log, 'a')
        log_file.write('meshlabserver cmd = %s\n' % cmd)
        log_file.write('***START OF MESHLAB STDOUT & STDERR***\n')
        log_file.close()
        log_file = open(log, 'a')
    else:
        if print_meshlabserver_output:
            log_file = None
            print('meshlabserver cmd = %s' % cmd)
            print('***START OF MESHLAB STDOUT & STDERR***')
        else:
            log_file = open(os.devnull, 'w')
    while True:
        # TODO: test if shell=True is really needed
        return_code = subprocess.call(cmd, shell=True,
                                      stdout=log_file, stderr=log_file,
                                      universal_newlines=True)
        if log is not None:
            log_file.close()
        if (return_code == 0) or handle_error(program_name='MeshLab', cmd=cmd, log=log):
            break
    if log is not None:
        log_file = open(log, 'a')
        log_file.write('***END OF MESHLAB STDOUT & STDERR***\n')
        log_file.write('meshlabserver return code = %s\n\n' % return_code)
        log_file.close()
    return return_code

def find_texture_files(fbasename, log=None):
    """Finds the filenames of the referenced texture file(s) (and material
    file for obj) for the mesh.

    Args:
        fbasename (str): input filename. Supported file extensions:
            obj
            ply
            dae
            x3d
            wrl
        log (str): filename to log output

    Returns:
        list: list of all of the texture filenames referenced by the input file.
            May contain duplicates if the texture files are referenced more
            than once. List is empty if no texture files are found.
        list: list of all of the unique texture filenames, also empty if no
            texture files are found.
        str: for obj files only, returns the name of the referenced material file.
            Returns None if no material file is found.

    """
    fext = os.path.splitext(fbasename)[1][1:].strip().lower()
    material_file = None
    texture_files = []
    vert_colors = False
    face_colors = False
    if fext == 'obj':
        # Material Format: mtllib ./model_mesh.obj.mtl
        with open(fbasename, 'r') as fread:
            for line in fread:
                if 'mtllib' in line:
                    material_file = os.path.basename(line.split()[1])
                    break
        if material_file is not None:
            # Texture Format: map_Kd model_texture.jpg
            with open(material_file, 'r') as fread:
                for line in fread:
                    if 'map_Kd' in line:
                        texture_files.append(os.path.basename(line.split()[1]))
    elif fext == 'ply':
        # Texture Format: comment TextureFile model_texture.jpg
        # This works for MeshLab & itSeez3D, but may not work for
        # every ply file.
        face_element = False
        with open(fbasename, 'rb') as fread:
            # read ascii header; works for both ascii & binary files
            while True:
                line = fread.readline().strip().decode('ascii')
                # print(line)
                if 'element face' in line:
                    face_element = True
                if 'red' in line:
                    if face_element:
                        face_colors = True
                    else:
                        vert_colors = True
                if 'TextureFile' in line:
                    texture_files.append(os.path.basename(line.split()[2]))
                if 'end_header' in line:
                    break
    elif fext == 'dae':  # COLLADA
        # elif fext == 'mlp':
        # Texture Format:   <image id="texture0" name="texture0">
        #               <init_from>model_texture.jpg</init_from>
        #           </image>
        namespace = 'http://www.collada.org/2005/11/COLLADASchema'
        tree = ET.parse(fbasename)
        #root = tree.getroot()
        #print('root = ', root)
        #print('root.tag = ', root.tag, 'root.attrib = ', root.attrib)
        for elem in tree.findall(
                '{%s}library_images/{%s}image/{%s}init_from' % (namespace, namespace, namespace)):
            texture_files.append(elem.text)
    elif fext == 'x3d':
        # Texture Format: <ImageTexture url="model_texture.jpg"/>
        #ns = 'http://www.w3.org/2001/XMLSchema-instance'
        tree = ET.parse(fbasename)
        #root = tree.getroot()
        #print('root = ', root)
        #print('root.tag = ', root.tag, 'root.attrib = ', root.attrib)
        # for elem in root:
        # for elem in tree.iter(): # iterate through tree; very useful to see possible tags
        #print('elem.tag = ', elem.tag)
        #print('elem.attrib = ', elem.attrib)
        for elem in tree.iter(tag='ImageTexture'):
            #print('elem.attrib = ', elem.attrib)
            texture_files.append(elem.attrib['url'])
    elif fext == 'wrl':
        # Texture Format: texture ImageTexture { url "model_texture.jpg" }
        with open(fbasename, 'r') as fread:
            for line in fread:
                if 'ImageTexture' in line:
                    texture_files.append(os.path.basename(line.split('"')[1]))
    elif fext != 'stl':  # add other formats that don't support texture, e.g. xyz?
        print('File extension %s is not currently supported' % fext)
        # TODO: raise exception here
    texture_files_unique = list(set(texture_files))
    if log is not None:
        log_file = open(log, 'a')
        log_file.write('Results of find_texture_files:\n')
        log_file.write('fbasename = %s\n' % fbasename)
        log_file.write('texture_files = %s\n' % texture_files)
        log_file.write('texture_files_unique = %s\n' % texture_files_unique)
        log_file.write('Number of texture files = %s\n' % len(texture_files))
        log_file.write(
            'Number of unique texture files = %s\n\n' %
            len(texture_files_unique))
        log_file.write('vertex colors = %s\n' % vert_colors)
        log_file.write('face colors = %s\n' % face_colors)
        log_file.close()
    colors = {'texture':bool(texture_files), 'vert_colors':vert_colors, 'face_colors':face_colors}
    return texture_files, texture_files_unique, material_file, colors

def default_output_mask(file_out, texture=True, vert_normals=True, vert_colors=False,
                        face_colors=False, ml_version=ML_VERSION):
    """
    Set default output mask options based on file extension
    Note: v1.34BETA changed -om switch to -m
    Possible options (not all options are available for every format):
     vc -> vertex colors
     vf -> vertex flags
     vq -> vertex quality
     vn -> vertex normals
     vt -> vertex texture coords
     fc -> face colors
     ff -> face flags
     fq -> face quality
     fn -> face normals
     wc -> wedge colors
     wn -> wedge normals
     wt -> wedge texture coords
    """
    vn = ''
    wt = ''
    vc = ''
    fc = ''

    if ml_version < '1.3.4':
        om = '-om'
    else:
        om = '-m'

    fext = os.path.splitext(file_out)[1][1:].strip().lower()
    if fext in ['stl', 'dxf', 'xyz']:
        om = ''
        texture = False
        vert_normals = False
        vert_colors = False
        face_colors = False
#    elif fext == 'ply':
#        vert_colors = True

    if vert_normals:
        vn = ' vn'
    if texture:
        wt = ' wt'
    if vert_colors:
        vc = ' vc'
    if face_colors:
        fc = ' fc'
    output_mask = '{}{}{}{}{}'.format(om, vn, wt, vc, fc)
    return output_mask

def begin(script='TEMP3D_default.mlx', file_in=None, mlp_in=None):
    """Create new mlx script and write opening tags.

    Performs special processing on stl files.

    If no input files are provided this will create a dummy
    file and delete it as the first filter. This works around
    the meshlab limitation that it must be provided an input
    file, even if you will be creating a mesh as the first
    filter.

    """
    script_file = open(script, 'w')
    script_file.write(''.join(['<!DOCTYPE FilterScript>\n',
                               '<FilterScript>\n']))
    script_file.close()

    current_layer = -1
    last_layer = -1
    stl = False

    # Process project files first
    if mlp_in is not None:
        # make a list if it isn't already
        if not isinstance(mlp_in, list):
            mlp_in = [mlp_in]
        for val in mlp_in:
            tree = ET.parse(val)
            #root = tree.getroot()
            for elem in tree.iter(tag='MLMesh'):
                filename = (elem.attrib['filename'])
                current_layer += 1
                last_layer += 1
                # If the mesh file extension is stl, change to that layer and
                # run clean.merge_vert
                if os.path.splitext(filename)[1][1:].strip().lower() == 'stl':
                    layers.change(script, current_layer)
                    clean.merge_vert(script)
                    stl = True

    # Process separate input files next
    if file_in is not None:
        # make a list if it isn't already
        if not isinstance(file_in, list):
            file_in = [file_in]
        for val in file_in:
            current_layer += 1
            last_layer += 1
            # If the mesh file extension is stl, change to that layer and
            # run clean.merge_vert
            if os.path.splitext(val)[1][1:].strip().lower() == 'stl':
                layers.change(script, current_layer)
                clean.merge_vert(script)
                stl = True

    # If some input files were stl, we need to change back to the last layer
    if stl:
        layers.change(script, last_layer)  # Change back to the last layer
    elif last_layer == -1:
        # If no input files are provided, create a dummy file
        # with a single vertex and delete it first in the script.
        # This works around the fact that meshlabserver will
        # not run without an input file.
        file_in = ['TEMP3D.xyz']
        file_in_descriptor = open(file_in[0], 'w')
        file_in_descriptor.write('0 0 0')
        file_in_descriptor.close()
        layers.delete(script)
    return current_layer, last_layer

def end(script='TEMP3D_default.mlx'):
    """Write the closing tag to mlx script."""
    script_file = open(script, 'a')
    script_file.write('</FilterScript>')
    script_file.close()

def create_mlp(file_out, mlp_mesh=None, mlp_raster=None):
    """ Create mlp file
    mlp_mesh (list containing dictionary)
        filename*
        label
        matrix

    mlp_raster
        filename*
        label
        semantic
        camera
            trans_vector*
            rotation_matrix*
            focal_length*
            image_px*
            image_res_mm_per_px*
            lens_distortion
            center_px
    * Required

    http://vcg.isti.cnr.it/~cignoni/newvcglib/html/shot.html
    """
    # Opening lines
    mlp_file = open(file_out, 'w')
    mlp_file.write('\n'.join([
        '<!DOCTYPE MeshLabDocument>',
        '<MeshLabProject>\n']))
    mlp_file.close()

    if mlp_mesh is not None:
        mlp_file = open(file_out, 'a')
        mlp_file.write(' <MeshGroup>\n')
        for i, val in enumerate(mlp_mesh):
            if 'label' not in mlp_mesh[i]:
                mlp_mesh[i]['label'] = mlp_mesh[i]['filename']
            if 'matrix' not in mlp_mesh[i]:
                mlp_mesh[i]['matrix'] = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
            mlp_file.write('  <MLMesh filename="{}" label="{}">\n'.format(mlp_mesh[i]['filename'], mlp_mesh[i]['label']))
            mlp_file.write('\n'.join([
                '   <MLMatrix44>',
                '{m[0]} {m[1]} {m[2]} {m[3]} '.format(m=mlp_mesh[i]['matrix'][0]),
                '{m[0]} {m[1]} {m[2]} {m[3]} '.format(m=mlp_mesh[i]['matrix'][1]),
                '{m[0]} {m[1]} {m[2]} {m[3]} '.format(m=mlp_mesh[i]['matrix'][2]),
                '{m[0]} {m[1]} {m[2]} {m[3]} '.format(m=mlp_mesh[i]['matrix'][3]),
                '</MLMatrix44>',
                '  </MLMesh>\n']))
        mlp_file.write(' </MeshGroup>\n')
        mlp_file.close()
        # print(mlp_mesh)
    else:
        mlp_file = open(file_out, 'a')
        mlp_file.write(' <MeshGroup/>\n')
        mlp_file.close()
    if mlp_raster is not None:
        mlp_file = open(file_out, 'a')
        mlp_file.write(' <RasterGroup>\n')
        for i, val in enumerate(mlp_raster):
            if 'label' not in mlp_raster[i]:
                mlp_raster[i]['label'] = mlp_raster[i]['filename']
            if 'semantic' not in mlp_raster[i]:
                mlp_raster[i]['semantic'] = 1
            if 'lens_distortion' not in mlp_raster[i]['camera']:
                mlp_raster[i]['camera']['lens_distortion'] = [0, 0]
            if 'center_px' not in mlp_raster[i]['camera']:
                mlp_raster[i]['camera']['center_px'] = [int(mlp_raster[i]['camera']['image_px'][0]/2), int(mlp_raster[i]['camera']['image_px'][1]/2)]

            mlp_file.write('  <MLRaster label="{}">\n'.format(mlp_raster[i]['label']))

            mlp_file.write(' '.join([
                '   <VCGCamera',
                'TranslationVector="{m[0]} {m[1]} {m[2]} {m[3]}"'.format(m=mlp_raster[i]['camera']['trans_vector']),
                'RotationMatrix="{m[0][0]} {m[0][1]} {m[0][2]} {m[0][3]} {m[1][0]} {m[1][1]} {m[1][2]} {m[1][3]} {m[2][0]} {m[2][1]} {m[2][2]} {m[2][3]} {m[3][0]} {m[3][1]} {m[3][2]} {m[3][3]} "'.format(m=mlp_raster[i]['camera']['rotation_matrix']),
                'FocalMm="{}"'.format(mlp_raster[i]['camera']['focal_length']),
                'ViewportPx="{m[0]} {m[1]}"'.format(m=mlp_raster[i]['camera']['image_px']),
                'PixelSizeMm="{m[0]} {m[1]}"'.format(m=mlp_raster[i]['camera']['image_res_mm_per_px']),
                'LensDistortion="{m[0]} {m[1]}"'.format(m=mlp_raster[i]['camera']['lens_distortion']),
                'CenterPx="{m[0]} {m[1]}"'.format(m=mlp_raster[i]['camera']['center_px']),
                '/>\n']))
            mlp_file.write('   <Plane semantic="{}" fileName="{}"/>\n'.format(mlp_raster[i]['semantic'], mlp_raster[i]['filename']))
            mlp_file.write(' </MLRaster>\n')
        mlp_file.write(' </RasterGroup>\n')
        mlp_file.close()
        # print(mlp_raster)
    else:
        mlp_file = open(file_out, 'a')
        mlp_file.write(' <RasterGroup/>\n')
        mlp_file.close()

    # Closing lines
    mlp_file = open(file_out, 'a')
    mlp_file.write('</MeshLabProject>\n')
    mlp_file.close()
    return


#!/usr/bin/python3
"""Example MeshLabXML script to create a heroic shield.

MeshLab is typically used to process existing meshes; this script was
created to demonstrate some of MeshLab's mesh creation features as well.
It demonstrates combining python functionality (math and flow control)
and MeshLab to create a parametric 3D model that's truly heroic!

Note that the final model is composed of separate surfaces. It is not
manifold and is e.g. not suitable for 3D printing; it's just a silly
example.

License:
    Written in 2016 by Tim Ayres 3DLirious@gmail.com

    To the extent possible under law, the author(s) have dedicated all
    copyright and related and neighboring rights to this software to the
    public domain worldwide. This software is distributed without any
    warranty.

    You should have received a copy of the CC0 Public Domain Dedication
    along with this software. If not, see
    <http://creativecommons.org/publicdomain/zero/1.0/>.

"""

import os
import math

import meshlabxml as mlx

# Add meshlabserver directory to OS PATH; omit this if it is already in
# your PATH
MESHLABSERVER_PATH = 'C:\\Program Files\\VCG\\MeshLab'
os.environ['PATH'] += os.pathsep + MESHLABSERVER_PATH

def main():
    """Run main script"""
    # segments = number of segments to use for circles
    segments = 50
    # star_points = number of points (or sides) of the star
    star_points = 5
    # star_radius = radius of circle circumscribing the star
    star_radius = 2
    # ring_thickness = thickness of the colored rings
    ring_thickness = 1
    # sphere_radius = radius of sphere the shield will be deformed to
    sphere_radius = 2 * (star_radius + 3 * ring_thickness)

    # Star calculations:
    # Visually approximate a star by using multiple diamonds (i.e. scaled
    # squares) which overlap in the center. For the star calculations,
    # consider a central polygon with triangles attached to the edges, all
    # circumscribed by a circle.
    # polygon_radius = distance from center of circle to polygon edge midpoint
    polygon_radius = star_radius / \
        (1 + math.tan(math.radians(180 / star_points)) /
         math.tan(math.radians(90 / star_points)))
    # width = 1/2 width of polygon edge/outer triangle bottom
    width = polygon_radius * math.tan(math.radians(180 / star_points))
    # height = height of outer triangle
    height = width / math.tan(math.radians(90 / star_points))

    shield = mlx.FilterScript(file_out="shield.ply")

    # Create the colored front of the shield using several concentric
    # annuluses; combine them together and subdivide so we have more vertices
    # to give a smoother deformation later.
    mlx.create.annulus(shield, radius=star_radius, cir_segments=segments, color='blue')
    mlx.create.annulus(shield,
        radius1=star_radius + ring_thickness,
        radius2=star_radius,
        cir_segments=segments,
        color='red')
    mlx.create.annulus(shield,
        radius1=star_radius + 2 * ring_thickness,
        radius2=star_radius + ring_thickness,
        cir_segments=segments,
        color='white')
    mlx.create.annulus(shield,
        radius1=star_radius + 3 * ring_thickness,
        radius2=star_radius + 2 * ring_thickness,
        cir_segments=segments,
        color='red')
    mlx.layers.join(shield)
    mlx.subdivide.midpoint(shield, iterations=2)

    # Create the inside surface of the shield & translate down slightly so it
    # doesn't overlap the front.
    mlx.create.annulus(shield,
        radius1=star_radius + 3 * ring_thickness,
        cir_segments=segments,
        color='silver')
    mlx.transform.rotate(shield, axis='y', angle=180)
    mlx.transform.translate(shield, value=[0, 0, -0.005])
    mlx.subdivide.midpoint(shield, iterations=4)

    # Create a diamond for the center star. First create a plane, specifying
    # extra vertices to support the final deformation. The length from the
    # center of the plane to the corners should be 1 for ease of scaling, so
    # we use a side length of sqrt(2) (thanks Pythagoras!). Rotate the plane
    # by 45 degrees and scale it to stretch it out per the calculations above,
    # then translate it into place (including moving it up in z slightly so
    # that it doesn't overlap the shield front).
    mlx.create.grid(shield,
        size=math.sqrt(2),
        x_segments=10,
        y_segments=10,
        center=True,
        color='white')
    mlx.transform.rotate(shield, axis='z', angle=45)
    mlx.transform.scale(shield, value=[width, height, 1])
    mlx.transform.translate(shield, value=[0, polygon_radius, 0.001])

    # Duplicate the diamond and rotate the duplicates around, generating the
    # star.
    for _ in range(1, star_points):
        mlx.layers.duplicate(shield)
        mlx.transform.rotate(shield, axis='z', angle=360 / star_points)

    # Combine everything together and deform using a spherical function.
    mlx.layers.join(shield)
    mlx.transform.vert_function(shield,
        z_func='sqrt(%s-x^2-y^2)-%s+z' %
        (sphere_radius**2, sphere_radius))

    # Run the script using meshlabserver and generate the model
    shield.run_script()
    return None

if __name__ == '__main__':
    main()


""" MeshLabXML deletion functions"""

from . import util
from . import select

def nonmanifold_vert(script):
    """ Select & delete the non manifold vertices that do not belong to
        non manifold edges.

    For example two cones connected by their apex. Vertices incident on
    non manifold edges are ignored.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    select.nonmanifold_vert(script)
    selected(script, face=False)
    return None

def nonmanifold_edge(script):
    """ Select & delete the faces and the vertices incident on
        non manifold edges (e.g. edges where more than two faces are incident).

    Note that this function selects the components that are related to
    non manifold edges. The case of non manifold vertices is specifically
    managed by nonmanifold_vert.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    select.nonmanifold_edge(script)
    selected(script, face=False)
    return None

def small_parts(script, ratio=0.2, non_closed_only=False):
    """ Select & delete the small disconnected parts (components) of a mesh.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        ratio (float): This ratio (between 0 and 1) defines the meaning of
            'small' as the threshold ratio between the number of faces of the
            largest component and the other ones. A larger value will select
            more components.
        non_closed_only (bool): Select only non-closed components.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    select.small_parts(script, ratio, non_closed_only)
    selected(script)
    return None

def selected(script, face=True, vert=True):
    """ Delete selected vertices and/or faces

    Note: if the mesh has no faces (e.g. a point cloud) you must
    set face=False, or the vertices will not be deleted

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        face (bool): if True the selected faces will be deleted. If vert
            is also True, then all the vertices surrounded by those faces will
            also be deleted. Note that if no faces are selected (only vertices)
            then this filter will not do anything. For example, if you want to
            delete a point cloud selection, you must set this to False.
        vert (bool): if True the selected vertices will be deleted.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if face and vert:
        filter_xml = '  <filter name="Delete Selected Faces and Vertices"/>\n'
    elif face and not vert:
        filter_xml = '  <filter name="Delete Selected Faces"/>\n'
    elif not face and vert:
        filter_xml = '  <filter name="Delete Selected Vertices"/>\n'
    util.write_filter(script, filter_xml)
    return None

def faces_from_nonmanifold_edges(script):
    """ For each non manifold edge it iteratively deletes the smallest area
        face until it becomes two-manifold.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Remove Faces from Non Manifold Edges"/>\n'
    util.write_filter(script, filter_xml)
    unreferenced_vert(script)
    return None

def unreferenced_vert(script):
    """ Check for every vertex on the mesh: if it is NOT referenced by a face,
        removes it.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if script.ml_version == '1.3.4BETA':
        filter_xml = '  <filter name="Remove Unreferenced Vertex"/>\n'
    else:
        filter_xml = '  <filter name="Remove Unreferenced Vertices"/>\n'
    util.write_filter(script, filter_xml)
    return None

def duplicate_faces(script):
    """ Delete all the duplicate faces.

    Two faces are considered equal if they are composed by the same set of
    vertices, regardless of the order of the vertices.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Remove Duplicate Faces"/>\n'
    util.write_filter(script, filter_xml)
    return None

def duplicate_verts(script):
    """ "Check for every vertex on the mesh: if there are two vertices with
        the same coordinates they are merged into a single one.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if script.ml_version == '1.3.4BETA':
        filter_xml = '  <filter name="Remove Duplicated Vertex"/>\n'
    else:
        filter_xml = '  <filter name="Remove Duplicate Vertices"/>\n'

    util.write_filter(script, filter_xml)
    return None

def zero_area_face(script):
    """ Remove null faces (the ones with area equal to zero)

    Args:
        script: the FilterScript object or script filename to write
            the filter to.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Remove Zero Area Faces"/>\n'
    util.write_filter(script, filter_xml)
    return None


""" MeshLabXML sampling functions """

from . import FilterScript
from . import util

def hausdorff_distance(script, sampled_layer=1, target_layer=0,
                       save_sample=False, sample_vert=True, sample_edge=True,
                       sample_faux_edge=False, sample_face=True,
                       sample_num=1000, maxdist=10):
    """ Compute the Hausdorff Distance between two meshes, sampling one of the
        two and finding for each sample the closest point over the other mesh.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        sampled_layer (int): The mesh layer whose surface is sampled. For each
            sample we search the closest point on the target mesh layer.
        target_layer (int): The mesh that is sampled for the comparison.
        save_sample (bool): Save the position and distance of all the used
            samples on both the two surfaces, creating two new layers with two
            point clouds representing the used samples.
        sample_vert (bool): For the search of maxima it is useful to sample
            vertices and edges of the mesh with a greater care. It is quite
            probable that the farthest points falls along edges or on mesh
            vertexes, and with uniform montecarlo sampling approaches the
            probability of taking a sample over a vertex or an edge is
            theoretically null. On the other hand this kind of sampling could
            make the overall sampling distribution slightly biased and slightly
            affects the cumulative results.
        sample_edge (bool): see sample_vert
        sample_faux_edge (bool): see sample_vert
        sample_face (bool): see sample_vert
        sample_num (int): The desired number of samples. It can be smaller or
            larger than the mesh size, and according to the chosen sampling
            strategy it will try to adapt.
        maxdist (int): Sample points for which we do not find anything within
            this distance are rejected and not considered neither for averaging
            nor for max.

    Layer stack:
        If save_sample is True, two new layers are created: 'Hausdorff Closest
            Points' and 'Hausdorff Sample Point'; and the current layer is
            changed to the last newly created layer.
        If save_sample is False, no impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    # MeshLab defaults:
    #  sample_num = number of vertices
    #  maxdist = 0.05 * AABB['diag'] #5% of AABB[diag]
    #  maxdist_max = AABB['diag']
    maxdist_max = 2*maxdist
    # TODO: parse output (min, max, mean, etc.)
    filter_xml = ''.join([
        '  <filter name="Hausdorff Distance">\n',
        '    <Param name="SampledMesh" ',
        'value="{:d}" '.format(sampled_layer),
        'description="Sampled Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="TargetMesh" ',
        'value="{:d}" '.format(target_layer),
        'description="Target Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="SaveSample" ',
        'value="{}" '.format(str(save_sample).lower()),
        'description="Save Samples" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SampleVert" ',
        'value="{}" '.format(str(sample_vert).lower()),
        'description="Sample Vertexes" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SampleEdge" ',
        'value="{}" '.format(str(sample_edge).lower()),
        'description="Sample Edges" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SampleFauxEdge" ',
        'value="{}" '.format(str(sample_faux_edge).lower()),
        'description="Sample FauxEdge" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SampleFace" ',
        'value="{}" '.format(str(sample_face).lower()),
        'value="%s" ' % str(sample_face).lower() +
        'description="Sample Faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SampleNum" ',
        'value="{:d}" '.format(sample_num),
        'description="Number of samples" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="MaxDist" ',
        'value="{}" '.format(maxdist),
        'value="%s" ' % maxdist +
        'description="Max Distance" ',
        'min="0" ',
        'max="{}" '.format(maxdist_max),
        'type="RichAbsPerc" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.parse_hausdorff = True
    if isinstance(script, FilterScript) and save_sample:
        script.add_layer('Hausdorff Closest Points')
        script.add_layer('Hausdorff Sample Point')
    return None

def poisson_disk(script, sample_num=1000, radius=0.0,
                 montecarlo_rate=20, save_montecarlo=False,
                 approx_geodesic_dist=False, subsample=False, refine=False,
                 refine_layer=0, best_sample=True, best_sample_pool=10,
                 exact_num=False, radius_variance=1.0):
    """ Create a new layer populated with a point sampling of the current mesh.

    Samples are generated according to a Poisson-disk distribution, using the
    algorithm described in:

    'Efficient and Flexible Sampling with Blue Noise Properties of Triangular Meshes'
    Massimiliano Corsini, Paolo Cignoni, Roberto Scopigno
    IEEE TVCG 2012

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        sample_num (int): The desired number of samples. The radius of the disk
            is calculated according to the sampling density.
        radius (float): If not zero this parameter overrides the previous
            parameter to allow exact radius specification.
        montecarlo_rate (int): The over-sampling rate that is used to generate
            the intial Monte Carlo samples (e.g. if this parameter is 'K' means
            that 'K * sample_num' points will be used). The generated
            Poisson-disk samples are a subset of these initial Monte Carlo
            samples. Larger numbers slow the process but make it a bit more
            accurate.
        save_montecarlo (bool): If True, it will generate an additional Layer
            with the Monte Carlo sampling that was pruned to build the Poisson
            distribution.
        approx_geodesic_dist (bool): If True Poisson-disk distances are
            computed using an approximate geodesic distance, e.g. an Euclidean
            distance weighted by a function of the difference between the
            normals of the two points.
        subsample (bool): If True the original vertices of the base mesh are
            used as base set of points. In this case the sample_num should be
            obviously much smaller than the original vertex number. Note that
            this option is very useful in the case you want to subsample a
            dense point cloud.
        refine (bool): If True the vertices of the refine_layer mesh layer are
            used as starting vertices, and they will be utterly refined by
            adding more and more points until possible.
        refine_layer (int): Used only if refine is True.
        best_sample (bool): If True it will use a simple heuristic for choosing
            the samples. At a small cost (it can slow the process a bit) it
            usually improves the maximality of the generated sampling.
        best_sample_pool (bool): Used only if best_sample is True. It controls
            the number of attempts that it makes to get the best sample. It is
            reasonable that it is smaller than the Monte Carlo oversampling
            factor.
        exact_num (bool): If True it will try to do a dicotomic search for the
            best Poisson-disk radius that will generate the requested number of
            samples with a tolerance of the 0.5%. Obviously it takes much
            longer.
        radius_variance (float): The radius of the disk is allowed to vary
            between r and r*var. If this parameter is 1 the sampling is the
            same as the Poisson-disk Sampling.

    Layer stack:
        Creates new layer 'Poisson-disk Samples'. Current layer is NOT changed
            to the new layer (see Bugs).
        If save_montecarlo is True, creates a new layer 'Montecarlo Samples'.
            Current layer is NOT changed to the new layer (see Bugs).

    MeshLab versions:
        2016.12
        1.3.4BETA

    Bugs:
        Current layer is NOT changed to the new layer, which is inconsistent
            with the majority of filters that create new layers.
    """
    filter_xml = ''.join([
        '  <filter name="Poisson-disk Sampling">\n',
        '    <Param name="SampleNum" ',
        'value="{:d}" '.format(sample_num),
        'description="Number of samples" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Radius" ',
        'value="{}" '.format(radius),
        'description="Explicit Radius" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="MontecarloRate" ',
        'value="{:d}" '.format(montecarlo_rate),
        'description="MonterCarlo OverSampling" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="SaveMontecarlo" ',
        'value="{}" '.format(str(save_montecarlo).lower()),
        'description="Save Montecarlo" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="ApproximateGeodesicDistance" ',
        'value="{}" '.format(str(approx_geodesic_dist).lower()),
        'description="Approximate Geodesic Distance" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="Subsample" ',
        'value="{}" '.format(str(subsample).lower()),
        'description="Base Mesh Subsampling" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="RefineFlag" ',
        'value="{}" '.format(str(refine).lower()),
        'description="Refine Existing Samples" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="RefineMesh" ',
        'value="{:d}" '.format(refine_layer),
        'description="Samples to be refined" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="BestSampleFlag" ',
        'value="{}" '.format(str(best_sample).lower()),
        'description="Best Sample Heuristic" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="BestSamplePool" ',
        'value="{:d}" '.format(best_sample_pool),
        'description="Best Sample Pool Size" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="ExactNumFlag" ',
        'value="{}" '.format(str(exact_num).lower()),
        'description="Exact number of samples" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="RadiusVariance" ',
        'value="{}" '.format(radius_variance),
        'description="Radius Variance" ',
        'type="RichFloat" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Poisson-disk Samples')
        if save_montecarlo:
            script.add_layer('Montecarlo Samples')
    return None

def mesh_element(script, sample_num=1000, element='VERT'):
    """ Create a new layer populated with a point sampling of the current mesh,
        at most one sample for each element of the mesh is created.

    Samples are taking in a uniform way, one for each element
    (vertex/edge/face); all the elements have the same probabilty of being
    choosen.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        sample_num (int): The desired number of elements that must be chosen.
            Being a subsampling of the original elements if this number should
            not be larger than the number of elements of the original mesh.
        element (enum in ['VERT', 'EDGE', 'FACE']): Choose what mesh element
            will be used for the subsampling. At most one point sample will
            be added for each one of the chosen elements

    Layer stack:
        Creates new layer 'Sampled Mesh'. Current layer is changed to the new
            layer.

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if element.lower() == 'vert':
        element_num = 0
    elif element.lower() == 'edge':
        element_num = 1
    elif element.lower() == 'face':
        element_num = 2
    filter_xml = ''.join([
        '  <filter name="Mesh Element Subsampling">\n',
        '    <Param name="Sampling" ',
        'value="{:d}" '.format(element_num),
        'description="Element to sample:" ',
        'enum_val0="Vertex" ',
        'enum_val1="Edge" ',
        'enum_val2="Face" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="SampleNum" ',
        'value="{:d}" '.format(sample_num),
        'description="Number of samples" ',
        'type="RichInt" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Sampled Mesh')
    return None

def clustered_vert(script, cell_size=1.0, strategy='AVERAGE', selected=False):
    """ "Create a new layer populated with a subsampling of the vertexes of the
        current mesh

    The subsampling is driven by a simple one-per-gridded cell strategy.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        cell_size (float): The size of the cell of the clustering grid. Smaller the cell finer the resulting mesh. For obtaining a very coarse mesh use larger values.
        strategy (enum 'AVERAGE' or 'CENTER'): Average: for each cell we take the average of the sample falling into. The resulting point is a new point.
Closest to center: for each cell we take the sample that is closest to the center of the cell. Choosen vertices are a subset of the original ones.
        selected (bool): If true only for the filter is applied only on the selected subset of the mesh.

    Layer stack:
        Creates new layer 'Cluster Samples'. Current layer is changed to the new
            layer.

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if strategy.lower() == 'average':
        strategy_num = 0
    elif strategy.lower() == 'center':
        strategy_num = 1

    filter_xml = ''.join([
        '  <filter name="Clustered Vertex Subsampling">\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(cell_size),
        'description="Cell Size" ',
        'min="0" ',
        'max="1000" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Sampling" ',
        'value="{:d}" '.format(strategy_num),
        'description="Representative Strategy:" ',
        'enum_val0="Average" ',
        'enum_val1="Closest to center" ',
        'enum_cardinality="2" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Selected" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Cluster Samples')
    return None


""" MeshLabXML texture functions """

from . import util

def flat_plane(script, plane=0, aspect_ratio=False):
    """Flat plane parameterization

    """
    filter_xml = ''.join([
        '  <filter name="Parametrization: Flat Plane ">\n',
        '    <Param name="projectionPlane"',
        'value="%d"' % plane,
        'description="Projection plane"',
        'enum_val0="XY"',
        'enum_val1="XZ"',
        'enum_val2="YZ"',
        'enum_cardinality="3"',
        'type="RichEnum"',
        'tooltip="Choose the projection plane"',
        '/>\n',
        '    <Param name="aspectRatio"',
        'value="%s"' % str(aspect_ratio).lower(),
        'description="Preserve Ratio"',
        'type="RichBool"',
        'tooltip="If checked the resulting parametrization will preserve the original apsect ratio of the model otherwise it will fill up the whole 0..1 uv space"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def per_triangle(script, sidedim=0, textdim=1024, border=2, method=1):
    """Trivial Per-Triangle parameterization

    """
    filter_xml = ''.join([
        '  <filter name="Parametrization: Trivial Per-Triangle ">\n',
        '    <Param name="sidedim"',
        'value="%d"' % sidedim,
        'description="Quads per line"',
        'type="RichInt"',
        'tooltip="Indicates how many triangles have to be put on each line (every quad contains two triangles). Leave 0 for automatic calculation"',
        '/>\n',
        '    <Param name="textdim"',
        'value="%d"' % textdim,
        'description="Texture Dimension (px)"',
        'type="RichInt"',
        'tooltip="Gives an indication on how big the texture is"',
        '/>\n',
        '    <Param name="border"',
        'value="%d"' % border,
        'description="Inter-Triangle border (px)"',
        'type="RichInt"',
        'tooltip="Specifies how many pixels to be left between triangles in parametrization domain"',
        '/>\n',
        '    <Param name="method"',
        'value="%d"' % method,
        'description="Method"',
        'enum_val0="Basic"',
        'enum_val1="Space-optimizing"',
        'enum_cardinality="2"',
        'type="RichEnum"',
        'tooltip="Choose space optimizing to map smaller faces into smaller triangles in parametrizazion domain"'
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def voronoi(script, region_num=10, overlap=False):
    """Voronoi Atlas parameterization

    """
    filter_xml = ''.join([
        '  <filter name="Parametrization: Voronoi Atlas">\n',
        '    <Param name="regionNum"',
        'value="%d"' % region_num,
        'description="Approx. Region Num"',
        'type="RichInt"',
        'tooltip="An estimation of the number of regions that must be generated. Smaller regions could lead to parametrizations with smaller distortion."',
        '/>\n',
        '    <Param name="overlapFlag"',
        'value="%s"' % str(overlap).lower(),
        'description="Overlap"',
        'type="RichBool"',
        'tooltip="If checked the resulting parametrization will be composed by overlapping regions, e.g. the resulting mesh will have duplicated faces: each region will have a ring of ovelapping duplicate faces that will ensure that border regions will be parametrized in the atlas twice. This is quite useful for building mipmap robust atlases"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def isometric(script, targetAbstractMinFaceNum=140, targetAbstractMaxFaceNum=180,
              stopCriteria=1, convergenceSpeed=1, DoubleStep=True):
    """Isometric parameterization

    """
    filter_xml = ''.join([
        '  <filter name="Iso Parametrization">\n',
        '    <Param name="targetAbstractMinFaceNum"',
        'value="%d"' % targetAbstractMinFaceNum,
        'description="Abstract Min Mesh Size"',
        'type="RichInt"',
        'tooltip="This number and the following one indicate the range face number of the abstract mesh that is used for the parametrization process. The algorithm will choose the best abstract mesh with the number of triangles within the specified interval. If the mesh has a very simple structure this range can be very low and strict; for a roughly spherical object if you can specify a range of [8,8] faces you get a octahedral abstract mesh, e.g. a geometry image. <br>Large numbers (greater than 400) are usually not of practical use."',
        '/>\n',
        '    <Param name="targetAbstractMaxFaceNum"',
        'value="%d"' % targetAbstractMaxFaceNum,
        'description="Abstract Max Mesh Size"',
        'type="RichInt"',
        'tooltip="Please notice that a large interval requires huge amount of memory to be allocated, in order save the intermediate results. An interval of 40 should be fine."',
        '/>\n',
        '    <Param name="stopCriteria"',
        'value="%d"' % stopCriteria,
        'description="Optimization Criteria"',
        'enum_val0="Best Heuristic"',
        'enum_val1="Area + Angle"',
        'enum_val2="Regularity"',
        'enum_val3="L2"',
        'enum_cardinality="4"',
        'type="RichEnum"',
        'tooltip="Choose a metric to stop the parametrization within the interval. 1: Best Heuristic : stop considering both isometry and number of faces of base domain. 2: Area + Angle : stop at minimum area and angle distorsion. 3: Regularity : stop at minimum number of irregular vertices. 4: L2 : stop at minimum OneWay L2 Stretch Eff"',
        '/>\n',
        '    <Param name="convergenceSpeed"',
        'value="%d"' % convergenceSpeed,
        'description="Convergence Precision"',
        'type="RichInt"',
        'tooltip="This parameter controls the convergence speed/precision of the optimization of the texture coordinates. Larger the number slower the processing and, eventually, slightly better results"',
        '/>\n',
        '    <Param name="DoubleStep"',
        'value="%s"' % str(DoubleStep).lower(),
        'description="Double Step"',
        'type="RichBool"',
        'tooltip="Use this bool to divide the parameterization in 2 steps. Double step makes the overall process faster and robust. Consider to disable this bool in case the object has topologycal noise or small handles."',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def isometric_build_atlased_mesh(script, BorderSize=0.1):
    """Isometric parameterization: Build Atlased Mesh

    This actually generates the UV mapping from the isometric parameterization

    """
    filter_xml = ''.join([
        '  <filter name="Iso Parametrization Build Atlased Mesh">\n',
        '    <Param name="BorderSize"',
        'value="%s"' % BorderSize,
        'description="BorderSize ratio"',
        'min="0.01"',
        'max="0.5"',
        'type="RichDynamicFloat"',
        'tooltip="This parameter controls the amount of space that must be left between each diamond when building the atlas. It directly affects how many triangle are splitted during this conversion. In abstract parametrization mesh triangles can naturally cross the triangles of the abstract domain, so when converting to a standard parametrization we must cut all the triangles that protrudes outside each diamond more than the specified threshold. The unit of the threshold is in percentage of the size of the diamond, the bigger the threshold the less triangles are splitted, but the more UV space is used (wasted)."',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def isometric_save(script, AbsName="TEMP3D.abs"):
    """Isometric parameterization: Save Abstract Domain

    """
    filter_xml = ''.join([
        '  <filter name="Iso Parametrization Save Abstract Domain">\n',
        '    <Param name="AbsName"',
        'value="%s"' % AbsName,
        'description="Abstract Mesh file"',
        'type="RichString"',
        'tooltip="The filename where the abstract mesh has to be saved"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def isometric_load(script, AbsName="TEMP3D.abs"):
    """Isometric parameterization: Load Abstract Domain

    """
    filter_xml = ''.join([
        '  <filter name="Iso Parametrization Load Abstract Domain">\n',
        '    <Param name="AbsName"',
        'value="%s"' % AbsName,
        'description="Abstract Mesh file"',
        'type="RichString"',
        'tooltip="The filename of the abstract mesh that has to be loaded"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def isometric_transfer(script, sourceMesh=0, targetMesh=1):
    """Isometric parameterization: transfer between meshes

    Provide the layer numbers of the source and target meshes.

    """
    filter_xml = ''.join([
        '  <filter name="Iso Parametrization transfer between meshes">\n',
        '    <Param name="sourceMesh"',
        'value="%s"' % sourceMesh,
        'description="Source Mesh"',
        'type="RichMesh"',
        'tooltip="The mesh already having an Isoparameterization"',
        '/>\n',
        '    <Param name="targetMesh"',
        'value="%s"' % targetMesh,
        'description="Target Mesh"',
        'type="RichMesh"',
        'tooltip="The mesh to be Isoparameterized"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def isometric_remesh(script, SamplingRate=10):
    """Isometric parameterization: remeshing

    """
    filter_xml = ''.join([
        '  <filter name="Iso Parametrization Remeshing">\n',
        '    <Param name="SamplingRate"',
        'value="%d"' % SamplingRate,
        'description="Sampling Rate"',
        'type="RichInt"',
        'tooltip="This specify the sampling rate for remeshing."',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def set_texture(script, textName="TEMP3D.png", textDim=1024):
    """Set texture

    """
    filter_xml = ''.join([
        '  <filter name="Set Texture">\n',
        '    <Param name="textName"',
        'value="%s"' % textName,
        'description="Texture file"',
        'type="RichString"',
        'tooltip="If the file exists it will be associated to the mesh else a dummy one will be created"',
        '/>\n',
        '    <Param name="textDim"',
        'value="%d"' % textDim,
        'description="Texture Dimension (px)"',
        'type="RichInt"',
        'tooltip="If the named texture doesn\'t exists the dummy one will be squared with this size"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def project_rasters(script, tex_file_out="TEMP3D.png", tex_size=1024,
                    fill_atlas_gaps=False, depth_threshold=0.5,
                    selected=False, use_angle=True, use_distance=True,
                    use_borders=True, use_silhouettes=True, use_alpha=False):
    """Set texture

    Creates new texture file
    tex_file_out = must be png
    fill_atlas_gaps  = setting this to false will leave the unprojected area transparent. This can then be easily composed with the original texture with PIL

    """
    filter_xml = ''.join([
        '  <filter name="Project active rasters color to current mesh, filling the texture">\n',
        '    <Param name="textName"',
        'value="%s"' % tex_file_out,
        'description="Texture file"',
        'type="RichString"',
        'tooltip="The texture file to be created"',
        '/>\n',
        '    <Param name="texsize"',
        'value="%d"' % tex_size,
        'description="pixel size of texture image"',
        'type="RichInt"',
        'tooltip="pixel size of texture image, the image will be a square tsize X tsize, most applications do require that tsize is a power of 2"',
        '/>\n',
        '    <Param name="dorefill"',
        'value="%s"' % str(fill_atlas_gaps).lower(),
        'description="fill atlas gaps"',
        'type="RichBool"',
        'tooltip="If true, unfilled areas of the mesh are interpolated, to avoid visible seams while mipmapping"',
        '/>\n',
        '    <Param name="deptheta"',
        'value="%s"' % depth_threshold,
        'description="depth threshold"',
        'type="RichFloat"',
        'tooltip="threshold value for depth buffer projection (shadow buffer)"',
        '/>\n',
        '    <Param name="onselection"',
        'value="%s"' % str(selected).lower(),
        'description="Only on selecton"',
        'type="RichBool"',
        'tooltip="If true, projection is only done for selected vertices"',
        '/>\n',
        '    <Param name="useangle"',
        'value="%s"' % str(use_angle).lower(),
        'description="use angle weight"',
        'type="RichBool"',
        'tooltip="If true, color contribution is weighted by pixel view angle"',
        '/>\n',
        '    <Param name="usedistance"',
        'value="%s"' % str(use_distance).lower(),
        'description="use distance weight"',
        'type="RichBool"',
        'tooltip="If true, color contribution is weighted by pixel view distance"',
        '/>\n',
        '    <Param name="useborders"',
        'value="%s"' % str(use_borders).lower(),
        'description="use image borders weight"',
        'type="RichBool"',
        'tooltip="If true, color contribution is weighted by pixel distance from image boundaries"',
        '/>\n',
        '    <Param name="usesilhouettes"',
        'value="%s"' % str(use_silhouettes).lower(),
        'description="use depth discontinuities weight"',
        'type="RichBool"',
        'tooltip="If true, color contribution is weighted by pixel distance from depth discontinuities (external and internal silhouettes)"',
        '/>\n',
        '    <Param name="usealpha"',
        'value="%s"' % str(use_alpha).lower(),
        'description="use image alpha weight"',
        'type="RichBool"',
        'tooltip="If true, alpha channel of the image is used as additional weight. In this way it is possible to mask-out parts of the images that should not be projected on the mesh. Please note this is not a transparency effect, but just influences the weighting between different images"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def param_texture_from_rasters(script, textName="TEMP3D.png", texsize=1024,
                               colorCorrection=True, colorCorrectionFilterSize=1,
                               useDistanceWeight=True, useImgBorderWeight=True,
                               useAlphaWeight=False, cleanIsolatedTriangles=True,
                               stretchingAllowed=False, textureGutter=4):
    """Set texture

    """
    filter_xml = ''.join([
        '  <filter name="Parameterization + texturing from registered rasters">\n',
        '    <Param name="textureSize"',
        'value="%d"' % texsize,
        'description="Texture size"',
        'type="RichInt"',
        'tooltip="Specifies the dimension of the generated texture"',
        '/>\n',
        '    <Param name="textureName"',
        'value="%s"' % textName,
        'description="Texture name"',
        'type="RichString"',
        'tooltip="Specifies the name of the file into which the texture image will be saved"',
        '/>\n',
        '    <Param name="colorCorrection"',
        'value="%s"' % str(colorCorrection).lower(),
        'description="Color correction"',
        'type="RichBool"',
        'tooltip="If true, the final texture is corrected so as to ensure seamless transitions"',
        '/>\n',
        '    <Param name="colorCorrectionFilterSize"',
        'value="%d"' % colorCorrectionFilterSize,
        'description="Color correction filter"',
        'type="RichInt"',
        'tooltip="It is the radius (in pixel) of the kernel that is used to compute the difference between corresponding texels in different rasters. Default is 1 that generate a 3x3 kernel. Highest values increase the robustness of the color correction process in the case of strong image-to-geometry misalignments"',
        '/>\n',
        '    <Param name="useDistanceWeight"',
        'value="%s"' % str(useDistanceWeight).lower(),
        'description="Use distance weight"',
        'type="RichBool"',
        'tooltip="Includes a weight accounting for the distance to the camera during the computation of reference images"',
        '/>\n',
        '    <Param name="useImgBorderWeight"',
        'value="%s"' % str(useImgBorderWeight).lower(),
        'description="Use image border weight"',
        'type="RichBool"',
        'tooltip="Includes a weight accounting for the distance to the image border during the computation of reference images"',
        '/>\n',
        '    <Param name="useAlphaWeight"',
        'value="%s"' % str(useAlphaWeight).lower(),
        'description="Use image alpha weight"',
        'type="RichBool"',
        'tooltip="If true, alpha channel of the image is used as additional weight. In this way it is possible to mask-out parts of the images that should not be projected on the mesh. Please note this is not a transparency effect, but just influences the weigthing between different images"',
        '/>\n',
        '    <Param name="cleanIsolatedTriangles"',
        'value="%s"' % str(cleanIsolatedTriangles).lower(),
        'description="Clean isolated triangles"',
        'type="RichBool"',
        'tooltip="Remove all patches compound of a single triangle by aggregating them to adjacent patches"',
        '/>\n',
        '    <Param name="stretchingAllowed"',
        'value="%s"' % str(stretchingAllowed).lower(),
        'description="UV stretching"',
        'type="RichBool"',
        'tooltip="If true, texture coordinates are stretched so as to cover the full interval [0,1] for both directions"',
        '/>\n',
        '    <Param name="textureGutter"',
        'value="%d"' % textureGutter,
        'description="Texture gutter"',
        'type="RichInt"',
        'tooltip="Extra boundary to add to each patch before packing in texture space (in pixels)"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def param_from_rasters(script, useDistanceWeight=True, useImgBorderWeight=True,
                       useAlphaWeight=False, cleanIsolatedTriangles=True,
                       stretchingAllowed=False, textureGutter=4):
    """Set texture

    """
    filter_xml = ''.join([
        '  <filter name="Parameterization from registered rasters">\n',
        '    <Param name="useDistanceWeight"',
        'value="%s"' % str(useDistanceWeight).lower(),
        'description="Use distance weight"',
        'type="RichBool"',
        'tooltip="Includes a weight accounting for the distance to the camera during the computation of reference images"',
        '/>\n',
        '    <Param name="useImgBorderWeight"',
        'value="%s"' % str(useImgBorderWeight).lower(),
        'description="Use image border weight"',
        'type="RichBool"',
        'tooltip="Includes a weight accounting for the distance to the image border during the computation of reference images"',
        '/>\n',
        '    <Param name="useAlphaWeight"',
        'value="%s"' % str(useAlphaWeight).lower(),
        'description="Use image alpha weight"',
        'type="RichBool"',
        'tooltip="If true, alpha channel of the image is used as additional weight. In this way it is possible to mask-out parts of the images that should not be projected on the mesh. Please note this is not a transparency effect, but just influences the weighting between different images"',
        '/>\n',
        '    <Param name="cleanIsolatedTriangles"',
        'value="%s"' % str(cleanIsolatedTriangles).lower(),
        'description="Clean isolated triangles"',
        'type="RichBool"',
        'tooltip="Remove all patches compound of a single triangle by aggregating them to adjacent patches"',
        '/>\n',
        '    <Param name="stretchingAllowed"',
        'value="%s"' % str(stretchingAllowed).lower(),
        'description="UV stretching"',
        'type="RichBool"',
        'tooltip="If true, texture coordinates are stretched so as to cover the full interval [0,1] for both directions"',
        '/>\n',
        '    <Param name="textureGutter"',
        'value="%d"' % textureGutter,
        'description="Texture gutter"',
        'type="RichInt"',
        'tooltip="Extra boundary to add to each patch before packing in texture space (in pixels)"',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None