~ubuntu-branches/ubuntu/saucy/blender/saucy-proposed

« back to all changes in this revision

Viewing changes to release/scripts/addons/mesh_bsurfaces.py

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2013-03-06 12:08:47 UTC
  • mfrom: (1.5.1) (14.1.8 experimental)
  • Revision ID: package-import@ubuntu.com-20130306120847-frjfaryb2zrotwcg
Tags: 2.66a-1ubuntu1
* Resynchronize with Debian (LP: #1076930, #1089256, #1052743, #999024,
  #1122888, #1147084)
* debian/control:
  - Lower build-depends on libavcodec-dev since we're not
    doing the libav9 transition in Ubuntu yet

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
#
3
3
#  This program is free software; you can redistribute it and/or
4
4
#  modify it under the terms of the GNU General Public License
5
 
#  as published by the Free Software Foundation; either version 2
6
 
#  of the License, or (at your option) any later version.
 
5
#  as published by the Free Software Foundation; version 2
 
6
#  of the License.
7
7
#
8
8
#  This program is distributed in the hope that it will be useful,
9
9
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
16
16
#
17
17
# ##### END GPL LICENSE BLOCK #####
18
18
 
 
19
 
19
20
bl_info = {
20
21
    "name": "Bsurfaces GPL Edition",
21
22
    "author": "Eclectiel",
22
 
    "version": (0,9),
23
 
    "blender": (2, 5, 7),
 
23
    "version": (1,5),
 
24
    "blender": (2, 63, 0),
 
25
    "api": 45996,
24
26
    "location": "View3D > EditMode > ToolShelf",
25
 
    "description": "Draw meshes and re-topologies with Grease Pencil",
26
 
    "warning": "Beta",
27
 
    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
28
 
        "Scripts/Mesh/Surface_Sketch",
29
 
    "tracker_url": "https://projects.blender.org/tracker/index.php?"\
30
 
        "func=detail&aid=26642&group_id=153&atid=469",
 
27
    "description": "Modeling and retopology tool.",
 
28
    "wiki_url": "http://www.bsurfaces.info",
 
29
    "tracker_url": "http://projects.blender.org/tracker/index.php?"\
 
30
        "func=detail&aid=26642",
31
31
    "category": "Mesh"}
32
 
 
 
32
        
33
33
 
34
34
import bpy
 
35
import bmesh
35
36
import math
 
37
import mathutils
 
38
import operator
36
39
 
37
40
from math import *
38
 
   
39
 
 
40
 
class VIEW3D_PT_tools_SURF_SKETCH(bpy.types.Panel):
 
41
 
 
42
 
 
43
 
 
44
 
 
45
class VIEW3D_PT_tools_SURFSK_mesh(bpy.types.Panel):
41
46
    bl_space_type = 'VIEW_3D'
42
47
    bl_region_type = 'TOOLS'
43
 
 
44
48
    bl_context = "mesh_edit"
45
49
    bl_label = "Bsurfaces"
46
50
    
47
51
    @classmethod
48
52
    def poll(cls, context):
49
53
        return context.active_object
50
 
 
 
54
    
 
55
    
51
56
    def draw(self, context):
52
57
        layout = self.layout
53
58
        
54
59
        scn = context.scene
 
60
        ob = context.object
55
61
        
56
62
        col = layout.column(align=True)
 
63
        row = layout.row()
 
64
        row.separator()
57
65
        col.operator("gpencil.surfsk_add_surface", text="Add Surface")
58
 
        col.prop(scn, "SURFSK_edges_U")
59
 
        col.prop(scn, "SURFSK_edges_V")
60
 
        
61
 
        layout.prop(scn, "SURFSK_keep_strokes")
62
 
        layout.operator("gpencil.surfsk_strokes_to_curves", text="Strokes to curves")
63
 
        
64
 
 
 
66
        col.operator("gpencil.surfsk_edit_strokes", text="Edit Strokes")
 
67
        col.prop(scn, "SURFSK_cyclic_cross")
 
68
        col.prop(scn, "SURFSK_cyclic_follow")
 
69
        col.prop(scn, "SURFSK_loops_on_strokes")
 
70
        col.prop(scn, "SURFSK_automatic_join")
 
71
        col.prop(scn, "SURFSK_keep_strokes")
 
72
        
 
73
        
 
74
        
 
75
class VIEW3D_PT_tools_SURFSK_curve(bpy.types.Panel):
 
76
    bl_space_type = 'VIEW_3D'
 
77
    bl_region_type = 'TOOLS'
 
78
    bl_context = "curve_edit"
 
79
    bl_label = "Bsurfaces"
 
80
    
 
81
    @classmethod
 
82
    def poll(cls, context):
 
83
        return context.active_object
 
84
    
 
85
    
 
86
    def draw(self, context):
 
87
        layout = self.layout
 
88
        
 
89
        scn = context.scene
 
90
        ob = context.object
 
91
        
 
92
        col = layout.column(align=True)
 
93
        row = layout.row()
 
94
        row.separator()
 
95
        col.operator("curve.surfsk_first_points", text="Set First Points")
 
96
        col.operator("curve.switch_direction", text="Switch Direction")
 
97
        col.operator("curve.surfsk_reorder_splines", text="Reorder Splines")
 
98
        
 
99
 
 
100
 
 
101
 
 
102
#### Returns the type of strokes used.
 
103
def get_strokes_type(main_object):
 
104
    strokes_type = ""
 
105
    strokes_num = 0
 
106
    
 
107
    # Check if they are grease pencil
 
108
    try:
 
109
        #### Get the active grease pencil layer.
 
110
        strokes_num = len(main_object.grease_pencil.layers.active.active_frame.strokes)
 
111
        
 
112
        if strokes_num > 0:
 
113
            strokes_type = "GP_STROKES"
 
114
    except:
 
115
        pass
 
116
    
 
117
    
 
118
    # Check if they are curves, if there aren't grease pencil strokes.
 
119
    if strokes_type == "":
 
120
        if len(bpy.context.selected_objects) == 2:
 
121
            for ob in bpy.context.selected_objects:
 
122
                if ob != bpy.context.scene.objects.active and ob.type == "CURVE":
 
123
                    strokes_type = "EXTERNAL_CURVE"
 
124
                    strokes_num = len(ob.data.splines)
 
125
                    
 
126
                    # Check if there is any non-bezier spline.
 
127
                    for i in range(len(ob.data.splines)):
 
128
                        if ob.data.splines[i].type != "BEZIER":
 
129
                            strokes_type = "CURVE_WITH_NON_BEZIER_SPLINES"
 
130
                            break
 
131
                            
 
132
                elif ob != bpy.context.scene.objects.active and ob.type != "CURVE":
 
133
                    strokes_type = "EXTERNAL_NO_CURVE"
 
134
        elif len(bpy.context.selected_objects) > 2:
 
135
            strokes_type = "MORE_THAN_ONE_EXTERNAL"
 
136
    
 
137
    
 
138
    # Check if there is a single stroke without any selection in the object.
 
139
    if strokes_num == 1 and main_object.data.total_vert_sel == 0:
 
140
        if strokes_type == "EXTERNAL_CURVE":
 
141
            strokes_type = "SINGLE_CURVE_STROKE_NO_SELECTION"
 
142
        elif strokes_type == "GP_STROKES":
 
143
            strokes_type = "SINGLE_GP_STROKE_NO_SELECTION"
 
144
        
 
145
    if strokes_num == 0 and main_object.data.total_vert_sel > 0:
 
146
        strokes_type = "SELECTION_ALONE"
 
147
        
 
148
        
 
149
    if strokes_type == "":
 
150
        strokes_type = "NO_STROKES"
 
151
    
 
152
    
 
153
    
 
154
    return strokes_type
 
155
 
 
156
 
 
157
 
 
158
 
 
159
# Surface generator operator.
65
160
class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator):
66
161
    bl_idname = "gpencil.surfsk_add_surface"
67
162
    bl_label = "Bsurfaces add surface"
68
 
    bl_description = "Generates a surface from grease pencil strokes or from curves"
 
163
    bl_description = "Generates surfaces from grease pencil strokes, bezier curves or loose edges."
69
164
    bl_options = {'REGISTER', 'UNDO'}
70
165
    
71
166
    
72
 
    ##### Get an ordered list of a chain of vertices.
73
 
    def get_ordered_verts(self, ob, all_selected_edges_idx, all_selected_verts_idx, first_vert_idx, middle_vertex_idx):
74
 
        # Order selected vertexes.
 
167
    edges_U = bpy.props.IntProperty(name = "Cross",
 
168
                        description = "Number of face-loops crossing the strokes.",
 
169
                        default = 1,
 
170
                        min = 1,
 
171
                        max = 200)
 
172
                        
 
173
    edges_V = bpy.props.IntProperty(name = "Follow",
 
174
                        description = "Number of face-loops following the strokes.",
 
175
                        default = 1,
 
176
                        min = 1,
 
177
                        max = 200)
 
178
    
 
179
    cyclic_cross = bpy.props.BoolProperty(name = "Cyclic Cross",
 
180
                        description = "Make cyclic the face-loops crossing the strokes.",
 
181
                        default = False)
 
182
                        
 
183
    cyclic_follow = bpy.props.BoolProperty(name = "Cyclic Follow",
 
184
                        description = "Make cyclic the face-loops following the strokes.",
 
185
                        default = False)
 
186
                        
 
187
    loops_on_strokes = bpy.props.BoolProperty(name = "Loops on strokes",
 
188
                        description = "Make the loops match the paths of the strokes.",
 
189
                        default = False)
 
190
    
 
191
    automatic_join = bpy.props.BoolProperty(name = "Automatic join",
 
192
                        description = "Join automatically vertices of either surfaces generated by crosshatching, or from the borders of closed shapes.",
 
193
                        default = False)
 
194
                        
 
195
    join_stretch_factor = bpy.props.FloatProperty(name = "Stretch",
 
196
                        description = "Amount of stretching or shrinking allowed for edges when joining vertices automatically.",
 
197
                        default = 1,
 
198
                        min = 0,
 
199
                        max = 3,
 
200
                        subtype = 'FACTOR')
 
201
    
 
202
    
 
203
    
 
204
    
 
205
    def draw(self, context):
 
206
        layout = self.layout
 
207
        
 
208
        scn = context.scene
 
209
        ob = context.object
 
210
        
 
211
        col = layout.column(align=True)
 
212
        row = layout.row()
 
213
        
 
214
        if not self.is_fill_faces:
 
215
            row.separator()
 
216
            if not self.is_crosshatch:
 
217
                if not self.selection_U_exists:
 
218
                    col.prop(self, "edges_U")
 
219
                    row.separator()
 
220
                    
 
221
                if not self.selection_V_exists:
 
222
                    col.prop(self, "edges_V")
 
223
                    row.separator()
 
224
                
 
225
                row.separator()
 
226
                
 
227
                if not self.selection_U_exists:
 
228
                    if not ((self.selection_V_exists and not self.selection_V_is_closed) or (self.selection_V2_exists and not self.selection_V2_is_closed)):
 
229
                        col.prop(self, "cyclic_cross")
 
230
                    
 
231
                if not self.selection_V_exists:
 
232
                    if not ((self.selection_U_exists and not self.selection_U_is_closed) or (self.selection_U2_exists and not self.selection_U2_is_closed)):
 
233
                        col.prop(self, "cyclic_follow")
 
234
                
 
235
                
 
236
                col.prop(self, "loops_on_strokes")
 
237
            
 
238
            col.prop(self, "automatic_join")    
 
239
                
 
240
            if self.automatic_join:
 
241
                row.separator()
 
242
                col.separator()
 
243
                row.separator()
 
244
                col.prop(self, "join_stretch_factor")
 
245
            
 
246
    
 
247
    
 
248
    #### Get an ordered list of a chain of vertices.
 
249
    def get_ordered_verts(self, ob, all_selected_edges_idx, all_selected_verts_idx, first_vert_idx, middle_vertex_idx, closing_vert_idx):
 
250
        # Order selected vertices.
75
251
        verts_ordered = []
76
 
        verts_ordered.append(self.main_object.data.vertices[first_vert_idx])
 
252
        if closing_vert_idx != None:
 
253
            verts_ordered.append(ob.data.vertices[closing_vert_idx])
 
254
            
 
255
        verts_ordered.append(ob.data.vertices[first_vert_idx])
77
256
        prev_v = first_vert_idx
78
257
        prev_ed = None
79
258
        finish_while = False
81
260
            edges_non_matched = 0
82
261
            for i in all_selected_edges_idx:
83
262
                if ob.data.edges[i] != prev_ed and ob.data.edges[i].vertices[0] == prev_v and ob.data.edges[i].vertices[1] in all_selected_verts_idx:
84
 
                    verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[1]])
 
263
                    verts_ordered.append(ob.data.vertices[ob.data.edges[i].vertices[1]])
85
264
                    prev_v = ob.data.edges[i].vertices[1]
86
265
                    prev_ed = ob.data.edges[i]
87
266
                elif ob.data.edges[i] != prev_ed and ob.data.edges[i].vertices[1] == prev_v and ob.data.edges[i].vertices[0] in all_selected_verts_idx:
88
 
                    verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[0]])
 
267
                    verts_ordered.append(ob.data.vertices[ob.data.edges[i].vertices[0]])
89
268
                    prev_v = ob.data.edges[i].vertices[0]
90
269
                    prev_ed = ob.data.edges[i]
91
270
                else:
97
276
            if finish_while:
98
277
                break
99
278
        
 
279
        if closing_vert_idx != None:
 
280
            verts_ordered.append(ob.data.vertices[closing_vert_idx])
 
281
        
100
282
        if middle_vertex_idx != None:
101
 
            verts_ordered.append(self.main_object.data.vertices[middle_vertex_idx])
 
283
            verts_ordered.append(ob.data.vertices[middle_vertex_idx])
102
284
            verts_ordered.reverse()
103
285
        
104
 
        return verts_ordered
 
286
        return tuple(verts_ordered)
105
287
    
106
288
    
107
289
    #### Calculates length of a chain of points.
108
290
    def get_chain_length(self, object, verts_ordered):
109
 
        matrix = object.matrix_world.copy()
 
291
        matrix = object.matrix_world
110
292
        
111
293
        edges_lengths = []
112
294
        edges_lengths_sum = 0
123
305
                edges_lengths_sum += edge_length
124
306
                
125
307
                prev_v_co = v_co
126
 
                
127
 
                
 
308
        
128
309
        return edges_lengths, edges_lengths_sum
129
310
    
130
311
    
156
337
            angle = abs(angle - math.pi)
157
338
        
158
339
        return angle
159
 
        
160
 
    
161
 
    #### Calculate distance between two points
162
 
    def pts_distance(self, p1_co, p2_co):
163
 
        p_difs = [p1_co[0] - p2_co[0], p1_co[1] - p2_co[1], p1_co[2] - p2_co[2]]
164
 
        distance = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
165
 
        
166
 
        return distance
167
 
        
168
 
    
169
 
    def execute(self, context):
 
340
    
 
341
    
 
342
        
 
343
    #### Calculate the which vert of verts_idx list is the nearest one to the point_co coordinates, and the distance.
 
344
    def shortest_distance(self, object, point_co, verts_idx):
 
345
        matrix = object.matrix_world
 
346
        
 
347
        for i in range(0, len(verts_idx)):
 
348
            dist = (point_co - matrix * object.data.vertices[verts_idx[i]].co).length
 
349
            if i == 0:
 
350
                prev_dist = dist
 
351
                nearest_vert_idx = verts_idx[i]
 
352
                shortest_dist = dist
 
353
                
 
354
            if dist < prev_dist:
 
355
                prev_dist = dist
 
356
                nearest_vert_idx = verts_idx[i]
 
357
                shortest_dist = dist
 
358
                    
 
359
        return nearest_vert_idx, shortest_dist
 
360
        
 
361
        
 
362
    #### Returns the index of the opposite vert tip in a chain, given a vert tip index as parameter, and a multidimentional list with all pairs of tips.
 
363
    def opposite_tip(self, vert_tip_idx, all_chains_tips_idx):
 
364
        opposite_vert_tip_idx = None
 
365
        for i in range(0, len(all_chains_tips_idx)):
 
366
            if vert_tip_idx == all_chains_tips_idx[i][0]:
 
367
                opposite_vert_tip_idx = all_chains_tips_idx[i][1]
 
368
            if vert_tip_idx == all_chains_tips_idx[i][1]:
 
369
                opposite_vert_tip_idx = all_chains_tips_idx[i][0]
 
370
        
 
371
        return opposite_vert_tip_idx
 
372
        
 
373
        
 
374
        
 
375
    #### Simplifies a spline and returns the new points coordinates.
 
376
    def simplify_spline(self, spline_coords, segments_num):
 
377
        simplified_spline = []
 
378
        points_between_segments = round(len(spline_coords) / segments_num)
 
379
        
 
380
        simplified_spline.append(spline_coords[0])
 
381
        for i in range(1, segments_num):
 
382
            simplified_spline.append(spline_coords[i * points_between_segments])
 
383
                    
 
384
        simplified_spline.append(spline_coords[len(spline_coords) - 1])
 
385
        
 
386
        return simplified_spline
 
387
    
 
388
    
 
389
    
 
390
    #### Cleans up the scene and gets it the same it was at the beginning, in case the script is interrupted in the middle of the execution.
 
391
    def cleanup_on_interruption(self):
 
392
        # If the original strokes curve comes from conversion from grease pencil and wasn't made by hand, delete it.
 
393
        if not self.using_external_curves:
 
394
            try:
 
395
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
396
                bpy.data.objects[self.original_curve.name].select = True
 
397
                bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
 
398
                
 
399
                bpy.ops.object.delete()
 
400
            except:
 
401
                pass
 
402
            
 
403
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
404
            bpy.data.objects[self.main_object.name].select = True
 
405
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
406
        else:
 
407
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
408
            bpy.data.objects[self.original_curve.name].select = True
 
409
            bpy.data.objects[self.main_object.name].select = True
 
410
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
411
        
 
412
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
413
    
 
414
    
 
415
    
 
416
    #### Returns a list with the coords of the points distributed over the splines passed to this method according to the proportions parameter.
 
417
    def distribute_pts(self, surface_splines, proportions):
 
418
        # Calculate the length of each final surface spline.
 
419
        surface_splines_lengths = []
 
420
        surface_splines_parsed = []
 
421
        for sp_idx in range(0, len(surface_splines)):
 
422
            # Calculate spline length
 
423
            surface_splines_lengths.append(0)
 
424
            for i in range(0, len(surface_splines[sp_idx].bezier_points)):
 
425
                if i == 0:
 
426
                    prev_p = surface_splines[sp_idx].bezier_points[i]
 
427
                else:
 
428
                    p = surface_splines[sp_idx].bezier_points[i]
 
429
                    
 
430
                    edge_length = (prev_p.co - p.co).length
 
431
                    
 
432
                    surface_splines_lengths[sp_idx] += edge_length
 
433
                    
 
434
                    prev_p = p
 
435
        
 
436
        
 
437
        # Calculate vertex positions with appropriate edge proportions, and ordered, for each spline.
 
438
        for sp_idx in range(0, len(surface_splines)):
 
439
            surface_splines_parsed.append([])
 
440
            surface_splines_parsed[sp_idx].append(surface_splines[sp_idx].bezier_points[0].co)
 
441
            
 
442
            prev_p_co = surface_splines[sp_idx].bezier_points[0].co
 
443
            p_idx = 0
 
444
            for prop_idx in range(len(proportions) - 1):
 
445
                target_length = surface_splines_lengths[sp_idx] * proportions[prop_idx]
 
446
                
 
447
                partial_segment_length = 0
 
448
                
 
449
                
 
450
                finish_while = False
 
451
                while True:
 
452
                    p_co = surface_splines[sp_idx].bezier_points[p_idx].co
 
453
                    
 
454
                    new_dist = (prev_p_co - p_co).length
 
455
                    
 
456
                    potential_segment_length = partial_segment_length + new_dist # The new distance that could have the partial segment if it is still shorter than the target length.
 
457
                    
 
458
                    
 
459
                    if potential_segment_length < target_length: # If the potential is still shorter, keep adding.
 
460
                        partial_segment_length = potential_segment_length
 
461
                        
 
462
                        p_idx += 1
 
463
                        prev_p_co = p_co
 
464
                        
 
465
                    elif potential_segment_length > target_length: # If the potential is longer than the target, calculate the target (a point between the last two points), and assign.
 
466
                        remaining_dist = target_length - partial_segment_length
 
467
                        vec = p_co - prev_p_co
 
468
                        vec.normalize()
 
469
                        intermediate_co = prev_p_co + (vec * remaining_dist)
 
470
                        
 
471
                        surface_splines_parsed[sp_idx].append(intermediate_co)
 
472
                        
 
473
                        partial_segment_length += remaining_dist
 
474
                        prev_p_co = intermediate_co
 
475
                        
 
476
                        finish_while = True
 
477
                        
 
478
                    elif potential_segment_length == target_length: # If the potential is equal to the target, assign.
 
479
                        surface_splines_parsed[sp_idx].append(p_co)
 
480
                        
 
481
                        prev_p_co = p_co
 
482
                        
 
483
                        finish_while = True
 
484
                        
 
485
                    if finish_while:
 
486
                        break
 
487
            
 
488
            # last point of the spline
 
489
            surface_splines_parsed[sp_idx].append(surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1].co)
 
490
        
 
491
        
 
492
        return surface_splines_parsed
 
493
    
 
494
    
 
495
    
 
496
    #### Counts the number of faces that belong to each edge.
 
497
    def edge_face_count(self, ob):
 
498
        ed_keys_count_dict = {}
 
499
        
 
500
        for face in ob.data.polygons:
 
501
            for ed_keys in face.edge_keys:
 
502
                if not ed_keys in ed_keys_count_dict:
 
503
                    ed_keys_count_dict[ed_keys] = 1
 
504
                else:
 
505
                    ed_keys_count_dict[ed_keys] += 1
 
506
        
 
507
        
 
508
        edge_face_count = []
 
509
        for i in range(len(ob.data.edges)):
 
510
            edge_face_count.append(0)
 
511
        
 
512
        for i in range(len(ob.data.edges)):
 
513
            ed = ob.data.edges[i]
 
514
            
 
515
            v1 = ed.vertices[0]
 
516
            v2 = ed.vertices[1]
 
517
            
 
518
            if (v1, v2) in ed_keys_count_dict:
 
519
                edge_face_count[i] = ed_keys_count_dict[(v1, v2)]
 
520
            elif (v2, v1) in ed_keys_count_dict:
 
521
                edge_face_count[i] = ed_keys_count_dict[(v2, v1)]
 
522
        
 
523
        
 
524
        return edge_face_count
 
525
    
 
526
    
 
527
    
 
528
    #### Fills with faces all the selected vertices which form empty triangles or quads.
 
529
    def fill_with_faces(self, object):
 
530
        all_selected_verts_count = self.main_object_selected_verts_count
 
531
        
 
532
        
 
533
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
 
534
        
 
535
        #### Calculate average length of selected edges.
 
536
        all_selected_verts = []
 
537
        original_sel_edges_count = 0
 
538
        for ed in object.data.edges:
 
539
            if object.data.vertices[ed.vertices[0]].select and object.data.vertices[ed.vertices[1]].select:
 
540
                coords = []
 
541
                coords.append(object.data.vertices[ed.vertices[0]].co)
 
542
                coords.append(object.data.vertices[ed.vertices[1]].co)
 
543
                
 
544
                original_sel_edges_count += 1
 
545
                
 
546
                if not ed.vertices[0] in all_selected_verts:
 
547
                    all_selected_verts.append(ed.vertices[0])
 
