From 42c8d100c09903c9c0067cdec0acb48ea4bff81a Mon Sep 17 00:00:00 2001
From: Bart Crouch <bartius.crouch@gmail.com>
Date: Sat, 31 Mar 2012 17:27:21 +0000
Subject: [PATCH] Moving LoopTools from trunk to contrib, as it is broken
 because of BMesh.

I'm working on fixing it, but that won't be in time for next release. Therefore I'm moving it to contrib until I'm done fixing it.

[[Split portion of a mixed commit.]]
---
 mesh_looptools.py | 3728 ---------------------------------------------
 1 file changed, 3728 deletions(-)
 delete mode 100644 mesh_looptools.py

diff --git a/mesh_looptools.py b/mesh_looptools.py
deleted file mode 100644
index 639b9d7d1..000000000
--- a/mesh_looptools.py
+++ /dev/null
@@ -1,3728 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-bl_info = {
-    'name': "LoopTools",
-    'author': "Bart Crouch",
-    'version': (3, 2, 4),
-    'blender': (2, 6, 2),
-    'location': "View3D > Toolbar and View3D > Specials (W-key)",
-    'warning': "Bridge & Loft functions removed",
-    'description': "Mesh modelling toolkit. Several tools to aid modelling",
-    'wiki_url': "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
-        "Scripts/Modeling/LoopTools",
-    'tracker_url': "http://projects.blender.org/tracker/index.php?"\
-        "func=detail&aid=26189",
-    'category': 'Mesh'}
-
-
-import bpy
-import mathutils
-import math
-
-
-##########################################
-####### General functions ################
-##########################################
-
-
-# used by all tools to improve speed on reruns
-looptools_cache = {}
-
-
-# force a full recalculation next time
-def cache_delete(tool):
-    if tool in looptools_cache:
-        del looptools_cache[tool]
-
-
-# check cache for stored information
-def cache_read(tool, object, mesh, input_method, boundaries):
-    # current tool not cached yet
-    if tool not in looptools_cache:
-        return(False, False, False, False, False)
-    # check if selected object didn't change
-    if object.name != looptools_cache[tool]["object"]:
-        return(False, False, False, False, False)
-    # check if input didn't change
-    if input_method != looptools_cache[tool]["input_method"]:
-        return(False, False, False, False, False)
-    if boundaries != looptools_cache[tool]["boundaries"]:
-        return(False, False, False, False, False)
-    modifiers = [mod.name for mod in object.modifiers if mod.show_viewport \
-        and mod.type == 'MIRROR']
-    if modifiers != looptools_cache[tool]["modifiers"]:
-        return(False, False, False, False, False)
-    input = [v.index for v in mesh.vertices if v.select and not v.hide]
-    if input != looptools_cache[tool]["input"]:
-        return(False, False, False, False, False)
-    # reading values
-    single_loops = looptools_cache[tool]["single_loops"]
-    loops = looptools_cache[tool]["loops"]
-    derived = looptools_cache[tool]["derived"]
-    mapping = looptools_cache[tool]["mapping"]
-    
-    return(True, single_loops, loops, derived, mapping)
-
-
-# store information in the cache
-def cache_write(tool, object, mesh, input_method, boundaries, single_loops,
-loops, derived, mapping):
-    # clear cache of current tool
-    if tool in looptools_cache:
-        del looptools_cache[tool]
-    # prepare values to be saved to cache
-    input = [v.index for v in mesh.vertices if v.select and not v.hide]
-    modifiers = [mod.name for mod in object.modifiers if mod.show_viewport \
-        and mod.type == 'MIRROR']
-    # update cache
-    looptools_cache[tool] = {"input": input, "object": object.name,
-        "input_method": input_method, "boundaries": boundaries,
-        "single_loops": single_loops, "loops": loops,
-        "derived": derived, "mapping": mapping, "modifiers": modifiers}
-
-
-# calculates natural cubic splines through all given knots
-def calculate_cubic_splines(mesh_mod, tknots, knots):
-    # hack for circular loops
-    if knots[0] == knots[-1] and len(knots) > 1:
-        circular = True
-        k_new1 = []
-        for k in range(-1, -5, -1):
-            if k - 1 < -len(knots):
-                k += len(knots)
-            k_new1.append(knots[k-1])
-        k_new2 = []
-        for k in range(4):
-            if k + 1 > len(knots) - 1:
-                k -= len(knots)
-            k_new2.append(knots[k+1])
-        for k in k_new1:
-            knots.insert(0, k)
-        for k in k_new2:
-            knots.append(k)
-        t_new1 = []
-        total1 = 0
-        for t in range(-1, -5, -1):
-            if t - 1 < -len(tknots):
-                t += len(tknots)
-            total1 += tknots[t] - tknots[t-1]
-            t_new1.append(tknots[0] - total1)
-        t_new2 = []
-        total2 = 0
-        for t in range(4):
-            if t + 1 > len(tknots) - 1:
-                t -= len(tknots)
-            total2 += tknots[t+1] - tknots[t]
-            t_new2.append(tknots[-1] + total2)
-        for t in t_new1:
-            tknots.insert(0, t)
-        for t in t_new2:
-            tknots.append(t)
-    else:
-        circular = False
-    # end of hack
-    
-    n = len(knots)
-    if n < 2:
-        return False
-    x = tknots[:]
-    locs = [mesh_mod.vertices[k].co[:] for k in knots]
-    result = []
-    for j in range(3):
-        a = []
-        for i in locs:
-            a.append(i[j])
-        h = []
-        for i in range(n-1):
-            if x[i+1] - x[i] == 0:
-                h.append(1e-8)
-            else:
-                h.append(x[i+1] - x[i])
-        q = [False]
-        for i in range(1, n-1):
-            q.append(3/h[i]*(a[i+1]-a[i]) - 3/h[i-1]*(a[i]-a[i-1]))
-        l = [1.0]
-        u = [0.0]
-        z = [0.0]
-        for i in range(1, n-1):
-            l.append(2*(x[i+1]-x[i-1]) - h[i-1]*u[i-1])
-            if l[i] == 0:
-                l[i] = 1e-8
-            u.append(h[i] / l[i])
-            z.append((q[i] - h[i-1] * z[i-1]) / l[i])
-        l.append(1.0)
-        z.append(0.0)
-        b = [False for i in range(n-1)]
-        c = [False for i in range(n)]
-        d = [False for i in range(n-1)]
-        c[n-1] = 0.0
-        for i in range(n-2, -1, -1):
-            c[i] = z[i] - u[i]*c[i+1]
-            b[i] = (a[i+1]-a[i])/h[i] - h[i]*(c[i+1]+2*c[i])/3
-            d[i] = (c[i+1]-c[i]) / (3*h[i])
-        for i in range(n-1):
-            result.append([a[i], b[i], c[i], d[i], x[i]])
-    splines = []
-    for i in range(len(knots)-1):
-        splines.append([result[i], result[i+n-1], result[i+(n-1)*2]])
-    if circular: # cleaning up after hack
-        knots = knots[4:-4]
-        tknots = tknots[4:-4]
-    
-    return(splines)
-
-
-# calculates linear splines through all given knots
-def calculate_linear_splines(mesh_mod, tknots, knots):
-    splines = []
-    for i in range(len(knots)-1):
-        a = mesh_mod.vertices[knots[i]].co
-        b = mesh_mod.vertices[knots[i+1]].co
-        d = b-a
-        t = tknots[i]
-        u = tknots[i+1]-t
-        splines.append([a, d, t, u]) # [locStart, locDif, tStart, tDif]
-    
-    return(splines)
-
-
-# calculate a best-fit plane to the given vertices
-def calculate_plane(mesh_mod, loop, method="best_fit", object=False):
-    # getting the vertex locations
-    locs = [mesh_mod.vertices[v].co.copy() for v in loop[0]]
-    
-    # calculating the center of masss
-    com = mathutils.Vector()
-    for loc in locs:
-        com += loc
-    com /= len(locs)
-    x, y, z = com
-    
-    if method == 'best_fit':
-        # creating the covariance matrix
-        mat = mathutils.Matrix(((0.0, 0.0, 0.0),
-                                (0.0, 0.0, 0.0),
-                                (0.0, 0.0, 0.0),
-                                ))
-        for loc in locs:
-            mat[0][0] += (loc[0]-x)**2
-            mat[1][0] += (loc[0]-x)*(loc[1]-y)
-            mat[2][0] += (loc[0]-x)*(loc[2]-z)
-            mat[0][1] += (loc[1]-y)*(loc[0]-x)
-            mat[1][1] += (loc[1]-y)**2
-            mat[2][1] += (loc[1]-y)*(loc[2]-z)
-            mat[0][2] += (loc[2]-z)*(loc[0]-x)
-            mat[1][2] += (loc[2]-z)*(loc[1]-y)
-            mat[2][2] += (loc[2]-z)**2
-        
-        # calculating the normal to the plane
-        normal = False
-        try:
-            mat.invert()
-        except:
-            if sum(mat[0]) == 0.0:
-                normal = mathutils.Vector((1.0, 0.0, 0.0))
-            elif sum(mat[1]) == 0.0:
-                normal = mathutils.Vector((0.0, 1.0, 0.0))
-            elif sum(mat[2]) == 0.0:
-                normal = mathutils.Vector((0.0, 0.0, 1.0))
-        if not normal:
-            # warning! this is different from .normalize()
-            itermax = 500
-            iter = 0
-            vec = mathutils.Vector((1.0, 1.0, 1.0))
-            vec2 = (mat * vec)/(mat * vec).length
-            while vec != vec2 and iter<itermax:
-                iter+=1
-                vec = vec2
-                vec2 = mat * vec
-                if vec2.length != 0:
-                    vec2 /= vec2.length
-            if vec2.length == 0:
-                vec2 = mathutils.Vector((1.0, 1.0, 1.0))
-            normal = vec2
-    
-    elif method == 'normal':
-        # averaging the vertex normals
-        v_normals = [mesh_mod.vertices[v].normal for v in loop[0]]
-        normal = mathutils.Vector()
-        for v_normal in v_normals:
-            normal += v_normal
-        normal /= len(v_normals)
-        normal.normalize()
-        
-    elif method == 'view':
-        # calculate view normal
-        rotation = bpy.context.space_data.region_3d.view_matrix.to_3x3().\
-            inverted()
-        normal = rotation * mathutils.Vector((0.0, 0.0, 1.0))
-        if object:
-            normal = object.matrix_world.inverted().to_euler().to_matrix() * \
-                     normal
-    
-    return(com, normal)
-
-
-# calculate splines based on given interpolation method (controller function)
-def calculate_splines(interpolation, mesh_mod, tknots, knots):
-    if interpolation == 'cubic':
-        splines = calculate_cubic_splines(mesh_mod, tknots, knots[:])
-    else: # interpolations == 'linear'
-        splines = calculate_linear_splines(mesh_mod, tknots, knots[:])
-    
-    return(splines)
-
-
-# check loops and only return valid ones
-def check_loops(loops, mapping, mesh_mod):
-    valid_loops = []
-    for loop, circular in loops:
-        # loop needs to have at least 3 vertices
-        if len(loop) < 3:
-            continue
-        # loop needs at least 1 vertex in the original, non-mirrored mesh
-        if mapping:
-            all_virtual = True
-            for vert in loop:
-                if mapping[vert] > -1:
-                    all_virtual = False
-                    break
-            if all_virtual:
-                continue
-        # vertices can not all be at the same location
-        stacked = True
-        for i in range(len(loop) - 1):
-            if (mesh_mod.vertices[loop[i]].co - \
-            mesh_mod.vertices[loop[i+1]].co).length > 1e-6:
-                stacked = False
-                break
-        if stacked:
-            continue    
-        # passed all tests, loop is valid
-        valid_loops.append([loop, circular])
-    
-    return(valid_loops)
-
-
-# input: mesh, output: dict with the edge-key as key and face-index as value
-def dict_edge_faces(mesh):
-    edge_faces = dict([[edge.key, []] for edge in mesh.edges if not edge.hide])
-    for face in mesh.faces:
-        if face.hide:
-            continue
-        for key in face.edge_keys:
-            edge_faces[key].append(face.index)
-    
-    return(edge_faces)
-
-# input: mesh (edge-faces optional), output: dict with face-face connections
-def dict_face_faces(mesh, edge_faces=False):
-    if not edge_faces:
-        edge_faces = dict_edge_faces(mesh)
-    
-    connected_faces = dict([[face.index, []] for face in mesh.faces if \
-        not face.hide])
-    for face in mesh.faces:
-        if face.hide:
-            continue
-        for edge_key in face.edge_keys:
-            for connected_face in edge_faces[edge_key]:
-                if connected_face == face.index:
-                    continue
-                connected_faces[face.index].append(connected_face)
-    
-    return(connected_faces)
-
-
-# input: mesh, output: dict with the vert index as key and edge-keys as value
-def dict_vert_edges(mesh):
-    vert_edges = dict([[v.index, []] for v in mesh.vertices if not v.hide])
-    for edge in mesh.edges:
-        if edge.hide:
-            continue
-        for vert in edge.key:
-            vert_edges[vert].append(edge.key)
-    
-    return(vert_edges)
-
-
-# input: mesh, output: dict with the vert index as key and face index as value
-def dict_vert_faces(mesh):
-    vert_faces = dict([[v.index, []] for v in mesh.vertices if not v.hide])
-    for face in mesh.faces:
-        if not face.hide:
-            for vert in face.vertices:
-                vert_faces[vert].append(face.index)
-                
-    return(vert_faces)
-
-
-# input: list of edge-keys, output: dictionary with vertex-vertex connections
-def dict_vert_verts(edge_keys):
-    # create connection data
-    vert_verts = {}
-    for ek in edge_keys:
-        for i in range(2):
-            if ek[i] in vert_verts:
-                vert_verts[ek[i]].append(ek[1-i])
-            else:
-                vert_verts[ek[i]] = [ek[1-i]]
-    
-    return(vert_verts)
-
-
-# calculate input loops
-def get_connected_input(object, mesh, scene, input):
-    # get mesh with modifiers applied
-    derived, mesh_mod = get_derived_mesh(object, mesh, scene)
-    
-    # calculate selected loops
-    edge_keys = [edge.key for edge in mesh_mod.edges if \
-        edge.select and not edge.hide]
-    loops = get_connected_selections(edge_keys)
-    
-    # if only selected loops are needed, we're done
-    if input == 'selected':
-        return(derived, mesh_mod, loops)
-    # elif input == 'all':    
-    loops = get_parallel_loops(mesh_mod, loops)
-    
-    return(derived, mesh_mod, loops)
-
-
-# sorts all edge-keys into a list of loops
-def get_connected_selections(edge_keys):
-    # create connection data
-    vert_verts = dict_vert_verts(edge_keys)
-    
-    # find loops consisting of connected selected edges
-    loops = []
-    while len(vert_verts) > 0:
-        loop = [iter(vert_verts.keys()).__next__()]
-        growing = True
-        flipped = False
-        
-        # extend loop
-        while growing:
-            # no more connection data for current vertex
-            if loop[-1] not in vert_verts:
-                if not flipped:
-                    loop.reverse()
-                    flipped = True
-                else:
-                    growing = False
-            else:
-                extended = False
-                for i, next_vert in enumerate(vert_verts[loop[-1]]):
-                    if next_vert not in loop:
-                        vert_verts[loop[-1]].pop(i)
-                        if len(vert_verts[loop[-1]]) == 0:
-                            del vert_verts[loop[-1]]
-                        # remove connection both ways
-                        if next_vert in vert_verts:
-                            if len(vert_verts[next_vert]) == 1:
-                                del vert_verts[next_vert]
-                            else:
-                                vert_verts[next_vert].remove(loop[-1])
-                        loop.append(next_vert)
-                        extended = True
-                        break
-                if not extended:
-                    # found one end of the loop, continue with next
-                    if not flipped:
-                        loop.reverse()
-                        flipped = True
-                    # found both ends of the loop, stop growing
-                    else:
-                        growing = False
-        
-        # check if loop is circular
-        if loop[0] in vert_verts:
-            if loop[-1] in vert_verts[loop[0]]:
-                # is circular
-                if len(vert_verts[loop[0]]) == 1:
-                    del vert_verts[loop[0]]
-                else:
-                    vert_verts[loop[0]].remove(loop[-1])
-                if len(vert_verts[loop[-1]]) == 1:
-                    del vert_verts[loop[-1]]
-                else:
-                    vert_verts[loop[-1]].remove(loop[0])
-                loop = [loop, True]
-            else:
-                # not circular
-                loop = [loop, False]
-        else:
-            # not circular
-            loop = [loop, False]
-        
-        loops.append(loop)
-    
-    return(loops)
-
-
-# get the derived mesh data, if there is a mirror modifier
-def get_derived_mesh(object, mesh, scene):
-    # check for mirror modifiers
-    if 'MIRROR' in [mod.type for mod in object.modifiers if mod.show_viewport]:
-        derived = True
-        # disable other modifiers
-        show_viewport = [mod.name for mod in object.modifiers if \
-            mod.show_viewport]
-        for mod in object.modifiers:
-            if mod.type != 'MIRROR':
-                mod.show_viewport = False
-        # get derived mesh
-        mesh_mod = object.to_mesh(scene, True, 'PREVIEW')
-        # re-enable other modifiers
-        for mod_name in show_viewport:
-            object.modifiers[mod_name].show_viewport = True
-    # no mirror modifiers, so no derived mesh necessary
-    else:
-        derived = False
-        mesh_mod = mesh
-    
-    return(derived, mesh_mod)
-
-
-# return a mapping of derived indices to indices
-def get_mapping(derived, mesh, mesh_mod, single_vertices, full_search, loops):
-    if not derived:
-        return(False)
-    
-    if full_search:
-        verts = [v for v in mesh.vertices if not v.hide]
-    else:
-        verts = [v for v in mesh.vertices if v.select and not v.hide]
-    
-    # non-selected vertices around single vertices also need to be mapped
-    if single_vertices:
-        mapping = dict([[vert, -1] for vert in single_vertices])
-        verts_mod = [mesh_mod.vertices[vert] for vert in single_vertices]
-        for v in verts:
-            for v_mod in verts_mod:
-                if (v.co - v_mod.co).length < 1e-6:
-                    mapping[v_mod.index] = v.index
-                    break
-        real_singles = [v_real for v_real in mapping.values() if v_real>-1]
-        
-        verts_indices = [vert.index for vert in verts]
-        for face in [face for face in mesh.faces if not face.select \
-        and not face.hide]:
-            for vert in face.vertices:
-                if vert in real_singles:
-                    for v in face.vertices:
-                        if not v in verts_indices:
-                            if mesh.vertices[v] not in verts:
-                                verts.append(mesh.vertices[v])
-                    break
-    
-    # create mapping of derived indices to indices
-    mapping = dict([[vert, -1] for loop in loops for vert in loop[0]])
-    if single_vertices:
-        for single in single_vertices:
-            mapping[single] = -1
-    verts_mod = [mesh_mod.vertices[i] for i in mapping.keys()]
-    for v in verts:
-        for v_mod in verts_mod:
-            if (v.co - v_mod.co).length < 1e-6:
-                mapping[v_mod.index] = v.index
-                verts_mod.remove(v_mod)
-                break
-    
-    return(mapping)
-
-
-# returns a list of all loops parallel to the input, input included
-def get_parallel_loops(mesh_mod, loops):
-    # get required dictionaries
-    edge_faces = dict_edge_faces(mesh_mod)
-    connected_faces = dict_face_faces(mesh_mod, edge_faces)
-    # turn vertex loops into edge loops
-    edgeloops = []
-    for loop in loops:
-        edgeloop = [[sorted([loop[0][i], loop[0][i+1]]) for i in \
-            range(len(loop[0])-1)], loop[1]]
-        if loop[1]: # circular
-            edgeloop[0].append(sorted([loop[0][-1], loop[0][0]]))
-        edgeloops.append(edgeloop[:])
-    # variables to keep track while iterating
-    all_edgeloops = []
-    has_branches = False
-    
-    for loop in edgeloops:
-        # initialise with original loop
-        all_edgeloops.append(loop[0])
-        newloops = [loop[0]]
-        verts_used = []
-        for edge in loop[0]:
-            if edge[0] not in verts_used:
-                verts_used.append(edge[0])
-            if edge[1] not in verts_used:
-                verts_used.append(edge[1])
-        
-        # find parallel loops
-        while len(newloops) > 0:
-            side_a = []
-            side_b = []
-            for i in newloops[-1]:
-                i = tuple(i)
-                forbidden_side = False
-                if not i in edge_faces:
-                    # weird input with branches
-                    has_branches = True
-                    break
-                for face in edge_faces[i]:
-                    if len(side_a) == 0 and forbidden_side != "a":
-                        side_a.append(face)
-                        if forbidden_side:
-                            break
-                        forbidden_side = "a"
-                        continue
-                    elif side_a[-1] in connected_faces[face] and \
-                    forbidden_side != "a":
-                        side_a.append(face)
-                        if forbidden_side:
-                            break
-                        forbidden_side = "a"
-                        continue
-                    if len(side_b) == 0 and forbidden_side != "b":
-                        side_b.append(face)
-                        if forbidden_side:
-                            break
-                        forbidden_side = "b"
-                        continue
-                    elif side_b[-1] in connected_faces[face] and \
-                    forbidden_side != "b":
-                        side_b.append(face)
-                        if forbidden_side:
-                            break
-                        forbidden_side = "b"
-                        continue
-            
-            if has_branches:
-                # weird input with branches
-                break
-            
-            newloops.pop(-1)
-            sides = []
-            if side_a:
-                sides.append(side_a)
-            if side_b:
-                sides.append(side_b)
-            
-            for side in sides:
-                extraloop = []
-                for fi in side:
-                    for key in mesh_mod.faces[fi].edge_keys:
-                        if key[0] not in verts_used and key[1] not in \
-                        verts_used:
-                            extraloop.append(key)
-                            break
-                if extraloop:
-                    for key in extraloop:
-                        for new_vert in key:
-                            if new_vert not in verts_used:
-                                verts_used.append(new_vert)
-                    newloops.append(extraloop)
-                    all_edgeloops.append(extraloop)
-    
-    # input contains branches, only return selected loop
-    if has_branches:
-        return(loops)
-    
-    # change edgeloops into normal loops
-    loops = []
-    for edgeloop in all_edgeloops:
-        loop = []
-        # grow loop by comparing vertices between consecutive edge-keys
-        for i in range(len(edgeloop)-1):
-            for vert in range(2):
-                if edgeloop[i][vert] in edgeloop[i+1]:
-                    loop.append(edgeloop[i][vert])
-                    break
-        if loop:
-            # add starting vertex
-            for vert in range(2):
-                if edgeloop[0][vert] != loop[0]:
-                    loop = [edgeloop[0][vert]] + loop
-                    break
-            # add ending vertex
-            for vert in range(2):
-                if edgeloop[-1][vert] != loop[-1]:
-                    loop.append(edgeloop[-1][vert])
-                    break
-            # check if loop is circular
-            if loop[0] == loop[-1]:
-                circular = True
-                loop = loop[:-1]
-            else:
-                circular = False
-        loops.append([loop, circular])
-    
-    return(loops)
-
-
-# gather initial data
-def initialise():
-    global_undo = bpy.context.user_preferences.edit.use_global_undo
-    bpy.context.user_preferences.edit.use_global_undo = False
-    bpy.ops.object.mode_set(mode='OBJECT')
-    object = bpy.context.active_object
-    mesh = bpy.context.active_object.data
-    
-    return(global_undo, object, mesh)
-
-
-# move the vertices to their new locations
-def move_verts(mesh, mapping, move, influence):
-    for loop in move:
-        for index, loc in loop:
-            if mapping:
-                if mapping[index] == -1:
-                    continue
-                else:
-                    index = mapping[index]
-            if influence >= 0:
-                mesh.vertices[index].co = loc*(influence/100) + \
-                    mesh.vertices[index].co*((100-influence)/100)
-            else:
-                mesh.vertices[index].co = loc
-
-
-# load custom tool settings 
-def settings_load(self):
-    lt = bpy.context.window_manager.looptools
-    tool = self.name.split()[0].lower()
-    keys = self.as_keywords().keys()
-    for key in keys:
-        setattr(self, key, getattr(lt, tool + "_" + key))
-
-
-# store custom tool settings
-def settings_write(self):
-    lt = bpy.context.window_manager.looptools
-    tool = self.name.split()[0].lower()
-    keys = self.as_keywords().keys()
-    for key in keys:
-        setattr(lt, tool + "_" + key, getattr(self, key))
-
-
-# clean up and set settings back to original state
-def terminate(global_undo):
-    bpy.ops.object.mode_set(mode='EDIT')
-    bpy.context.user_preferences.edit.use_global_undo = global_undo
-
-
-##########################################
-####### Bridge functions #################
-##########################################
-
-# calculate a cubic spline through the middle section of 4 given coordinates
-def bridge_calculate_cubic_spline(mesh, coordinates):
-    result = []
-    x = [0, 1, 2, 3]
-    
-    for j in range(3):
-        a = []
-        for i in coordinates:
-            a.append(float(i[j]))
-        h = []
-        for i in range(3):
-            h.append(x[i+1]-x[i])
-        q = [False]
-        for i in range(1,3):
-            q.append(3.0/h[i]*(a[i+1]-a[i])-3.0/h[i-1]*(a[i]-a[i-1]))
-        l = [1.0]
-        u = [0.0]
-        z = [0.0]
-        for i in range(1,3):
-            l.append(2.0*(x[i+1]-x[i-1])-h[i-1]*u[i-1])
-            u.append(h[i]/l[i])
-            z.append((q[i]-h[i-1]*z[i-1])/l[i])
-        l.append(1.0)
-        z.append(0.0)
-        b = [False for i in range(3)]
-        c = [False for i in range(4)]
-        d = [False for i in range(3)]
-        c[3] = 0.0
-        for i in range(2,-1,-1):
-            c[i] = z[i]-u[i]*c[i+1]
-            b[i] = (a[i+1]-a[i])/h[i]-h[i]*(c[i+1]+2.0*c[i])/3.0
-            d[i] = (c[i+1]-c[i])/(3.0*h[i])
-        for i in range(3):
-            result.append([a[i], b[i], c[i], d[i], x[i]])
-    spline = [result[1], result[4], result[7]]
-
-    return(spline)
-
-
-# return a list with new vertex location vectors, a list with face vertex 
-# integers, and the highest vertex integer in the virtual mesh
-def bridge_calculate_geometry(mesh, lines, vertex_normals, segments,
-interpolation, cubic_strength, min_width, max_vert_index):
-    new_verts = []
-    faces = []
-    
-    # calculate location based on interpolation method
-    def get_location(line, segment, splines):
-        v1 = mesh.vertices[lines[line][0]].co
-        v2 = mesh.vertices[lines[line][1]].co
-        if interpolation == 'linear':
-            return v1 + (segment/segments) * (v2-v1)
-        else: # interpolation == 'cubic'
-            m = (segment/segments)
-            ax,bx,cx,dx,tx = splines[line][0]
-            x = ax+bx*m+cx*m**2+dx*m**3
-            ay,by,cy,dy,ty = splines[line][1]
-            y = ay+by*m+cy*m**2+dy*m**3
-            az,bz,cz,dz,tz = splines[line][2]
-            z = az+bz*m+cz*m**2+dz*m**3
-            return mathutils.Vector((x, y, z))
-        
-    # no interpolation needed
-    if segments == 1:
-        for i, line in enumerate(lines):
-            if i < len(lines)-1:
-                faces.append([line[0], lines[i+1][0], lines[i+1][1], line[1]])
-    # more than 1 segment, interpolate
-    else:
-        # calculate splines (if necessary) once, so no recalculations needed
-        if interpolation == 'cubic':
-            splines = []
-            for line in lines:
-                v1 = mesh.vertices[line[0]].co
-                v2 = mesh.vertices[line[1]].co
-                size = (v2-v1).length * cubic_strength
-                splines.append(bridge_calculate_cubic_spline(mesh,
-                    [v1+size*vertex_normals[line[0]], v1, v2,
-                    v2+size*vertex_normals[line[1]]]))
-        else:
-            splines = False
-        
-        # create starting situation
-        virtual_width = [(mesh.vertices[lines[i][0]].co -
-                          mesh.vertices[lines[i+1][0]].co).length for i
-                          in range(len(lines)-1)]
-        new_verts = [get_location(0, seg, splines) for seg in range(1,
-            segments)]
-        first_line_indices = [i for i in range(max_vert_index+1,
-            max_vert_index+segments)]
-        
-        prev_verts = new_verts[:] # vertex locations of verts on previous line
-        prev_vert_indices = first_line_indices[:]
-        max_vert_index += segments - 1 # highest vertex index in virtual mesh
-        next_verts = [] # vertex locations of verts on current line
-        next_vert_indices = []
-        
-        for i, line in enumerate(lines):
-            if i < len(lines)-1:
-                v1 = line[0]
-                v2 = lines[i+1][0]
-                end_face = True
-                for seg in range(1, segments):
-                    loc1 = prev_verts[seg-1]
-                    loc2 = get_location(i+1, seg, splines)
-                    if (loc1-loc2).length < (min_width/100)*virtual_width[i] \
-                    and line[1]==lines[i+1][1]:
-                        # triangle, no new vertex
-                        faces.append([v1, v2, prev_vert_indices[seg-1],
-                            prev_vert_indices[seg-1]])
-                        next_verts += prev_verts[seg-1:]
-                        next_vert_indices += prev_vert_indices[seg-1:]
-                        end_face = False
-                        break
-                    else:
-                        if i == len(lines)-2 and lines[0] == lines[-1]:
-                            # quad with first line, no new vertex
-                            faces.append([v1, v2, first_line_indices[seg-1],
-                                prev_vert_indices[seg-1]])
-                            v2 = first_line_indices[seg-1]
-                            v1 = prev_vert_indices[seg-1]
-                        else:
-                            # quad, add new vertex
-                            max_vert_index += 1
-                            faces.append([v1, v2, max_vert_index,
-                                prev_vert_indices[seg-1]])
-                            v2 = max_vert_index
-                            v1 = prev_vert_indices[seg-1]
-                            new_verts.append(loc2)
-                            next_verts.append(loc2)
-                            next_vert_indices.append(max_vert_index)
-                if end_face:
-                    faces.append([v1, v2, lines[i+1][1], line[1]])
-                
-                prev_verts = next_verts[:]
-                prev_vert_indices = next_vert_indices[:]
-                next_verts = []
-                next_vert_indices = []
-    
-    return(new_verts, faces, max_vert_index)
-
-
-# calculate lines (list of lists, vertex indices) that are used for bridging
-def bridge_calculate_lines(mesh, loops, mode, twist, reverse):
-    lines = []
-    loop1, loop2 = [i[0] for i in loops]
-    loop1_circular, loop2_circular = [i[1] for i in loops]
-    circular = loop1_circular or loop2_circular
-    circle_full = False
-    
-    # calculate loop centers
-    centers = []
-    for loop in [loop1, loop2]:
-        center = mathutils.Vector()
-        for vertex in loop:
-            center += mesh.vertices[vertex].co
-        center /= len(loop)
-        centers.append(center)
-    for i, loop in enumerate([loop1, loop2]):
-        for vertex in loop:
-            if mesh.vertices[vertex].co == centers[i]:
-                # prevent zero-length vectors in angle comparisons
-                centers[i] += mathutils.Vector((0.01, 0, 0))
-                break
-    center1, center2 = centers
-    
-    # calculate the normals of the virtual planes that the loops are on
-    normals = []
-    normal_plurity = False
-    for i, loop in enumerate([loop1, loop2]):
-        # covariance matrix
-        mat = mathutils.Matrix(((0.0, 0.0, 0.0),
-                                (0.0, 0.0, 0.0),
-                                (0.0, 0.0, 0.0)))
-        x, y, z = centers[i]
-        for loc in [mesh.vertices[vertex].co for vertex in loop]:
-            mat[0][0] += (loc[0]-x)**2
-            mat[1][0] += (loc[0]-x)*(loc[1]-y)
-            mat[2][0] += (loc[0]-x)*(loc[2]-z)
-            mat[0][1] += (loc[1]-y)*(loc[0]-x)
-            mat[1][1] += (loc[1]-y)**2
-            mat[2][1] += (loc[1]-y)*(loc[2]-z)
-            mat[0][2] += (loc[2]-z)*(loc[0]-x)
-            mat[1][2] += (loc[2]-z)*(loc[1]-y)
-            mat[2][2] += (loc[2]-z)**2
-        # plane normal
-        normal = False
-        if sum(mat[0]) < 1e-6 or sum(mat[1]) < 1e-6 or sum(mat[2]) < 1e-6:
-            normal_plurity = True
-        try:
-            mat.invert()
-        except:
-            if sum(mat[0]) == 0:
-                normal = mathutils.Vector((1.0, 0.0, 0.0))
-            elif sum(mat[1]) == 0:
-                normal = mathutils.Vector((0.0, 1.0, 0.0))
-            elif sum(mat[2]) == 0:
-                normal = mathutils.Vector((0.0, 0.0, 1.0))
-        if not normal:
-            # warning! this is different from .normalize()
-            itermax = 500
-            iter = 0
-            vec = mathutils.Vector((1.0, 1.0, 1.0))
-            vec2 = (mat * vec)/(mat * vec).length
-            while vec != vec2 and iter<itermax:
-                iter+=1
-                vec = vec2
-                vec2 = mat * vec
-                if vec2.length != 0:
-                    vec2 /= vec2.length
-            if vec2.length == 0:
-                vec2 = mathutils.Vector((1.0, 1.0, 1.0))
-            normal = vec2
-        normals.append(normal)
-    # have plane normals face in the same direction (maximum angle: 90 degrees)
-    if ((center1 + normals[0]) - center2).length < \
-    ((center1 - normals[0]) - center2).length:
-        normals[0].negate()
-    if ((center2 + normals[1]) - center1).length > \
-    ((center2 - normals[1]) - center1).length:
-        normals[1].negate()
-    
-    # rotation matrix, representing the difference between the plane normals
-    axis = normals[0].cross(normals[1])
-    axis = mathutils.Vector([loc if abs(loc) > 1e-8 else 0 for loc in axis])
-    if axis.angle(mathutils.Vector((0, 0, 1)), 0) > 1.5707964:
-        axis.negate()
-    angle = normals[0].dot(normals[1])
-    rotation_matrix = mathutils.Matrix.Rotation(angle, 4, axis)
-    
-    # if circular, rotate loops so they are aligned
-    if circular:
-        # make sure loop1 is the circular one (or both are circular)
-        if loop2_circular and not loop1_circular:
-            loop1_circular, loop2_circular = True, False
-            loop1, loop2 = loop2, loop1
-        
-        # match start vertex of loop1 with loop2
-        target_vector = mesh.vertices[loop2[0]].co - center2
-        dif_angles = [[(rotation_matrix * (mesh.vertices[vertex].co - center1)
-                       ).angle(target_vector, 0), False, i] for
-                       i, vertex in enumerate(loop1)]
-        dif_angles.sort()
-        if len(loop1) != len(loop2):
-            angle_limit = dif_angles[0][0] * 1.2 # 20% margin
-            dif_angles = [[(mesh.vertices[loop2[0]].co - \
-                mesh.vertices[loop1[index]].co).length, angle, index] for \
-                angle, distance, index in dif_angles if angle <= angle_limit]
-            dif_angles.sort()
-        loop1 = loop1[dif_angles[0][2]:] + loop1[:dif_angles[0][2]]
-    
-    # have both loops face the same way
-    if normal_plurity and not circular:
-        second_to_first, second_to_second, second_to_last = \
-            [(mesh.vertices[loop1[1]].co - center1).\
-            angle(mesh.vertices[loop2[i]].co - center2) for i in [0, 1, -1]]
-        last_to_first, last_to_second = [(mesh.vertices[loop1[-1]].co - \
-            center1).angle(mesh.vertices[loop2[i]].co - center2) for \
-            i in [0, 1]]
-        if (min(last_to_first, last_to_second)*1.1 < min(second_to_first, \
-        second_to_second)) or (loop2_circular and second_to_last*1.1 < \
-        min(second_to_first, second_to_second)):
-            loop1.reverse()
-            if circular:
-                loop1 = [loop1[-1]] + loop1[:-1]
-    else:
-        angle = (mesh.vertices[loop1[0]].co - center1).\
-            cross(mesh.vertices[loop1[1]].co - center1).angle(normals[0], 0)
-        target_angle = (mesh.vertices[loop2[0]].co - center2).\
-            cross(mesh.vertices[loop2[1]].co - center2).angle(normals[1], 0)
-        limit = 1.5707964 # 0.5*pi, 90 degrees
-        if not ((angle > limit and target_angle > limit) or \
-        (angle < limit and target_angle < limit)):
-            loop1.reverse()
-            if circular:
-                loop1 = [loop1[-1]] + loop1[:-1]
-        elif normals[0].angle(normals[1]) > limit:
-            loop1.reverse()
-            if circular:
-                loop1 = [loop1[-1]] + loop1[:-1]
-    
-    # both loops have the same length
-    if len(loop1) == len(loop2):
-        # manual override
-        if twist:
-            if abs(twist) < len(loop1):
-                loop1 = loop1[twist:]+loop1[:twist]
-        if reverse:
-            loop1.reverse()
-        
-        lines.append([loop1[0], loop2[0]])
-        for i in range(1, len(loop1)):
-            lines.append([loop1[i], loop2[i]])
-    
-    # loops of different lengths
-    else:
-        # make loop1 longest loop
-        if len(loop2) > len(loop1):
-            loop1, loop2 = loop2, loop1
-            loop1_circular, loop2_circular = loop2_circular, loop1_circular
-        
-        # manual override
-        if twist:
-            if abs(twist) < len(loop1):
-                loop1 = loop1[twist:]+loop1[:twist]
-        if reverse:
-            loop1.reverse()
-            
-        # shortest angle difference doesn't always give correct start vertex
-        if loop1_circular and not loop2_circular:
-            shifting = 1
-            while shifting:
-                if len(loop1) - shifting < len(loop2):
-                    shifting = False
-                    break
-                to_last, to_first = [(rotation_matrix *
-                    (mesh.vertices[loop1[-1]].co - center1)).angle((mesh.\
-                    vertices[loop2[i]].co - center2), 0) for i in [-1, 0]]
-                if to_first < to_last:
-                    loop1 = [loop1[-1]] + loop1[:-1]
-                    shifting += 1
-                else:
-                    shifting = False
-                    break
-        
-        # basic shortest side first
-        if mode == 'basic':
-            lines.append([loop1[0], loop2[0]])
-            for i in range(1, len(loop1)):
-                if i >= len(loop2) - 1:
-                    # triangles
-                    lines.append([loop1[i], loop2[-1]])
-                else:
-                    # quads
-                    lines.append([loop1[i], loop2[i]])
-        
-        # shortest edge algorithm
-        else: # mode == 'shortest'
-            lines.append([loop1[0], loop2[0]])
-            prev_vert2 = 0
-            for i in range(len(loop1) -1):
-                if prev_vert2 == len(loop2) - 1 and not loop2_circular:
-                    # force triangles, reached end of loop2
-                    tri, quad = 0, 1
-                elif prev_vert2 == len(loop2) - 1 and loop2_circular:
-                    # at end of loop2, but circular, so check with first vert
-                    tri, quad = [(mesh.vertices[loop1[i+1]].co -
-                                  mesh.vertices[loop2[j]].co).length
-                                 for j in [prev_vert2, 0]]
-
-                    circle_full = 2
-                elif len(loop1) - 1 - i == len(loop2) - 1 - prev_vert2 and \
-                not circle_full:
-                    # force quads, otherwise won't make it to end of loop2
-                    tri, quad = 1, 0
-                else:
-                    # calculate if tri or quad gives shortest edge
-                    tri, quad = [(mesh.vertices[loop1[i+1]].co -
-                                  mesh.vertices[loop2[j]].co).length
-                                 for j in range(prev_vert2, prev_vert2+2)]
-                
-                # triangle
-                if tri < quad:
-                    lines.append([loop1[i+1], loop2[prev_vert2]])
-                    if circle_full == 2:
-                        circle_full = False
-                # quad
-                elif not circle_full:
-                    lines.append([loop1[i+1], loop2[prev_vert2+1]])
-                    prev_vert2 += 1
-                # quad to first vertex of loop2
-                else:
-                    lines.append([loop1[i+1], loop2[0]])
-                    prev_vert2 = 0
-                    circle_full = True
-    
-    # final face for circular loops
-    if loop1_circular and loop2_circular:
-        lines.append([loop1[0], loop2[0]])
-    
-    return(lines)
-
-
-# calculate number of segments needed
-def bridge_calculate_segments(mesh, lines, loops, segments):
-    # return if amount of segments is set by user
-    if segments != 0:
-        return segments
-    
-    # edge lengths
-    average_edge_length = [(mesh.vertices[vertex].co - \
-        mesh.vertices[loop[0][i+1]].co).length for loop in loops for \
-        i, vertex in enumerate(loop[0][:-1])]
-    # closing edges of circular loops
-    average_edge_length += [(mesh.vertices[loop[0][-1]].co - \
-        mesh.vertices[loop[0][0]].co).length for loop in loops if loop[1]] 
-    
-    # average lengths
-    average_edge_length = sum(average_edge_length) / len(average_edge_length)
-    average_bridge_length = sum([(mesh.vertices[v1].co - \
-        mesh.vertices[v2].co).length for v1, v2 in lines]) / len(lines)
-    
-    segments = max(1, round(average_bridge_length / average_edge_length))
-        
-    return(segments)
-
-
-# return dictionary with vertex index as key, and the normal vector as value
-def bridge_calculate_virtual_vertex_normals(mesh, lines, loops, edge_faces,
-edgekey_to_edge):
-    if not edge_faces: # interpolation isn't set to cubic
-        return False
-    
-    # pity reduce() isn't one of the basic functions in python anymore
-    def average_vector_dictionary(dic):
-        for key, vectors in dic.items():
-            #if type(vectors) == type([]) and len(vectors) > 1:
-            if len(vectors) > 1:
-                average = mathutils.Vector()
-                for vector in vectors:
-                    average += vector
-                average /= len(vectors)
-                dic[key] = [average]
-        return dic
-    
-    # get all edges of the loop
-    edges = [[edgekey_to_edge[tuple(sorted([loops[j][0][i],
-        loops[j][0][i+1]]))] for i in range(len(loops[j][0])-1)] for \
-        j in [0,1]]
-    edges = edges[0] + edges[1]
-    for j in [0, 1]:
-        if loops[j][1]: # circular
-            edges.append(edgekey_to_edge[tuple(sorted([loops[j][0][0],
-                loops[j][0][-1]]))])
-    
-    """
-    calculation based on face topology (assign edge-normals to vertices)
-    
-    edge_normal = face_normal x edge_vector
-    vertex_normal = average(edge_normals)
-    """
-    vertex_normals = dict([(vertex, []) for vertex in loops[0][0]+loops[1][0]])
-    for edge in edges:
-        faces = edge_faces[edge.key] # valid faces connected to edge
-        
-        if faces:
-            # get edge coordinates
-            v1, v2 = [mesh.vertices[edge.key[i]].co for i in [0,1]]
-            edge_vector = v1 - v2
-            if edge_vector.length < 1e-4:
-                # zero-length edge, vertices at same location
-                continue
-            edge_center = (v1 + v2) / 2
-            
-            # average face coordinates, if connected to more than 1 valid face
-            if len(faces) > 1:
-                face_normal = mathutils.Vector()
-                face_center = mathutils.Vector()
-                for face in faces:
-                    face_normal += face.normal
-                    face_center += face.center
-                face_normal /= len(faces)
-                face_center /= len(faces)
-            else:
-                face_normal = faces[0].normal
-                face_center = faces[0].center
-            if face_normal.length < 1e-4:
-                # faces with a surface of 0 have no face normal
-                continue
-            
-            # calculate virtual edge normal
-            edge_normal = edge_vector.cross(face_normal)
-            edge_normal.length = 0.01
-            if (face_center - (edge_center + edge_normal)).length > \
-            (face_center - (edge_center - edge_normal)).length:
-                # make normal face the correct way
-                edge_normal.negate()
-            edge_normal.normalize()
-            # add virtual edge normal as entry for both vertices it connects
-            for vertex in edge.key:
-                vertex_normals[vertex].append(edge_normal)
-    
-    """ 
-    calculation based on connection with other loop (vertex focused method) 
-    - used for vertices that aren't connected to any valid faces
-    
-    plane_normal = edge_vector x connection_vector
-    vertex_normal = plane_normal x edge_vector
-    """
-    vertices = [vertex for vertex, normal in vertex_normals.items() if not \
-        normal]
-    
-    if vertices:
-        # edge vectors connected to vertices
-        edge_vectors = dict([[vertex, []] for vertex in vertices])
-        for edge in edges:
-            for v in edge.key:
-                if v in edge_vectors:
-                    edge_vector = mesh.vertices[edge.key[0]].co - \
-                        mesh.vertices[edge.key[1]].co
-                    if edge_vector.length < 1e-4:
-                        # zero-length edge, vertices at same location
-                        continue
-                    edge_vectors[v].append(edge_vector)
-    
-        # connection vectors between vertices of both loops
-        connection_vectors = dict([[vertex, []] for vertex in vertices])
-        connections = dict([[vertex, []] for vertex in vertices])
-        for v1, v2 in lines:
-            if v1 in connection_vectors or v2 in connection_vectors:
-                new_vector = mesh.vertices[v1].co - mesh.vertices[v2].co
-                if new_vector.length < 1e-4:
-                    # zero-length connection vector,
-                    # vertices in different loops at same location
-                    continue
-                if v1 in connection_vectors:
-                    connection_vectors[v1].append(new_vector)
-                    connections[v1].append(v2)
-                if v2 in connection_vectors:
-                    connection_vectors[v2].append(new_vector)
-                    connections[v2].append(v1)
-        connection_vectors = average_vector_dictionary(connection_vectors)
-        connection_vectors = dict([[vertex, vector[0]] if vector else \
-            [vertex, []] for vertex, vector in connection_vectors.items()])
-        
-        for vertex, values in edge_vectors.items():
-            # vertex normal doesn't matter, just assign a random vector to it
-            if not connection_vectors[vertex]:
-                vertex_normals[vertex] = [mathutils.Vector((1, 0, 0))]
-                continue
-            
-            # calculate to what location the vertex is connected, 
-            # used to determine what way to flip the normal
-            connected_center = mathutils.Vector()
-            for v in connections[vertex]:
-                connected_center += mesh.vertices[v].co
-            if len(connections[vertex]) > 1:
-                connected_center /= len(connections[vertex])
-            if len(connections[vertex]) == 0:
-                # shouldn't be possible, but better safe than sorry
-                vertex_normals[vertex] = [mathutils.Vector((1, 0, 0))]
-                continue
-            
-            # can't do proper calculations, because of zero-length vector
-            if not values:
-                if (connected_center - (mesh.vertices[vertex].co + \
-                connection_vectors[vertex])).length < (connected_center - \
-                (mesh.vertices[vertex].co - connection_vectors[vertex])).\
-                length:
-                    connection_vectors[vertex].negate()
-                vertex_normals[vertex] = [connection_vectors[vertex].\
-                    normalized()]
-                continue
-            
-            # calculate vertex normals using edge-vectors,
-            # connection-vectors and the derived plane normal
-            for edge_vector in values:
-                plane_normal = edge_vector.cross(connection_vectors[vertex])
-                vertex_normal = edge_vector.cross(plane_normal)
-                vertex_normal.length = 0.1
-                if (connected_center - (mesh.vertices[vertex].co + \
-                vertex_normal)).length < (connected_center - \
-                (mesh.vertices[vertex].co - vertex_normal)).length:
-                # make normal face the correct way
-                    vertex_normal.negate()
-                vertex_normal.normalize()
-                vertex_normals[vertex].append(vertex_normal)
-    
-    # average virtual vertex normals, based on all edges it's connected to
-    vertex_normals = average_vector_dictionary(vertex_normals)
-    vertex_normals = dict([[vertex, vector[0]] for vertex, vector in \
-        vertex_normals.items()])
-    
-    return(vertex_normals)
-
-
-# add vertices to mesh
-def bridge_create_vertices(mesh, vertices):
-    start_index = len(mesh.vertices)
-    mesh.vertices.add(len(vertices))
-    for i in range(len(vertices)):
-        mesh.vertices[start_index + i].co = vertices[i]
-
-
-# add faces to mesh
-def bridge_create_faces(mesh, faces, twist):
-    # have the normal point the correct way
-    if twist < 0:
-        [face.reverse() for face in faces]
-        faces = [face[2:]+face[:2] if face[0]==face[1] else face for \
-            face in faces]
-    
-    # eekadoodle prevention
-    for i in range(len(faces)):
-        if not faces[i][-1]:
-            if faces[i][0] == faces[i][-1]:
-                faces[i] = [faces[i][1], faces[i][2], faces[i][3], faces[i][1]]
-            else:
-                faces[i] = [faces[i][-1]] + faces[i][:-1]
-    
-    start_faces = len(mesh.faces)
-    mesh.faces.add(len(faces))
-    for i in range(len(faces)):
-        mesh.faces[start_faces + i].vertices_raw = faces[i]
-    mesh.update(calc_edges = True) # calc_edges prevents memory-corruption
-
-
-# calculate input loops
-def bridge_get_input(mesh):
-    # create list of internal edges, which should be skipped
-    eks_of_selected_faces = [item for sublist in [face.edge_keys for face \
-        in mesh.faces if face.select and not face.hide] for item in sublist]
-    edge_count = {}
-    for ek in eks_of_selected_faces:
-        if ek in edge_count:
-            edge_count[ek] += 1
-        else:
-            edge_count[ek] = 1
-    internal_edges = [ek for ek in edge_count if edge_count[ek] > 1]
-    
-    # sort correct edges into loops
-    selected_edges = [edge.key for edge in mesh.edges if edge.select \
-        and not edge.hide and edge.key not in internal_edges]
-    loops = get_connected_selections(selected_edges)
-    
-    return(loops)
-
-
-# return values needed by the bridge operator
-def bridge_initialise(mesh, interpolation):
-    if interpolation == 'cubic':
-        # dict with edge-key as key and list of connected valid faces as value
-        face_blacklist = [face.index for face in mesh.faces if face.select or \
-            face.hide]
-        edge_faces = dict([[edge.key, []] for edge in mesh.edges if not \
-            edge.hide])
-        for face in mesh.faces:
-            if face.index in face_blacklist:
-                continue
-            for key in face.edge_keys:
-                edge_faces[key].append(face)
-        # dictionary with the edge-key as key and edge as value
-        edgekey_to_edge = dict([[edge.key, edge] for edge in mesh.edges if \
-            edge.select and not edge.hide])
-    else:
-        edge_faces = False
-        edgekey_to_edge = False
-    
-    # selected faces input
-    old_selected_faces = [face.index for face in mesh.faces if face.select \
-        and not face.hide]
-    
-    # find out if faces created by bridging should be smoothed
-    smooth = False
-    if mesh.faces:
-        if sum([face.use_smooth for face in mesh.faces])/len(mesh.faces) \
-        >= 0.5:
-            smooth = True
-    
-    return(edge_faces, edgekey_to_edge, old_selected_faces, smooth)
-
-
-# return a string with the input method
-def bridge_input_method(loft, loft_loop):
-    method = ""
-    if loft:
-        if loft_loop:
-            method = "Loft loop"
-        else:
-            method = "Loft no-loop"
-    else:
-        method = "Bridge"
-    
-    return(method)
-
-
-# match up loops in pairs, used for multi-input bridging
-def bridge_match_loops(mesh, loops):
-    # calculate average loop normals and centers
-    normals = []
-    centers = []
-    for vertices, circular in loops:
-        normal = mathutils.Vector()
-        center = mathutils.Vector()
-        for vertex in vertices:
-            normal += mesh.vertices[vertex].normal
-            center += mesh.vertices[vertex].co
-        normals.append(normal / len(vertices) / 10)
-        centers.append(center / len(vertices))
-    
-    # possible matches if loop normals are faced towards the center
-    # of the other loop
-    matches = dict([[i, []] for i in range(len(loops))])
-    matches_amount = 0
-    for i in range(len(loops) + 1):
-        for j in range(i+1, len(loops)):
-            if (centers[i] - centers[j]).length > (centers[i] - (centers[j] \
-            + normals[j])).length and (centers[j] - centers[i]).length > \
-            (centers[j] - (centers[i] + normals[i])).length:
-                matches_amount += 1
-                matches[i].append([(centers[i] - centers[j]).length, i, j])
-                matches[j].append([(centers[i] - centers[j]).length, j, i])
-    # if no loops face each other, just make matches between all the loops
-    if matches_amount == 0:
-        for i in range(len(loops) + 1):
-            for j in range(i+1, len(loops)):
-                matches[i].append([(centers[i] - centers[j]).length, i, j])
-                matches[j].append([(centers[i] - centers[j]).length, j, i])
-    for key, value in matches.items():
-        value.sort()
-    
-    # matches based on distance between centers and number of vertices in loops
-    new_order = []
-    for loop_index in range(len(loops)):
-        if loop_index in new_order:
-            continue
-        loop_matches = matches[loop_index]
-        if not loop_matches:
-            continue
-        shortest_distance = loop_matches[0][0]
-        shortest_distance *= 1.1
-        loop_matches = [[abs(len(loops[loop_index][0]) - \
-            len(loops[loop[2]][0])), loop[0], loop[1], loop[2]] for loop in \
-            loop_matches if loop[0] < shortest_distance]
-        loop_matches.sort()
-        for match in loop_matches:
-            if match[3] not in new_order:
-                new_order += [loop_index, match[3]]
-                break
-    
-    # reorder loops based on matches
-    if len(new_order) >= 2:
-        loops = [loops[i] for i in new_order]
-    
-    return(loops)
-
-
-# have normals of selection face outside
-def bridge_recalculate_normals():
-    bpy.ops.object.mode_set(mode = 'EDIT')
-    bpy.ops.mesh.normals_make_consistent()
-
-
-# remove old_selected_faces
-def bridge_remove_internal_faces(mesh, old_selected_faces):
-    select_mode = [i for i in bpy.context.tool_settings.mesh_select_mode]
-    bpy.context.tool_settings.mesh_select_mode = [False, False, True]
-    
-    # hack to keep track of the current selection
-    for edge in mesh.edges:
-        if edge.select and not edge.hide:
-            edge.bevel_weight = (edge.bevel_weight/3) + 0.2
-        else:
-            edge.bevel_weight = (edge.bevel_weight/3) + 0.6
-    
-    # remove faces
-    bpy.ops.object.mode_set(mode = 'EDIT')
-    bpy.ops.mesh.select_all(action = 'DESELECT')
-    bpy.ops.object.mode_set(mode = 'OBJECT')
-    for face in old_selected_faces:
-        mesh.faces[face].select = True
-    bpy.ops.object.mode_set(mode = 'EDIT')
-    bpy.ops.mesh.delete(type = 'FACE')
-    
-    # restore old selection, using hack
-    bpy.ops.object.mode_set(mode = 'OBJECT')
-    bpy.context.tool_settings.mesh_select_mode = [False, True, False]
-    for edge in mesh.edges:
-        if edge.bevel_weight < 0.6:
-            edge.bevel_weight = (edge.bevel_weight-0.2) * 3
-            edge.select = True
-        else:
-            edge.bevel_weight = (edge.bevel_weight-0.6) * 3
-    bpy.ops.object.mode_set(mode = 'EDIT')
-    bpy.ops.object.mode_set(mode = 'OBJECT')
-    bpy.context.tool_settings.mesh_select_mode = select_mode
-
-
-# update list of internal faces that are flagged for removal
-def bridge_save_unused_faces(mesh, old_selected_faces, loops):
-    # key: vertex index, value: lists of selected faces using it
-    vertex_to_face = dict([[i, []] for i in range(len(mesh.vertices))])
-    [[vertex_to_face[vertex_index].append(face) for vertex_index in \
-        mesh.faces[face].vertices] for face in old_selected_faces]
-    
-    # group selected faces that are connected
-    groups = []
-    grouped_faces = []
-    for face in old_selected_faces:
-        if face in grouped_faces:
-            continue
-        grouped_faces.append(face)
-        group = [face]
-        new_faces = [face]
-        while new_faces:
-            grow_face = new_faces[0]
-            for vertex in mesh.faces[grow_face].vertices:
-                vertex_face_group = [face for face in vertex_to_face[vertex] \
-                    if face not in grouped_faces]
-                new_faces += vertex_face_group
-                grouped_faces += vertex_face_group
-                group += vertex_face_group
-            new_faces.pop(0)
-        groups.append(group)
-    
-    # key: vertex index, value: True/False (is it in a loop that is used)
-    used_vertices = dict([[i, 0] for i in range(len(mesh.vertices))])
-    for loop in loops:
-        for vertex in loop[0]:
-            used_vertices[vertex] = True
-    
-    # check if group is bridged, if not remove faces from internal faces list
-    for group in groups:
-        used = False
-        for face in group:
-            if used:
-                break
-            for vertex in mesh.faces[face].vertices:
-                if used_vertices[vertex]:
-                    used = True
-                    break
-        if not used:
-            for face in group:
-                old_selected_faces.remove(face)
-
-
-# add the newly created faces to the selection
-def bridge_select_new_faces(mesh, amount, smooth):
-    select_mode = [i for i in bpy.context.tool_settings.mesh_select_mode]
-    bpy.context.tool_settings.mesh_select_mode = [False, False, True]
-    for i in range(amount):
-        mesh.faces[-(i+1)].select = True
-        mesh.faces[-(i+1)].use_smooth = smooth
-    bpy.ops.object.mode_set(mode = 'EDIT')
-    bpy.ops.object.mode_set(mode = 'OBJECT')
-    bpy.context.tool_settings.mesh_select_mode = select_mode
-
-
-# sort loops, so they are connected in the correct order when lofting
-def bridge_sort_loops(mesh, loops, loft_loop):
-    # simplify loops to single points, and prepare for pathfinding
-    x, y, z = [[sum([mesh.vertices[i].co[j] for i in loop[0]]) / \
-        len(loop[0]) for loop in loops] for j in range(3)]
-    nodes = [mathutils.Vector((x[i], y[i], z[i])) for i in range(len(loops))]
-    
-    active_node = 0
-    open = [i for i in range(1, len(loops))]
-    path = [[0,0]]
-    # connect node to path, that is shortest to active_node
-    while len(open) > 0:
-        distances = [(nodes[active_node] - nodes[i]).length for i in open]
-        active_node = open[distances.index(min(distances))]
-        open.remove(active_node)
-        path.append([active_node, min(distances)])
-    # check if we didn't start in the middle of the path
-    for i in range(2, len(path)):
-        if (nodes[path[i][0]]-nodes[0]).length < path[i][1]:
-            temp = path[:i]
-            path.reverse()
-            path = path[:-i] + temp
-            break
-    
-    # reorder loops
-    loops = [loops[i[0]] for i in path]
-    # if requested, duplicate first loop at last position, so loft can loop
-    if loft_loop:
-        loops = loops + [loops[0]]
-    
-    return(loops)
-
-
-##########################################
-####### Circle functions #################
-##########################################
-
-# convert 3d coordinates to 2d coordinates on plane
-def circle_3d_to_2d(mesh_mod, loop, com, normal):
-    # project vertices onto the plane
-    verts = [mesh_mod.vertices[v] for v in loop[0]]
-    verts_projected = [[v.co - (v.co - com).dot(normal) * normal, v.index]
-                       for v in verts]
-
-    # calculate two vectors (p and q) along the plane
-    m = mathutils.Vector((normal[0] + 1.0, normal[1], normal[2]))
-    p = m - (m.dot(normal) * normal)
-    if p.dot(p) == 0.0:
-        m = mathutils.Vector((normal[0], normal[1] + 1.0, normal[2]))
-        p = m - (m.dot(normal) * normal)
-    q = p.cross(normal)
-    
-    # change to 2d coordinates using perpendicular projection
-    locs_2d = []
-    for loc, vert in verts_projected:
-        vloc = loc - com
-        x = p.dot(vloc) / p.dot(p)
-        y = q.dot(vloc) / q.dot(q)
-        locs_2d.append([x, y, vert])
-    
-    return(locs_2d, p, q)
-
-
-# calculate a best-fit circle to the 2d locations on the plane
-def circle_calculate_best_fit(locs_2d):
-    # initial guess
-    x0 = 0.0
-    y0 = 0.0
-    r = 1.0
-    
-    # calculate center and radius (non-linear least squares solution)
-    for iter in range(500):
-        jmat = []
-        k = []
-        for v in locs_2d:
-            d = (v[0]**2-2.0*x0*v[0]+v[1]**2-2.0*y0*v[1]+x0**2+y0**2)**0.5
-            jmat.append([(x0-v[0])/d, (y0-v[1])/d, -1.0])
-            k.append(-(((v[0]-x0)**2+(v[1]-y0)**2)**0.5-r))
-        jmat2 = mathutils.Matrix(((0.0, 0.0, 0.0),
-                                  (0.0, 0.0, 0.0),
-                                  (0.0, 0.0, 0.0),
-                                  ))
-        k2 = mathutils.Vector((0.0, 0.0, 0.0))
-        for i in range(len(jmat)):
-            k2 += mathutils.Vector(jmat[i])*k[i]
-            jmat2[0][0] += jmat[i][0]**2
-            jmat2[1][0] += jmat[i][0]*jmat[i][1]
-            jmat2[2][0] += jmat[i][0]*jmat[i][2]
-            jmat2[1][1] += jmat[i][1]**2
-            jmat2[2][1] += jmat[i][1]*jmat[i][2]
-            jmat2[2][2] += jmat[i][2]**2
-        jmat2[0][1] = jmat2[1][0]
-        jmat2[0][2] = jmat2[2][0]
-        jmat2[1][2] = jmat2[2][1]
-        try:
-            jmat2.invert()
-        except:
-            pass
-        dx0, dy0, dr = jmat2 * k2
-        x0 += dx0
-        y0 += dy0
-        r += dr
-        # stop iterating if we're close enough to optimal solution
-        if abs(dx0)<1e-6 and abs(dy0)<1e-6 and abs(dr)<1e-6:
-            break
-    
-    # return center of circle and radius
-    return(x0, y0, r)
-
-
-# calculate circle so no vertices have to be moved away from the center
-def circle_calculate_min_fit(locs_2d):
-    # center of circle
-    x0 = (min([i[0] for i in locs_2d])+max([i[0] for i in locs_2d]))/2.0
-    y0 = (min([i[1] for i in locs_2d])+max([i[1] for i in locs_2d]))/2.0
-    center = mathutils.Vector([x0, y0])
-    # radius of circle
-    r = min([(mathutils.Vector([i[0], i[1]])-center).length for i in locs_2d])
-    
-    # return center of circle and radius
-    return(x0, y0, r)
-
-
-# calculate the new locations of the vertices that need to be moved
-def circle_calculate_verts(flatten, mesh_mod, locs_2d, com, p, q, normal):
-    # changing 2d coordinates back to 3d coordinates
-    locs_3d = []
-    for loc in locs_2d:
-        locs_3d.append([loc[2], loc[0]*p + loc[1]*q + com])
-    
-    if flatten: # flat circle
-        return(locs_3d)
-    
-    else: # project the locations on the existing mesh
-        vert_edges = dict_vert_edges(mesh_mod)
-        vert_faces = dict_vert_faces(mesh_mod)
-        faces = [f for f in mesh_mod.faces if not f.hide]
-        rays = [normal, -normal]
-        new_locs = []
-        for loc in locs_3d:
-            projection = False
-            if mesh_mod.vertices[loc[0]].co == loc[1]: # vertex hasn't moved
-                projection = loc[1]
-            else:
-                dif = normal.angle(loc[1]-mesh_mod.vertices[loc[0]].co)
-                if -1e-6 < dif < 1e-6 or math.pi-1e-6 < dif < math.pi+1e-6:
-                    # original location is already along projection normal
-                    projection = mesh_mod.vertices[loc[0]].co
-                else:
-                    # quick search through adjacent faces
-                    for face in vert_faces[loc[0]]:
-                        verts = [mesh_mod.vertices[v].co for v in \
-                            mesh_mod.faces[face].vertices]
-                        if len(verts) == 3: # triangle
-                            v1, v2, v3 = verts
-                            v4 = False
-                        else: # quad
-                            v1, v2, v3, v4 = verts
-                        for ray in rays:
-                            intersect = mathutils.geometry.\
-                            intersect_ray_tri(v1, v2, v3, ray, loc[1])
-                            if intersect:
-                                projection = intersect
-                                break
-                            elif v4:
-                                intersect = mathutils.geometry.\
-                                intersect_ray_tri(v1, v3, v4, ray, loc[1])
-                                if intersect:
-                                    projection = intersect
-                                    break
-                        if projection:
-                            break
-            if not projection:
-                # check if projection is on adjacent edges
-                for edgekey in vert_edges[loc[0]]:
-                    line1 = mesh_mod.vertices[edgekey[0]].co
-                    line2 = mesh_mod.vertices[edgekey[1]].co
-                    intersect, dist = mathutils.geometry.intersect_point_line(\
-                        loc[1], line1, line2)
-                    if 1e-6 < dist < 1 - 1e-6:
-                        projection = intersect
-                        break
-            if not projection:
-                # full search through the entire mesh
-                hits = []
-                for face in faces:
-                    verts = [mesh_mod.vertices[v].co for v in face.vertices]
-                    if len(verts) == 3: # triangle
-                        v1, v2, v3 = verts
-                        v4 = False
-                    else: # quad
-                        v1, v2, v3, v4 = verts
-                    for ray in rays:
-                        intersect = mathutils.geometry.intersect_ray_tri(\
-                            v1, v2, v3, ray, loc[1])
-                        if intersect:
-                            hits.append([(loc[1] - intersect).length,
-                                intersect])
-                            break
-                        elif v4:
-                            intersect = mathutils.geometry.intersect_ray_tri(\
-                                v1, v3, v4, ray, loc[1])
-                            if intersect:
-                                hits.append([(loc[1] - intersect).length,
-                                    intersect])
-                                break
-                if len(hits) >= 1:
-                    # if more than 1 hit with mesh, closest hit is new loc
-                    hits.sort()
-                    projection = hits[0][1]
-            if not projection:
-                # nothing to project on, remain at flat location
-                projection = loc[1]
-            new_locs.append([loc[0], projection])
-        
-        # return new positions of projected circle
-        return(new_locs)
-
-
-# check loops and only return valid ones
-def circle_check_loops(single_loops, loops, mapping, mesh_mod):
-    valid_single_loops = {}
-    valid_loops = []
-    for i, [loop, circular] in enumerate(loops):
-        # loop needs to have at least 3 vertices
-        if len(loop) < 3:
-            continue
-        # loop needs at least 1 vertex in the original, non-mirrored mesh
-        if mapping:
-            all_virtual = True
-            for vert in loop:
-                if mapping[vert] > -1:
-                    all_virtual = False
-                    break
-            if all_virtual:
-                continue
-        # loop has to be non-collinear
-        collinear = True
-        loc0 = mathutils.Vector(mesh_mod.vertices[loop[0]].co[:])
-        loc1 = mathutils.Vector(mesh_mod.vertices[loop[1]].co[:])
-        for v in loop[2:]:
-            locn = mathutils.Vector(mesh_mod.vertices[v].co[:])
-            if loc0 == loc1 or loc1 == locn:
-                loc0 = loc1
-                loc1 = locn
-                continue
-            d1 = loc1-loc0
-            d2 = locn-loc1
-            if -1e-6 < d1.angle(d2, 0) < 1e-6:
-                loc0 = loc1
-                loc1 = locn
-                continue
-            collinear = False
-            break
-        if collinear:
-            continue
-        # passed all tests, loop is valid
-        valid_loops.append([loop, circular])
-        valid_single_loops[len(valid_loops)-1] = single_loops[i]
-    
-    return(valid_single_loops, valid_loops)
-
-
-# calculate the location of single input vertices that need to be flattened
-def circle_flatten_singles(mesh_mod, com, p, q, normal, single_loop):
-    new_locs = []
-    for vert in single_loop:
-        loc = mathutils.Vector(mesh_mod.vertices[vert].co[:])
-        new_locs.append([vert,  loc - (loc-com).dot(normal)*normal])
-    
-    return(new_locs)
-
-
-# calculate input loops
-def circle_get_input(object, mesh, scene):
-    # get mesh with modifiers applied
-    derived, mesh_mod = get_derived_mesh(object, mesh, scene)
-    
-    # create list of edge-keys based on selection state
-    faces = False
-    for face in mesh.faces:
-        if face.select and not face.hide:
-            faces = True
-            break
-    if faces:
-        # get selected, non-hidden , non-internal edge-keys
-        eks_selected = [key for keys in [face.edge_keys for face in \
-            mesh_mod.faces if face.select and not face.hide] for key in keys]
-        edge_count = {}
-        for ek in eks_selected:
-            if ek in edge_count:
-                edge_count[ek] += 1
-            else:
-                edge_count[ek] = 1
-        edge_keys = [edge.key for edge in mesh_mod.edges if edge.select \
-            and not edge.hide and edge_count.get(edge.key, 1)==1]
-    else:
-        # no faces, so no internal edges either
-        edge_keys = [edge.key for edge in mesh_mod.edges if edge.select \
-            and not edge.hide]
-    
-    # add edge-keys around single vertices
-    verts_connected = dict([[vert, 1] for edge in [edge for edge in \
-        mesh_mod.edges if edge.select and not edge.hide] for vert in edge.key])
-    single_vertices = [vert.index for vert in mesh_mod.vertices if \
-        vert.select and not vert.hide and not \
-        verts_connected.get(vert.index, False)]
-    
-    if single_vertices and len(mesh.faces)>0:
-        vert_to_single = dict([[v.index, []] for v in mesh_mod.vertices \
-            if not v.hide])
-        for face in [face for face in mesh_mod.faces if not face.select \
-        and not face.hide]:
-            for vert in face.vertices:
-                if vert in single_vertices:
-                    for ek in face.edge_keys:
-                        if not vert in ek:
-                            edge_keys.append(ek)
-                            if vert not in vert_to_single[ek[0]]:
-                                vert_to_single[ek[0]].append(vert)
-                            if vert not in vert_to_single[ek[1]]:
-                                vert_to_single[ek[1]].append(vert)
-                    break
-    
-    # sort edge-keys into loops
-    loops = get_connected_selections(edge_keys)
-    
-    # find out to which loops the single vertices belong
-    single_loops = dict([[i, []] for i in range(len(loops))])
-    if single_vertices and len(mesh.faces)>0:
-        for i, [loop, circular] in enumerate(loops):
-            for vert in loop:
-                if vert_to_single[vert]:
-                    for single in vert_to_single[vert]:
-                        if single not in single_loops[i]:
-                            single_loops[i].append(single)
-    
-    return(derived, mesh_mod, single_vertices, single_loops, loops)
-
-
-# recalculate positions based on the influence of the circle shape
-def circle_influence_locs(locs_2d, new_locs_2d, influence):
-    for i in range(len(locs_2d)):
-        oldx, oldy, j = locs_2d[i]
-        newx, newy, k = new_locs_2d[i]
-        altx = newx*(influence/100)+ oldx*((100-influence)/100)
-        alty = newy*(influence/100)+ oldy*((100-influence)/100)
-        locs_2d[i] = [altx, alty, j]
-    
-    return(locs_2d)
-
-
-# project 2d locations on circle, respecting distance relations between verts
-def circle_project_non_regular(locs_2d, x0, y0, r):
-    for i in range(len(locs_2d)):
-        x, y, j = locs_2d[i]
-        loc = mathutils.Vector([x-x0, y-y0])
-        loc.length = r
-        locs_2d[i] = [loc[0], loc[1], j]
-    
-    return(locs_2d)
-
-
-# project 2d locations on circle, with equal distance between all vertices
-def circle_project_regular(locs_2d, x0, y0, r):
-    # find offset angle and circling direction
-    x, y, i = locs_2d[0]
-    loc = mathutils.Vector([x-x0, y-y0])
-    loc.length = r
-    offset_angle = loc.angle(mathutils.Vector([1.0, 0.0]), 0.0)
-    loca = mathutils.Vector([x-x0, y-y0, 0.0])
-    if loc[1] < -1e-6:
-        offset_angle *= -1
-    x, y, j = locs_2d[1]
-    locb = mathutils.Vector([x-x0, y-y0, 0.0])
-    if loca.cross(locb)[2] >= 0:
-        ccw = 1
-    else:
-        ccw = -1
-    # distribute vertices along the circle
-    for i in range(len(locs_2d)):
-        t = offset_angle + ccw * (i / len(locs_2d) * 2 * math.pi)
-        x = math.cos(t) * r
-        y = math.sin(t) * r
-        locs_2d[i] = [x, y, locs_2d[i][2]]
-    
-    return(locs_2d)
-
-
-# shift loop, so the first vertex is closest to the center
-def circle_shift_loop(mesh_mod, loop, com):
-    verts, circular = loop
-    distances = [[(mesh_mod.vertices[vert].co - com).length, i] \
-        for i, vert in enumerate(verts)]
-    distances.sort()
-    shift = distances[0][1]
-    loop = [verts[shift:] + verts[:shift], circular]
-    
-    return(loop)
-
-
-##########################################
-####### Curve functions ##################
-##########################################
-
-# create lists with knots and points, all correctly sorted
-def curve_calculate_knots(loop, verts_selected):
-    knots = [v for v in loop[0] if v in verts_selected]
-    points = loop[0][:]
-    # circular loop, potential for weird splines
-    if loop[1]:
-        offset = int(len(loop[0]) / 4)
-        kpos = []
-        for k in knots:
-            kpos.append(loop[0].index(k))
-        kdif = []
-        for i in range(len(kpos) - 1):
-            kdif.append(kpos[i+1] - kpos[i])
-        kdif.append(len(loop[0]) - kpos[-1] + kpos[0])
-        kadd = []
-        for k in kdif:
-            if k > 2 * offset:
-                kadd.append([kdif.index(k), True])
-            # next 2 lines are optional, they insert
-            # an extra control point in small gaps
-            #elif k > offset:
-            #   kadd.append([kdif.index(k), False])
-        kins = []
-        krot = False
-        for k in kadd: # extra knots to be added
-            if k[1]: # big gap (break circular spline)
-                kpos = loop[0].index(knots[k[0]]) + offset
-                if kpos > len(loop[0]) - 1:
-                    kpos -= len(loop[0])
-                kins.append([knots[k[0]], loop[0][kpos]])
-                kpos2 = k[0] + 1
-                if kpos2 > len(knots)-1:
-                    kpos2 -= len(knots)
-                kpos2 = loop[0].index(knots[kpos2]) - offset
-                if kpos2 < 0:
-                    kpos2 += len(loop[0])
-                kins.append([loop[0][kpos], loop[0][kpos2]])
-                krot = loop[0][kpos2]
-            else: # small gap (keep circular spline)
-                k1 = loop[0].index(knots[k[0]])
-                k2 = k[0] + 1
-                if k2 > len(knots)-1:
-                    k2 -= len(knots)
-                k2 = loop[0].index(knots[k2])
-                if k2 < k1:
-                    dif = len(loop[0]) - 1 - k1 + k2
-                else:
-                    dif = k2 - k1
-                kn = k1 + int(dif/2)
-                if kn > len(loop[0]) - 1:
-                    kn -= len(loop[0])
-                kins.append([loop[0][k1], loop[0][kn]])
-        for j in kins: # insert new knots
-            knots.insert(knots.index(j[0]) + 1, j[1])
-        if not krot: # circular loop
-            knots.append(knots[0])
-            points = loop[0][loop[0].index(knots[0]):]
-            points += loop[0][0:loop[0].index(knots[0]) + 1]
-        else: # non-circular loop (broken by script)
-            krot = knots.index(krot)
-            knots = knots[krot:] + knots[0:krot]
-            if loop[0].index(knots[0]) > loop[0].index(knots[-1]):
-                points = loop[0][loop[0].index(knots[0]):]
-                points += loop[0][0:loop[0].index(knots[-1])+1]
-            else:
-                points = loop[0][loop[0].index(knots[0]):\
-                    loop[0].index(knots[-1]) + 1]
-    # non-circular loop, add first and last point as knots
-    else:
-        if loop[0][0] not in knots:
-            knots.insert(0, loop[0][0])
-        if loop[0][-1] not in knots:
-            knots.append(loop[0][-1])
-    
-    return(knots, points)
-
-
-# calculate relative positions compared to first knot
-def curve_calculate_t(mesh_mod, knots, points, pknots, regular, circular):
-    tpoints = []
-    loc_prev = False
-    len_total = 0
-    
-    for p in points:
-        if p in knots:
-            loc = pknots[knots.index(p)] # use projected knot location
-        else:
-            loc = mathutils.Vector(mesh_mod.vertices[p].co[:])
-        if not loc_prev:
-            loc_prev = loc
-        len_total += (loc-loc_prev).length
-        tpoints.append(len_total)
-        loc_prev = loc
-    tknots = []
-    for p in points:
-        if p in knots:
-            tknots.append(tpoints[points.index(p)])
-    if circular:
-        tknots[-1] = tpoints[-1]
-    
-    # regular option
-    if regular:
-        tpoints_average = tpoints[-1] / (len(tpoints) - 1)
-        for i in range(1, len(tpoints) - 1):
-            tpoints[i] = i * tpoints_average
-        for i in range(len(knots)):
-            tknots[i] = tpoints[points.index(knots[i])]
-        if circular:
-            tknots[-1] = tpoints[-1]
-    
-    
-    return(tknots, tpoints)
-
-
-# change the location of non-selected points to their place on the spline
-def curve_calculate_vertices(mesh_mod, knots, tknots, points, tpoints, splines,
-interpolation, restriction):
-    newlocs = {}
-    move = []
-    
-    for p in points:
-        if p in knots:
-            continue
-        m = tpoints[points.index(p)]
-        if m in tknots:
-            n = tknots.index(m)
-        else:
-            t = tknots[:]
-            t.append(m)
-            t.sort()
-            n = t.index(m) - 1
-        if n > len(splines) - 1:
-            n = len(splines) - 1
-        elif n < 0:
-            n = 0
-        
-        if interpolation == 'cubic':
-            ax, bx, cx, dx, tx = splines[n][0]
-            x = ax + bx*(m-tx) + cx*(m-tx)**2 + dx*(m-tx)**3
-            ay, by, cy, dy, ty = splines[n][1]
-            y = ay + by*(m-ty) + cy*(m-ty)**2 + dy*(m-ty)**3
-            az, bz, cz, dz, tz = splines[n][2]
-            z = az + bz*(m-tz) + cz*(m-tz)**2 + dz*(m-tz)**3
-            newloc = mathutils.Vector([x,y,z])
-        else: # interpolation == 'linear'
-            a, d, t, u = splines[n]
-            newloc = ((m-t)/u)*d + a
-
-        if restriction != 'none': # vertex movement is restricted
-            newlocs[p] = newloc
-        else: # set the vertex to its new location
-            move.append([p, newloc])
-        
-    if restriction != 'none': # vertex movement is restricted
-        for p in points:
-            if p in newlocs:
-                newloc = newlocs[p]
-            else:
-                move.append([p, mesh_mod.vertices[p].co])
-                continue
-            oldloc = mesh_mod.vertices[p].co
-            normal = mesh_mod.vertices[p].normal
-            dloc = newloc - oldloc
-            if dloc.length < 1e-6:
-                move.append([p, newloc])
-            elif restriction == 'extrude': # only extrusions
-                if dloc.angle(normal, 0) < 0.5 * math.pi + 1e-6:
-                    move.append([p, newloc])
-            else: # restriction == 'indent' only indentations
-                if dloc.angle(normal) > 0.5 * math.pi - 1e-6:
-                    move.append([p, newloc])
-
-    return(move)
-
-
-# trim loops to part between first and last selected vertices (including)
-def curve_cut_boundaries(mesh_mod, loops):
-    cut_loops = []
-    for loop, circular in loops:
-        if circular:
-            # don't cut
-            cut_loops.append([loop, circular])
-            continue
-        selected = [mesh_mod.vertices[v].select for v in loop]
-        first = selected.index(True)
-        selected.reverse()
-        last = -selected.index(True)
-        if last == 0:
-            cut_loops.append([loop[first:], circular])
-        else:
-            cut_loops.append([loop[first:last], circular])
-    
-    return(cut_loops)
-
-
-# calculate input loops
-def curve_get_input(object, mesh, boundaries, scene):
-    # get mesh with modifiers applied
-    derived, mesh_mod = get_derived_mesh(object, mesh, scene)
-    
-    # vertices that still need a loop to run through it
-    verts_unsorted = [v.index for v in mesh_mod.vertices if \
-        v.select and not v.hide]
-    # necessary dictionaries
-    vert_edges = dict_vert_edges(mesh_mod)
-    edge_faces = dict_edge_faces(mesh_mod)
-    correct_loops = []
-    
-    # find loops through each selected vertex
-    while len(verts_unsorted) > 0:
-        loops = curve_vertex_loops(mesh_mod, verts_unsorted[0], vert_edges,
-            edge_faces)
-        verts_unsorted.pop(0)
-        
-        # check if loop is fully selected
-        search_perpendicular = False
-        i = -1
-        for loop, circular in loops:
-            i += 1
-            selected = [v for v in loop if mesh_mod.vertices[v].select]
-            if len(selected) < 2:
-                # only one selected vertex on loop, don't use
-                loops.pop(i)
-                continue
-            elif len(selected) == len(loop):
-                search_perpendicular = loop
-                break
-        # entire loop is selected, find perpendicular loops
-        if search_perpendicular:
-            for vert in loop:
-                if vert in verts_unsorted:
-                    verts_unsorted.remove(vert)
-            perp_loops = curve_perpendicular_loops(mesh_mod, loop,
-                vert_edges, edge_faces)
-            for perp_loop in perp_loops:
-                correct_loops.append(perp_loop)
-        # normal input
-        else:
-            for loop, circular in loops:
-                correct_loops.append([loop, circular])
-    
-    # boundaries option
-    if boundaries:
-        correct_loops = curve_cut_boundaries(mesh_mod, correct_loops)
-    
-    return(derived, mesh_mod, correct_loops)
-
-
-# return all loops that are perpendicular to the given one
-def curve_perpendicular_loops(mesh_mod, start_loop, vert_edges, edge_faces):
-    # find perpendicular loops
-    perp_loops = []
-    for start_vert in start_loop:
-        loops = curve_vertex_loops(mesh_mod, start_vert, vert_edges,
-            edge_faces)
-        for loop, circular in loops:
-            selected = [v for v in loop if mesh_mod.vertices[v].select]
-            if len(selected) == len(loop):
-                continue
-            else:
-                perp_loops.append([loop, circular, loop.index(start_vert)])
-    
-    # trim loops to same lengths
-    shortest = [[len(loop[0]), i] for i, loop in enumerate(perp_loops)\
-        if not loop[1]]
-    if not shortest:
-        # all loops are circular, not trimming
-        return([[loop[0], loop[1]] for loop in perp_loops])
-    else:
-        shortest = min(shortest)
-    shortest_start = perp_loops[shortest[1]][2]
-    before_start = shortest_start
-    after_start = shortest[0] - shortest_start - 1
-    bigger_before = before_start > after_start
-    trimmed_loops = []
-    for loop in perp_loops:
-        # have the loop face the same direction as the shortest one
-        if bigger_before:
-            if loop[2] < len(loop[0]) / 2:
-                loop[0].reverse()
-                loop[2] = len(loop[0]) - loop[2] - 1
-        else:
-            if loop[2] > len(loop[0]) / 2:
-                loop[0].reverse()
-                loop[2] = len(loop[0]) - loop[2] - 1
-        # circular loops can shift, to prevent wrong trimming
-        if loop[1]:
-            shift = shortest_start - loop[2]
-            if loop[2] + shift > 0 and loop[2] + shift < len(loop[0]):
-                loop[0] = loop[0][-shift:] + loop[0][:-shift]
-            loop[2] += shift
-            if loop[2] < 0:
-                loop[2] += len(loop[0])
-            elif loop[2] > len(loop[0]) -1:
-                loop[2] -= len(loop[0])
-        # trim
-        start = max(0, loop[2] - before_start)
-        end = min(len(loop[0]), loop[2] + after_start + 1)
-        trimmed_loops.append([loop[0][start:end], False])
-    
-    return(trimmed_loops)
-
-
-# project knots on non-selected geometry
-def curve_project_knots(mesh_mod, verts_selected, knots, points, circular):
-    # function to project vertex on edge
-    def project(v1, v2, v3):
-        # v1 and v2 are part of a line
-        # v3 is projected onto it
-        v2 -= v1
-        v3 -= v1
-        p = v3.project(v2)
-        return(p + v1)
-    
-    if circular: # project all knots
-        start = 0
-        end = len(knots)
-        pknots = []
-    else: # first and last knot shouldn't be projected
-        start = 1
-        end = -1
-        pknots = [mathutils.Vector(mesh_mod.vertices[knots[0]].co[:])]
-    for knot in knots[start:end]:
-        if knot in verts_selected:
-            knot_left = knot_right = False
-            for i in range(points.index(knot)-1, -1*len(points), -1):
-                if points[i] not in knots:
-                    knot_left = points[i]
-                    break
-            for i in range(points.index(knot)+1, 2*len(points)):
-                if i > len(points) - 1:
-                    i -= len(points)
-                if points[i] not in knots:
-                    knot_right = points[i]
-                    break
-            if knot_left and knot_right and knot_left != knot_right:
-                knot_left = mathutils.Vector(\
-                    mesh_mod.vertices[knot_left].co[:])
-                knot_right = mathutils.Vector(\
-                    mesh_mod.vertices[knot_right].co[:])
-                knot = mathutils.Vector(mesh_mod.vertices[knot].co[:])
-                pknots.append(project(knot_left, knot_right, knot))
-            else:
-                pknots.append(mathutils.Vector(mesh_mod.vertices[knot].co[:]))
-        else: # knot isn't selected, so shouldn't be changed
-            pknots.append(mathutils.Vector(mesh_mod.vertices[knot].co[:]))
-    if not circular:
-        pknots.append(mathutils.Vector(mesh_mod.vertices[knots[-1]].co[:]))
-    
-    return(pknots)
-
-
-# find all loops through a given vertex
-def curve_vertex_loops(mesh_mod, start_vert, vert_edges, edge_faces):
-    edges_used = []
-    loops = []
-        
-    for edge in vert_edges[start_vert]:
-        if edge in edges_used:
-            continue
-        loop = []
-        circular = False
-        for vert in edge:
-            active_faces = edge_faces[edge]
-            new_vert = vert
-            growing = True
-            while growing:
-                growing = False
-                new_edges = vert_edges[new_vert]
-                loop.append(new_vert)
-                if len(loop) > 1:
-                    edges_used.append(tuple(sorted([loop[-1], loop[-2]])))
-                if len(new_edges) < 3 or len(new_edges) > 4:
-                    # pole
-                    break
-                else:
-                    # find next edge
-                    for new_edge in new_edges:
-                        if new_edge in edges_used:
-                            continue
-                        eliminate = False
-                        for new_face in edge_faces[new_edge]:
-                            if new_face in active_faces:
-                                eliminate = True
-                                break
-                        if eliminate:
-                            continue
-                        # found correct new edge
-                        active_faces = edge_faces[new_edge]
-                        v1, v2 = new_edge
-                        if v1 != new_vert:
-                            new_vert = v1
-                        else:
-                            new_vert = v2
-                        if new_vert == loop[0]:
-                            circular = True
-                        else:
-                            growing = True
-                        break
-            if circular:
-                break
-            loop.reverse()
-        loops.append([loop, circular])
-    
-    return(loops)
-
-
-##########################################
-####### Flatten functions ################
-##########################################
-
-# sort input into loops
-def flatten_get_input(mesh):
-    vert_verts = dict_vert_verts([edge.key for edge in mesh.edges \
-        if edge.select and not edge.hide])
-    verts = [v.index for v in mesh.vertices if v.select and not v.hide]
-    
-    # no connected verts, consider all selected verts as a single input
-    if not vert_verts:
-        return([[verts, False]])
-    
-    loops = []
-    while len(verts) > 0:
-        # start of loop
-        loop = [verts[0]]
-        verts.pop(0)
-        if loop[-1] in vert_verts:
-            to_grow = vert_verts[loop[-1]]
-        else:
-            to_grow = []
-        # grow loop
-        while len(to_grow) > 0:
-            new_vert = to_grow[0]
-            to_grow.pop(0)
-            if new_vert in loop:
-                continue
-            loop.append(new_vert)
-            verts.remove(new_vert)
-            to_grow += vert_verts[new_vert]
-        # add loop to loops
-        loops.append([loop, False])
-    
-    return(loops)
-
-
-# calculate position of vertex projections on plane
-def flatten_project(mesh, loop, com, normal):
-    verts = [mesh.vertices[v] for v in loop[0]]
-    verts_projected = [[v.index, mathutils.Vector(v.co[:]) - \
-        (mathutils.Vector(v.co[:])-com).dot(normal)*normal] for v in verts]
-    
-    return(verts_projected)
-
-
-##########################################
-####### Relax functions ##################
-##########################################
-
-# create lists with knots and points, all correctly sorted
-def relax_calculate_knots(loops):
-    all_knots = []
-    all_points = []
-    for loop, circular in loops:
-        knots = [[], []]
-        points = [[], []]
-        if circular:
-            if len(loop)%2 == 1: # odd
-                extend = [False, True, 0, 1, 0, 1]
-            else: # even
-                extend = [True, False, 0, 1, 1, 2]
-        else:
-            if len(loop)%2 == 1: # odd
-                extend = [False, False, 0, 1, 1, 2]
-            else: # even
-                extend = [False, False, 0, 1, 1, 2]
-        for j in range(2):
-            if extend[j]:
-                loop = [loop[-1]] + loop + [loop[0]]
-            for i in range(extend[2+2*j], len(loop), 2):
-                knots[j].append(loop[i])
-            for i in range(extend[3+2*j], len(loop), 2):
-                if loop[i] == loop[-1] and not circular:
-                    continue
-                if len(points[j]) == 0:
-                    points[j].append(loop[i])
-                elif loop[i] != points[j][0]:
-                    points[j].append(loop[i])
-            if circular:
-                if knots[j][0] != knots[j][-1]:
-                    knots[j].append(knots[j][0])
-        if len(points[1]) == 0:
-            knots.pop(1)
-            points.pop(1)
-        for k in knots:
-            all_knots.append(k)
-        for p in points:
-            all_points.append(p)
-    
-    return(all_knots, all_points)
-
-
-# calculate relative positions compared to first knot
-def relax_calculate_t(mesh_mod, knots, points, regular):
-    all_tknots = []
-    all_tpoints = []
-    for i in range(len(knots)):
-        amount = len(knots[i]) + len(points[i])
-        mix  = []
-        for j in range(amount):
-            if j%2 == 0:
-                mix.append([True, knots[i][round(j/2)]])
-            elif j == amount-1:
-                mix.append([True, knots[i][-1]])
-            else:
-                mix.append([False, points[i][int(j/2)]])
-        len_total = 0
-        loc_prev = False
-        tknots = []
-        tpoints = []
-        for m in mix:
-            loc = mathutils.Vector(mesh_mod.vertices[m[1]].co[:])
-            if not loc_prev:
-                loc_prev = loc
-            len_total += (loc - loc_prev).length
-            if m[0]:
-                tknots.append(len_total)
-            else:
-                tpoints.append(len_total)
-            loc_prev = loc
-        if regular:
-            tpoints = []
-            for p in range(len(points[i])):
-                tpoints.append((tknots[p] + tknots[p+1]) / 2)
-        all_tknots.append(tknots)
-        all_tpoints.append(tpoints)
-    
-    return(all_tknots, all_tpoints)
-
-
-# change the location of the points to their place on the spline
-def relax_calculate_verts(mesh_mod, interpolation, tknots, knots, tpoints,
-points, splines):
-    change = []
-    move = []
-    for i in range(len(knots)):
-        for p in points[i]:
-            m = tpoints[i][points[i].index(p)]
-            if m in tknots[i]:
-                n = tknots[i].index(m)
-            else:
-                t = tknots[i][:]
-                t.append(m)
-                t.sort()
-                n = t.index(m)-1
-            if n > len(splines[i]) - 1:
-                n = len(splines[i]) - 1
-            elif n < 0:
-                n = 0
-            
-            if interpolation == 'cubic':
-                ax, bx, cx, dx, tx = splines[i][n][0]
-                x = ax + bx*(m-tx) + cx*(m-tx)**2 + dx*(m-tx)**3
-                ay, by, cy, dy, ty = splines[i][n][1]
-                y = ay + by*(m-ty) + cy*(m-ty)**2 + dy*(m-ty)**3
-                az, bz, cz, dz, tz = splines[i][n][2]
-                z = az + bz*(m-tz) + cz*(m-tz)**2 + dz*(m-tz)**3
-                change.append([p, mathutils.Vector([x,y,z])])
-            else: # interpolation == 'linear'
-                a, d, t, u = splines[i][n]
-                if u == 0:
-                    u = 1e-8
-                change.append([p, ((m-t)/u)*d + a])
-    for c in change:
-        move.append([c[0], (mesh_mod.vertices[c[0]].co + c[1]) / 2])
-    
-    return(move)
-
-
-##########################################
-####### Space functions ##################
-##########################################
-
-# calculate relative positions compared to first knot
-def space_calculate_t(mesh_mod, knots):
-    tknots = []
-    loc_prev = False
-    len_total = 0
-    for k in knots:
-        loc = mathutils.Vector(mesh_mod.vertices[k].co[:])
-        if not loc_prev:
-            loc_prev = loc
-        len_total += (loc - loc_prev).length
-        tknots.append(len_total)
-        loc_prev = loc
-    amount = len(knots)
-    t_per_segment = len_total / (amount - 1)
-    tpoints = [i * t_per_segment for i in range(amount)]
-    
-    return(tknots, tpoints)
-
-
-# change the location of the points to their place on the spline
-def space_calculate_verts(mesh_mod, interpolation, tknots, tpoints, points,
-splines):
-    move = []
-    for p in points:
-        m = tpoints[points.index(p)]
-        if m in tknots:
-            n = tknots.index(m)
-        else:
-            t = tknots[:]
-            t.append(m)
-            t.sort()
-            n = t.index(m) - 1
-        if n > len(splines) - 1:
-            n = len(splines) - 1
-        elif n < 0:
-            n = 0
-        
-        if interpolation == 'cubic':
-            ax, bx, cx, dx, tx = splines[n][0]
-            x = ax + bx*(m-tx) + cx*(m-tx)**2 + dx*(m-tx)**3
-            ay, by, cy, dy, ty = splines[n][1]
-            y = ay + by*(m-ty) + cy*(m-ty)**2 + dy*(m-ty)**3
-            az, bz, cz, dz, tz = splines[n][2]
-            z = az + bz*(m-tz) + cz*(m-tz)**2 + dz*(m-tz)**3
-            move.append([p, mathutils.Vector([x,y,z])])
-        else: # interpolation == 'linear'
-            a, d, t, u = splines[n]
-            move.append([p, ((m-t)/u)*d + a])
-    
-    return(move)
-
-
-##########################################
-####### Operators ########################
-##########################################
-
-# bridge operator
-class Bridge(bpy.types.Operator):
-    bl_idname = 'mesh.looptools_bridge'
-    bl_label = "Bridge / Loft"
-    bl_description = "Bridge two, or loft several, loops of vertices"
-    bl_options = {'REGISTER', 'UNDO'}
-    
-    cubic_strength = bpy.props.FloatProperty(name = "Strength",
-        description = "Higher strength results in more fluid curves",
-        default = 1.0,
-        soft_min = -3.0,
-        soft_max = 3.0)
-    interpolation = bpy.props.EnumProperty(name = "Interpolation mode",
-        items = (('cubic', "Cubic", "Gives curved results"),
-            ('linear', "Linear", "Basic, fast, straight interpolation")),
-        description = "Interpolation mode: algorithm used when creating "\
-            "segments",
-        default = 'cubic')
-    loft = bpy.props.BoolProperty(name = "Loft",
-        description = "Loft multiple loops, instead of considering them as "\
-            "a multi-input for bridging",
-        default = False)
-    loft_loop = bpy.props.BoolProperty(name = "Loop",
-        description = "Connect the first and the last loop with each other",
-        default = False)
-    min_width = bpy.props.IntProperty(name = "Minimum width",
-        description = "Segments with an edge smaller than this are merged "\
-            "(compared to base edge)",
-        default = 0,
-        min = 0,
-        max = 100,
-        subtype = 'PERCENTAGE')
-    mode = bpy.props.EnumProperty(name = "Mode",
-        items = (('basic', "Basic", "Fast algorithm"), ('shortest',
-            "Shortest edge", "Slower algorithm with better vertex matching")),
-        description = "Algorithm used for bridging",
-        default = 'shortest')
-    remove_faces = bpy.props.BoolProperty(name = "Remove faces",
-        description = "Remove faces that are internal after bridging",
-        default = True)
-    reverse = bpy.props.BoolProperty(name = "Reverse",
-        description = "Manually override the direction in which the loops "\
-                      "are bridged. Only use if the tool gives the wrong " \
-                      "result",
-        default = False)
-    segments = bpy.props.IntProperty(name = "Segments",
-        description = "Number of segments used to bridge the gap "\
-            "(0 = automatic)",
-        default = 1,
-        min = 0,
-        soft_max = 20)
-    twist = bpy.props.IntProperty(name = "Twist",
-        description = "Twist what vertices are connected to each other",
-        default = 0)
-    
-    @classmethod
-    def poll(cls, context):
-        ob = context.active_object
-        return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-    
-    def draw(self, context):
-        layout = self.layout
-        #layout.prop(self, "mode") # no cases yet where 'basic' mode is needed
-        
-        # top row
-        col_top = layout.column(align=True)
-        row = col_top.row(align=True)
-        col_left = row.column(align=True)
-        col_right = row.column(align=True)
-        col_right.active = self.segments != 1
-        col_left.prop(self, "segments")
-        col_right.prop(self, "min_width", text="")
-        # bottom row
-        bottom_left = col_left.row()
-        bottom_left.active = self.segments != 1
-        bottom_left.prop(self, "interpolation", text="")
-        bottom_right = col_right.row()
-        bottom_right.active = self.interpolation == 'cubic'
-        bottom_right.prop(self, "cubic_strength")
-        # boolean properties
-        col_top.prop(self, "remove_faces")
-        if self.loft:
-            col_top.prop(self, "loft_loop")
-        
-        # override properties
-        col_top.separator()
-        row = layout.row(align = True)
-        row.prop(self, "twist")
-        row.prop(self, "reverse")
-    
-    def invoke(self, context, event):
-        # load custom settings
-        context.window_manager.looptools.bridge_loft = self.loft
-        settings_load(self)
-        return self.execute(context)
-    
-    def execute(self, context):
-        # initialise
-        global_undo, object, mesh = initialise()
-        edge_faces, edgekey_to_edge, old_selected_faces, smooth = \
-            bridge_initialise(mesh, self.interpolation)
-        settings_write(self)
-        
-        # check cache to see if we can save time
-        input_method = bridge_input_method(self.loft, self.loft_loop)
-        cached, single_loops, loops, derived, mapping = cache_read("Bridge",
-            object, mesh, input_method, False)
-        if not cached:
-            # get loops
-            loops = bridge_get_input(mesh)
-            if loops:
-                # reorder loops if there are more than 2
-                if len(loops) > 2:
-                    if self.loft:
-                        loops = bridge_sort_loops(mesh, loops, self.loft_loop)
-                    else:
-                        loops = bridge_match_loops(mesh, loops)
-        
-        # saving cache for faster execution next time
-        if not cached:
-            cache_write("Bridge", object, mesh, input_method, False, False,
-                loops, False, False)
-        
-        if loops:
-            # calculate new geometry
-            vertices = []
-            faces = []
-            max_vert_index = len(mesh.vertices)-1
-            for i in range(1, len(loops)):
-                if not self.loft and i%2 == 0:
-                    continue
-                lines = bridge_calculate_lines(mesh, loops[i-1:i+1],
-                    self.mode, self.twist, self.reverse)
-                vertex_normals = bridge_calculate_virtual_vertex_normals(mesh,
-                    lines, loops[i-1:i+1], edge_faces, edgekey_to_edge)
-                segments = bridge_calculate_segments(mesh, lines,
-                    loops[i-1:i+1], self.segments)
-                new_verts, new_faces, max_vert_index = \
-                    bridge_calculate_geometry(mesh, lines, vertex_normals,
-                    segments, self.interpolation, self.cubic_strength,
-                    self.min_width, max_vert_index)
-                if new_verts:
-                    vertices += new_verts
-                if new_faces:
-                    faces += new_faces
-            # make sure faces in loops that aren't used, aren't removed
-            if self.remove_faces and old_selected_faces:
-                bridge_save_unused_faces(mesh, old_selected_faces, loops)
-            # create vertices
-            if vertices:
-                bridge_create_vertices(mesh, vertices)
-            # create faces
-            if faces:
-                bridge_create_faces(mesh, faces, self.twist)
-                bridge_select_new_faces(mesh, len(faces), smooth)
-            # edge-data could have changed, can't use cache next run
-            if faces and not vertices:
-                cache_delete("Bridge")
-            # delete internal faces
-            if self.remove_faces and old_selected_faces:
-                bridge_remove_internal_faces(mesh, old_selected_faces)
-            # make sure normals are facing outside
-            bridge_recalculate_normals()
-        
-        terminate(global_undo)
-        return{'FINISHED'}
-
-
-# circle operator
-class Circle(bpy.types.Operator):
-    bl_idname = "mesh.looptools_circle"
-    bl_label = "Circle"
-    bl_description = "Move selected vertices into a circle shape"
-    bl_options = {'REGISTER', 'UNDO'}
-    
-    custom_radius = bpy.props.BoolProperty(name = "Radius",
-        description = "Force a custom radius",
-        default = False)
-    fit = bpy.props.EnumProperty(name = "Method",
-        items = (("best", "Best fit", "Non-linear least squares"),
-            ("inside", "Fit inside","Only move vertices towards the center")),
-        description = "Method used for fitting a circle to the vertices",
-        default = 'best')
-    flatten = bpy.props.BoolProperty(name = "Flatten",
-        description = "Flatten the circle, instead of projecting it on the " \
-            "mesh",
-        default = True)
-    influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    radius = bpy.props.FloatProperty(name = "Radius",
-        description = "Custom radius for circle",
-        default = 1.0,
-        min = 0.0,
-        soft_max = 1000.0)
-    regular = bpy.props.BoolProperty(name = "Regular",
-        description = "Distribute vertices at constant distances along the " \
-            "circle",
-        default = True)
-    
-    @classmethod
-    def poll(cls, context):
-        ob = context.active_object
-        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-    
-    def draw(self, context):
-        layout = self.layout
-        col = layout.column()
-        
-        col.prop(self, "fit")
-        col.separator()
-        
-        col.prop(self, "flatten")
-        row = col.row(align=True)
-        row.prop(self, "custom_radius")
-        row_right = row.row(align=True)
-        row_right.active = self.custom_radius
-        row_right.prop(self, "radius", text="")
-        col.prop(self, "regular")
-        col.separator()
-                
-        col.prop(self, "influence")
-    
-    def invoke(self, context, event):
-        # load custom settings
-        settings_load(self)
-        return self.execute(context)
-    
-    def execute(self, context):
-        # initialise
-        global_undo, object, mesh = initialise()
-        settings_write(self)
-        # check cache to see if we can save time
-        cached, single_loops, loops, derived, mapping = cache_read("Circle",
-            object, mesh, False, False)
-        if cached:
-            derived, mesh_mod = get_derived_mesh(object, mesh, context.scene)
-        else:
-            # find loops
-            derived, mesh_mod, single_vertices, single_loops, loops = \
-                circle_get_input(object, mesh, context.scene)
-            mapping = get_mapping(derived, mesh, mesh_mod, single_vertices,
-                False, loops)
-            single_loops, loops = circle_check_loops(single_loops, loops,
-                mapping, mesh_mod)
-        
-        # saving cache for faster execution next time
-        if not cached:
-            cache_write("Circle", object, mesh, False, False, single_loops,
-                loops, derived, mapping)
-        
-        move = []
-        for i, loop in enumerate(loops):
-            # best fitting flat plane
-            com, normal = calculate_plane(mesh_mod, loop)
-            # if circular, shift loop so we get a good starting vertex
-            if loop[1]:
-                loop = circle_shift_loop(mesh_mod, loop, com)
-            # flatten vertices on plane
-            locs_2d, p, q = circle_3d_to_2d(mesh_mod, loop, com, normal)
-            # calculate circle
-            if self.fit == 'best':
-                x0, y0, r = circle_calculate_best_fit(locs_2d)
-            else: # self.fit == 'inside'
-                x0, y0, r = circle_calculate_min_fit(locs_2d)
-            # radius override
-            if self.custom_radius:
-                r = self.radius / p.length
-            # calculate positions on circle
-            if self.regular:
-                new_locs_2d = circle_project_regular(locs_2d[:], x0, y0, r)
-            else:
-                new_locs_2d = circle_project_non_regular(locs_2d[:], x0, y0, r)
-            # take influence into account
-            locs_2d = circle_influence_locs(locs_2d, new_locs_2d,
-                self.influence)
-            # calculate 3d positions of the created 2d input
-            move.append(circle_calculate_verts(self.flatten, mesh_mod,
-                locs_2d, com, p, q, normal))
-            # flatten single input vertices on plane defined by loop
-            if self.flatten and single_loops:
-                move.append(circle_flatten_singles(mesh_mod, com, p, q,
-                    normal, single_loops[i]))
-        
-        # move vertices to new locations
-        move_verts(mesh, mapping, move, -1)
-        
-        # cleaning up 
-        if derived:
-            bpy.context.blend_data.meshes.remove(mesh_mod)
-        terminate(global_undo)
-        
-        return{'FINISHED'}
-
-
-# curve operator
-class Curve(bpy.types.Operator):
-    bl_idname = "mesh.looptools_curve"
-    bl_label = "Curve"
-    bl_description = "Turn a loop into a smooth curve"
-    bl_options = {'REGISTER', 'UNDO'}
-    
-    boundaries = bpy.props.BoolProperty(name = "Boundaries",
-        description = "Limit the tool to work within the boundaries of the "\
-            "selected vertices",
-        default = False)
-    influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    interpolation = bpy.props.EnumProperty(name = "Interpolation",
-        items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
-            ("linear", "Linear", "Simple and fast linear algorithm")),
-        description = "Algorithm used for interpolation",
-        default = 'cubic')
-    regular = bpy.props.BoolProperty(name = "Regular",
-        description = "Distribute vertices at constant distances along the" \
-            "curve",
-        default = True)
-    restriction = bpy.props.EnumProperty(name = "Restriction",
-        items = (("none", "None", "No restrictions on vertex movement"),
-            ("extrude", "Extrude only","Only allow extrusions (no "\
-                "indentations)"),
-            ("indent", "Indent only", "Only allow indentation (no "\
-                "extrusions)")),
-        description = "Restrictions on how the vertices can be moved",
-        default = 'none')
-    
-    @classmethod
-    def poll(cls, context):
-        ob = context.active_object
-        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-    
-    def draw(self, context):
-        layout = self.layout
-        col = layout.column()
-        
-        col.prop(self, "interpolation")
-        col.prop(self, "restriction")
-        col.prop(self, "boundaries")
-        col.prop(self, "regular")
-        col.separator()
-        
-        col.prop(self, "influence")
-    
-    def invoke(self, context, event):
-        # load custom settings
-        settings_load(self)
-        return self.execute(context)
-    
-    def execute(self, context):
-        # initialise
-        global_undo, object, mesh = initialise()
-        settings_write(self)
-        # check cache to see if we can save time
-        cached, single_loops, loops, derived, mapping = cache_read("Curve",
-            object, mesh, False, self.boundaries)
-        if cached:
-            derived, mesh_mod = get_derived_mesh(object, mesh, context.scene)
-        else:
-            # find loops
-            derived, mesh_mod, loops = curve_get_input(object, mesh,
-                self.boundaries, context.scene)
-            mapping = get_mapping(derived, mesh, mesh_mod, False, True, loops)
-            loops = check_loops(loops, mapping, mesh_mod)
-        verts_selected = [v.index for v in mesh_mod.vertices if v.select \
-            and not v.hide]
-        
-        # saving cache for faster execution next time
-        if not cached:
-            cache_write("Curve", object, mesh, False, self.boundaries, False,
-                loops, derived, mapping)
-        
-        move = []
-        for loop in loops:
-            knots, points = curve_calculate_knots(loop, verts_selected)
-            pknots = curve_project_knots(mesh_mod, verts_selected, knots,
-                points, loop[1])
-            tknots, tpoints = curve_calculate_t(mesh_mod, knots, points,
-                pknots, self.regular, loop[1])
-            splines = calculate_splines(self.interpolation, mesh_mod,
-                tknots, knots)
-            move.append(curve_calculate_vertices(mesh_mod, knots, tknots,
-                points, tpoints, splines, self.interpolation,
-                self.restriction))
-        
-        # move vertices to new locations
-        move_verts(mesh, mapping, move, self.influence)
-        
-        # cleaning up 
-        if derived:
-            bpy.context.blend_data.meshes.remove(mesh_mod)
-        
-        terminate(global_undo)
-        return{'FINISHED'}
-
-
-# flatten operator
-class Flatten(bpy.types.Operator):
-    bl_idname = "mesh.looptools_flatten"
-    bl_label = "Flatten"
-    bl_description = "Flatten vertices on a best-fitting plane"
-    bl_options = {'REGISTER', 'UNDO'}
-    
-    influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    plane = bpy.props.EnumProperty(name = "Plane",
-        items = (("best_fit", "Best fit", "Calculate a best fitting plane"),
-            ("normal", "Normal", "Derive plane from averaging vertex "\
-            "normals"),
-            ("view", "View", "Flatten on a plane perpendicular to the "\
-            "viewing angle")),
-        description = "Plane on which vertices are flattened",
-        default = 'best_fit')
-    restriction = bpy.props.EnumProperty(name = "Restriction",
-        items = (("none", "None", "No restrictions on vertex movement"),
-            ("bounding_box", "Bounding box", "Vertices are restricted to "\
-            "movement inside the bounding box of the selection")),
-        description = "Restrictions on how the vertices can be moved",
-        default = 'none')
-    
-    @classmethod
-    def poll(cls, context):
-        ob = context.active_object
-        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-    
-    def draw(self, context):
-        layout = self.layout
-        col = layout.column()
-        
-        col.prop(self, "plane")
-        #col.prop(self, "restriction")
-        col.separator()
-        
-        col.prop(self, "influence")
-    
-    def invoke(self, context, event):
-        # load custom settings
-        settings_load(self)
-        return self.execute(context)
-    
-    def execute(self, context):
-        # initialise
-        global_undo, object, mesh = initialise()
-        settings_write(self)
-        # check cache to see if we can save time
-        cached, single_loops, loops, derived, mapping = cache_read("Flatten",
-            object, mesh, False, False)
-        if not cached:
-            # order input into virtual loops
-            loops = flatten_get_input(mesh)
-            loops = check_loops(loops, mapping, mesh)
-        
-        # saving cache for faster execution next time
-        if not cached:
-            cache_write("Flatten", object, mesh, False, False, False, loops,
-                False, False)
-        
-        move = []
-        for loop in loops:
-            # calculate plane and position of vertices on them
-            com, normal = calculate_plane(mesh, loop, method=self.plane,
-                object=object)
-            to_move = flatten_project(mesh, loop, com, normal)
-            if self.restriction == 'none':
-                move.append(to_move)
-            else:
-                move.append(to_move)
-        move_verts(mesh, False, move, self.influence)
-        
-        terminate(global_undo)
-        return{'FINISHED'}
-
-
-# relax operator
-class Relax(bpy.types.Operator):
-    bl_idname = "mesh.looptools_relax"
-    bl_label = "Relax"
-    bl_description = "Relax the loop, so it is smoother"
-    bl_options = {'REGISTER', 'UNDO'}
-    
-    input = bpy.props.EnumProperty(name = "Input",
-        items = (("all", "Parallel (all)", "Also use non-selected "\
-                "parallel loops as input"),
-            ("selected", "Selection","Only use selected vertices as input")),
-        description = "Loops that are relaxed",
-        default = 'selected')
-    interpolation = bpy.props.EnumProperty(name = "Interpolation",
-        items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
-            ("linear", "Linear", "Simple and fast linear algorithm")),
-        description = "Algorithm used for interpolation",
-        default = 'cubic')
-    iterations = bpy.props.EnumProperty(name = "Iterations",
-        items = (("1", "1", "One"),
-            ("3", "3", "Three"),
-            ("5", "5", "Five"),
-            ("10", "10", "Ten"),
-            ("25", "25", "Twenty-five")),
-        description = "Number of times the loop is relaxed",
-        default = "1")
-    regular = bpy.props.BoolProperty(name = "Regular",
-        description = "Distribute vertices at constant distances along the" \
-            "loop",
-        default = True)
-    
-    @classmethod
-    def poll(cls, context):
-        ob = context.active_object
-        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-    
-    def draw(self, context):
-        layout = self.layout
-        col = layout.column()
-        
-        col.prop(self, "interpolation")
-        col.prop(self, "input")
-        col.prop(self, "iterations")
-        col.prop(self, "regular")
-    
-    def invoke(self, context, event):
-        # load custom settings
-        settings_load(self)
-        return self.execute(context)
-    
-    def execute(self, context):
-        # initialise
-        global_undo, object, mesh = initialise()
-        settings_write(self)
-        # check cache to see if we can save time
-        cached, single_loops, loops, derived, mapping = cache_read("Relax",
-            object, mesh, self.input, False)
-        if cached:
-            derived, mesh_mod = get_derived_mesh(object, mesh, context.scene)
-        else:
-            # find loops
-            derived, mesh_mod, loops = get_connected_input(object, mesh,
-                context.scene, self.input)
-            mapping = get_mapping(derived, mesh, mesh_mod, False, False, loops)
-            loops = check_loops(loops, mapping, mesh_mod)
-        knots, points = relax_calculate_knots(loops)
-        
-        # saving cache for faster execution next time
-        if not cached:
-            cache_write("Relax", object, mesh, self.input, False, False, loops,
-                derived, mapping)
-        
-        for iteration in range(int(self.iterations)):
-            # calculate splines and new positions
-            tknots, tpoints = relax_calculate_t(mesh_mod, knots, points,
-                self.regular)
-            splines = []
-            for i in range(len(knots)):
-                splines.append(calculate_splines(self.interpolation, mesh_mod,
-                    tknots[i], knots[i]))
-            move = [relax_calculate_verts(mesh_mod, self.interpolation,
-                tknots, knots, tpoints, points, splines)]
-            move_verts(mesh, mapping, move, -1)
-        
-        # cleaning up 
-        if derived:
-            bpy.context.blend_data.meshes.remove(mesh_mod)
-        terminate(global_undo)
-        
-        return{'FINISHED'}
-
-
-# space operator
-class Space(bpy.types.Operator):
-    bl_idname = "mesh.looptools_space"
-    bl_label = "Space"
-    bl_description = "Space the vertices in a regular distrubtion on the loop"
-    bl_options = {'REGISTER', 'UNDO'}
-    
-    influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    input = bpy.props.EnumProperty(name = "Input",
-        items = (("all", "Parallel (all)", "Also use non-selected "\
-                "parallel loops as input"),
-            ("selected", "Selection","Only use selected vertices as input")),
-        description = "Loops that are spaced",
-        default = 'selected')
-    interpolation = bpy.props.EnumProperty(name = "Interpolation",
-        items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
-            ("linear", "Linear", "Vertices are projected on existing edges")),
-        description = "Algorithm used for interpolation",
-        default = 'cubic')
-    
-    @classmethod
-    def poll(cls, context):
-        ob = context.active_object
-        return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
-    
-    def draw(self, context):
-        layout = self.layout
-        col = layout.column()
-        
-        col.prop(self, "interpolation")
-        col.prop(self, "input")
-        col.separator()
-        
-        col.prop(self, "influence")
-    
-    def invoke(self, context, event):
-        # load custom settings
-        settings_load(self)
-        return self.execute(context)
-    
-    def execute(self, context):
-        # initialise
-        global_undo, object, mesh = initialise()
-        settings_write(self)
-        # check cache to see if we can save time
-        cached, single_loops, loops, derived, mapping = cache_read("Space",
-            object, mesh, self.input, False)
-        if cached:
-            derived, mesh_mod = get_derived_mesh(object, mesh, context.scene)
-        else:
-            # find loops
-            derived, mesh_mod, loops = get_connected_input(object, mesh,
-                context.scene, self.input)
-            mapping = get_mapping(derived, mesh, mesh_mod, False, False, loops)
-            loops = check_loops(loops, mapping, mesh_mod)
-        
-        # saving cache for faster execution next time
-        if not cached:
-            cache_write("Space", object, mesh, self.input, False, False, loops,
-                derived, mapping)
-        
-        move = []
-        for loop in loops:
-            # calculate splines and new positions
-            if loop[1]: # circular
-                loop[0].append(loop[0][0])
-            tknots, tpoints = space_calculate_t(mesh_mod, loop[0][:])
-            splines = calculate_splines(self.interpolation, mesh_mod,
-                tknots, loop[0][:])
-            move.append(space_calculate_verts(mesh_mod, self.interpolation,
-                tknots, tpoints, loop[0][:-1], splines))
-        
-        # move vertices to new locations
-        move_verts(mesh, mapping, move, self.influence)
-        
-        # cleaning up 
-        if derived:
-            bpy.context.blend_data.meshes.remove(mesh_mod)
-        terminate(global_undo)
-        
-        return{'FINISHED'}
-
-
-##########################################
-####### GUI and registration #############
-##########################################
-
-# menu containing all tools
-class VIEW3D_MT_edit_mesh_looptools(bpy.types.Menu):
-    bl_label = "LoopTools"
-    
-    def draw(self, context):
-        layout = self.layout
-        
-#        layout.operator("mesh.looptools_bridge", text="Bridge").loft = False
-        layout.operator("mesh.looptools_circle")
-        layout.operator("mesh.looptools_curve")
-        layout.operator("mesh.looptools_flatten")
-#        layout.operator("mesh.looptools_bridge", text="Loft").loft = True
-        layout.operator("mesh.looptools_relax")
-        layout.operator("mesh.looptools_space")
-
-
-# panel containing all tools
-class VIEW3D_PT_tools_looptools(bpy.types.Panel):
-    bl_space_type = 'VIEW_3D'
-    bl_region_type = 'TOOLS'
-    bl_context = "mesh_edit"
-    bl_label = "LoopTools"
-
-    def draw(self, context):
-        layout = self.layout
-        col = layout.column(align=True)
-        lt = context.window_manager.looptools
-        
-        # bridge - first line
-#        split = col.split(percentage=0.15)
-#        if lt.display_bridge:
-#            split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT')
-#        else:
-#            split.prop(lt, "display_bridge", text="", icon='RIGHTARROW')
-#        split.operator("mesh.looptools_bridge", text="Bridge").loft = False
-        # bridge - settings
-#        if lt.display_bridge:
-#            box = col.column(align=True).box().column()
-            #box.prop(self, "mode")
-            
-            # top row
-#            col_top = box.column(align=True)
-#            row = col_top.row(align=True)
-#            col_left = row.column(align=True)
-#            col_right = row.column(align=True)
-#            col_right.active = lt.bridge_segments != 1
-#            col_left.prop(lt, "bridge_segments")
-#            col_right.prop(lt, "bridge_min_width", text="")
-#            # bottom row
-#            bottom_left = col_left.row()
-#            bottom_left.active = lt.bridge_segments != 1
-#            bottom_left.prop(lt, "bridge_interpolation", text="")
-#            bottom_right = col_right.row()
-#            bottom_right.active = lt.bridge_interpolation == 'cubic'
-#            bottom_right.prop(lt, "bridge_cubic_strength")
-            # boolean properties
-#            col_top.prop(lt, "bridge_remove_faces")
-            
-            # override properties
-#            col_top.separator()
-#            row = box.row(align = True)
-#            row.prop(lt, "bridge_twist")
-#            row.prop(lt, "bridge_reverse")
-        
-        # circle - first line
-        split = col.split(percentage=0.15)
-        if lt.display_circle:
-            split.prop(lt, "display_circle", text="", icon='DOWNARROW_HLT')
-        else:
-            split.prop(lt, "display_circle", text="", icon='RIGHTARROW')
-        split.operator("mesh.looptools_circle")
-        # circle - settings
-        if lt.display_circle:
-            box = col.column(align=True).box().column()
-            box.prop(lt, "circle_fit")
-            box.separator()
-            
-            box.prop(lt, "circle_flatten")
-            row = box.row(align=True)
-            row.prop(lt, "circle_custom_radius")
-            row_right = row.row(align=True)
-            row_right.active = lt.circle_custom_radius
-            row_right.prop(lt, "circle_radius", text="")
-            box.prop(lt, "circle_regular")
-            box.separator()
-            
-            box.prop(lt, "circle_influence")
-        
-        # curve - first line
-        split = col.split(percentage=0.15)
-        if lt.display_curve:
-            split.prop(lt, "display_curve", text="", icon='DOWNARROW_HLT')
-        else:
-            split.prop(lt, "display_curve", text="", icon='RIGHTARROW')
-        split.operator("mesh.looptools_curve")
-        # curve - settings
-        if lt.display_curve:
-            box = col.column(align=True).box().column()
-            box.prop(lt, "curve_interpolation")
-            box.prop(lt, "curve_restriction")
-            box.prop(lt, "curve_boundaries")
-            box.prop(lt, "curve_regular")
-            box.separator()
-            
-            box.prop(lt, "curve_influence")
-        
-        # flatten - first line
-        split = col.split(percentage=0.15)
-        if lt.display_flatten:
-            split.prop(lt, "display_flatten", text="", icon='DOWNARROW_HLT')
-        else:
-            split.prop(lt, "display_flatten", text="", icon='RIGHTARROW')
-        split.operator("mesh.looptools_flatten")
-        # flatten - settings
-        if lt.display_flatten:
-            box = col.column(align=True).box().column()
-            box.prop(lt, "flatten_plane")
-            #box.prop(lt, "flatten_restriction")
-            box.separator()
-            
-            box.prop(lt, "flatten_influence")
-        
-        # loft - first line
-#        split = col.split(percentage=0.15)
-#        if lt.display_loft:
-#            split.prop(lt, "display_loft", text="", icon='DOWNARROW_HLT')
-#        else:
-#            split.prop(lt, "display_loft", text="", icon='RIGHTARROW')
-#        split.operator("mesh.looptools_bridge", text="Loft").loft = True
-#        # loft - settings
-#        if lt.display_loft:
-#            box = col.column(align=True).box().column()
-#            #box.prop(self, "mode")
-#            
-#            # top row
-#            col_top = box.column(align=True)
-#            row = col_top.row(align=True)
-#            col_left = row.column(align=True)
-#            col_right = row.column(align=True)
-#            col_right.active = lt.bridge_segments != 1
-#            col_left.prop(lt, "bridge_segments")
-#            col_right.prop(lt, "bridge_min_width", text="")
-#            # bottom row
-#            bottom_left = col_left.row()
-#            bottom_left.active = lt.bridge_segments != 1
-#            bottom_left.prop(lt, "bridge_interpolation", text="")
-#            bottom_right = col_right.row()
-#            bottom_right.active = lt.bridge_interpolation == 'cubic'
-#            bottom_right.prop(lt, "bridge_cubic_strength")
-#            # boolean properties
-#            col_top.prop(lt, "bridge_remove_faces")
-#            col_top.prop(lt, "bridge_loft_loop")
-#            
-#            # override properties
-#            col_top.separator()
-#            row = box.row(align = True)
-#            row.prop(lt, "bridge_twist")
-#            row.prop(lt, "bridge_reverse")
-        
-        # relax - first line
-        split = col.split(percentage=0.15)
-        if lt.display_relax:
-            split.prop(lt, "display_relax", text="", icon='DOWNARROW_HLT')
-        else:
-            split.prop(lt, "display_relax", text="", icon='RIGHTARROW')
-        split.operator("mesh.looptools_relax")
-        # relax - settings
-        if lt.display_relax:
-            box = col.column(align=True).box().column()
-            box.prop(lt, "relax_interpolation")
-            box.prop(lt, "relax_input")
-            box.prop(lt, "relax_iterations")
-            box.prop(lt, "relax_regular")
-        
-        # space - first line
-        split = col.split(percentage=0.15)
-        if lt.display_space:
-            split.prop(lt, "display_space", text="", icon='DOWNARROW_HLT')
-        else:
-            split.prop(lt, "display_space", text="", icon='RIGHTARROW')
-        split.operator("mesh.looptools_space")
-        # space - settings
-        if lt.display_space:
-            box = col.column(align=True).box().column()
-            box.prop(lt, "space_interpolation")
-            box.prop(lt, "space_input")
-            box.separator()
-            
-            box.prop(lt, "space_influence")
-
-
-# property group containing all properties for the gui in the panel
-class LoopToolsProps(bpy.types.PropertyGroup):
-    """
-    Fake module like class
-    bpy.context.window_manager.looptools
-    """
-    
-    # general display properties
-#    display_bridge = bpy.props.BoolProperty(name = "Bridge settings",
-#        description = "Display settings of the Bridge tool",
-#        default = False)
-    display_circle = bpy.props.BoolProperty(name = "Circle settings",
-        description = "Display settings of the Circle tool",
-        default = False)
-    display_curve = bpy.props.BoolProperty(name = "Curve settings",
-        description = "Display settings of the Curve tool",
-        default = False)
-    display_flatten = bpy.props.BoolProperty(name = "Flatten settings",
-        description = "Display settings of the Flatten tool",
-        default = False)
-#    display_loft = bpy.props.BoolProperty(name = "Loft settings",
-#        description = "Display settings of the Loft tool",
-#        default = False)
-    display_relax = bpy.props.BoolProperty(name = "Relax settings",
-        description = "Display settings of the Relax tool",
-        default = False)
-    display_space = bpy.props.BoolProperty(name = "Space settings",
-        description = "Display settings of the Space tool",
-        default = False)
-    
-    # bridge properties
-    bridge_cubic_strength = bpy.props.FloatProperty(name = "Strength",
-        description = "Higher strength results in more fluid curves",
-        default = 1.0,
-        soft_min = -3.0,
-        soft_max = 3.0)
-    bridge_interpolation = bpy.props.EnumProperty(name = "Interpolation mode",
-        items = (('cubic', "Cubic", "Gives curved results"),
-            ('linear', "Linear", "Basic, fast, straight interpolation")),
-        description = "Interpolation mode: algorithm used when creating "\
-            "segments",
-        default = 'cubic')
-    bridge_loft = bpy.props.BoolProperty(name = "Loft",
-        description = "Loft multiple loops, instead of considering them as "\
-            "a multi-input for bridging",
-        default = False)
-    bridge_loft_loop = bpy.props.BoolProperty(name = "Loop",
-        description = "Connect the first and the last loop with each other",
-        default = False)
-    bridge_min_width = bpy.props.IntProperty(name = "Minimum width",
-        description = "Segments with an edge smaller than this are merged "\
-            "(compared to base edge)",
-        default = 0,
-        min = 0,
-        max = 100,
-        subtype = 'PERCENTAGE')
-    bridge_mode = bpy.props.EnumProperty(name = "Mode",
-        items = (('basic', "Basic", "Fast algorithm"),
-                 ('shortest', "Shortest edge", "Slower algorithm with " \
-                                               "better vertex matching")),
-        description = "Algorithm used for bridging",
-        default = 'shortest')
-    bridge_remove_faces = bpy.props.BoolProperty(name = "Remove faces",
-        description = "Remove faces that are internal after bridging",
-        default = True)
-    bridge_reverse = bpy.props.BoolProperty(name = "Reverse",
-        description = "Manually override the direction in which the loops "\
-                      "are bridged. Only use if the tool gives the wrong " \
-                      "result",
-        default = False)
-    bridge_segments = bpy.props.IntProperty(name = "Segments",
-        description = "Number of segments used to bridge the gap "\
-            "(0 = automatic)",
-        default = 1,
-        min = 0,
-        soft_max = 20)
-    bridge_twist = bpy.props.IntProperty(name = "Twist",
-        description = "Twist what vertices are connected to each other",
-        default = 0)
-    
-    # circle properties
-    circle_custom_radius = bpy.props.BoolProperty(name = "Radius",
-        description = "Force a custom radius",
-        default = False)
-    circle_fit = bpy.props.EnumProperty(name = "Method",
-        items = (("best", "Best fit", "Non-linear least squares"),
-            ("inside", "Fit inside","Only move vertices towards the center")),
-        description = "Method used for fitting a circle to the vertices",
-        default = 'best')
-    circle_flatten = bpy.props.BoolProperty(name = "Flatten",
-        description = "Flatten the circle, instead of projecting it on the " \
-            "mesh",
-        default = True)
-    circle_influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    circle_radius = bpy.props.FloatProperty(name = "Radius",
-        description = "Custom radius for circle",
-        default = 1.0,
-        min = 0.0,
-        soft_max = 1000.0)
-    circle_regular = bpy.props.BoolProperty(name = "Regular",
-        description = "Distribute vertices at constant distances along the " \
-            "circle",
-        default = True)
-    
-    # curve properties
-    curve_boundaries = bpy.props.BoolProperty(name = "Boundaries",
-        description = "Limit the tool to work within the boundaries of the "\
-            "selected vertices",
-        default = False)
-    curve_influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    curve_interpolation = bpy.props.EnumProperty(name = "Interpolation",
-        items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
-            ("linear", "Linear", "Simple and fast linear algorithm")),
-        description = "Algorithm used for interpolation",
-        default = 'cubic')
-    curve_regular = bpy.props.BoolProperty(name = "Regular",
-        description = "Distribute vertices at constant distances along the" \
-            "curve",
-        default = True)
-    curve_restriction = bpy.props.EnumProperty(name = "Restriction",
-        items = (("none", "None", "No restrictions on vertex movement"),
-            ("extrude", "Extrude only","Only allow extrusions (no "\
-                "indentations)"),
-            ("indent", "Indent only", "Only allow indentation (no "\
-                "extrusions)")),
-        description = "Restrictions on how the vertices can be moved",
-        default = 'none')
-    
-    # flatten properties
-    flatten_influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    flatten_plane = bpy.props.EnumProperty(name = "Plane",
-        items = (("best_fit", "Best fit", "Calculate a best fitting plane"),
-            ("normal", "Normal", "Derive plane from averaging vertex "\
-            "normals"),
-            ("view", "View", "Flatten on a plane perpendicular to the "\
-            "viewing angle")),
-        description = "Plane on which vertices are flattened",
-        default = 'best_fit')
-    flatten_restriction = bpy.props.EnumProperty(name = "Restriction",
-        items = (("none", "None", "No restrictions on vertex movement"),
-            ("bounding_box", "Bounding box", "Vertices are restricted to "\
-            "movement inside the bounding box of the selection")),
-        description = "Restrictions on how the vertices can be moved",
-        default = 'none')
-    
-    # relax properties
-    relax_input = bpy.props.EnumProperty(name = "Input",
-        items = (("all", "Parallel (all)", "Also use non-selected "\
-                "parallel loops as input"),
-            ("selected", "Selection","Only use selected vertices as input")),
-        description = "Loops that are relaxed",
-        default = 'selected')
-    relax_interpolation = bpy.props.EnumProperty(name = "Interpolation",
-        items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
-            ("linear", "Linear", "Simple and fast linear algorithm")),
-        description = "Algorithm used for interpolation",
-        default = 'cubic')
-    relax_iterations = bpy.props.EnumProperty(name = "Iterations",
-        items = (("1", "1", "One"),
-            ("3", "3", "Three"),
-            ("5", "5", "Five"),
-            ("10", "10", "Ten"),
-            ("25", "25", "Twenty-five")),
-        description = "Number of times the loop is relaxed",
-        default = "1")
-    relax_regular = bpy.props.BoolProperty(name = "Regular",
-        description = "Distribute vertices at constant distances along the" \
-            "loop",
-        default = True)
-    
-    # space properties
-    space_influence = bpy.props.FloatProperty(name = "Influence",
-        description = "Force of the tool",
-        default = 100.0,
-        min = 0.0,
-        max = 100.0,
-        precision = 1,
-        subtype = 'PERCENTAGE')
-    space_input = bpy.props.EnumProperty(name = "Input",
-        items = (("all", "Parallel (all)", "Also use non-selected "\
-                "parallel loops as input"),
-            ("selected", "Selection","Only use selected vertices as input")),
-        description = "Loops that are spaced",
-        default = 'selected')
-    space_interpolation = bpy.props.EnumProperty(name = "Interpolation",
-        items = (("cubic", "Cubic", "Natural cubic spline, smooth results"),
-            ("linear", "Linear", "Vertices are projected on existing edges")),
-        description = "Algorithm used for interpolation",
-        default = 'cubic')
-
-
-# draw function for integration in menus
-def menu_func(self, context):
-    self.layout.menu("VIEW3D_MT_edit_mesh_looptools")
-    self.layout.separator()
-
-
-# define classes for registration
-classes = [VIEW3D_MT_edit_mesh_looptools,
-    VIEW3D_PT_tools_looptools,
-    LoopToolsProps,
-    Bridge,
-    Circle,
-    Curve,
-    Flatten,
-    Relax,
-    Space]
-
-
-# registering and menu integration
-def register():
-    for c in classes:
-        bpy.utils.register_class(c)
-    bpy.types.VIEW3D_MT_edit_mesh_specials.prepend(menu_func)
-    bpy.types.WindowManager.looptools = bpy.props.PointerProperty(\
-        type = LoopToolsProps)
-
-
-# unregistering and removing menus
-def unregister():
-    for c in classes:
-        bpy.utils.unregister_class(c)
-    bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
-    try:
-        del bpy.types.WindowManager.looptools
-    except:
-        pass
-
-
-if __name__ == "__main__":
-    register()
-- 
GitLab