Skip to content
Snippets Groups Projects
mesh_bsurfaces.py 165 KiB
Newer Older
  • Learn to ignore specific revisions
  •                     if selection_type == "TWO_NOT_CONNECTED":
                            self.selection_V2_exists = True
    
                            first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
                    else:
                        self.selection_V_is_closed = True
                        closing_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
    
                        # Get the neighbors of the first (unselected) vert of the closed selection U.
                        vert_neighbors = []
                        for verts in single_unselected_verts_and_neighbors:
                            if verts[0] == nearest_tip_to_first_st_first_pt_idx:
                                vert_neighbors.append(verts[1])
                                vert_neighbors.append(verts[2])
                                break
    
                        verts_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, vert_neighbors[0], middle_vertex_idx, None)
    
                        for i in range(0, len(verts_V)):
                            if verts_V[i].index == nearest_vert_to_second_st_first_pt_idx:
                                if i >= len(verts_V) / 2: # If the vertex nearest to the first point of the second stroke is in the first half of the selected verts.
                                    first_vert_V_idx = vert_neighbors[1]
                                    break
                                else:
                                    first_vert_V_idx = vert_neighbors[0]
                                    break
    
                    if selection_type == "TWO_NOT_CONNECTED":
                        self.selection_V2_exists = True
    
                        if nearest_tip_to_first_st_last_pt_idx not in single_unselected_verts or nearest_tip_to_first_st_last_pt_idx == middle_vertex_idx: # If the second selection is not closed.
                            self.selection_V2_is_closed = False
                            first_neighbor_V2_idx = None
                            closing_vert_V2_idx = None
    
                            first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
    
                        else:
                            self.selection_V2_is_closed = True
                            closing_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
    
                            # Get the neighbors of the first (unselected) vert of the closed selection U.
                            vert_neighbors = []
                            for verts in single_unselected_verts_and_neighbors:
                                if verts[0] == nearest_tip_to_first_st_last_pt_idx:
                                    vert_neighbors.append(verts[1])
                                    vert_neighbors.append(verts[2])
                                    break
    
                            verts_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, vert_neighbors[0], middle_vertex_idx, None)
    
                            for i in range(0, len(verts_V2)):
                                if verts_V2[i].index == nearest_vert_to_second_st_last_pt_idx:
                                    if i >= len(verts_V2) / 2: # If the vertex nearest to the first point of the second stroke is in the first half of the selected verts.
                                        first_vert_V2_idx = vert_neighbors[1]
                                        break
                                    else:
                                        first_vert_V2_idx = vert_neighbors[0]
                                        break
    
                    else:
                        self.selection_V2_exists = False
    
                else:
                    self.selection_U_exists = True
                    self.selection_V_exists = False
                    if nearest_tip_to_first_st_first_pt_idx not in single_unselected_verts or nearest_tip_to_first_st_first_pt_idx == middle_vertex_idx: # If the first selection is not closed.
                        self.selection_U_is_closed = False
                        first_neighbor_U_idx = None
                        closing_vert_U_idx = None
    
                        points_tips = []
                        points_tips.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_idx].co)
                        points_tips.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_opposite_idx].co)
    
                        points_first_stroke_tips = []
                        points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
                        points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[len(self.main_splines.data.splines[0].bezier_points) - 1].co)
    
                        vec_A = points_tips[0] - points_tips[1]
                        vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
    
                        # Compare the direction of the selection and the first grease pencil stroke to determine which is the "first" vertex of the selection.
                        if vec_A.dot(vec_B) < 0:
                            first_vert_U_idx = nearest_tip_to_first_st_first_pt_opposite_idx
                        else:
                            first_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
    
                    else:
                        self.selection_U_is_closed = True
                        closing_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
    
                        # Get the neighbors of the first (unselected) vert of the closed selection U.
                        vert_neighbors = []
                        for verts in single_unselected_verts_and_neighbors:
                            if verts[0] == nearest_tip_to_first_st_first_pt_idx:
                                vert_neighbors.append(verts[1])
                                vert_neighbors.append(verts[2])
                                break
    
                        points_first_and_neighbor = []
                        points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_idx].co)
                        points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[vert_neighbors[0]].co)
    
                        points_first_stroke_tips = []
                        points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
                        points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[1].co)
    
                        vec_A = points_first_and_neighbor[0] - points_first_and_neighbor[1]
                        vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
    
                        # Compare the direction of the selection and the first grease pencil stroke to determine which is the vertex neighbor to the first vertex (unselected) of the closed selection. This will determine the direction of the closed selection.
                        if vec_A.dot(vec_B) < 0:
                            first_vert_U_idx = vert_neighbors[1]
                        else:
                            first_vert_U_idx = vert_neighbors[0]
    
                    if selection_type == "TWO_NOT_CONNECTED":
                        self.selection_U2_exists = True
    
                        if nearest_tip_to_last_st_first_pt_idx not in single_unselected_verts or nearest_tip_to_last_st_first_pt_idx == middle_vertex_idx: # If the second selection is not closed.
                            self.selection_U2_is_closed = False
                            first_neighbor_U2_idx = None
                            closing_vert_U2_idx = None
    
                            first_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
    
                        else:
                            self.selection_U2_is_closed = True
                            closing_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
    
                            # Get the neighbors of the first (unselected) vert of the closed selection U.
                            vert_neighbors = []
                            for verts in single_unselected_verts_and_neighbors:
                                if verts[0] == nearest_tip_to_last_st_first_pt_idx:
                                    vert_neighbors.append(verts[1])
                                    vert_neighbors.append(verts[2])
                                    break
    
                            points_first_and_neighbor = []
                            points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_last_st_first_pt_idx].co)
                            points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[vert_neighbors[0]].co)
    
                            points_last_stroke_tips = []
                            points_last_stroke_tips.append(self.main_splines.data.splines[len(self.main_splines.data.splines) - 1].bezier_points[0].co)
                            points_last_stroke_tips.append(self.main_splines.data.splines[len(self.main_splines.data.splines) - 1].bezier_points[1].co)
    
                            vec_A = points_first_and_neighbor[0] - points_first_and_neighbor[1]
                            vec_B = points_last_stroke_tips[0] - points_last_stroke_tips[1]
    
                            # Compare the direction of the selection and the last grease pencil stroke to determine which is the vertex neighbor to the first vertex (unselected) of the closed selection. This will determine the direction of the closed selection.
                            if vec_A.dot(vec_B) < 0:
                                first_vert_U2_idx = vert_neighbors[1]
                            else:
                                first_vert_U2_idx = vert_neighbors[0]
    
                    else:
                        self.selection_U2_exists = False
    
            elif selection_type == "NO_SELECTION":
                self.selection_U_exists = False
                self.selection_V_exists = False
    
            #### Get an ordered list of the vertices of Selection-U.
            verts_ordered_U = []
            if self.selection_U_exists:
                verts_ordered_U = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U_idx, middle_vertex_idx, closing_vert_U_idx)
                verts_ordered_U_indices = [x.index for x in verts_ordered_U]
    
            #### Get an ordered list of the vertices of Selection-U2.
            verts_ordered_U2 = []
            if self.selection_U2_exists:
                verts_ordered_U2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U2_idx, middle_vertex_idx, closing_vert_U2_idx)
                verts_ordered_U2_indices = [x.index for x in verts_ordered_U2]
    
            #### Get an ordered list of the vertices of Selection-V.
            verts_ordered_V = []
            if self.selection_V_exists:
                verts_ordered_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V_idx, middle_vertex_idx, closing_vert_V_idx)
                verts_ordered_V_indices = [x.index for x in verts_ordered_V]
    
            #### Get an ordered list of the vertices of Selection-V2.
            verts_ordered_V2 = []
            if self.selection_V2_exists:
                verts_ordered_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V2_idx, middle_vertex_idx, closing_vert_V2_idx)
                verts_ordered_V2_indices = [x.index for x in verts_ordered_V2]
    
            #### Check if when there are two-not-connected selections both have the same number of verts. If not terminate the script.
            if ((self.selection_U2_exists and len(verts_ordered_U) != len(verts_ordered_U2)) or (self.selection_V2_exists and len(verts_ordered_V) != len(verts_ordered_V2))):
                # Display a warning.
                self.report({'WARNING'}, "Both selections must have the same number of edges")
    
                self.cleanup_on_interruption()
    
                self.stopping_errors = True
    
                return{'CANCELLED'}
    
            #### Calculate edges U proportions.
    
            # Sum selected edges U lengths.
            edges_lengths_U = []
            edges_lengths_sum_U = 0
    
            if self.selection_U_exists:
                edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(self.main_object, verts_ordered_U)
    
            if self.selection_U2_exists:
                edges_lengths_U2, edges_lengths_sum_U2 = self.get_chain_length(self.main_object, verts_ordered_U2)
    
            # Sum selected edges V lengths.
            edges_lengths_V = []
            edges_lengths_sum_V = 0
    
            if self.selection_V_exists:
                edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(self.main_object, verts_ordered_V)
    
            if self.selection_V2_exists:
                edges_lengths_V2, edges_lengths_sum_V2 = self.get_chain_length(self.main_object, verts_ordered_V2)
    
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = bpy.context.scene.SURFSK_precision)
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            # Proportions U.
            edges_proportions_U = []
            edges_proportions_U = self.get_edges_proportions(edges_lengths_U, edges_lengths_sum_U, self.selection_U_exists, self.edges_U)
            verts_count_U = len(edges_proportions_U) + 1
    
            if self.selection_U2_exists:
                edges_proportions_U2 = []
                edges_proportions_U2 = self.get_edges_proportions(edges_lengths_U2, edges_lengths_sum_U2, self.selection_U2_exists, self.edges_V)
                verts_count_U2 = len(edges_proportions_U2) + 1
    
            # Proportions V.
            edges_proportions_V = []
            edges_proportions_V = self.get_edges_proportions(edges_lengths_V, edges_lengths_sum_V, self.selection_V_exists, self.edges_V)
            verts_count_V = len(edges_proportions_V) + 1
    
            if self.selection_V2_exists:
                edges_proportions_V2 = []
                edges_proportions_V2 = self.get_edges_proportions(edges_lengths_V2, edges_lengths_sum_V2, self.selection_V2_exists, self.edges_V)
                verts_count_V2 = len(edges_proportions_V2) + 1
    
            #### Cyclic Follow: simplify sketched curves, make them Cyclic, and complete the actual sketched curves with a "closing segment".
            if self.cyclic_follow and not self.selection_V_exists and not ((self.selection_U_exists and not self.selection_U_is_closed) or (self.selection_U2_exists and not self.selection_U2_is_closed)):
                simplified_spline_coords = []
                simplified_curve = []
                ob_simplified_curve = []
                splines_first_v_co = []
                for i in range(len(self.main_splines.data.splines)):
                    # Create a curve object for the actual spline "cyclic extension".
                    simplified_curve.append(bpy.data.curves.new('SURFSKIO_simpl_crv', 'CURVE'))
                    ob_simplified_curve.append(bpy.data.objects.new('SURFSKIO_simpl_crv', simplified_curve[i]))
                    bpy.context.scene.objects.link(ob_simplified_curve[i])
    
                    simplified_curve[i].dimensions = "3D"
    
                    spline_coords = []
                    for bp in self.main_splines.data.splines[i].bezier_points:
                        spline_coords.append(bp.co)
    
                    # Simplification.
                    simplified_spline_coords.append(self.simplify_spline(spline_coords, 5))
    
                    # Get the coordinates of the first vert of the actual spline.
                    splines_first_v_co.append(simplified_spline_coords[i][0])
    
                    # Generate the spline.
                    spline = simplified_curve[i].splines.new('BEZIER')
                    spline.bezier_points.add(len(simplified_spline_coords[i]) - 1) # less one because one point is added when the spline is created.
                    for p in range(0, len(simplified_spline_coords[i])):
                        spline.bezier_points[p].co = simplified_spline_coords[i][p]
    
                    spline.use_cyclic_u = True
    
                    spline_bp_count = len(spline.bezier_points)
    
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.data.objects[ob_simplified_curve[i].name].select = True
                    bpy.context.scene.objects.active = bpy.context.scene.objects[ob_simplified_curve[i].name]
    
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
                    bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
                    bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                    # Select the "closing segment", and subdivide it.
                    ob_simplified_curve[i].data.splines[0].bezier_points[0].select_control_point = True
                    ob_simplified_curve[i].data.splines[0].bezier_points[0].select_left_handle = True
                    ob_simplified_curve[i].data.splines[0].bezier_points[0].select_right_handle = True
    
                    ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_control_point = True
                    ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_left_handle = True
                    ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_right_handle = True
    
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    segments = sqrt((ob_simplified_curve[i].data.splines[0].bezier_points[0].co - ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].co).length / self.average_gp_segment_length)
                    for t in range(2):
                        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = segments)
    
                    # Delete the other vertices and make it non-cyclic to keep only the needed verts of the "closing segment".
                    bpy.ops.curve.select_all(action = 'INVERT')
    
                    bpy.ops.curve.delete(type='VERT')
    
                    ob_simplified_curve[i].data.splines[0].use_cyclic_u = False
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                    # Add the points of the "closing segment" to the original curve from grease pencil stroke.
                    first_new_index = len(self.main_splines.data.splines[i].bezier_points)
                    self.main_splines.data.splines[i].bezier_points.add(len(ob_simplified_curve[i].data.splines[0].bezier_points) - 1)
                    for t in range(1, len(ob_simplified_curve[i].data.splines[0].bezier_points)):
                        self.main_splines.data.splines[i].bezier_points[t - 1 + first_new_index].co = ob_simplified_curve[i].data.splines[0].bezier_points[t].co
    
                    # Delete the temporal curve.
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.data.objects[ob_simplified_curve[i].name].select = True
                    bpy.context.scene.objects.active = bpy.context.scene.objects[ob_simplified_curve[i].name]
    
                    bpy.ops.object.delete()
    
            #### Get the coords of the points distributed along the sketched strokes, with proportions-U of the first selection.
            pts_on_strokes_with_proportions_U = self.distribute_pts(self.main_splines.data.splines, edges_proportions_U)
    
            sketched_splines_parsed = []
    
            if self.selection_U2_exists:
                # Initialize the multidimensional list with the proportions of all the segments.
                proportions_loops_crossing_strokes = []
                for i in range(len(pts_on_strokes_with_proportions_U)):
                    proportions_loops_crossing_strokes.append([])
    
                    for t in range(len(pts_on_strokes_with_proportions_U[0])):
                        proportions_loops_crossing_strokes[i].append(None)
    
                # Calculate the proportions of each segment of the loops-U from pts_on_strokes_with_proportions_U.
                for lp in range(len(pts_on_strokes_with_proportions_U[0])):
                    loop_segments_lengths = []
    
                    for st in range(len(pts_on_strokes_with_proportions_U)):
                        if st == 0: # When on the first stroke, add the segment from the selection to the dirst stroke.
                            loop_segments_lengths.append(((self.main_object.matrix_world * verts_ordered_U[lp].co) - pts_on_strokes_with_proportions_U[0][lp]).length)
    
                        if st != len(pts_on_strokes_with_proportions_U) - 1: # For all strokes except for the last, calculate the distance from the actual stroke to the next.
                            loop_segments_lengths.append((pts_on_strokes_with_proportions_U[st][lp] - pts_on_strokes_with_proportions_U[st + 1][lp]).length)
    
                        if st == len(pts_on_strokes_with_proportions_U) - 1: # When on the last stroke, add the segments from the last stroke to the second selection.
                            loop_segments_lengths.append((pts_on_strokes_with_proportions_U[st][lp] - (self.main_object.matrix_world * verts_ordered_U2[lp].co)).length)
    
                    # Calculate full loop length.
                    loop_seg_lengths_sum = 0
                    for i in range(len(loop_segments_lengths)):
                        loop_seg_lengths_sum += loop_segments_lengths[i]
    
                    # Fill the multidimensional list with the proportions of all the segments.
                    for st in range(len(pts_on_strokes_with_proportions_U)):
                        proportions_loops_crossing_strokes[st][lp] = loop_segments_lengths[st] / loop_seg_lengths_sum
    
                # Calculate proportions for each stroke.
                for st in range(len(pts_on_strokes_with_proportions_U)):
                    actual_stroke_spline = []
                    actual_stroke_spline.append(self.main_splines.data.splines[st]) # Needs to be a list for the "distribute_pts" method.
    
                    # Calculate the proportions for the actual stroke.
                    actual_edges_proportions_U = []
                    for i in range(len(edges_proportions_U)):
                        proportions_sum = 0
    
                        # Sum the proportions of this loop up to the actual.
                        for t in range(0, st + 1):
                            proportions_sum += proportions_loops_crossing_strokes[t][i]
    
                        actual_edges_proportions_U.append(edges_proportions_U[i] - ((edges_proportions_U[i] - edges_proportions_U2[i]) * proportions_sum))  # i + 1, because proportions_loops_crossing_strokes refers to loops, and the proportions refer to edges, so we start at the element 1 of proportions_loops_crossing_strokes instead of element 0.
    
                    points_actual_spline = self.distribute_pts(actual_stroke_spline, actual_edges_proportions_U)
                    sketched_splines_parsed.append(points_actual_spline[0])
    
            else:
                sketched_splines_parsed = pts_on_strokes_with_proportions_U
    
            #### If the selection type is "TWO_NOT_CONNECTED" replace the points of the last spline with the points in the "target" selection.
            if selection_type == "TWO_NOT_CONNECTED":
                if self.selection_U2_exists:
                    for i in range(0, len(sketched_splines_parsed[len(sketched_splines_parsed) - 1])):
                        sketched_splines_parsed[len(sketched_splines_parsed) - 1][i] = self.main_object.matrix_world * verts_ordered_U2[i].co
    
            #### Create temporary curves along the "control-points" found on the sketched curves and the mesh selection.
            mesh_ctrl_pts_name = "SURFSKIO_ctrl_pts"
            me = bpy.data.meshes.new(mesh_ctrl_pts_name)
            ob_ctrl_pts = bpy.data.objects.new(mesh_ctrl_pts_name, me)
            ob_ctrl_pts.data = me
            bpy.context.scene.objects.link(ob_ctrl_pts)
    
            cyclic_loops_U = []
            first_verts = []
            second_verts = []
            last_verts = []
            for i in range(0, verts_count_U):
                vert_num_in_spline = 1
    
                if self.selection_U_exists:
                    ob_ctrl_pts.data.vertices.add(1)
                    last_v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
                    last_v.co = self.main_object.matrix_world * verts_ordered_U[i].co
    
                    vert_num_in_spline += 1
    
                for t in range(0, len(sketched_splines_parsed)):
                    ob_ctrl_pts.data.vertices.add(1)
                    v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
                    v.co = sketched_splines_parsed[t][i]
    
                    if vert_num_in_spline > 1:
                        ob_ctrl_pts.data.edges.add(1)
                        ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[0] = len(ob_ctrl_pts.data.vertices) - 2
                        ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[1] = len(ob_ctrl_pts.data.vertices) - 1
    
                    if t == 0:
                        first_verts.append(v.index)
    
                    if t == 1:
                        second_verts.append(v.index)
    
                    if t == len(sketched_splines_parsed) - 1:
                        last_verts.append(v.index)
    
                    vert_num_in_spline += 1
    
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[ob_ctrl_pts.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
    
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            #### Determine which loops-U will be "Cyclic".
            for i in range(0, len(first_verts)):
                if self.automatic_join and not self.cyclic_cross and selection_type != "TWO_CONNECTED" and len(self.main_splines.data.splines) >= 3: # When there is Cyclic Cross there is no need of Automatic Join, (and there are at least three strokes).
                    v = ob_ctrl_pts.data.vertices
    
                    first_point_co = v[first_verts[i]].co
                    second_point_co = v[second_verts[i]].co
                    last_point_co = v[last_verts[i]].co
    
                    # Coordinates of the point in the center of both the first and last verts.
                    verts_center_co = [(first_point_co[0] + last_point_co[0]) / 2, (first_point_co[1] + last_point_co[1]) / 2, (first_point_co[2] + last_point_co[2]) / 2]
    
                    vec_A = second_point_co - first_point_co
                    vec_B = second_point_co - mathutils.Vector(verts_center_co)
    
                    # Calculate the length of the first segment of the loop, and the length it would have after moving the first vert to the middle position between first and last.
                    length_original = (second_point_co - first_point_co).length
                    length_target = (second_point_co - mathutils.Vector(verts_center_co)).length
    
                    angle = vec_A.angle(vec_B) / math.pi
    
                    if length_target <= length_original * 1.03 * self.join_stretch_factor and angle <= 0.008 * self.join_stretch_factor and not self.selection_U_exists: # If the target length doesn't stretch too much, and the its angle doesn't change to much either.
                        cyclic_loops_U.append(True)
    
                        # Move the first vert to the center coordinates.
                        ob_ctrl_pts.data.vertices[first_verts[i]].co = verts_center_co
    
                        # Select the last verts from Cyclic loops, for later deletion all at once.
                        v[last_verts[i]].select = True
    
                    else:
                        cyclic_loops_U.append(False)
    
                else:
                    if self.cyclic_cross and not self.selection_U_exists and not ((self.selection_V_exists and not self.selection_V_is_closed) or (self.selection_V2_exists and not self.selection_V2_is_closed)): # If "Cyclic Cross" is active then "all" crossing curves become cyclic.
                        cyclic_loops_U.append(True)
                    else:
                        cyclic_loops_U.append(False)
    
            # The cyclic_loops_U list needs to be reversed.
            cyclic_loops_U.reverse()
    
            # Delete the previously selected (last_)verts.
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.mesh.delete('INVOKE_REGION_WIN', type='VERT')
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            # Create curves from control points.
            bpy.ops.object.convert('INVOKE_REGION_WIN', target='CURVE', keep_original=False)
            ob_curves_surf = bpy.context.scene.objects.active
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type='BEZIER')
            bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
    
            # Make Cyclic the splines designated as Cyclic.
            for i in range(0, len(cyclic_loops_U)):
                ob_curves_surf.data.splines[i].use_cyclic_u = cyclic_loops_U[i]
    
            #### Get the coords of all points on first loop-U, for later comparison with its subdivided version, to know which points of the loops-U are crossed by the original strokes. The indices wiil be the same for the other loops-U.
            if self.loops_on_strokes:
                coords_loops_U_control_points = []
                for p in ob_ctrl_pts.data.splines[0].bezier_points:
                    coords_loops_U_control_points.append(["%.4f" % p.co[0], "%.4f" % p.co[1], "%.4f" % p.co[2]])
    
                tuple(coords_loops_U_control_points)
    
            # Calculate number of edges-V in case option "Loops on strokes" is active or inactive.
            if self.loops_on_strokes and not self.selection_V_exists:
                    edges_V_count = len(self.main_splines.data.splines) * self.edges_V
            else:
                edges_V_count = len(edges_proportions_V)
    
            # The Follow precision will vary depending on the number of Follow face-loops.
            precision_multiplier = round(2 + (edges_V_count / 15))
    
            curve_cuts = bpy.context.scene.SURFSK_precision * precision_multiplier
    
            # Subdivide the curves.
            bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = curve_cuts)
    
            # The verts position shifting that happens with splines subdivision. For later reorder splines points.
            verts_position_shift = curve_cuts + 1
    
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            # Reorder coordinates of the points of each spline to put the first point of the spline starting at the position it was the first point before sudividing the curve. And make a new curve object per spline (to handle memory better later).
            splines_U_objects = []
            for i in range(len(ob_curves_surf.data.splines)):
                spline_U_curve = bpy.data.curves.new('SURFSKIO_spline_U_' + str(i), 'CURVE')
                ob_spline_U = bpy.data.objects.new('SURFSKIO_spline_U_' + str(i), spline_U_curve)
                bpy.context.scene.objects.link(ob_spline_U)
    
                spline_U_curve.dimensions = "3D"
    
                # Add points to the spline in the new curve object.
                ob_spline_U.data.splines.new('BEZIER')
                for t in range(len(ob_curves_surf.data.splines[i].bezier_points)):
                    if cyclic_loops_U[i] == True and not self.selection_U_exists: # If the loop is cyclic.
                        if t + verts_position_shift <= len(ob_curves_surf.data.splines[i].bezier_points) - 1:
                            point_index = t + verts_position_shift
                        else:
                            point_index = t + verts_position_shift - len(ob_curves_surf.data.splines[i].bezier_points)
                    else:
                        point_index = t
    
                    if t > 0: # to avoid adding the first point since it's added when the spline is created.
                        ob_spline_U.data.splines[0].bezier_points.add(1)
                    ob_spline_U.data.splines[0].bezier_points[t].co = ob_curves_surf.data.splines[i].bezier_points[point_index].co
    
                if cyclic_loops_U[i] == True and not self.selection_U_exists: # If the loop is cyclic.
                    # Add a last point at the same location as the first one.
                    ob_spline_U.data.splines[0].bezier_points.add(1)
                    ob_spline_U.data.splines[0].bezier_points[len(ob_spline_U.data.splines[0].bezier_points) - 1].co = ob_spline_U.data.splines[0].bezier_points[0].co
                else:
                    ob_spline_U.data.splines[0].use_cyclic_u = False
    
                splines_U_objects.append(ob_spline_U)
    
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[ob_spline_U.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[ob_spline_U.name]
    
            #### When option "Loops on strokes" is active each "Cross" loop will have its own proportions according to where the original strokes "touch" them.
            if self.loops_on_strokes:
                # Get the indices of points where the original strokes "touch" loops-U.
                points_U_crossed_by_strokes = []
                for i in range(len(splines_U_objects[0].data.splines[0].bezier_points)):
                    bp = splines_U_objects[0].data.splines[0].bezier_points[i]
                    if ["%.4f" % bp.co[0], "%.4f" % bp.co[1], "%.4f" % bp.co[2]] in coords_loops_U_control_points:
                        points_U_crossed_by_strokes.append(i)
    
                # Make a dictionary with the number of the edge, in the selected chain V, corresponding to each stroke.
                edge_order_number_for_splines = {}
                if self.selection_V_exists:
                    # For two-connected selections add a first hypothetic stroke at the begining.
                    if selection_type == "TWO_CONNECTED":
                        edge_order_number_for_splines[0] = 0
    
                    for i in range(len(self.main_splines.data.splines)):
                        sp = self.main_splines.data.splines[i]
                        v_idx, dist_temp = self.shortest_distance(self.main_object, sp.bezier_points[0].co, verts_ordered_V_indices)
    
                        edge_idx_in_chain = verts_ordered_V_indices.index(v_idx) # Get the position (edges count) of the vert v_idx in the selected chain V.
    
                        # For two-connected selections the strokes go after the hypothetic stroke added before, so the index adds one per spline.
                        if selection_type == "TWO_CONNECTED":
                            spline_number = i + 1
                        else:
                            spline_number = i
    
                        edge_order_number_for_splines[spline_number] = edge_idx_in_chain
    
                        # Get the first and last verts indices for later comparison.
                        if i == 0:
                            first_v_idx = v_idx
                        elif i == len(self.main_splines.data.splines) - 1:
                            last_v_idx = v_idx
    
                    if self.selection_V_is_closed:
                        # If there is no last stroke on the last vertex (same as first vertex), add a hypothetic spline at last vert order.
                        if first_v_idx != last_v_idx:
                            edge_order_number_for_splines[(len(self.main_splines.data.splines) - 1) + 1] = len(verts_ordered_V_indices) - 1
                        else:
                            if self.cyclic_cross:
                                edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = len(verts_ordered_V_indices) - 2
                                edge_order_number_for_splines[(len(self.main_splines.data.splines) - 1) + 1] = len(verts_ordered_V_indices) - 1
                            else:
                                edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = len(verts_ordered_V_indices) - 1
    
            #### Get the coords of the points distributed along the "crossing curves", with appropriate proportions-V.
            surface_splines_parsed = []
            for i in range(len(splines_U_objects)):
                sp_ob = splines_U_objects[i]
                # If "Loops on strokes" option is active, calculate the proportions for each loop-U.
                if self.loops_on_strokes:
                    # Segments distances from stroke to stroke.
                    dist = 0
                    full_dist = 0
                    segments_distances = []
                    for t in range(len(sp_ob.data.splines[0].bezier_points)):
                        bp = sp_ob.data.splines[0].bezier_points[t]
    
                        if t == 0:
                            last_p = bp.co
                        else:
                            actual_p = bp.co
                            dist += (last_p - actual_p).length
    
                            if t in points_U_crossed_by_strokes:
                                segments_distances.append(dist)
                                full_dist += dist
    
                            last_p = actual_p
    
                    # Calculate Proportions.
                    used_edges_proportions_V = []
                    for t in range(len(segments_distances)):
                        if self.selection_V_exists:
                            if t == 0:
                                order_number_last_stroke = 0
    
                            segment_edges_length_V = 0
                            segment_edges_length_V2 = 0
                            for order in range(order_number_last_stroke, edge_order_number_for_splines[t + 1]):
                                segment_edges_length_V += edges_lengths_V[order]
                                if self.selection_V2_exists:
                                    segment_edges_length_V2 += edges_lengths_V2[order]
    
                            for order in range(order_number_last_stroke, edge_order_number_for_splines[t + 1]):
                                # Calculate each "sub-segment" (the ones between each stroke) length.
                                if self.selection_V2_exists:
                                    proportion_sub_seg = (edges_lengths_V2[order] - ((edges_lengths_V2[order] - edges_lengths_V[order]) / len(splines_U_objects) * i)) / (segment_edges_length_V2 - (segment_edges_length_V2 - segment_edges_length_V) / len(splines_U_objects) * i)
                                    sub_seg_dist = segments_distances[t] * proportion_sub_seg
                                else:
                                    proportion_sub_seg = edges_lengths_V[order] / segment_edges_length_V
                                    sub_seg_dist = segments_distances[t] * proportion_sub_seg
    
                                used_edges_proportions_V.append(sub_seg_dist / full_dist)
    
                            order_number_last_stroke = edge_order_number_for_splines[t + 1]
    
                        else:
                            for c in range(self.edges_V):
                                # Calculate each "sub-segment" (the ones between each stroke) length.
    
                                sub_seg_dist = segments_distances[t] / self.edges_V
    
                                used_edges_proportions_V.append(sub_seg_dist / full_dist)
    
                    actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
                    surface_splines_parsed.append(actual_spline[0])
    
                else:
                    if self.selection_V2_exists:
                        used_edges_proportions_V = []
                        for p in range(len(edges_proportions_V)):
                            used_edges_proportions_V.append(edges_proportions_V2[p] - ((edges_proportions_V2[p] - edges_proportions_V[p]) / len(splines_U_objects) * i))
                    else:
                        used_edges_proportions_V = edges_proportions_V
    
                    actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
                    surface_splines_parsed.append(actual_spline[0])
    
            # Set the verts of the first and last splines to the locations of the respective verts in the selections.
            if self.selection_V_exists:
                for i in range(0, len(surface_splines_parsed[0])):
                    surface_splines_parsed[len(surface_splines_parsed) - 1][i] = self.main_object.matrix_world * verts_ordered_V[i].co
    
            if selection_type == "TWO_NOT_CONNECTED":
                if self.selection_V2_exists:
                    for i in range(0, len(surface_splines_parsed[0])):
                        surface_splines_parsed[0][i] = self.main_object.matrix_world * verts_ordered_V2[i].co
    
            # When "Automatic join" option is active (and the selection type is not "TWO_CONNECTED"), merge the verts of the tips of the loops when they are "near enough".
            if self.automatic_join and selection_type != "TWO_CONNECTED":
                #### Join the tips of "Follow" loops that are near enough and must be "closed".
                if not self.selection_V_exists and len(edges_proportions_U) >= 3:
                    for i in range(len(surface_splines_parsed[0])):
                        sp = surface_splines_parsed
                        loop_segment_dist = (sp[0][i] - sp[1][i]).length
                        full_loop_dist = loop_segment_dist * self.edges_U
    
                        verts_middle_position_co = [(sp[0][i][0] + sp[len(sp) - 1][i][0]) / 2, (sp[0][i][1] + sp[len(sp) - 1][i][1]) / 2, (sp[0][i][2] + sp[len(sp) - 1][i][2]) / 2]
    
                        points_original = []
                        points_original.append(sp[1][i])
                        points_original.append(sp[0][i])
    
                        points_target = []
                        points_target.append(sp[1][i])
                        points_target.append(mathutils.Vector(verts_middle_position_co))
    
                        vec_A = points_original[0] - points_original[1]
                        vec_B = points_target[0] - points_target[1]
    
                        angle = vec_A.angle(vec_B) / math.pi
    
                        edge_new_length = (mathutils.Vector(verts_middle_position_co) - sp[1][i]).length
    
                        if edge_new_length <= loop_segment_dist * 1.5 * self.join_stretch_factor and angle < 0.25 * self.join_stretch_factor: # If after moving the verts to the middle point, the segment doesn't stretch too much.
                            if not (self.selection_U_exists and i == 0) and not (self.selection_U2_exists and i == len(surface_splines_parsed[0]) - 1): # Avoid joining when the actual loop must be merged with the original mesh.
                                # Change the coords of both verts to the middle position.
                                surface_splines_parsed[0][i] = verts_middle_position_co
                                surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_middle_position_co
    
            #### Delete object with control points and object from grease pencil convertion.
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[ob_ctrl_pts.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
    
            bpy.ops.object.delete()
    
            for sp_ob in splines_U_objects:
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[sp_ob.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[sp_ob.name]
    
                bpy.ops.object.delete()
    
            #### Generate surface.
    
            # Get all verts coords.
            all_surface_verts_co = []
            for i in range(0, len(surface_splines_parsed)):
                # Get coords of all verts and make a list with them
                for pt_co in surface_splines_parsed[i]:
                    all_surface_verts_co.append(pt_co)
    
            # Define verts for each face.
            all_surface_faces = []
            for i in range(0, len(all_surface_verts_co) - len(surface_splines_parsed[0])):
                if ((i + 1) / len(surface_splines_parsed[0]) != int((i + 1) / len(surface_splines_parsed[0]))):
                    all_surface_faces.append([i+1, i , i + len(surface_splines_parsed[0]), i + len(surface_splines_parsed[0]) + 1])
    
            # Build the mesh.
            surf_me_name = "SURFSKIO_surface"
            me_surf = bpy.data.meshes.new(surf_me_name)
    
            me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
    
            me_surf.update()
    
            ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
            bpy.context.scene.objects.link(ob_surface)
    
            # Select all the "unselected but participating" verts, from closed selection or double selections with middle-vertex, for later join with remove doubles.
            for v_idx in single_unselected_verts:
                self.main_object.data.vertices[v_idx].select = True
    
            #### Join the new mesh to the main object.
            ob_surface.select = True
            self.main_object.select = True
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
    
            bpy.ops.object.join('INVOKE_REGION_WIN')
    
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=0.0001)
    
            bpy.ops.mesh.normals_make_consistent('INVOKE_REGION_WIN', inside=False)
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
            return{'FINISHED'}
    
        def execute(self, context):
            bpy.context.user_preferences.edit.use_global_undo = False
    
            if not self.is_fill_faces:
                bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
    
                # Build splines from the "last saved splines".
                last_saved_curve = bpy.data.curves.new('SURFSKIO_last_crv', 'CURVE')
                self.main_splines = bpy.data.objects.new('SURFSKIO_last_crv', last_saved_curve)
                bpy.context.scene.objects.link(self.main_splines)
    
                last_saved_curve.dimensions = "3D"
    
                for sp in self.last_strokes_splines_coords:
                    spline = self.main_splines.data.splines.new('BEZIER')
                    spline.bezier_points.add(len(sp) - 1) # less one because one point is added when the spline is created.
                    for p in range(0, len(sp)):
                        spline.bezier_points[p].co = [sp[p][0], sp[p][1], sp[p][2]]
    
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.main_splines.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.main_splines.name]
    
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
                bpy.ops.curve.handle_type_set(type='VECTOR') # Important to make it vector first and then automatic, otherwise the tips handles get too big and distort the shrinkwrap results later.
                bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                self.main_splines.name = "SURFSKIO_temp_strokes"
    
                if self.is_crosshatch:
                    strokes_for_crosshatch = True
                    strokes_for_rectangular_surface = False
                else:
                    strokes_for_rectangular_surface = True
                    strokes_for_crosshatch = False
    
                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.editmode_toggle('INVOKE_REGION_WIN')
    
                if strokes_for_rectangular_surface:
                    self.rectangular_surface()
                elif strokes_for_crosshatch:
                    self.crosshatch_surface_execute()
    
                #### Delete main splines
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.main_splines.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.main_splines.name]
    
                bpy.ops.object.delete()
    
                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.editmode_toggle('INVOKE_REGION_WIN')
    
                bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
    
            return{'FINISHED'}
    
        def invoke(self, context, event):
            self.initial_global_undo_state = bpy.context.user_preferences.edit.use_global_undo
    
            self.main_object = bpy.context.scene.objects.active
            self.main_object_selected_verts_count = int(self.main_object.data.total_vert_sel)
    
            bpy.context.user_preferences.edit.use_global_undo = False
    
            bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
    
            # Out Edit mode and In again to make sure the actual mesh selections are being taken.
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            self.cyclic_cross = bpy.context.scene.SURFSK_cyclic_cross
            self.cyclic_follow = bpy.context.scene.SURFSK_cyclic_follow
            self.automatic_join = bpy.context.scene.SURFSK_automatic_join
            self.loops_on_strokes = bpy.context.scene.SURFSK_loops_on_strokes
            self.keep_strokes = bpy.context.scene.SURFSK_keep_strokes
    
            self.edges_U = 10
    
            if self.loops_on_strokes:
                self.edges_V = 3
            else:
                self.edges_V = 10
    
            self.is_fill_faces = False
    
            self.stopping_errors = False
    
            self.last_strokes_splines_coords = []
    
            #### Determine the type of the strokes.
            self.strokes_type = get_strokes_type(self.main_object)
    
            #### Check if it will be used grease pencil strokes or curves.
            if self.strokes_type == "GP_STROKES" or self.strokes_type == "EXTERNAL_CURVE": # If there are strokes to be used.
                if self.strokes_type == "GP_STROKES":
                    # Convert grease pencil strokes to curve.
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                    bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
                    # XXX gpencil.convert now keep org object as active/selected, *not* newly created curve!
                    # XXX This is far from perfect, but should work in most cases...
    #                self.original_curve = bpy.context.object
                    for ob in bpy.context.selected_objects:
                        if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
                            self.original_curve = ob
    
                    self.using_external_curves = False
                elif self.strokes_type == "EXTERNAL_CURVE":
                    for ob in bpy.context.selected_objects:
                        if ob != bpy.context.scene.objects.active:
                            self.original_curve = ob