548
                    
 
549
                if not ed.vertices[1] in all_selected_verts:
 
550
                    all_selected_verts.append(ed.vertices[1])
 
551
                
 
552
        
 
553
        tuple(all_selected_verts)
 
554
        
 
555
        
 
556
        #### Check if there is any edge selected. If not, interrupt the script.
 
557
        if original_sel_edges_count == 0 and all_selected_verts_count > 0:
 
558
            return 0
 
559
        
 
560
        
 
561
        
 
562
        #### Get all edges connected to selected verts.
 
563
        all_edges_around_sel_verts = []
 
564
        edges_connected_to_sel_verts = {}
 
565
        verts_connected_to_every_vert = {}
 
566
        for ed_idx in range(len(object.data.edges)):
 
567
            ed = object.data.edges[ed_idx]
 
568
            include_edge = False
 
569
            
 
570
            if ed.vertices[0] in all_selected_verts:
 
571
                if not ed.vertices[0] in edges_connected_to_sel_verts:
 
572
                    edges_connected_to_sel_verts[ed.vertices[0]] = []
 
573
                
 
574
                edges_connected_to_sel_verts[ed.vertices[0]].append(ed_idx)
 
575
                include_edge = True
 
576
            
 
577
            if ed.vertices[1] in all_selected_verts:
 
578
                if not ed.vertices[1] in edges_connected_to_sel_verts:
 
579
                    edges_connected_to_sel_verts[ed.vertices[1]] = []
 
580
                
 
581
                edges_connected_to_sel_verts[ed.vertices[1]].append(ed_idx)
 
582
                include_edge = True
 
583
            
 
584
            
 
585
            if include_edge == True:
 
586
                all_edges_around_sel_verts.append(ed_idx)
 
587
            
 
588
            
 
589
            # Get all connected verts to each vert.
 
590
            if not ed.vertices[0] in verts_connected_to_every_vert:
 
591
                verts_connected_to_every_vert[ed.vertices[0]] = []
 
592
            
 
593
            if not ed.vertices[1] in verts_connected_to_every_vert:
 
594
                verts_connected_to_every_vert[ed.vertices[1]] = []
 
595
                
 
596
            verts_connected_to_every_vert[ed.vertices[0]].append(ed.vertices[1])
 
597
            verts_connected_to_every_vert[ed.vertices[1]].append(ed.vertices[0])
 
598
            
 
599
        
 
600
        
 
601
        
 
602
        #### Get all verts connected to faces.
 
603
        all_verts_part_of_faces = []
 
604
        all_edges_faces_count = []
 
605
        all_edges_faces_count += self.edge_face_count(object)
 
606
        
 
607
        # Get only the selected edges that have faces attached.
 
608
        count_faces_of_edges_around_sel_verts = {}
 
609
        selected_verts_with_faces = []
 
610
        for ed_idx in all_edges_around_sel_verts:
 
611
            count_faces_of_edges_around_sel_verts[ed_idx] = all_edges_faces_count[ed_idx]
 
612
            
 
613
            if all_edges_faces_count[ed_idx] > 0:
 
614
                ed = object.data.edges[ed_idx]
 
615
                
 
616
                if not ed.vertices[0] in selected_verts_with_faces:
 
617
                    selected_verts_with_faces.append(ed.vertices[0])
 
618
                
 
619
                if not ed.vertices[1] in selected_verts_with_faces:
 
620
                    selected_verts_with_faces.append(ed.vertices[1])
 
621
        
 
622
                all_verts_part_of_faces.append(ed.vertices[0])
 
623
                all_verts_part_of_faces.append(ed.vertices[1])
 
624
        
 
625
        tuple(selected_verts_with_faces)
 
626
        
 
627
        
 
628
        
 
629
        #### Discard unneeded verts from calculations.
 
630
        participating_verts = []
 
631
        movable_verts = []
 
632
        for v_idx in all_selected_verts:
 
633
            vert_has_edges_with_one_face = False
 
634
            
 
635
            for ed_idx in edges_connected_to_sel_verts[v_idx]: # Check if the actual vert has at least one edge connected to only one face.
 
636
                if count_faces_of_edges_around_sel_verts[ed_idx] == 1:
 
637
                    vert_has_edges_with_one_face = True
 
638
            
 
639
            # If the vert has two or less edges connected and the vert is not part of any face. Or the vert is part of any face and at least one of the connected edges has only one face attached to it.
 
640
            if (len(edges_connected_to_sel_verts[v_idx]) == 2 and not v_idx in all_verts_part_of_faces) or len(edges_connected_to_sel_verts[v_idx]) == 1 or (v_idx in all_verts_part_of_faces and vert_has_edges_with_one_face):
 
641
                participating_verts.append(v_idx)
 
642
                
 
643
                if not v_idx in all_verts_part_of_faces:
 
644
                    movable_verts.append(v_idx)
 
645
        
 
646
        
 
647
        
 
648
        #### Remove from movable verts list those that are part of closed geometry (ie: triangles, quads)
 
649
        for mv_idx in movable_verts:
 
650
            freeze_vert = False
 
651
            mv_connected_verts = verts_connected_to_every_vert[mv_idx]
 
652
            
 
653
            for actual_v_idx in all_selected_verts:
 
654
                count_shared_neighbors = 0
 
655
                checked_verts = []
 
656
                
 
657
                for mv_conn_v_idx in mv_connected_verts:
 
658
                    if mv_idx != actual_v_idx:
 
659
                        if mv_conn_v_idx in verts_connected_to_every_vert[actual_v_idx] and not mv_conn_v_idx in checked_verts:
 
660
                            count_shared_neighbors += 1
 
661
                            checked_verts.append(mv_conn_v_idx)
 
662
                            
 
663
                            
 
664
                            if actual_v_idx in mv_connected_verts:
 
665
                                freeze_vert = True
 
666
                                break
 
667
                            
 
668
                        if count_shared_neighbors == 2:
 
669
                            freeze_vert = True
 
670
                            break
 
671
                
 
672
                if freeze_vert:
 
673
                    break
 
674
            
 
675
            if freeze_vert:
 
676
                movable_verts.remove(mv_idx)
 
677
                        
 
678
        
 
679
        
 
680
        #### Calculate merge distance for participating verts.
 
681
        shortest_edge_length = None
 
682
        for ed in object.data.edges:
 
683
            if ed.vertices[0] in movable_verts and ed.vertices[1] in movable_verts:
 
684
                v1 = object.data.vertices[ed.vertices[0]]
 
685
                v2 = object.data.vertices[ed.vertices[1]]
 
686
                
 
687
                length = (v1.co - v2.co).length
 
688
                
 
689
                if shortest_edge_length == None:
 
690
                    shortest_edge_length = length
 
691
                else:
 
692
                    if length < shortest_edge_length:
 
693
                        shortest_edge_length = length
 
694
            
 
695
        if shortest_edge_length != None:
 
696
            edges_merge_distance = shortest_edge_length * 0.5
 
697
        else:
 
698
            edges_merge_distance = 0
 
699
        
 
700
        
 
701
        
 
702
        
 
703
        #### Get together the verts near enough. They will be merged later.
 
704
        remaining_verts = []
 
705
        remaining_verts += participating_verts
 
706
        for v1_idx in participating_verts:
 
707
            if v1_idx in remaining_verts and v1_idx in movable_verts:
 
708
                verts_to_merge = []
 
709
                coords_verts_to_merge = {}
 
710
                
 
711
                verts_to_merge.append(v1_idx)
 
712
                
 
713
                v1_co = object.data.vertices[v1_idx].co
 
714
                coords_verts_to_merge[v1_idx] = (v1_co[0], v1_co[1], v1_co[2])
 
715
                
 
716
                
 
717
                for v2_idx in remaining_verts:
 
718
                    if v1_idx != v2_idx:
 
719
                        v2_co = object.data.vertices[v2_idx].co
 
720
                        
 
721
                        dist = (v1_co - v2_co).length
 
722
                        
 
723
                        if dist <= edges_merge_distance: # Add the verts which are near enough.
 
724
                            verts_to_merge.append(v2_idx)
 
725
                            
 
726
                            coords_verts_to_merge[v2_idx] = (v2_co[0], v2_co[1], v2_co[2])
 
727
                            
 
728
                
 
729
                for vm_idx in verts_to_merge:
 
730
                    remaining_verts.remove(vm_idx)
 
731
                
 
732
                
 
733
                if len(verts_to_merge) > 1:
 
734
                    # Calculate middle point of the verts to merge.
 
735
                    sum_x_co = 0
 
736
                    sum_y_co = 0
 
737
                    sum_z_co = 0
 
738
                    movable_verts_to_merge_count = 0
 
739
                    for i in range(len(verts_to_merge)):
 
740
                        if verts_to_merge[i] in movable_verts:
 
741
                            v_co = object.data.vertices[verts_to_merge[i]].co
 
742
                            
 
743
                            sum_x_co += v_co[0]
 
744
                            sum_y_co += v_co[1]
 
745
                            sum_z_co += v_co[2]
 
746
                            
 
747
                            movable_verts_to_merge_count += 1
 
748
                    
 
749
                    middle_point_co = [sum_x_co / movable_verts_to_merge_count, sum_y_co / movable_verts_to_merge_count, sum_z_co / movable_verts_to_merge_count]
 
750
                    
 
751
                    
 
752
                    # Check if any vert to be merged is not movable.
 
753
                    shortest_dist = None
 
754
                    are_verts_not_movable = False
 
755
                    verts_not_movable = []
 
756
                    for v_merge_idx in verts_to_merge:
 
757
                        if v_merge_idx in participating_verts and not v_merge_idx in movable_verts:
 
758
                            are_verts_not_movable = True
 
759
                            verts_not_movable.append(v_merge_idx)
 
760
                    
 
761
                    if are_verts_not_movable:
 
762
                        # Get the vert connected to faces, that is nearest to the middle point of the movable verts.
 
763
                        shortest_dist = None
 
764
                        for vcf_idx in verts_not_movable:
 
765
                                dist = abs((object.data.vertices[vcf_idx].co - mathutils.Vector(middle_point_co)).length)
 
766
                                
 
767
                                if shortest_dist == None:
 
768
                                    shortest_dist = dist
 
769
                                    nearest_vert_idx = vcf_idx
 
770
                                else:
 
771
                                    if dist < shortest_dist:
 
772
                                        shortest_dist = dist
 
773
                                        nearest_vert_idx = vcf_idx
 
774
                                    
 
775
                        coords = object.data.vertices[nearest_vert_idx].co
 
776
                        target_point_co = [coords[0], coords[1], coords[2]]                                    
 
777
                    else:
 
778
                         target_point_co = middle_point_co
 
779
                       
 
780
                    
 
781
                    # Move verts to merge to the middle position.
 
782
                    for v_merge_idx in verts_to_merge:
 
783
                        if v_merge_idx in movable_verts: # Only move the verts that are not part of faces.
 
784
                            object.data.vertices[v_merge_idx].co[0] = target_point_co[0]
 
785
                            object.data.vertices[v_merge_idx].co[1] = target_point_co[1]
 
786
                            object.data.vertices[v_merge_idx].co[2] = target_point_co[2]
 
787
                
 
788
                
 
789
        
 
790
        #### Perform "Remove Doubles" to weld all the disconnected verts
 
791
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
 
792
        bpy.ops.mesh.remove_doubles(threshold=0.0001)
 
793
        
 
794
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
 
795
        
 
796
        
 
797
        #### Get all the definitive selected edges, after weldding.
 
798
        selected_edges = []
 
799
        edges_per_vert = {} # Number of faces of each selected edge.
 
800
        for ed in object.data.edges:
 
801
            if object.data.vertices[ed.vertices[0]].select and object.data.vertices[ed.vertices[1]].select:
 
802
                selected_edges.append(ed.index)
 
803
                
 
804
                # Save all the edges that belong to each vertex.
 
805
                if not ed.vertices[0] in edges_per_vert:
 
806
                    edges_per_vert[ed.vertices[0]] = []
 
807
                
 
808
                if not ed.vertices[1] in edges_per_vert:
 
809
                    edges_per_vert[ed.vertices[1]] = []
 
810
                
 
811
                edges_per_vert[ed.vertices[0]].append(ed.index)
 
812
                edges_per_vert[ed.vertices[1]].append(ed.index)
 
813
        
 
814
        # Check if all the edges connected to each vert have two faces attached to them. To discard them later and make calculations faster.
 
815
        a = []
 
816
        a += self.edge_face_count(object)
 
817
        tuple(a)
 
818
        verts_surrounded_by_faces = {}
 
819
        for v_idx in edges_per_vert:
 
820
            edges = edges_per_vert[v_idx]
 
821
            
 
822
            edges_with_two_faces_count = 0
 
823
            for ed_idx in edges_per_vert[v_idx]:
 
824
                if a[ed_idx] == 2:
 
825
                    edges_with_two_faces_count += 1
 
826
            
 
827
            if edges_with_two_faces_count == len(edges_per_vert[v_idx]):
 
828
                verts_surrounded_by_faces[v_idx] = True
 
829
            else:
 
830
                verts_surrounded_by_faces[v_idx] = False
 
831
                
 
832
                
 
833
        #### Get all the selected vertices.
 
834
        selected_verts_idx = []
 
835
        for v in object.data.vertices:
 
836
            if v.select:
 
837
                selected_verts_idx.append(v.index)
 
838
        
 
839
        
 
840
        #### Get all the faces of the object.
 
841
        all_object_faces_verts_idx = []
 
842
        for face in object.data.polygons:
 
843
            face_verts = []
 
844
            face_verts.append(face.vertices[0])
 
845
            face_verts.append(face.vertices[1])
 
846
            face_verts.append(face.vertices[2])
 
847
            
 
848
            if len(face.vertices) == 4:
 
849
                face_verts.append(face.vertices[3])
 
850
                
 
851
            all_object_faces_verts_idx.append(face_verts)
 
852
                        
 
853
            
 
854
        #### Deselect all vertices.
 
855
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
 
856
        bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
857
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
 
858
        
 
859
        
 
860
        
 
861
        #### Make a dictionary with the verts related to each vert.
 
862
        related_key_verts = {}
 
863
        for ed_idx in selected_edges:
 
864
            ed = object.data.edges[ed_idx]
 
865
            
 
866
            if not verts_surrounded_by_faces[ed.vertices[0]]:
 
867
                if not ed.vertices[0] in related_key_verts:
 
868
                    related_key_verts[ed.vertices[0]] = []
 
869
                
 
870
                if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
 
871
                    related_key_verts[ed.vertices[0]].append(ed.vertices[1])
 
872
            
 
873
            if not verts_surrounded_by_faces[ed.vertices[1]]:
 
874
                if not ed.vertices[1] in related_key_verts:
 
875
                    related_key_verts[ed.vertices[1]] = []
 
876
                
 
877
                if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
 
878
                    related_key_verts[ed.vertices[1]].append(ed.vertices[0])
 
879
        
 
880
        
 
881
            
 
882
        #### Get groups of verts forming each face.
 
883
        faces_verts_idx = [] 
 
884
        for v1 in related_key_verts: # verts-1 .... 
 
885
            for v2 in related_key_verts: # verts-2
 
886
                if v1 != v2:
 
887
                    related_verts_in_common = []
 
888
                    v2_in_rel_v1 = False
 
889
                    v1_in_rel_v2 = False
 
890
                    for rel_v1 in related_key_verts[v1]:
 
891
                        if rel_v1 in related_key_verts[v2]: # Check if related verts of verts-1 are related verts of verts-2.
 
892
                            related_verts_in_common.append(rel_v1)
 
893
                        
 
894
                    if v2 in related_key_verts[v1]:
 
895
                        v2_in_rel_v1 = True
 
896
                            
 
897
                    if v1 in related_key_verts[v2]:
 
898
                        v1_in_rel_v2 = True
 
899
                    
 
900
                    
 
901
                    repeated_face = False
 
902
                    # If two verts have two related verts in common, they form a quad.
 
903
                    if len(related_verts_in_common) == 2:
 
904
                        # Check if the face is already saved.
 
905
                        all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
 
906
                        
 
907
                        
 
908
                        for f_verts in all_faces_to_check_idx:
 
909
                            repeated_verts = 0
 
910
                            
 
911
                            if len(f_verts) == 4:
 
912
                                if v1 in f_verts: repeated_verts += 1
 
913
                                if v2 in f_verts: repeated_verts += 1
 
914
                                if related_verts_in_common[0] in f_verts: repeated_verts += 1
 
915
                                if related_verts_in_common[1] in f_verts: repeated_verts += 1
 
916
                                
 
917
                                if repeated_verts == len(f_verts):
 
918
                                    repeated_face = True
 
919
                                    break
 
920
                        
 
921
                        if not repeated_face:
 
922
                            faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
 
923
                        
 
924
                    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.
 
925
                        # Check if the face is already saved.
 
926
                        all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
 
927
                        
 
928
                        for f_verts in all_faces_to_check_idx:
 
929
                            repeated_verts = 0
 
930
                            
 
931
                            if len(f_verts) == 3:
 
932
                                if v1 in f_verts: repeated_verts += 1
 
933
                                if v2 in f_verts: repeated_verts += 1
 
934
                                if related_verts_in_common[0] in f_verts: repeated_verts += 1
 
935
                                
 
936
                                if repeated_verts == len(f_verts):
 
937
                                    repeated_face = True
 
938
                                    break
 
939
                        
 
940
                        if not repeated_face:
 
941
                            faces_verts_idx.append([v1, related_verts_in_common[0], v2])
 
942
                
 
943
            
 
944
        #### Keep only the faces that don't overlap by ignoring quads that overlap with two adjacent triangles.
 
945
        faces_to_not_include_idx = [] # Indices of faces_verts_idx to eliminate.
 
946
        all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
 
947
        for i in range(len(faces_verts_idx)):
 
948
            for t in range(len(all_faces_to_check_idx)):
 
949
                if i != t:
 
950
                    verts_in_common = 0
 
951
                    
 
952
                    if len(faces_verts_idx[i]) == 4 and len(all_faces_to_check_idx[t]) == 3:
 
953
                        for v_idx in all_faces_to_check_idx[t]:
 
954
                            if v_idx in faces_verts_idx[i]:
 
955
                                verts_in_common += 1
 
956
                                
 
957
                        if verts_in_common == 3: # If it doesn't have all it's vertices repeated in the other face.
 
958
                            if not i in faces_to_not_include_idx:
 
959
                                faces_to_not_include_idx.append(i)
 
960
        
 
961
        
 
962
        #### Build faces discarding the ones in faces_to_not_include.
 
963
        me = object.data
 
964
        bm = bmesh.new()
 
965
        bm.from_mesh(me)
 
966
        
 
967
        num_faces_created = 0
 
968
        for i in range(len(faces_verts_idx)):
 
969
            if not i in faces_to_not_include_idx:
 
970
                bm.faces.new([ bm.verts[v] for v in faces_verts_idx[i] ])
 
971
                
 
972
                num_faces_created += 1
 
973
        
 
974
        bm.to_mesh(me)
 
975
        bm.free()
 
976
        
 
977
        
 
978
        
 
979
        for v_idx in selected_verts_idx:
 
980
            self.main_object.data.vertices[v_idx].select = True
 
981
        
 
982
        
 
983
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
 
984
        bpy.ops.mesh.normals_make_consistent(inside=False)
 
985
        bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
 
986
        
 
987
            
 
988
        return num_faces_created
 
989
        
 
990
        
 
991
    
 
992
    #### Crosshatch skinning.
 
993
    def crosshatch_surface_invoke(self, ob_original_splines):
 
994
        self.is_crosshatch = False
 
995
        self.crosshatch_merge_distance = 0
 
996
        
 
997
        
 
998
        objects_to_delete = [] # duplicated strokes to be deleted.
 
999
        
 
1000
        # 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)
 
1001
        self.modifiers_prev_viewport_state = []
 
1002
        if len(self.main_object.modifiers) > 0:
 
1003
            for m_idx in range(len(self.main_object.modifiers)):
 
1004
                self.modifiers_prev_viewport_state.append(self.main_object.modifiers[m_idx].show_viewport)
 
1005
                
 
1006
                self.main_object.modifiers[m_idx].show_viewport = False
 
1007
        
 
1008
        
 
1009
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1010
        bpy.data.objects[ob_original_splines.name].select = True
 
1011
        bpy.context.scene.objects.active = bpy.data.objects[ob_original_splines.name]
 
1012
        
 
1013
        
 
1014
        if len(ob_original_splines.data.splines) >= 2:
 
1015
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
1016
            ob_splines = bpy.context.object
 
1017
            ob_splines.name = "SURFSKIO_NE_STR"
 
1018
            
 
1019
            
 
1020
            #### Get estimative merge distance (sum up the distances from the first point to all other points, then average them and then divide them).
 
1021
            first_point_dist_sum = 0
 
1022
            first_dist = 0
 
1023
            second_dist = 0
 
1024
            coords_first_pt = ob_splines.data.splines[0].bezier_points[0].co
 
1025
            for i in range(len(ob_splines.data.splines)):
 
1026
                sp = ob_splines.data.splines[i]
 
1027
                
 
1028
                if coords_first_pt != sp.bezier_points[0].co:
 
1029
                    first_dist = (coords_first_pt - sp.bezier_points[0].co).length
 
1030
                    
 
1031
                if coords_first_pt != sp.bezier_points[len(sp.bezier_points) - 1].co:
 
1032
                    second_dist = (coords_first_pt - sp.bezier_points[len(sp.bezier_points) - 1].co).length
 
1033
                
 
1034
                first_point_dist_sum += first_dist + second_dist
 
1035
                
 
1036
                
 
1037
                if i == 0:
 
1038
                    if first_dist != 0:
 
1039
                        shortest_dist = first_dist
 
1040
                    elif second_dist != 0:
 
1041
                        shortest_dist = second_dist
 
1042
                        
 
1043
                    
 
1044
                if shortest_dist > first_dist and first_dist != 0:
 
1045
                    shortest_dist = first_dist
 
1046
                
 
1047
                if shortest_dist > second_dist and second_dist != 0:
 
1048
                    shortest_dist = second_dist
 
1049
                
 
1050
            
 
1051
            self.crosshatch_merge_distance = shortest_dist / 20
 
1052
            
 
1053
            
 
1054
            
 
1055
            #### Recalculation of merge distance.
 
1056
            
 
1057
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
1058
            
 
1059
            ob_calc_merge_dist = bpy.context.object
 
1060
            ob_calc_merge_dist.name = "SURFSKIO_CALC_TMP"
 
1061
            
 
1062
            objects_to_delete.append(ob_calc_merge_dist)
 
1063
            
 
1064
            
 
1065
            
 
1066
            #### Smooth out strokes a little to improve crosshatch detection.
 
1067
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1068
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
 
1069
            
 
1070
            for i in range(4):
 
1071
                bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1072
            
 
1073
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1074
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1075
            
 
1076
            
 
1077
            
 
1078
            #### Convert curves into mesh.
 
1079
            ob_calc_merge_dist.data.resolution_u = 12
 
1080
            bpy.ops.object.convert(target='MESH', keep_original=False)
 
1081
            
 
1082
            # Find "intersection-nodes".
 
1083
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1084
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
 
1085
            bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=self.crosshatch_merge_distance)
 
1086
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1087
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1088
            
 
1089
            # Remove verts with less than three edges.
 
1090
            verts_edges_count = {}
 
1091
            for ed in ob_calc_merge_dist.data.edges:
 
1092
                v = ed.vertices
 
1093
                
 
1094
                if v[0] not in verts_edges_count:
 
1095
                    verts_edges_count[v[0]] = 0
 
1096
                
 
1097
                if v[1] not in verts_edges_count:
 
1098
                    verts_edges_count[v[1]] = 0
 
