156
337
angle = abs(angle - math.pi)
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]))
169
def execute(self, context):
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
347
for i in range(0, len(verts_idx)):
348
dist = (point_co - matrix * object.data.vertices[verts_idx[i]].co).length
351
nearest_vert_idx = verts_idx[i]
356
nearest_vert_idx = verts_idx[i]
359
return nearest_vert_idx, shortest_dist
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]
371
return opposite_vert_tip_idx
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)
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])
384
simplified_spline.append(spline_coords[len(spline_coords) - 1])
386
return simplified_spline
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:
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]
399
bpy.ops.object.delete()
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]
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]
412
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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)):
426
prev_p = surface_splines[sp_idx].bezier_points[i]
428
p = surface_splines[sp_idx].bezier_points[i]
430
edge_length = (prev_p.co - p.co).length
432
surface_splines_lengths[sp_idx] += edge_length
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)
442
prev_p_co = surface_splines[sp_idx].bezier_points[0].co
444
for prop_idx in range(len(proportions) - 1):
445
target_length = surface_splines_lengths[sp_idx] * proportions[prop_idx]
447
partial_segment_length = 0
452
p_co = surface_splines[sp_idx].bezier_points[p_idx].co
454
new_dist = (prev_p_co - p_co).length
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.
459
if potential_segment_length < target_length: # If the potential is still shorter, keep adding.
460
partial_segment_length = potential_segment_length
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
469
intermediate_co = prev_p_co + (vec * remaining_dist)
471
surface_splines_parsed[sp_idx].append(intermediate_co)
473
partial_segment_length += remaining_dist
474
prev_p_co = intermediate_co
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)
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)
492
return surface_splines_parsed
496
#### Counts the number of faces that belong to each edge.
497
def edge_face_count(self, ob):
498
ed_keys_count_dict = {}
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
505
ed_keys_count_dict[ed_keys] += 1
509
for i in range(len(ob.data.edges)):
510
edge_face_count.append(0)
512
for i in range(len(ob.data.edges)):
513
ed = ob.data.edges[i]
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)]
524
return edge_face_count
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
533
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
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:
541
coords.append(object.data.vertices[ed.vertices[0]].co)
542
coords.append(object.data.vertices[ed.vertices[1]].co)
544
original_sel_edges_count += 1
546
if not ed.vertices[0] in all_selected_verts:
547
all_selected_verts.append(ed.vertices[0])
549
if not ed.vertices[1] in all_selected_verts:
550
all_selected_verts.append(ed.vertices[1])
553
tuple(all_selected_verts)
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:
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]
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]] = []
574
edges_connected_to_sel_verts[ed.vertices[0]].append(ed_idx)
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]] = []
581
edges_connected_to_sel_verts[ed.vertices[1]].append(ed_idx)
585
if include_edge == True:
586
all_edges_around_sel_verts.append(ed_idx)
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]] = []
593
if not ed.vertices[1] in verts_connected_to_every_vert:
594
verts_connected_to_every_vert[ed.vertices[1]] = []
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])
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)
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]
613
if all_edges_faces_count[ed_idx] > 0:
614
ed = object.data.edges[ed_idx]
616
if not ed.vertices[0] in selected_verts_with_faces:
617
selected_verts_with_faces.append(ed.vertices[0])
619
if not ed.vertices[1] in selected_verts_with_faces:
620
selected_verts_with_faces.append(ed.vertices[1])
622
all_verts_part_of_faces.append(ed.vertices[0])
623
all_verts_part_of_faces.append(ed.vertices[1])
625
tuple(selected_verts_with_faces)
629
#### Discard unneeded verts from calculations.
630
participating_verts = []
632
for v_idx in all_selected_verts:
633
vert_has_edges_with_one_face = False
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
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)
643
if not v_idx in all_verts_part_of_faces:
644
movable_verts.append(v_idx)
648
#### Remove from movable verts list those that are part of closed geometry (ie: triangles, quads)
649
for mv_idx in movable_verts:
651
mv_connected_verts = verts_connected_to_every_vert[mv_idx]
653
for actual_v_idx in all_selected_verts:
654
count_shared_neighbors = 0
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)
664
if actual_v_idx in mv_connected_verts:
668
if count_shared_neighbors == 2:
676
movable_verts.remove(mv_idx)
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]]
687
length = (v1.co - v2.co).length
689
if shortest_edge_length == None:
690
shortest_edge_length = length
692
if length < shortest_edge_length:
693
shortest_edge_length = length
695
if shortest_edge_length != None:
696
edges_merge_distance = shortest_edge_length * 0.5
698
edges_merge_distance = 0
703
#### Get together the verts near enough. They will be merged later.
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:
709
coords_verts_to_merge = {}
711
verts_to_merge.append(v1_idx)
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])
717
for v2_idx in remaining_verts:
719
v2_co = object.data.vertices[v2_idx].co
721
dist = (v1_co - v2_co).length
723
if dist <= edges_merge_distance: # Add the verts which are near enough.
724
verts_to_merge.append(v2_idx)
726
coords_verts_to_merge[v2_idx] = (v2_co[0], v2_co[1], v2_co[2])
729
for vm_idx in verts_to_merge:
730
remaining_verts.remove(vm_idx)
733
if len(verts_to_merge) > 1:
734
# Calculate middle point of the verts to merge.
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
747
movable_verts_to_merge_count += 1
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]
752
# Check if any vert to be merged is not movable.
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)
761
if are_verts_not_movable:
762
# Get the vert connected to faces, that is nearest to the middle point of the movable verts.
764
for vcf_idx in verts_not_movable:
765
dist = abs((object.data.vertices[vcf_idx].co - mathutils.Vector(middle_point_co)).length)
767
if shortest_dist == None:
769
nearest_vert_idx = vcf_idx
771
if dist < shortest_dist:
773
nearest_vert_idx = vcf_idx
775
coords = object.data.vertices[nearest_vert_idx].co
776
target_point_co = [coords[0], coords[1], coords[2]]
778
target_point_co = middle_point_co
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]
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)
794
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
797
#### Get all the definitive selected edges, after weldding.
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)
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]] = []
808
if not ed.vertices[1] in edges_per_vert:
809
edges_per_vert[ed.vertices[1]] = []
811
edges_per_vert[ed.vertices[0]].append(ed.index)
812
edges_per_vert[ed.vertices[1]].append(ed.index)
814
# Check if all the edges connected to each vert have two faces attached to them. To discard them later and make calculations faster.
816
a += self.edge_face_count(object)
818
verts_surrounded_by_faces = {}
819
for v_idx in edges_per_vert:
820
edges = edges_per_vert[v_idx]
822
edges_with_two_faces_count = 0
823
for ed_idx in edges_per_vert[v_idx]:
825
edges_with_two_faces_count += 1
827
if edges_with_two_faces_count == len(edges_per_vert[v_idx]):
828
verts_surrounded_by_faces[v_idx] = True
830
verts_surrounded_by_faces[v_idx] = False
833
#### Get all the selected vertices.
834
selected_verts_idx = []
835
for v in object.data.vertices:
837
selected_verts_idx.append(v.index)
840
#### Get all the faces of the object.
841
all_object_faces_verts_idx = []
842
for face in object.data.polygons:
844
face_verts.append(face.vertices[0])
845
face_verts.append(face.vertices[1])
846
face_verts.append(face.vertices[2])
848
if len(face.vertices) == 4:
849
face_verts.append(face.vertices[3])
851
all_object_faces_verts_idx.append(face_verts)
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')
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]
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]] = []
870
if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
871
related_key_verts[ed.vertices[0]].append(ed.vertices[1])
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]] = []
877
if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
878
related_key_verts[ed.vertices[1]].append(ed.vertices[0])
882
#### Get groups of verts forming each face.
884
for v1 in related_key_verts: # verts-1 ....
885
for v2 in related_key_verts: # verts-2
887
related_verts_in_common = []
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)
894
if v2 in related_key_verts[v1]:
897
if v1 in related_key_verts[v2]:
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
908
for f_verts in all_faces_to_check_idx:
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
917
if repeated_verts == len(f_verts):
921
if not repeated_face:
922
faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
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
928
for f_verts in all_faces_to_check_idx:
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
936
if repeated_verts == len(f_verts):
940
if not repeated_face:
941
faces_verts_idx.append([v1, related_verts_in_common[0], v2])
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)):
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]:
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)
962
#### Build faces discarding the ones in faces_to_not_include.
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] ])
972
num_faces_created += 1
979
for v_idx in selected_verts_idx:
980
self.main_object.data.vertices[v_idx].select = True
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')
988
return num_faces_created
992
#### Crosshatch skinning.
993
def crosshatch_surface_invoke(self, ob_original_splines):
994
self.is_crosshatch = False
995
self.crosshatch_merge_distance = 0
998
objects_to_delete = [] # duplicated strokes to be deleted.
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)
1006
self.main_object.modifiers[m_idx].show_viewport = False
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]
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"
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
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]
1028
if coords_first_pt != sp.bezier_points[0].co:
1029
first_dist = (coords_first_pt - sp.bezier_points[0].co).length
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
1034
first_point_dist_sum += first_dist + second_dist
1039
shortest_dist = first_dist
1040
elif second_dist != 0:
1041
shortest_dist = second_dist
1044
if shortest_dist > first_dist and first_dist != 0:
1045
shortest_dist = first_dist
1047
if shortest_dist > second_dist and second_dist != 0:
1048
shortest_dist = second_dist
1051
self.crosshatch_merge_distance = shortest_dist / 20
1055
#### Recalculation of merge distance.
1057
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
1059
ob_calc_merge_dist = bpy.context.object
1060
ob_calc_merge_dist.name = "SURFSKIO_CALC_TMP"
1062
objects_to_delete.append(ob_calc_merge_dist)
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')
1071
bpy.ops.curve.smooth('INVOKE_REGION_WIN')
1073
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
1074
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1078
#### Convert curves into mesh.
1079
ob_calc_merge_dist.data.resolution_u = 12
1080
bpy.ops.object.convert(target='MESH', keep_original=False)
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')
1089
# Remove verts with less than three edges.
1090
verts_edges_count = {}
1091
for ed in ob_calc_merge_dist.data.edges:
1094
if v[0] not in verts_edges_count:
1095
verts_edges_count[v[0]] = 0
1097
if v[1] not in verts_edges_count:
1098
verts_edges_count[v[1]] = 0
1100
verts_edges_count[v[0]] += 1
1101
verts_edges_count[v[1]] += 1
1103
nodes_verts_coords = []
1104
for v_idx in verts_edges_count:
1105
v = ob_calc_merge_dist.data.vertices[v_idx]
1107
if verts_edges_count[v_idx] < 3:
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')
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')
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]
1123
#### Check if the strokes are a crosshatch.
1124
if len(nodes_verts_coords) >= 3:
1125
self.is_crosshatch = True
1127
shortest_dist = None
1128
for co_1 in nodes_verts_coords:
1129
for co_2 in nodes_verts_coords:
1131
dist = (mathutils.Vector(co_1) - mathutils.Vector(co_2)).length
1133
if shortest_dist != None:
1134
if dist < shortest_dist:
1135
shortest_dist = dist
1137
shortest_dist = dist
1139
self.crosshatch_merge_distance = shortest_dist / 3
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]
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')
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:
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]
1164
vec_1 = p1.co - p2.co
1165
vec_2 = p2.co - p3.co
1167
if p2.co != p1.co and p2.co != p3.co:
1168
angle = vec_1.angle(vec_2)
1169
angle_sum += degrees(angle)
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
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
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
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
1191
#### Smooth out strokes a little to improve crosshatch detection.
1192
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1195
bpy.ops.curve.smooth('INVOKE_REGION_WIN')
1197
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
1198
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1203
#### Simplify the splines.
1204
for sp in ob_splines.data.splines:
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
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
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]
1223
vec_1 = p1.co - p2.co
1224
vec_2 = p2.co - p3.co
1226
if p2.co != p1.co and p2.co != p3.co:
1227
angle = vec_1.angle(vec_2)
1228
angle_sum += degrees(angle)
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
1239
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1241
bpy.ops.curve.select_all(action = 'INVERT')
1243
bpy.ops.curve.delete(type='SELECTED')
1244
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1248
objects_to_delete.append(ob_splines)
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')
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([])
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]]
1265
all_intersections = []
1266
checked_splines = []
1267
for i in range(len(all_points_coords)):
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]
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]
1280
intersec_coords = mathutils.geometry.intersect_line_line(bp1_co, bp2_co, bp3_co, bp4_co)
1282
if intersec_coords != None:
1283
dist = (intersec_coords[0] - intersec_coords[1]).length
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)
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]))
1296
checked_splines.append(i)
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.
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]] = []
1308
self.crosshatch_strokes_coords[all_intersections[i][0]].append(all_intersections[i][3]) # Save intersection coords.
1311
self.is_crosshatch = False
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()
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]
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)
1341
self.main_object.modifiers[m_idx].show_viewport = False
1344
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
1348
me_name = "SURFSKIO_STK_TMP"
1349
me = bpy.data.meshes.new(me_name)
1351
all_verts_coords = []
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]
1357
all_verts_coords.append(coords)
1360
all_edges.append((len(all_verts_coords) - 2, len(all_verts_coords) - 1))
1363
me.from_pydata(all_verts_coords, all_edges, [])
1367
ob = bpy.data.objects.new(me_name, me)
1369
bpy.context.scene.objects.link(ob)
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]
1377
#### Get together each vert and its nearest, to the middle position.
1378
verts = ob.data.vertices
1380
for i in range(len(verts)):
1381
shortest_dist = None
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
1388
if shortest_dist != None:
1389
if dist < shortest_dist:
1390
shortest_dist = dist
1393
shortest_dist = dist
1396
middle_location = (verts[i].co + verts[nearest_vert].co) / 2
1398
verts[i].co = middle_location
1399
verts[nearest_vert].co = middle_location
1401
checked_verts.append(i)
1402
checked_verts.append(nearest_vert)
1407
#### Calculate average length between all the generated edges.
1408
ob = bpy.context.object
1410
for ed in ob.data.edges:
1411
v1 = ob.data.vertices[ed.vertices[0]]
1412
v2 = ob.data.vertices[ed.vertices[1]]
1414
lengths_sum += (v1.co - v2.co).length
1416
edges_count = len(ob.data.edges)
1418
average_edge_length = lengths_sum / edges_count
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')
1426
final_points_ob = bpy.context.scene.objects.active
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]] = []
1435
if not ed.vertices[1] in related_key_verts:
1436
related_key_verts[ed.vertices[1]] = []
1439
if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
1440
related_key_verts[ed.vertices[0]].append(ed.vertices[1])
1442
if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
1443
related_key_verts[ed.vertices[1]].append(ed.vertices[0])
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
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)
1459
if v2 in related_key_verts[v1]:
1462
if v1 in related_key_verts[v2]:
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:
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
1479
if repeated_verts == len(f_verts):
1480
repeated_face = True
1483
if not repeated_face:
1484
faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
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:
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
1496
if repeated_verts == len(f_verts):
1497
repeated_face = True
1500
if not repeated_face:
1501
faces_verts_idx.append([v1, related_verts_in_common[0], v2])
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)):
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
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)
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]])
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:
1533
for v_idx in faces_verts_idx[i]:
1536
all_surface_faces.append(face)
1539
surf_me_name = "SURFSKIO_surface"
1540
me_surf = bpy.data.meshes.new(surf_me_name)
1542
me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
1546
ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
1547
bpy.context.scene.objects.link(ob_surface)
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]
1554
bpy.ops.object.delete()
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]
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')
1570
#### Join crosshatch results with original mesh.
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
1577
if len(ob_surface.data.edges) > 0:
1578
average_surface_edges_length = edges_length_sum / len(ob_surface.data.edges)
1580
average_surface_edges_length = 0.0001
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]] = []
1588
surface_connected_verts[ed.vertices[0]].append(ed.vertices[1])
1591
if not ed.vertices[1] in surface_connected_verts:
1592
surface_connected_verts[ed.vertices[1]] = []
1594
surface_connected_verts[ed.vertices[1]].append(ed.vertices[0])
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')
1603
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
1605
final_ob_duplicate = bpy.context.scene.objects.active
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
1611
bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
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
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":
1623
main_object_verts_coords.append(["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]])
1625
tuple(main_object_verts_coords)
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)
1641
points_target.append(ob_surface.data.vertices[c_v_idx].co)
1642
points_target.append(final_ob_duplicate.data.vertices[i].co)
1644
vec_A = points_original[0] - points_original[1]
1645
vec_B = points_target[0] - points_target[1]
1647
dist_A = (points_original[0] - points_original[1]).length
1648
dist_B = (points_target[0] - points_target[1]).length
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
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
1661
merge_actual_vert = False
1664
if merge_actual_vert:
1665
coords = final_ob_duplicate.data.vertices[i].co
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":
1671
comparison_coords = ["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]]
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.
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)
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
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()
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]
1701
bpy.ops.object.join('INVOKE_REGION_WIN')
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)
1708
bpy.ops.mesh.select_all(action='DESELECT')
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]
1722
def rectangular_surface(self):
170
1723
#### Selected edges.
171
1724
all_selected_edges_idx = []
172
1725
all_selected_verts = []
352
1929
first_vert_V_idx = verts_tips_parsed_idx[0]
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
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
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])
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)
1952
all_chains_tips_and_middle_vert += single_unselected_verts
1954
all_participating_verts = all_chains_tips_and_middle_vert + all_verts_idx
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)
368
nearest_tip_first_st_first_pt_idx = i
370
nearest_tip_first_pair_first_pt_idx = v_idx
372
# Shortest distance to the first point of the first stroke
373
shortest_distance_to_first_stroke = dist
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)
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)
384
nearest_tip_first_st_last_pt_pair_idx = i
385
nearest_tip_first_st_last_pt_point_idx = v_idx
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)
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)
396
nearest_tip_last_st_first_pt_pair_idx = i
397
nearest_tip_last_st_first_pt_point_idx = v_idx
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)
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)
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)
403
1977
# Determine if the single selection will be treated as U or as V.
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)
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
408
1982
average_edge_length = edges_sum / len(all_selected_edges_idx)
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
417
first_vert_V_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][nearest_tip_first_pair_first_pt_idx]
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)
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
2000
first_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
2002
if selection_type == "TWO_NOT_CONNECTED":
2003
self.selection_V2_exists = True
2005
first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
2007
self.selection_V_is_closed = True
2008
closing_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
2010
# Get the neighbors of the first (unselected) vert of the closed selection U.
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])
2018
verts_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, vert_neighbors[0], middle_vertex_idx, None)
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]
2026
first_vert_V_idx = vert_neighbors[0]
419
2031
if selection_type == "TWO_NOT_CONNECTED":
420
selection_V2_exists = True
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]
2032
self.selection_V2_exists = True
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
2039
first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
2042
self.selection_V2_is_closed = True
2043
closing_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
2045
# Get the neighbors of the first (unselected) vert of the closed selection U.
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])
2054
verts_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, vert_neighbors[0], middle_vertex_idx, None)
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]
2062
first_vert_V2_idx = vert_neighbors[0]
425
selection_V2_exists = False
2066
self.selection_V2_exists = False
428
selection_U_exists = True
429
selection_V_exists = False
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)
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)
437
vec_A = points_tips[0] - points_tips[1]
438
vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
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
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)
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)
2084
vec_A = points_tips[0] - points_tips[1]
2085
vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
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
2091
first_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
444
first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]
2094
self.selection_U_is_closed = True
2095
closing_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
2097
# Get the neighbors of the first (unselected) vert of the closed selection U.
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])
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)
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)
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]
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]
2120
first_vert_U_idx = vert_neighbors[0]
446
2124
if selection_type == "TWO_NOT_CONNECTED":
447
selection_U2_exists = True
2125
self.selection_U2_exists = True
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
2132
first_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
2135
self.selection_U2_is_closed = True
2136
closing_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
2138
# Get the neighbors of the first (unselected) vert of the closed selection U.
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])
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)
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)
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]
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]
2161
first_vert_U2_idx = vert_neighbors[0]
451
selection_U2_exists = False
2164
self.selection_U2_exists = False
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
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]
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]
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)
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]
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]
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")
2203
self.cleanup_on_interruption()
2205
self.stopping_errors = True
475
2211
#### Calculate edges U proportions.
478
2214
edges_lengths_U = []
479
2215
edges_lengths_sum_U = 0
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)
2220
if self.selection_U2_exists:
2221
edges_lengths_U2, edges_lengths_sum_U2 = self.get_chain_length(self.main_object, verts_ordered_U2)
484
2223
# Sum selected edges V lengths.
485
2224
edges_lengths_V = []
486
2225
edges_lengths_sum_V = 0
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)
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')
2230
if self.selection_V2_exists:
2231
edges_lengths_V2, edges_lengths_sum_V2 = self.get_chain_length(self.main_object, verts_ordered_V2)
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')
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
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
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
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
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])
2278
simplified_curve[i].dimensions = "3D"
2281
for bp in self.main_splines.data.splines[i].bezier_points:
2282
spline_coords.append(bp.co)
2285
simplified_spline_coords.append(self.simplify_spline(spline_coords, 5))
2287
# Get the coordinates of the first vert of the actual spline.
2288
splines_first_v_co.append(simplified_spline_coords[i][0])
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]
2298
spline.use_cyclic_u = True
2300
spline_bp_count = len(spline.bezier_points)
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]
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')
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
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
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)
2325
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = segments)
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')
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
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]
2347
bpy.ops.object.delete()
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)
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)):
517
prev_p = sketched_splines[sp_idx].bezier_points[i]
519
p = sketched_splines[sp_idx].bezier_points[i]
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]))
524
sketched_splines_lengths[sp_idx] += edge_length
528
# Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
529
sketched_splines_parsed.append([])
530
partial_spline_length = 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)):
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]
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]))
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)
550
edges_proportions_sum_U += edges_proportions_U[related_edge_U]
553
edges_lengths_sum_U += partial_spline_length
554
partial_spline_length = edge_length
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)
562
#### If the selection type is "TWO_NOT_CONNECTED" replace the last point of each spline with the points in the "target" selection.
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([])
2362
for t in range(len(pts_on_strokes_with_proportions_U[0])):
2363
proportions_loops_crossing_strokes[i].append(None)
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 = []
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)
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)
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)
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]
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
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.
2395
# Calculate the proportions for the actual stroke.
2396
actual_edges_proportions_U = []
2397
for i in range(len(edges_proportions_U)):
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]
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.
2407
points_actual_spline = self.distribute_pts(actual_stroke_spline, actual_edges_proportions_U)
2408
sketched_splines_parsed.append(points_actual_spline[0])
2411
sketched_splines_parsed = pts_on_strokes_with_proportions_U
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
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)
577
2434
for i in range(0, verts_count_U):
578
2435
vert_num_in_spline = 1
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
585
2442
vert_num_in_spline += 1
587
for sp in sketched_splines_parsed:
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]
2448
v.co = sketched_splines_parsed[t][i]
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
2457
first_verts.append(v.index)
2460
second_verts.append(v.index)
2462
if t == len(sketched_splines_parsed) - 1:
2463
last_verts.append(v.index)
599
2468
vert_num_in_spline += 1
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]
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')
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
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
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]
2492
vec_A = second_point_co - first_point_co
2493
vec_B = second_point_co - mathutils.Vector(verts_center_co)
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
2500
angle = vec_A.angle(vec_B) / math.pi
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)
2506
# Move the first vert to the center coordinates.
2507
ob_ctrl_pts.data.vertices[first_verts[i]].co = verts_center_co
2509
# Select the last verts from Cyclic loops, for later deletion all at once.
2510
v[last_verts[i]].select = True
2513
cyclic_loops_U.append(False)
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)
2519
cyclic_loops_U.append(False)
2521
# The cyclic_loops_U list needs to be reversed.
2522
cyclic_loops_U.reverse()
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')
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')
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]
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]])
2547
tuple(coords_loops_U_control_points)
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
2554
edges_V_count = len(edges_proportions_V)
2557
# The Follow precision will vary depending on the number of Follow face-loops.
2558
precision_multiplier = round(2 + (edges_V_count / 15))
2560
curve_cuts = bpy.context.scene.SURFSK_precision * precision_multiplier
2562
# Subdivide the curves.
2563
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = curve_cuts)
2565
# The verts position shifting that happens with splines subdivision. For later reorder splines points.
2566
verts_position_shift = curve_cuts + 1
617
2568
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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)
2578
spline_U_curve.dimensions = "3D"
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
2588
point_index = t + verts_position_shift - len(ob_curves_surf.data.splines[i].bezier_points)
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
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
2602
ob_spline_U.data.splines[0].use_cyclic_u = False
2605
splines_U_objects.append(ob_spline_U)
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]
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)
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
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)
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.
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
2643
edge_order_number_for_splines[spline_number] = edge_idx_in_chain
2646
# Get the first and last verts indices for later comparison.
2649
elif i == len(self.main_splines.data.splines) - 1:
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
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
2662
edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = len(verts_ordered_V_indices) - 1
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)):
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.
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]
2683
dist += (last_p - actual_p).length
2685
if t in points_U_crossed_by_strokes:
2686
segments_distances.append(dist)
2693
# Calculate Proportions.
2694
used_edges_proportions_V = []
2695
for t in range(len(segments_distances)):
2696
if self.selection_V_exists:
2698
order_number_last_stroke = 0
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]
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
2714
proportion_sub_seg = edges_lengths_V[order] / segment_edges_length_V
2715
sub_seg_dist = segments_distances[t] * proportion_sub_seg
2717
used_edges_proportions_V.append(sub_seg_dist / full_dist)
2719
order_number_last_stroke = edge_order_number_for_splines[t + 1]
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)
2727
actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
2728
surface_splines_parsed.append(actual_spline[0])
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))
631
p = surface_splines[sp_idx].bezier_points[i]
633
edge_length = self.pts_distance(prev_p.co, p.co)
635
surface_splines_lengths[sp_idx] += edge_length
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')
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
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)):
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]
658
edge_length = self.pts_distance(prev_p.co, p.co)
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)
665
edges_proportions_sum_V += edges_proportions_V[related_edge_V]
668
edges_lengths_sum_V += partial_spline_length
669
partial_spline_length = edge_length
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)
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
2738
actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
2739
surface_splines_parsed.append(actual_spline[0])
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
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
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
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]
2768
points_original = []
2769
points_original.append(sp[1][i])
2770
points_original.append(sp[0][i])
2773
points_target.append(sp[1][i])
2774
points_target.append(mathutils.Vector(verts_middle_position_co))
2776
vec_A = points_original[0] - points_original[1]
2777
vec_B = points_target[0] - points_target[1]
2780
angle = vec_A.angle(vec_B) / math.pi
2782
edge_new_length = (mathutils.Vector(verts_middle_position_co) - sp[1][i]).length
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
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()
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()
2797
bpy.ops.object.delete()
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]
2805
bpy.ops.object.delete()
706
2810
#### Generate surface.
732
2836
bpy.context.scene.objects.link(ob_surface)
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
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]
739
2849
bpy.ops.object.join('INVOKE_REGION_WIN')
740
2851
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
741
bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', mergedist=0.0001)
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')
746
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
749
#### Delete grease pencil strokes
2863
def execute(self, context):
2864
bpy.context.user_preferences.edit.use_global_undo = False
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')
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)
2874
last_saved_curve.dimensions = "3D"
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]]
2883
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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]
2889
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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')
2898
self.main_splines.name = "SURFSKIO_temp_strokes"
2901
if self.is_crosshatch:
2902
strokes_for_crosshatch = True
2903
strokes_for_rectangular_surface = False
2905
strokes_for_rectangular_surface = True
2906
strokes_for_crosshatch = False
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]
2913
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
2916
if strokes_for_rectangular_surface:
2917
self.rectangular_surface()
2918
elif strokes_for_crosshatch:
2919
self.crosshatch_surface_execute()
2922
#### Delete main splines
2923
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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]
2929
bpy.ops.object.delete()
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]
2935
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
2938
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
2944
def invoke(self, context, event):
2945
self.initial_global_undo_state = bpy.context.user_preferences.edit.use_global_undo
2947
self.main_object = bpy.context.scene.objects.active
2948
self.main_object_selected_verts_count = int(self.main_object.data.total_vert_sel)
2951
bpy.context.user_preferences.edit.use_global_undo = False
2954
bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
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')
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
2970
if self.loops_on_strokes:
2975
self.is_fill_faces = False
2977
self.stopping_errors = False
2979
self.last_strokes_splines_coords = []
2982
#### Determine the type of the strokes.
2983
self.strokes_type = get_strokes_type(self.main_object)
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
3004
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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]
3014
bpy.ops.object.delete()
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]
3021
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
3024
self.temporary_curve = bpy.context.scene.objects.active
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')
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]
3038
if len(sp.bezier_points) == 1:
3039
sp.bezier_points[0].select_control_point = True
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')
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]
3050
#### Set a minimum number of points for crosshatch
3051
minimum_points_num = 15
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]
3058
if len(sp.bezier_points) < minimum_points_num:
3059
for bp in sp.bezier_points:
3060
bp.select_control_point = True
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.
3068
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
3069
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
3071
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
3075
# Detect if the strokes are a crosshatch and do it if it is.
3076
self.crosshatch_surface_invoke(self.temporary_curve)
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]
3085
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
3087
#### Set a minimum number of points for rectangular surfaces.
3088
minimum_points_num = 60
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]
3094
if len(sp.bezier_points) < minimum_points_num:
3095
for bp in sp.bezier_points:
3096
bp.select_control_point = True
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.
3104
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts = subdivide_cuts)
3105
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
3107
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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]])
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]
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]
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
3131
tuple(self.last_strokes_splines_coords)
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
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
3144
self.average_gp_segment_length = segments_lengths_sum / segments_count
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]
3152
bpy.ops.object.delete()
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]
3159
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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.
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]
3173
bpy.ops.object.delete()
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]
3179
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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')
3188
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
3191
if not self.stopping_errors:
3196
elif self.strokes_type == "SELECTION_ALONE":
3197
self.is_fill_faces = True
3199
created_faces_count = self.fill_with_faces(self.main_object)
3201
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
3203
if created_faces_count == 0:
3204
self.report({'WARNING'}, "There aren't any strokes.")
3205
return {"CANCELLED"}
3212
elif self.strokes_type == "EXTERNAL_NO_CURVE":
3213
self.report({'WARNING'}, "The secondary object is not a Curve.")
3216
elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
3217
self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
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.")
3224
elif self.strokes_type == "NO_STROKES":
3225
self.report({'WARNING'}, "There aren't any strokes.")
3228
elif self.strokes_type == "CURVE_WITH_NON_BEZIER_SPLINES":
3229
self.report({'WARNING'}, "All splines must be Bezier.")
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."
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:
3253
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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]
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"):
3268
#ob_gp_strokes = bpy.context.object
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]
3275
bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
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]
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
3290
elif self.strokes_type == "EXTERNAL_NO_CURVE":
3291
self.report({'WARNING'}, "The secondary object is not a Curve.")
3293
elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
3294
self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
3296
elif self.strokes_type == "NO_STROKES" or self.strokes_type == "SELECTION_ALONE":
3297
self.report({'WARNING'}, "There aren't any strokes.")
3304
def invoke (self, context, event):
3305
self.main_object = bpy.context.object
3307
self.execute(context)
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'}
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
3331
#GP_strokes_curve = bpy.context.object
3332
objects_to_delete.append(GP_strokes_curve)
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]
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')
3344
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
3345
GP_strokes_mesh = bpy.context.object
3346
objects_to_delete.append(GP_strokes_mesh)
3348
GP_strokes_mesh.data.resolution_u = 1
3349
bpy.ops.object.convert(target='MESH', keep_original=False)
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]
3356
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
3357
curves_duplicate_1 = bpy.context.object
3358
objects_to_delete.append(curves_duplicate_1)
3362
minimum_points_num = 500
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]
3370
if len(sp.bezier_points) < minimum_points_num:
3371
for bp in sp.bezier_points:
3372
bp.select_control_point = True
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.
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')
3385
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
3386
curves_duplicate_2 = bpy.context.object
3387
objects_to_delete.append(curves_duplicate_2)
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]
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')
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
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])
3412
dist = (bp_1_co - bp_2_co).length
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
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]])
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 = {}
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
3436
GP_connection_points[gp_st_idx] = GPvert_spline_relationship
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
3445
splines_new_order.append(GP_connection_points[i][k])
3451
curve_original_name = self.main_curve.name
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]
3457
self.main_curve.name = "SURFSKIO_CRV_ORD"
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')
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
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')
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]
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
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"]
3492
bpy.ops.object.join('INVOKE_REGION_WIN')
3495
#### Go back to the original name of the curves object.
3496
bpy.context.object.name = curve_original_name
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]
3505
bpy.ops.object.delete()
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]
3512
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
3513
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
3516
bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
3524
def invoke (self, context, event):
3525
self.main_curve = bpy.context.object
3528
there_are_GP_strokes = False
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)
3534
there_are_GP_strokes = True
755
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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
765
self.execute(context)
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"
3539
if there_are_GP_strokes:
3540
self.execute(context)
3541
self.report({'INFO'}, "Splines have been reordered.")
3543
self.report({'WARNING'}, "Draw grease pencil strokes to connect splines.")
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'}
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"
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')
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]
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')
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
3559
splines_to_invert = []
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
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)
3570
#### Reorder points of cyclic splines, and set all handles to "Automatic".
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]
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.
3584
for spline_idx in cyclic_splines_new_first_pt:
3585
sp = self.main_curve.data.splines[spline_idx]
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])
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))
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))
3599
spline_old_coords.append([coords, left_handle_type, right_handle_type, left_handle_length, right_handle_length, left_handle_xyz, right_handle_xyz])
3602
for t in range(len(sp.bezier_points)):
3603
bp = sp.bezier_points
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
3608
new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
3610
bp[t].co = mathutils.Vector(spline_old_coords[new_index][0])
3612
bp[t].handle_left.length = spline_old_coords[new_index][3]
3613
bp[t].handle_right.length = spline_old_coords[new_index][4]
3615
bp[t].handle_left_type = "FREE"
3616
bp[t].handle_right_type = "FREE"
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]
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]
3626
bp[t].handle_left_type = spline_old_coords[new_index][1]
3627
bp[t].handle_right_type = spline_old_coords[new_index][2]
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')
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')
3639
bpy.ops.curve.switch_direction()
3641
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
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]
3650
bp = self.main_curve.data.splines[i].bezier_points[len(self.main_curve.data.splines[i].bezier_points) - 1]
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')
819
3664
def invoke (self, context, event):
820
self.main_object = bpy.context.object
3665
self.main_curve = bpy.context.object
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.')
3673
return {'CANCELLED'}
3675
if self.main_curve.data.splines[i].use_cyclic_u:
3676
self.cyclic_splines.append(i)
823
3680
self.execute(context)
3681
self.report({'INFO'}, "First points have been set.")
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)
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)
3698
bpy.types.Scene.SURFSK_cyclic_cross = bpy.props.BoolProperty(
3699
name="Cyclic Cross",
3700
description="Make cyclic the face-loops crossing the strokes.",
3703
bpy.types.Scene.SURFSK_cyclic_follow = bpy.props.BoolProperty(
3704
name="Cyclic Follow",
3705
description="Make cyclic the face-loops following the strokes.",
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.",
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.",
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.",
838
kc = bpy.context.window_manager.keyconfigs.addon
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(
3725
description="Precision level of the surface calculation.",
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)
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
855
kc = bpy.context.window_manager.keyconfigs.addon
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)
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
868
3748
if __name__ == "__main__":