""" MeshLabXML subdivide functions """

from . import util

def loop(script, iterations=1, loop_weight=0, edge_threshold=0,
         selected=False):
    """ Apply Loop's Subdivision Surface algorithm.

    It is an approximant subdivision method and it works for every triangle
    and has rules for extraordinary vertices.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): Number of times the model is subdivided.
        loop_weight (int): Change the weights used. Allow to optimize some
            behaviours in spite of others. Valid values are:
            0 - Loop (default)
            1 - Enhance regularity
            2 - Enhance continuity
        edge_threshold (float): All the edges longer than this threshold will
            be refined. Setting this value to zero will force a uniform
            refinement.
        selected (bool): If selected the filter is performed only on the
            selected faces.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Subdivision Surfaces: Loop">\n',
        '    <Param name="LoopWeight" ',
        'value="{:d}" '.format(loop_weight),
        'description="Weighting scheme" ',
        'enum_val0="Loop" ',
        'enum_val1="Enhance regularity" ',
        'enum_val2="Enhance continuity" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="Iterations" ',
        'value="{:d}" '.format(iterations),
        'description="Iterations" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(edge_threshold),
        'description="Edge Threshold" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Affect only selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def ls3loop(script, iterations=1, loop_weight=0, edge_threshold=0,
            selected=False):
    """ Apply LS3 Subdivision Surface algorithm using Loop's weights.

    This refinement method take normals into account.
    See: Boye', S. Guennebaud, G. & Schlick, C.
    "Least squares subdivision surfaces"
    Computer Graphics Forum, 2010.

    Alternatives weighting schemes are based on the paper:
    Barthe, L. & Kobbelt, L.
    "Subdivision scheme tuning around extraordinary vertices"
    Computer Aided Geometric Design, 2004, 21, 561-583.

    The current implementation of these schemes don't handle vertices of
    valence > 12

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): Number of times the model is subdivided.
        loop_weight (int): Change the weights used. Allow to optimize some
            behaviours in spite of others. Valid values are:
            0 - Loop (default)
            1 - Enhance regularity
            2 - Enhance continuity
        edge_threshold (float): All the edges longer than this threshold will
            be refined. Setting this value to zero will force a uniform
            refinement.
        selected (bool): If selected the filter is performed only on the
            selected faces.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Subdivision Surfaces: LS3 Loop">\n',
        '    <Param name="LoopWeight" ',
        'value="{:d}" '.format(loop_weight),
        'description="Weighting scheme" ',
        'enum_val0="Loop" ',
        'enum_val1="Enhance regularity" ',
        'enum_val2="Enhance continuity" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="Iterations" ',
        'value="{:d}" '.format(iterations),
        'description="Iterations" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(edge_threshold),
        'description="Edge Threshold" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Affect only selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def midpoint(script, iterations=1, edge_threshold=0, selected=False):
    """ Apply a plain subdivision scheme where every edge is split on its
        midpoint.

    Useful to uniformly refine a mesh substituting each triangle with four
    smaller triangles.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): Number of times the model is subdivided.
        edge_threshold (float): All the edges longer than this threshold will
            be refined. Setting this value to zero will force a uniform
            refinement.
        selected (bool): If selected the filter is performed only on the
            selected faces.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Subdivision Surfaces: Midpoint">\n',
        '    <Param name="Iterations" ',
        'value="{:d}" '.format(iterations),
        'description="Iterations" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(edge_threshold),
        'description="Edge Threshold" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Affect only selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def butterfly(script, iterations=1, edge_threshold=0, selected=False):
    """ Apply Butterfly Subdivision Surface algorithm.

    It is an interpolated method, defined on arbitrary triangular meshes.
    The scheme is known to be C1 but not C2 on regular meshes.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): Number of times the model is subdivided.
        edge_threshold (float): All the edges longer than this threshold will
            be refined. Setting this value to zero will force a uniform
            refinement.
        selected (bool): If selected the filter is performed only on the
            selected faces.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Subdivision Surfaces: Butterfly Subdivision">\n',
        '    <Param name="Iterations" ',
        'value="{:d}" '.format(iterations),
        'description="Iterations" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(edge_threshold),
        'description="Edge Threshold" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Affect only selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def catmull_clark(script):
    """ Apply the Catmull-Clark Subdivision Surfaces.

    Note that position of the new vertices is simply linearly interpolated.
    If the mesh is triangle based (no faux edges) it generates a quad mesh,
    otherwise it honors the faux-edge bits.

    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="Subdivision Surfaces: Catmull-Clark"/>\n'
    util.write_filter(script, filter_xml)
    return None


"""MeshLabXML cleaning and repairing functions

See select and delete modules for additional cleaning functions
"""

from . import util

def merge_vert(script, threshold=0.0):
    """ Merge together all the vertices that are nearer than the specified
    threshold. Like a unify duplicate vertices but with some tolerance.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        threshold (float): Merging distance. All the vertices that are closer
            than this threshold are merged together. Use very small values,
            default is zero.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Merge Close Vertices">\n',
        '    <Param name="Threshold" ',
        'value="{}" '.format(threshold),
        'description="Merging distance" ',
        'min="0" ',
        'max="1" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def close_holes(script, hole_max_edge=30, selected=False,
                sel_new_face=True, self_intersection=True):
    """ Close holes smaller than a given threshold

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        hole_max_edge (int): The size is expressed as number of edges composing
            the hole boundary.
        selected (bool): Only the holes with at least one of the boundary faces
            selected are closed.
        sel_new_face (bool): After closing a hole the faces that have been
            created are left selected. Any previous selection is lost. Useful
            for example for smoothing or subdividing the newly created holes.
        self_intersection (bool): When closing an holes it tries to prevent the
            creation of faces that intersect faces adjacent to the boundary of
            the hole. It is an heuristic, non intersecting hole filling can be
            NP-complete.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Close Holes">\n',
        '    <Param name="maxholesize" ',
        'value="{:d}" '.format(hole_max_edge),
        'description="Max size to be closed" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Close holes with selected faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="NewFaceSelected" ',
        'value="{}" '.format(str(sel_new_face).lower()),
        'description="Select the newly created faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SelfIntersection" ',
        'value="{}" '.format(str(self_intersection).lower()),
        'description="Prevent creation of selfIntersecting faces" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def split_vert_on_nonmanifold_face(script, vert_displacement_ratio=0.0):
    """ Split non-manifold vertices until it becomes two-manifold.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        vert_displacement_ratio (float): When a vertex is split it is moved
            along the average vector going from its position to the centroid
            of the FF connected faces sharing it.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Split Vertexes Incident on Non Manifold Faces">\n',
        '    <Param name="VertDispRatio" ',
        'value="{}" '.format(vert_displacement_ratio),
        'description="Vertex Displacement Ratio" ',
        'type="RichFloat" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def fix_folded_face(script):
    """ Delete all the single folded faces.

    A face is considered folded if its normal is opposite to all the adjacent
    faces. It is removed by flipping it against the face f adjacent along the
    edge e such that the vertex opposite to e fall inside f.

    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 Isolated Folded Faces by Edge Flip"/>\n'
    util.write_filter(script, filter_xml)
    return None

def snap_mismatched_borders(script, edge_dist_ratio=0.01, unify_vert=True):
    """ Try to snap together adjacent borders that are slightly mismatched.

    This situation can happen on badly triangulated adjacent patches defined by
    high order surfaces. For each border vertex the filter snaps it onto the
    closest boundary edge only if it is closest of edge_legth*threshold. When
    vertex is snapped the corresponding face it split and a new vertex is
    created.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        edge_dist_ratio (float): Collapse edge when the edge / distance ratio
            is greater than this value. E.g. for default value 1000 two
            straight border edges are collapsed if the central vertex dist from
            the straight line composed by the two edges less than a 1/1000 of
            the sum of the edges length. Larger values enforce that only
            vertexes very close to the line are removed.
        unify_vert (bool): If true the snap vertices are welded together.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Snap Mismatched Borders">\n',
        '    <Param name="EdgeDistRatio" ',
        'value="{}" '.format(edge_dist_ratio),
        'description="Edge Distance Ratio" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="UnifyVertices" ',
        'value="{}" '.format(str(unify_vert).lower()),
        'description="UnifyVertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None


""" MeshLabXML transformation and deformation functions """

import sys
import math
import re

from . import FilterScript
from . import util
from . import mp_func

