Skip to content
Snippets Groups Projects
mesh_bsurfaces.py 204 KiB
Newer Older
  • Learn to ignore specific revisions
  •             points_B.append(
    
                    self.main_object.matrix_world @ self.main_object.data.vertices[verts_tips_parsed_idx[1]].co
    
                    )
                points_B.append(
    
                    self.main_object.matrix_world @ self.main_object.data.vertices[middle_vertex_idx].co
    
                    )
                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
                    )
    
                angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
                angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
    
                if angle_A < angle_B:
                    first_vert_U_idx = verts_tips_parsed_idx[0]
                    first_vert_V_idx = verts_tips_parsed_idx[1]
                else:
                    first_vert_U_idx = verts_tips_parsed_idx[1]
                    first_vert_V_idx = verts_tips_parsed_idx[0]
    
            elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
                first_sketched_point_first_stroke_co = self.main_splines.data.splines[0].bezier_points[0].co
    
                last_sketched_point_first_stroke_co = \
                        self.main_splines.data.splines[0].bezier_points[
                                                        len(self.main_splines.data.splines[0].bezier_points) - 1
                                                        ].co
                first_sketched_point_last_stroke_co = \
                        self.main_splines.data.splines[
                                                len(self.main_splines.data.splines) - 1
                                                ].bezier_points[0].co
    
                if len(self.main_splines.data.splines) > 1:
                    first_sketched_point_second_stroke_co = self.main_splines.data.splines[1].bezier_points[0].co
    
                    last_sketched_point_second_stroke_co = \
                        self.main_splines.data.splines[1].bezier_points[
                                                len(self.main_splines.data.splines[1].bezier_points) - 1
                                                ].co
    
                single_unselected_neighbors = []  # Only the neighbors of the single unselected verts
    
                for verts_neig_idx in single_unselected_verts_and_neighbors:
                    single_unselected_neighbors.append(verts_neig_idx[1])
                    single_unselected_neighbors.append(verts_neig_idx[2])
    
                all_chains_tips_and_middle_vert = []
                for v_idx in all_chains_tips_idx:
                    if v_idx not in single_unselected_neighbors:
                        all_chains_tips_and_middle_vert.append(v_idx)
    
                all_chains_tips_and_middle_vert += single_unselected_verts
    
                all_participating_verts = all_chains_tips_and_middle_vert + all_verts_idx
    
                # The tip of the selected vertices nearest to the first point of the first sketched stroke
                nearest_tip_to_first_st_first_pt_idx, shortest_distance_to_first_stroke = \
                        self.shortest_distance(
                                    self.main_object,
                                    first_sketched_point_first_stroke_co,
                                    all_chains_tips_and_middle_vert
                                    )
                # If the nearest tip is not from a closed selection, get the opposite tip vertex index
                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:
    
                    nearest_tip_to_first_st_first_pt_opposite_idx = \
                        self.opposite_tip(
                                    nearest_tip_to_first_st_first_pt_idx,
                                    verts_tips_same_chain_idx
                                    )
                # The tip of the selected vertices nearest to the last point of the first sketched stroke
    
                nearest_tip_to_first_st_last_pt_idx, _temp_dist = \
    
                        self.shortest_distance(
                                    self.main_object,
                                    last_sketched_point_first_stroke_co,
                                    all_chains_tips_and_middle_vert
                                    )
                # The tip of the selected vertices nearest to the first point of the last sketched stroke
                nearest_tip_to_last_st_first_pt_idx, shortest_distance_to_last_stroke = \
                        self.shortest_distance(
                                    self.main_object,
                                    first_sketched_point_last_stroke_co,
                                    all_chains_tips_and_middle_vert
                                    )
    
                if len(self.main_splines.data.splines) > 1:
    
                    # The selected vertex nearest to the first point of the second sketched stroke
                    # (This will be useful to determine the direction of the closed
                    # selection V when extruding along strokes)
    
                    nearest_vert_to_second_st_first_pt_idx, _temp_dist = \
    
                            self.shortest_distance(
                                    self.main_object,
                                    first_sketched_point_second_stroke_co,
                                    all_verts_idx
                                    )
                    # The selected vertex nearest to the first point of the second sketched stroke
                    # (This will be useful to determine the direction of the closed
                    # selection V2 when extruding along strokes)
    
                    nearest_vert_to_second_st_last_pt_idx, _temp_dist = \
    
                            self.shortest_distance(
                                    self.main_object,
                                    last_sketched_point_second_stroke_co,
                                    all_verts_idx
                                    )
                # Determine if the single selection will be treated as U or as V
    
                edges_sum = 0
                for i in all_selected_edges_idx:
    
                    edges_sum += (
    
                        (self.main_object.matrix_world @
    
                         self.main_object.data.vertices[self.main_object.data.edges[i].vertices[0]].co) -
    
                        (self.main_object.matrix_world @
    
                         self.main_object.data.vertices[self.main_object.data.edges[i].vertices[1]].co)
                        ).length
    
                average_edge_length = edges_sum / len(all_selected_edges_idx)
    
                # Get shortest distance from the first point of the last stroke to any participating vertex
    
                _temp_idx, shortest_distance_to_last_stroke = \
    
                            self.shortest_distance(
                                    self.main_object,
                                    first_sketched_point_last_stroke_co,
                                    all_participating_verts
                                    )
                # If the beginning of the first stroke is near enough, and its orientation
                # difference with the first edge of the nearest selection chain is not too high,
                # interpret things as an "extrude along strokes" instead of "extrude through strokes"
                if shortest_distance_to_first_stroke < average_edge_length / 4 and \
                 shortest_distance_to_last_stroke < average_edge_length and \
                 len(self.main_splines.data.splines) > 1:
    
                    self.selection_U_exists = False
                    self.selection_V_exists = True
    
                    # If the first selection is not closed
                    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:
    
                        self.selection_V_is_closed = False
                        closing_vert_U_idx = None
                        closing_vert_U2_idx = None
                        closing_vert_V_idx = None
                        closing_vert_V2_idx = None
    
                        first_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
    
                        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 the vertex nearest to the first point of the second stroke
                                # is in the first half of the selected verts
                                if i >= len(verts_V) / 2:
    
                                    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 the second selection is not closed
                        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:
    
                            self.selection_V2_is_closed = False
                            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 the vertex nearest to the first point of the second stroke
                                    # is in the first half of the selected verts
                                    if i >= len(verts_V2) / 2:
    
                                        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 the first selection is not closed
                    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:
    
                        self.selection_U_is_closed = False
                        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 the second selection is not closed
                        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:
    
                            self.selection_U2_is_closed = False
                            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
                                        )
    
            # 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
                                        )
    
            # 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
                                        )
    
            # 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.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.bsurfaces.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
                                        )
    
            # 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
                                        )
    
            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
                                        )
    
            # 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.collection.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')
    
                    # less one because one point is added when the spline is created
                    spline.bezier_points.add(len(simplified_spline_coords[i]) - 1)
    
                    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')
    
                    ob_simplified_curve[i].select_set(True)
    
                    bpy.context.view_layer.objects.active = ob_simplified_curve[i]
    
                    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.delete({"selected_objects": [ob_simplified_curve[i]]})
    
            # 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)):
    
                        # When on the first stroke, add the segment from the selection to the dirst stroke
                        if st == 0:
                            loop_segments_lengths.append(
    
                                        ((self.main_object.matrix_world @ verts_ordered_U[lp].co) -
    
                                        pts_on_strokes_with_proportions_U[0][lp]).length
                                        )
                        # For all strokes except for the last, calculate the distance
                        # from the actual stroke to the next
                        if st != len(pts_on_strokes_with_proportions_U) - 1:
                            loop_segments_lengths.append(
                                        (pts_on_strokes_with_proportions_U[st][lp] -
                                        pts_on_strokes_with_proportions_U[st + 1][lp]).length
                                        )
                        # When on the last stroke, add the segments
                        # from the last stroke to the second selection
                        if st == len(pts_on_strokes_with_proportions_U) - 1:
                            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 = []
    
                    # Needs to be a list for the "distribute_pts" method
                    actual_stroke_spline.append(self.main_splines.data.splines[st])
    
                    # 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]
    
                        # 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
                        actual_edges_proportions_U.append(
                                edges_proportions_U[i] -
                                ((edges_proportions_U[i] - edges_proportions_U2[i]) * proportions_sum)
                                )
    
                    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.collection.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)
    
                    last_v = v
                    vert_num_in_spline += 1
    
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
            ob_ctrl_pts.select_set(True)
    
            bpy.context.view_layer.objects.active = ob_ctrl_pts
    
            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)):
    
                # When there is Cyclic Cross there is no need of
                # Automatic Join, (and there are at least three strokes)
                if self.automatic_join and not self.cyclic_cross and \
                   selection_type != "TWO_CONNECTED" and len(self.main_splines.data.splines) >= 3:
    
                    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 - 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 - Vector(verts_center_co)).length
    
                    angle = vec_A.angle(vec_B) / pi
    
                    # If the target length doesn't stretch too much, and the
                    # its angle doesn't change to much either
                    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:
    
                        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
    
                    else:
                        cyclic_loops_U.append(False)
                else:
    
                    # If "Cyclic Cross" is active then "all" crossing curves become cyclic
                    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)):
    
    
                        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.view_layer.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 will 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.bsurfaces.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.collection.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] is 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
    
                    # to avoid adding the first point since it's added when the spline is created
                    if t > 0:
    
                        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] is 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')
    
                ob_spline_U.select_set(True)
    
                bpy.context.view_layer.objects.active = ob_spline_U
    
            # 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 beginning.
    
                    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
                                                    )
                        # Get the position (edges count) of the vert v_idx in the selected chain V
                        edge_idx_in_chain = verts_ordered_V_indices.index(v_idx)
    
                        # 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
    
                                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]
    
                            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 != "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
    
                        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(Vector(verts_middle_position_co))
    
                        vec_A = points_original[0] - points_original[1]
                        vec_B = points_target[0] - points_target[1]