1099
                    
 
1100
                verts_edges_count[v[0]] += 1
 
1101
                verts_edges_count[v[1]] += 1
 
1102
            
 
1103
            nodes_verts_coords = []
 
1104
            for v_idx in verts_edges_count:
 
1105
                v = ob_calc_merge_dist.data.vertices[v_idx]
 
1106
                
 
1107
                if verts_edges_count[v_idx] < 3:
 
1108
                    v.select = True
 
1109
                    
 
1110
            
 
1111
            # Remove them.
 
1112
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1113
            bpy.ops.mesh.delete('INVOKE_REGION_WIN', type='VERT')
 
1114
            bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
 
1115
            
 
1116
            # Remove doubles to discard very near verts from calculations of distance.
 
1117
            bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=self.crosshatch_merge_distance * 4.0)
 
1118
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1119
            
 
1120
            # Get all coords of the resulting nodes.
 
1121
            nodes_verts_coords = [(v.co[0], v.co[1], v.co[2]) for v in ob_calc_merge_dist.data.vertices]
 
1122
            
 
1123
            #### Check if the strokes are a crosshatch.
 
1124
            if len(nodes_verts_coords) >= 3:
 
1125
                self.is_crosshatch = True
 
1126
                
 
1127
                shortest_dist = None
 
1128
                for co_1 in nodes_verts_coords:
 
1129
                    for co_2 in nodes_verts_coords:
 
1130
                        if co_1 != co_2:
 
1131
                            dist = (mathutils.Vector(co_1) - mathutils.Vector(co_2)).length
 
1132
                            
 
1133
                            if shortest_dist != None:
 
1134
                                if dist < shortest_dist:
 
1135
                                    shortest_dist = dist
 
1136
                            else:
 
1137
                                shortest_dist = dist
 
1138
                
 
1139
                self.crosshatch_merge_distance = shortest_dist / 3
 
1140
            
 
1141
            
 
1142
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1143
            bpy.data.objects[ob_splines.name].select = True
 
1144
            bpy.context.scene.objects.active = bpy.data.objects[ob_splines.name]
 
1145
            
 
1146
            #### Deselect all points.
 
1147
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1148
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1149
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1150
            
 
1151
            
 
1152
            
 
1153
            #### Smooth splines in a localized way, to eliminate "saw-teeth" like shapes when there are many points.
 
1154
            for sp in ob_splines.data.splines:
 
1155
                angle_sum = 0
 
1156
                
 
1157
                angle_limit = 2 # Degrees
 
1158
                for t in range(len(sp.bezier_points)):
 
1159
                    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.
 
1160
                        p1 = sp.bezier_points[t]
 
1161
                        p2 = sp.bezier_points[t + 1]
 
1162
                        p3 = sp.bezier_points[t + 2]
 
1163
                        
 
1164
                        vec_1 = p1.co - p2.co
 
1165
                        vec_2 = p2.co - p3.co
 
1166
                        
 
1167
                        if p2.co != p1.co and p2.co != p3.co:
 
1168
                            angle = vec_1.angle(vec_2)
 
1169
                            angle_sum += degrees(angle)
 
1170
                            
 
1171
                            if angle_sum >= angle_limit: # If sum of angles is grater than the limit.
 
1172
                                if (p1.co - p2.co).length <= self.crosshatch_merge_distance:
 
1173
                                    p1.select_control_point = True; p1.select_left_handle = True; p1.select_right_handle = True
 
1174
                                    p2.select_control_point = True; p2.select_left_handle = True; p2.select_right_handle = True
 
1175
                                
 
1176
                                if (p1.co - p2.co).length <= self.crosshatch_merge_distance:
 
1177
                                    p3.select_control_point = True; p3.select_left_handle = True; p3.select_right_handle = True
 
1178
                                
 
1179
                                angle_sum = 0
 
1180
                
 
1181
                sp.bezier_points[0].select_control_point = False
 
1182
                sp.bezier_points[0].select_left_handle = False
 
1183
                sp.bezier_points[0].select_right_handle = False
 
1184
                
 
1185
                sp.bezier_points[len(sp.bezier_points) - 1].select_control_point = False
 
1186
                sp.bezier_points[len(sp.bezier_points) - 1].select_left_handle = False
 
1187
                sp.bezier_points[len(sp.bezier_points) - 1].select_right_handle  = False
 
1188
            
 
1189
            
 
1190
            
 
1191
            #### Smooth out strokes a little to improve crosshatch detection.
 
1192
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1193
            
 
1194
            for i in range(15):
 
1195
                bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1196
            
 
1197
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1198
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1199
            
 
1200
            
 
1201
            
 
1202
            
 
1203
            #### Simplify the splines.
 
1204
            for sp in ob_splines.data.splines:
 
1205
                angle_sum = 0
 
1206
                
 
1207
                sp.bezier_points[0].select_control_point = True
 
1208
                sp.bezier_points[0].select_left_handle = True
 
1209
                sp.bezier_points[0].select_right_handle = True
 
1210
                
 
1211
                sp.bezier_points[len(sp.bezier_points) - 1].select_control_point = True
 
1212
                sp.bezier_points[len(sp.bezier_points) - 1].select_left_handle = True
 
1213
                sp.bezier_points[len(sp.bezier_points) - 1].select_right_handle  = True
 
1214
                
 
1215
                
 
1216
                angle_limit = 15 # Degrees
 
1217
                for t in range(len(sp.bezier_points)):
 
1218
                    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.
 
1219
                        p1 = sp.bezier_points[t]
 
1220
                        p2 = sp.bezier_points[t + 1]
 
1221
                        p3 = sp.bezier_points[t + 2]
 
1222
                        
 
1223
                        vec_1 = p1.co - p2.co
 
1224
                        vec_2 = p2.co - p3.co
 
1225
                        
 
1226
                        if p2.co != p1.co and p2.co != p3.co:
 
1227
                            angle = vec_1.angle(vec_2)
 
1228
                            angle_sum += degrees(angle)
 
1229
                            
 
1230
                            if angle_sum >= angle_limit: # If sum of angles is grater than the limit.
 
1231
                                p1.select_control_point = True; p1.select_left_handle = True; p1.select_right_handle = True
 
1232
                                p2.select_control_point = True; p2.select_left_handle = True; p2.select_right_handle = True
 
1233
                                p3.select_control_point = True; p3.select_left_handle = True; p3.select_right_handle = True
 
1234
                                
 
1235
                                angle_sum = 0
 
1236
                            
 
1237
                            
 
1238
            
 
1239
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1240
            
 
1241
            bpy.ops.curve.select_all(action = 'INVERT')
 
1242
            
 
1243
            bpy.ops.curve.delete(type='SELECTED')
 
1244
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1245
            
 
1246
            
 
1247
            
 
1248
            objects_to_delete.append(ob_splines)
 
1249
            
 
1250
            
 
1251
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1252
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1253
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1254
            
 
1255
            
 
1256
            #### Check if the strokes are a crosshatch.
 
1257
            if self.is_crosshatch:
 
1258
                all_points_coords = []
 
1259
                for i in range(len(ob_splines.data.splines)):
 
1260
                    all_points_coords.append([])
 
1261
                    
 
1262
                    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]]
 
1263
                
 
1264
                
 
1265
                all_intersections = []
 
1266
                checked_splines = []
 
1267
                for i in range(len(all_points_coords)):
 
1268
                        
 
1269
                    for t in range(len(all_points_coords[i]) - 1):
 
1270
                        bp1_co = all_points_coords[i][t]
 
1271
                        bp2_co = all_points_coords[i][t + 1]
 
1272
                        
 
1273
                        for i2 in range(len(all_points_coords)):
 
1274
                            if i != i2 and not i2 in checked_splines:
 
1275
                                for t2 in range(len(all_points_coords[i2]) - 1):
 
1276
                                    bp3_co = all_points_coords[i2][t2]
 
1277
                                    bp4_co = all_points_coords[i2][t2 + 1]
 
1278
                                    
 
1279
                                    
 
1280
                                    intersec_coords = mathutils.geometry.intersect_line_line(bp1_co, bp2_co, bp3_co, bp4_co)
 
1281
                                    
 
1282
                                    if intersec_coords != None:
 
1283
                                        dist = (intersec_coords[0] - intersec_coords[1]).length
 
1284
                                        
 
1285
                                        if dist <= self.crosshatch_merge_distance * 1.5:
 
1286
                                            temp_co, percent1 = mathutils.geometry.intersect_point_line(intersec_coords[0], bp1_co, bp2_co)
 
1287
                                            
 
1288
                                            if (percent1 >= -0.02 and percent1 <= 1.02):
 
1289
                                                temp_co, percent2 = mathutils.geometry.intersect_point_line(intersec_coords[1], bp3_co, bp4_co)
 
1290
                                                if (percent2 >= -0.02 and percent2 <= 1.02):
 
1291
                                                    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.
 
1292
                                                    all_intersections.append((i2, t2, percent2, ob_splines.matrix_world * intersec_coords[1]))
 
1293
                                            
 
1294
                                            
 
1295
                        
 
1296
                        checked_splines.append(i)        
 
1297
                
 
1298
                
 
1299
                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.
 
1300
                
 
1301
                
 
1302
                
 
1303
                self.crosshatch_strokes_coords = {}
 
1304
                for i in range(len(all_intersections)):
 
1305
                    if not all_intersections[i][0] in self.crosshatch_strokes_coords:
 
1306
                        self.crosshatch_strokes_coords[all_intersections[i][0]] = []
 
1307
                        
 
1308
                    self.crosshatch_strokes_coords[all_intersections[i][0]].append(all_intersections[i][3]) # Save intersection coords.
 
1309
                
 
1310
            else:
 
1311
                self.is_crosshatch = False
 
1312
                
 
1313
        
 
1314
        #### Delete all duplicates.
 
1315
        for o in objects_to_delete:
 
1316
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1317
            bpy.data.objects[o.name].select = True
 
1318
            bpy.context.scene.objects.active = bpy.data.objects[o.name]
 
1319
            bpy.ops.object.delete()
 
1320
            
 
1321
            
 
1322
        #### If the main object has modifiers, turn their "viewport view status" to what it was before the forced deactivation above.
 
1323
        if len(self.main_object.modifiers) > 0:
 
1324
            for m_idx in range(len(self.main_object.modifiers)):
 
1325
                self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx]
 
1326
        
 
1327
        
 
1328
            
 
1329
        return
 
1330
    
 
1331
    
 
1332
    
 
1333
    #### Part of the Crosshatch process that is repeated when the operator is tweaked.
 
1334
    def crosshatch_surface_execute(self):
 
1335
        # 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)
 
1336
        self.modifiers_prev_viewport_state = []
 
1337
        if len(self.main_object.modifiers) > 0:
 
1338
            for m_idx in range(len(self.main_object.modifiers)):
 
1339
                self.modifiers_prev_viewport_state.append(self.main_object.modifiers[m_idx].show_viewport)
 
1340
                
 
1341
                self.main_object.modifiers[m_idx].show_viewport = False
 
1342
                
 
1343
                
 
1344
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1345
        
 
1346
        
 
1347
        
 
1348
        me_name = "SURFSKIO_STK_TMP"
 
1349
        me = bpy.data.meshes.new(me_name)
 
1350
        
 
1351
        all_verts_coords = []
 
1352
        all_edges = []
 
1353
        for st_idx in self.crosshatch_strokes_coords:
 
1354
            for co_idx in range(len(self.crosshatch_strokes_coords[st_idx])):
 
1355
                coords = self.crosshatch_strokes_coords[st_idx][co_idx]
 
1356
                
 
1357
                all_verts_coords.append(coords)
 
1358
                
 
1359
                if co_idx > 0:
 
1360
                    all_edges.append((len(all_verts_coords) - 2, len(all_verts_coords) - 1))
 
1361
                    
 
1362
        
 
1363
        me.from_pydata(all_verts_coords, all_edges, [])
 
1364
        
 
1365
        me.update()
 
1366
        
 
1367
        ob = bpy.data.objects.new(me_name, me)
 
1368
        ob.data = me
 
1369
        bpy.context.scene.objects.link(ob)
 
1370
        
 
1371
        
 
1372
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1373
        bpy.data.objects[ob.name].select = True
 
1374
        bpy.context.scene.objects.active = bpy.data.objects[ob.name]
 
1375
        
 
1376
        
 
1377
        #### Get together each vert and its nearest, to the middle position.
 
1378
        verts = ob.data.vertices
 
1379
        checked_verts = []
 
1380
        for i in range(len(verts)):
 
1381
            shortest_dist = None
 
1382
            
 
1383
            if not i in checked_verts:
 
1384
                for t in range(len(verts)):
 
1385
                    if i != t and not t in checked_verts:
 
1386
                        dist = (verts[i].co - verts[t].co).length
 
1387
                        
 
1388
                        if shortest_dist != None:
 
1389
                            if dist < shortest_dist:
 
1390
                                shortest_dist = dist
 
1391
                                nearest_vert = t
 
1392
                        else:
 
1393
                            shortest_dist = dist
 
1394
                            nearest_vert = t
 
1395
                
 
1396
                middle_location = (verts[i].co + verts[nearest_vert].co) / 2
 
1397
                
 
1398
                verts[i].co = middle_location
 
1399
                verts[nearest_vert].co = middle_location
 
1400
                
 
1401
                checked_verts.append(i)
 
1402
                checked_verts.append(nearest_vert)
 
1403
        
 
1404
        
 
1405
        
 
1406
        
 
1407
        #### Calculate average length between all the generated edges.
 
1408
        ob = bpy.context.object
 
1409
        lengths_sum = 0
 
1410
        for ed in ob.data.edges:
 
1411
            v1 = ob.data.vertices[ed.vertices[0]]
 
1412
            v2 = ob.data.vertices[ed.vertices[1]]
 
1413
            
 
1414
            lengths_sum += (v1.co - v2.co).length
 
1415
        
 
1416
        edges_count = len(ob.data.edges)
 
1417
        
 
1418
        average_edge_length = lengths_sum / edges_count
 
1419
        
 
1420
        
 
1421
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1422
        bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
 
1423
        bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=average_edge_length / 15.0)
 
1424
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1425
        
 
1426
        final_points_ob = bpy.context.scene.objects.active
 
1427
        
 
1428
        
 
1429
        #### Make a dictionary with the verts related to each vert.
 
1430
        related_key_verts = {}
 
1431
        for ed in final_points_ob.data.edges:
 
1432
            if not ed.vertices[0] in related_key_verts:
 
1433
                related_key_verts[ed.vertices[0]] = []
 
1434
                
 
1435
            if not ed.vertices[1] in related_key_verts:
 
1436
                related_key_verts[ed.vertices[1]] = []
 
1437
            
 
1438
            
 
1439
            if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
 
1440
                related_key_verts[ed.vertices[0]].append(ed.vertices[1])
 
1441
                
 
1442
            if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
 
1443
                related_key_verts[ed.vertices[1]].append(ed.vertices[0])
 
1444
        
 
1445
        
 
1446
        
 
1447
        #### Get groups of verts forming each face.
 
1448
        faces_verts_idx = [] 
 
1449
        for v1 in related_key_verts: # verts-1 .... 
 
1450
            for v2 in related_key_verts: # verts-2
 
1451
                if v1 != v2:
 
1452
                    related_verts_in_common = []
 
1453
                    v2_in_rel_v1 = False
 
1454
                    v1_in_rel_v2 = False
 
1455
                    for rel_v1 in related_key_verts[v1]:
 
1456
                        if rel_v1 in related_key_verts[v2]: # Check if related verts of verts-1 are related verts of verts-2.
 
1457
                            related_verts_in_common.append(rel_v1)
 
1458
                        
 
1459
                    if v2 in related_key_verts[v1]:
 
1460
                        v2_in_rel_v1 = True
 
1461
                            
 
1462
                    if v1 in related_key_verts[v2]:
 
1463
                        v1_in_rel_v2 = True
 
1464
                    
 
1465
                    
 
1466
                    repeated_face = False
 
1467
                    # If two verts have two related verts in common, they form a quad.
 
1468
                    if len(related_verts_in_common) == 2:
 
1469
                        # Check if the face is already saved.
 
1470
                        for f_verts in faces_verts_idx:
 
1471
                            repeated_verts = 0
 
1472
                            
 
1473
                            if len(f_verts) == 4:
 
1474
                                if v1 in f_verts: repeated_verts += 1
 
1475
                                if v2 in f_verts: repeated_verts += 1
 
1476
                                if related_verts_in_common[0] in f_verts: repeated_verts += 1
 
1477
                                if related_verts_in_common[1] in f_verts: repeated_verts += 1
 
1478
                                
 
1479
                                if repeated_verts == len(f_verts):
 
1480
                                    repeated_face = True
 
1481
                                    break
 
1482
                        
 
1483
                        if not repeated_face:
 
1484
                            faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
 
1485
                        
 
1486
                    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.
 
1487
                        # Check if the face is already saved.
 
1488
                        for f_verts in faces_verts_idx:
 
1489
                            repeated_verts = 0
 
1490
                            
 
1491
                            if len(f_verts) == 3:
 
1492
                                if v1 in f_verts: repeated_verts += 1
 
1493
                                if v2 in f_verts: repeated_verts += 1
 
1494
                                if related_verts_in_common[0] in f_verts: repeated_verts += 1
 
1495
                                
 
1496
                                if repeated_verts == len(f_verts):
 
1497
                                    repeated_face = True
 
1498
                                    break
 
1499
                        
 
1500
                        if not repeated_face:
 
1501
                            faces_verts_idx.append([v1, related_verts_in_common[0], v2])
 
1502
                
 
1503
        
 
1504
        #### Keep only the faces that don't overlap by ignoring quads that overlap with two adjacent triangles.
 
1505
        faces_to_not_include_idx = [] # Indices of faces_verts_idx to eliminate.
 
1506
        for i in range(len(faces_verts_idx)):
 
1507
            for t in range(len(faces_verts_idx)):
 
1508
                if i != t:
 
1509
                    verts_in_common = 0
 
1510
                    
 
1511
                    if len(faces_verts_idx[i]) == 4 and len(faces_verts_idx[t]) == 3:
 
1512
                        for v_idx in faces_verts_idx[t]:
 
1513
                            if v_idx in faces_verts_idx[i]:
 
1514
                                verts_in_common += 1
 
1515
                                
 
1516
                        if verts_in_common == 3: # If it doesn't have all it's vertices repeated in the other face.
 
1517
                            if not i in faces_to_not_include_idx:
 
1518
                                faces_to_not_include_idx.append(i)
 
1519
        
 
1520
        
 
1521
        #### Build surface.
 
1522
        all_surface_verts_co = []
 
1523
        verts_idx_translation = {}
 
1524
        for i in range(len(final_points_ob.data.vertices)):
 
1525
            coords = final_points_ob.data.vertices[i].co
 
1526
            all_surface_verts_co.append([coords[0], coords[1], coords[2]])
 
1527
            
 
1528
        # Verts of each face.
 
1529
        all_surface_faces = []
 
1530
        for i in range(len(faces_verts_idx)):
 
1531
            if not i in faces_to_not_include_idx:
 
1532
                face = []
 
1533
                for v_idx in faces_verts_idx[i]:
 
1534
                    face.append(v_idx)
 
1535
                
 
1536
                all_surface_faces.append(face)
 
1537
        
 
1538
        # Build the mesh.
 
1539
        surf_me_name = "SURFSKIO_surface"
 
1540
        me_surf = bpy.data.meshes.new(surf_me_name)
 
1541
        
 
1542
        me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
 
1543
        
 
1544
        me_surf.update()
 
1545
        
 
1546
        ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
 
1547
        bpy.context.scene.objects.link(ob_surface)
 
1548
        
 
1549
        # Delete final points temporal object
 
1550
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1551
        bpy.data.objects[final_points_ob.name].select = True
 
1552
        bpy.context.scene.objects.active = bpy.data.objects[final_points_ob.name]
 
1553
        
 
1554
        bpy.ops.object.delete()
 
1555
        
 
1556
        
 
1557
        # Delete isolated verts if there are any.
 
1558
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1559
        bpy.data.objects[ob_surface.name].select = True
 
1560
        bpy.context.scene.objects.active = bpy.data.objects[ob_surface.name]
 
1561
        
 
1562
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1563
        bpy.ops.mesh.select_all(action='DESELECT')
 
1564
        bpy.ops.mesh.select_face_by_sides(type='NOTEQUAL')
 
1565
        bpy.ops.mesh.delete()
 
1566
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1567
        
 
1568
        
 
1569
        
 
1570
        #### Join crosshatch results with original mesh.
 
1571
        
 
1572
        # Calculate a distance to merge the verts of the crosshatch surface to the main object.
 
1573
        edges_length_sum = 0
 
1574
        for ed in ob_surface.data.edges:
 
1575
            edges_length_sum += (ob_surface.data.vertices[ed.vertices[0]].co - ob_surface.data.vertices[ed.vertices[1]].co).length
 
1576
        
 
1577
        if len(ob_surface.data.edges) > 0:
 
1578
            average_surface_edges_length = edges_length_sum / len(ob_surface.data.edges)
 
1579
        else:
 
1580
            average_surface_edges_length = 0.0001
 
1581
        
 
1582
        # Make dictionary with all the verts connected to each vert, on the new surface object.
 
1583
        surface_connected_verts = {}
 
1584
        for ed in ob_surface.data.edges:
 
1585
            if not ed.vertices[0] in surface_connected_verts:
 
1586
                surface_connected_verts[ed.vertices[0]] = []
 
1587
                
 
1588
            surface_connected_verts[ed.vertices[0]].append(ed.vertices[1])
 
1589
            
 
1590
            
 
1591
            if not ed.vertices[1] in surface_connected_verts:
 
1592
                surface_connected_verts[ed.vertices[1]] = []
 
1593
                
 
1594
            surface_connected_verts[ed.vertices[1]].append(ed.vertices[0])
 
1595
            
 
1596
        
 
1597
        
 
1598
        # Duplicate the new surface object, and use shrinkwrap to calculate later the nearest verts to the main object.
 
1599
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1600
        bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1601
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1602
        
 
1603
        bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
1604
        
 
1605
        final_ob_duplicate = bpy.context.scene.objects.active
 
1606
        
 
1607
        bpy.ops.object.modifier_add('INVOKE_REGION_WIN', type='SHRINKWRAP')
 
1608
        final_ob_duplicate.modifiers["Shrinkwrap"].wrap_method = "NEAREST_VERTEX"
 
1609
        final_ob_duplicate.modifiers["Shrinkwrap"].target = self.main_object
 
1610
        
 
1611
        bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
 
1612
        
 
1613
        
 
1614
        # Make list with verts of original mesh as index and coords as value.
 
1615
        main_object_verts_coords = []
 
1616
        for v in self.main_object.data.vertices:
 
1617
            coords = self.main_object.matrix_world * v.co
 
1618
            
 
1619
            for c in range(len(coords)): # To avoid problems when taking "-0.00" as a different value as "0.00".
 
1620
                if "%.3f" % coords[c] == "-0.00":
 
1621
                    coords[c] = 0
 
