Skip to content
Snippets Groups Projects
mesh_looptools.py 138 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bart Crouch's avatar
    Bart Crouch committed
                    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.\
    
    Bart Crouch's avatar
    Bart Crouch committed
                        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]]
    
    
    Bart Crouch's avatar
    Bart Crouch committed
                        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)]
    
    Bart Crouch's avatar
    Bart Crouch committed
                    
                    # 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()
    
    Bart Crouch's avatar
    Bart Crouch committed
                    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()
    
    Bart Crouch's avatar
    Bart Crouch committed
                    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))]
    
    Bart Crouch's avatar
    Bart Crouch committed
                    continue
                
                # calculate to what location the vertex is connected, 
                # used to determine what way to flip the normal
    
                connected_center = mathutils.Vector()
    
    Bart Crouch's avatar
    Bart Crouch committed
                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))]
    
    Bart Crouch's avatar
    Bart Crouch committed
                    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()
    
    Bart Crouch's avatar
    Bart Crouch committed
            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))]
    
    Bart Crouch's avatar
    Bart Crouch committed
        
        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]
    
    
    Bart Crouch's avatar
    Bart Crouch committed
        # calculate two vectors (p and q) along the plane
    
        m = mathutils.Vector((normal[0] + 1.0, normal[1], normal[2]))
    
    Bart Crouch's avatar
    Bart Crouch committed
        p = m - (m.dot(normal) * normal)
        if p.dot(p) == 0.0:
    
            m = mathutils.Vector((normal[0], normal[1] + 1.0, normal[2]))
    
    Bart Crouch's avatar
    Bart Crouch committed
            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))
    
    Bart Crouch's avatar
    Bart Crouch committed
            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]
    
    Bart Crouch's avatar
    Bart Crouch committed
                jmat2[1][1] += jmat[i][1]**2
    
                jmat2[2][1] += jmat[i][1]*jmat[i][2]
    
    Bart Crouch's avatar
    Bart Crouch committed
                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]
    
    Bart Crouch's avatar
    Bart Crouch committed
            try:
                jmat2.invert()
            except:
                pass
    
    Bart Crouch's avatar
    Bart Crouch committed
    1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000
            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: