Newer
Older
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 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.
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
CoDEmanX
committed
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.
self.selection_V2_is_closed = False
first_neighbor_V2_idx = None
closing_vert_V2_idx = None
CoDEmanX
committed
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 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.
first_vert_V2_idx = vert_neighbors[1]
break
else:
first_vert_V2_idx = vert_neighbors[0]
break
CoDEmanX
committed
else:
self.selection_V2_exists = False
CoDEmanX
committed
else:
self.selection_U_exists = True
self.selection_V_exists = False
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.
self.selection_U_is_closed = False
first_neighbor_U_idx = None
closing_vert_U_idx = None
CoDEmanX
committed
points_tips = []
points_tips.append(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)
CoDEmanX
committed
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)
CoDEmanX
committed
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
points_first_and_neighbor = []
points_first_and_neighbor.append(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)
CoDEmanX
committed
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
CoDEmanX
committed
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.
self.selection_U2_is_closed = False
first_neighbor_U2_idx = None
closing_vert_U2_idx = None
CoDEmanX
committed
first_vert_U2_idx = nearest_tip_to_last_st_first_pt_idx
CoDEmanX
committed
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
points_first_and_neighbor = []
points_first_and_neighbor.append(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)
CoDEmanX
committed
points_last_stroke_tips = []
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)
CoDEmanX
committed
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]
CoDEmanX
committed
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
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
#### Calculate edges U proportions.
CoDEmanX
committed
# Sum selected edges U lengths.
edges_lengths_U = []
edges_lengths_sum_U = 0
CoDEmanX
committed
if self.selection_U_exists:
edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(self.main_object, verts_ordered_U)
CoDEmanX
committed
if self.selection_U2_exists:
edges_lengths_U2, edges_lengths_sum_U2 = self.get_chain_length(self.main_object, verts_ordered_U2)
CoDEmanX
committed
# Sum selected edges V lengths.
edges_lengths_V = []
edges_lengths_sum_V = 0
CoDEmanX
committed
if self.selection_V_exists:
edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(self.main_object, verts_ordered_V)
CoDEmanX
committed
if self.selection_V2_exists:
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.SURFSK_precision)
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Proportions U.
edges_proportions_U = []
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
# Proportions V.
edges_proportions_V = []
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)):
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.scene.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
# Simplification.
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
# Generate the spline.
spline = simplified_curve[i].splines.new('BEZIER')
spline.bezier_points.add(len(simplified_spline_coords[i]) - 1) # less one because one point is added when the spline is created.
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')
bpy.data.objects[ob_simplified_curve[i].name].select = True
bpy.context.scene.objects.active = bpy.context.scene.objects[ob_simplified_curve[i].name]
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)
for t in range(2):
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
# Delete the temporal curve.
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_simplified_curve[i].name].select = True
bpy.context.scene.objects.active = bpy.context.scene.objects[ob_simplified_curve[i].name]
CoDEmanX
committed
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
CoDEmanX
committed
if self.selection_U2_exists:
# 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)):
if st == 0: # When on the first stroke, add the segment from the selection to the dirst stroke.
loop_segments_lengths.append(((self.main_object.matrix_world * verts_ordered_U[lp].co) - pts_on_strokes_with_proportions_U[0][lp]).length)
CoDEmanX
committed
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.
loop_segments_lengths.append((pts_on_strokes_with_proportions_U[st][lp] - pts_on_strokes_with_proportions_U[st + 1][lp]).length)
CoDEmanX
committed
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.
loop_segments_lengths.append((pts_on_strokes_with_proportions_U[st][lp] - (self.main_object.matrix_world * verts_ordered_U2[lp].co)).length)
CoDEmanX
committed
# 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 = []
actual_stroke_spline.append(self.main_splines.data.splines[st]) # Needs to be a list for the "distribute_pts" method.
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]
CoDEmanX
committed
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.
CoDEmanX
committed
points_actual_spline = self.distribute_pts(actual_stroke_spline, actual_edges_proportions_U)
sketched_splines_parsed.append(points_actual_spline[0])
CoDEmanX
committed
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.scene.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
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_ctrl_pts.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
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)):
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).
v = ob_ctrl_pts.data.vertices
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]
CoDEmanX
committed
vec_A = second_point_co - first_point_co
vec_B = second_point_co - mathutils.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 - mathutils.Vector(verts_center_co)).length
CoDEmanX
committed
angle = vec_A.angle(vec_B) / math.pi
CoDEmanX
committed
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.
cyclic_loops_U.append(True)
CoDEmanX
committed
# Move the first vert to the center coordinates.
ob_ctrl_pts.data.vertices[first_verts[i]].co = verts_center_co
CoDEmanX
committed
# Select the last verts from Cyclic loops, for later deletion all at once.
v[last_verts[i]].select = True
CoDEmanX
committed
else:
cyclic_loops_U.append(False)
CoDEmanX
committed
else:
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.
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.scene.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 wiil 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))
CoDEmanX
committed
curve_cuts = bpy.context.scene.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
CoDEmanX
committed
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.scene.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] == 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
CoDEmanX
committed
if t > 0: # to avoid adding the first point since it's added when the spline is created.
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] == 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)
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_spline_U.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_spline_U.name]
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.
if self.loops_on_strokes:
# 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 begining.
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)
CoDEmanX
committed
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.
CoDEmanX
committed
# 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 self.selection_V_is_closed:
# If there is no last stroke on the last vertex (same as first vertex), add a hypothetic spline at last vert order.
if first_v_idx != last_v_idx:
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
else:
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.
if self.loops_on_strokes:
# 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.
if self.selection_V2_exists:
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 is not "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]
CoDEmanX
committed
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(mathutils.Vector(verts_middle_position_co))
CoDEmanX
committed
vec_A = points_original[0] - points_original[1]
vec_B = points_target[0] - points_target[1]
CoDEmanX
committed
angle = vec_A.angle(vec_B) / math.pi
CoDEmanX
committed
edge_new_length = (mathutils.Vector(verts_middle_position_co) - sp[1][i]).length
CoDEmanX
committed
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.
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.
# Change the coords of both verts to the middle position.
surface_splines_parsed[0][i] = verts_middle_position_co
surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_middle_position_co
CoDEmanX
committed
#### Delete object with control points and object from grease pencil convertion.
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_ctrl_pts.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
CoDEmanX
committed
CoDEmanX
committed
for sp_ob in splines_U_objects:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[sp_ob.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[sp_ob.name]
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
# Get all verts coords.
all_surface_verts_co = []
for i in range(0, len(surface_splines_parsed)):
# Get coords of all verts and make a list with them
for pt_co in surface_splines_parsed[i]:
all_surface_verts_co.append(pt_co)
CoDEmanX
committed
# Define verts for each face.
all_surface_faces = []
for i in range(0, len(all_surface_verts_co) - len(surface_splines_parsed[0])):
if ((i + 1) / len(surface_splines_parsed[0]) != int((i + 1) / len(surface_splines_parsed[0]))):
all_surface_faces.append([i+1, i , i + len(surface_splines_parsed[0]), i + len(surface_splines_parsed[0]) + 1])
CoDEmanX
committed
# Build the mesh.
surf_me_name = "SURFSKIO_surface"
me_surf = bpy.data.meshes.new(surf_me_name)
CoDEmanX
committed
me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
CoDEmanX
committed
CoDEmanX
committed
ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
bpy.context.scene.objects.link(ob_surface)
CoDEmanX
committed
# Select all the "unselected but participating" verts, from closed selection or double selections with middle-vertex, for later join with remove doubles.
for v_idx in single_unselected_verts:
self.main_object.data.vertices[v_idx].select = True
CoDEmanX
committed
#### Join the new mesh to the main object.
ob_surface.select = True
self.main_object.select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.join('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=0.0001)
bpy.ops.mesh.normals_make_consistent('INVOKE_REGION_WIN', inside=False)
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
CoDEmanX
committed
def execute(self, context):
bpy.context.user_preferences.edit.use_global_undo = False
CoDEmanX
committed
if not self.is_fill_faces:
bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
CoDEmanX
committed
# Build splines from the "last saved splines".
last_saved_curve = bpy.data.curves.new('SURFSKIO_last_crv', 'CURVE')
self.main_splines = bpy.data.objects.new('SURFSKIO_last_crv', last_saved_curve)
bpy.context.scene.objects.link(self.main_splines)
CoDEmanX
committed
last_saved_curve.dimensions = "3D"
CoDEmanX
committed
for sp in self.last_strokes_splines_coords:
spline = self.main_splines.data.splines.new('BEZIER')
spline.bezier_points.add(len(sp) - 1) # less one because one point is added when the spline is created.
for p in range(0, len(sp)):
spline.bezier_points[p].co = [sp[p][0], sp[p][1], sp[p][2]]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_splines.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_splines.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
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.
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
self.main_splines.name = "SURFSKIO_temp_strokes"
CoDEmanX
committed
if self.is_crosshatch:
strokes_for_crosshatch = True
strokes_for_rectangular_surface = False
else:
strokes_for_rectangular_surface = True
strokes_for_crosshatch = False
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
if strokes_for_rectangular_surface:
self.rectangular_surface()
elif strokes_for_crosshatch:
self.crosshatch_surface_execute()
CoDEmanX
committed
#### Delete main splines
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_splines.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_splines.name]
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
CoDEmanX
committed
CoDEmanX
committed
def invoke(self, context, event):
self.initial_global_undo_state = bpy.context.user_preferences.edit.use_global_undo
CoDEmanX
committed
self.main_object = bpy.context.scene.objects.active
self.main_object_selected_verts_count = int(self.main_object.data.total_vert_sel)
CoDEmanX
committed
bpy.context.user_preferences.edit.use_global_undo = False
CoDEmanX
committed
bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode', value='True, False, False')
CoDEmanX
committed
# Out Edit mode and In again to make sure the actual mesh selections are being taken.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
self.cyclic_cross = bpy.context.scene.SURFSK_cyclic_cross
self.cyclic_follow = bpy.context.scene.SURFSK_cyclic_follow
self.automatic_join = bpy.context.scene.SURFSK_automatic_join
self.loops_on_strokes = bpy.context.scene.SURFSK_loops_on_strokes
self.keep_strokes = bpy.context.scene.SURFSK_keep_strokes
CoDEmanX
committed
CoDEmanX
committed
if self.loops_on_strokes:
self.edges_V = 3
else:
self.edges_V = 10
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
self.last_strokes_splines_coords = []
CoDEmanX
committed
#### Determine the type of the strokes.
self.strokes_type = get_strokes_type(self.main_object)
CoDEmanX
committed
#### Check if it will be used grease pencil strokes or curves.
if self.strokes_type == "GP_STROKES" or self.strokes_type == "EXTERNAL_CURVE": # If there are strokes to be used.
if self.strokes_type == "GP_STROKES":
# Convert grease pencil strokes to curve.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
Bastien Montagne
committed
bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
# XXX gpencil.convert now keep org object as active/selected, *not* newly created curve!
# XXX This is far from perfect, but should work in most cases...
# self.original_curve = bpy.context.object
for ob in bpy.context.selected_objects:
if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
self.original_curve = ob
self.using_external_curves = False
elif self.strokes_type == "EXTERNAL_CURVE":
for ob in bpy.context.selected_objects:
if ob != bpy.context.scene.objects.active:
self.original_curve = ob