1622
                        
 
1623
            main_object_verts_coords.append(["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]])
 
1624
        
 
1625
        tuple(main_object_verts_coords)
 
1626
        
 
1627
        
 
1628
        # Determine which verts will be merged, snap them to the nearest verts on the original verts, and get them selected.
 
1629
        crosshatch_verts_to_merge = []
 
1630
        if self.automatic_join:
 
1631
            for i in range(len(ob_surface.data.vertices)):
 
1632
                # 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.
 
1633
                merge_actual_vert = True
 
1634
                if len(surface_connected_verts[i]) < 4:
 
1635
                    for c_v_idx in surface_connected_verts[i]:
 
1636
                        points_original = []
 
1637
                        points_original.append(ob_surface.data.vertices[c_v_idx].co)
 
1638
                        points_original.append(ob_surface.data.vertices[i].co)
 
1639
                        
 
1640
                        points_target = []
 
1641
                        points_target.append(ob_surface.data.vertices[c_v_idx].co)
 
1642
                        points_target.append(final_ob_duplicate.data.vertices[i].co)
 
1643
                        
 
1644
                        vec_A = points_original[0] - points_original[1]
 
1645
                        vec_B = points_target[0] - points_target[1]
 
1646
                        
 
1647
                        dist_A = (points_original[0] - points_original[1]).length
 
1648
                        dist_B = (points_target[0] - points_target[1]).length
 
1649
                        
 
1650
                        
 
1651
                        if not (points_original[0] == points_original[1] or points_target[0] == points_target[1]): # If any vector's length is zero.
 
1652
                            angle = vec_A.angle(vec_B) / math.pi
 
1653
                        else:
 
1654
                            angle= 0
 
1655
                        
 
1656
                        
 
1657
                        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.
 
1658
                            merge_actual_vert = False
 
1659
                            break
 
1660
                else:
 
1661
                    merge_actual_vert = False
 
1662
                    
 
1663
                    
 
1664
                if merge_actual_vert:
 
1665
                    coords = final_ob_duplicate.data.vertices[i].co
 
1666
                    
 
1667
                    for c in range(len(coords)): # To avoid problems when taking "-0.000" as a different value as "0.00".
 
1668
                        if "%.3f" % coords[c] == "-0.00":
 
1669
                            coords[c] = 0
 
1670
                        
 
1671
                    comparison_coords = ["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]]
 
1672
                    
 
1673
                    
 
1674
                    if comparison_coords in main_object_verts_coords:
 
1675
                        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.
 
1676
                        
 
1677
                        if self.main_object.data.vertices[main_object_related_vert_idx].select == True or self.main_object_selected_verts_count == 0:
 
1678
                            ob_surface.data.vertices[i].co = final_ob_duplicate.data.vertices[i].co
 
1679
                            ob_surface.data.vertices[i].select = True
 
1680
                            crosshatch_verts_to_merge.append(i)
 
1681
                            
 
1682
                            # Make sure the vert in the main object is selected, in case it wasn't selected and the "join crosshatch" option is active.
 
1683
                            self.main_object.data.vertices[main_object_related_vert_idx].select = True
 
1684
        
 
1685
        
 
1686
        
 
1687
        
 
1688
        # Delete duplicated object.
 
1689
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1690
        bpy.data.objects[final_ob_duplicate.name].select = True
 
1691
        bpy.context.scene.objects.active = bpy.data.objects[final_ob_duplicate.name]
 
1692
        bpy.ops.object.delete()
 
1693
        
 
1694
        
 
1695
        # Join crosshatched surface and main object.
 
1696
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
1697
        bpy.data.objects[ob_surface.name].select = True
 
1698
        bpy.data.objects[self.main_object.name].select = True
 
1699
        bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
1700
        
 
1701
        bpy.ops.object.join('INVOKE_REGION_WIN')
 
1702
        
 
1703
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1704
        # Perform Remove doubles to merge verts.
 
1705
        if not (self.automatic_join == False and self.main_object_selected_verts_count == 0):
 
1706
            bpy.ops.mesh.remove_doubles(threshold=0.0001)
 
1707
        
 
1708
        bpy.ops.mesh.select_all(action='DESELECT')
 
1709
        
 
1710
        
 
1711
        #### If the main object has modifiers, turn their "viewport view status" to what it was before the forced deactivation above.
 
1712
        if len(self.main_object.modifiers) > 0:
 
1713
            for m_idx in range(len(self.main_object.modifiers)):
 
1714
                self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx]
 
1715
            
 
1716
            
 
1717
        
 
1718
        return{'FINISHED'}
 
1719
        
 
1720
        
 
1721
    
 
1722
    def rectangular_surface(self):
170
1723
        #### Selected edges.
171
1724
        all_selected_edges_idx = []
172
1725
        all_selected_verts = []
175
1728
            if ed.select:
176
1729
                all_selected_edges_idx.append(ed.index)
177
1730
                
178
 
                # Selected vertexes.
 
1731
                # Selected vertices.
179
1732
                if not ed.vertices[0] in all_selected_verts:
180
1733
                    all_selected_verts.append(self.main_object.data.vertices[ed.vertices[0]])
181
1734
                if not ed.vertices[1] in all_selected_verts:
186
1739
                all_verts_idx.append(ed.vertices[1])
187
1740
        
188
1741
        
 
1742
        
189
1743
        #### Identify the tips and "middle-vertex" that separates U from V, if there is one.
190
1744
        all_chains_tips_idx = []
191
1745
        for v_idx in all_verts_idx:
192
1746
            if all_verts_idx.count(v_idx) < 2:
193
1747
                all_chains_tips_idx.append(v_idx)
194
1748
        
 
1749
        
 
1750
        
195
1751
        edges_connected_to_tips = []
196
1752
        for ed in self.main_object.data.edges:
197
1753
            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):
198
1754
                edges_connected_to_tips.append(ed)
199
1755
        
 
1756
        
 
1757
        #### Check closed selections.
 
1758
        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")
 
1759
        
 
1760
        # 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".
 
1761
        single_unselected_verts = []
 
1762
        for ed in edges_connected_to_tips:
 
1763
            for ed_b in edges_connected_to_tips:
 
1764
                if ed != ed_b:
 
1765
                    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:
 
1766
                        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.
 
1767
                        single_unselected_verts.append(ed.vertices[0])
 
1768
                        break
 
1769
                    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:
 
1770
                        single_unselected_verts_and_neighbors.append([ed.vertices[0], ed.vertices[1], ed_b.vertices[0]])
 
1771
                        single_unselected_verts.append(ed.vertices[0])
 
1772
                        break
 
1773
                    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:
 
1774
                        single_unselected_verts_and_neighbors.append([ed.vertices[1], ed.vertices[0], ed_b.vertices[1]])
 
1775
                        single_unselected_verts.append(ed.vertices[1])
 
1776
                        break
 
1777
                    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:
 
1778
                        single_unselected_verts_and_neighbors.append([ed.vertices[1], ed.vertices[0], ed_b.vertices[0]])
 
1779
                        single_unselected_verts.append(ed.vertices[1])
 
1780
                        break
 
1781
        
 
1782
        
200
1783
        middle_vertex_idx = None
201
1784
        tips_to_discard_idx = []
202
 
        for ed_tips in edges_connected_to_tips:
203
 
            for ed_tips_b in edges_connected_to_tips:
204
 
                if (ed_tips != ed_tips_b):
205
 
                    if ed_tips.vertices[0] in all_verts_idx and (((ed_tips.vertices[1] == ed_tips_b.vertices[0]) or ed_tips.vertices[1] == ed_tips_b.vertices[1])):
206
 
                        middle_vertex_idx = ed_tips.vertices[1]
207
 
                        tips_to_discard_idx.append(ed_tips.vertices[0])
208
 
                    elif ed_tips.vertices[1] in all_verts_idx and (((ed_tips.vertices[0] == ed_tips_b.vertices[0]) or ed_tips.vertices[0] == ed_tips_b.vertices[1])):
209
 
                        middle_vertex_idx = ed_tips.vertices[0]
210
 
                        tips_to_discard_idx.append(ed_tips.vertices[1])
211
 
        
212
 
        
 
1785
        # Check if there is a "middle-vertex", and get its index.
 
1786
        for i in range(0, len(single_unselected_verts_and_neighbors)):
 
1787
            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)
 
1788
            
 
1789
            if single_unselected_verts_and_neighbors[i][2] != actual_chain_verts[len(actual_chain_verts) - 1].index:
 
1790
                middle_vertex_idx = single_unselected_verts_and_neighbors[i][0]
 
1791
                tips_to_discard_idx.append(single_unselected_verts_and_neighbors[i][1])
 
1792
                tips_to_discard_idx.append(single_unselected_verts_and_neighbors[i][2])
 
1793
            
 
1794
            
213
1795
        #### List with pairs of verts that belong to the tips of each selection chain (row).
214
1796
        verts_tips_same_chain_idx = []
215
1797
        if len(all_chains_tips_idx) >= 2:
216
1798
            checked_v = []
217
1799
            for i in range(0, len(all_chains_tips_idx)):
218
1800
                if all_chains_tips_idx[i] not in checked_v:
219
 
                    v_chain = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, all_chains_tips_idx[i], middle_vertex_idx)
 
1801
                    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)
220
1802
                    
221
1803
                    verts_tips_same_chain_idx.append([v_chain[0].index, v_chain[len(v_chain) - 1].index])
222
1804
                    
224
1806
                    checked_v.append(v_chain[len(v_chain) - 1].index)
225
1807
        
226
1808
        
227
 
        #### Selection tips (vertices)
 
1809
        #### Selection tips (vertices).
228
1810
        verts_tips_parsed_idx = []
229
1811
        if len(all_chains_tips_idx) >= 2:
230
1812
            for spec_v_idx in all_chains_tips_idx:
231
1813
                if (spec_v_idx not in tips_to_discard_idx):
232
1814
                    verts_tips_parsed_idx.append(spec_v_idx)
233
1815
        
234
 
        
 
1816
    
235
1817
        #### Identify the type of selection made by the user.
236
1818
        if middle_vertex_idx != None:
237
 
            if len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
 
1819
            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).
238
1820
                selection_type = "TWO_CONNECTED"
239
1821
            else:
240
 
                # The type of the selection was not identified, so the script stops.
241
 
                return
 
1822
                # The type of the selection was not identified, the script stops.
 
1823
                self.report({'WARNING'}, "The selection isn't valid.")
 
1824
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1825
                self.cleanup_on_interruption()
 
1826
                self.stopping_errors = True
 
1827
                
 
1828
                return{'CANCELLED'}
242
1829
        else:
243
 
            if len(all_chains_tips_idx) == 2: # If there are 2 tips (one selection chain)
 
1830
            if len(all_chains_tips_idx) == 2: # If there are 2 tips
244
1831
                selection_type = "SINGLE"
245
 
            elif len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
 
1832
            elif len(all_chains_tips_idx) == 4: # If there are 4 tips
246
1833
                selection_type = "TWO_NOT_CONNECTED"
247
1834
            elif len(all_chains_tips_idx) == 0:
248
 
                selection_type = "NO_SELECTION"
 
1835
                if len(self.main_splines.data.splines) > 1:
 
1836
                    selection_type = "NO_SELECTION"
 
1837
                else:
 
1838
                    # 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.
 
1839
                    self.report({'WARNING'}, "The selection isn't valid.")
 
1840
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1841
                    self.cleanup_on_interruption()
 
1842
                    self.stopping_errors = True
 
1843
                    
 
1844
                    return{'CANCELLED'}
249
1845
            else:
250
 
                # The type of the selection was not identified, so the script stops.
251
 
                return
252
 
        
253
 
        
254
 
        #### Check if it will be used grease pencil strokes or curves.
255
 
        selected_objs = bpy.context.selected_objects
256
 
        if len(selected_objs) > 1:
257
 
            for ob in selected_objs:
258
 
                if ob != bpy.context.scene.objects.active:
259
 
                    ob_gp_strokes = ob
260
 
            using_external_curves = True
261
 
            
262
 
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
263
 
        else:
264
 
            #### Vheck if there is a grease pencil layer. If not, quit.
265
 
            try:
266
 
                x = self.main_object.grease_pencil.layers.active.active_frame.strokes
267
 
            except:
 
1846
                # The type of the selection was not identified, the script stops.
 
1847
                self.report({'WARNING'}, "The selection isn't valid.")
 
1848
                
 
1849
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1850
                self.cleanup_on_interruption()
 
1851
                
 
1852
                self.stopping_errors = True
 
1853
                
268
1854
                return{'CANCELLED'}
269
 
                
270
 
            #### Convert grease pencil strokes to curve.
 
1855
        
 
1856
        
 
1857
        
 
1858
        #### If the selection type is TWO_NOT_CONNECTED and there is only one stroke, stop the script.
 
1859
        if selection_type == "TWO_NOT_CONNECTED" and len(self.main_splines.data.splines) == 1:
 
1860
            self.report({'WARNING'}, "At least two strokes are needed when there are two not connected selections.")
271
1861
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
272
 
            bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE')
273
 
            ob_gp_strokes = bpy.context.object
274
 
            
275
 
            
276
 
            using_external_curves = False
277
 
            
278
 
            
279
 
        
280
 
        ob_gp_strokes.name = "SURFSK_temp_strokes"
 
1862
            self.cleanup_on_interruption()
 
1863
            self.stopping_errors = True
 
1864
            
 
1865
            return{'CANCELLED'}
 
1866
        
 
1867
        
 
1868
        
 
1869
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1870
        
281
1871
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
282
 
 
283
 
        myobject = bpy.data.objects[ob_gp_strokes.name]
284
 
        bpy.context.scene.objects.active = myobject
285
 
        myobject.select = True
286
 
#        bpy.ops.object.select_name('INVOKE_REGION_WIN', name = ob_gp_strokes.name)
287
 
        bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
288
 
        
289
 
        
290
 
        #### If "Keep strokes" is active make a duplicate of the original strokes, which will be intact
291
 
        if bpy.context.scene.SURFSK_keep_strokes:
292
 
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
293
 
            bpy.context.object.name = "SURFSK_used_strokes"
294
 
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
295
 
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
296
 
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
297
 
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
298
 
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
299
 
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
300
 
            bpy.ops.curve.smooth('INVOKE_REGION_WIN')
301
 
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
302
 
            
303
 
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
304
 
            myobject = bpy.data.objects[ob_gp_strokes.name]
305
 
            bpy.context.scene.objects.active = myobject
306
 
            myobject.select = True
307
 
            bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
308
 
        
309
 
        
310
 
        #### Enter editmode for the new curve (converted from grease pencil strokes).
311
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
312
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
313
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
314
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
315
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
316
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
317
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
318
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
319
 
        
320
 
        
321
 
        selection_U_exists = False
322
 
        selection_U2_exists = False
323
 
        selection_V_exists = False
324
 
        selection_V2_exists = False
325
 
        #### Define what vertexes are at the tips of each selection and are not the middle-vertex.
 
1872
        bpy.data.objects[self.main_splines.name].select = True
 
1873
        bpy.context.scene.objects.active = bpy.context.scene.objects[self.main_splines.name]
 
1874
        
 
1875
        
 
1876
        #### Enter editmode for the new curve (converted from grease pencil strokes), to smooth it out.
 
1877
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1878
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1879
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1880
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1881
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1882
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1883
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
 
1884
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
1885
        
 
1886
        
 
1887
        self.selection_U_exists = False
 
1888
        self.selection_U2_exists = False
 
1889
        self.selection_V_exists = False
 
1890
        self.selection_V2_exists = False
 
1891
        
 
1892
        self.selection_U_is_closed = False
 
1893
        self.selection_U2_is_closed = False
 
1894
        self.selection_V_is_closed = False
 
1895
        self.selection_V2_is_closed = False
 
1896
        
 
1897
        #### Define what vertices are at the tips of each selection and are not the middle-vertex.
326
1898
        if selection_type == "TWO_CONNECTED":
327
 
            selection_U_exists = True
328
 
            selection_V_exists = True
 
1899
            self.selection_U_exists = True
 
1900
            self.selection_V_exists = True
 
1901
            
 
1902
            closing_vert_U_idx = None
 
1903
            closing_vert_V_idx = None
 
1904
            closing_vert_U2_idx = None
 
1905
            closing_vert_V2_idx = None
329
1906
            
330
1907
            # Determine which selection is Selection-U and which is Selection-V.
331
1908
            points_A = []
338
1915
            points_B.append(self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_parsed_idx[1]].co)
339
1916
            points_B.append(self.main_object.matrix_world * self.main_object.data.vertices[middle_vertex_idx].co)
340
1917
            
341
 
            points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
342
 
            points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
 
1918
            points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
 
1919
            points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[len(self.main_splines.data.splines[0].bezier_points) - 1].co)
343
1920
            
344
1921
            angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
345
1922
            angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
352
1929
                first_vert_V_idx = verts_tips_parsed_idx[0]
353
1930
                
354
1931
        elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
355
 
            first_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[0].co
356
 
            last_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co
357
 
            
358
 
            first_sketched_point_last_stroke_co = ob_gp_strokes.data.splines[len(ob_gp_strokes.data.splines) - 1].bezier_points[0].co
 
1932
            first_sketched_point_first_stroke_co = self.main_splines.data.splines[0].bezier_points[0].co
 
1933
            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
 
1934
            first_sketched_point_last_stroke_co = self.main_splines.data.splines[len(self.main_splines.data.splines) - 1].bezier_points[0].co
 
1935
            if len(self.main_splines.data.splines) > 1:
 
1936
                first_sketched_point_second_stroke_co = self.main_splines.data.splines[1].bezier_points[0].co
 
1937
                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
 
1938
            
 
1939
            
 
1940
            single_unselected_neighbors = [] # Only the neighbors of the single unselected verts.
 
1941
            for verts_neig_idx in single_unselected_verts_and_neighbors:
 
1942
                single_unselected_neighbors.append(verts_neig_idx[1])
 
1943
                single_unselected_neighbors.append(verts_neig_idx[2])
 
1944
            
 
1945
            
 
1946
            all_chains_tips_and_middle_vert = []
 
1947
            for v_idx in all_chains_tips_idx:
 
1948
                if v_idx not in single_unselected_neighbors:
 
1949
                    all_chains_tips_and_middle_vert.append(v_idx)
 
1950
                    
 
1951
            
 
1952
            all_chains_tips_and_middle_vert += single_unselected_verts
 
1953
            
 
1954
            all_participating_verts = all_chains_tips_and_middle_vert + all_verts_idx
359
1955
            
360
1956
            # The tip of the selected vertices nearest to the first point of the first sketched stroke.
361
 
            prev_dist = 999999999999
362
 
            for i in range(0, len(verts_tips_same_chain_idx)):
363
 
                for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
364
 
                    dist = self.pts_distance(first_sketched_point_first_stroke_co, self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_same_chain_idx[i][v_idx]].co)
365
 
                    if dist < prev_dist:
366
 
                        prev_dist = dist
367
 
                        
368
 
                        nearest_tip_first_st_first_pt_idx = i
369
 
                        
370
 
                        nearest_tip_first_pair_first_pt_idx = v_idx
371
 
                        
372
 
                        # Shortest distance to the first point of the first stroke  
373
 
                        shortest_distance_to_first_stroke = dist
374
 
            
 
1957
            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)
 
1958
            # If the nearest tip is not from a closed selection, get the opposite tip vertex index.
 
1959
            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:
 
1960
                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)
375
1961
            
376
1962
            # The tip of the selected vertices nearest to the last point of the first sketched stroke.
377
 
            prev_dist = 999999999999
378
 
            for i in range(0, len(verts_tips_same_chain_idx)):
379
 
                for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
380
 
                    dist = self.pts_distance(last_sketched_point_first_stroke_co, self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_same_chain_idx[i][v_idx]].co)
381
 
                    if dist < prev_dist:
382
 
                        prev_dist = dist
383
 
                        
384
 
                        nearest_tip_first_st_last_pt_pair_idx = i
385
 
                        nearest_tip_first_st_last_pt_point_idx = v_idx
386
 
            
 
1963
            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)
387
1964
            
388
1965
            # The tip of the selected vertices nearest to the first point of the last sketched stroke.
389
 
            prev_dist = 999999999999
390
 
            for i in range(0, len(verts_tips_same_chain_idx)):
391
 
                for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
392
 
                    dist = self.pts_distance(first_sketched_point_last_stroke_co, self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_same_chain_idx[i][v_idx]].co)
393
 
                    if dist < prev_dist:
394
 
                        prev_dist = dist
395
 
                        
396
 
                        nearest_tip_last_st_first_pt_pair_idx = i
397
 
                        nearest_tip_last_st_first_pt_point_idx = v_idx
398
 
            
399
 
            
400
 
            points_tips = []
401
 
            points_first_stroke_tips = []
 
1966
            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)
 
1967
            
 
1968
            if len(self.main_splines.data.splines) > 1:
 
1969
                # 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)
 
1970
                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)
 
1971
                
 
1972
                # 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)
 
1973
                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)
 
1974
            
 
1975
            
402
1976
            
403
1977
            # Determine if the single selection will be treated as U or as V.
404
1978
            edges_sum = 0
405
1979
            for i in all_selected_edges_idx:
406
 
                edges_sum += self.pts_distance(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)
407
 
            
 
1980
                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
 
1981
                
408
1982
            average_edge_length = edges_sum / len(all_selected_edges_idx)
409
1983
            
410
1984
            
411
 
            
412
 
            # If the beginning of the first stroke is near enough to interpret things as an "extrude along strokes" instead of "extrude through strokes"
413
 
            if shortest_distance_to_first_stroke < average_edge_length / 3:
414
 
                selection_U_exists = False
415
 
                selection_V_exists = True
416
 
                
417
 
                first_vert_V_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][nearest_tip_first_pair_first_pt_idx]
418
 
                
 
1985
            # Get shortest distance from the first point of the last stroke to any participating vertex.
 
1986
            temp_idx, shortest_distance_to_last_stroke = self.shortest_distance(self.main_object, first_sketched_point_last_stroke_co, all_participating_verts)
 
1987
            
 
1988
            
 
1989
            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"
 
1990
                self.selection_U_exists = False
 
1991
                self.selection_V_exists = True
 
1992
                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.
 
1993
                    self.selection_V_is_closed = False
 
1994
                    first_neighbor_V_idx = None
 
1995
                    closing_vert_U_idx = None
 
1996
                    closing_vert_U2_idx = None
 
1997
                    closing_vert_V_idx = None
 
1998
                    closing_vert_V2_idx = None
 
1999
                    
 
2000
                    first_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
 
2001
                    
 
2002
                    if selection_type == "TWO_NOT_CONNECTED":
 
2003
                        self.selection_V2_exists = True
 
2004
                        
 
2005
                        first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
 
2006
                else:
 
2007
                    self.selection_V_is_closed = True
 
2008
                    closing_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
 
2009
                    
 
2010
                    # Get the neighbors of the first (unselected) vert of the closed selection U.
 
2011
                    vert_neighbors = []
 
2012
                    for verts in single_unselected_verts_and_neighbors:
 
2013
                        if verts[0] == nearest_tip_to_first_st_first_pt_idx:
 
2014
                            vert_neighbors.append(verts[1])
 
2015
                            vert_neighbors.append(verts[2])
 
2016
                            break
 
2017
                    
 
2018
                    verts_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, vert_neighbors[0], middle_vertex_idx, None)
 
2019
                    
 
2020
                    for i in range(0, len(verts_V)):
 
2021
                        if verts_V[i].index == nearest_vert_to_second_st_first_pt_idx:
 
2022
                            if i >= len(verts_V) / 2: # If the vertex nearest to the first point of the second stroke is in the first half of the selected verts.
 
2023
                                first_vert_V_idx = vert_neighbors[1]
 
2024
                                break
 
2025
                            else:
 
2026
                                first_vert_V_idx = vert_neighbors[0]
 
2027
                                break
 
2028
                    
 
2029
                    
 
2030
                    
419
2031
                if selection_type == "TWO_NOT_CONNECTED":
420
 
                    selection_V2_exists = True
421
 
                    
422
 
                    first_vert_V2_idx = verts_tips_same_chain_idx[nearest_tip_first_st_last_pt_pair_idx][nearest_tip_first_st_last_pt_point_idx]
423
 
                    
 
2032
                    self.selection_V2_exists = True
 
