Newer
Older
points_first_stroke_tips.append(
self.main_splines.data.splines[0].bezier_points[
len(self.main_splines.data.splines[0].bezier_points) - 1
].co
)
CoDEmanX
committed
angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
CoDEmanX
committed
if angle_A < angle_B:
first_vert_U_idx = verts_tips_parsed_idx[0]
first_vert_V_idx = verts_tips_parsed_idx[1]
else:
first_vert_U_idx = verts_tips_parsed_idx[1]
first_vert_V_idx = verts_tips_parsed_idx[0]
CoDEmanX
committed
elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
first_sketched_point_first_stroke_co = self.main_splines.data.splines[0].bezier_points[0].co
last_sketched_point_first_stroke_co = \
self.main_splines.data.splines[0].bezier_points[
len(self.main_splines.data.splines[0].bezier_points) - 1
].co
first_sketched_point_last_stroke_co = \
self.main_splines.data.splines[
len(self.main_splines.data.splines) - 1
].bezier_points[0].co
if len(self.main_splines.data.splines) > 1:
first_sketched_point_second_stroke_co = self.main_splines.data.splines[1].bezier_points[0].co
last_sketched_point_second_stroke_co = \
self.main_splines.data.splines[1].bezier_points[
len(self.main_splines.data.splines[1].bezier_points) - 1
].co
CoDEmanX
committed
single_unselected_neighbors = [] # Only the neighbors of the single unselected verts
for verts_neig_idx in single_unselected_verts_and_neighbors:
single_unselected_neighbors.append(verts_neig_idx[1])
single_unselected_neighbors.append(verts_neig_idx[2])
CoDEmanX
committed
all_chains_tips_and_middle_vert = []
for v_idx in all_chains_tips_idx:
if v_idx not in single_unselected_neighbors:
all_chains_tips_and_middle_vert.append(v_idx)
CoDEmanX
committed
all_chains_tips_and_middle_vert += single_unselected_verts
CoDEmanX
committed
all_participating_verts = all_chains_tips_and_middle_vert + all_verts_idx
CoDEmanX
committed
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
# The tip of the selected vertices nearest to the first point of the first sketched stroke
nearest_tip_to_first_st_first_pt_idx, shortest_distance_to_first_stroke = \
self.shortest_distance(
self.main_object,
first_sketched_point_first_stroke_co,
all_chains_tips_and_middle_vert
)
# If the nearest tip is not from a closed selection, get the opposite tip vertex index
if nearest_tip_to_first_st_first_pt_idx not in single_unselected_verts or \
nearest_tip_to_first_st_first_pt_idx == middle_vertex_idx:
nearest_tip_to_first_st_first_pt_opposite_idx = \
self.opposite_tip(
nearest_tip_to_first_st_first_pt_idx,
verts_tips_same_chain_idx
)
# The tip of the selected vertices nearest to the last point of the first sketched stroke
nearest_tip_to_first_st_last_pt_idx, temp_dist = \
self.shortest_distance(
self.main_object,
last_sketched_point_first_stroke_co,
all_chains_tips_and_middle_vert
)
# The tip of the selected vertices nearest to the first point of the last sketched stroke
nearest_tip_to_last_st_first_pt_idx, shortest_distance_to_last_stroke = \
self.shortest_distance(
self.main_object,
first_sketched_point_last_stroke_co,
all_chains_tips_and_middle_vert
)
if len(self.main_splines.data.splines) > 1:
# The selected vertex nearest to the first point of the second sketched stroke
# (This will be useful to determine the direction of the closed
# selection V when extruding along strokes)
nearest_vert_to_second_st_first_pt_idx, temp_dist = \
self.shortest_distance(
self.main_object,
first_sketched_point_second_stroke_co,
all_verts_idx
)
# The selected vertex nearest to the first point of the second sketched stroke
# (This will be useful to determine the direction of the closed
# selection V2 when extruding along strokes)
nearest_vert_to_second_st_last_pt_idx, temp_dist = \
self.shortest_distance(
self.main_object,
last_sketched_point_second_stroke_co,
all_verts_idx
)
# Determine if the single selection will be treated as U or as V
edges_sum = 0
for i in all_selected_edges_idx:
(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
CoDEmanX
committed
average_edge_length = edges_sum / len(all_selected_edges_idx)
CoDEmanX
committed
# Get shortest distance from the first point of the last stroke to any participating vertex
temp_idx, shortest_distance_to_last_stroke = \
self.shortest_distance(
self.main_object,
first_sketched_point_last_stroke_co,
all_participating_verts
)
# If the beginning of the first stroke is near enough, and its orientation
# difference with the first edge of the nearest selection chain is not too high,
# interpret things as an "extrude along strokes" instead of "extrude through strokes"
if shortest_distance_to_first_stroke < average_edge_length / 4 and \
shortest_distance_to_last_stroke < average_edge_length and \
len(self.main_splines.data.splines) > 1:
CoDEmanX
committed
self.selection_U_exists = False
self.selection_V_exists = True
# If the first selection is not closed
if nearest_tip_to_first_st_first_pt_idx not in single_unselected_verts or \
nearest_tip_to_first_st_first_pt_idx == middle_vertex_idx:
self.selection_V_is_closed = False
first_neighbor_V_idx = None
closing_vert_U_idx = None
closing_vert_U2_idx = None
closing_vert_V_idx = None
closing_vert_V2_idx = None
CoDEmanX
committed
first_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
CoDEmanX
committed
if selection_type == "TWO_NOT_CONNECTED":
self.selection_V2_exists = True
CoDEmanX
committed
first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
else:
self.selection_V_is_closed = True
closing_vert_V_idx = nearest_tip_to_first_st_first_pt_idx
CoDEmanX
committed
# Get the neighbors of the first (unselected) vert of the closed selection U.
vert_neighbors = []
for verts in single_unselected_verts_and_neighbors:
if verts[0] == nearest_tip_to_first_st_first_pt_idx:
vert_neighbors.append(verts[1])
vert_neighbors.append(verts[2])
break
CoDEmanX
committed
verts_V = self.get_ordered_verts(
self.main_object, all_selected_edges_idx,
all_verts_idx, vert_neighbors[0], middle_vertex_idx, None
)
CoDEmanX
committed
for i in range(0, len(verts_V)):
if verts_V[i].index == nearest_vert_to_second_st_first_pt_idx:
# If the vertex nearest to the first point of the second stroke
# is in the first half of the selected verts
if i >= len(verts_V) / 2:
first_vert_V_idx = vert_neighbors[1]
break
else:
first_vert_V_idx = vert_neighbors[0]
break
CoDEmanX
committed
if selection_type == "TWO_NOT_CONNECTED":
self.selection_V2_exists = True
# If the second selection is not closed
if nearest_tip_to_first_st_last_pt_idx not in single_unselected_verts or \
nearest_tip_to_first_st_last_pt_idx == middle_vertex_idx:
CoDEmanX
committed
self.selection_V2_is_closed = False
first_neighbor_V2_idx = None
closing_vert_V2_idx = None
first_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
CoDEmanX
committed
else:
self.selection_V2_is_closed = True
closing_vert_V2_idx = nearest_tip_to_first_st_last_pt_idx
CoDEmanX
committed
# Get the neighbors of the first (unselected) vert of the closed selection U
vert_neighbors = []
for verts in single_unselected_verts_and_neighbors:
if verts[0] == nearest_tip_to_first_st_last_pt_idx:
vert_neighbors.append(verts[1])
vert_neighbors.append(verts[2])
break
CoDEmanX
committed
verts_V2 = self.get_ordered_verts(
self.main_object, all_selected_edges_idx,
all_verts_idx, vert_neighbors[0], middle_vertex_idx, None
)
CoDEmanX
committed
for i in range(0, len(verts_V2)):
if verts_V2[i].index == nearest_vert_to_second_st_last_pt_idx:
# If the vertex nearest to the first point of the second stroke
# is in the first half of the selected verts
if i >= len(verts_V2) / 2:
first_vert_V2_idx = vert_neighbors[1]
break
else:
first_vert_V2_idx = vert_neighbors[0]
break
else:
self.selection_V2_exists = False
CoDEmanX
committed
else:
self.selection_U_exists = True
self.selection_V_exists = False
# If the first selection is not closed
if nearest_tip_to_first_st_first_pt_idx not in single_unselected_verts or \
nearest_tip_to_first_st_first_pt_idx == middle_vertex_idx:
self.selection_U_is_closed = False
first_neighbor_U_idx = None
closing_vert_U_idx = None
CoDEmanX
committed
self.main_object.matrix_world @
self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_idx].co
)
points_tips.append(
self.main_object.matrix_world @
self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_opposite_idx].co
)
points_first_stroke_tips = []
points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
points_first_stroke_tips.append(
self.main_splines.data.splines[0].bezier_points[
len(self.main_splines.data.splines[0].bezier_points) - 1
].co
)
vec_A = points_tips[0] - points_tips[1]
vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
CoDEmanX
committed
# Compare the direction of the selection and the first
# grease pencil stroke to determine which is the "first" vertex of the selection
if vec_A.dot(vec_B) < 0:
first_vert_U_idx = nearest_tip_to_first_st_first_pt_opposite_idx
else:
first_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
CoDEmanX
committed
else:
self.selection_U_is_closed = True
closing_vert_U_idx = nearest_tip_to_first_st_first_pt_idx
CoDEmanX
committed
# Get the neighbors of the first (unselected) vert of the closed selection U
vert_neighbors = []
for verts in single_unselected_verts_and_neighbors:
if verts[0] == nearest_tip_to_first_st_first_pt_idx:
vert_neighbors.append(verts[1])
vert_neighbors.append(verts[2])
break
CoDEmanX
committed
self.main_object.matrix_world @
self.main_object.data.vertices[nearest_tip_to_first_st_first_pt_idx].co
)
points_first_and_neighbor.append(
self.main_object.matrix_world @
self.main_object.data.vertices[vert_neighbors[0]].co
)
points_first_stroke_tips = []
points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[1].co)
CoDEmanX
committed
vec_A = points_first_and_neighbor[0] - points_first_and_neighbor[1]
vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
CoDEmanX
committed
# Compare the direction of the selection and the first grease pencil stroke to
# determine which is the vertex neighbor to the first vertex (unselected) of
# the closed selection. This will determine the direction of the closed selection
if vec_A.dot(vec_B) < 0:
first_vert_U_idx = vert_neighbors[1]
else:
first_vert_U_idx = vert_neighbors[0]
CoDEmanX
committed
if selection_type == "TWO_NOT_CONNECTED":
self.selection_U2_exists = True
# If the second selection is not closed
if nearest_tip_to_last_st_first_pt_idx not in single_unselected_verts or \
nearest_tip_to_last_st_first_pt_idx == middle_vertex_idx:
CoDEmanX
committed
self.selection_U2_is_closed = False
first_neighbor_U2_idx = None
closing_vert_U2_idx = None
first_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
else:
self.selection_U2_is_closed = True
closing_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
CoDEmanX
committed
# Get the neighbors of the first (unselected) vert of the closed selection U
vert_neighbors = []
for verts in single_unselected_verts_and_neighbors:
if verts[0] == nearest_tip_to_last_st_first_pt_idx:
vert_neighbors.append(verts[1])
vert_neighbors.append(verts[2])
break
CoDEmanX
committed
self.main_object.matrix_world @
self.main_object.data.vertices[nearest_tip_to_last_st_first_pt_idx].co
)
points_first_and_neighbor.append(
self.main_object.matrix_world @
self.main_object.data.vertices[vert_neighbors[0]].co
)
points_last_stroke_tips.append(
self.main_splines.data.splines[
len(self.main_splines.data.splines) - 1
].bezier_points[0].co
)
points_last_stroke_tips.append(
self.main_splines.data.splines[
len(self.main_splines.data.splines) - 1
].bezier_points[1].co
)
vec_A = points_first_and_neighbor[0] - points_first_and_neighbor[1]
vec_B = points_last_stroke_tips[0] - points_last_stroke_tips[1]
CoDEmanX
committed
# Compare the direction of the selection and the last grease pencil stroke to
# determine which is the vertex neighbor to the first vertex (unselected) of
# the closed selection. This will determine the direction of the closed selection
if vec_A.dot(vec_B) < 0:
first_vert_U2_idx = vert_neighbors[1]
else:
first_vert_U2_idx = vert_neighbors[0]
else:
self.selection_U2_exists = False
CoDEmanX
committed
elif selection_type == "NO_SELECTION":
self.selection_U_exists = False
self.selection_V_exists = False
CoDEmanX
committed
# Get an ordered list of the vertices of Selection-U
verts_ordered_U = []
if self.selection_U_exists:
verts_ordered_U = self.get_ordered_verts(
self.main_object, all_selected_edges_idx,
all_verts_idx, first_vert_U_idx,
middle_vertex_idx, closing_vert_U_idx
)
verts_ordered_U_indices = [x.index for x in verts_ordered_U]
CoDEmanX
committed
# Get an ordered list of the vertices of Selection-U2
verts_ordered_U2 = []
if self.selection_U2_exists:
verts_ordered_U2 = self.get_ordered_verts(
self.main_object, all_selected_edges_idx,
all_verts_idx, first_vert_U2_idx,
middle_vertex_idx, closing_vert_U2_idx
)
verts_ordered_U2_indices = [x.index for x in verts_ordered_U2]
CoDEmanX
committed
# Get an ordered list of the vertices of Selection-V
verts_ordered_V = []
if self.selection_V_exists:
verts_ordered_V = self.get_ordered_verts(
self.main_object, all_selected_edges_idx,
all_verts_idx, first_vert_V_idx,
middle_vertex_idx, closing_vert_V_idx
)
verts_ordered_V_indices = [x.index for x in verts_ordered_V]
CoDEmanX
committed
# Get an ordered list of the vertices of Selection-V2
verts_ordered_V2 = []
if self.selection_V2_exists:
verts_ordered_V2 = self.get_ordered_verts(
self.main_object, all_selected_edges_idx,
all_verts_idx, first_vert_V2_idx,
middle_vertex_idx, closing_vert_V2_idx
)
verts_ordered_V2_indices = [x.index for x in verts_ordered_V2]
CoDEmanX
committed
# Check if when there are two-not-connected selections both have the same
# number of verts. If not terminate the script
if ((self.selection_U2_exists and len(verts_ordered_U) != len(verts_ordered_U2)) or
(self.selection_V2_exists and len(verts_ordered_V) != len(verts_ordered_V2))):
# Display a warning
self.report({'WARNING'}, "Both selections must have the same number of edges")
CoDEmanX
committed
self.cleanup_on_interruption()
self.stopping_errors = True
CoDEmanX
committed
CoDEmanX
committed
# Calculate edges U proportions
# Sum selected edges U lengths
edges_lengths_U = []
edges_lengths_sum_U = 0
CoDEmanX
committed
edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(
self.main_object,
verts_ordered_U
)
edges_lengths_U2, edges_lengths_sum_U2 = self.get_chain_length(
self.main_object,
verts_ordered_U2
)
# Sum selected edges V lengths
edges_lengths_V = []
edges_lengths_sum_V = 0
CoDEmanX
committed
edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(
self.main_object,
verts_ordered_V
)
edges_lengths_V2, edges_lengths_sum_V2 = self.get_chain_length(
self.main_object,
verts_ordered_V2
)
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.subdivide('INVOKE_REGION_WIN',
number_cuts=bpy.context.scene.bsurfaces.SURFSK_precision)
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
edges_proportions_U = self.get_edges_proportions(
edges_lengths_U, edges_lengths_sum_U,
self.selection_U_exists, self.edges_U
)
verts_count_U = len(edges_proportions_U) + 1
CoDEmanX
committed
if self.selection_U2_exists:
edges_proportions_U2 = []
edges_proportions_U2 = self.get_edges_proportions(
edges_lengths_U2, edges_lengths_sum_U2,
self.selection_U2_exists, self.edges_V
)
verts_count_U2 = len(edges_proportions_U2) + 1
CoDEmanX
committed
edges_proportions_V = self.get_edges_proportions(
edges_lengths_V, edges_lengths_sum_V,
self.selection_V_exists, self.edges_V
)
verts_count_V = len(edges_proportions_V) + 1
CoDEmanX
committed
if self.selection_V2_exists:
edges_proportions_V2 = []
edges_proportions_V2 = self.get_edges_proportions(
edges_lengths_V2, edges_lengths_sum_V2,
self.selection_V2_exists, self.edges_V
)
verts_count_V2 = len(edges_proportions_V2) + 1
CoDEmanX
committed
# Cyclic Follow: simplify sketched curves, make them Cyclic, and complete
# the actual sketched curves with a "closing segment"
if self.cyclic_follow and not self.selection_V_exists and not \
((self.selection_U_exists and not self.selection_U_is_closed) or
(self.selection_U2_exists and not self.selection_U2_is_closed)):
CoDEmanX
committed
simplified_spline_coords = []
simplified_curve = []
ob_simplified_curve = []
splines_first_v_co = []
for i in range(len(self.main_splines.data.splines)):
# Create a curve object for the actual spline "cyclic extension"
simplified_curve.append(bpy.data.curves.new('SURFSKIO_simpl_crv', 'CURVE'))
ob_simplified_curve.append(bpy.data.objects.new('SURFSKIO_simpl_crv', simplified_curve[i]))
bpy.context.collection.objects.link(ob_simplified_curve[i])
CoDEmanX
committed
simplified_curve[i].dimensions = "3D"
CoDEmanX
committed
spline_coords = []
for bp in self.main_splines.data.splines[i].bezier_points:
spline_coords.append(bp.co)
CoDEmanX
committed
simplified_spline_coords.append(self.simplify_spline(spline_coords, 5))
CoDEmanX
committed
# Get the coordinates of the first vert of the actual spline
splines_first_v_co.append(simplified_spline_coords[i][0])
CoDEmanX
committed
spline = simplified_curve[i].splines.new('BEZIER')
# less one because one point is added when the spline is created
spline.bezier_points.add(len(simplified_spline_coords[i]) - 1)
for p in range(0, len(simplified_spline_coords[i])):
spline.bezier_points[p].co = simplified_spline_coords[i][p]
CoDEmanX
committed
CoDEmanX
committed
spline_bp_count = len(spline.bezier_points)
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
ob_simplified_curve[i].select_set(True)
bpy.context.view_layer.objects.active = ob_simplified_curve[i]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Select the "closing segment", and subdivide it
ob_simplified_curve[i].data.splines[0].bezier_points[0].select_control_point = True
ob_simplified_curve[i].data.splines[0].bezier_points[0].select_left_handle = True
ob_simplified_curve[i].data.splines[0].bezier_points[0].select_right_handle = True
CoDEmanX
committed
ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_control_point = True
ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_left_handle = True
ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].select_right_handle = True
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
segments = sqrt(
(ob_simplified_curve[i].data.splines[0].bezier_points[0].co -
ob_simplified_curve[i].data.splines[0].bezier_points[spline_bp_count - 1].co).length /
self.average_gp_segment_length
)
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=segments)
CoDEmanX
committed
# Delete the other vertices and make it non-cyclic to
# keep only the needed verts of the "closing segment"
bpy.ops.curve.select_all(action='INVERT')
ob_simplified_curve[i].data.splines[0].use_cyclic_u = False
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Add the points of the "closing segment" to the original curve from grease pencil stroke
first_new_index = len(self.main_splines.data.splines[i].bezier_points)
self.main_splines.data.splines[i].bezier_points.add(
len(ob_simplified_curve[i].data.splines[0].bezier_points) - 1
)
for t in range(1, len(ob_simplified_curve[i].data.splines[0].bezier_points)):
self.main_splines.data.splines[i].bezier_points[t - 1 + first_new_index].co = \
ob_simplified_curve[i].data.splines[0].bezier_points[t].co
CoDEmanX
committed
bpy.ops.object.delete({"selected_objects": [ob_simplified_curve[i]]})
CoDEmanX
committed
# Get the coords of the points distributed along the sketched strokes,
# with proportions-U of the first selection
pts_on_strokes_with_proportions_U = self.distribute_pts(
self.main_splines.data.splines,
edges_proportions_U
)
CoDEmanX
committed
# Initialize the multidimensional list with the proportions of all the segments
proportions_loops_crossing_strokes = []
for i in range(len(pts_on_strokes_with_proportions_U)):
proportions_loops_crossing_strokes.append([])
CoDEmanX
committed
for t in range(len(pts_on_strokes_with_proportions_U[0])):
proportions_loops_crossing_strokes[i].append(None)
CoDEmanX
committed
# Calculate the proportions of each segment of the loops-U from pts_on_strokes_with_proportions_U
for lp in range(len(pts_on_strokes_with_proportions_U[0])):
loop_segments_lengths = []
CoDEmanX
committed
for st in range(len(pts_on_strokes_with_proportions_U)):
# When on the first stroke, add the segment from the selection to the dirst stroke
if st == 0:
loop_segments_lengths.append(
((self.main_object.matrix_world @ verts_ordered_U[lp].co) -
pts_on_strokes_with_proportions_U[0][lp]).length
)
# For all strokes except for the last, calculate the distance
# from the actual stroke to the next
if st != len(pts_on_strokes_with_proportions_U) - 1:
loop_segments_lengths.append(
(pts_on_strokes_with_proportions_U[st][lp] -
pts_on_strokes_with_proportions_U[st + 1][lp]).length
)
# When on the last stroke, add the segments
# from the last stroke to the second selection
if st == len(pts_on_strokes_with_proportions_U) - 1:
loop_segments_lengths.append(
(pts_on_strokes_with_proportions_U[st][lp] -
(self.main_object.matrix_world @ verts_ordered_U2[lp].co)).length
)
# Calculate full loop length
loop_seg_lengths_sum = 0
for i in range(len(loop_segments_lengths)):
loop_seg_lengths_sum += loop_segments_lengths[i]
CoDEmanX
committed
# Fill the multidimensional list with the proportions of all the segments
for st in range(len(pts_on_strokes_with_proportions_U)):
proportions_loops_crossing_strokes[st][lp] = \
loop_segments_lengths[st] / loop_seg_lengths_sum
CoDEmanX
committed
# Calculate proportions for each stroke
for st in range(len(pts_on_strokes_with_proportions_U)):
actual_stroke_spline = []
# Needs to be a list for the "distribute_pts" method
actual_stroke_spline.append(self.main_splines.data.splines[st])
CoDEmanX
committed
# Calculate the proportions for the actual stroke.
actual_edges_proportions_U = []
for i in range(len(edges_proportions_U)):
proportions_sum = 0
CoDEmanX
committed
# Sum the proportions of this loop up to the actual.
for t in range(0, st + 1):
proportions_sum += proportions_loops_crossing_strokes[t][i]
# i + 1, because proportions_loops_crossing_strokes refers to loops,
# and the proportions refer to edges, so we start at the element 1
# of proportions_loops_crossing_strokes instead of element 0
actual_edges_proportions_U.append(
edges_proportions_U[i] -
((edges_proportions_U[i] - edges_proportions_U2[i]) * proportions_sum)
)
points_actual_spline = self.distribute_pts(actual_stroke_spline, actual_edges_proportions_U)
sketched_splines_parsed.append(points_actual_spline[0])
else:
sketched_splines_parsed = pts_on_strokes_with_proportions_U
CoDEmanX
committed
# If the selection type is "TWO_NOT_CONNECTED" replace the
# points of the last spline with the points in the "target" selection
if selection_type == "TWO_NOT_CONNECTED":
if self.selection_U2_exists:
for i in range(0, len(sketched_splines_parsed[len(sketched_splines_parsed) - 1])):
sketched_splines_parsed[len(sketched_splines_parsed) - 1][i] = \
self.main_object.matrix_world @ verts_ordered_U2[i].co
CoDEmanX
committed
# Create temporary curves along the "control-points" found
# on the sketched curves and the mesh selection
mesh_ctrl_pts_name = "SURFSKIO_ctrl_pts"
me = bpy.data.meshes.new(mesh_ctrl_pts_name)
ob_ctrl_pts = bpy.data.objects.new(mesh_ctrl_pts_name, me)
ob_ctrl_pts.data = me
bpy.context.collection.objects.link(ob_ctrl_pts)
CoDEmanX
committed
cyclic_loops_U = []
first_verts = []
second_verts = []
last_verts = []
for i in range(0, verts_count_U):
vert_num_in_spline = 1
CoDEmanX
committed
if self.selection_U_exists:
ob_ctrl_pts.data.vertices.add(1)
last_v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
last_v.co = self.main_object.matrix_world @ verts_ordered_U[i].co
CoDEmanX
committed
CoDEmanX
committed
for t in range(0, len(sketched_splines_parsed)):
ob_ctrl_pts.data.vertices.add(1)
v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
v.co = sketched_splines_parsed[t][i]
CoDEmanX
committed
if vert_num_in_spline > 1:
ob_ctrl_pts.data.edges.add(1)
ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[0] = \
len(ob_ctrl_pts.data.vertices) - 2
ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[1] = \
len(ob_ctrl_pts.data.vertices) - 1
CoDEmanX
committed
if t == 0:
first_verts.append(v.index)
CoDEmanX
committed
if t == 1:
second_verts.append(v.index)
CoDEmanX
committed
if t == len(sketched_splines_parsed) - 1:
last_verts.append(v.index)
CoDEmanX
committed
last_v = v
vert_num_in_spline += 1
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = ob_ctrl_pts
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Determine which loops-U will be "Cyclic"
for i in range(0, len(first_verts)):
# When there is Cyclic Cross there is no need of
# Automatic Join, (and there are at least three strokes)
if self.automatic_join and not self.cyclic_cross and \
selection_type != "TWO_CONNECTED" and len(self.main_splines.data.splines) >= 3:
CoDEmanX
committed
first_point_co = v[first_verts[i]].co
second_point_co = v[second_verts[i]].co
last_point_co = v[last_verts[i]].co
CoDEmanX
committed
# Coordinates of the point in the center of both the first and last verts.
verts_center_co = [
(first_point_co[0] + last_point_co[0]) / 2,
(first_point_co[1] + last_point_co[1]) / 2,
(first_point_co[2] + last_point_co[2]) / 2
]
vec_A = second_point_co - first_point_co
vec_B = second_point_co - Vector(verts_center_co)
CoDEmanX
committed
# Calculate the length of the first segment of the loop,
# and the length it would have after moving the first vert
# to the middle position between first and last
length_original = (second_point_co - first_point_co).length
length_target = (second_point_co - Vector(verts_center_co)).length
CoDEmanX
committed
CoDEmanX
committed
# If the target length doesn't stretch too much, and the
# its angle doesn't change to much either
if length_target <= length_original * 1.03 * self.join_stretch_factor and \
angle <= 0.008 * self.join_stretch_factor and not self.selection_U_exists:
CoDEmanX
committed
# Move the first vert to the center coordinates
ob_ctrl_pts.data.vertices[first_verts[i]].co = verts_center_co
# Select the last verts from Cyclic loops, for later deletion all at once
v[last_verts[i]].select = True
else:
cyclic_loops_U.append(False)
else:
# If "Cyclic Cross" is active then "all" crossing curves become cyclic
if self.cyclic_cross and not self.selection_U_exists and not \
((self.selection_V_exists and not self.selection_V_is_closed) or
(self.selection_V2_exists and not self.selection_V2_is_closed)):
cyclic_loops_U.append(True)
else:
cyclic_loops_U.append(False)
CoDEmanX
committed
# The cyclic_loops_U list needs to be reversed.
cyclic_loops_U.reverse()
CoDEmanX
committed
# Delete the previously selected (last_)verts.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.mesh.delete('INVOKE_REGION_WIN', type='VERT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Create curves from control points.
bpy.ops.object.convert('INVOKE_REGION_WIN', target='CURVE', keep_original=False)
ob_curves_surf = bpy.context.view_layer.objects.active
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type='BEZIER')
bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
CoDEmanX
committed
# Make Cyclic the splines designated as Cyclic.
for i in range(0, len(cyclic_loops_U)):
ob_curves_surf.data.splines[i].use_cyclic_u = cyclic_loops_U[i]
CoDEmanX
committed
# Get the coords of all points on first loop-U, for later comparison with its
# subdivided version, to know which points of the loops-U are crossed by the
# original strokes. The indices will be the same for the other loops-U
if self.loops_on_strokes:
coords_loops_U_control_points = []
for p in ob_ctrl_pts.data.splines[0].bezier_points:
coords_loops_U_control_points.append(["%.4f" % p.co[0], "%.4f" % p.co[1], "%.4f" % p.co[2]])
CoDEmanX
committed
tuple(coords_loops_U_control_points)
CoDEmanX
committed
# Calculate number of edges-V in case option "Loops on strokes" is active or inactive
if self.loops_on_strokes and not self.selection_V_exists:
edges_V_count = len(self.main_splines.data.splines) * self.edges_V
else:
edges_V_count = len(edges_proportions_V)
CoDEmanX
committed
# The Follow precision will vary depending on the number of Follow face-loops
precision_multiplier = round(2 + (edges_V_count / 15))
curve_cuts = bpy.context.scene.bsurfaces.SURFSK_precision * precision_multiplier
CoDEmanX
committed
# Subdivide the curves
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=curve_cuts)
CoDEmanX
committed
# The verts position shifting that happens with splines subdivision.
# For later reorder splines points
verts_position_shift = curve_cuts + 1
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Reorder coordinates of the points of each spline to put the first point of
# the spline starting at the position it was the first point before sudividing
# the curve. And make a new curve object per spline (to handle memory better later)
splines_U_objects = []
for i in range(len(ob_curves_surf.data.splines)):
spline_U_curve = bpy.data.curves.new('SURFSKIO_spline_U_' + str(i), 'CURVE')
ob_spline_U = bpy.data.objects.new('SURFSKIO_spline_U_' + str(i), spline_U_curve)
bpy.context.collection.objects.link(ob_spline_U)
CoDEmanX
committed
spline_U_curve.dimensions = "3D"
CoDEmanX
committed
# Add points to the spline in the new curve object
ob_spline_U.data.splines.new('BEZIER')
for t in range(len(ob_curves_surf.data.splines[i].bezier_points)):
if cyclic_loops_U[i] is True and not self.selection_U_exists: # If the loop is cyclic
if t + verts_position_shift <= len(ob_curves_surf.data.splines[i].bezier_points) - 1:
point_index = t + verts_position_shift
else:
point_index = t + verts_position_shift - len(ob_curves_surf.data.splines[i].bezier_points)
else:
point_index = t
# to avoid adding the first point since it's added when the spline is created
if t > 0:
ob_spline_U.data.splines[0].bezier_points.add(1)
ob_spline_U.data.splines[0].bezier_points[t].co = \
ob_curves_surf.data.splines[i].bezier_points[point_index].co
CoDEmanX
committed
if cyclic_loops_U[i] is True and not self.selection_U_exists: # If the loop is cyclic
# Add a last point at the same location as the first one
ob_spline_U.data.splines[0].bezier_points.add(1)
ob_spline_U.data.splines[0].bezier_points[len(ob_spline_U.data.splines[0].bezier_points) - 1].co = \
ob_spline_U.data.splines[0].bezier_points[0].co
else:
ob_spline_U.data.splines[0].use_cyclic_u = False
CoDEmanX
committed
splines_U_objects.append(ob_spline_U)
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = ob_spline_U
CoDEmanX
committed
# When option "Loops on strokes" is active each "Cross" loop will have
# its own proportions according to where the original strokes "touch" them
# Get the indices of points where the original strokes "touch" loops-U
points_U_crossed_by_strokes = []
for i in range(len(splines_U_objects[0].data.splines[0].bezier_points)):
bp = splines_U_objects[0].data.splines[0].bezier_points[i]
if ["%.4f" % bp.co[0], "%.4f" % bp.co[1], "%.4f" % bp.co[2]] in coords_loops_U_control_points:
points_U_crossed_by_strokes.append(i)
CoDEmanX
committed
# Make a dictionary with the number of the edge, in the selected chain V, corresponding to each stroke
edge_order_number_for_splines = {}
if self.selection_V_exists:
# For two-connected selections add a first hypothetic stroke at the beginning.
if selection_type == "TWO_CONNECTED":
edge_order_number_for_splines[0] = 0
CoDEmanX
committed
for i in range(len(self.main_splines.data.splines)):
sp = self.main_splines.data.splines[i]
v_idx, dist_temp = self.shortest_distance(
self.main_object,
sp.bezier_points[0].co,
verts_ordered_V_indices
)
# Get the position (edges count) of the vert v_idx in the selected chain V
edge_idx_in_chain = verts_ordered_V_indices.index(v_idx)
# For two-connected selections the strokes go after the
# hypothetic stroke added before, so the index adds one per spline
if selection_type == "TWO_CONNECTED":
spline_number = i + 1
else:
spline_number = i
CoDEmanX
committed
edge_order_number_for_splines[spline_number] = edge_idx_in_chain
CoDEmanX
committed
# Get the first and last verts indices for later comparison
if i == 0:
first_v_idx = v_idx
elif i == len(self.main_splines.data.splines) - 1:
last_v_idx = v_idx
CoDEmanX
committed
# If there is no last stroke on the last vertex (same as first vertex),
# add a hypothetic spline at last vert order
edge_order_number_for_splines[(len(self.main_splines.data.splines) - 1) + 1] = \
len(verts_ordered_V_indices) - 1
else:
if self.cyclic_cross:
edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = \
len(verts_ordered_V_indices) - 2
edge_order_number_for_splines[(len(self.main_splines.data.splines) - 1) + 1] = \
len(verts_ordered_V_indices) - 1
edge_order_number_for_splines[len(self.main_splines.data.splines) - 1] = \
len(verts_ordered_V_indices) - 1
CoDEmanX
committed
# Get the coords of the points distributed along the
# "crossing curves", with appropriate proportions-V
surface_splines_parsed = []
for i in range(len(splines_U_objects)):
sp_ob = splines_U_objects[i]
# If "Loops on strokes" option is active, calculate the proportions for each loop-U
# Segments distances from stroke to stroke
dist = 0
full_dist = 0
segments_distances = []
for t in range(len(sp_ob.data.splines[0].bezier_points)):
bp = sp_ob.data.splines[0].bezier_points[t]
CoDEmanX
committed
if t == 0:
last_p = bp.co
else:
actual_p = bp.co
dist += (last_p - actual_p).length
CoDEmanX
committed
if t in points_U_crossed_by_strokes:
segments_distances.append(dist)
full_dist += dist
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
# Calculate Proportions.
used_edges_proportions_V = []
for t in range(len(segments_distances)):
if self.selection_V_exists:
if t == 0:
order_number_last_stroke = 0
CoDEmanX
committed
segment_edges_length_V = 0
segment_edges_length_V2 = 0
for order in range(order_number_last_stroke, edge_order_number_for_splines[t + 1]):
segment_edges_length_V += edges_lengths_V[order]
if self.selection_V2_exists:
segment_edges_length_V2 += edges_lengths_V2[order]
CoDEmanX
committed
for order in range(order_number_last_stroke, edge_order_number_for_splines[t + 1]):
# Calculate each "sub-segment" (the ones between each stroke) length
proportion_sub_seg = (edges_lengths_V2[order] -
((edges_lengths_V2[order] - edges_lengths_V[order]) /
len(splines_U_objects) * i)) / (segment_edges_length_V2 -
(segment_edges_length_V2 - segment_edges_length_V) /
len(splines_U_objects) * i)
sub_seg_dist = segments_distances[t] * proportion_sub_seg
else:
proportion_sub_seg = edges_lengths_V[order] / segment_edges_length_V
sub_seg_dist = segments_distances[t] * proportion_sub_seg
CoDEmanX
committed
used_edges_proportions_V.append(sub_seg_dist / full_dist)
CoDEmanX
committed
order_number_last_stroke = edge_order_number_for_splines[t + 1]
CoDEmanX
committed
else:
for c in range(self.edges_V):
# Calculate each "sub-segment" (the ones between each stroke) length
CoDEmanX
committed
sub_seg_dist = segments_distances[t] / self.edges_V
used_edges_proportions_V.append(sub_seg_dist / full_dist)
CoDEmanX
committed
actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
surface_splines_parsed.append(actual_spline[0])
CoDEmanX
committed
else:
if self.selection_V2_exists:
used_edges_proportions_V = []
for p in range(len(edges_proportions_V)):
used_edges_proportions_V.append(
edges_proportions_V2[p] -
((edges_proportions_V2[p] -
edges_proportions_V[p]) / len(splines_U_objects) * i)
)
else:
used_edges_proportions_V = edges_proportions_V
CoDEmanX
committed
actual_spline = self.distribute_pts(sp_ob.data.splines, used_edges_proportions_V)
surface_splines_parsed.append(actual_spline[0])
CoDEmanX
committed
# Set the verts of the first and last splines to the locations
# of the respective verts in the selections
if self.selection_V_exists:
for i in range(0, len(surface_splines_parsed[0])):
surface_splines_parsed[len(surface_splines_parsed) - 1][i] = \
self.main_object.matrix_world @ verts_ordered_V[i].co
CoDEmanX
committed
if selection_type == "TWO_NOT_CONNECTED":
if self.selection_V2_exists:
for i in range(0, len(surface_splines_parsed[0])):
surface_splines_parsed[0][i] = self.main_object.matrix_world @ verts_ordered_V2[i].co
CoDEmanX
committed
# When "Automatic join" option is active (and the selection type != "TWO_CONNECTED"),
# merge the verts of the tips of the loops when they are "near enough"
if self.automatic_join and selection_type != "TWO_CONNECTED":
# Join the tips of "Follow" loops that are near enough and must be "closed"
if not self.selection_V_exists and len(edges_proportions_U) >= 3:
for i in range(len(surface_splines_parsed[0])):
sp = surface_splines_parsed
loop_segment_dist = (sp[0][i] - sp[1][i]).length
full_loop_dist = loop_segment_dist * self.edges_U
CoDEmanX
committed
verts_middle_position_co = [
(sp[0][i][0] + sp[len(sp) - 1][i][0]) / 2,
(sp[0][i][1] + sp[len(sp) - 1][i][1]) / 2,
(sp[0][i][2] + sp[len(sp) - 1][i][2]) / 2
]
points_original = []
points_original.append(sp[1][i])
points_original.append(sp[0][i])
CoDEmanX
committed
points_target = []
points_target.append(sp[1][i])
points_target.append(Vector(verts_middle_position_co))