Newer
Older
self.main_object.matrix_world @ self.main_object.data.vertices[verts_tips_parsed_idx[1]].co
self.main_object.matrix_world @ self.main_object.data.vertices[middle_vertex_idx].co
)
points_first_stroke_tips.append(
self.main_splines.data.splines[0].bezier_points[0].co
)
points_first_stroke_tips.append(
self.main_splines.data.splines[0].bezier_points[
len(self.main_splines.data.splines[0].bezier_points) - 1
].co
)
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
# 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
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
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
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
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
)
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
)
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
)
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
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
)
CoDEmanX
committed
edges_proportions_V = self.get_edges_proportions(
edges_lengths_V, edges_lengths_sum_V,
self.selection_V_exists, self.edges_V
)
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
)
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
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
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))
CoDEmanX
committed
vec_A = points_original[0] - points_original[1]
vec_B = points_target[0] - points_target[1]