2033
                    
 
2034
                    if nearest_tip_to_first_st_last_pt_idx not in single_unselected_verts or nearest_tip_to_first_st_last_pt_idx == middle_vertex_idx: # If the second selection is not closed.
 
2035
                        self.selection_V2_is_closed = False
 
2036
                        first_neighbor_V2_idx = None
 
2037
                        closing_vert_V2_idx = None
 
2038
                        
 
2039
                        first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
 
2040
                        
 
2041
                    else:
 
2042
                        self.selection_V2_is_closed = True
 
2043
                        closing_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
 
2044
                        
 
2045
                        # Get the neighbors of the first (unselected) vert of the closed selection U.
 
2046
                        vert_neighbors = []
 
2047
                        for verts in single_unselected_verts_and_neighbors:
 
2048
                            if verts[0] == nearest_tip_to_first_st_last_pt_idx:
 
2049
                                vert_neighbors.append(verts[1])
 
2050
                                vert_neighbors.append(verts[2])
 
2051
                                break
 
2052
                            
 
2053
                        
 
2054
                        verts_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, vert_neighbors[0], middle_vertex_idx, None)
 
2055
                        
 
2056
                        for i in range(0, len(verts_V2)):
 
2057
                            if verts_V2[i].index == nearest_vert_to_second_st_last_pt_idx:
 
2058
                                if i >= len(verts_V2) / 2: # If the vertex nearest to the first point of the second stroke is in the first half of the selected verts.
 
2059
                                    first_vert_V2_idx = vert_neighbors[1]
 
2060
                                    break
 
2061
                                else:
 
2062
                                    first_vert_V2_idx = vert_neighbors[0]
 
2063
                                    break
 
2064
                        
424
2065
                else:
425
 
                    selection_V2_exists = False
 
2066
                    self.selection_V2_exists = False
426
2067
                
427
2068
            else:
428
 
                selection_U_exists = True
429
 
                selection_V_exists = False
430
 
                
431
 
                points_tips.append(self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]].co)
432
 
                points_tips.append(self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]].co)
433
 
                
434
 
                points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
435
 
                points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
436
 
                
437
 
                vec_A = points_tips[0] - points_tips[1]
438
 
                vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
439
 
                
440
 
                # Compare the direction of the selection and the first grease pencil stroke to determine which is the "first" vertex of the selection.
441
 
                if vec_A.dot(vec_B) < 0:
442
 
                    first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]
 
2069
                self.selection_U_exists = True
 
2070
                self.selection_V_exists = False
 
2071
                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.
 
2072
                    self.selection_U_is_closed = False
 
2073
                    first_neighbor_U_idx = None
 
2074
                    closing_vert_U_idx = None
 
2075
                    
 
2076
                    points_tips = []
 
2077
                    points_tips.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_idx].co)
 
2078
                    points_tips.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_opposite_idx].co)
 
2079
                    
 
2080
                    points_first_stroke_tips = []
 
2081
                    points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
 
2082
                    points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[len(self.main_splines.data.splines[0].bezier_points) - 1].co)
 
2083
                    
 
2084
                    vec_A = points_tips[0] - points_tips[1]
 
2085
                    vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
 
2086
                    
 
2087
                    # Compare the direction of the selection and the first grease pencil stroke to determine which is the "first" vertex of the selection.
 
2088
                    if vec_A.dot(vec_B) < 0:
 
2089
                        first_vert_U_idx = nearest_tip_to_first_st_first_pt_opposite_idx
 
2090
                    else:
 
2091
                        first_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
 
2092
                        
443
2093
                else:
444
 
                    first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]
445
 
            
 
2094
                    self.selection_U_is_closed = True
 
2095
                    closing_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
 
2096
                    
 
2097
                    # Get the neighbors of the first (unselected) vert of the closed selection U.
 
2098
                    vert_neighbors = []
 
2099
                    for verts in single_unselected_verts_and_neighbors:
 
2100
                        if verts[0] == nearest_tip_to_first_st_first_pt_idx:
 
2101
                            vert_neighbors.append(verts[1])
 
2102
                            vert_neighbors.append(verts[2])
 
2103
                            break
 
2104
                    
 
2105
                    points_first_and_neighbor = []
 
2106
                    points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_idx].co)
 
2107
                    points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[vert_neighbors[0]].co)
 
2108
                    
 
2109
                    points_first_stroke_tips = []
 
2110
                    points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
 
2111
                    points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[1].co)
 
2112
                    
 
2113
                    vec_A = points_first_and_neighbor[0] - points_first_and_neighbor[1]
 
2114
                    vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
 
2115
                    
 
2116
                    # Compare the direction of the selection and the first grease pencil stroke to determine which is the vertex neighbor to the first vertex (unselected) of the closed selection. This will determine the direction of the closed selection.
 
2117
                    if vec_A.dot(vec_B) < 0:
 
2118
                        first_vert_U_idx = vert_neighbors[1]
 
2119
                    else:
 
2120
                        first_vert_U_idx = vert_neighbors[0]
 
2121
                
 
2122
                
 
2123
                
446
2124
                if selection_type == "TWO_NOT_CONNECTED":
447
 
                    selection_U2_exists = True
 
2125
                    self.selection_U2_exists = True
448
2126
                    
449
 
                    first_vert_U2_idx = verts_tips_same_chain_idx[nearest_tip_last_st_first_pt_pair_idx][nearest_tip_last_st_first_pt_point_idx]
 
2127
                    if nearest_tip_to_last_st_first_pt_idx not in single_unselected_verts or nearest_tip_to_last_st_first_pt_idx == middle_vertex_idx: # If the second selection is not closed.
 
2128
                        self.selection_U2_is_closed = False
 
2129
                        first_neighbor_U2_idx = None
 
2130
                        closing_vert_U2_idx = None
 
2131
                        
 
2132
                        first_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
 
2133
                        
 
2134
                    else:
 
2135
                        self.selection_U2_is_closed = True
 
2136
                        closing_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
 
2137
                        
 
2138
                        # Get the neighbors of the first (unselected) vert of the closed selection U.
 
2139
                        vert_neighbors = []
 
2140
                        for verts in single_unselected_verts_and_neighbors:
 
2141
                            if verts[0] == nearest_tip_to_last_st_first_pt_idx:
 
2142
                                vert_neighbors.append(verts[1])
 
2143
                                vert_neighbors.append(verts[2])
 
2144
                                break
 
2145
                        
 
2146
                        points_first_and_neighbor = []
 
2147
                        points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[nearest_tip_to_last_st_first_pt_idx].co)
 
2148
                        points_first_and_neighbor.append(self.main_object.matrix_world * self.main_object.data.vertices[vert_neighbors[0]].co)
 
2149
                        
 
2150
                        points_last_stroke_tips = []
 
2151
                        points_last_stroke_tips.append(self.main_splines.data.splines[len(self.main_splines.data.splines) - 1].bezier_points[0].co)
 
2152
                        points_last_stroke_tips.append(self.main_splines.data.splines[len(self.main_splines.data.splines) - 1].bezier_points[1].co)
 
2153
                        
 
2154
                        vec_A = points_first_and_neighbor[0] - points_first_and_neighbor[1]
 
2155
                        vec_B = points_last_stroke_tips[0] - points_last_stroke_tips[1]
 
2156
                        
 
2157
                        # Compare the direction of the selection and the last grease pencil stroke to determine which is the vertex neighbor to the first vertex (unselected) of the closed selection. This will determine the direction of the closed selection.
 
2158
                        if vec_A.dot(vec_B) < 0:
 
2159
                            first_vert_U2_idx = vert_neighbors[1]
 
2160
                        else:
 
2161
                            first_vert_U2_idx = vert_neighbors[0]
 
2162
                            
450
2163
                else:
451
 
                    selection_U2_exists = False
 
2164
                    self.selection_U2_exists = False
452
2165
                
453
2166
        elif selection_type == "NO_SELECTION":
454
 
            selection_U_exists = False
455
 
            selection_V_exists = False
 
2167
            self.selection_U_exists = False
 
2168
            self.selection_V_exists = False
 
2169
        
456
2170
        
457
2171
        
458
2172
        #### Get an ordered list of the vertices of Selection-U.
459
 
        if selection_U_exists:
460
 
            verts_ordered_U = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U_idx, middle_vertex_idx)
 
2173
        verts_ordered_U = []
 
2174
        if self.selection_U_exists:
 
2175
            verts_ordered_U = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U_idx, middle_vertex_idx, closing_vert_U_idx)
 
2176
            verts_ordered_U_indices = [x.index for x in verts_ordered_U]
461
2177
            
462
 
        #### Get an ordered list of the vertices of Selection-U.
463
 
        if selection_U2_exists:
464
 
            verts_ordered_U2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U2_idx, middle_vertex_idx)
 
2178
        #### Get an ordered list of the vertices of Selection-U2.
 
2179
        verts_ordered_U2 = []
 
2180
        if self.selection_U2_exists:
 
2181
            verts_ordered_U2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U2_idx, middle_vertex_idx, closing_vert_U2_idx)
 
2182
            verts_ordered_U2_indices = [x.index for x in verts_ordered_U2]
465
2183
        
466
2184
        #### Get an ordered list of the vertices of Selection-V.
467
 
        if selection_V_exists:
468
 
            verts_ordered_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V_idx, middle_vertex_idx)
469
 
        
470
 
        #### Get an ordered list of the vertices of Selection-U.
471
 
        if selection_V2_exists:
472
 
            verts_ordered_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V2_idx, middle_vertex_idx)
 
2185
        verts_ordered_V = []
 
2186
        if self.selection_V_exists:
 
2187
            verts_ordered_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V_idx, middle_vertex_idx, closing_vert_V_idx)
 
2188
            verts_ordered_V_indices = [x.index for x in verts_ordered_V]
 
2189
        
 
2190
        #### Get an ordered list of the vertices of Selection-V2.
 
2191
        verts_ordered_V2 = []
 
2192
        if self.selection_V2_exists:
 
2193
            verts_ordered_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V2_idx, middle_vertex_idx, closing_vert_V2_idx)
 
2194
            verts_ordered_V2_indices = [x.index for x in verts_ordered_V2]
 
2195
        
 
2196
        
 
2197
        
 
2198
        #### Check if when there are two-not-connected selections both have the same number of verts. If not terminate the script.
 
2199
        if ((self.selection_U2_exists and len(verts_ordered_U) != len(verts_ordered_U2)) or (self.selection_V2_exists and len(verts_ordered_V) != len(verts_ordered_V2))):
 
2200
            # Display a warning.
 
2201
            self.report({'WARNING'}, "Both selections must have the same number of edges")
 
2202
            
 
2203
            self.cleanup_on_interruption()
 
2204
            
 
2205
            self.stopping_errors = True
 
2206
            
 
2207
            return{'CANCELLED'}
 
2208
        
473
2209
        
474
2210
        
475
2211
        #### Calculate edges U proportions.
478
2214
        edges_lengths_U = []
479
2215
        edges_lengths_sum_U = 0
480
2216
        
481
 
        if selection_U_exists:
 
2217
        if self.selection_U_exists:
482
2218
            edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(self.main_object, verts_ordered_U)
 
2219
            
 
2220
        if self.selection_U2_exists:
 
2221
            edges_lengths_U2, edges_lengths_sum_U2 = self.get_chain_length(self.main_object, verts_ordered_U2)
483
2222
        
484
2223
        # Sum selected edges V lengths.
485
2224
        edges_lengths_V = []
486
2225
        edges_lengths_sum_V = 0
487
2226
        
488
 
        if selection_V_exists:
 
2227
        if self.selection_V_exists:
489
2228
            edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(self.main_object, verts_ordered_V)
490
 
        
491
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
492
 
        for i in range(0, int(bpy.context.scene.SURFSK_precision)):
493
 
            bpy.ops.curve.subdivide('INVOKE_REGION_WIN')
494
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
495
 
 
 
2229
            
 
2230
        if self.selection_V2_exists:
 
2231
            edges_lengths_V2, edges_lengths_sum_V2 = self.get_chain_length(self.main_object, verts_ordered_V2)
 
2232
            
 
2233
        
 
2234
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2235
        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = bpy.context.scene.SURFSK_precision)
 
2236
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2237
        
 
2238
        
496
2239
        # Proportions U.
497
2240
        edges_proportions_U = []
498
 
        edges_proportions_U = self.get_edges_proportions(edges_lengths_U, edges_lengths_sum_U, selection_U_exists, bpy.context.scene.SURFSK_edges_U)
 
2241
        edges_proportions_U = self.get_edges_proportions(edges_lengths_U, edges_lengths_sum_U, self.selection_U_exists, self.edges_U)
499
2242
        verts_count_U = len(edges_proportions_U) + 1
500
2243
        
 
2244
        if self.selection_U2_exists:
 
2245
            edges_proportions_U2 = []
 
2246
            edges_proportions_U2 = self.get_edges_proportions(edges_lengths_U2, edges_lengths_sum_U2, self.selection_U2_exists, self.edges_V)
 
2247
            verts_count_U2 = len(edges_proportions_U2) + 1
 
2248
        
501
2249
        # Proportions V.
502
2250
        edges_proportions_V = []
503
 
        edges_proportions_V = self.get_edges_proportions(edges_lengths_V, edges_lengths_sum_V, selection_V_exists, bpy.context.scene.SURFSK_edges_V)
 
2251
        edges_proportions_V = self.get_edges_proportions(edges_lengths_V, edges_lengths_sum_V, self.selection_V_exists, self.edges_V)
504
2252
        verts_count_V = len(edges_proportions_V) + 1
505
2253
        
506
 
        
507
 
        
508
 
        #### Get ordered lists of points on each sketched curve that mimics the proportions of the edges in the vertex selection.
509
 
        sketched_splines = ob_gp_strokes.data.splines
510
 
        sketched_splines_lengths = []
 
2254
        if self.selection_V2_exists:
 
2255
            edges_proportions_V2 = []
 
2256
            edges_proportions_V2 = self.get_edges_proportions(edges_lengths_V2, edges_lengths_sum_V2, self.selection_V2_exists, self.edges_V)
 
2257
            verts_count_V2 = len(edges_proportions_V2) + 1
 
2258
            
 
2259
            
 
2260
        
 
2261
        
 
2262
        
 
2263
        
 
2264
        
 
2265
        
 
2266
        #### Cyclic Follow: simplify sketched curves, make them Cyclic, and complete the actual sketched curves with a "closing segment".
 
2267
        if self.cyclic_follow and not self.selection_V_exists and not ((self.selection_U_exists and not self.selection_U_is_closed) or (self.selection_U2_exists and not self.selection_U2_is_closed)):
 
2268
            simplified_spline_coords = []
 
2269
            simplified_curve = []
 
2270
            ob_simplified_curve = []
 
2271
            splines_first_v_co = []
 
2272
            for i in range(len(self.main_splines.data.splines)):
 
2273
                # Create a curve object for the actual spline "cyclic extension".
 
2274
                simplified_curve.append(bpy.data.curves.new('SURFSKIO_simpl_crv', 'CURVE'))
 
2275
                ob_simplified_curve.append(bpy.data.objects.new('SURFSKIO_simpl_crv', simplified_curve[i]))
 
2276
                bpy.context.scene.objects.link(ob_simplified_curve[i])
 
2277
                
 
2278
                simplified_curve[i].dimensions = "3D"
 
2279
                
 
2280
                spline_coords = []
 
2281
                for bp in self.main_splines.data.splines[i].bezier_points:
 
2282
                    spline_coords.append(bp.co)
 
2283
                
 
2284
                # Simplification.
 
2285
                simplified_spline_coords.append(self.simplify_spline(spline_coords, 5))
 
2286
                
 
2287
                # Get the coordinates of the first vert of the actual spline.
 
2288
                splines_first_v_co.append(simplified_spline_coords[i][0])
 
2289
                
 
2290
                
 
2291
                # Generate the spline.
 
2292
                spline = simplified_curve[i].splines.new('BEZIER')
 
2293
                spline.bezier_points.add(len(simplified_spline_coords[i]) - 1) # less one because one point is added when the spline is created.
 
2294
                for p in range(0, len(simplified_spline_coords[i])):
 
2295
                    spline.bezier_points[p].co = simplified_spline_coords[i][p]
 
2296
                    
 
2297
                
 
2298
                spline.use_cyclic_u = True
 
2299
                
 
2300
                spline_bp_count = len(spline.bezier_points)
 
2301
                
 
2302
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2303
                bpy.data.objects[ob_simplified_curve[i].name].select = True
 
2304
                bpy.context.scene.objects.active = bpy.context.scene.objects[ob_simplified_curve[i].name]
 
2305
                
 
2306
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2307
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
 
2308
                bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
 
2309
                bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2310
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2311
                
 
2312
                
 
2313
                # Select the "closing segment", and subdivide it.
 
2314
                ob_simplified_curve[i].data.splines[0].bezier_points[0].select_control_point = True
 
2315
                ob_simplified_curve[i].data.splines[0].bezier_points[0].select_left_handle = True
 
2316
                ob_simplified_curve[i].data.splines[0].bezier_points[0].select_right_handle = True
 
2317
                
 
2318
                ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_control_point = True
 
2319
                ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_left_handle = True
 
2320
                ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_right_handle = True
 
2321
                
 
2322
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2323
                segments = sqrt((ob_simplified_curve[i].data.splines[0].bezier_points[0].co - ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].co).length / self.average_gp_segment_length)
 
2324
                for t in range(2):
 
2325
                    bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = segments)
 
2326
                
 
2327
                
 
2328
                # Delete the other vertices and make it non-cyclic to keep only the needed verts of the "closing segment".
 
2329
                bpy.ops.curve.select_all(action = 'INVERT')
 
2330
                bpy.ops.curve.delete(type='SELECTED')
 
2331
                ob_simplified_curve[i].data.splines[0].use_cyclic_u = False
 
2332
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2333
                
 
2334
                
 
2335
                # Add the points of the "closing segment" to the original curve from grease pencil stroke.
 
2336
                first_new_index = len(self.main_splines.data.splines[i].bezier_points)
 
2337
                self.main_splines.data.splines[i].bezier_points.add(len(ob_simplified_curve[i].data.splines[0].bezier_points) - 1)
 
2338
                for t in range(1, len(ob_simplified_curve[i].data.splines[0].bezier_points)):
 
2339
                    self.main_splines.data.splines[i].bezier_points[t - 1 + first_new_index].co = ob_simplified_curve[i].data.splines[0].bezier_points[t].co
 
2340
                
 
2341
                
 
2342
                # Delete the temporal curve.
 
2343
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2344
                bpy.data.objects[ob_simplified_curve[i].name].select = True
 
2345
                bpy.context.scene.objects.active = bpy.context.scene.objects[ob_simplified_curve[i].name]
 
2346
                
 
2347
                bpy.ops.object.delete()
 
2348
        
 
2349
        
 
2350
        
 
2351
        #### Get the coords of the points distributed along the sketched strokes, with proportions-U of the first selection.
 
2352
        pts_on_strokes_with_proportions_U = self.distribute_pts(self.main_splines.data.splines, edges_proportions_U)
 
2353
        
511
2354
        sketched_splines_parsed = []
512
 
        for sp_idx in range(0, len(sketched_splines)):
513
 
            # Calculate spline length
514
 
            sketched_splines_lengths.append(0)
515
 
            for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
516
 
                if i == 0:
517
 
                    prev_p = sketched_splines[sp_idx].bezier_points[i]
518
 
                else:
519
 
                    p = sketched_splines[sp_idx].bezier_points[i]
520
 
                    
521
 
                    p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
522
 
                    edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
523
 
                    
524
 
                    sketched_splines_lengths[sp_idx] += edge_length
525
 
                    
526
 
                    prev_p = p
527
 
            
528
 
            # Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
529
 
            sketched_splines_parsed.append([])
530
 
            partial_spline_length = 0
531
 
            related_edge_U = 0
532
 
            edges_proportions_sum_U = 0
533
 
            edges_lengths_sum_U = 0
534
 
            for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
535
 
                if i == 0:
536
 
                    prev_p = sketched_splines[sp_idx].bezier_points[i]
537
 
                    sketched_splines_parsed[sp_idx].append(prev_p.co)
538
 
                elif i != len(sketched_splines[sp_idx].bezier_points) - 1:
539
 
                    p = sketched_splines[sp_idx].bezier_points[i]
540
 
                    
541
 
                    p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
542
 
                    edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
543
 
                    
544
 
                    
545
 
                    if edges_proportions_sum_U + edges_proportions_U[related_edge_U] - ((edges_lengths_sum_U + partial_spline_length + edge_length) / sketched_splines_lengths[sp_idx]) > 0: # comparing proportions to see if the proportion in the selection is found in the spline.
546
 
                        partial_spline_length += edge_length
547
 
                    elif related_edge_U < len(edges_proportions_U) - 1:
548
 
                        sketched_splines_parsed[sp_idx].append(prev_p.co)
549
 
                        
550
 
                        edges_proportions_sum_U += edges_proportions_U[related_edge_U]
551
 
                        related_edge_U += 1
552
 
                        
553
 
                        edges_lengths_sum_U += partial_spline_length
554
 
                        partial_spline_length = edge_length
555
 
                    
556
 
                    prev_p = p
557
 
                else: # last point of the spline for the last edge
558
 
                    p = sketched_splines[sp_idx].bezier_points[len(sketched_splines[sp_idx].bezier_points) - 1]
559
 
                    sketched_splines_parsed[sp_idx].append(p.co)
560
 
        
561
 
        
562
 
        #### If the selection type is "TWO_NOT_CONNECTED" replace the last point of each spline with the points in the "target" selection.
 
2355
        
 
2356
        if self.selection_U2_exists:
 
2357
            # Initialize the multidimensional list with the proportions of all the segments.
 
2358
            proportions_loops_crossing_strokes = []
 
2359
            for i in range(len(pts_on_strokes_with_proportions_U)):
 
2360
                proportions_loops_crossing_strokes.append([])
 
2361
                
 
2362
                for t in range(len(pts_on_strokes_with_proportions_U[0])):
 
2363
                    proportions_loops_crossing_strokes[i].append(None)
 
2364
                    
 
2365
            
 
2366
            # Calculate the proportions of each segment of the loops-U from pts_on_strokes_with_proportions_U.
 
2367
            for lp in range(len(pts_on_strokes_with_proportions_U[0])):
 
2368
                loop_segments_lengths = []
 
2369
                
 
2370
                for st in range(len(pts_on_strokes_with_proportions_U)):
 
2371
                    if st == 0: # When on the first stroke, add the segment from the selection to the dirst stroke.
 
2372
                        loop_segments_lengths.append(((self.main_object.matrix_world * verts_ordered_U[lp].co) - pts_on_strokes_with_proportions_U[0][lp]).length)
 
2373
                        
 
2374
                    if st != len(pts_on_strokes_with_proportions_U) - 1: # For all strokes except for the last, calculate the distance from the actual stroke to the next.
 
2375
                        loop_segments_lengths.append((pts_on_strokes_with_proportions_U[st][lp] - pts_on_strokes_with_proportions_U[st + 1][lp]).length)
 
2376
                    
 
2377
                    if st == len(pts_on_strokes_with_proportions_U) - 1: # When on the last stroke, add the segments from the last stroke to the second selection.
 
2378
                        loop_segments_lengths.append((pts_on_strokes_with_proportions_U[st][lp] - (self.main_object.matrix_world * verts_ordered_U2[lp].co)).length)
 
2379
                
 
2380
                # Calculate full loop length.
 
2381
                loop_seg_lengths_sum = 0
 
2382
                for i in range(len(loop_segments_lengths)):
 
2383
                    loop_seg_lengths_sum += loop_segments_lengths[i]
 
