Skip to content
Snippets Groups Projects
mesh_bsurfaces.py 179 KiB
Newer Older
  • Learn to ignore specific revisions
  •         self.modifiers_prev_viewport_state = []
            if len(self.main_object.modifiers) > 0:
                for m_idx in range(len(self.main_object.modifiers)):
                    self.modifiers_prev_viewport_state.append(self.main_object.modifiers[m_idx].show_viewport)
                    
                    self.main_object.modifiers[m_idx].show_viewport = False
            
            
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[ob_original_splines.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[ob_original_splines.name]
            
            
            if len(ob_original_splines.data.splines) >= 2:
                bpy.ops.object.duplicate('INVOKE_REGION_WIN')
                ob_splines = bpy.context.object
                ob_splines.name = "SURFSKIO_NE_STR"
                
                
                #### Get estimative merge distance (sum up the distances from the first point to all other points, then average them and then divide them).
                first_point_dist_sum = 0
                first_dist = 0
                second_dist = 0
                coords_first_pt = ob_splines.data.splines[0].bezier_points[0].co
                for i in range(len(ob_splines.data.splines)):
                    sp = ob_splines.data.splines[i]
                    
                    if coords_first_pt != sp.bezier_points[0].co:
                        first_dist = (coords_first_pt - sp.bezier_points[0].co).length
                        
                    if coords_first_pt != sp.bezier_points[len(sp.bezier_points) - 1].co:
                        second_dist = (coords_first_pt - sp.bezier_points[len(sp.bezier_points) - 1].co).length
                    
                    first_point_dist_sum += first_dist + second_dist
                    
                    
                    if i == 0:
                        if first_dist != 0:
                            shortest_dist = first_dist
                        elif second_dist != 0:
                            shortest_dist = second_dist
    
                        
                    if shortest_dist > first_dist and first_dist != 0:
                        shortest_dist = first_dist
                    
                    if shortest_dist > second_dist and second_dist != 0:
                        shortest_dist = second_dist
                    
    
                self.crosshatch_merge_distance = shortest_dist / 20
    
                #### Recalculation of merge distance.
                
                bpy.ops.object.duplicate('INVOKE_REGION_WIN')
                
                ob_calc_merge_dist = bpy.context.object
                ob_calc_merge_dist.name = "SURFSKIO_CALC_TMP"
                
                objects_to_delete.append(ob_calc_merge_dist)
                
                
                
                #### Smooth out strokes a little to improve crosshatch detection.
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
                
                for i in range(4):
                    bpy.ops.curve.smooth('INVOKE_REGION_WIN')
                
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                #### Convert curves into mesh.
                ob_calc_merge_dist.data.resolution_u = 12
                bpy.ops.object.convert(target='MESH', keep_original=False)
                
                # Find "intersection-nodes".
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
    
                bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=self.crosshatch_merge_distance)
    
                bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                # Remove verts with less than three edges.
                verts_edges_count = {}
                for ed in ob_calc_merge_dist.data.edges:
                    v = ed.vertices
    
                    if v[0] not in verts_edges_count:
                        verts_edges_count[v[0]] = 0
    
                    if v[1] not in verts_edges_count:
                        verts_edges_count[v[1]] = 0
    
                    verts_edges_count[v[0]] += 1
                    verts_edges_count[v[1]] += 1
                
                nodes_verts_coords = []
                for v_idx in verts_edges_count:
                    v = ob_calc_merge_dist.data.vertices[v_idx]
                    
                    if verts_edges_count[v_idx] < 3:
                        v.select = True
    
                
                # Remove them.
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.mesh.delete('INVOKE_REGION_WIN', type='VERT')
                bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
                
                # Remove doubles to discard very near verts from calculations of distance.
    
                bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=self.crosshatch_merge_distance * 4.0)
    
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                # Get all coords of the resulting nodes.
                nodes_verts_coords = [(v.co[0], v.co[1], v.co[2]) for v in ob_calc_merge_dist.data.vertices]
                
                #### Check if the strokes are a crosshatch.
                if len(nodes_verts_coords) >= 3:
                    self.is_crosshatch = True
    
                    shortest_dist = None
                    for co_1 in nodes_verts_coords:
                        for co_2 in nodes_verts_coords:
                            if co_1 != co_2:
                                dist = (mathutils.Vector(co_1) - mathutils.Vector(co_2)).length
                                
                                if shortest_dist != None:
                                    if dist < shortest_dist:
                                        shortest_dist = dist
                                else:
                                    shortest_dist = dist
    
                    self.crosshatch_merge_distance = shortest_dist / 3
                
                
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[ob_splines.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[ob_splines.name]
                
                #### Deselect all points.
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                
                
                #### Smooth splines in a localized way, to eliminate "saw-teeth" like shapes when there are many points.
                for sp in ob_splines.data.splines:
                    angle_sum = 0
    
                    angle_limit = 2 # Degrees
                    for t in range(len(sp.bezier_points)):
                        if t <= len(sp.bezier_points) - 3: # Because on each iteration it checks the "next two points" of the actual. This way it doesn't go out of range.
                            p1 = sp.bezier_points[t]
                            p2 = sp.bezier_points[t + 1]
                            p3 = sp.bezier_points[t + 2]
                            
                            vec_1 = p1.co - p2.co
                            vec_2 = p2.co - p3.co
                            
                            if p2.co != p1.co and p2.co != p3.co:
                                angle = vec_1.angle(vec_2)
                                angle_sum += degrees(angle)
                                
                                if angle_sum >= angle_limit: # If sum of angles is grater than the limit.
                                    if (p1.co - p2.co).length <= self.crosshatch_merge_distance:
                                        p1.select_control_point = True; p1.select_left_handle = True; p1.select_right_handle = True
                                        p2.select_control_point = True; p2.select_left_handle = True; p2.select_right_handle = True
                                    
                                    if (p1.co - p2.co).length <= self.crosshatch_merge_distance:
                                        p3.select_control_point = True; p3.select_left_handle = True; p3.select_right_handle = True
                                    
                                    angle_sum = 0
    
                    sp.bezier_points[0].select_control_point = False
                    sp.bezier_points[0].select_left_handle = False
                    sp.bezier_points[0].select_right_handle = False
    
                    sp.bezier_points[len(sp.bezier_points) - 1].select_control_point = False
                    sp.bezier_points[len(sp.bezier_points) - 1].select_left_handle = False
                    sp.bezier_points[len(sp.bezier_points) - 1].select_right_handle  = False
    
                
                
                #### Smooth out strokes a little to improve crosshatch detection.
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                for i in range(15):
                    bpy.ops.curve.smooth('INVOKE_REGION_WIN')
                
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                
                
                
                #### Simplify the splines.
                for sp in ob_splines.data.splines:
                    angle_sum = 0
                    
                    sp.bezier_points[0].select_control_point = True
                    sp.bezier_points[0].select_left_handle = True
                    sp.bezier_points[0].select_right_handle = True
                    
                    sp.bezier_points[len(sp.bezier_points) - 1].select_control_point = True
                    sp.bezier_points[len(sp.bezier_points) - 1].select_left_handle = True
                    sp.bezier_points[len(sp.bezier_points) - 1].select_right_handle  = True
                    
                    
                    angle_limit = 15 # Degrees
                    for t in range(len(sp.bezier_points)):
                        if t <= len(sp.bezier_points) - 3: # Because on each iteration it checks the "next two points" of the actual. This way it doesn't go out of range.
                            p1 = sp.bezier_points[t]
                            p2 = sp.bezier_points[t + 1]
                            p3 = sp.bezier_points[t + 2]
                            
                            vec_1 = p1.co - p2.co
                            vec_2 = p2.co - p3.co
                            
                            if p2.co != p1.co and p2.co != p3.co:
                                angle = vec_1.angle(vec_2)
                                angle_sum += degrees(angle)
                                
                                if angle_sum >= angle_limit: # If sum of angles is grater than the limit.
                                    p1.select_control_point = True; p1.select_left_handle = True; p1.select_right_handle = True
                                    p2.select_control_point = True; p2.select_left_handle = True; p2.select_right_handle = True
                                    p3.select_control_point = True; p3.select_left_handle = True; p3.select_right_handle = True
                                    
                                    angle_sum = 0
                                
                                
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                bpy.ops.curve.select_all(action = 'INVERT')
                
                bpy.ops.curve.delete(type='SELECTED')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                
                
                objects_to_delete.append(ob_splines)
                
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                
                #### Check if the strokes are a crosshatch.
                if self.is_crosshatch:
                    all_points_coords = []
                    for i in range(len(ob_splines.data.splines)):
                        all_points_coords.append([])
    
                        all_points_coords[i] = [mathutils.Vector((x, y, z)) for x, y, z in [bp.co for bp in ob_splines.data.splines[i].bezier_points]]
    
                    
                    all_intersections = []
                    checked_splines = []
                    for i in range(len(all_points_coords)):
                            
                        for t in range(len(all_points_coords[i]) - 1):
                            bp1_co = all_points_coords[i][t]
                            bp2_co = all_points_coords[i][t + 1]
                            
                            for i2 in range(len(all_points_coords)):
                                if i != i2 and not i2 in checked_splines:
                                    for t2 in range(len(all_points_coords[i2]) - 1):
                                        bp3_co = all_points_coords[i2][t2]
                                        bp4_co = all_points_coords[i2][t2 + 1]
                                        
                                        
                                        intersec_coords = mathutils.geometry.intersect_line_line(bp1_co, bp2_co, bp3_co, bp4_co)
                                        
                                        if intersec_coords != None:
                                            dist = (intersec_coords[0] - intersec_coords[1]).length
                                            
                                            if dist <= self.crosshatch_merge_distance * 1.5:
                                                temp_co, percent1 = mathutils.geometry.intersect_point_line(intersec_coords[0], bp1_co, bp2_co)
                                                
                                                if (percent1 >= -0.02 and percent1 <= 1.02):
                                                    temp_co, percent2 = mathutils.geometry.intersect_point_line(intersec_coords[1], bp3_co, bp4_co)
                                                    if (percent2 >= -0.02 and percent2 <= 1.02):
                                                        all_intersections.append((i, t, percent1, ob_splines.matrix_world * intersec_coords[0])) # Format: spline index, first point index from corresponding segment, percentage from first point of actual segment, coords of intersection point.
                                                        all_intersections.append((i2, t2, percent2, ob_splines.matrix_world * intersec_coords[1]))
                                                
                                                
                            
                            checked_splines.append(i)        
                    
                    
                    all_intersections.sort(key = operator.itemgetter(0,1,2)) # Sort list by spline, then by corresponding first point index of segment, and then by percentage from first point of segment: elements 0 and 1 respectively.
                    
                    
                    
                    self.crosshatch_strokes_coords = {}
                    for i in range(len(all_intersections)):
                        if not all_intersections[i][0] in self.crosshatch_strokes_coords:
                            self.crosshatch_strokes_coords[all_intersections[i][0]] = []
                            
                        self.crosshatch_strokes_coords[all_intersections[i][0]].append(all_intersections[i][3]) # Save intersection coords.
                    
                else:
                    self.is_crosshatch = False
                    
            
            #### Delete all duplicates.
            for o in objects_to_delete:
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[o.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[o.name]
                bpy.ops.object.delete()
                
                
            #### If the main object has modifiers, turn their "viewport view status" to what it was before the forced deactivation above.
            if len(self.main_object.modifiers) > 0:
                for m_idx in range(len(self.main_object.modifiers)):
                    self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx]
    
            return
        
        
        
        #### Part of the Crosshatch process that is repeated when the operator is tweaked.
        def crosshatch_surface_execute(self):
            # If the main object uses modifiers deactivate them temporarily until the surface is joined. (without this the surface verts merging with the main object doesn't work well)
            self.modifiers_prev_viewport_state = []
            if len(self.main_object.modifiers) > 0:
                for m_idx in range(len(self.main_object.modifiers)):
                    self.modifiers_prev_viewport_state.append(self.main_object.modifiers[m_idx].show_viewport)
                    
                    self.main_object.modifiers[m_idx].show_viewport = False
                    
                    
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            me_name = "SURFSKIO_STK_TMP"
            me = bpy.data.meshes.new(me_name)
    
            all_verts_coords = []
            all_edges = []
            for st_idx in self.crosshatch_strokes_coords:
                for co_idx in range(len(self.crosshatch_strokes_coords[st_idx])):
                    coords = self.crosshatch_strokes_coords[st_idx][co_idx]
                    
                    all_verts_coords.append(coords)
                    
                    if co_idx > 0:
                        all_edges.append((len(all_verts_coords) - 2, len(all_verts_coords) - 1))
                        
    
            me.from_pydata(all_verts_coords, all_edges, [])
    
            ob = bpy.data.objects.new(me_name, me)
            ob.data = me
            bpy.context.scene.objects.link(ob)
            
            
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[ob.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[ob.name]
            
            
            #### Get together each vert and its nearest, to the middle position.
            verts = ob.data.vertices
            checked_verts = []
            for i in range(len(verts)):
                shortest_dist = None
                
                if not i in checked_verts:
                    for t in range(len(verts)):
                        if i != t and not t in checked_verts:
                            dist = (verts[i].co - verts[t].co).length
                            
                            if shortest_dist != None:
                                if dist < shortest_dist:
                                    shortest_dist = dist
                                    nearest_vert = t
                            else:
                                shortest_dist = dist
                                nearest_vert = t
                    
                    middle_location = (verts[i].co + verts[nearest_vert].co) / 2
                    
                    verts[i].co = middle_location
                    verts[nearest_vert].co = middle_location
                    
                    checked_verts.append(i)
                    checked_verts.append(nearest_vert)
            
            
            
            
            #### Calculate average length between all the generated edges.
            ob = bpy.context.object
            lengths_sum = 0
            for ed in ob.data.edges:
                v1 = ob.data.vertices[ed.vertices[0]]
                v2 = ob.data.vertices[ed.vertices[1]]
                
                lengths_sum += (v1.co - v2.co).length
            
            edges_count = len(ob.data.edges)
            
            average_edge_length = lengths_sum / edges_count
    
    Eclectiel L's avatar
    Eclectiel L committed
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
    
            bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=average_edge_length / 15.0)
    
    Eclectiel L's avatar
    Eclectiel L committed
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            final_points_ob = bpy.context.scene.objects.active
            
            
            #### Make a dictionary with the verts related to each vert.
            related_key_verts = {}
            for ed in final_points_ob.data.edges:
                if not ed.vertices[0] in related_key_verts:
                    related_key_verts[ed.vertices[0]] = []
                    
                if not ed.vertices[1] in related_key_verts:
                    related_key_verts[ed.vertices[1]] = []
                
                
                if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
                    related_key_verts[ed.vertices[0]].append(ed.vertices[1])
                    
                if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
                    related_key_verts[ed.vertices[1]].append(ed.vertices[0])
            
            
            
            #### Get groups of verts forming each face.
            faces_verts_idx = [] 
            for v1 in related_key_verts: # verts-1 .... 
                for v2 in related_key_verts: # verts-2
                    if v1 != v2:
                        related_verts_in_common = []
                        v2_in_rel_v1 = False
                        v1_in_rel_v2 = False
                        for rel_v1 in related_key_verts[v1]:
                            if rel_v1 in related_key_verts[v2]: # Check if related verts of verts-1 are related verts of verts-2.
                                related_verts_in_common.append(rel_v1)
                            
                        if v2 in related_key_verts[v1]:
                            v2_in_rel_v1 = True
                                
                        if v1 in related_key_verts[v2]:
                            v1_in_rel_v2 = True
                        
                        
                        repeated_face = False
                        # If two verts have two related verts in common, they form a quad.
                        if len(related_verts_in_common) == 2:
                            # Check if the face is already saved.
                            for f_verts in faces_verts_idx:
                                repeated_verts = 0
                                
                                if len(f_verts) == 4:
                                    if v1 in f_verts: repeated_verts += 1
                                    if v2 in f_verts: repeated_verts += 1
                                    if related_verts_in_common[0] in f_verts: repeated_verts += 1
                                    if related_verts_in_common[1] in f_verts: repeated_verts += 1
                                    
                                    if repeated_verts == len(f_verts):
                                        repeated_face = True
                                        break
                            
                            if not repeated_face:
                                faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
                            
                        elif v2_in_rel_v1 and v1_in_rel_v2 and len(related_verts_in_common) == 1: # If Two verts have one related vert in common and they are related to each other, they form a triangle.
                            # Check if the face is already saved.
                            for f_verts in faces_verts_idx:
                                repeated_verts = 0
                                
                                if len(f_verts) == 3:
                                    if v1 in f_verts: repeated_verts += 1
                                    if v2 in f_verts: repeated_verts += 1
                                    if related_verts_in_common[0] in f_verts: repeated_verts += 1
                                    
                                    if repeated_verts == len(f_verts):
                                        repeated_face = True
                                        break
                            
                            if not repeated_face:
                                faces_verts_idx.append([v1, related_verts_in_common[0], v2])
                    
            
            #### Keep only the faces that don't overlap by ignoring quads that overlap with two adjacent triangles.
            faces_to_not_include_idx = [] # Indices of faces_verts_idx to eliminate.
            for i in range(len(faces_verts_idx)):
                for t in range(len(faces_verts_idx)):
                    if i != t:
                        verts_in_common = 0
                        
                        if len(faces_verts_idx[i]) == 4 and len(faces_verts_idx[t]) == 3:
                            for v_idx in faces_verts_idx[t]:
                                if v_idx in faces_verts_idx[i]:
                                    verts_in_common += 1
                                    
                            if verts_in_common == 3: # If it doesn't have all it's vertices repeated in the other face.
                                if not i in faces_to_not_include_idx:
                                    faces_to_not_include_idx.append(i)
            
            
            #### Build surface.
            all_surface_verts_co = []
            verts_idx_translation = {}
            for i in range(len(final_points_ob.data.vertices)):
                coords = final_points_ob.data.vertices[i].co
                all_surface_verts_co.append([coords[0], coords[1], coords[2]])
                
            # Verts of each face.
            all_surface_faces = []
            for i in range(len(faces_verts_idx)):
                if not i in faces_to_not_include_idx:
                    face = []
                    for v_idx in faces_verts_idx[i]:
                        face.append(v_idx)
                    
                    all_surface_faces.append(face)
            
            # 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)
            
            # Delete final points temporal object
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[final_points_ob.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[final_points_ob.name]
            
            bpy.ops.object.delete()
            
            
            # Delete isolated verts if there are any.
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[ob_surface.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[ob_surface.name]
            
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.mesh.select_all(action='DESELECT')
    
            bpy.ops.mesh.select_face_by_sides(type='NOTEQUAL')
    
            bpy.ops.mesh.delete()
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            
            
            
            #### Join crosshatch results with original mesh.
            
            # Calculate a distance to merge the verts of the crosshatch surface to the main object.
            edges_length_sum = 0
            for ed in ob_surface.data.edges:
                edges_length_sum += (ob_surface.data.vertices[ed.vertices[0]].co - ob_surface.data.vertices[ed.vertices[1]].co).length
            
            if len(ob_surface.data.edges) > 0:
                average_surface_edges_length = edges_length_sum / len(ob_surface.data.edges)
            else:
                average_surface_edges_length = 0.0001
            
            # Make dictionary with all the verts connected to each vert, on the new surface object.
            surface_connected_verts = {}
            for ed in ob_surface.data.edges:
                if not ed.vertices[0] in surface_connected_verts:
                    surface_connected_verts[ed.vertices[0]] = []
                    
                surface_connected_verts[ed.vertices[0]].append(ed.vertices[1])
                
                
                if not ed.vertices[1] in surface_connected_verts:
                    surface_connected_verts[ed.vertices[1]] = []
                    
                surface_connected_verts[ed.vertices[1]].append(ed.vertices[0])
                
            
            
            # Duplicate the new surface object, and use shrinkwrap to calculate later the nearest verts to the main object.
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
            
            final_ob_duplicate = bpy.context.scene.objects.active
            
            bpy.ops.object.modifier_add('INVOKE_REGION_WIN', type='SHRINKWRAP')
            final_ob_duplicate.modifiers["Shrinkwrap"].wrap_method = "NEAREST_VERTEX"
            final_ob_duplicate.modifiers["Shrinkwrap"].target = self.main_object
            
            bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
            
            
            # Make list with verts of original mesh as index and coords as value.
            main_object_verts_coords = []
            for v in self.main_object.data.vertices:
                coords = self.main_object.matrix_world * v.co
                
                for c in range(len(coords)): # To avoid problems when taking "-0.00" as a different value as "0.00".
                    if "%.3f" % coords[c] == "-0.00":
                        coords[c] = 0
                            
                main_object_verts_coords.append(["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]])
            
            tuple(main_object_verts_coords)
            
            
            # Determine which verts will be merged, snap them to the nearest verts on the original verts, and get them selected.
            crosshatch_verts_to_merge = []
            if self.automatic_join:
                for i in range(len(ob_surface.data.vertices)):
                    # Calculate the distance from each of the connected verts to the actual vert, and compare it with the distance they would have if joined. If they don't change much, that vert can be joined.
                    merge_actual_vert = True
                    if len(surface_connected_verts[i]) < 4:
                        for c_v_idx in surface_connected_verts[i]:
                            points_original = []
                            points_original.append(ob_surface.data.vertices[c_v_idx].co)
                            points_original.append(ob_surface.data.vertices[i].co)
                            
                            points_target = []
                            points_target.append(ob_surface.data.vertices[c_v_idx].co)
                            points_target.append(final_ob_duplicate.data.vertices[i].co)
                            
                            vec_A = points_original[0] - points_original[1]
                            vec_B = points_target[0] - points_target[1]
                            
                            dist_A = (points_original[0] - points_original[1]).length
                            dist_B = (points_target[0] - points_target[1]).length
                            
                            
                            if not (points_original[0] == points_original[1] or points_target[0] == points_target[1]): # If any vector's length is zero.
                                angle = vec_A.angle(vec_B) / math.pi
                            else:
                                angle= 0
                            
                            
                            if dist_B > dist_A * 1.7 * self.join_stretch_factor or dist_B < dist_A / 2 / self.join_stretch_factor or angle >= 0.15 * self.join_stretch_factor: # Set a range of acceptable variation in the connected edges.
                                merge_actual_vert = False
                                break
                    else:
                        merge_actual_vert = False
                        
                        
                    if merge_actual_vert:
                        coords = final_ob_duplicate.data.vertices[i].co
                        
                        for c in range(len(coords)): # To avoid problems when taking "-0.000" as a different value as "0.00".
                            if "%.3f" % coords[c] == "-0.00":
                                coords[c] = 0
                            
                        comparison_coords = ["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]]
                        
                        
                        if comparison_coords in main_object_verts_coords:
                            main_object_related_vert_idx = main_object_verts_coords.index(comparison_coords) # Get the index of the vert with those coords in the main object.
                            
                            if self.main_object.data.vertices[main_object_related_vert_idx].select == True or self.main_object_selected_verts_count == 0:
                                ob_surface.data.vertices[i].co = final_ob_duplicate.data.vertices[i].co
                                ob_surface.data.vertices[i].select = True
                                crosshatch_verts_to_merge.append(i)
                                
                                # Make sure the vert in the main object is selected, in case it wasn't selected and the "join crosshatch" option is active.
                                self.main_object.data.vertices[main_object_related_vert_idx].select = True
            
            
            
            
            # Delete duplicated object.
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[final_ob_duplicate.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[final_ob_duplicate.name]
            bpy.ops.object.delete()
            
            
            # Join crosshatched surface and main object.
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[ob_surface.name].select = True
            bpy.data.objects[self.main_object.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
            
            bpy.ops.object.join('INVOKE_REGION_WIN')
            
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            # Perform Remove doubles to merge verts.
            if not (self.automatic_join == False and self.main_object_selected_verts_count == 0):
    
                bpy.ops.mesh.remove_doubles(threshold=0.0001)
    
            
            bpy.ops.mesh.select_all(action='DESELECT')
            
            
            #### If the main object has modifiers, turn their "viewport view status" to what it was before the forced deactivation above.
            if len(self.main_object.modifiers) > 0:
                for m_idx in range(len(self.main_object.modifiers)):
                    self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx]
                
                
            
            return{'FINISHED'}
            
            
        
        def rectangular_surface(self):
            #### Selected edges.
            all_selected_edges_idx = []
            all_selected_verts = []
            all_verts_idx = []
            for ed in self.main_object.data.edges:
                if ed.select:
                    all_selected_edges_idx.append(ed.index)
                    
                    # Selected vertices.
                    if not ed.vertices[0] in all_selected_verts:
                        all_selected_verts.append(self.main_object.data.vertices[ed.vertices[0]])
                    if not ed.vertices[1] in all_selected_verts:
                        all_selected_verts.append(self.main_object.data.vertices[ed.vertices[1]])
                        
                    # All verts (both from each edge) to determine later which are at the tips (those not repeated twice).
                    all_verts_idx.append(ed.vertices[0])
                    all_verts_idx.append(ed.vertices[1])
            
            
            
            #### Identify the tips and "middle-vertex" that separates U from V, if there is one.
            all_chains_tips_idx = []
            for v_idx in all_verts_idx:
                if all_verts_idx.count(v_idx) < 2:
                    all_chains_tips_idx.append(v_idx)
            
            
            
            edges_connected_to_tips = []
            for ed in self.main_object.data.edges:
                if (ed.vertices[0] in all_chains_tips_idx or ed.vertices[1] in all_chains_tips_idx) and not (ed.vertices[0] in all_verts_idx and ed.vertices[1] in all_verts_idx):
                    edges_connected_to_tips.append(ed)
            
            
            #### Check closed selections.
            single_unselected_verts_and_neighbors = [] # List with groups of three verts, where the first element of the pair is the unselected vert of a closed selection and the other two elements are the selected neighbor verts (it will be useful to determine which selection chain the unselected vert belongs to, and determine the "middle-vertex")
            
            # To identify a "closed" selection (a selection that is a closed chain except for one vertex) find the vertex in common that have the edges connected to tips. If there is a vertex in common, that one is the unselected vert that closes the selection or is a "middle-vertex".
            single_unselected_verts = []
            for ed in edges_connected_to_tips:
                for ed_b in edges_connected_to_tips:
                    if ed != ed_b:
                        if ed.vertices[0] == ed_b.vertices[0] and not self.main_object.data.vertices[ed.vertices[0]].select and ed.vertices[0] not in single_unselected_verts:
                            single_unselected_verts_and_neighbors.append([ed.vertices[0], ed.vertices[1], ed_b.vertices[1]]) # The second element is one of the tips of the selected vertices of the closed selection.
                            single_unselected_verts.append(ed.vertices[0])
                            break
                        elif ed.vertices[0] == ed_b.vertices[1] and not self.main_object.data.vertices[ed.vertices[0]].select and ed.vertices[0] not in single_unselected_verts:
                            single_unselected_verts_and_neighbors.append([ed.vertices[0], ed.vertices[1], ed_b.vertices[0]])
                            single_unselected_verts.append(ed.vertices[0])
                            break
                        elif ed.vertices[1] == ed_b.vertices[0] and not self.main_object.data.vertices[ed.vertices[1]].select and ed.vertices[1] not in single_unselected_verts:
                            single_unselected_verts_and_neighbors.append([ed.vertices[1], ed.vertices[0], ed_b.vertices[1]])
                            single_unselected_verts.append(ed.vertices[1])
                            break
                        elif ed.vertices[1] == ed_b.vertices[1] and not self.main_object.data.vertices[ed.vertices[1]].select and ed.vertices[1] not in single_unselected_verts:
                            single_unselected_verts_and_neighbors.append([ed.vertices[1], ed.vertices[0], ed_b.vertices[0]])
                            single_unselected_verts.append(ed.vertices[1])
                            break
            
            
            middle_vertex_idx = None
            tips_to_discard_idx = []
            # Check if there is a "middle-vertex", and get its index.
            for i in range(0, len(single_unselected_verts_and_neighbors)):
                actual_chain_verts = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, single_unselected_verts_and_neighbors[i][1], None, None)
                
                if single_unselected_verts_and_neighbors[i][2] != actual_chain_verts[len(actual_chain_verts) - 1].index:
                    middle_vertex_idx = single_unselected_verts_and_neighbors[i][0]
                    tips_to_discard_idx.append(single_unselected_verts_and_neighbors[i][1])
                    tips_to_discard_idx.append(single_unselected_verts_and_neighbors[i][2])
                
                
            #### List with pairs of verts that belong to the tips of each selection chain (row).
            verts_tips_same_chain_idx = []
            if len(all_chains_tips_idx) >= 2:
                checked_v = []
                for i in range(0, len(all_chains_tips_idx)):
                    if all_chains_tips_idx[i] not in checked_v:
                        v_chain = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, all_chains_tips_idx[i], middle_vertex_idx, None)
                        
                        verts_tips_same_chain_idx.append([v_chain[0].index, v_chain[len(v_chain) - 1].index])
                        
                        checked_v.append(v_chain[0].index)
                        checked_v.append(v_chain[len(v_chain) - 1].index)
            
            
            #### Selection tips (vertices).
            verts_tips_parsed_idx = []
            if len(all_chains_tips_idx) >= 2:
                for spec_v_idx in all_chains_tips_idx:
                    if (spec_v_idx not in tips_to_discard_idx):
                        verts_tips_parsed_idx.append(spec_v_idx)
            
        
            #### Identify the type of selection made by the user.
            if middle_vertex_idx != None:
                if len(all_chains_tips_idx) == 4 and len(single_unselected_verts_and_neighbors) == 1: # If there are 4 tips (two selection chains), and there is only one single unselected vert (the middle vert).
                    selection_type = "TWO_CONNECTED"
                else:
                    # The type of the selection was not identified, the script stops.
                    self.report({'WARNING'}, "The selection isn't valid.")
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    self.cleanup_on_interruption()
                    self.stopping_errors = True
                    
                    return{'CANCELLED'}
            else:
                if len(all_chains_tips_idx) == 2: # If there are 2 tips
                    selection_type = "SINGLE"
                elif len(all_chains_tips_idx) == 4: # If there are 4 tips
                    selection_type = "TWO_NOT_CONNECTED"
                elif len(all_chains_tips_idx) == 0:
                    if len(self.main_splines.data.splines) > 1:
                        selection_type = "NO_SELECTION"
                    else:
                        # If the selection was not identified and there is only one stroke, there's no possibility to build a surface, so the script is interrupted.
                        self.report({'WARNING'}, "The selection isn't valid.")
                        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                        self.cleanup_on_interruption()
                        self.stopping_errors = True
                        
                        return{'CANCELLED'}
                else:
                    # The type of the selection was not identified, the script stops.
                    self.report({'WARNING'}, "The selection isn't valid.")
                    
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    self.cleanup_on_interruption()
                    
                    self.stopping_errors = True
                    
                    return{'CANCELLED'}
            
            
            
            #### If the selection type is TWO_NOT_CONNECTED and there is only one stroke, stop the script.
            if selection_type == "TWO_NOT_CONNECTED" and len(self.main_splines.data.splines) == 1:
                self.report({'WARNING'}, "At least two strokes are needed when there are two not connected selections.")
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                self.cleanup_on_interruption()
                self.stopping_errors = True
                
                return{'CANCELLED'}
            
            
            
            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.context.scene.objects[self.main_splines.name]
            
            
            #### Enter editmode for the new curve (converted from grease pencil strokes), to smooth it out.
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            
            
            self.selection_U_exists = False
            self.selection_U2_exists = False
            self.selection_V_exists = False
            self.selection_V2_exists = False
            
            self.selection_U_is_closed = False
            self.selection_U2_is_closed = False
            self.selection_V_is_closed = False
            self.selection_V2_is_closed = False
            
            #### Define what vertices are at the tips of each selection and are not the middle-vertex.
            if selection_type == "TWO_CONNECTED":
                self.selection_U_exists = True
                self.selection_V_exists = True
                
                closing_vert_U_idx = None
                closing_vert_V_idx = None
                closing_vert_U2_idx = None
                closing_vert_V2_idx = None
                
                # Determine which selection is Selection-U and which is Selection-V.
                points_A = []
                points_B = []
                points_first_stroke_tips = []
                
                points_A.append(self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_parsed_idx[0]].co)
                points_A.append(self.main_object.matrix_world * self.main_object.data.vertices[middle_vertex_idx].co)
                
                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 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: # 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"
                    self.selection_U_exists = False
                    self.selection_V_exists = True
                    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_V_is_closed = False
                        first_neighbor_V_idx = None
                        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