Skip to content
Snippets Groups Projects
mesh_bsurfaces.py 180 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### 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; version 2
    #  of the License.
    
    #
    #  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 #####
    
    
    Eclectiel L's avatar
    Eclectiel L committed
        "name": "Bsurfaces GPL Edition",
    
        "author": "Eclectiel",
    
        "version": (1,5),
    
        "blender": (2, 63, 0),
    
        "api": 45996,
    
    Eclectiel L's avatar
    Eclectiel L committed
        "location": "View3D > EditMode > ToolShelf",
    
        "description": "Modeling and retopology tool.",
    
        "wiki_url": "http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.64/Bsurfaces_1.5",
    
        "tracker_url": "http://projects.blender.org/tracker/index.php?"\
            "func=detail&aid=26642",
    
        "category": "Mesh"}
    
    import bmesh
    
    import mathutils
    import operator
    
    
    
    
    class VIEW3D_PT_tools_SURFSK_mesh(bpy.types.Panel):
    
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'TOOLS'
        bl_context = "mesh_edit"
    
    Eclectiel L's avatar
    Eclectiel L committed
        bl_label = "Bsurfaces"
    
        @classmethod
        def poll(cls, context):
    
            return context.active_object
    
        def draw(self, context):
            layout = self.layout
            
            scn = context.scene
    
            ob = context.object
    
            
            col = layout.column(align=True)
    
            row = layout.row()
            row.separator()
    
    Eclectiel L's avatar
    Eclectiel L committed
            col.operator("gpencil.surfsk_add_surface", text="Add Surface")
    
            col.operator("gpencil.surfsk_edit_strokes", text="Edit Strokes")
            col.prop(scn, "SURFSK_cyclic_cross")
            col.prop(scn, "SURFSK_cyclic_follow")
            col.prop(scn, "SURFSK_loops_on_strokes")
            col.prop(scn, "SURFSK_automatic_join")
            col.prop(scn, "SURFSK_keep_strokes")
            
            
            
    class VIEW3D_PT_tools_SURFSK_curve(bpy.types.Panel):
        bl_space_type = 'VIEW_3D'
        bl_region_type = 'TOOLS'
        bl_context = "curve_edit"
        bl_label = "Bsurfaces"
        
        @classmethod
        def poll(cls, context):
            return context.active_object
        
        
        def draw(self, context):
            layout = self.layout
            
            scn = context.scene
            ob = context.object
            
            col = layout.column(align=True)
            row = layout.row()
            row.separator()
            col.operator("curve.surfsk_first_points", text="Set First Points")
            col.operator("curve.switch_direction", text="Switch Direction")
            col.operator("curve.surfsk_reorder_splines", text="Reorder Splines")
            
    
    
    
    #### Returns the type of strokes used.
    def get_strokes_type(main_object):
        strokes_type = ""
        strokes_num = 0
        
        # Check if they are grease pencil
        try:
            #### Get the active grease pencil layer.
            strokes_num = len(main_object.grease_pencil.layers.active.active_frame.strokes)
            
            if strokes_num > 0:
                strokes_type = "GP_STROKES"
        except:
            pass
        
        
        # Check if they are curves, if there aren't grease pencil strokes.
        if strokes_type == "":
            if len(bpy.context.selected_objects) == 2:
                for ob in bpy.context.selected_objects:
                    if ob != bpy.context.scene.objects.active and ob.type == "CURVE":
                        strokes_type = "EXTERNAL_CURVE"
                        strokes_num = len(ob.data.splines)
                        
                        # Check if there is any non-bezier spline.
                        for i in range(len(ob.data.splines)):
                            if ob.data.splines[i].type != "BEZIER":
                                strokes_type = "CURVE_WITH_NON_BEZIER_SPLINES"
                                break
                                
                    elif ob != bpy.context.scene.objects.active and ob.type != "CURVE":
                        strokes_type = "EXTERNAL_NO_CURVE"
            elif len(bpy.context.selected_objects) > 2:
                strokes_type = "MORE_THAN_ONE_EXTERNAL"
        
        
        # Check if there is a single stroke without any selection in the object.
        if strokes_num == 1 and main_object.data.total_vert_sel == 0:
            if strokes_type == "EXTERNAL_CURVE":
                strokes_type = "SINGLE_CURVE_STROKE_NO_SELECTION"
            elif strokes_type == "GP_STROKES":
                strokes_type = "SINGLE_GP_STROKE_NO_SELECTION"
    
        if strokes_num == 0 and main_object.data.total_vert_sel > 0:
            strokes_type = "SELECTION_ALONE"
    
            
        if strokes_type == "":
            strokes_type = "NO_STROKES"
        
        
        
        return strokes_type
    
    
    
    
    # Surface generator operator.
    
    Eclectiel L's avatar
    Eclectiel L committed
    class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator):
        bl_idname = "gpencil.surfsk_add_surface"
    
    Eclectiel L's avatar
    Eclectiel L committed
        bl_label = "Bsurfaces add surface"
    
        bl_description = "Generates surfaces from grease pencil strokes, bezier curves or loose edges."
    
    Eclectiel L's avatar
    Eclectiel L committed
        bl_options = {'REGISTER', 'UNDO'}
    
        edges_U = bpy.props.IntProperty(name = "Cross",
                            description = "Number of face-loops crossing the strokes.",
                            default = 1,
                            min = 1,
                            max = 200)
                            
        edges_V = bpy.props.IntProperty(name = "Follow",
                            description = "Number of face-loops following the strokes.",
                            default = 1,
                            min = 1,
                            max = 200)
        
        cyclic_cross = bpy.props.BoolProperty(name = "Cyclic Cross",
                            description = "Make cyclic the face-loops crossing the strokes.",
                            default = False)
                            
        cyclic_follow = bpy.props.BoolProperty(name = "Cyclic Follow",
                            description = "Make cyclic the face-loops following the strokes.",
                            default = False)
                            
        loops_on_strokes = bpy.props.BoolProperty(name = "Loops on strokes",
                            description = "Make the loops match the paths of the strokes.",
                            default = False)
        
        automatic_join = bpy.props.BoolProperty(name = "Automatic join",
                            description = "Join automatically vertices of either surfaces generated by crosshatching, or from the borders of closed shapes.",
                            default = False)
                            
        join_stretch_factor = bpy.props.FloatProperty(name = "Stretch",
                            description = "Amount of stretching or shrinking allowed for edges when joining vertices automatically.",
                            default = 1,
                            min = 0,
                            max = 3,
                            subtype = 'FACTOR')
        
        
        
        
        def draw(self, context):
            layout = self.layout
            
            scn = context.scene
            ob = context.object
            
            col = layout.column(align=True)
            row = layout.row()
            
            if not self.is_fill_faces:
                row.separator()
                if not self.is_crosshatch:
                    if not self.selection_U_exists:
                        col.prop(self, "edges_U")
                        row.separator()
                        
                    if not self.selection_V_exists:
                        col.prop(self, "edges_V")
                        row.separator()
                    
                    row.separator()
                    
                    if not self.selection_U_exists:
                        if not ((self.selection_V_exists and not self.selection_V_is_closed) or (self.selection_V2_exists and not self.selection_V2_is_closed)):
                            col.prop(self, "cyclic_cross")
                        
                    if not self.selection_V_exists:
                        if not ((self.selection_U_exists and not self.selection_U_is_closed) or (self.selection_U2_exists and not self.selection_U2_is_closed)):
                            col.prop(self, "cyclic_follow")
                    
                    
                    col.prop(self, "loops_on_strokes")
                
                col.prop(self, "automatic_join")    
                    
                if self.automatic_join:
                    row.separator()
                    col.separator()
                    row.separator()
                    col.prop(self, "join_stretch_factor")
                
        
        
        #### Get an ordered list of a chain of vertices.
        def get_ordered_verts(self, ob, all_selected_edges_idx, all_selected_verts_idx, first_vert_idx, middle_vertex_idx, closing_vert_idx):
            # Order selected vertices.
    
            verts_ordered = []
    
            if closing_vert_idx != None:
                verts_ordered.append(ob.data.vertices[closing_vert_idx])
                
            verts_ordered.append(ob.data.vertices[first_vert_idx])
    
            prev_v = first_vert_idx
            prev_ed = None
            finish_while = False
            while True:
                edges_non_matched = 0
                for i in all_selected_edges_idx:
    
                    if ob.data.edges[i] != prev_ed and ob.data.edges[i].vertices[0] == prev_v and ob.data.edges[i].vertices[1] in all_selected_verts_idx:
    
                        verts_ordered.append(ob.data.vertices[ob.data.edges[i].vertices[1]])
    
                        prev_v = ob.data.edges[i].vertices[1]
    
                        prev_ed = ob.data.edges[i]
    
                    elif ob.data.edges[i] != prev_ed and ob.data.edges[i].vertices[1] == prev_v and ob.data.edges[i].vertices[0] in all_selected_verts_idx:
    
                        verts_ordered.append(ob.data.vertices[ob.data.edges[i].vertices[0]])
    
                        prev_v = ob.data.edges[i].vertices[0]
    
                        prev_ed = ob.data.edges[i]
                    else:
                        edges_non_matched += 1
                        
                        if edges_non_matched == len(all_selected_edges_idx):
                            finish_while = True
                        
                if finish_while:
                    break
            
    
            if closing_vert_idx != None:
                verts_ordered.append(ob.data.vertices[closing_vert_idx])
            
    
            if middle_vertex_idx != None:
    
                verts_ordered.append(ob.data.vertices[middle_vertex_idx])
    
                verts_ordered.reverse()
            
    
            return tuple(verts_ordered)
    
        
        
        #### Calculates length of a chain of points.
    
    Eclectiel L's avatar
    Eclectiel L committed
        def get_chain_length(self, object, verts_ordered):
    
            matrix = object.matrix_world
    
    Eclectiel L's avatar
    Eclectiel L committed
            
    
            edges_lengths = []
            edges_lengths_sum = 0
            for i in range(0, len(verts_ordered)):
                if i == 0:
    
                    prev_v_co = matrix * verts_ordered[i].co
    
                    v_co = matrix * verts_ordered[i].co
    
    Eclectiel L's avatar
    Eclectiel L committed
                    v_difs = [prev_v_co[0] - v_co[0], prev_v_co[1] - v_co[1], prev_v_co[2] - v_co[2]]
    
                    edge_length = abs(sqrt(v_difs[0] * v_difs[0] + v_difs[1] * v_difs[1] + v_difs[2] * v_difs[2]))
                    
                    edges_lengths.append(edge_length)
                    edges_lengths_sum += edge_length
                    
    
    Eclectiel L's avatar
    Eclectiel L committed
                    prev_v_co = v_co
    
            return edges_lengths, edges_lengths_sum
        
        
        #### Calculates the proportion of the edges of a chain of edges, relative to the full chain length.
        def get_edges_proportions(self, edges_lengths, edges_lengths_sum, use_boundaries, fixed_edges_num):
            edges_proportions = []
            if use_boundaries:
                verts_count = 1
                for l in edges_lengths:
                    edges_proportions.append(l / edges_lengths_sum)
                    verts_count += 1
            else:
                verts_count = 1
                for n in range(0, fixed_edges_num):
                    edges_proportions.append(1 / fixed_edges_num)
                    verts_count += 1
            
            return edges_proportions
        
        
        #### Calculates the angle between two pairs of points in space.
        def orientation_difference(self, points_A_co, points_B_co): # each parameter should be a list with two elements, and each element should be a x,y,z coordinate.
            vec_A = points_A_co[0] - points_A_co[1]
            vec_B = points_B_co[0] - points_B_co[1]
            
            angle = vec_A.angle(vec_B)
            
            if angle > 0.5 * math.pi:
                angle = abs(angle - math.pi)
            
            return angle
        
    
        #### Calculate the which vert of verts_idx list is the nearest one to the point_co coordinates, and the distance.
        def shortest_distance(self, object, point_co, verts_idx):
            matrix = object.matrix_world
    
            for i in range(0, len(verts_idx)):
                dist = (point_co - matrix * object.data.vertices[verts_idx[i]].co).length
                if i == 0:
                    prev_dist = dist
                    nearest_vert_idx = verts_idx[i]
                    shortest_dist = dist
    
                if dist < prev_dist:
                    prev_dist = dist
                    nearest_vert_idx = verts_idx[i]
                    shortest_dist = dist
    
            return nearest_vert_idx, shortest_dist
    
        #### Returns the index of the opposite vert tip in a chain, given a vert tip index as parameter, and a multidimentional list with all pairs of tips.
        def opposite_tip(self, vert_tip_idx, all_chains_tips_idx):
            opposite_vert_tip_idx = None
            for i in range(0, len(all_chains_tips_idx)):
                if vert_tip_idx == all_chains_tips_idx[i][0]:
                    opposite_vert_tip_idx = all_chains_tips_idx[i][1]
                if vert_tip_idx == all_chains_tips_idx[i][1]:
                    opposite_vert_tip_idx = all_chains_tips_idx[i][0]
    
            return opposite_vert_tip_idx
    
        #### Simplifies a spline and returns the new points coordinates.
        def simplify_spline(self, spline_coords, segments_num):
            simplified_spline = []
            points_between_segments = round(len(spline_coords) / segments_num)
    
            simplified_spline.append(spline_coords[0])
            for i in range(1, segments_num):
                simplified_spline.append(spline_coords[i * points_between_segments])
                        
            simplified_spline.append(spline_coords[len(spline_coords) - 1])
    
            return simplified_spline
        
        
        
        #### Cleans up the scene and gets it the same it was at the beginning, in case the script is interrupted in the middle of the execution.
        def cleanup_on_interruption(self):
            # If the original strokes curve comes from conversion from grease pencil and wasn't made by hand, delete it.
            if not self.using_external_curves:
                try:
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.data.objects[self.original_curve.name].select = True
                    bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
                    
                    bpy.ops.object.delete()
                except:
                    pass
                
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.main_object.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
    
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.original_curve.name].select = True
                bpy.data.objects[self.main_object.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
            
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
        
        
        
        #### Returns a list with the coords of the points distributed over the splines passed to this method according to the proportions parameter.
        def distribute_pts(self, surface_splines, proportions):
            # Calculate the length of each final surface spline.
            surface_splines_lengths = []
            surface_splines_parsed = []
            for sp_idx in range(0, len(surface_splines)):
                # Calculate spline length
                surface_splines_lengths.append(0)
                for i in range(0, len(surface_splines[sp_idx].bezier_points)):
                    if i == 0:
                        prev_p = surface_splines[sp_idx].bezier_points[i]
                    else:
                        p = surface_splines[sp_idx].bezier_points[i]
                        
                        edge_length = (prev_p.co - p.co).length
                        
                        surface_splines_lengths[sp_idx] += edge_length
                        
                        prev_p = p
    
            # Calculate vertex positions with appropriate edge proportions, and ordered, for each spline.
            for sp_idx in range(0, len(surface_splines)):
                surface_splines_parsed.append([])
                surface_splines_parsed[sp_idx].append(surface_splines[sp_idx].bezier_points[0].co)
    
                prev_p_co = surface_splines[sp_idx].bezier_points[0].co
                p_idx = 0
                for prop_idx in range(len(proportions) - 1):
                    target_length = surface_splines_lengths[sp_idx] * proportions[prop_idx]
    
    Eclectiel L's avatar
    Eclectiel L committed
                    
    
                    partial_segment_length = 0
                    
                    
                    finish_while = False
                    while True:
                        p_co = surface_splines[sp_idx].bezier_points[p_idx].co
                        
                        new_dist = (prev_p_co - p_co).length
                        
                        potential_segment_length = partial_segment_length + new_dist # The new distance that could have the partial segment if it is still shorter than the target length.
                        
                        
                        if potential_segment_length < target_length: # If the potential is still shorter, keep adding.
                            partial_segment_length = potential_segment_length
                            
                            p_idx += 1
                            prev_p_co = p_co
                            
                        elif potential_segment_length > target_length: # If the potential is longer than the target, calculate the target (a point between the last two points), and assign.
                            remaining_dist = target_length - partial_segment_length
                            vec = p_co - prev_p_co
                            vec.normalize()
                            intermediate_co = prev_p_co + (vec * remaining_dist)
                            
                            surface_splines_parsed[sp_idx].append(intermediate_co)
                            
                            partial_segment_length += remaining_dist
                            prev_p_co = intermediate_co
                            
                            finish_while = True
                            
                        elif potential_segment_length == target_length: # If the potential is equal to the target, assign.
                            surface_splines_parsed[sp_idx].append(p_co)
                            
                            prev_p_co = p_co
                            
                            finish_while = True
                            
                        if finish_while:
                            break
    
                # last point of the spline
                surface_splines_parsed[sp_idx].append(surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1].co)
            
            
            return surface_splines_parsed
        
        
        
        #### Counts the number of faces that belong to each edge.
        def edge_face_count(self, ob):
            ed_keys_count_dict = {}
            
            for face in ob.data.polygons:
                for ed_keys in face.edge_keys:
                    if not ed_keys in ed_keys_count_dict:
                        ed_keys_count_dict[ed_keys] = 1
                    else:
                        ed_keys_count_dict[ed_keys] += 1
            
            
            edge_face_count = []
            for i in range(len(ob.data.edges)):
                edge_face_count.append(0)
            
            for i in range(len(ob.data.edges)):
                ed = ob.data.edges[i]
    
                v1 = ed.vertices[0]
                v2 = ed.vertices[1]
    
                if (v1, v2) in ed_keys_count_dict:
                    edge_face_count[i] = ed_keys_count_dict[(v1, v2)]
                elif (v2, v1) in ed_keys_count_dict:
                    edge_face_count[i] = ed_keys_count_dict[(v2, v1)]
    
            return edge_face_count
        
        
        
        #### Fills with faces all the selected vertices which form empty triangles or quads.
        def fill_with_faces(self, object):
            all_selected_verts_count = self.main_object_selected_verts_count
    
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
    
            #### Calculate average length of selected edges.
            all_selected_verts = []
            original_sel_edges_count = 0
            for ed in object.data.edges:
                if object.data.vertices[ed.vertices[0]].select and object.data.vertices[ed.vertices[1]].select:
                    coords = []
                    coords.append(object.data.vertices[ed.vertices[0]].co)
                    coords.append(object.data.vertices[ed.vertices[1]].co)
                    
                    original_sel_edges_count += 1
                    
                    if not ed.vertices[0] in all_selected_verts:
                        all_selected_verts.append(ed.vertices[0])
                        
                    if not ed.vertices[1] in all_selected_verts:
                        all_selected_verts.append(ed.vertices[1])
                    
    
            tuple(all_selected_verts)
    
            
            #### Check if there is any edge selected. If not, interrupt the script.
            if original_sel_edges_count == 0 and all_selected_verts_count > 0:
                return 0
            
            
            
            #### Get all edges connected to selected verts.
            all_edges_around_sel_verts = []
            edges_connected_to_sel_verts = {}
            verts_connected_to_every_vert = {}
            for ed_idx in range(len(object.data.edges)):
                ed = object.data.edges[ed_idx]
                include_edge = False
    
                if ed.vertices[0] in all_selected_verts:
                    if not ed.vertices[0] in edges_connected_to_sel_verts:
                        edges_connected_to_sel_verts[ed.vertices[0]] = []
                    
                    edges_connected_to_sel_verts[ed.vertices[0]].append(ed_idx)
                    include_edge = True
    
                if ed.vertices[1] in all_selected_verts:
                    if not ed.vertices[1] in edges_connected_to_sel_verts:
                        edges_connected_to_sel_verts[ed.vertices[1]] = []
                    
                    edges_connected_to_sel_verts[ed.vertices[1]].append(ed_idx)
                    include_edge = True
    
                if include_edge == True:
                    all_edges_around_sel_verts.append(ed_idx)
    
                # Get all connected verts to each vert.
                if not ed.vertices[0] in verts_connected_to_every_vert:
                    verts_connected_to_every_vert[ed.vertices[0]] = []
                
                if not ed.vertices[1] in verts_connected_to_every_vert:
                    verts_connected_to_every_vert[ed.vertices[1]] = []
    
                verts_connected_to_every_vert[ed.vertices[0]].append(ed.vertices[1])
                verts_connected_to_every_vert[ed.vertices[1]].append(ed.vertices[0])
    
            
            
            
            #### Get all verts connected to faces.
            all_verts_part_of_faces = []
            all_edges_faces_count = []
            all_edges_faces_count += self.edge_face_count(object)
            
            # Get only the selected edges that have faces attached.
            count_faces_of_edges_around_sel_verts = {}
            selected_verts_with_faces = []
            for ed_idx in all_edges_around_sel_verts:
                count_faces_of_edges_around_sel_verts[ed_idx] = all_edges_faces_count[ed_idx]
    
                if all_edges_faces_count[ed_idx] > 0:
                    ed = object.data.edges[ed_idx]
                    
                    if not ed.vertices[0] in selected_verts_with_faces:
                        selected_verts_with_faces.append(ed.vertices[0])
                    
                    if not ed.vertices[1] in selected_verts_with_faces:
                        selected_verts_with_faces.append(ed.vertices[1])
            
                    all_verts_part_of_faces.append(ed.vertices[0])
                    all_verts_part_of_faces.append(ed.vertices[1])
            
            tuple(selected_verts_with_faces)
            
            
            
            #### Discard unneeded verts from calculations.
            participating_verts = []
            movable_verts = []
            for v_idx in all_selected_verts:
                vert_has_edges_with_one_face = False
                
                for ed_idx in edges_connected_to_sel_verts[v_idx]: # Check if the actual vert has at least one edge connected to only one face.
                    if count_faces_of_edges_around_sel_verts[ed_idx] == 1:
                        vert_has_edges_with_one_face = True
                
                # If the vert has two or less edges connected and the vert is not part of any face. Or the vert is part of any face and at least one of the connected edges has only one face attached to it.
                if (len(edges_connected_to_sel_verts[v_idx]) == 2 and not v_idx in all_verts_part_of_faces) or len(edges_connected_to_sel_verts[v_idx]) == 1 or (v_idx in all_verts_part_of_faces and vert_has_edges_with_one_face):
                    participating_verts.append(v_idx)
                    
                    if not v_idx in all_verts_part_of_faces:
                        movable_verts.append(v_idx)
            
            
            
            #### Remove from movable verts list those that are part of closed geometry (ie: triangles, quads)
            for mv_idx in movable_verts:
                freeze_vert = False
                mv_connected_verts = verts_connected_to_every_vert[mv_idx]
                
                for actual_v_idx in all_selected_verts:
                    count_shared_neighbors = 0
                    checked_verts = []
                    
                    for mv_conn_v_idx in mv_connected_verts:
                        if mv_idx != actual_v_idx:
                            if mv_conn_v_idx in verts_connected_to_every_vert[actual_v_idx] and not mv_conn_v_idx in checked_verts:
                                count_shared_neighbors += 1
                                checked_verts.append(mv_conn_v_idx)
                                
                                
                                if actual_v_idx in mv_connected_verts:
                                    freeze_vert = True
                                    break
                                
                            if count_shared_neighbors == 2:
                                freeze_vert = True
                                break
                    
                    if freeze_vert:
                        break
                
                if freeze_vert:
                    movable_verts.remove(mv_idx)
    
            
            
            #### Calculate merge distance for participating verts.
            shortest_edge_length = None
            for ed in object.data.edges:
                if ed.vertices[0] in movable_verts and ed.vertices[1] in movable_verts:
                    v1 = object.data.vertices[ed.vertices[0]]
                    v2 = object.data.vertices[ed.vertices[1]]
                    
                    length = (v1.co - v2.co).length
                    
                    if shortest_edge_length == None:
                        shortest_edge_length = length
                    else:
                        if length < shortest_edge_length:
                            shortest_edge_length = length
                
            if shortest_edge_length != None:
                edges_merge_distance = shortest_edge_length * 0.5
            else:
                edges_merge_distance = 0
            
            
            
            
            #### Get together the verts near enough. They will be merged later.
            remaining_verts = []
            remaining_verts += participating_verts
            for v1_idx in participating_verts:
                if v1_idx in remaining_verts and v1_idx in movable_verts:
                    verts_to_merge = []
                    coords_verts_to_merge = {}
                    
                    verts_to_merge.append(v1_idx)
                    
                    v1_co = object.data.vertices[v1_idx].co
                    coords_verts_to_merge[v1_idx] = (v1_co[0], v1_co[1], v1_co[2])
                    
                    
                    for v2_idx in remaining_verts:
                        if v1_idx != v2_idx:
                            v2_co = object.data.vertices[v2_idx].co
    
                            dist = (v1_co - v2_co).length
    
                            if dist <= edges_merge_distance: # Add the verts which are near enough.
                                verts_to_merge.append(v2_idx)
                                
                                coords_verts_to_merge[v2_idx] = (v2_co[0], v2_co[1], v2_co[2])
                                
                    
                    for vm_idx in verts_to_merge:
                        remaining_verts.remove(vm_idx)
                    
                    
                    if len(verts_to_merge) > 1:
                        # Calculate middle point of the verts to merge.
                        sum_x_co = 0
                        sum_y_co = 0
                        sum_z_co = 0
                        movable_verts_to_merge_count = 0
                        for i in range(len(verts_to_merge)):
                            if verts_to_merge[i] in movable_verts:
                                v_co = object.data.vertices[verts_to_merge[i]].co
                                
                                sum_x_co += v_co[0]
                                sum_y_co += v_co[1]
                                sum_z_co += v_co[2]
                                
                                movable_verts_to_merge_count += 1
                        
                        middle_point_co = [sum_x_co / movable_verts_to_merge_count, sum_y_co / movable_verts_to_merge_count, sum_z_co / movable_verts_to_merge_count]
                        
                        
                        # Check if any vert to be merged is not movable.
                        shortest_dist = None
                        are_verts_not_movable = False
                        verts_not_movable = []
                        for v_merge_idx in verts_to_merge:
                            if v_merge_idx in participating_verts and not v_merge_idx in movable_verts:
                                are_verts_not_movable = True
                                verts_not_movable.append(v_merge_idx)
                        
                        if are_verts_not_movable:
                            # Get the vert connected to faces, that is nearest to the middle point of the movable verts.
                            shortest_dist = None
                            for vcf_idx in verts_not_movable:
                                    dist = abs((object.data.vertices[vcf_idx].co - mathutils.Vector(middle_point_co)).length)
                                    
                                    if shortest_dist == None:
                                        shortest_dist = dist
                                        nearest_vert_idx = vcf_idx
                                    else:
                                        if dist < shortest_dist:
                                            shortest_dist = dist
                                            nearest_vert_idx = vcf_idx
                                        
                            coords = object.data.vertices[nearest_vert_idx].co
                            target_point_co = [coords[0], coords[1], coords[2]]                                    
                        else:
                             target_point_co = middle_point_co
                           
                        
                        # Move verts to merge to the middle position.
                        for v_merge_idx in verts_to_merge:
                            if v_merge_idx in movable_verts: # Only move the verts that are not part of faces.
                                object.data.vertices[v_merge_idx].co[0] = target_point_co[0]
                                object.data.vertices[v_merge_idx].co[1] = target_point_co[1]
                                object.data.vertices[v_merge_idx].co[2] = target_point_co[2]
                    
                    
            
            #### Perform "Remove Doubles" to weld all the disconnected verts
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
    
            bpy.ops.mesh.remove_doubles(threshold=0.0001)
    
            
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
            
            
            #### Get all the definitive selected edges, after weldding.
            selected_edges = []
            edges_per_vert = {} # Number of faces of each selected edge.
            for ed in object.data.edges:
                if object.data.vertices[ed.vertices[0]].select and object.data.vertices[ed.vertices[1]].select:
                    selected_edges.append(ed.index)
                    
                    # Save all the edges that belong to each vertex.
                    if not ed.vertices[0] in edges_per_vert:
                        edges_per_vert[ed.vertices[0]] = []
                    
                    if not ed.vertices[1] in edges_per_vert:
                        edges_per_vert[ed.vertices[1]] = []
                    
                    edges_per_vert[ed.vertices[0]].append(ed.index)
                    edges_per_vert[ed.vertices[1]].append(ed.index)
            
            # Check if all the edges connected to each vert have two faces attached to them. To discard them later and make calculations faster.
            a = []
            a += self.edge_face_count(object)
            tuple(a)
            verts_surrounded_by_faces = {}
            for v_idx in edges_per_vert:
                edges = edges_per_vert[v_idx]
    
                edges_with_two_faces_count = 0
                for ed_idx in edges_per_vert[v_idx]:
                    if a[ed_idx] == 2:
                        edges_with_two_faces_count += 1
    
                if edges_with_two_faces_count == len(edges_per_vert[v_idx]):
                    verts_surrounded_by_faces[v_idx] = True
                else:
                    verts_surrounded_by_faces[v_idx] = False
                    
                    
            #### Get all the selected vertices.
            selected_verts_idx = []
            for v in object.data.vertices:
                if v.select:
                    selected_verts_idx.append(v.index)
            
            
            #### Get all the faces of the object.
            all_object_faces_verts_idx = []
            for face in object.data.polygons:
                face_verts = []
                face_verts.append(face.vertices[0])
                face_verts.append(face.vertices[1])
                face_verts.append(face.vertices[2])
                
                if len(face.vertices) == 4:
                    face_verts.append(face.vertices[3])
                    
                all_object_faces_verts_idx.append(face_verts)
                            
                
            #### Deselect all vertices.
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
            
            
            
            #### Make a dictionary with the verts related to each vert.
            related_key_verts = {}
            for ed_idx in selected_edges:
                ed = object.data.edges[ed_idx]
                
                if not verts_surrounded_by_faces[ed.vertices[0]]:
                    if not ed.vertices[0] in related_key_verts:
                        related_key_verts[ed.vertices[0]] = []
                    
                    if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
                        related_key_verts[ed.vertices[0]].append(ed.vertices[1])
                
                if not verts_surrounded_by_faces[ed.vertices[1]]:
                    if not ed.vertices[1] in related_key_verts:
                        related_key_verts[ed.vertices[1]] = []
                    
                    if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
                        related_key_verts[ed.vertices[1]].append(ed.vertices[0])
            
            
                
            #### Get groups of verts forming each face.
            faces_verts_idx = [] 
            for v1 in related_key_verts: # verts-1 .... 
                for v2 in related_key_verts: # verts-2
                    if v1 != v2:
                        related_verts_in_common = []
                        v2_in_rel_v1 = False
                        v1_in_rel_v2 = False
                        for rel_v1 in related_key_verts[v1]:
                            if rel_v1 in related_key_verts[v2]: # Check if related verts of verts-1 are related verts of verts-2.
                                related_verts_in_common.append(rel_v1)
                            
                        if v2 in related_key_verts[v1]:
                            v2_in_rel_v1 = True
                                
                        if v1 in related_key_verts[v2]:
                            v1_in_rel_v2 = True
                        
                        
                        repeated_face = False
                        # If two verts have two related verts in common, they form a quad.
                        if len(related_verts_in_common) == 2:
                            # Check if the face is already saved.
                            all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
                            
                            
                            for f_verts in all_faces_to_check_idx:
                                repeated_verts = 0
                                
                                if len(f_verts) == 4:
                                    if v1 in f_verts: repeated_verts += 1
                                    if v2 in f_verts: repeated_verts += 1
                                    if related_verts_in_common[0] in f_verts: repeated_verts += 1
                                    if related_verts_in_common[1] in f_verts: repeated_verts += 1
                                    
                                    if repeated_verts == len(f_verts):
                                        repeated_face = True
                                        break
                            
                            if not repeated_face:
                                faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
                            
                        elif v2_in_rel_v1 and v1_in_rel_v2 and len(related_verts_in_common) == 1: # If Two verts have one related vert in common and they are related to each other, they form a triangle.
                            # Check if the face is already saved.
                            all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
    
                            for f_verts in all_faces_to_check_idx:
                                repeated_verts = 0
                                
                                if len(f_verts) == 3:
                                    if v1 in f_verts: repeated_verts += 1
                                    if v2 in f_verts: repeated_verts += 1
                                    if related_verts_in_common[0] in f_verts: repeated_verts += 1
                                    
                                    if repeated_verts == len(f_verts):
                                        repeated_face = True
                                        break
                            
                            if not repeated_face:
                                faces_verts_idx.append([v1, related_verts_in_common[0], v2])
                    
    
            #### Keep only the faces that don't overlap by ignoring quads that overlap with two adjacent triangles.
            faces_to_not_include_idx = [] # Indices of faces_verts_idx to eliminate.
            all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
            for i in range(len(faces_verts_idx)):
                for t in range(len(all_faces_to_check_idx)):
                    if i != t:
                        verts_in_common = 0
                        
                        if len(faces_verts_idx[i]) == 4 and len(all_faces_to_check_idx[t]) == 3:
                            for v_idx in all_faces_to_check_idx[t]:
                                if v_idx in faces_verts_idx[i]:
                                    verts_in_common += 1
                                    
                            if verts_in_common == 3: # If it doesn't have all it's vertices repeated in the other face.
                                if not i in faces_to_not_include_idx:
                                    faces_to_not_include_idx.append(i)
            
            
            #### Build faces discarding the ones in faces_to_not_include.
            me = object.data
            bm = bmesh.new()
            bm.from_mesh(me)
            
            num_faces_created = 0
            for i in range(len(faces_verts_idx)):
                if not i in faces_to_not_include_idx:
                    bm.faces.new([ bm.verts[v] for v in faces_verts_idx[i] ])
                    
                    num_faces_created += 1
            
            bm.to_mesh(me)
            bm.free()
            
            
            
            for v_idx in selected_verts_idx:
                self.main_object.data.vertices[v_idx].select = True
            
            
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
            bpy.ops.mesh.normals_make_consistent(inside=False)
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
            
    
            return num_faces_created
            
            
        
        #### Crosshatch skinning.
        def crosshatch_surface_invoke(self, ob_original_splines):
            self.is_crosshatch = False
            self.crosshatch_merge_distance = 0
            
            
            objects_to_delete = [] # duplicated strokes to be deleted.
            
            # If the main object uses modifiers deactivate them temporarily until the surface is joined. (without this the surface verts merging with the main object doesn't work well)