2384
                    
 
2385
                # Fill the multidimensional list with the proportions of all the segments.
 
2386
                for st in range(len(pts_on_strokes_with_proportions_U)):
 
2387
                    proportions_loops_crossing_strokes[st][lp] = loop_segments_lengths[st] / loop_seg_lengths_sum
 
2388
                    
 
2389
            
 
2390
            # Calculate proportions for each stroke.
 
2391
            for st in range(len(pts_on_strokes_with_proportions_U)):
 
2392
                actual_stroke_spline = []
 
2393
                actual_stroke_spline.append(self.main_splines.data.splines[st]) # Needs to be a list for the "distribute_pts" method.
 
2394
                
 
2395
                # Calculate the proportions for the actual stroke.
 
2396
                actual_edges_proportions_U = []
 
2397
                for i in range(len(edges_proportions_U)):
 
2398
                    proportions_sum = 0
 
2399
                    
 
2400
                    # Sum the proportions of this loop up to the actual.
 
2401
                    for t in range(0, st + 1):
 
2402
                        proportions_sum += proportions_loops_crossing_strokes[t][i]
 
2403
                        
 
2404
                    actual_edges_proportions_U.append(edges_proportions_U[i] - ((edges_proportions_U[i] - edges_proportions_U2[i]) * proportions_sum))  # i + 1, because proportions_loops_crossing_strokes refers to loops, and the proportions refer to edges, so we start at the element 1 of proportions_loops_crossing_strokes instead of element 0.
 
2405
                
 
2406
                
 
2407
                points_actual_spline = self.distribute_pts(actual_stroke_spline, actual_edges_proportions_U)
 
2408
                sketched_splines_parsed.append(points_actual_spline[0])
 
2409
                
 
2410
        else:
 
2411
            sketched_splines_parsed = pts_on_strokes_with_proportions_U
 
2412
        
 
2413
        
 
2414
        
 
2415
        #### If the selection type is "TWO_NOT_CONNECTED" replace the points of the last spline with the points in the "target" selection.
563
2416
        if selection_type == "TWO_NOT_CONNECTED":
564
 
            if selection_U2_exists:
 
2417
            if self.selection_U2_exists:
565
2418
                for i in range(0, len(sketched_splines_parsed[len(sketched_splines_parsed) - 1])):
566
2419
                    sketched_splines_parsed[len(sketched_splines_parsed) - 1][i] = self.main_object.matrix_world * verts_ordered_U2[i].co
567
 
                
 
2420
        
568
2421
        
569
2422
        #### Create temporary curves along the "control-points" found on the sketched curves and the mesh selection.
570
 
        mesh_ctrl_pts_name = "SURFSK_ctrl_pts"
 
2423
        mesh_ctrl_pts_name = "SURFSKIO_ctrl_pts"
571
2424
        me = bpy.data.meshes.new(mesh_ctrl_pts_name)
572
2425
        ob_ctrl_pts = bpy.data.objects.new(mesh_ctrl_pts_name, me)
573
2426
        ob_ctrl_pts.data = me
574
2427
        bpy.context.scene.objects.link(ob_ctrl_pts)
575
2428
        
576
2429
        
 
2430
        cyclic_loops_U = []
 
2431
        first_verts = []
 
2432
        second_verts = []
 
2433
        last_verts = []
577
2434
        for i in range(0, verts_count_U):
578
2435
            vert_num_in_spline = 1
579
2436
            
580
 
            if selection_U_exists:
 
2437
            if self.selection_U_exists:
581
2438
                ob_ctrl_pts.data.vertices.add(1)
582
2439
                last_v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
583
2440
                last_v.co = self.main_object.matrix_world * verts_ordered_U[i].co
584
2441
                
585
2442
                vert_num_in_spline += 1
586
 
                
587
 
            for sp in sketched_splines_parsed:
 
2443
            
 
2444
            
 
2445
            for t in range(0, len(sketched_splines_parsed)):
588
2446
                ob_ctrl_pts.data.vertices.add(1)
589
2447
                v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
590
 
                v.co = sp[i]
 
2448
                v.co = sketched_splines_parsed[t][i]
 
2449
                
591
2450
                
592
2451
                if vert_num_in_spline > 1:
593
2452
                    ob_ctrl_pts.data.edges.add(1)
594
2453
                    ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[0] = len(ob_ctrl_pts.data.vertices) - 2
595
2454
                    ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[1] = len(ob_ctrl_pts.data.vertices) - 1
596
 
 
 
2455
                
 
2456
                if t == 0:
 
2457
                    first_verts.append(v.index)
 
2458
                    
 
2459
                if t == 1:
 
2460
                    second_verts.append(v.index)
 
2461
                
 
2462
                if t == len(sketched_splines_parsed) - 1:
 
2463
                    last_verts.append(v.index)
 
2464
                    
 
2465
                    
597
2466
                last_v = v
598
2467
                
599
2468
                vert_num_in_spline += 1
600
2469
        
 
2470
        
601
2471
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
602
 
        myobject = bpy.data.objects[ob_ctrl_pts.name]
603
 
        bpy.context.scene.objects.active = myobject
604
 
        myobject.select = True
605
 
#        bpy.ops.object.select_name('INVOKE_REGION_WIN', name = ob_ctrl_pts.name)
 
2472
        bpy.data.objects[ob_ctrl_pts.name].select = True
606
2473
        bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
607
2474
        
 
2475
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2476
        bpy.ops.mesh.select_all(action='DESELECT')
 
2477
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2478
        
 
2479
        
 
2480
        #### Determine which loops-U will be "Cyclic".
 
2481
        for i in range(0, len(first_verts)):
 
2482
            if self.automatic_join and not self.cyclic_cross and selection_type != "TWO_CONNECTED" and len(self.main_splines.data.splines) >= 3: # When there is Cyclic Cross there is no need of Automatic Join, (and there are at least three strokes).
 
2483
                v = ob_ctrl_pts.data.vertices
 
2484
                
 
2485
                first_point_co = v[first_verts[i]].co
 
2486
                second_point_co = v[second_verts[i]].co
 
2487
                last_point_co = v[last_verts[i]].co
 
2488
                
 
2489
                # Coordinates of the point in the center of both the first and last verts.
 
2490
                verts_center_co = [(first_point_co[0] + last_point_co[0]) / 2, (first_point_co[1] + last_point_co[1]) / 2, (first_point_co[2] + last_point_co[2]) / 2]
 
2491
                
 
2492
                vec_A = second_point_co - first_point_co
 
2493
                vec_B = second_point_co - mathutils.Vector(verts_center_co)
 
2494
                
 
2495
                
 
2496
                # Calculate the length of the first segment of the loop, and the length it would have after moving the first vert to the middle position between first and last.
 
2497
                length_original = (second_point_co - first_point_co).length
 
2498
                length_target = (second_point_co - mathutils.Vector(verts_center_co)).length
 
2499
                
 
2500
                angle = vec_A.angle(vec_B) / math.pi
 
2501
                
 
2502
                
 
2503
                if length_target <= length_original * 1.03 * self.join_stretch_factor and angle <= 0.008 * self.join_stretch_factor and not self.selection_U_exists: # If the target length doesn't stretch too much, and the its angle doesn't change to much either.
 
2504
                    cyclic_loops_U.append(True)
 
2505
                    
 
2506
                    # Move the first vert to the center coordinates.
 
2507
                    ob_ctrl_pts.data.vertices[first_verts[i]].co = verts_center_co
 
2508
                    
 
2509
                    # Select the last verts from Cyclic loops, for later deletion all at once.
 
2510
                    v[last_verts[i]].select = True
 
2511
                    
 
2512
                else:
 
2513
                    cyclic_loops_U.append(False)
 
2514
                    
 
2515
            else:
 
2516
                if self.cyclic_cross and not self.selection_U_exists and not ((self.selection_V_exists and not self.selection_V_is_closed) or (self.selection_V2_exists and not self.selection_V2_is_closed)): # If "Cyclic Cross" is active then "all" crossing curves become cyclic.
 
2517
                    cyclic_loops_U.append(True)
 
2518
                else:
 
2519
                    cyclic_loops_U.append(False)
 
2520
        
 
2521
        # The cyclic_loops_U list needs to be reversed.
 
2522
        cyclic_loops_U.reverse()
 
2523
        
 
2524
        # Delete the previously selected (last_)verts.
 
2525
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2526
        bpy.ops.mesh.delete('INVOKE_REGION_WIN', type='VERT')
 
2527
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
608
2528
        
609
2529
        # Create curves from control points.
610
2530
        bpy.ops.object.convert('INVOKE_REGION_WIN', target='CURVE', keep_original=False)
612
2532
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
613
2533
        bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type='BEZIER')
614
2534
        bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
615
 
        for i in range(0, int(bpy.context.scene.SURFSK_precision)):
616
 
            bpy.ops.curve.subdivide('INVOKE_REGION_WIN')
 
2535
        
 
2536
        # Make Cyclic the splines designated as Cyclic.
 
2537
        for i in range(0, len(cyclic_loops_U)):
 
2538
            ob_curves_surf.data.splines[i].use_cyclic_u = cyclic_loops_U[i]
 
2539
        
 
2540
        
 
2541
        #### Get the coords of all points on first loop-U, for later comparison with its subdivided version, to know which points of the loops-U are crossed by the original strokes. The indices wiil be the same for the other loops-U.
 
2542
        if self.loops_on_strokes:
 
2543
            coords_loops_U_control_points = []
 
2544
            for p in ob_ctrl_pts.data.splines[0].bezier_points:
 
2545
                coords_loops_U_control_points.append(["%.4f" % p.co[0], "%.4f" % p.co[1], "%.4f" % p.co[2]])
 
2546
            
 
2547
            tuple(coords_loops_U_control_points)
 
2548
        
 
2549
        
 
2550
        # Calculate number of edges-V in case option "Loops on strokes" is active or inactive.
 
2551
        if self.loops_on_strokes and not self.selection_V_exists:
 
2552
                edges_V_count = len(self.main_splines.data.splines) * self.edges_V
 
2553
        else:
 
2554
            edges_V_count = len(edges_proportions_V)
 
2555
            
 
2556
        
 
2557
        # The Follow precision will vary depending on the number of Follow face-loops.
 
2558
        precision_multiplier = round(2 + (edges_V_count / 15))
 
2559
        
 
2560
        curve_cuts = bpy.context.scene.SURFSK_precision * precision_multiplier
 
2561
        
 
2562
        # Subdivide the curves.
 
2563
        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = curve_cuts)
 
2564
        
 
2565
        # The verts position shifting that happens with splines subdivision. For later reorder splines points.
 
2566
        verts_position_shift = curve_cuts + 1
 
2567
        
617
2568
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
618
2569
        
619
2570
        
620
 
        # Calculate the length of each final surface spline.
621
 
        surface_splines = ob_curves_surf.data.splines
622
 
        surface_splines_lengths = []
 
2571
        # Reorder coordinates of the points of each spline to put the first point of the spline starting at the position it was the first point before sudividing the curve. And make a new curve object per spline (to handle memory better later).
 
2572
        splines_U_objects = []
 
2573
        for i in range(len(ob_curves_surf.data.splines)):
 
2574
            spline_U_curve = bpy.data.curves.new('SURFSKIO_spline_U_' + str(i), 'CURVE')
 
2575
            ob_spline_U = bpy.data.objects.new('SURFSKIO_spline_U_' + str(i), spline_U_curve)
 
2576
            bpy.context.scene.objects.link(ob_spline_U)
 
2577
            
 
2578
            spline_U_curve.dimensions = "3D"
 
2579
            
 
2580
            
 
2581
            # Add points to the spline in the new curve object.
 
2582
            ob_spline_U.data.splines.new('BEZIER')
 
2583
            for t in range(len(ob_curves_surf.data.splines[i].bezier_points)):
 
2584
                if cyclic_loops_U[i] == True and not self.selection_U_exists: # If the loop is cyclic.
 
2585
                    if t + verts_position_shift <= len(ob_curves_surf.data.splines[i].bezier_points) - 1:
 
2586
                        point_index = t + verts_position_shift
 
2587
                    else:
 
2588
                        point_index = t + verts_position_shift - len(ob_curves_surf.data.splines[i].bezier_points)
 
2589
                else:
 
2590
                    point_index = t
 
2591
                
 
2592
                if t > 0: # to avoid adding the first point since it's added when the spline is created.
 
2593
                    ob_spline_U.data.splines[0].bezier_points.add(1)
 
2594
                ob_spline_U.data.splines[0].bezier_points[t].co = ob_curves_surf.data.splines[i].bezier_points[point_index].co
 
2595
            
 
2596
            
 
2597
            if cyclic_loops_U[i] == True and not self.selection_U_exists: # If the loop is cyclic.
 
2598
                # Add a last point at the same location as the first one.
 
2599
                ob_spline_U.data.splines[0].bezier_points.add(1)
 
2600
                ob_spline_U.data.splines[0].bezier_points[len(ob_spline_U.data.splines[0].bezier_points) - 1].co = ob_spline_U.data.splines[0].bezier_points[0].co
 
2601
            else:
 
2602
                ob_spline_U.data.splines[0].use_cyclic_u = False
 
2603
            
 
2604
            
 
2605
            splines_U_objects.append(ob_spline_U)
 
2606
            
 
2607
            
 
2608
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2609
            bpy.data.objects[ob_spline_U.name].select = True
 
2610
            bpy.context.scene.objects.active = bpy.data.objects[ob_spline_U.name]
 
2611
            
 
2612
        
 
2613
        
 
2614
        #### When option "Loops on strokes" is active each "Cross" loop will have its own proportions according to where the original strokes "touch" them.
 
2615
        if self.loops_on_strokes:
 
2616
            # Get the indices of points where the original strokes "touch" loops-U.
 
2617
            points_U_crossed_by_strokes = []
 
2618
            for i in range(len(splines_U_objects[0].data.splines[0].bezier_points)):
 
2619
                bp = splines_U_objects[0].data.splines[0].bezier_points[i]
 
2620
                if ["%.4f" % bp.co[0], "%.4f" % bp.co[1], "%.4f" % bp.co[2]] in coords_loops_U_control_points:
 
2621
                    points_U_crossed_by_strokes.append(i)
 
2622
            
 
2623
            # Make a dictionary with the number of the edge, in the selected chain V, corresponding to each stroke.
 
2624
            edge_order_number_for_splines = {}
 
2625
            if self.selection_V_exists:
 
2626
                # For two-connected selections add a first hypothetic stroke at the begining.
 
2627
                if selection_type == "TWO_CONNECTED":
 
2628
                    edge_order_number_for_splines[0] = 0
 
2629
                        
 
2630
                        
 
2631
                for i in range(len(self.main_splines.data.splines)):
 
2632
                    sp = self.main_splines.data.splines[i]
 
2633
                    v_idx, dist_temp = self.shortest_distance(self.main_object, sp.bezier_points[0].co, verts_ordered_V_indices)
 
2634
                    
 
2635
                    edge_idx_in_chain = verts_ordered_V_indices.index(v_idx) # Get the position (edges count) of the vert v_idx in the selected chain V.
 
2636
                    
 
2637
                    # For two-connected selections the strokes go after the hypothetic stroke added before, so the index adds one per spline.
 
2638
                    if selection_type == "TWO_CONNECTED":
 
2639
                        spline_number = i + 1
 
2640
                    else:
 
2641
                        spline_number = i
 
2642
                    
 
2643
                    edge_order_number_for_splines[spline_number] = edge_idx_in_chain
 
2644
                    
 
2645
                    
 
2646
                    # Get the first and last verts indices for later comparison.
 
2647
                    if i == 0:
 
2648
                        first_v_idx = v_idx
 
2649
                    elif i == len(self.main_splines.data.splines) - 1:
 
2650
                        last_v_idx = v_idx
 
2651
                        
 
2652
                
 
2653
                if self.selection_V_is_closed:
 
2654
                    # If there is no last stroke on the last vertex (same as first vertex), add a hypothetic spline at last vert order.
 
2655
                    if first_v_idx != last_v_idx:
 
2656
                        edge_order_number_for_splines[(len(self.main_splines.data.splines) - 1) + 1] = len(verts_ordered_V_indices) - 1
 
2657
                    else:
 
2658
                        if self.cyclic_cross:
 
2659
                            edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = len(verts_ordered_V_indices) - 2
 
2660
                            edge_order_number_for_splines[(len(self.main_splines.data.splines) - 1) + 1] = len(verts_ordered_V_indices) - 1
 
2661
                        else:
 
2662
                            edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = len(verts_ordered_V_indices) - 1
 
2663
                        
 
2664
                        
 
2665
        
 
2666
        #### Get the coords of the points distributed along the "crossing curves", with appropriate proportions-V.
623
2667
        surface_splines_parsed = []
624
 
        for sp_idx in range(0, len(surface_splines)):
625
 
            # Calculate spline length
626
 
            surface_splines_lengths.append(0)
627
 
            for i in range(0, len(surface_splines[sp_idx].bezier_points)):
628
 
                if i == 0:
629
 
                    prev_p = surface_splines[sp_idx].bezier_points[i]
 
2668
        for i in range(len(splines_U_objects)):
 
2669
            sp_ob = splines_U_objects[i]
 
2670
            # If "Loops on strokes" option is active, calculate the proportions for each loop-U.
 
2671
            if self.loops_on_strokes:
 
2672
                # Segments distances from stroke to stroke.
 
2673
                dist = 0
 
2674
                full_dist = 0
 
2675
                segments_distances = []
 
2676
                for t in range(len(sp_ob.data.splines[0].bezier_points)):
 
2677
                    bp = sp_ob.data.splines[0].bezier_points[t]
 
2678
                    
 
2679
                    if t == 0:
 
2680
                        last_p = bp.co
 
2681
                    else:
 
2682
                        actual_p = bp.co
 
2683
                        dist += (last_p - actual_p).length
 
2684
                        
 
2685
                        if t in points_U_crossed_by_strokes:
 
2686
                            segments_distances.append(dist)
 
2687
                            full_dist += dist
 
2688
                            
 
2689
                            dist = 0
 
2690
                        
 
2691
                        last_p = actual_p
 
2692
                
 
2693
                # Calculate Proportions.
 
2694
                used_edges_proportions_V = []
 
2695
                for t in range(len(segments_distances)):
 
2696
                    if self.selection_V_exists:
 
2697
                        if t == 0:
 
2698
                            order_number_last_stroke = 0
 
2699
                        
 
2700
                        segment_edges_length_V = 0
 
2701
                        segment_edges_length_V2 = 0
 
2702
                        for order in range(order_number_last_stroke, edge_order_number_for_splines[t + 1]):
 
2703
                            segment_edges_length_V += edges_lengths_V[order]
 
2704
                            if self.selection_V2_exists:
 
2705
                                segment_edges_length_V2 += edges_lengths_V2[order]
 
2706
                        
 
2707
                        
 
2708
                        for order in range(order_number_last_stroke, edge_order_number_for_splines[t + 1]):
 
2709
                            # Calculate each "sub-segment" (the ones between each stroke) length.
 
2710
                            if self.selection_V2_exists:
 
2711
                                proportion_sub_seg = (edges_lengths_V2[order] - ((edges_lengths_V2[order] - edges_lengths_V[order]) / len(splines_U_objects) * i)) / (segment_edges_length_V2 - (segment_edges_length_V2 - segment_edges_length_V) / len(splines_U_objects) * i)
 
2712
                                sub_seg_dist = segments_distances[t] * proportion_sub_seg
 
2713
                            else:
 
2714
                                proportion_sub_seg = edges_lengths_V[order] / segment_edges_length_V
 
2715
                                sub_seg_dist = segments_distances[t] * proportion_sub_seg
 
2716
                                
 
2717
                            used_edges_proportions_V.append(sub_seg_dist / full_dist)
 
2718
                            
 
2719
                        order_number_last_stroke = edge_order_number_for_splines[t + 1]
 
2720
                        
 
2721
                    else:
 
2722
                        for c in range(self.edges_V):
 
2723
                            # Calculate each "sub-segment" (the ones between each stroke) length.
 
2724
                            sub_seg_dist = segments_distances[t] / self.edges_V  
 
2725
                            used_edges_proportions_V.append(sub_seg_dist / full_dist)
 
2726
                
 
2727
                actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
 
2728
                surface_splines_parsed.append(actual_spline[0])
 
2729
                
 
2730
            else:
 
2731
                if self.selection_V2_exists:
 
2732
                    used_edges_proportions_V = []
 
2733
                    for p in range(len(edges_proportions_V)):
 
2734
                        used_edges_proportions_V.append(edges_proportions_V2[p] - ((edges_proportions_V2[p] - edges_proportions_V[p]) / len(splines_U_objects) * i))
630
2735
                else:
631
 
                    p = surface_splines[sp_idx].bezier_points[i]
632
 
                    
633
 
                    edge_length = self.pts_distance(prev_p.co, p.co)
634
 
                    
635
 
                    surface_splines_lengths[sp_idx] += edge_length
636
 
                    
637
 
                    prev_p = p
638
 
        
639
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
640
 
        for i in range(0, int(bpy.context.scene.SURFSK_precision)):
641
 
            bpy.ops.curve.subdivide('INVOKE_REGION_WIN')
642
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
643
 
 
644
 
        for sp_idx in range(0, len(surface_splines)):
645
 
            # Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
646
 
            surface_splines_parsed.append([])
647
 
            partial_spline_length = 0
648
 
            related_edge_V = 0
649
 
            edges_proportions_sum_V = 0
650
 
            edges_lengths_sum_V = 0
651
 
            for i in range(0, len(surface_splines[sp_idx].bezier_points)):
652
 
                if i == 0:
653
 
                    prev_p = surface_splines[sp_idx].bezier_points[i]
654
 
                    surface_splines_parsed[sp_idx].append(prev_p.co)
655
 
                elif i != len(surface_splines[sp_idx].bezier_points) - 1:
656
 
                    p = surface_splines[sp_idx].bezier_points[i]
657
 
                    
658
 
                    edge_length = self.pts_distance(prev_p.co, p.co)
659
 
                    
660
 
                    if edges_proportions_sum_V + edges_proportions_V[related_edge_V] - ((edges_lengths_sum_V + partial_spline_length + edge_length) / surface_splines_lengths[sp_idx]) > 0: # comparing proportions to see if the proportion in the selection is found in the spline.
661
 
                        partial_spline_length += edge_length
662
 
                    elif related_edge_V < len(edges_proportions_V) - 1:
663
 
                        surface_splines_parsed[sp_idx].append(prev_p.co)
664
 
                        
665
 
                        edges_proportions_sum_V += edges_proportions_V[related_edge_V]
666
 
                        related_edge_V += 1
667
 
                        
668
 
                        edges_lengths_sum_V += partial_spline_length
669
 
                        partial_spline_length = edge_length
670
 
                    
671
 
                    prev_p = p
672
 
                else: # last point of the spline for the last edge
673
 
                    p = surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1]
674
 
                    surface_splines_parsed[sp_idx].append(p.co)
675
 
        
676
 
        # Set the first and last verts of each spline to the locations of the respective verts in the selections.
677
 
        if selection_V_exists:
 
2736
                    used_edges_proportions_V = edges_proportions_V
 
2737
                
 
2738
                actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
 
2739
                surface_splines_parsed.append(actual_spline[0])
 
2740
        
 
2741
        
 
2742
        
 
2743
        
 
2744
        # Set the verts of the first and last splines to the locations of the respective verts in the selections.
 
2745
        if self.selection_V_exists:
678
2746
            for i in range(0, len(surface_splines_parsed[0])):
679
2747
                surface_splines_parsed[len(surface_splines_parsed) - 1][i] = self.main_object.matrix_world * verts_ordered_V[i].co
680
2748
        
