Skip to content
Snippets Groups Projects
mesh_bsurfaces.py 179 KiB
Newer Older
  • Learn to ignore specific revisions
  • 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342
                
                #### Make sure there are no objects left from erroneous executions of this operator, with the reserved names used here.
                for o in bpy.data.objects:
                    if o.name.find("SURFSKIO_") != -1:
                        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()
                
                
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.original_curve.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
                
                bpy.ops.object.duplicate('INVOKE_REGION_WIN')
                
                
                self.temporary_curve = bpy.context.scene.objects.active
                
                
                # Deselect all points of the curve
                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')
                
                
                
                # Delete splines with only a single isolated point.
                for i in range(len(self.temporary_curve.data.splines)):
                    sp = self.temporary_curve.data.splines[i]
                    
                    if len(sp.bezier_points) == 1:
                        sp.bezier_points[0].select_control_point = True
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.curve.delete(type='SELECTED')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.temporary_curve.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
                
                #### Set a minimum number of points for crosshatch
                minimum_points_num = 15
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                # Check if the number of points of each curve has at least the number of points of minimum_points_num, which is a bit more than the face-loops limit. If not, subdivide to reach at least that number of ponts.
                for i in range(len(self.temporary_curve.data.splines)):
                    sp = self.temporary_curve.data.splines[i]
                    
                    if len(sp.bezier_points) < minimum_points_num:
                        for bp in sp.bezier_points:
                            bp.select_control_point = True
                        
                        if (len(sp.bezier_points) - 1) != 0:
                            subdivide_cuts = int((minimum_points_num - len(sp.bezier_points)) / (len(sp.bezier_points) - 1)) + 1 # Formula to get the number of cuts that will make a curve of N number of points have near to "minimum_points_num" points, when subdividing with this number of cuts.
                        else:
                            subdivide_cuts = 0
                            
                        
                        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
                        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                        
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                
                
                # Detect if the strokes are a crosshatch and do it if it is.
                self.crosshatch_surface_invoke(self.temporary_curve)
                
                
                
                if not self.is_crosshatch:
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.data.objects[self.temporary_curve.name].select = True
                    bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
                    
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    
                    #### Set a minimum number of points for rectangular surfaces.
                    minimum_points_num = 60
                    
                    # Check if the number of points of each curve has at least the number of points of minimum_points_num, which is a bit more than the face-loops limit. If not, subdivide to reach at least that number of ponts.
                    for i in range(len(self.temporary_curve.data.splines)):
                        sp = self.temporary_curve.data.splines[i]
                        
                        if len(sp.bezier_points) < minimum_points_num:
                            for bp in sp.bezier_points:
                                bp.select_control_point = True
                            
                            if (len(sp.bezier_points) - 1) != 0:
                                subdivide_cuts = int((minimum_points_num - len(sp.bezier_points)) / (len(sp.bezier_points) - 1)) + 1 # Formula to get the number of cuts that will make a curve of N number of points have near to "minimum_points_num" points, when subdividing with this number of cuts.
                            else:
                                subdivide_cuts = 0
                                
                            
                            bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
                            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                            
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    
                
                
                
                # Save coordinates of the actual strokes (as the "last saved splines").
                for sp_idx in range(len(self.temporary_curve.data.splines)):
                    self.last_strokes_splines_coords.append([])
                    for bp_idx in range(len(self.temporary_curve.data.splines[sp_idx].bezier_points)):
                        coords = self.temporary_curve.matrix_world * self.temporary_curve.data.splines[sp_idx].bezier_points[bp_idx].co
                        self.last_strokes_splines_coords[sp_idx].append([coords[0], coords[1], coords[2]])
                
                
                # Check for cyclic splines, put the first and last points in the middle of their actual positions.
                for sp_idx in range(len(self.temporary_curve.data.splines)):
                    if self.temporary_curve.data.splines[sp_idx].use_cyclic_u == True:
                        first_p_co = self.last_strokes_splines_coords[sp_idx][0]
                        last_p_co = self.last_strokes_splines_coords[sp_idx][len(self.last_strokes_splines_coords[sp_idx]) - 1]
                        
                        target_co = [(first_p_co[0] + last_p_co[0]) / 2, (first_p_co[1] + last_p_co[1]) / 2, (first_p_co[2] + last_p_co[2]) / 2]
                        
                        self.last_strokes_splines_coords[sp_idx][0] = target_co
                        self.last_strokes_splines_coords[sp_idx][len(self.last_strokes_splines_coords[sp_idx]) - 1] = target_co
                
                tuple(self.last_strokes_splines_coords)
                
                
                
                # Estimation of the average length of the segments between each point of the grease pencil strokes. Will be useful to determine whether a curve should be made "Cyclic".
                segments_lengths_sum = 0
                segments_count = 0
                random_spline = self.temporary_curve.data.splines[0].bezier_points
                for i in range(0, len(random_spline)):
                    if i != 0 and len(random_spline) - 1 >= i:
                        segments_lengths_sum += (random_spline[i - 1].co - random_spline[i].co).length
                        segments_count += 1
                
                self.average_gp_segment_length = segments_lengths_sum / segments_count
                
                
                #### Delete temporary strokes curve object
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.temporary_curve.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
                
                bpy.ops.object.delete()
                
                
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.main_object.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                    
                self.execute(context)
                bpy.context.user_preferences.edit.use_global_undo = False # Set again since "execute()" will turn it again to its initial value.
                
                
                #### If "Keep strokes" option is not active, delete original strokes curve object. 
                if (not self.stopping_errors and not self.keep_strokes) or self.is_crosshatch:
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.data.objects[self.original_curve.name].select = True
                    bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
                    
                    bpy.ops.object.delete()
                    
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                    bpy.data.objects[self.main_object.name].select = True
                    bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
                    
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                    
                
                
                #### Delete grease pencil strokes.
                if self.strokes_type == "GP_STROKES" and not self.stopping_errors:
                    bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
                
                
                bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
                
                
                if not self.stopping_errors:
                    return {"FINISHED"}
                else:
                    return{"CANCELLED"}
                
            elif self.strokes_type == "SELECTION_ALONE":
                self.is_fill_faces = True
                
                created_faces_count = self.fill_with_faces(self.main_object)
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                if created_faces_count == 0:
                    self.report({'WARNING'}, "There aren't any strokes.")
                    return {"CANCELLED"}
                else:
                    return {"FINISHED"}
                
                
                
                
            elif self.strokes_type == "EXTERNAL_NO_CURVE":
                self.report({'WARNING'}, "The secondary object is not a Curve.")
                return{"CANCELLED"}
            
            elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
                self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
                return{"CANCELLED"}
            
            elif self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
                self.report({'WARNING'}, "It's needed at least one stroke and one selection, or two strokes.")
                return{"CANCELLED"}
            
            elif self.strokes_type == "NO_STROKES":
                self.report({'WARNING'}, "There aren't any strokes.")
                return{"CANCELLED"}
            
            elif self.strokes_type == "CURVE_WITH_NON_BEZIER_SPLINES":
                self.report({'WARNING'}, "All splines must be Bezier.")
                return{"CANCELLED"}
            
            else:
                return{"CANCELLED"}
    
    
    # Edit strokes operator.
    class GPENCIL_OT_SURFSK_edit_strokes(bpy.types.Operator):
        bl_idname = "gpencil.surfsk_edit_strokes"
        bl_label = "Bsurfaces edit strokes"
        bl_description = "Edit the grease pencil strokes or curves used."
        
        
        def execute(self, context):
            #### Determine the type of the strokes.
            self.strokes_type = get_strokes_type(self.main_object)
            #### Check if strokes are grease pencil strokes or a curves object.
            selected_objs = bpy.context.selected_objects
            if self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
                for ob in selected_objs:
                    if ob != bpy.context.scene.objects.active:
                        curve_ob = ob
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[curve_ob.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[curve_ob.name]
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            elif self.strokes_type == "GP_STROKES" or self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION":
                #### Convert grease pencil strokes to curve.
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE')
                ob_gp_strokes = bpy.context.object
                
                #### Delete grease pencil strokes.
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[self.main_object.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
                
                bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
                
                
                #### Clean up curves.
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[ob_gp_strokes.name].select = True
                bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
                
                curve_crv = ob_gp_strokes.data
                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")
                bpy.data.curves[curve_crv.name].show_handles = False
                bpy.data.curves[curve_crv.name].show_normal_face = False
                
            elif self.strokes_type == "EXTERNAL_NO_CURVE":
                self.report({'WARNING'}, "The secondary object is not a Curve.")
                return{"CANCELLED"}
            elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
                self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
                return{"CANCELLED"}
            elif self.strokes_type == "NO_STROKES" or self.strokes_type == "SELECTION_ALONE":
                self.report({'WARNING'}, "There aren't any strokes.")
                return{"CANCELLED"}
            else:
                return{"CANCELLED"}
                    
           
           
        def invoke (self, context, event):
            self.main_object = bpy.context.object
            
            self.execute(context)
            
            return {"FINISHED"}
    
    
    
    
    class CURVE_OT_SURFSK_reorder_splines(bpy.types.Operator):
        bl_idname = "curve.surfsk_reorder_splines"
        bl_label = "Bsurfaces reorder splines"
        bl_description = "Defines the order of the splines by using grease pencil strokes."
        bl_options = {'REGISTER', 'UNDO'}
        
        
        
        def execute(self, context):
            objects_to_delete = []
            #### Convert grease pencil strokes to curve.
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE')
            
            GP_strokes_curve = bpy.context.object
            objects_to_delete.append(GP_strokes_curve)
            
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[GP_strokes_curve.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[GP_strokes_curve.name]
            
            
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
            bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = 100)
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
            GP_strokes_mesh = bpy.context.object
            objects_to_delete.append(GP_strokes_mesh)
            
            GP_strokes_mesh.data.resolution_u = 1
            bpy.ops.object.convert(target='MESH', keep_original=False)
            
            
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[self.main_curve.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[self.main_curve.name]
    
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
            curves_duplicate_1 = bpy.context.object
            objects_to_delete.append(curves_duplicate_1)
    
            
            minimum_points_num = 500
            
            
            for x in range(round(minimum_points_num / 100)): # Some iterations since the subdivision operator has a limit of 100 subdivisions per iteration.
                #### Check if the number of points of each curve has at least the number of points of minimum_points_num. If not, subdivide to reach at least that number of ponts.
                for i in range(len(curves_duplicate_1.data.splines)):
                    sp = curves_duplicate_1.data.splines[i]
                    
                    if len(sp.bezier_points) < minimum_points_num:
                        for bp in sp.bezier_points:
                            bp.select_control_point = True
    
                        if (len(sp.bezier_points) - 1) != 0:
                            subdivide_cuts = int((minimum_points_num - len(sp.bezier_points)) / (len(sp.bezier_points) - 1)) + 1 # Formula to get the number of cuts that will make a curve of N number of points have near to "minimum_points_num" points, when subdividing with this number of cuts.
                        else:
                            subdivide_cuts = 0
    
                        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
                        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
            curves_duplicate_2 = bpy.context.object
            objects_to_delete.append(curves_duplicate_2)
    
            #### Duplicate the duplicate and add Shrinkwrap to it, with the grease pencil strokes curve as target.
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[curves_duplicate_2.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[curves_duplicate_2.name]
    
            bpy.ops.object.modifier_add('INVOKE_REGION_WIN', type='SHRINKWRAP')
            curves_duplicate_2.modifiers["Shrinkwrap"].wrap_method = "NEAREST_VERTEX"
            curves_duplicate_2.modifiers["Shrinkwrap"].target = GP_strokes_mesh
            bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
            
            
            #### Get the distance of each vert from its original position to its position with Shrinkwrap.
            nearest_points_coords = {}
            for st_idx in range(len(curves_duplicate_1.data.splines)):
                for bp_idx in range(len(curves_duplicate_1.data.splines[st_idx].bezier_points)):
                    bp_1_co = curves_duplicate_1.matrix_world * curves_duplicate_1.data.splines[st_idx].bezier_points[bp_idx].co
                    bp_2_co = curves_duplicate_2.matrix_world * curves_duplicate_2.data.splines[st_idx].bezier_points[bp_idx].co
    
                    if bp_idx == 0:
                        shortest_dist = (bp_1_co - bp_2_co).length
                        nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0], "%.4f" % bp_2_co[1], "%.4f" % bp_2_co[2])
                        
                    dist = (bp_1_co - bp_2_co).length
    
                    if dist < shortest_dist:
                        nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0], "%.4f" % bp_2_co[1], "%.4f" % bp_2_co[2])
                        shortest_dist = dist
    
            #### Get all coords of GP strokes points, for comparison.
            GP_strokes_coords = []
            for st_idx in range(len(GP_strokes_curve.data.splines)):
                GP_strokes_coords.append([("%.4f" % x if "%.4f" % x != "-0.00" else "0.00", "%.4f" % y if "%.4f" % y != "-0.00" else "0.00", "%.4f" % z if "%.4f" % z != "-0.00" else "0.00") for x, y, z in [bp.co for bp in GP_strokes_curve.data.splines[st_idx].bezier_points]])
    
            #### Check the point of the GP strokes with the same coords as the nearest points of the curves (with shrinkwrap).
            GP_connection_points = {} # Dictionary with GP stroke index as index, and a list as value. The list has as index the point index of the GP stroke nearest to the spline, and as value the spline index.
            for gp_st_idx in range(len(GP_strokes_coords)):
                GPvert_spline_relationship = {}
                
                for splines_st_idx in range(len(nearest_points_coords)):
                    if nearest_points_coords[splines_st_idx] in GP_strokes_coords[gp_st_idx]:
                        GPvert_spline_relationship[GP_strokes_coords[gp_st_idx].index(nearest_points_coords[splines_st_idx])] = splines_st_idx
    
                
                GP_connection_points[gp_st_idx] = GPvert_spline_relationship
    
            #### Get the splines new order.
            splines_new_order = []
            for i in GP_connection_points:
                dict_keys = sorted(GP_connection_points[i].keys()) # Sort dictionaries by key
                
                for k in dict_keys:
                    splines_new_order.append(GP_connection_points[i][k])
                    
    
            #### Reorder.
    
            curve_original_name = self.main_curve.name
    
    Eclectiel L's avatar
    Eclectiel L committed
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
            bpy.data.objects[self.main_curve.name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[self.main_curve.name]
    
            self.main_curve.name = "SURFSKIO_CRV_ORD"
    
            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')
    
            for sp_idx in range(len(self.main_curve.data.splines)):
                self.main_curve.data.splines[0].bezier_points[0].select_control_point = True
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                bpy.ops.curve.separate('INVOKE_REGION_WIN')
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            #### Get the names of the separated splines objects in the original order.
            splines_unordered = {}
            for o in bpy.data.objects:
                if o.name.find("SURFSKIO_CRV_ORD") != -1:
                    spline_order_string = o.name.partition(".")[2]
                    
                    if spline_order_string != "" and int(spline_order_string) > 0:
                        spline_order_index = int(spline_order_string) - 1
                        splines_unordered[spline_order_index] = o.name
    
            #### Join all splines objects in final order.
            for order_idx in splines_new_order:
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
                bpy.data.objects[splines_unordered[order_idx]].select = True
                bpy.data.objects["SURFSKIO_CRV_ORD"].select = True
                bpy.context.scene.objects.active = bpy.data.objects["SURFSKIO_CRV_ORD"]
                
                bpy.ops.object.join('INVOKE_REGION_WIN')
                
                
            #### Go back to the original name of the curves object.
            bpy.context.object.name = curve_original_name
    
            #### Delete all unused objects.
            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()
                
    
    Eclectiel L's avatar
    Eclectiel L committed
            
    
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
            bpy.data.objects[curve_original_name].select = True
            bpy.context.scene.objects.active = bpy.data.objects[curve_original_name]
    
    Eclectiel L's avatar
    Eclectiel L committed
            
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
    Eclectiel L's avatar
    Eclectiel L committed
            
    
            bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
    
            
        def invoke (self, context, event):
    
            self.main_curve = bpy.context.object
    
            there_are_GP_strokes = False
            try:
                #### Get the active grease pencil layer.
                strokes_num = len(self.main_curve.grease_pencil.layers.active.active_frame.strokes)
                
                if strokes_num > 0:
                    there_are_GP_strokes = True
            except:
                pass
                
            
            if there_are_GP_strokes:
                self.execute(context)
                self.report({'INFO'}, "Splines have been reordered.")
            else:
                self.report({'WARNING'}, "Draw grease pencil strokes to connect splines.")
            
            return {"FINISHED"}
        
        
        
    
    class CURVE_OT_SURFSK_first_points(bpy.types.Operator):
        bl_idname = "curve.surfsk_first_points"
        bl_label = "Bsurfaces set first points"
        bl_description = "Set the selected points as the first point of each spline."
        bl_options = {'REGISTER', 'UNDO'}
        
    
        def execute(self, context):
    
            splines_to_invert = []
    
            #### Check non-cyclic splines to invert.
            for i in range(len(self.main_curve.data.splines)):
                b_points = self.main_curve.data.splines[i].bezier_points
                
                if not i in self.cyclic_splines: # Only for non-cyclic splines
                    if b_points[len(b_points) - 1].select_control_point:
                        splines_to_invert.append(i)
    
            #### Reorder points of cyclic splines, and set all handles to "Automatic".
            
            # Check first selected point.
            cyclic_splines_new_first_pt = {}
            for i in self.cyclic_splines:
                sp = self.main_curve.data.splines[i]
                
                for t in range(len(sp.bezier_points)):
                    bp = sp.bezier_points[t]
                    if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
                        cyclic_splines_new_first_pt[i] = t
                        break # To take only one if there are more.
            
            # Reorder.
            for spline_idx in cyclic_splines_new_first_pt:
                sp = self.main_curve.data.splines[spline_idx]
                
                spline_old_coords = []
                for bp_old in sp.bezier_points:
                    coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
                    
                    left_handle_type = str(bp_old.handle_left_type)
                    left_handle_length = float(bp_old.handle_left.length)
                    left_handle_xyz = (float(bp_old.handle_left.x), float(bp_old.handle_left.y), float(bp_old.handle_left.z))
                    
                    right_handle_type = str(bp_old.handle_right_type)
                    right_handle_length = float(bp_old.handle_right.length)
                    right_handle_xyz = (float(bp_old.handle_right.x), float(bp_old.handle_right.y), float(bp_old.handle_right.z))
                    
                    spline_old_coords.append([coords, left_handle_type, right_handle_type, left_handle_length, right_handle_length, left_handle_xyz, right_handle_xyz])
                    
                
                for t in range(len(sp.bezier_points)):
                    bp = sp.bezier_points
                    
                    if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
                        new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
                    else:
                        new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
                    
                    bp[t].co = mathutils.Vector(spline_old_coords[new_index][0])
                    
                    bp[t].handle_left.length = spline_old_coords[new_index][3]
                    bp[t].handle_right.length = spline_old_coords[new_index][4]
                    
                    bp[t].handle_left_type = "FREE"
                    bp[t].handle_right_type = "FREE"
                    
                    bp[t].handle_left.x = spline_old_coords[new_index][5][0]
                    bp[t].handle_left.y = spline_old_coords[new_index][5][1]
                    bp[t].handle_left.z = spline_old_coords[new_index][5][2]
                    
                    bp[t].handle_right.x = spline_old_coords[new_index][6][0]
                    bp[t].handle_right.y = spline_old_coords[new_index][6][1]
                    bp[t].handle_right.z = spline_old_coords[new_index][6][2]
                    
                    bp[t].handle_left_type = spline_old_coords[new_index][1]
                    bp[t].handle_right_type = spline_old_coords[new_index][2]
                    
            
            
            #### Invert the non-cyclic splines designated above.
            for i in range(len(splines_to_invert)):
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
                
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                self.main_curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
                
                bpy.ops.curve.switch_direction()
                
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
            #### Keep selected the first vert of each spline.
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
            for i in range(len(self.main_curve.data.splines)):
                if not self.main_curve.data.splines[i].use_cyclic_u:
                    bp = self.main_curve.data.splines[i].bezier_points[0]
                else:
                    bp = self.main_curve.data.splines[i].bezier_points[len(self.main_curve.data.splines[i].bezier_points) - 1]
                
                bp.select_control_point = True
                bp.select_right_handle = True
                bp.select_left_handle = True
    
    Eclectiel L's avatar
    Eclectiel L committed
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
        def invoke (self, context, event):
    
            self.main_curve = bpy.context.object
    
            # Check if all curves are Bezier, and detect which ones are cyclic.
            self.cyclic_splines = []
            for i in range(len(self.main_curve.data.splines)):
                if self.main_curve.data.splines[i].type != "BEZIER":
                    self.report({'WARNING'}, 'All splines must be Bezier type.')
                    
                    return {'CANCELLED'}
                else:
                    if self.main_curve.data.splines[i].use_cyclic_u:
                        self.cyclic_splines.append(i)
                        
                        
    
            
            self.execute(context)
    
            self.report({'INFO'}, "First points have been set.")
    
    def register():
    
        bpy.utils.register_class(VIEW3D_PT_tools_SURFSK_mesh)
        bpy.utils.register_class(VIEW3D_PT_tools_SURFSK_curve)
    
    Eclectiel L's avatar
    Eclectiel L committed
        bpy.utils.register_class(GPENCIL_OT_SURFSK_add_surface)
    
        bpy.utils.register_class(GPENCIL_OT_SURFSK_edit_strokes)
        bpy.utils.register_class(CURVE_OT_SURFSK_reorder_splines)
        bpy.utils.register_class(CURVE_OT_SURFSK_first_points)
        
        
    
    Eclectiel L's avatar
    Eclectiel L committed
        
    
        bpy.types.Scene.SURFSK_cyclic_cross = bpy.props.BoolProperty(
            name="Cyclic Cross",
            description="Make cyclic the face-loops crossing the strokes.",
            default=False)
            
        bpy.types.Scene.SURFSK_cyclic_follow = bpy.props.BoolProperty(
            name="Cyclic Follow",
            description="Make cyclic the face-loops following the strokes.",
            default=False)
            
        bpy.types.Scene.SURFSK_keep_strokes = bpy.props.BoolProperty(
            name="Keep strokes",
            description="Keeps the sketched strokes or curves after adding the surface.",
            default=False)
        
        bpy.types.Scene.SURFSK_automatic_join = bpy.props.BoolProperty(
            name="Automatic join",
            description="Join automatically vertices of either surfaces generated by crosshatching, or from the borders of closed shapes.",
            default=True)
        
        bpy.types.Scene.SURFSK_loops_on_strokes = bpy.props.BoolProperty(
            name="Loops on strokes",
            description="Make the loops match the paths of the strokes.",
            default=True)
    
        bpy.types.Scene.SURFSK_precision = bpy.props.IntProperty(
            name="Precision",
            description="Precision level of the surface calculation.",
            default=2,
            min=1,
            max=100)
    
    Eclectiel L's avatar
    Eclectiel L committed
        
    
    def unregister():
    
        bpy.utils.unregister_class(VIEW3D_PT_tools_SURFSK_mesh)
        bpy.utils.unregister_class(VIEW3D_PT_tools_SURFSK_curve)
    
    Eclectiel L's avatar
    Eclectiel L committed
        bpy.utils.unregister_class(GPENCIL_OT_SURFSK_add_surface)
    
        bpy.utils.unregister_class(GPENCIL_OT_SURFSK_edit_strokes)
        bpy.utils.unregister_class(CURVE_OT_SURFSK_reorder_splines)
        bpy.utils.unregister_class(CURVE_OT_SURFSK_first_points)
    
    Eclectiel L's avatar
    Eclectiel L committed
        
    
        del bpy.types.Scene.SURFSK_precision
        del bpy.types.Scene.SURFSK_keep_strokes
    
        del bpy.types.Scene.SURFSK_automatic_join
        del bpy.types.Scene.SURFSK_cyclic_cross
        del bpy.types.Scene.SURFSK_cyclic_follow
        del bpy.types.Scene.SURFSK_loops_on_strokes
    
    Eclectiel L's avatar
    Eclectiel L committed
        
    
    if __name__ == "__main__":
    
    Guillermo S. Romero's avatar
    Guillermo S. Romero committed
        register()