def translate2(script, value=(0.0, 0.0, 0.0), center=False, freeze=True,
               all_layers=False):
    """

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        value (vector, tuple or list): Absolute translation value along the X axis,
        center Translate center of bbox to the origin.
        freeze (bool): The transformation is explicitly applied and the  vertex coords are actually changed.
        all_layers (bool): The transformation is explicitly applied to all the mesh and raster layers in the project.

    """
    # Convert value to list if it isn't already
    if not isinstance(value, list):
        value = list(value)
    filter_xml = ''.join([
        '  <filter name="Transform: Move, Translate, Center">\n',
        '    <Param name="axisX" ',
        'value="%s" ' % value[0],
        'description="X Axis" ',
        'min="-500" ',
        'max="500" ',
        'type="RichDynamicFloat" ',
        '/>\n',
        '    <Param name="axisY" ',
        'value="%s" ' % value[1],
        'description="Y Axis" ',
        'min="-500" ',
        'max="500" ',
        'type="RichDynamicFloat" ',
        '/>\n',
        '    <Param name="axisZ" ',
        'value="%s" ' % value[2],
        'description="Z Axis" ',
        'min="-500" ',
        'max="500" ',
        'type="RichDynamicFloat" ',
        '/>\n',
        '    <Param name="centerFlag" ',
        'value="%s" ' % str(center).lower(),
        'description="Translate center of bbox to the origin." ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="Freeze" ',
        'value="%s" ' % str(freeze).lower(),
        'description="Freeze Matrix." ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="ToAll" ',
        'value="%s" ' % str(all_layers).lower(),
        'description="Apply to all layers." ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def translate(script, value=(0.0, 0.0, 0.0)):
    """An alternative translate implementation that uses a geometric function.
    This is more accurate than the built-in version."""
    # Convert value to list if it isn't already
    if not isinstance(value, list):
        value = list(value)
    vert_function(script,
             x_func='x+(%s)' % value[0],
             y_func='y+(%s)' % value[1],
             z_func='z+(%s)' % value[2])
    return None

def rotate2(script, axis='z', angle=0.0, custom_axis=None, center_pt='origin',
            custom_center_pt=None, freeze=True, all_layers=False):
    """

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        axis (str): Choose a method.
        angle (float): "Angle of rotation (in degrees). If snapping is',
        ' enabled this value is rounded according to the snap',
        ' value.
        custom_axis (vector): This rotation axis is used only if the',
        ' _custom axis_ option is chosen.
        center_pt (str): Choose a method.
        custom_center_pt (bool): This rotation center is used only if the',
        ' _custom point_ option is chosen.
        freeze (bool): The transformation is explicitly applied and the',
        ' vertex coords are actually changed.
        all_layers (bool): The transformation is explicitly applied to all the',
        ' mesh and raster layers in the project.
    """
    # Convert axis name into number
    if axis.lower() == 'x':
        axis_num = 0
    elif axis.lower() == 'y':
        axis_num = 1
    elif axis.lower() == 'z':
        axis_num = 2
    else:  # custom axis
        axis_num = 3
        if custom_axis is None:
            print('WARNING: a custom axis was selected, however',
                  '"custom_axis" was not provided. Using default (Z).')
            custom_axis = [0.0, 0.0, 1.0]
    # Convert center point name into number
    if center_pt.lower() == 'origin':
        center_pt_num = 0
    elif center_pt.lower() == 'barycenter':
        center_pt_num = 1
    else:  # custom cente point
        center_pt_num = 2
        if custom_center_pt is None:
            print('WARNING: a custom center point was selected, however',
                  '"custom_center_pt" was not provided. Using default',
                  '(origin).')
            custom_center_pt = [0.0, 0.0, 0.0]
    filter_xml = ''.join([
        '  <filter name="Transform: Rotate">\n',
        '    <Param name="rotAxis" ',
        'value="%d" ' % axis_num,
        'description="Rotation on:" ',
        'enum_val0="X axis" ',
        'enum_val1="Y axis" ',
        'enum_val2="Z axis" ',
        'enum_val3="custom axis" ',
        'enum_cardinality="4" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="rotCenter" ',
        'value="%d" ' % center_pt_num,
        'description="Center of rotation:" ',
        'enum_val0="origin" ',
        'enum_val1="barycenter" ',
        'enum_val2="custom point" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="angle" ',
        'value="%s" ' % angle,
        'description="Rotation Angle" ',
        'min="-360" ',
        'max="360" ',
        'type="RichDynamicFloat" ',
        '/>\n',
        '    <Param name="snapFlag" ',
        'value="false" ',
        'description="Snap angle" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="customAxis" ',
        'x="%s" ' % custom_axis[0],
        'y="%s" ' % custom_axis[1],
        'z="%s" ' % custom_axis[2],
        'description="Custom axis" ',
        'type="RichPoint3f" ',
        '/>\n',
        '    <Param name="customCenter" ',
        'x="%s" ' % custom_center_pt[0],
        'y="%s" ' % custom_center_pt[1],
        'z="%s" ' % custom_center_pt[2],
        'description="Custom center" ',
        'type="RichPoint3f" ',
        '/>\n',
        '    <Param name="snapAngle" ',
        'value="30" ',
        'description="Snapping Value" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="Freeze" ',
        'value="%s" ' % str(freeze).lower(),
        'description="Freeze Matrix." ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="ToAll" ',
        'value="%s" ' % str(all_layers).lower(),
        'description="Apply to all layers." ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def rotate(script, axis='z', angle=0.0):
    """An alternative rotate implementation that uses a geometric function.
    This is more accurate than the built-in version."""
    angle = math.radians(angle)
    if axis.lower() == 'x':
        vert_function(script,
                 x_func='x',
                 y_func='y*cos({angle})-z*sin({angle})'.format(angle=angle),
                 z_func='y*sin({angle})+z*cos({angle})'.format(angle=angle))
    elif axis.lower() == 'y':
        vert_function(script,
                 x_func='z*sin({angle})+x*cos({angle})'.format(angle=angle),
                 y_func='y',
                 z_func='z*cos({angle})-x*sin({angle})'.format(angle=angle))
    elif axis.lower() == 'z':
        vert_function(script,
                 x_func='x*cos({angle})-y*sin({angle})'.format(angle=angle),
                 y_func='x*sin({angle})+y*cos({angle})'.format(angle=angle),
                 z_func='z')
    else:
        print('Axis name is not valid; exiting ...')
        sys.exit(1)
    return None

def scale2(script, value=1.0, uniform=True, center_pt='origin',
           custom_center_pt=None, unit=False, freeze=True, all_layers=False):
    """

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        value (float): Scaling along the X axis.
        uniform (bool): If selected an uniform scaling (the same for all the',
        ' three axis) is applied (the X axis value is used).
        center_pt (str): Choose a method.
        custom_center_pt (point): This scaling center is used only if the',
        ' _custom point_ option is chosen.
        unit (bool): If selected, the object is scaled to a box whose',
        ' sides are at most 1 unit length.
        freeze (bool): The transformation is explicitly applied and the',
        ' vertex coords are actually changed.
        all_layers (bool): The transformation is explicitly applied to all the',
        ' mesh and raster layers in the project.
    """
    """# Convert value to list if it isn't already
    if not isinstance(value, list):
        value = list(value)
    # If a single value was supplied use it for all 3 axes
    if len(value) == 1:
        value = [value[0], value[0], value[0]]"""
    value = util.make_list(value, 3)
    # Convert center point name into number
    if center_pt.lower() == 'origin':
        center_pt_num = 0
    elif center_pt.lower() == 'barycenter':
        center_pt_num = 1
    else:  # custom axis
        center_pt_num = 2
        if custom_center_pt is None:
            print('WARNING: a custom center point was selected, however',
                  '"custom_center_pt" was not provided. Using default',
                  '(origin).')
            custom_center_pt = [0.0, 0.0, 0.0]
    filter_xml = ''.join([
        '  <filter name="Transform: Scale">\n',
        '    <Param name="axisX" ',
        'value="%s" ' % value[0],
        'description="X Axis" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="axisY" ',
        'value="%s" ' % value[1],
        'description="Y Axis" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="axisZ" ',
        'value="%s" ' % value[2],
        'description="Z Axis" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="uniformFlag" ',
        'value="%s" ' % str(uniform).lower(),
        'description="Uniform Scaling" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="scaleCenter" ',
        'value="%d" ' % center_pt_num,
        'description="Center of scaling:" ',
        'enum_val0="origin" ',
        'enum_val1="barycenter" ',
        'enum_val2="custom point" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="customCenter" ',
        'x="%s" ' % custom_center_pt[0],
        'y="%s" ' % custom_center_pt[1],
        'z="%s" ' % custom_center_pt[2],
        'description="Custom center" ',
        'type="RichPoint3f" ',
        '/>\n',
        '    <Param name="unitFlag" ',
        'value="%s" ' % str(unit).lower(),
        'description="Scale to Unit bbox" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="Freeze" ',
        'value="%s" ' % str(freeze).lower(),
        'description="Freeze Matrix." ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="ToAll" ',
        'value="%s" ' % str(all_layers).lower(),
        'description="Apply to all layers." ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def scale(script, value=1.0):
    """An alternative scale implementation that uses a geometric function.
    This is more accurate than the built-in version."""
    """# Convert value to list if it isn't already
    if not isinstance(value, list):
        value = list(value)
    # If a single value was supplied use it for all 3 axes
    if len(value) == 1:
        value = [value[0], value[0], value[0]]"""
    value = util.make_list(value, 3)
    vert_function(script,
             x_func='x*(%s)' % value[0],
             y_func='y*(%s)' % value[1],
             z_func='z*(%s)' % value[2])
    return None

def freeze_matrix(script, all_layers=False):
    """ Freeze the current transformation matrix into the coordinates of the
        vertices of the mesh (and set this matrix to the identity).

    In other words it applies in a definitive way the current matrix to the
    vertex coordinates.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        all_layers (bool): If selected the filter will be applied to all
            visible mesh layers.

    """
    filter_xml = ''.join([
        '  <filter name="Freeze Current Matrix">\n',
        '    <Param name="allLayers" ',
        'value="%s" ' % str(all_layers).lower(),
        'description="Apply to all visible Layers" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def function(script, x_func='x', y_func='y', z_func='z'):
    """Geometric function using muparser lib to generate new Coordinates

    You can change x, y, z for every vertex according to the function specified.

    See help(mlx.muparser_ref) for muparser reference documentation.
    It's possible to use the following per-vertex variables in the expression:

    Variables (per vertex):
        x, y, z (coordinates)
        nx, ny, nz (normal)
        r, g, b, a (color)
        q (quality)
        rad (radius)
        vi (vertex index)
        vtu, vtv (texture coordinates)
        ti (texture index)
        vsel (is the vertex selected? 1 yes, 0 no)
        and all custom vertex attributes already defined by user.

    Args:
        x_func (str): function to generate new coordinates for x
        y_func (str): function to generate new coordinates for y
        z_func (str): function to generate new coordinates for z

    Layer stack:
        No impacts

    MeshLab versions:
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Geometric Function">\n',
        '    <Param name="x" ',
        'value="{}" '.format(str(x_func).replace('&', '&').replace('<', '<')),
        'description="func x = " ',
        'type="RichString" ',
        '/>\n',
        '    <Param name="y" ',
        'value="{}" '.format(str(y_func).replace('&', '&').replace('<', '<')),
        'description="func y = " ',
        'type="RichString" ',
        '/>\n',
        '    <Param name="z" ',
        'value="{}" '.format(str(z_func).replace('&', '&').replace('<', '<')),
        'description="func z = " ',
        'type="RichString" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def vert_function(script, x_func='x', y_func='y', z_func='z', selected=False):
    """Geometric function using muparser lib to generate new Coordinates

    You can change x, y, z for every vertex according to the function specified.

    See help(mlx.muparser_ref) for muparser reference documentation.
    It's possible to use the following per-vertex variables in the expression:

    Variables (per vertex):
        x, y, z (coordinates)
        nx, ny, nz (normal)
        r, g, b, a (color)
        q (quality)
        rad (radius)
        vi (vertex index)
        vtu, vtv (texture coordinates)
        ti (texture index)
        vsel (is the vertex selected? 1 yes, 0 no)
        and all custom vertex attributes already defined by user.

    Args:
        x_func (str): function to generate new coordinates for x
        y_func (str): function to generate new coordinates for y
        z_func (str): function to generate new coordinates for z
        selected (bool): if True, only affects selected vertices (ML ver 2016.12 & up)

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if script.ml_version == '1.3.4BETA':
        filter_xml = ''.join([
            '  <filter name="Geometric Function">\n',
            '    <Param name="x" ',
            'value="{}" '.format(str(x_func).replace('&', '&').replace('<', '<')),
            'description="func x = " ',
            'type="RichString" ',
            '/>\n',
            '    <Param name="y" ',
            'value="{}" '.format(str(y_func).replace('&', '&').replace('<', '<')),
            'description="func y = " ',
            'type="RichString" ',
            '/>\n',
            '    <Param name="z" ',
            'value="{}" '.format(str(z_func).replace('&', '&').replace('<', '<')),
            'description="func z = " ',
            'type="RichString" ',
            '/>\n',
            '  </filter>\n'])
    else:
        filter_xml = ''.join([
            '  <filter name="Per Vertex Geometric Function">\n',
            '    <Param name="x" ',
            'value="{}" '.format(str(x_func).replace('&', '&').replace('<', '<')),
            'description="func x = " ',
            'type="RichString" ',
            '/>\n',
            '    <Param name="y" ',
            'value="{}" '.format(str(y_func).replace('&', '&').replace('<', '<')),
            'description="func y = " ',
            'type="RichString" ',
            '/>\n',
            '    <Param name="z" ',
            'value="{}" '.format(str(z_func).replace('&', '&').replace('<', '<')),
            'description="func z = " ',
            'type="RichString" ',
            '/>\n',
            '    <Param name="onselected" ',
            'value="%s" ' % str(selected).lower(),
            'description="only on selection" ',
            'type="RichBool" ',
            '/>\n',
            '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def function_cyl_co(script, r_func='r', theta_func='theta', z_func='z'):
    """Geometric function using cylindrical coordinates.

    Define functions in Z up cylindrical coordinates, with radius 'r',
    angle 'theta', and height 'z'

    See "function" docs for additional usage info and accepted parameters.

    Args:
        r_func (str): function to generate new coordinates for radius
        theta_func (str): function to generate new coordinates for angle.
            0 degrees is on the +X axis.
        z_func (str): function to generate new coordinates for height

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """

    r = 'sqrt(x^2+y^2)'
    # In newer MeshLab atan2 is builtin to muparser
    if isinstance(script, FilterScript) and script.ml_version >= '2016.12':
        theta = 'atan2(y, x)'
    else:
        theta = mp_func.mp_atan2('y', 'x')

    # Use re matching to match whole word; this prevents matching
    # 'sqrt' and 'rint' when replacing 'r'
    r_func = re.sub(r"\br\b", r, r_func).replace('theta', theta)
    theta_func = re.sub(r"\br\b", r, theta_func).replace('theta', theta)
    z_func = re.sub(r"\br\b", r, z_func).replace('theta', theta)

    x_func = '(r)*cos(theta)'.replace('r', r_func).replace('theta', theta_func)
    y_func = '(r)*sin(theta)'.replace('r', r_func).replace('theta', theta_func)

    vert_function(script, x_func, y_func, z_func)
    return None

def radial_flare2(script, flare_radius=None, start_radius=None, end_radius=None,
                  end_height=None):
    """
    flare_radius must be >= end_height (height)
    end_radius max = flare_radius + r

    end_radius (num): radius of mesh at end of flare

    +15 r= 8.8205
    -15 r= 1.1795

    z=10, 5 +/-15 - +/-15*0.74535599249992989880305788957709
    """
    # TODO: set radius limit, make it so flare continues to expand linearly after radius limit
    # if(r<=radius_limit, flare, factor*z+constant
    # TODO: add option to specify radius at height instead of radius
    if (end_radius is not None) and (end_height is not None):
        # this is only correct if r is constant
        # start_radius here is really r at end_height
        #flare_radius = '-((start_radius-end_radius)^2 + end_height^2)/(2*(start_radius-end_radius))'
        if (end_radius - start_radius) < end_height:
            flare_radius = -((start_radius-end_radius)**2 + end_height**2)/(2*(start_radius-end_radius))
            #print('flare_radius = %s' % flare_radius)
        else:
            print('Error, end_radius is too large for end_height; angle is > 90d')

    r_func = 'if(z>0, (r) + (flare_radius) - (flare_radius)*sqrt(1-z^2/(flare_radius)^2), (r))'.replace('flare_radius', str(flare_radius))
    #r_func = 'if(z>0, (r) + (flare_radius) - (flare_radius)*sqrt(1-z^2/(flare_radius)^2), (r))'.replace('flare_radius', str(flare_radius)).replace('start_radius', str(start_radius)).replace('end_radius', str(end_radius)).replace('end_height', str(end_height))
    function_cyl_co(script, r_func)
    return None

def radial_flare(script, flare_radius=None, start_radius=None, end_radius=None,
                 end_height=None):
    """
    flare_radius must be >= z2 (height)
    r2 max = flare_radius + r

    r2 (num): radius of mesh at end of flare

    +15 r= 8.8205
    -15 r= 1.1795

    z=10, 5 +/-15 - +/-15*0.74535599249992989880305788957709
    """
    # TODO: set radius limit, make it so flare continues to expand linearly after radius limit
    # if(r<=radius_limit, flare, factor*z+constant
    # TODO: add option to specify radius at height instead of radius
    effective_radius = '(flare_radius) + (start_radius) - (r)'

    r_func = 'if(z>0, (flare_radius) + (start_radius) - (effective_radius)*cos(z/(flare_radius)), (r))'
    z_func = 'if(z>0, (effective_radius)*sin(z/(flare_radius)), z)'

    r_func = r_func.replace('effective_radius', str(effective_radius)).replace('start_radius', str(start_radius)).replace('flare_radius', str(flare_radius))
    z_func = z_func.replace('effective_radius', str(effective_radius)).replace('start_radius', str(start_radius)).replace('flare_radius', str(flare_radius))

    function_cyl_co(script=script, r_func=r_func, z_func=z_func)
    return None

def curl_rim(script, curl_radius=None, start_radius=None, end_radius=None,
             end_height=None):
    """
    flare_radius must be >= z2 (height)
    r2 max = flare_radius + r

    r2 (num): radius of mesh at end of flare

    +15 r= 8.8205
    -15 r= 1.1795

    z=10, 5 +/-15 - +/-15*0.74535599249992989880305788957709
    """
    # TODO: set radius limit, make it so flare continues to expand linearly after radius limit
    # if(r<=radius_limit, flare, factor*z+constant
    # TODO: add option to specify radius at height instead of radius
    effective_radius = '(curl_radius) - z'

    r_func = 'if((r)>(start_radius), (start_radius) + (effective_radius)*sin(((r)-(start_radius))/(curl_radius)), (r))'
    z_func = 'if((r)>(start_radius), (curl_radius) - (effective_radius)*cos(((r)-(start_radius))/(curl_radius)), z)'

    r_func = r_func.replace('effective_radius', str(effective_radius)).replace('start_radius', str(start_radius)).replace('curl_radius', str(curl_radius))
    z_func = z_func.replace('effective_radius', str(effective_radius)).replace('start_radius', str(start_radius)).replace('curl_radius', str(curl_radius))

    function_cyl_co(script=script, r_func=r_func, z_func=z_func)
    return None

def wrap2cylinder(script, radius=1, pitch=0, taper=0, pitch_func=None,
                  taper_func=None):
    """Deform mesh around cylinder of radius and axis z

    y = 0 will be on the surface of radius "radius"
    pitch != 0 will create a helix, with distance "pitch" traveled in z for each rotation
    taper = change in r over z. E.g. a value of 0.5 will shrink r by 0.5 for every z length of 1

    """
    """vert_function(s=s, x='(%s+y-taper)*sin(x/(%s+y))' % (radius, radius),
                     y='(%s+y)*cos(x/(%s+y))' % (radius, radius),
                     z='z-%s*x/(2*%s*(%s+y))' % (pitch, pi, radius))"""
    if pitch_func is None:
        pitch_func = '-(pitch)*x/(2*pi*(radius))'
    pitch_func = pitch_func.replace(
        'pitch', str(pitch)).replace(
            'pi', str(math.pi)).replace(
                'radius', str(radius))
    if taper_func is None:
        taper_func = '-(taper)*(pitch_func)'
    taper_func = taper_func.replace(
        'taper', str(taper)).replace(
            'pitch_func', str(pitch_func)).replace(
                'pi', str(math.pi))

    x_func = '(y+(radius)+(taper_func))*sin(x/(radius))'.replace(
        'radius', str(radius)).replace('taper_func', str(taper_func))
    y_func = '(y+(radius)+(taper_func))*cos(x/(radius))'.replace(
        'radius', str(radius)).replace('taper_func', str(taper_func))
    z_func = 'z+(pitch_func)'.replace('pitch_func', str(pitch_func))

    vert_function(script, x_func, y_func, z_func)
    return None

def wrap2sphere(script, radius=1):
    """
    """
    #r = 'sqrt(x^2+y^2)'

    r_func = '(z+(radius))*sin((r)/(radius))'.replace('radius', str(radius))
    z_func = '(z+(radius))*cos((r)/(radius))'.replace('radius', str(radius))

    #z_func='if(r<radius, sqrt(radius-r^2)-radius+z'.replace('radius', str(radius))
    #z_func='sqrt(radius-x^2-y^2)-radius+z'.replace('radius', str(radius))
    # z_func='sqrt(%s-x^2-y^2)-%s+z' % (sphere_radius**2, sphere_radius))
    function_cyl_co(script=script, r_func=r_func, z_func=z_func)
    return None

def emboss_sphere(script, radius=1, radius_limit=None, angle=None):
    """

    angle overrides radius_limit
    """
    if angle is not None:
        radius_limit = radius * math.sin(math.radians(angle / 2))
    if (radius_limit is None) or (radius_limit > radius):
        radius_limit = radius
    r = 'sqrt(x^2+y^2)'

    #r_func = '(z+radius)*sin(r/radius)'.replace('radius', str(radius))
    #z_func = '(z+radius)*cos(r/radius)'.replace('radius', str(radius))

    z_func = 'if((r)<=(radius_limit), sqrt((radius)^2-(r)^2)+z-sqrt((radius)^2-(radius_limit)^2), z)'
    #z_func='if((r<=radius), if((r<=radius_limit), sqrt(radius^2-r^2)+z-sqrt(radius^2-radius_limit^2), z), z)'
    #z_func='if((r<=radius) and (r<=radius_limit), sqrt(radius^2-r^2)+z-sqrt(radius^2-radius_limit^2), z)'
    z_func = re.sub(r"\br\b", r, z_func).replace(
        'radius_limit', str(radius_limit)).replace('radius', str(radius))

    #z_func='sqrt(radius-x^2-y^2)-radius+z'.replace('radius', str(radius))
    # z_func='sqrt(%s-x^2-y^2)-%s+z' % (sphere_radius**2, sphere_radius))
    vert_function(script=script, z_func=z_func)
    return None

def bend(script, radius=1, pitch=0, taper=0, angle=0, straght_start=True,
         straght_end=False, radius_limit=None, outside_limit_end=True):
    """Bends mesh around cylinder of radius radius and axis z to a certain angle

    straight_ends: Only apply twist (pitch) over the area that is bent

    outside_limit_end (bool): should values outside of the bend radius_limit be considered part
        of the end (True) or the start (False)?
    """
    if radius_limit is None:
        radius_limit = 2 * radius
    # TODO: add limit so bend only applies over y<2*radius; add option to set
    # larger limit
    angle = math.radians(angle)
    segment = radius * angle
    """vert_function(s=s, x='if(x<%s and x>-%s, (%s+y)*sin(x/%s), (%s+y)*sin(%s/%s)+(x-%s)*cos(%s/%s))'
                        % (segment, segment,  radius, radius,  radius, segment, radius, segment, segment, radius),
                     y='if(x<%s*%s/2 and x>-%s*%s/2, (%s+y)*cos(x/%s), (%s+y)*cos(%s)-(x-%s*%s)*sin(%s))'
                        % (radius, angle, radius, angle, radius, radius, radius, angle/2, radius, angle/2, angle/2),"""
    pitch_func = '-(pitch)*x/(2*pi*(radius))'.replace(
        'pitch', str(pitch)).replace(
            'pi', str(math.pi)).replace(
                'radius', str(radius))
    taper_func = '(taper)*(pitch_func)'.replace(
        'taper', str(taper)).replace(
            'pitch_func', str(pitch_func)).replace(
                'pi', str(math.pi))
    # y<radius_limit

    if outside_limit_end:
        x_func = 'if(x<(segment) and y<(radius_limit), if(x>0, (y+(radius)+(taper_func))*sin(x/(radius)), x), (y+(radius)+(taper_func))*sin(angle)+(x-(segment))*cos(angle))'
    else:
        x_func = 'if(x<(segment), if(x>0 and y<(radius_limit), (y+(radius)+(taper_func))*sin(x/(radius)), x), if(y<(radius_limit), (y+(radius)+(taper_func))*sin(angle)+(x-(segment))*cos(angle), x))'

    x_func = x_func.replace(
        # x_func = 'if(x<segment, if(x>0, (y+radius)*sin(x/radius), x),
        # (y+radius)*sin(angle)-segment)'.replace(
        'segment', str(segment)).replace(
            'radius_limit', str(radius_limit)).replace(
                'radius', str(radius)).replace(
                    'taper_func', str(taper_func)).replace(
                        'angle', str(angle))

    if outside_limit_end:
        y_func = 'if(x<(segment) and y<(radius_limit), if(x>0, (y+(radius)+(taper_func))*cos(x/(radius))-(radius), y), (y+(radius)+(taper_func))*cos(angle)-(x-(segment))*sin(angle)-(radius))'
    else:
        y_func = 'if(x<(segment), if(x>0 and y<(radius_limit), (y+(radius)+(taper_func))*cos(x/(radius))-(radius), y), if(y<(radius_limit), (y+(radius)+(taper_func))*cos(angle)-(x-(segment))*sin(angle)-(radius), y))'

    y_func = y_func.replace(
        'segment', str(segment)).replace(
            'radius_limit', str(radius_limit)).replace(
                'radius', str(radius)).replace(
                    'taper_func', str(taper_func)).replace(
                        'angle', str(angle))

    if straght_start:
        start = 'z'
    else:
        start = 'z+(pitch_func)'
    if straght_end:
        end = 'z-(pitch)*(angle)/(2*pi)'
    else:
        end = 'z+(pitch_func)'

    if outside_limit_end:
        z_func = 'if(x<(segment) and y<(radius_limit), if(x>0, z+(pitch_func), (start)), (end))'
    else:
        z_func = 'if(x<(segment), if(x>0 and y<(radius_limit), z+(pitch_func), (start)), if(y<(radius_limit), (end), z))'
    z_func = z_func.replace(
        'start', str(start)).replace(
            'end', str(end)).replace(
                'segment', str(segment)).replace(
                    'radius_limit', str(radius_limit)).replace(
                        'radius', str(radius)).replace(
                            'angle', str(angle)).replace(
                                'pitch_func', str(pitch_func)).replace(
                                    'pitch', str(pitch)).replace(
                                        'pi', str(math.pi))

    """
    if straight_ends:
        z_func = 'if(x<segment, if(x>0, z+(pitch_func), z), z-pitch*angle/(2*pi))'.replace(
            'segment', str(segment)).replace(
            'radius', str(radius)).replace(
            'angle', str(angle)).replace(
            'pitch_func', str(pitch_func)).replace(
            'pitch', str(pitch)).replace(
            'pi', str(math.pi))
    else:
        #z_func = 'if(x<segment, z+(pitch_func), z-(taper)*(pitch)*(x)/(2*pi*(radius)))'.replace(
        #z_func = 'if(x<segment, z+(pitch_func), z+(pitch_func))'.replace(
        #bestz_func = 'if(x<segment, z+(pitch_func), z+(pitch_func)+(-(taper)*(pitch)*(x-segment)/(2*pi*(radius))))'.replace(
        #z_func = 'if(x<segment, z+(pitch_func), z+(pitch_func)+(-(taper)*(pitch)*x/(2*pi*(radius))))'.replace(
        #z_func = 'if(x<segment, z+(pitch_func), z+(pitch_func)+((taper)*pitch*angle/(2*pi)))'.replace(
        z_func = 'z+(pitch_func)'.replace(
            'radius', str(radius)).replace(
            'segment', str(segment)).replace(
            'angle', str(angle)).replace(
            'taper', str(taper)).replace(
            'pitch_func', str(pitch_func)).replace(
            'pitch', str(pitch)).replace(
            'pi', str(math.pi))
    """
    """
    x_func = 'if(x<%s, if(x>-%s, (%s+y)*sin(x/%s), (%s+y)*sin(-%s)+(x+%s)*cos(-%s)), (%s+y)*sin(%s)+(x-%s)*cos(%s))' % (
        segment, segment, radius, radius, radius, angle, segment, angle, radius, angle, segment, angle)
    y_func = 'if(x<%s, if(x>-%s, (%s+y)*cos(x/%s), (%s+y)*cos(-%s)-(x+%s)*sin(-%s)), (%s+y)*cos(%s)-(x-%s)*sin(%s))' % (
        segment, segment, radius, radius, radius, angle, segment, angle, radius, angle, segment, angle)
    if straight_ends:
        z_func = 'if(x<%s, if(x>-%s, z-%s*x/(2*%s*%s), z+%s*%s/(2*%s)), z-%s*%s/(2*%s))' % (
            segment, segment, pitch, math.pi, radius, pitch, angle, math.pi, pitch, angle, math.pi)
    else:
        z_func = 'z-%s*x/(2*%s*%s)' % (pitch, math.pi, radius)
    """
    vert_function(script, x_func=x_func, y_func=y_func, z_func=z_func)
    return None

def deform2curve(script, curve=mp_func.torus_knot('t'), step=0.001):
    """ Deform a mesh along a parametric curve function

    Provide a parametric curve function with z as the parameter. This will
    deform the xy cross section of the mesh along the curve as z increases.

    Source: http://blackpawn.com/texts/pqtorus/

    Methodology:
    T  = P' - P
    N1 = P' + P
    B  = T x N1
    N  = B x T

    newPoint = point.x*N + point.y*B

    """
    curve_step = []
    for idx, val in enumerate(curve):
        curve[idx] = val.replace('t', 'z')
        curve_step.append(val.replace('t', 'z+{}'.format(step)))

    tangent = mp_func.v_subtract(curve_step, curve)
    normal1 = mp_func.v_add(curve_step, curve)
    bee = mp_func.v_cross(tangent, normal1)
    normal = mp_func.v_cross(bee, tangent)
    bee = mp_func.v_normalize(bee)
    normal = mp_func.v_normalize(normal)

    new_point = mp_func.v_add(mp_func.v_multiply('x', normal), mp_func.v_multiply('y', bee))
    function = mp_func.v_add(curve, new_point)

    vert_function(script, x_func=function[0], y_func=function[1], z_func=function[2])
    return function

# TODO: add function to round mesh to desired tolerance
# use muparser rint (round to nearest integer)


""" MeshLabXML functions to transfer attributes """

from . import util

def tex2vc(script):
    """Transfer texture colors to vertex colors

    BUG: this does not work correctly if the file has multiple textures; it
    only uses one texture and remaps all of the UVs to that
    https://github.com/cnr-isti-vclab/meshlab/issues/124
    should be fixed in post 2016.12 release

    """
    filter_xml = '  <filter name="Transfer Color: Texture to Vertex"/>\n'
    util.write_filter(script, filter_xml)
    return None

def vc2tex(script, tex_name='TEMP3D_texture.png', tex_width=1024,
           tex_height=1024, overwrite_tex=False, assign_tex=False,
           fill_tex=True):
    """Transfer vertex colors to texture colors

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        tex_name (str): The texture file to be created
        tex_width (int): The texture width
        tex_height (int): The texture height
        overwrite_tex (bool): If current mesh has a texture will be overwritten (with provided texture dimension)
        assign_tex (bool): Assign the newly created texture
        fill_tex (bool): If enabled the unmapped texture space is colored using a pull push filling algorithm, if false is set to black
    """
    filter_xml = ''.join([
        '  <filter name="Vertex Color to Texture">\n',
        '    <Param name="textName" ',
        'value="%s" ' % tex_name,
        'description="Texture file" ',
        'type="RichString" ',
        '/>\n',
        '    <Param name="textW" ',
        'value="%d" ' % tex_width,
        'description="Texture width (px)" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="textH" ',
        'value="%d" ' % tex_height,
        'description="Texture height (px)" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="overwrite" ',
        'value="%s" ' % str(overwrite_tex).lower(),
        'description="Overwrite texture" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="assign" ',
        'value="%s" ' % str(assign_tex).lower(),
        'description="Assign Texture" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="pullpush" ',
        'value="%s" ' % str(fill_tex).lower(),
        'description="Fill texture" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def fc2vc(script):
    """Transfer face colors to vertex colors

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
    """
    filter_xml = '  <filter name="Transfer Color: Face to Vertex"/>\n'
    util.write_filter(script, filter_xml)
    return None

def vc2fc(script):
    """Transfer vertex colors to face colors

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
    """
    filter_xml = '  <filter name="Transfer Color: Vertex to Face"/>\n'
    util.write_filter(script, filter_xml)
    return None

def mesh2fc(script, all_visible_layers=False):
    """Transfer mesh colors to face colors

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        all_visible_layers (bool): If true the color mapping is applied to all the meshes
    """
    filter_xml = ''.join([
        '  <filter name="Transfer Color: Mesh to Face">\n',
        '    <Param name="allVisibleMesh" ',
        'value="%s" ' % str(all_visible_layers).lower(),
        'description="Apply to all Meshes" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def vert_attr_2_meshes(script, source_mesh=0, target_mesh=1,
                             geometry=False, normal=False, color=True,
                             quality=False, selection=False,
                             quality_distance=False, max_distance=0.5):
    """Vertex Attribute Transfer (between 2 meshes)

    Transfer the chosen per-vertex attributes from one mesh to another. Useful to transfer attributes to different representations of the same object. For each vertex of the target mesh the closest point (not vertex!) on the source mesh is computed, and the requested interpolated attributes from that source point are copied into the target vertex.

    The algorithm assumes that the two meshes are reasonably similar and aligned.

    UpperBound: absolute value (not percentage)

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        source_mesh (int): The mesh that contains the source data that we want to transfer
        target_mesh (int): The mesh whose vertexes will receive the data from the source
        geometry (bool): If enabled, the position of each vertex of the target mesh will be snapped onto the corresponding closest point on the source mesh
        normal (bool): If enabled, the normal of each vertex of the target mesh will get the (interpolated) normal of the corresponding closest point on the source mesh
        color (bool): If enabled, the color of each vertex of the target mesh will become the color of the corresponding closest point on the source mesh
        quality (bool): If enabled, the quality of each vertex of the target mesh will become the quality of the corresponding closest point on the source mesh
        selection (bool): If enabled, each vertex of the target mesh will be selected if the corresponding closest point on the source mesh falls in a selected face
        quality_distance (bool): If enabled, we store the distance of the transferred value as in the vertex quality
        max_distance (float): Sample points for which we do not find anything within this distance are rejected and not considered for recovering attributes

    """
    filter_xml = ''.join([
        '  <filter name="Vertex Attribute Transfer">\n',
        '    <Param name="SourceMesh" ',
        'value="{:d}" '.format(source_mesh),
        'description="Source Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="TargetMesh" ',
        'value="{:d}" '.format(target_mesh),
        'description="Target Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="GeomTransfer" ',
        'value="{}" '.format(str(geometry).lower()),
        'description="Transfer Geometry" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="NormalTransfer" ',
        'value="{}" '.format(str(normal).lower()),
        'description="Transfer Normal" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="ColorTransfer" ',
        'value="{}" '.format(str(color).lower()),
        'description="Transfer Color" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="QualityTransfer" ',
        'value="{}" '.format(str(quality).lower()),
        'description="Transfer quality" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="SelectionTransfer" ',
        'value="{}" '.format(str(selection).lower()),
        'description="Transfer Selection" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="QualityDistance" ',
        'value="{}" '.format(str(quality_distance).lower()),
        'description="Store dist. as quality" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="UpperBound" ',
        'value="{}" '.format(max_distance),
        'description="Max Dist Search" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def vert_attr2tex_2_meshes(script, source_mesh=0, target_mesh=1, attribute=0,
                           max_distance=0.5, tex_name='TEMP3D_texture.png',
                           tex_width=1024, tex_height=1024,
                           overwrite_tex=True, assign_tex=False,
                           fill_tex=True):
    """Transfer Vertex Attributes to Texture (between 2 meshes)

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        source_mesh (int): The mesh that contains the source data that we want to transfer
        target_mesh (int): The mesh whose texture will be filled according to source mesh data
        attribute (int): Choose what attribute has to be transferred onto the target texture. You can choose between Per vertex attributes (color, normal, quality) or to transfer color information from source mesh texture
        max_distance (float): Sample points for which we do not find anything within this distance are rejected and not considered for recovering data
        tex_name (str): The texture file to be created
        tex_width (int): The texture width
        tex_height (int): The texture height
        overwrite_tex (bool): If target mesh has a texture will be overwritten (with provided texture dimension)
        assign_tex (bool): Assign the newly created texture to target mesh
        fill_tex (bool): If enabled the unmapped texture space is colored using a pull push filling algorithm, if false is set to black

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if script.ml_version == '1.3.4BETA':
        filter_name = 'Transfer Vertex Attributes to Texture (between 2 meshes)'
    else:
        filter_name = 'Transfer: Vertex Attributes to Texture (1 or 2 meshes)'
    filter_xml = ''.join([
        '  <filter name="{}">\n'.format(filter_name),
        '    <Param name="sourceMesh" ',
        'value="%d" ' % source_mesh,
        'description="Source Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="targetMesh" ',
        'value="%d" ' % target_mesh,
        'description="Target Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="AttributeEnum" ',
        'value="%d" ' % attribute,
        'description="Color Data Source" ',
        'enum_val0="Vertex Color" ',
        'enum_val1="Vertex Normal" ',
        'enum_val2="Vertex Quality" ',
        'enum_val3="Texture Color" ',
        'enum_cardinality="4" ',
        'type="RichEnum" ',
        '/>\n',
        '    <Param name="upperBound" ',
        'value="%s" ' % max_distance,
        'description="Max Dist Search" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="textName" ',
        'value="%s" ' % tex_name,
        'description="Texture file" ',
        'type="RichString" ',
        '/>\n',
        '    <Param name="textW" ',
        'value="%d" ' % tex_width,
        'description="Texture width (px)" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="textH" ',
        'value="%d" ' % tex_height,
        'description="Texture height (px)" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="overwrite" ',
        'value="%s" ' % str(overwrite_tex).lower(),
        'description="Overwrite Target Mesh Texture" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="assign" ',
        'value="%s" ' % str(assign_tex).lower(),
        'description="Assign Texture" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="pullpush" ',
        'value="%s" ' % str(fill_tex).lower(),
        'description="Fill texture" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def tex2vc_2_meshes(script, source_mesh=0, target_mesh=1, max_distance=0.5):
    """Transfer texture colors to vertex colors (between 2 meshes)

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        source_mesh (int): The mesh with associated texture that we want to sample from
        target_mesh (int): The mesh whose vertex color will be filled according to source mesh texture
        max_distance (float): Sample points for which we do not find anything within this distance are rejected and not considered for recovering color

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if script.ml_version == '1.3.4BETA':
        filter_name = 'Texture to Vertex Color (between 2 meshes)'
    else:
        filter_name = 'Transfer: Texture to Vertex Color (1 or 2 meshes)'
    filter_xml = ''.join([
        '  <filter name="{}">\n'.format(filter_name),
        '    <Param name="sourceMesh" ',
        'value="%d" ' % source_mesh,
        'description="Source Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="targetMesh" ',
        'value="%d" ' % target_mesh,
        'description="Target Mesh" ',
        'type="RichMesh" ',
        '/>\n',
        '    <Param name="upperBound" ',
        'value="%s" ' % max_distance,
        'description="Max Dist Search" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None


""" MeshLabXML remeshing functions """

#import meshlabxml as mlx
from . import FilterScript
from . import util
from . import sampling
from . import vert_color
from . import select
from . import delete
from . import smooth

def simplify(script, texture=True, faces=25000, target_perc=0.0,
             quality_thr=0.3, preserve_boundary=False, boundary_weight=1.0,
             optimal_placement=True, preserve_normal=False,
             planar_quadric=False, selected=False, extra_tex_coord_weight=1.0,
             preserve_topology=True, quality_weight=False, autoclean=True):
    """ Simplify a mesh using a Quadric based Edge Collapse Strategy, better
        than clustering but slower. Optionally tries to preserve UV
        parametrization for textured meshes.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        texture (bool):
        faces (int): The desired final number of faces
        target_perc (float): If non zero, this parameter specifies the desired
            final size of the mesh as a percentage of the initial mesh size.
        quality_thr (float): Quality threshold for penalizing bad shaped faces.
            The value is in the range [0..1]0 accept any kind of face (no
            penalties), 0.5 penalize faces with quality less than 0.5,
            proportionally to their shape.
        preserve_boundary (bool): The simplification process tries not to
            affect mesh boundaries
        boundary_weight (float): The importance of the boundary during
            simplification. Default (1.0) means that the boundary has the same
            importance of the rest. Values greater than 1.0 raise boundary
            importance and has the effect of removing less vertices on the
            border. Admitted range of values (0,+inf).
        optimal_placement (bool): Each collapsed vertex is placed in the
            position minimizing the quadric error. It can fail (creating bad
            spikes) in case of very flat areas. If disabled edges are collapsed
            onto one of the two original vertices and the final mesh is
            composed by a subset of the original vertices.
        preserve_normal (bool): Try to avoid face flipping effects and try to
            preserve the original orientation of the surface.
        planar_quadric (bool): Add additional simplification constraints that
            improves the quality of the simplification of the planar portion of
            the mesh.
        selected (bool): The simplification is applied only to the selected set
            of faces. Take care of the target number of faces!
        extra_tex_coord_weight (float): Additional weight for each extra
            Texture Coordinates for every (selected) vertex. Ignored if texture
            is False.
        preserve_topology (bool): Avoid all the collapses that should cause a
            topology change in the mesh (like closing holes, squeezing handles,
            etc). If checked the genus of the mesh should stay unchanged.
        quality_weight (bool): Use the Per-Vertex quality as a weighting factor
            for the simplification. The weight is used as a error amplification
            value, so a vertex with a high quality value will not be simplified
            and a portion of the mesh with low quality values will be
            aggressively simplified.
        autoclean (bool): After the simplification an additional set of steps
            is performed to clean the mesh (unreferenced vertices, bad faces,
            etc).

    Layer stack:
        Unchanged; current mesh is simplified in place.

    MeshLab versions:
        2016.12 (different filter name)
        1.3.4BETA
    """
    if texture:
        if isinstance(script, FilterScript) and (script.ml_version == '2016.12'):
            filter_xml = '  <filter name="Simplification: Quadric Edge Collapse Decimation (with texture)">\n'
        else:
            filter_xml = '  <filter name="Quadric Edge Collapse Decimation (with texture)">\n'
    else:
        if isinstance(script, FilterScript) and (script.ml_version == '2016.12'):
            filter_xml = '  <filter name="Simplification: Quadric Edge Collapse Decimation">\n'
        else:
            filter_xml = '  <filter name="Quadric Edge Collapse Decimation">\n'
    # Parameters common to both 'with' and 'without texture'
    filter_xml = ''.join([
        filter_xml,
        '    <Param name="TargetFaceNum" ',
        'value="{:d}" '.format(faces),
        'description="Target number of faces" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="TargetPerc" ',
        'value="{}" '.format(target_perc),
        'description="Percentage reduction (0..1)" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="QualityThr" ',
        'value="{}" '.format(quality_thr),
        'description="Quality threshold" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="PreserveBoundary" ',
        'value="{}" '.format(str(preserve_boundary).lower()),
        'description="Preserve Boundary of the mesh" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="BoundaryWeight" ',
        'value="{}" '.format(boundary_weight),
        'description="Boundary Preserving Weight" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="OptimalPlacement" ',
        'value="{}" '.format(str(optimal_placement).lower()),
        'description="Optimal position of simplified vertices" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="PreserveNormal" ',
        'value="{}" '.format(str(preserve_normal).lower()),
        'description="Preserve Normal" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="PlanarQuadric" ',
        'value="{}" '.format(str(planar_quadric).lower()),
        'description="Planar Simplification" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="Selected" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Simplify only selected faces" ',
        'type="RichBool" ',
        '/>\n'])
    if texture:  # Parameters unique to 'with texture'
        filter_xml = ''.join([
            filter_xml,
            '    <Param name="Extratcoordw" ',
            'value="{}" '.format(extra_tex_coord_weight),
            'description="Texture Weight" ',
            'type="RichFloat" ',
            '/>\n'])
    else:  # Parameters unique to 'without texture'
        filter_xml = ''.join([
            filter_xml,
            '    <Param name="PreserveTopology" ',
            'value="{}" '.format(str(preserve_topology).lower()),
            'description="Preserve Topology" ',
            'type="RichBool" ',
            '/>\n',
            '    <Param name="QualityWeight" ',
            'value="{}" '.format(str(quality_weight).lower()),
            'description="Weighted Simplification" ',
            'type="RichBool" ',
            '/>\n',
            '    <Param name="AutoClean" ',
            'value="{}" '.format(str(autoclean).lower()),
            'description="Post-simplification cleaning" ',
            'type="RichBool" ',
            '/>\n'])
    filter_xml = ''.join([filter_xml, '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def uniform_resampling(script, voxel=1.0, offset=0.0, merge_vert=True,
                       discretize=False, multisample=False, thicken=False):
    """ Create a new mesh that is a resampled version of the current one.

    The resampling is done by building a uniform volumetric representation
    where each voxel contains the signed distance from the original surface.
    The resampled surface is reconstructed using the marching cube algorithm
    over this volume.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        voxel (float): voxel (cell) size for resampling. Smaller cells give
            better precision at a higher computational cost. Remember that
            halving the cell size means that you build a volume 8 times larger.
        offset (float): offset amount of the created surface (i.e. distance of
            the created surface from the original one). If offset is zero, the
            created surface passes on the original mesh itself. Values greater
            than zero mean an external surface (offset), and lower than zero
            mean an internal surface (inset). In practice this value is the
            threshold passed to the Marching Cube algorithm to extract the
            isosurface from the distance field representation.
        merge_vert (bool): if True the mesh generated by MC will be cleaned by
            unifying vertices that are almost coincident.
        discretize (bool): if True the position of the intersected edge of the
            marching cube grid is not computed by linear interpolation, but it
            is placed in fixed middle position. As a consequence the resampled
            object will look severely aliased by a stairstep appearance. Useful
            only for simulating the output of 3D printing devices.
        multisample (bool): if True the distance field is more accurately
            compute by multisampling the volume (7 sample for each voxel). Much
            slower but less artifacts.
        thicken (bool): if True, you have to choose a non zero Offset and a
            double surface is built around the original surface, inside and
            outside. Is useful to convert thin floating surfaces into solid,
            thick meshes.

    Layer stack:
        Creates 1 new layer 'Offset mesh'
        Current layer is changed to new layer

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Uniform Mesh Resampling">\n',
        '    <Param name="CellSize" ',
        'value="{}" '.format(voxel),
        'description="Precision" ',
        'min="0" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="Offset" ',
        'value="{}" '.format(offset),
        'description="Offset" ',
        'min="-100" ',
        'max="100" ',
        'type="RichAbsPerc" ',
        '/>\n',
        '    <Param name="mergeCloseVert" ',
        'value="{}" '.format(str(merge_vert).lower()),
        'description="Clean Vertices" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="discretize" ',
        'value="{}" '.format(str(discretize).lower()),
        'description="Discretize" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="multisample" ',
        'value="{}" '.format(str(multisample).lower()),
        'description="Multisample" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="absDist" ',
        'value="{}" '.format(str(thicken).lower()),
        'description="Absolute Distance" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Offset mesh')
    return None

def hull(script, reorient_normal=True):
    """ Calculate the convex hull with Qhull library
        http://www.qhull.org/html/qconvex.htm

    The convex hull of a set of points is the boundary of the minimal convex
    set containing the given non-empty finite set of points.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        reorient_normal (bool): Re-orient all faces coherentely after hull
            operation.

    Layer stack:
        Creates 1 new layer 'Convex Hull'
        Current layer is changed to new layer

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Convex Hull">\n',
        '    <Param name="reorient" ',
        'value="{}" '.format(str(reorient_normal).lower()),
        'description="Re-orient all faces coherentely" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Convex Hull')
    return None

def surface_poisson(script, octree_depth=10, solver_divide=8,
                    samples_per_node=1.0, offset=1.0):
    """ Use the points and normals to build a surface using the Poisson
        Surface reconstruction approach.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        octree_depth (int): Set the depth of the Octree used for extracting the
            final surface. Suggested range 5..10. Higher numbers mean higher
            precision in the reconstruction but also higher processing times.
            Be patient.
        solver_divide (int): This integer argument specifies the depth at which
            a block Gauss-Seidel solver is used to solve the Laplacian
            equation. Using this parameter helps reduce the memory overhead at
            the cost of a small increase in reconstruction time. In practice,
            the authors have found that for reconstructions of depth 9 or
            higher a subdivide depth of 7 or 8 can reduce the memory usage. The
            default value is 8.
        samples_per_node (float): This floating point value specifies the
            minimum number of sample points that should fall within an octree
            node as the octree
construction is adapted to sampling density.
            For noise-free samples, small values in the range [1.0 - 5.0] can
            be used. For more noisy samples, larger values in the range
            [15.0 - 20.0] may be needed to provide a smoother, noise-reduced,
            reconstruction. The default value is 1.0.
        offset (float): This floating point value specifies a correction value
            for the isosurface threshold that is chosen. Values less than 1
            mean internal offsetting, greater than 1 mean external offsetting.
            Good values are in the range 0.5 .. 2. The default value is 1.0
            (no offsetting).

    Layer stack:
        Creates 1 new layer 'Poisson mesh'
        Current layer is changed to new layer

    MeshLab versions:
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Surface Reconstruction: Poisson">\n',
        '    <Param name="OctDepth" ',
        'value="{:d}" '.format(octree_depth),
        'description="Octree Depth" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="SolverDivide" ',
        'value="{:d}" '.format(solver_divide),
        'description="Solver Divide" ',
        'type="RichInt" ',
        '/>\n',
        '    <Param name="SamplesPerNode" ',
        'value="{}" '.format(samples_per_node),
        'description="Samples per Node" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="Offset" ',
        'value="{}" '.format(offset),
        'description="Surface offsetting" ',
        'type="RichFloat" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Poisson mesh', change_layer=True)
    return None

def surface_poisson_screened(script, visible_layer=False, depth=8,
                             full_depth=5, cg_depth=0, scale=1.1,
                             samples_per_node=1.5, point_weight=4.0,
                             iterations=8, confidence=False, pre_clean=False):
    """ This surface reconstruction algorithm creates watertight
        surfaces from oriented point sets.

    The filter uses the original code of Michael Kazhdan and Matthew Bolitho
    implementing the algorithm in the following paper:

    Michael Kazhdan, Hugues Hoppe,
    "Screened Poisson surface reconstruction"
    ACM Trans. Graphics, 32(3), 2013

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        visible_layer (bool): If True all the visible layers will be used for
            providing the points
        depth (int): This integer is the maximum depth of the tree that will
            be used for surface reconstruction. Running at depth d corresponds
            to solving on a voxel grid whose resolution is no larger than
            2^d x 2^d x 2^d. Note that since the reconstructor adapts the
            octree to the sampling density, the specified reconstruction depth
            is only an upper bound. The default value for this parameter is 8.
        full_depth (int): This integer specifies the depth beyond depth the
            octree will be adapted. At coarser depths, the octree will be
            complete, containing all 2^d x 2^d x 2^d nodes. The default value
            for this parameter is 5.
        cg_depth (int): This integer is the depth up to which a
            conjugate-gradients solver will be used to solve the linear system.
            Beyond this depth Gauss-Seidel relaxation will be used. The default
            value for this parameter is 0.
        scale (float): This floating point value specifies the ratio between
            the diameter of the cube used for reconstruction and the diameter
            of the samples' bounding cube.  The default value is 1.1.
        samples_per_node (float): This floating point value specifies the
            minimum number of sample points that should fall within an octree
            node as the octree construction is adapted to sampling density. For
            noise-free samples, small values in the range [1.0 - 5.0] can be
            used. For more noisy samples, larger values in the range
            [15.0 - 20.0] may be needed to provide a smoother, noise-reduced,
            reconstruction. The default value is 1.5.
        point_weight (float): This floating point value specifies the
            importance that interpolation of the point samples is given in the
            formulation of the screened Poisson equation. The results of the
            original (unscreened) Poisson Reconstruction can be obtained by
            setting this value to 0. The default value for this parameter is 4.
        iterations (int): This integer value specifies the number of
            Gauss-Seidel relaxations to be performed at each level of the
            hierarchy. The default value for this parameter is 8.
        confidence (bool): If True this tells the reconstructor to use the
            quality as confidence information; this is done by scaling the unit
            normals with the quality values. When the flag is not enabled, all
            normals are normalized to have unit-length prior to reconstruction.
        pre_clean (bool): If True will force a cleaning pre-pass on the data
            removing all unreferenced vertices or vertices with null normals.

    Layer stack:
        Creates 1 new layer 'Poisson mesh'
        Current layer is not changed

    MeshLab versions:
        2016.12
    """
    filter_xml = ''.join([
        '  <xmlfilter name="Screened Poisson Surface Reconstruction">\n',
        '    <xmlparam name="cgDepth" value="{:d}"/>\n'.format(cg_depth),
        '    <xmlparam name="confidence" value="{}"/>\n'.format(str(confidence).lower()),
        '    <xmlparam name="depth" value="{:d}"/>\n'.format(depth),
        '    <xmlparam name="fullDepth" value="{:d}"/>\n'.format(full_depth),
        '    <xmlparam name="iters" value="{:d}"/>\n'.format(iterations),
        '    <xmlparam name="pointWeight" value="{}"/>\n'.format(point_weight),
        '    <xmlparam name="preClean" value="{}"/>\n'.format(str(pre_clean).lower()),
        '    <xmlparam name="samplesPerNode" value="{}"/>\n'.format(samples_per_node),
        '    <xmlparam name="scale" value="{}"/>\n'.format(scale),
        '    <xmlparam name="visibleLayer" value="{}"/>\n'.format(str(visible_layer).lower()),
        '  </xmlfilter>\n'])
    util.write_filter(script, filter_xml)
    if isinstance(script, FilterScript):
        script.add_layer('Poisson mesh', change_layer=False)
    return None

def curvature_flipping(script, angle_threshold=1.0, curve_type=0,
                       selected=False):
    """ Use the points and normals to build a surface using the Poisson
        Surface reconstruction approach.

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        angle_threshold (float): To avoid excessive flipping/swapping we
            consider only couple of faces with a significant diedral angle
            (e.g. greater than the indicated threshold).
        curve_type (int): Choose a metric to compute surface curvature on vertices
            H = mean curv, K = gaussian curv, A = area per vertex
            1: Mean curvature = H
            2: Norm squared mean curvature = (H * H) / A
            3: Absolute curvature:
                if(K >= 0) return 2 * H
                else return 2 * sqrt(H ^ 2 - A * K)

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Curvature flipping optimization">\n',
        '    <Param name="selection" ',
        'value="{}" '.format(str(selected).lower()),
        'description="Update selection" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="pthreshold" ',
        'value="{}" '.format(angle_threshold),
        'description="Angle Thr (deg)" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="curvtype" ',
        'value="{:d}" '.format(curve_type),
        'description="Curvature metric" ',
        'enum_val0="mean" ',
        'enum_val1="norm squared" ',
        'enum_val2="absolute" ',
        'enum_cardinality="3" ',
        'type="RichEnum" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def voronoi(script, hole_num=50, target_layer=None, sample_layer=None, thickness=0.5, backward=True):
    """ Turn a model into a surface with Voronoi style holes in it

    References:
    http://meshlabstuff.blogspot.com/2009/03/creating-voronoi-sphere.html
    http://meshlabstuff.blogspot.com/2009/04/creating-voronoi-sphere-2.html

    Requires FilterScript object

    Args:
        script: the FilterScript object to write the filter to. Does not
            work with a script filename.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """

    if target_layer is None:
        target_layer = script.current_layer()
    if sample_layer is None:
        # Current layer is currently not changed after poisson_disk is run
        sampling.poisson_disk(script, sample_num=hole_num)
        sample_layer = script.last_layer()

    vert_color.voronoi(script, target_layer=target_layer, source_layer=sample_layer, backward=backward)
    select.vert_quality(script, min_quality=0.0, max_quality=thickness)
    if backward:
        select.invert(script)
    delete.selected(script)
    smooth.laplacian(script, iterations=3)

    return None

def solidify(script, thickness):
    pass


"""MeshLabXML selection functions"""

from . import util

def all(script, face=True, vert=True):
    """ Select all the faces of the current mesh

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        faces (bool): If True the filter will select all the faces.
        verts (bool): If True the filter will select all the vertices.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Select All">\n',
        '    <Param name="allFaces" ',
        'value="{}" '.format(str(face).lower()),
        'description="DSelect all Faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="allVerts" ',
        'value="{}" '.format(str(vert).lower()),
        'description="Select all Vertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def none(script, face=True, vert=True):
    """ Clear the current set of selected faces

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        faces (bool): If True the filter will deselect all the faces.
        verts (bool): If True the filter will deselect all the vertices.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Select None">\n',
        '    <Param name="allFaces" ',
        'value="{}" '.format(str(face).lower()),
        'description="De-select all Faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="allVerts" ',
        'value="{}" '.format(str(vert).lower()),
        'description="De-select all Vertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def invert(script, face=True, vert=True):
    """  Invert the current set of selected faces

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        faces (bool): If True the filter will invert the selected faces.
        verts (bool): If True the filter will invert the selected vertices.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Invert Selection">\n',
        '    <Param name="InvFaces" ',
        'value="{}" '.format(str(face).lower()),
        'description="Invert Faces" ',
        'type="RichBool" ',
        '/>\n',
        '    <Param name="InvVerts" ',
        'value="{}" '.format(str(vert).lower()),
        'description="Invert Vertices" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def border(script):
    """ Select vertices and faces on the boundary

    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="Select Border"/>\n'
    util.write_filter(script, filter_xml)
    return None

def grow(script, iterations=1):
    """ Grow (dilate, expand) the current set of selected faces

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): the number of times to grow the selection.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Dilate Selection"/>\n'
    for _ in range(iterations):
        util.write_filter(script, filter_xml)
    return None

def shrink(script, iterations=1):
    """ Shrink (erode, reduce) the current set of selected faces

    Args:
        script: the FilterScript object or script filename to write
            the filter to.
        iterations (int): the number of times to shrink the selection.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = '  <filter name="Erode Selection"/>\n'
    for _ in range(iterations):
        util.write_filter(script, filter_xml)
    return None

def self_intersecting_face(script):
    """ Select only self intersecting faces

    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="Select Self Intersecting Faces"/>\n'
    util.write_filter(script, filter_xml)
    return None

def nonmanifold_vert(script):
    """ Select 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
    """
    filter_xml = '  <filter name="Select non Manifold Vertices"/>\n'
    util.write_filter(script, filter_xml)
    return None

def nonmanifold_edge(script):
    """ Select 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
    """
    filter_xml = '  <filter name="Select non Manifold Edges "/>\n'
    util.write_filter(script, filter_xml)
    return None

def small_parts(script, ratio=0.2, non_closed_only=False):
    """ Select 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
    """
    filter_xml = ''.join([
        '  <filter name="Small component selection">\n',
        '    <Param name="NbFaceRatio" ',
        'value="{}" '.format(ratio),
        'description="Small component ratio" ',
        'type="RichFloat" ',
        '/>\n',
        '    <Param name="NonClosedOnly" ',
        'value="{}" '.format(str(non_closed_only).lower()),
        'description="Select only non closed components" ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def vert_quality(script, min_quality=0.0, max_quality=0.05, inclusive=True):
    """ Select all the faces and vertexes within the specified vertex quality
        range.

    Args:
        script: the FilterScript object or script filename to write
            the filter] to.
        min_quality (float): Minimum acceptable quality value.
        max_quality (float): Maximum acceptable quality value.
        inclusive (bool): If True only the faces with ALL the vertices within
            the specified range are selected. Otherwise any face with at least
            one vertex within the range is selected.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Select by Vertex Quality">\n',
        '    <Param name="minQ" ',
        'value="{}" '.format(min_quality),
        'description="Min Quality" ',
        'min="0" ',
        'max="{}" '.format(2 * max_quality),
        'type="RichDynamicFloat" ',
        '/>\n',
        '    <Param name="maxQ" ',
        'value="{}" '.format(max_quality),
        'description="Max Quality" ',
        'min="0" ',
        'max="{}" '.format(2 * max_quality),
        'type="RichDynamicFloat" ',
        '/>\n',
        '    <Param name="Inclusive" ',
        'value="{}" '.format(str(inclusive).lower()),
        'description="Inclusive Sel." ',
        'type="RichBool" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def face_function(script, function='(fi == 0)'):
    """Boolean function using muparser lib to perform face selection over
        current mesh.

    See help(mlx.muparser_ref) for muparser reference documentation.

    It's possible to use parenthesis, per-vertex variables and boolean operator:
        (, ), and, or, <, >, =
    It's possible to use per-face variables like attributes associated to the three
    vertices of every face.

    Variables (per face):
        x0, y0, z0 for first vertex; x1,y1,z1 for second vertex; x2,y2,z2 for third vertex
        nx0, ny0, nz0, nx1, ny1, nz1, etc. for vertex normals
        r0, g0, b0, a0, etc. for vertex color
        q0, q1, q2 for quality
        wtu0, wtv0, wtu1, wtv1, wtu2, wtv2 (per wedge texture coordinates)
        ti for face texture index (>= ML2016.12)
        vsel0, vsel1, vsel2 for vertex selection (1 yes, 0 no) (>= ML2016.12)
        fr, fg, fb, fa for face color (>= ML2016.12)
        fq for face quality (>= ML2016.12)
        fnx, fny, fnz for face normal (>= ML2016.12)
        fsel face selection (1 yes, 0 no) (>= ML2016.12)

    Args:
        script: the FilterScript object or script filename to write
            the filter] to.
        function (str): a boolean function that will be evaluated in order
            to select a subset of faces.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    filter_xml = ''.join([
        '  <filter name="Conditional Face Selection">\n',
        '    <Param name="condSelect" ',
        'value="{}" '.format(str(function).replace('&', '&').replace('<', '<')),
        'description="boolean function" ',
        'type="RichString" ',
        '/>\n',
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def vert_function(script, function='(q < 0)', strict_face_select=True):
    """Boolean function using muparser lib to perform vertex selection over current mesh.

    See help(mlx.muparser_ref) for muparser reference documentation.

    It's possible to use parenthesis, per-vertex variables and boolean operator:
        (, ), and, or, <, >, =
    It's possible to use the following per-vertex variables in the expression:

    Variables:
        x, y, z (coordinates)
        nx, ny, nz (normal)
        r, g, b, a (color)
        q (quality)
        rad
        vi (vertex index)
        vtu, vtv (texture coordinates)
        ti (texture index)
        vsel (is the vertex selected? 1 yes, 0 no)
        and all custom vertex attributes already defined by user.

    Args:
        script: the FilterScript object or script filename to write
            the filter] to.
        function (str): a boolean function that will be evaluated in order
            to select a subset of vertices. Example: (y > 0) and (ny > 0)
        strict_face_select (bool): if True a face is selected if ALL its
            vertices are selected. If False a face is selected if at least
            one of its vertices is selected. ML v1.3.4BETA only; this is
            ignored in 2016.12. In 2016.12 only vertices are selected.

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if script.ml_version == '1.3.4BETA':
        strict_select = ''.join([
            '    <Param name="strictSelect" ',
            'value="{}" '.format(str(strict_face_select).lower()),
            'description="Strict face selection" ',
            'type="RichBool" ',
            '/>\n',
            ])
    else:
        strict_select = ''

    filter_xml = ''.join([
        '  <filter name="Conditional Vertex Selection">\n',
        '    <Param name="condSelect" ',
        'value="{}" '.format(str(function).replace('&', '&').replace('<', '<')),
        'description="boolean function" ',
        'type="RichString" ',
        '/>\n',
        strict_select,
        '  </filter>\n'])
    util.write_filter(script, filter_xml)
    return None

def cylindrical_vert(script, radius=1.0, inside=True):
    """Select all vertices within a cylindrical radius

    Args:
        radius (float): radius of the sphere
        center_pt (3 coordinate tuple or list): center point of the sphere

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    if inside:
        function = 'sqrt(x^2+y^2)<={}'.format(radius)
    else:
        function = 'sqrt(x^2+y^2)>={}'.format(radius)
    vert_function(script, function=function)
    return None

def spherical_vert(script, radius=1.0, center_pt=(0.0, 0.0, 0.0)):
    """Select all vertices within a spherical radius

    Args:
        radius (float): radius of the sphere
        center_pt (3 coordinate tuple or list): center point of the sphere

    Layer stack:
        No impacts

    MeshLab versions:
        2016.12
        1.3.4BETA
    """
    function = 'sqrt((x-{})^2+(y-{})^2+(z-{})^2)<={}'.format(
        center_pt[0], center_pt[1], center_pt[2], radius)
    vert_function(script, function=function)
    return None