681
2749
        if selection_type == "TWO_NOT_CONNECTED":
682
 
            if selection_V2_exists:
 
2750
            if self.selection_V2_exists:
683
2751
                for i in range(0, len(surface_splines_parsed[0])):
684
2752
                    surface_splines_parsed[0][i] = self.main_object.matrix_world * verts_ordered_V2[i].co
685
2753
        
686
2754
        
 
2755
        
 
2756
        
 
2757
        # When "Automatic join" option is active (and the selection type is not "TWO_CONNECTED"), merge the verts of the tips of the loops when they are "near enough".
 
2758
        if self.automatic_join and selection_type != "TWO_CONNECTED":
 
2759
            #### Join the tips of "Follow" loops that are near enough and must be "closed".
 
2760
            if not self.selection_V_exists and len(edges_proportions_U) >= 3:
 
2761
                for i in range(len(surface_splines_parsed[0])):
 
2762
                    sp = surface_splines_parsed
 
2763
                    loop_segment_dist = (sp[0][i] - sp[1][i]).length
 
2764
                    full_loop_dist = loop_segment_dist * self.edges_U
 
2765
                    
 
2766
                    verts_middle_position_co = [(sp[0][i][0] + sp[len(sp) - 1][i][0]) / 2, (sp[0][i][1] + sp[len(sp) - 1][i][1]) / 2, (sp[0][i][2] + sp[len(sp) - 1][i][2]) / 2]
 
2767
                    
 
2768
                    points_original = []
 
2769
                    points_original.append(sp[1][i])
 
2770
                    points_original.append(sp[0][i])
 
2771
                    
 
2772
                    points_target = []
 
2773
                    points_target.append(sp[1][i])
 
2774
                    points_target.append(mathutils.Vector(verts_middle_position_co))
 
2775
                    
 
2776
                    vec_A = points_original[0] - points_original[1]
 
2777
                    vec_B = points_target[0] - points_target[1]
 
2778
                    
 
2779
                    
 
2780
                    angle = vec_A.angle(vec_B) / math.pi
 
2781
                    
 
2782
                    edge_new_length = (mathutils.Vector(verts_middle_position_co) - sp[1][i]).length
 
2783
                    
 
2784
                    if edge_new_length <= loop_segment_dist * 1.5 * self.join_stretch_factor and angle < 0.25 * self.join_stretch_factor: # If after moving the verts to the middle point, the segment doesn't stretch too much.
 
2785
                        if not (self.selection_U_exists and i == 0) and not (self.selection_U2_exists and i == len(surface_splines_parsed[0]) - 1): # Avoid joining when the actual loop must be merged with the original mesh.
 
2786
                            # Change the coords of both verts to the middle position.
 
2787
                            surface_splines_parsed[0][i] = verts_middle_position_co
 
2788
                            surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_middle_position_co
 
2789
                    
 
2790
        
 
2791
        
687
2792
        #### Delete object with control points and object from grease pencil convertion.
688
2793
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
689
 
        myobject = bpy.data.objects[ob_ctrl_pts.name]
690
 
        bpy.context.scene.objects.active = myobject
691
 
        myobject.select = True
692
 
#        bpy.ops.object.select_name('INVOKE_REGION_WIN', name = ob_ctrl_pts.name)
 
2794
        bpy.data.objects[ob_ctrl_pts.name].select = True
693
2795
        bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
694
 
        bpy.ops.object.delete()
695
 
        
696
 
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
697
 
        myobject = bpy.data.objects[ob_gp_strokes.name]
698
 
        bpy.context.scene.objects.active = myobject
699
 
        myobject.select = True
700
 
#        bpy.ops.object.select_name('INVOKE_REGION_WIN', name = ob_gp_strokes.name)
701
 
        bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
702
 
        bpy.ops.object.delete()
703
 
            
 
2796
        
 
2797
        bpy.ops.object.delete()
 
2798
        
 
2799
        
 
2800
        for sp_ob in splines_U_objects:
 
2801
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2802
            bpy.data.objects[sp_ob.name].select = True
 
2803
            bpy.context.scene.objects.active = bpy.data.objects[sp_ob.name]
 
2804
            
 
2805
            bpy.ops.object.delete()
 
2806
            
 
2807
        
704
2808
        
705
2809
        
706
2810
        #### Generate surface.
721
2825
        
722
2826
        
723
2827
        # Build the mesh.
724
 
        surf_me_name = "SURFSK_surface"
 
2828
        surf_me_name = "SURFSKIO_surface"
725
2829
        me_surf = bpy.data.meshes.new(surf_me_name)
726
2830
        
727
2831
        me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
732
2836
        bpy.context.scene.objects.link(ob_surface)
733
2837
        
734
2838
        
 
2839
        # Select all the "unselected but participating" verts, from closed selection or double selections with middle-vertex, for later join with remove doubles.
 
2840
        for v_idx in single_unselected_verts:
 
2841
            self.main_object.data.vertices[v_idx].select = True
 
2842
        
 
2843
        
735
2844
        #### Join the new mesh to the main object.
736
2845
        ob_surface.select = True
737
2846
        self.main_object.select = True
738
2847
        bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
2848
        
739
2849
        bpy.ops.object.join('INVOKE_REGION_WIN')
 
2850
        
740
2851
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
741
 
        bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', mergedist=0.0001)
 
2852
        
 
2853
        bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=0.0001)
742
2854
        bpy.ops.mesh.normals_make_consistent('INVOKE_REGION_WIN', inside=False)
743
2855
        bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
744
2856
        
745
2857
        
746
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
747
 
        
748
 
        
749
 
        #### Delete grease pencil strokes
 
2858
        
 
2859
        return{'FINISHED'}
 
2860
        
 
2861
        
 
2862
        
 
2863
    def execute(self, context):
 
2864
        bpy.context.user_preferences.edit.use_global_undo = False
 
2865
        
 
2866
        if not self.is_fill_faces:
 
2867
            bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
 
2868
            
 
2869
            # Build splines from the "last saved splines".
 
2870
            last_saved_curve = bpy.data.curves.new('SURFSKIO_last_crv', 'CURVE')
 
2871
            self.main_splines = bpy.data.objects.new('SURFSKIO_last_crv', last_saved_curve)
 
2872
            bpy.context.scene.objects.link(self.main_splines)
 
2873
            
 
2874
            last_saved_curve.dimensions = "3D"
 
2875
            
 
2876
            for sp in self.last_strokes_splines_coords:
 
2877
                spline = self.main_splines.data.splines.new('BEZIER')
 
2878
                spline.bezier_points.add(len(sp) - 1) # less one because one point is added when the spline is created.
 
2879
                for p in range(0, len(sp)):
 
2880
                    spline.bezier_points[p].co = [sp[p][0], sp[p][1], sp[p][2]]
 
2881
            
 
2882
            
 
2883
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2884
            
 
2885
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2886
            bpy.data.objects[self.main_splines.name].select = True
 
2887
            bpy.context.scene.objects.active = bpy.data.objects[self.main_splines.name]
 
2888
            
 
2889
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2890
            
 
2891
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
 
2892
            bpy.ops.curve.handle_type_set(type='VECTOR') # Important to make it vector first and then automatic, otherwise the tips handles get too big and distort the shrinkwrap results later.
 
2893
            bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
 
2894
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2895
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2896
            
 
2897
            
 
2898
            self.main_splines.name = "SURFSKIO_temp_strokes"
 
2899
            
 
2900
            
 
2901
            if self.is_crosshatch:
 
2902
                strokes_for_crosshatch = True
 
2903
                strokes_for_rectangular_surface = False
 
2904
            else:
 
2905
                strokes_for_rectangular_surface = True
 
2906
                strokes_for_crosshatch = False
 
2907
            
 
2908
            
 
2909
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2910
            bpy.data.objects[self.main_object.name].select = True
 
2911
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
2912
            
 
2913
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2914
            
 
2915
            
 
2916
            if strokes_for_rectangular_surface:
 
2917
                self.rectangular_surface()
 
2918
            elif strokes_for_crosshatch:
 
2919
                self.crosshatch_surface_execute()
 
2920
            
 
2921
            
 
2922
            #### Delete main splines
 
2923
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2924
            
 
2925
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2926
            bpy.data.objects[self.main_splines.name].select = True
 
2927
            bpy.context.scene.objects.active = bpy.data.objects[self.main_splines.name]
 
2928
            
 
2929
            bpy.ops.object.delete()
 
2930
            
 
2931
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
2932
            bpy.data.objects[self.main_object.name].select = True
 
2933
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
2934
            
 
2935
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2936
            
 
2937
            
 
2938
            bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
 
2939
            
 
2940
        return{'FINISHED'}
 
2941
        
 
2942
        
 
2943
        
 
2944
    def invoke(self, context, event):
 
2945
        self.initial_global_undo_state = bpy.context.user_preferences.edit.use_global_undo
 
2946
        
 
2947
        self.main_object = bpy.context.scene.objects.active
 
2948
        self.main_object_selected_verts_count = int(self.main_object.data.total_vert_sel)
 
2949
        
 
2950
        
 
2951
        bpy.context.user_preferences.edit.use_global_undo = False
 
2952
        
 
2953
        
 
2954
        bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
 
2955
        
 
2956
        # Out Edit mode and In again to make sure the actual mesh selections are being taken.
 
2957
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2958
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2959
        
 
2960
        
 
2961
        
 
2962
        self.cyclic_cross = bpy.context.scene.SURFSK_cyclic_cross
 
2963
        self.cyclic_follow = bpy.context.scene.SURFSK_cyclic_follow
 
2964
        self.automatic_join = bpy.context.scene.SURFSK_automatic_join
 
2965
        self.loops_on_strokes = bpy.context.scene.SURFSK_loops_on_strokes
 
2966
        self.keep_strokes = bpy.context.scene.SURFSK_keep_strokes
 
2967
        
 
2968
        self.edges_U = 10
 
2969
        
 
2970
        if self.loops_on_strokes:
 
2971
            self.edges_V = 3
 
2972
        else:
 
2973
            self.edges_V = 10
 
2974
        
 
2975
        self.is_fill_faces = False
 
2976
        
 
2977
        self.stopping_errors = False
 
2978
        
 
2979
        self.last_strokes_splines_coords = []
 
2980
        
 
2981
        
 
2982
        #### Determine the type of the strokes.
 
2983
        self.strokes_type = get_strokes_type(self.main_object)
 
2984
        
 
2985
        #### Check if it will be used grease pencil strokes or curves.
 
2986
        if self.strokes_type == "GP_STROKES" or self.strokes_type == "EXTERNAL_CURVE": # If there are strokes to be used.
 
2987
            if self.strokes_type == "GP_STROKES":
 
2988
                # Convert grease pencil strokes to curve.
 
2989
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
2990
                bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
 
2991
                # XXX gpencil.convert now keep org object as active/selected, *not* newly created curve!
 
2992
                # XXX This is far from perfect, but should work in most cases...
 
2993
#                self.original_curve = bpy.context.object
 
2994
                for ob in bpy.context.selected_objects:
 
2995
                    if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
 
2996
                        self.original_curve = ob
 
2997
                self.using_external_curves = False
 
2998
            elif self.strokes_type == "EXTERNAL_CURVE":
 
2999
                for ob in bpy.context.selected_objects:
 
3000
                    if ob != bpy.context.scene.objects.active:
 
3001
                        self.original_curve = ob
 
3002
                self.using_external_curves = True
 
3003
                
 
3004
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3005
            
 
3006
            
 
3007
            #### Make sure there are no objects left from erroneous executions of this operator, with the reserved names used here.
 
3008
            for o in bpy.data.objects:
 
3009
                if o.name.find("SURFSKIO_") != -1:
 
3010
                    bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3011
                    bpy.data.objects[o.name].select = True
 
3012
                    bpy.context.scene.objects.active = bpy.data.objects[o.name]
 
3013
                    
 
3014
                    bpy.ops.object.delete()
 
3015
            
 
3016
            
 
3017
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3018
            bpy.data.objects[self.original_curve.name].select = True
 
3019
            bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
 
3020
            
 
3021
            bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
3022
            
 
3023
            
 
3024
            self.temporary_curve = bpy.context.scene.objects.active
 
3025
            
 
3026
            
 
3027
            # Deselect all points of the curve
 
3028
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3029
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3030
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3031
            
 
3032
            
 
3033
            
 
3034
            # Delete splines with only a single isolated point.
 
3035
            for i in range(len(self.temporary_curve.data.splines)):
 
3036
                sp = self.temporary_curve.data.splines[i]
 
3037
                
 
3038
                if len(sp.bezier_points) == 1:
 
3039
                    sp.bezier_points[0].select_control_point = True
 
3040
            
 
3041
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3042
            bpy.ops.curve.delete(type='SELECTED')
 
3043
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3044
            
 
3045
            
 
3046
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3047
            bpy.data.objects[self.temporary_curve.name].select = True
 
3048
            bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
 
3049
            
 
3050
            #### Set a minimum number of points for crosshatch
 
3051
            minimum_points_num = 15
 
3052
            
 
3053
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3054
            # 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.
 
3055
            for i in range(len(self.temporary_curve.data.splines)):
 
3056
                sp = self.temporary_curve.data.splines[i]
 
3057
                
 
3058
                if len(sp.bezier_points) < minimum_points_num:
 
3059
                    for bp in sp.bezier_points:
 
3060
                        bp.select_control_point = True
 
3061
                    
 
3062
                    if (len(sp.bezier_points) - 1) != 0:
 
3063
                        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.
 
3064
                    else:
 
3065
                        subdivide_cuts = 0
 
3066
                        
 
3067
                    
 
3068
                    bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
 
3069
                    bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3070
                    
 
3071
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3072
            
 
3073
            
 
3074
            
 
3075
            # Detect if the strokes are a crosshatch and do it if it is.
 
3076
            self.crosshatch_surface_invoke(self.temporary_curve)
 
3077
            
 
3078
            
 
3079
            
 
3080
            if not self.is_crosshatch:
 
3081
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3082
                bpy.data.objects[self.temporary_curve.name].select = True
 
3083
                bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
 
3084
                
 
3085
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3086
                
 
3087
                #### Set a minimum number of points for rectangular surfaces.
 
3088
                minimum_points_num = 60
 
3089
                
 
3090
                # 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.
 
3091
                for i in range(len(self.temporary_curve.data.splines)):
 
3092
                    sp = self.temporary_curve.data.splines[i]
 
3093
                    
 
3094
                    if len(sp.bezier_points) < minimum_points_num:
 
3095
                        for bp in sp.bezier_points:
 
3096
                            bp.select_control_point = True
 
3097
                        
 
3098
                        if (len(sp.bezier_points) - 1) != 0:
 
3099
                            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.
 
3100
                        else:
 
3101
                            subdivide_cuts = 0
 
3102
                            
 
3103
                        
 
3104
                        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
 
3105
                        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3106
                        
 
3107
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3108
                
 
3109
            
 
3110
            
 
3111
            
 
3112
            # Save coordinates of the actual strokes (as the "last saved splines").
 
3113
            for sp_idx in range(len(self.temporary_curve.data.splines)):
 
3114
                self.last_strokes_splines_coords.append([])
 
3115
                for bp_idx in range(len(self.temporary_curve.data.splines[sp_idx].bezier_points)):
 
3116
                    coords = self.temporary_curve.matrix_world * self.temporary_curve.data.splines[sp_idx].bezier_points[bp_idx].co
 
3117
                    self.last_strokes_splines_coords[sp_idx].append([coords[0], coords[1], coords[2]])
 
3118
            
 
3119
            
 
3120
            # Check for cyclic splines, put the first and last points in the middle of their actual positions.
 
3121
            for sp_idx in range(len(self.temporary_curve.data.splines)):
 
3122
                if self.temporary_curve.data.splines[sp_idx].use_cyclic_u == True:
 
3123
                    first_p_co = self.last_strokes_splines_coords[sp_idx][0]
 
3124
                    last_p_co = self.last_strokes_splines_coords[sp_idx][len(self.last_strokes_splines_coords[sp_idx]) - 1]
 
3125
                    
 
3126
                    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]
 
3127
                    
 
3128
                    self.last_strokes_splines_coords[sp_idx][0] = target_co
 
3129
                    self.last_strokes_splines_coords[sp_idx][len(self.last_strokes_splines_coords[sp_idx]) - 1] = target_co
 
3130
            
 
3131
            tuple(self.last_strokes_splines_coords)
 
3132
            
 
3133
            
 
3134
            
 
3135
            # 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".
 
3136
            segments_lengths_sum = 0
 
3137
            segments_count = 0
 
3138
            random_spline = self.temporary_curve.data.splines[0].bezier_points
 
3139
            for i in range(0, len(random_spline)):
 
3140
                if i != 0 and len(random_spline) - 1 >= i:
 
3141
                    segments_lengths_sum += (random_spline[i - 1].co - random_spline[i].co).length
 
3142
                    segments_count += 1
 
3143
            
 
3144
            self.average_gp_segment_length = segments_lengths_sum / segments_count
 
3145
            
 
3146
            
 
3147
            #### Delete temporary strokes curve object
 
3148
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3149
            bpy.data.objects[self.temporary_curve.name].select = True
 
3150
            bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
 
3151
            
 
3152
            bpy.ops.object.delete()
 
3153
            
 
3154
            
 
3155
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3156
            bpy.data.objects[self.main_object.name].select = True
 
3157
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
3158
            
 
3159
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3160
            
 
3161
                
 
3162
            self.execute(context)
 
3163
            bpy.context.user_preferences.edit.use_global_undo = False # Set again since "execute()" will turn it again to its initial value.
 
3164
            
 
3165
            
 
3166
            #### If "Keep strokes" option is not active, delete original strokes curve object. 
 
3167
            if (not self.stopping_errors and not self.keep_strokes) or self.is_crosshatch:
 
3168
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3169
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3170
                bpy.data.objects[self.original_curve.name].select = True
 
3171
                bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
 
3172
                
 
3173
                bpy.ops.object.delete()
 
3174
                
 
3175
                bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3176
                bpy.data.objects[self.main_object.name].select = True
 
3177
                bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
3178
                
 
3179
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3180
                
 
3181
            
 
3182
            
 
3183
            #### Delete grease pencil strokes.
 
3184
            if self.strokes_type == "GP_STROKES" and not self.stopping_errors:
 
3185
                bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
 
3186
            
 
3187
            
 
3188
            bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
 
3189
            
 
3190
            
 
3191
            if not self.stopping_errors:
 
3192
                return {"FINISHED"}
 
3193
            else:
 
3194
                return{"CANCELLED"}
 
3195
            
 
3196
        elif self.strokes_type == "SELECTION_ALONE":
 
3197
            self.is_fill_faces = True
 
3198
            
 
3199
            created_faces_count = self.fill_with_faces(self.main_object)
 
3200
            
 
3201
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3202
            
 
3203
            if created_faces_count == 0:
 
3204
                self.report({'WARNING'}, "There aren't any strokes.")
 
3205
                return {"CANCELLED"}
 
3206
            else:
 
3207
                return {"FINISHED"}
 
3208
            
 
3209
            
 
3210
            
 
3211
            
 
3212
        elif self.strokes_type == "EXTERNAL_NO_CURVE":
 
3213
            self.report({'WARNING'}, "The secondary object is not a Curve.")
 
3214
            return{"CANCELLED"}
 
3215
        
 
3216
        elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
 
3217
            self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
 
3218
            return{"CANCELLED"}
 
3219
        
 
3220
        elif self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
 
3221
            self.report({'WARNING'}, "It's needed at least one stroke and one selection, or two strokes.")
 
3222
            return{"CANCELLED"}
 
3223
        
 
3224
        elif self.strokes_type == "NO_STROKES":
 
3225
            self.report({'WARNING'}, "There aren't any strokes.")
 
3226
            return{"CANCELLED"}
 
3227
        
 
3228
        elif self.strokes_type == "CURVE_WITH_NON_BEZIER_SPLINES":
 
3229
            self.report({'WARNING'}, "All splines must be Bezier.")
 
3230
            return{"CANCELLED"}
 
3231
        
 
3232
        else:
 
3233
            return{"CANCELLED"}
 
3234
 
 
3235
 
 
3236
# Edit strokes operator.
 
3237
class GPENCIL_OT_SURFSK_edit_strokes(bpy.types.Operator):
 
3238
    bl_idname = "gpencil.surfsk_edit_strokes"
 
3239
    bl_label = "Bsurfaces edit strokes"
 
3240
    bl_description = "Edit the grease pencil strokes or curves used."
 
3241
    
 
3242
    
 
3243
    def execute(self, context):
 
3244
        #### Determine the type of the strokes.
 
3245
        self.strokes_type = get_strokes_type(self.main_object)
 
3246
        #### Check if strokes are grease pencil strokes or a curves object.
 
3247
        selected_objs = bpy.context.selected_objects
 
3248
        if self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
 
3249
            for ob in selected_objs:
 
3250
                if ob != bpy.context.scene.objects.active:
 
3251
                    curve_ob = ob
 
3252
            
 
3253
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3254
            
 
3255
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3256
            bpy.data.objects[curve_ob.name].select = True
 
3257
            bpy.context.scene.objects.active = bpy.data.objects[curve_ob.name]
 
3258
            
 
3259
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3260
        elif self.strokes_type == "GP_STROKES" or self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION":
 
3261
            #### Convert grease pencil strokes to curve.
 
3262
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3263
            bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
 
3264
            for ob in bpy.context.selected_objects:
 
3265
                    if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
 
3266
                        ob_gp_strokes = ob
 
3267
            
 
3268
            #ob_gp_strokes = bpy.context.object
 
3269
            
 
3270
            #### Delete grease pencil strokes.
 
3271
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3272
            bpy.data.objects[self.main_object.name].select = True
 
3273
            bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
 
3274
            
 
3275
            bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
 
3276
            
 
3277
            
 
3278
            #### Clean up curves.
 
3279
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3280
            bpy.data.objects[ob_gp_strokes.name].select = True
 
3281
            bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
 
3282
            
 
3283
            curve_crv = ob_gp_strokes.data
 
3284
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3285
            bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type="BEZIER")
 
3286
            bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type="AUTOMATIC")
 
3287
            bpy.data.curves[curve_crv.name].show_handles = False
 
3288
            bpy.data.curves[curve_crv.name].show_normal_face = False
 
3289
            
 
3290
        elif self.strokes_type == "EXTERNAL_NO_CURVE":
 
3291
            self.report({'WARNING'}, "The secondary object is not a Curve.")
 
3292
            return{"CANCELLED"}
 
3293
        elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
 
3294
            self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
 
3295
            return{"CANCELLED"}
 
3296
        elif self.strokes_type == "NO_STROKES" or self.strokes_type == "SELECTION_ALONE":
 
3297
            self.report({'WARNING'}, "There aren't any strokes.")
 
3298
            return{"CANCELLED"}
 
3299
        else:
 
3300
            return{"CANCELLED"}
 
3301
                
 
3302
       
 
3303
       
 
3304
    def invoke (self, context, event):
 
3305
        self.main_object = bpy.context.object
 
3306
        
 
3307
        self.execute(context)
 
3308
        
 
3309
        return {"FINISHED"}
 
3310
 
 
3311
 
 
3312
 
 
3313
 
 
3314
class CURVE_OT_SURFSK_reorder_splines(bpy.types.Operator):
 
3315
    bl_idname = "curve.surfsk_reorder_splines"
 
3316
    bl_label = "Bsurfaces reorder splines"
 
3317
    bl_description = "Defines the order of the splines by using grease pencil strokes."
 
3318
    bl_options = {'REGISTER', 'UNDO'}
 
3319
    
 
3320
    
 
3321
    
 
3322
    def execute(self, context):
 
3323
        objects_to_delete = []
 
3324
        #### Convert grease pencil strokes to curve.
 
3325
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3326
        bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
 
3327
        for ob in bpy.context.selected_objects:
 
3328
            if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
 
3329
                GP_strokes_curve = ob
 
3330
        
 
3331
        #GP_strokes_curve = bpy.context.object
 
3332
        objects_to_delete.append(GP_strokes_curve)
 
3333
        
 
3334
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3335
        bpy.data.objects[GP_strokes_curve.name].select = True
 
3336
        bpy.context.scene.objects.active = bpy.data.objects[GP_strokes_curve.name]
 
3337
        
 
3338
        
 
3339
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3340
        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
 
3341
        bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = 100)
 
3342
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3343
        
 
3344
        bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
3345
        GP_strokes_mesh = bpy.context.object
 
3346
        objects_to_delete.append(GP_strokes_mesh)
 
3347
        
 
3348
        GP_strokes_mesh.data.resolution_u = 1
 
3349
        bpy.ops.object.convert(target='MESH', keep_original=False)
 
3350
        
 
3351
        
 
3352
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3353
        bpy.data.objects[self.main_curve.name].select = True
 
3354
        bpy.context.scene.objects.active = bpy.data.objects[self.main_curve.name]
 
3355
        
 
3356
        bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
3357
        curves_duplicate_1 = bpy.context.object
 
3358
        objects_to_delete.append(curves_duplicate_1)
 
3359
        
 
3360
        
 
3361
        
 
3362
        minimum_points_num = 500
 
3363
        
 
3364
        
 
3365
        for x in range(round(minimum_points_num / 100)): # Some iterations since the subdivision operator has a limit of 100 subdivisions per iteration.
 
3366
            #### 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.
 
3367
            for i in range(len(curves_duplicate_1.data.splines)):
 
3368
                sp = curves_duplicate_1.data.splines[i]
 
3369
                
 
3370
                if len(sp.bezier_points) < minimum_points_num:
 
3371
                    for bp in sp.bezier_points:
 
3372
                        bp.select_control_point = True
 
3373
                        
 
3374
                    if (len(sp.bezier_points) - 1) != 0:
 
3375
                        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.
 
3376
                    else:
 
3377
                        subdivide_cuts = 0
 
3378
                    
 
3379
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3380
                    bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
 
3381
                    bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3382
                    bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3383
        
 
3384
        
 
3385
        bpy.ops.object.duplicate('INVOKE_REGION_WIN')
 
3386
        curves_duplicate_2 = bpy.context.object
 
3387
        objects_to_delete.append(curves_duplicate_2)
 
3388
        
 
3389
        
 
3390
        #### Duplicate the duplicate and add Shrinkwrap to it, with the grease pencil strokes curve as target.
 
3391
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3392
        bpy.data.objects[curves_duplicate_2.name].select = True
 
3393
        bpy.context.scene.objects.active = bpy.data.objects[curves_duplicate_2.name]
 
3394
        
 
3395
        bpy.ops.object.modifier_add('INVOKE_REGION_WIN', type='SHRINKWRAP')
 
3396
        curves_duplicate_2.modifiers["Shrinkwrap"].wrap_method = "NEAREST_VERTEX"
 
3397
        curves_duplicate_2.modifiers["Shrinkwrap"].target = GP_strokes_mesh
 
3398
        bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
 
3399
        
 
3400
        
 
3401
        #### Get the distance of each vert from its original position to its position with Shrinkwrap.
 
3402
        nearest_points_coords = {}
 
3403
        for st_idx in range(len(curves_duplicate_1.data.splines)):
 
3404
            for bp_idx in range(len(curves_duplicate_1.data.splines[st_idx].bezier_points)):
 
3405
                bp_1_co = curves_duplicate_1.matrix_world * curves_duplicate_1.data.splines[st_idx].bezier_points[bp_idx].co
 
3406
                bp_2_co = curves_duplicate_2.matrix_world * curves_duplicate_2.data.splines[st_idx].bezier_points[bp_idx].co
 
3407
                
 
3408
                if bp_idx == 0:
 
3409
                    shortest_dist = (bp_1_co - bp_2_co).length
 
3410
                    nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0], "%.4f" % bp_2_co[1], "%.4f" % bp_2_co[2])
 
3411
                    
 
3412
                dist = (bp_1_co - bp_2_co).length
 
3413
                
 
3414
                if dist < shortest_dist:
 
3415
                    nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0], "%.4f" % bp_2_co[1], "%.4f" % bp_2_co[2])
 
3416
                    shortest_dist = dist
 
3417
                
 
3418
                
 
3419
        
 
3420
        #### Get all coords of GP strokes points, for comparison.
 
3421
        GP_strokes_coords = []
 
3422
        for st_idx in range(len(GP_strokes_curve.data.splines)):
 
3423
            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]])
 
3424
        
 
3425
        
 
3426
        #### Check the point of the GP strokes with the same coords as the nearest points of the curves (with shrinkwrap).
 
3427
        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.
 
3428
        for gp_st_idx in range(len(GP_strokes_coords)):
 
3429
            GPvert_spline_relationship = {}
 
3430
            
 
3431
            for splines_st_idx in range(len(nearest_points_coords)):
 
3432
                if nearest_points_coords[splines_st_idx] in GP_strokes_coords[gp_st_idx]:
 
3433
                    GPvert_spline_relationship[GP_strokes_coords[gp_st_idx].index(nearest_points_coords[splines_st_idx])] = splines_st_idx
 
3434
                    
 
3435
            
 
3436
            GP_connection_points[gp_st_idx] = GPvert_spline_relationship
 
3437
        
 
3438
        
 
3439
        #### Get the splines new order.
 
3440
        splines_new_order = []
 
3441
        for i in GP_connection_points:
 
3442
            dict_keys = sorted(GP_connection_points[i].keys()) # Sort dictionaries by key
 
3443
            
 
3444
            for k in dict_keys:
 
3445
                splines_new_order.append(GP_connection_points[i][k])
 
3446
                
 
3447
        
 
3448
        
 
3449
        #### Reorder.
 
3450
        
 
3451
        curve_original_name = self.main_curve.name
 
3452
        
 
3453
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3454
        bpy.data.objects[self.main_curve.name].select = True
 
3455
        bpy.context.scene.objects.active = bpy.data.objects[self.main_curve.name]
 
3456
        
 
3457
        self.main_curve.name = "SURFSKIO_CRV_ORD"
 
3458
        
 
3459
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3460
        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3461
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3462
        
 
3463
        
 
3464
        for sp_idx in range(len(self.main_curve.data.splines)):
 
3465
            self.main_curve.data.splines[0].bezier_points[0].select_control_point = True
 
3466
            
 
3467
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3468
            bpy.ops.curve.separate('INVOKE_REGION_WIN')
 
3469
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3470
        
 
3471
        
 
3472
        
 
3473
        #### Get the names of the separated splines objects in the original order.
 
3474
        splines_unordered = {}
 
3475
        for o in bpy.data.objects:
 
3476
            if o.name.find("SURFSKIO_CRV_ORD") != -1:
 
3477
                spline_order_string = o.name.partition(".")[2]
 
3478
                
 
3479
                if spline_order_string != "" and int(spline_order_string) > 0:
 
3480
                    spline_order_index = int(spline_order_string) - 1
 
3481
                    splines_unordered[spline_order_index] = o.name
 
3482
        
 
3483
        
 
3484
        
 
3485
        #### Join all splines objects in final order.
 
3486
        for order_idx in splines_new_order:
 
3487
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3488
            bpy.data.objects[splines_unordered[order_idx]].select = True
 
3489
            bpy.data.objects["SURFSKIO_CRV_ORD"].select = True
 
3490
            bpy.context.scene.objects.active = bpy.data.objects["SURFSKIO_CRV_ORD"]
 
3491
            
 
3492
            bpy.ops.object.join('INVOKE_REGION_WIN')
 
3493
            
 
3494
            
 
3495
        #### Go back to the original name of the curves object.
 
3496
        bpy.context.object.name = curve_original_name
 
3497
        
 
3498
        
 
3499
        #### Delete all unused objects.
 
3500
        for o in objects_to_delete:
 
3501
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3502
            bpy.data.objects[o.name].select = True
 
3503
            bpy.context.scene.objects.active = bpy.data.objects[o.name]
 
3504
            
 
3505
            bpy.ops.object.delete()
 
3506
            
 
3507
        
 
3508
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3509
        bpy.data.objects[curve_original_name].select = True
 
3510
        bpy.context.scene.objects.active = bpy.data.objects[curve_original_name]
 
3511
        
 
3512
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3513
        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3514
        
 
3515
        
 
3516
        bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
 
3517
        
 
3518
        
 
3519
        
 
3520
        return {"FINISHED"}
 
3521
    
 
3522
    
 
3523
        
 
3524
    def invoke (self, context, event):
 
3525
        self.main_curve = bpy.context.object
 
3526
        
 
3527
        
 
3528
        there_are_GP_strokes = False
750
3529
        try:
751
 
            bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
 
3530
            #### Get the active grease pencil layer.
 
3531
            strokes_num = len(self.main_curve.grease_pencil.layers.active.active_frame.strokes)
 
3532
            
 
3533
            if strokes_num > 0:
 
3534
                there_are_GP_strokes = True
752
3535
        except:
753
3536
            pass
754
 
        
755
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
756
 
        
757
 
        
758
 
        return {"FINISHED"}
759
 
        
760
 
    def invoke (self, context, event):
761
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
762
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
763
 
        self.main_object = bpy.context.scene.objects.active
764
 
        
765
 
        self.execute(context)
766
 
        
767
 
        return {"FINISHED"}
768
 
 
769
 
 
770
 
 
771
 
 
772
 
class GPENCIL_OT_SURFSK_strokes_to_curves(bpy.types.Operator):
773
 
    bl_idname = "gpencil.surfsk_strokes_to_curves"
774
 
    bl_label = "Bsurfaces strokes to curves"
775
 
    bl_description = "Convert grease pencil strokes into curves and enter edit mode"
 
3537
            
 
3538
        
 
3539
        if there_are_GP_strokes:
 
3540
            self.execute(context)
 
3541
            self.report({'INFO'}, "Splines have been reordered.")
 
3542
        else:
 
3543
            self.report({'WARNING'}, "Draw grease pencil strokes to connect splines.")
 
3544
        
 
3545
        return {"FINISHED"}
 
3546
    
 
3547
    
 
3548
    
 
3549
 
 
3550
class CURVE_OT_SURFSK_first_points(bpy.types.Operator):
 
3551
    bl_idname = "curve.surfsk_first_points"
 
3552
    bl_label = "Bsurfaces set first points"
 
3553
    bl_description = "Set the selected points as the first point of each spline."
 
3554
    bl_options = {'REGISTER', 'UNDO'}
 
3555
    
776
3556
    
777
3557
    
778
3558
    def execute(self, context):
779
 
        #### Convert grease pencil strokes to curve.
780
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
781
 
        bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE')
782
 
        ob_gp_strokes = bpy.context.object
783
 
        ob_gp_strokes.name = "SURFSK_strokes"
784
 
        
785
 
        #### Delete grease pencil strokes.
786
 
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
787
 
        bpy.ops.object.select_name('INVOKE_REGION_WIN', name = self.main_object.name)
788
 
        bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
789
 
        bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
790
 
        
791
 
        
792
 
        bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
793
 
        bpy.ops.object.select_name('INVOKE_REGION_WIN', name = ob_gp_strokes.name)
794
 
        bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
795
 
        
796
 
        
797
 
        #bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
798
 
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
799
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
800
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
801
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
802
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
803
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
804
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
805
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
806
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
807
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
808
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
809
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
810
 
        bpy.ops.curve.smooth('INVOKE_REGION_WIN')
811
 
        
812
 
        curve_crv = ob_gp_strokes.data
813
 
        bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type="BEZIER")
814
 
        bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type="AUTOMATIC")
815
 
        bpy.data.curves[curve_crv.name].show_handles = False
816
 
        bpy.data.curves[curve_crv.name].show_normal_face = False
817
 
       
818
 
       
 
3559
        splines_to_invert = []
 
3560
        
 
3561
        #### Check non-cyclic splines to invert.
 
3562
        for i in range(len(self.main_curve.data.splines)):
 
3563
            b_points = self.main_curve.data.splines[i].bezier_points
 
3564
            
 
3565
            if not i in self.cyclic_splines: # Only for non-cyclic splines
 
3566
                if b_points[len(b_points) - 1].select_control_point:
 
3567
                    splines_to_invert.append(i)
 
3568
        
 
3569
        
 
3570
        #### Reorder points of cyclic splines, and set all handles to "Automatic".
 
3571
        
 
3572
        # Check first selected point.
 
3573
        cyclic_splines_new_first_pt = {}
 
3574
        for i in self.cyclic_splines:
 
3575
            sp = self.main_curve.data.splines[i]
 
3576
            
 
3577
            for t in range(len(sp.bezier_points)):
 
3578
                bp = sp.bezier_points[t]
 
3579
                if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
 
3580
                    cyclic_splines_new_first_pt[i] = t
 
3581
                    break # To take only one if there are more.
 
3582
        
 
3583
        # Reorder.
 
3584
        for spline_idx in cyclic_splines_new_first_pt:
 
3585
            sp = self.main_curve.data.splines[spline_idx]
 
3586
            
 
3587
            spline_old_coords = []
 
3588
            for bp_old in sp.bezier_points:
 
3589
                coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
 
3590
                
 
3591
                left_handle_type = str(bp_old.handle_left_type)
 
3592
                left_handle_length = float(bp_old.handle_left.length)
 
3593
                left_handle_xyz = (float(bp_old.handle_left.x), float(bp_old.handle_left.y), float(bp_old.handle_left.z))
 
3594
                
 
3595
                right_handle_type = str(bp_old.handle_right_type)
 
3596
                right_handle_length = float(bp_old.handle_right.length)
 
3597
                right_handle_xyz = (float(bp_old.handle_right.x), float(bp_old.handle_right.y), float(bp_old.handle_right.z))
 
3598
                
 
3599
                spline_old_coords.append([coords, left_handle_type, right_handle_type, left_handle_length, right_handle_length, left_handle_xyz, right_handle_xyz])
 
3600
                
 
3601
            
 
3602
            for t in range(len(sp.bezier_points)):
 
3603
                bp = sp.bezier_points
 
3604
                
 
3605
                if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
 
3606
                    new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
 
3607
                else:
 
3608
                    new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
 
3609
                
 
3610
                bp[t].co = mathutils.Vector(spline_old_coords[new_index][0])
 
3611
                
 
3612
                bp[t].handle_left.length = spline_old_coords[new_index][3]
 
3613
                bp[t].handle_right.length = spline_old_coords[new_index][4]
 
3614
                
 
3615
                bp[t].handle_left_type = "FREE"
 
3616
                bp[t].handle_right_type = "FREE"
 
3617
                
 
3618
                bp[t].handle_left.x = spline_old_coords[new_index][5][0]
 
3619
                bp[t].handle_left.y = spline_old_coords[new_index][5][1]
 
3620
                bp[t].handle_left.z = spline_old_coords[new_index][5][2]
 
3621
                
 
3622
                bp[t].handle_right.x = spline_old_coords[new_index][6][0]
 
3623
                bp[t].handle_right.y = spline_old_coords[new_index][6][1]
 
3624
                bp[t].handle_right.z = spline_old_coords[new_index][6][2]
 
3625
                
 
3626
                bp[t].handle_left_type = spline_old_coords[new_index][1]
 
3627
                bp[t].handle_right_type = spline_old_coords[new_index][2]
 
3628
                
 
3629
        
 
3630
        
 
3631
        #### Invert the non-cyclic splines designated above.
 
3632
        for i in range(len(splines_to_invert)):
 
3633
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3634
            
 
3635
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3636
            self.main_curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
 
3637
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3638
            
 
3639
            bpy.ops.curve.switch_direction()
 
3640
            
 
3641
        bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
 
3642
        
 
3643
        
 
3644
        #### Keep selected the first vert of each spline.
 
3645
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3646
        for i in range(len(self.main_curve.data.splines)):
 
3647
            if not self.main_curve.data.splines[i].use_cyclic_u:
 
3648
                bp = self.main_curve.data.splines[i].bezier_points[0]
 
3649
            else:
 
3650
                bp = self.main_curve.data.splines[i].bezier_points[len(self.main_curve.data.splines[i].bezier_points) - 1]
 
3651
            
 
3652
            bp.select_control_point = True
 
3653
            bp.select_right_handle = True
 
3654
            bp.select_left_handle = True
 
3655
        bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
 
3656
        
 
3657
        
 
3658
        
 
3659
        
 
3660
        return {'FINISHED'}
 
3661
    
 
3662
    
 
3663
        
819
3664
    def invoke (self, context, event):
820
 
        self.main_object = bpy.context.object
 
3665
        self.main_curve = bpy.context.object
821
3666
        
 
3667
        # Check if all curves are Bezier, and detect which ones are cyclic.
 
3668
        self.cyclic_splines = []
 
3669
        for i in range(len(self.main_curve.data.splines)):
 
3670
            if self.main_curve.data.splines[i].type != "BEZIER":
 
3671
                self.report({'WARNING'}, 'All splines must be Bezier type.')
 
3672
                
 
3673
                return {'CANCELLED'}
 
3674
            else:
 
3675
                if self.main_curve.data.splines[i].use_cyclic_u:
 
3676
                    self.cyclic_splines.append(i)
 
3677
                    
 
3678
                    
822
3679
        
823
3680
        self.execute(context)
 
3681
        self.report({'INFO'}, "First points have been set.")
824
3682
        
825
 
        return {"FINISHED"}
826
 
 
827
 
 
 
3683
        return {'FINISHED'}
 
3684
    
 
3685
    
 
3686
    
 
3687
    
828
3688
def register():
 
3689
    bpy.utils.register_class(VIEW3D_PT_tools_SURFSK_mesh)
 
3690
    bpy.utils.register_class(VIEW3D_PT_tools_SURFSK_curve)
829
3691
    bpy.utils.register_class(GPENCIL_OT_SURFSK_add_surface)
830
 
    bpy.utils.register_class(VIEW3D_PT_tools_SURF_SKETCH)
831
 
    bpy.utils.register_class(GPENCIL_OT_SURFSK_strokes_to_curves)
832
 
    
833
 
    bpy.types.Scene.SURFSK_edges_U = bpy.props.IntProperty(name="Cross", description="Number of edge rings crossing the strokes (perpendicular to strokes direction)", default=10, min=0, max=100000)
834
 
    bpy.types.Scene.SURFSK_edges_V = bpy.props.IntProperty(name="Follow", description="Number of edge rings following the strokes (parallel to strokes direction)", default=10, min=0, max=100000)
835
 
    bpy.types.Scene.SURFSK_precision = bpy.props.IntProperty(name="Precision", description="Precision level of the surface calculation", default=4, min=0, max=100000)
836
 
    bpy.types.Scene.SURFSK_keep_strokes = bpy.props.BoolProperty(name="Keep strokes", description="Keeps the sketched strokes after adding the surface", default=False)
 
3692
    bpy.utils.register_class(GPENCIL_OT_SURFSK_edit_strokes)
 
3693
    bpy.utils.register_class(CURVE_OT_SURFSK_reorder_splines)
 
3694
    bpy.utils.register_class(CURVE_OT_SURFSK_first_points)
 
3695
    
 
3696
    
 
3697
    
 
3698
    bpy.types.Scene.SURFSK_cyclic_cross = bpy.props.BoolProperty(
 
3699
        name="Cyclic Cross",
 
3700
        description="Make cyclic the face-loops crossing the strokes.",
 
3701
        default=False)
 
3702
        
 
3703
    bpy.types.Scene.SURFSK_cyclic_follow = bpy.props.BoolProperty(
 
3704
        name="Cyclic Follow",
 
3705
        description="Make cyclic the face-loops following the strokes.",
 
3706
        default=False)
 
3707
        
 
3708
    bpy.types.Scene.SURFSK_keep_strokes = bpy.props.BoolProperty(
 
3709
        name="Keep strokes",
 
3710
        description="Keeps the sketched strokes or curves after adding the surface.",
 
3711
        default=False)
 
3712
    
 
3713
    bpy.types.Scene.SURFSK_automatic_join = bpy.props.BoolProperty(
 
3714
        name="Automatic join",
 
3715
        description="Join automatically vertices of either surfaces generated by crosshatching, or from the borders of closed shapes.",
 
3716
        default=True)
 
3717
    
 
3718
    bpy.types.Scene.SURFSK_loops_on_strokes = bpy.props.BoolProperty(
 
3719
        name="Loops on strokes",
 
3720
        description="Make the loops match the paths of the strokes.",
 
3721
        default=True)
837
3722
 
838
 
    kc = bpy.context.window_manager.keyconfigs.addon
839
 
    if kc:
840
 
        km = kc.keymaps.new(name="3D View", space_type="VIEW_3D")
841
 
        keymap_item_add_surf = km.keymap_items.new("gpencil.surfsk_add_surface","E","PRESS", key_modifier="D")
842
 
        keymap_item_stroke_to_curve = km.keymap_items.new("gpencil.surfsk_strokes_to_curves","C","PRESS", key_modifier="D")
 
3723
    bpy.types.Scene.SURFSK_precision = bpy.props.IntProperty(
 
3724
        name="Precision",
 
3725
        description="Precision level of the surface calculation.",
 
3726
        default=2,
 
3727
        min=1,
 
3728
        max=100)
843
3729
    
844
3730
 
845
3731
def unregister():
 
3732
    bpy.utils.unregister_class(VIEW3D_PT_tools_SURFSK_mesh)
 
3733
    bpy.utils.unregister_class(VIEW3D_PT_tools_SURFSK_curve)
846
3734
    bpy.utils.unregister_class(GPENCIL_OT_SURFSK_add_surface)
847
 
    bpy.utils.unregister_class(VIEW3D_PT_tools_SURF_SKETCH)
848
 
    bpy.utils.unregister_class(GPENCIL_OT_SURFSK_strokes_to_curves)
 
3735
    bpy.utils.unregister_class(GPENCIL_OT_SURFSK_edit_strokes)
 
3736
    bpy.utils.unregister_class(CURVE_OT_SURFSK_reorder_splines)
 
3737
    bpy.utils.unregister_class(CURVE_OT_SURFSK_first_points)
849
3738
    
850
 
    del bpy.types.Scene.SURFSK_edges_U
851
 
    del bpy.types.Scene.SURFSK_edges_V
852
3739
    del bpy.types.Scene.SURFSK_precision
853
3740
    del bpy.types.Scene.SURFSK_keep_strokes
854
 
    
855
 
    kc = bpy.context.window_manager.keyconfigs.addon
856
 
    if kc:
857
 
        km = kc.keymaps["3D View"]
858
 
        for kmi in km.keymap_items:
859
 
            if kmi.idname == 'wm.call_menu':
860
 
                if kmi.properties.name == "GPENCIL_OT_SURFSK_add_surface":
861
 
                    km.keymap_items.remove(kmi)
862
 
                elif kmi.properties.name == "GPENCIL_OT_SURFSK_strokes_to_curves":
863
 
                    km.keymap_items.remove(kmi)   
864
 
                else:
865
 
                    continue
866
 
 
867
 
    
 
3741
    del bpy.types.Scene.SURFSK_automatic_join
 
3742
    del bpy.types.Scene.SURFSK_cyclic_cross
 
3743
    del bpy.types.Scene.SURFSK_cyclic_follow
 
3744
    del bpy.types.Scene.SURFSK_loops_on_strokes
 
3745
    
 
3746
 
 
3747
 
868
3748
if __name__ == "__main__":
869
3749
    register()
 
3750
    
 
3751
    
 
3752