Updating stitchification methods and visualizations.

-- Handle SVG files with any number of paths.
-- Use a new method to compute intersections between line segments.  It uses cross products, is robust to horizontal/vertical segments, and detects if there is no intersection.
-- Remove consecutive duplicate points from the extracted SVG control points, which seems common and does not always start with the first point.
-- Ignore any empty paths extracted from the SVG file.
-- Add stitches in an order that preserves the original SVG path directionality.
-- For each segment in an SVG path, compute intersections with each other path then add stitches in each inter-intersection section according to the desired pitch (avoiding the intersections themselves).
-- Optionally animate the stitches to visualize the embroidery sequence.
-- Allowing PyPlot to choose a good marker size when visualizing the stitch pattern.
-- Testing with SVGs that have 1, 2, or 3 paths, and an SVG that has all 8 paths.
pull/140/head
Joseph DelPreto 2022-04-13 00:06:20 -04:00
rodzic 2d5ea5305e
commit 239f503572
2 zmienionych plików z 334 dodań i 247 usunięć

Wyświetl plik

@ -1,68 +1,113 @@
from generateDST_utils import *
import argparse
import time
import os
script_dir = os.path.dirname(os.path.realpath(__file__))
# Specify the input SVG file.
# design_dir = '.'
design_dir = os.path.join(script_dir, 'SVG')
# svg_filename = 'embroidery_sensor_matrix.svg'
# svg_filename = 'test_pattern_1path.svg'
# svg_filename = 'test_pattern_2paths.svg'
# svg_filename = 'test_pattern_2paths_skewed.svg'
# svg_filename = 'test_pattern_3paths.svg'
svg_filename = 'test_pattern_allPaths.svg'
svg_filepath = os.path.realpath(os.path.join(design_dir, svg_filename))
# Specify where to save the output DST file.
output_dir = os.path.realpath(os.path.join(script_dir, 'DST'))
os.makedirs(output_dir, exist_ok=True)
dst_filename = svg_filename.replace('.svg', '.dst')
# Parse command line arguments.
parser = argparse.ArgumentParser()
parser.add_argument('--input_path', type=str, default='./SVG/Asset 2palm.svg', help='input file path')
# parser.add_argument('--input_path', type=str, default='./Asset 1sensor1_exportNotResponsive.svg', help='input file path')
parser.add_argument('--output_path', type=str, default='./output/', help='output path')
parser.add_argument('--output_name', type=str, default='Asset 2palm.dst', help='output file name')
parser.add_argument('--pitch', type=int, default=40, help='pitch')
parser.add_argument('--viz', type=bool, default=True, help='viz')
parser.add_argument('--scale', type=float, default=10/4.2067, help='design scale')
parser.add_argument('--input_path', type=str, default=svg_filepath, help='input file path')
parser.add_argument('--output_dir', type=str, default=output_dir, help='output directory')
parser.add_argument('--output_filename', type=str, default=dst_filename, help='output file name')
parser.add_argument('--pitch', type=int, default=30, help='pitch')
parser.add_argument('--viz_svg', type=bool, default=False, help='viz')
parser.add_argument('--viz_dst', type=bool, default=True, help='viz')
parser.add_argument('--viz_dst_sim', type=bool, default=False, help='viz')
# NOTE: the embroidery machine's units are 0.1 mm, so a scale factor of 10 is useful.
parser.add_argument('--scale', type=float, default=10, help='design scale')
args = parser.parse_args()
dst_filepath = os.path.join(args.output_dir, args.output_filename)
pattern = pyembroidery.EmbPattern()
# Read control points from the SVG file.
print ('Extracting control points from %s' % (args.input_path))
start_time_s = time.time()
x_all, y_all = extract_pt_from_svg(args.input_path, args.scale,
viz=args.viz, target_units=None)
print ('control points extracted from svg')
viz=args.viz_svg, target_units='mm',
remove_duplicates=True)
num_paths = len(x_all)
print('Extracted control points in %0.2fs' % (time.time() - start_time_s))
print()
'''single path'''
if len(x_all) == 1:
x_pos_all = []
y_pos_all = []
for i in range(len(x_all[0])-1):
if i%2 == 0:
x, y = draw_line(pattern, [x_all[0][i], y_all[0][i]], [x_all[0][i+1], y_all[0][i+1]],
args.pitch, endpoint=False)
x_pos_all += x
y_pos_all += y
pyembroidery.write_dst(pattern, args.output_path + args.output_name)
print ('single path, pitch=', args.pitch, ', ', args.output_path + args.output_name, 'saved')
if args.viz:
marker_size = (max(x_pos_all)-min(x_pos_all))
plt.scatter(x_pos_all, y_pos_all, c='green', s=marker_size)
for i in range(len(x_pos_all)-1):
plt.plot([x_pos_all[i], x_pos_all[i+1]], [y_pos_all[i], y_pos_all[i+1]])
plt.show()
# Start an embroidery pattern.
# x_pos_all and y_pos_all will represent the points that are added to the pattern.
pattern = pyembroidery.EmbPattern()
x_stitch_all = [[] for p in range(num_paths)]
y_stitch_all = [[] for p in range(num_paths)]
# Add points to represent each path in the SVG file.
for path_index in range(num_paths):
print ('Stitching path %d/%d' % (path_index+1, num_paths))
start_time_s = time.time()
# Switch to a new thread for each new path.
if path_index > 0:
pattern.color_change()
# Add stitch points for this path.
x, y = stitch_path(pattern, x_all, y_all,
path_index=path_index, pitch=args.pitch)
x_stitch_all[path_index] += x
y_stitch_all[path_index] += y
print('Stitched path %d in %0.2fs' % (path_index+1, time.time() - start_time_s))
print()
'''two crossing paths'''
if len(x_all) == 2:
x_pos_all = [[],[]]
y_pos_all = [[],[]]
print (len(x_all[0]), len(x_all[1]))
x, y = drawline_halfway_1(pattern, x_all, y_all, pitch=args.pitch)
x_pos_all[0] += x
y_pos_all[0] += y
pattern.color_change()
x, y = drawline_halfway_2(pattern, x_all, y_all, pitch=args.pitch)
x_pos_all[1] += x
y_pos_all[1] += y
# Write the path to a DST file.
print('Writing the stitch pattern to %s' % dst_filepath)
start_time_s = time.time()
pyembroidery.write_dst(pattern, dst_filepath)
print('Wrote the DST file in %0.2fs' % (time.time() - start_time_s))
pyembroidery.write_dst(pattern, args.output_path + args.output_name)
print ('two paths, pitch=', args.pitch, ', ', args.output_path + args.output_name, 'saved')
if args.viz:
# marker_size = (max(x_pos_all[0])-min(x_pos_all[0]))
# print (x_pos_all[0])
plt.scatter(x_pos_all[0], y_pos_all[0], c='green', s=10)
for i in range(len(x_pos_all[0])-1):
plt.plot([x_pos_all[0][i], x_pos_all[0][i+1]], [y_pos_all[0][i], y_pos_all[0][i+1]])
plt.scatter(x_pos_all[1], y_pos_all[1], c='blue', s=10)
for i in range(len(x_pos_all[1])-1):
plt.plot([x_pos_all[1][i], x_pos_all[1][i+1]], [y_pos_all[1][i], y_pos_all[1][i+1]])
plt.show()
# Visualize the stitch pattern if desired.
if args.viz_dst:
# Step through the stitches to more easily visualize the order if desired.
if args.viz_dst_sim:
for path_index in range(num_paths):
sim_handles = []
# Plot the path without markers.
sim_handles.append(plt.plot(x_stitch_all[path_index], y_stitch_all[path_index], '-'))
# Show the real aspect ratio of the design.
plt.gca().set_aspect('equal')
# Show markers in stitch order.
for i in range(len(x_stitch_all[path_index])):
sim_handles.append(plt.plot(x_stitch_all[path_index][i],
y_stitch_all[path_index][i], '.'))
# Show the plot and wait for a specified timeout (or for the user to click the window).
plt.waitforbuttonpress(0.01)
# Remove the simulated stitches.
for sim_handle in sim_handles:
for h in sim_handle:
h.remove()
# Plot the final design with stitch markers, all in a single color.
plt.plot(x_stitch_all[path_index], y_stitch_all[path_index], '.-')
# Indicate the starting stitch.
plt.plot(x_stitch_all[path_index][0], y_stitch_all[path_index][0], 'k.')
# Plot the design, with a single color per path.
plt.clf()
for path_index in range(num_paths):
# Plot the final design with stitch markers, all in a single color.
plt.plot(x_stitch_all[path_index], y_stitch_all[path_index], '.-')
# Indicate the starting stitch.
plt.plot(x_stitch_all[path_index][0], y_stitch_all[path_index][0], 'k.')
# Show the real aspect ratio of the design.
plt.gca().set_aspect('equal')
# Show the plot and wait for the user to close the window.
plt.show()

Wyświetl plik

@ -8,6 +8,90 @@ import math
import cv2
import re
# Compute the intersection between two line segments.
# @param endpoints_1 and endpoints_2 are each 2x2 numpy arrays of the form [[x1, y1], [x2, y2]]
# @return The intersection point as a 2-element numpy array.
# If the two segments are colinear and overlapping, will return the midpoint of the intersection area.
# Returns None if the segments do not overlap.
# If return_scenario_info is True, returns a tuple (intersection_point, info)
# where info is a dict with keys 'parallel' and 'colinear'
# The code is based on https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
def get_segments_intersection(endpoints_1, endpoints_2, return_scenario_info=False, debug=False):
if isinstance(endpoints_1, (list, tuple)):
endpoints_1 = np.array(endpoints_1)
if isinstance(endpoints_2, (list, tuple)):
endpoints_2 = np.array(endpoints_2)
p = endpoints_1[0,:]
r = endpoints_1[1,:] - endpoints_1[0,:]
q = endpoints_2[0,:]
s = endpoints_2[1,:] - endpoints_2[0,:]
if debug:
print('p', p, 'r', r)
print('q', q, 's', s)
scenario_info = {
'colinear': False,
'parallel': False
}
if np.cross(r, s) == 0 and np.cross((q - p), r) == 0:
scenario_info['colinear'] = True
# The segments are colinear, so check if they are overlapping or disjoint.
# Frist, represent each endpoint in terms of the other segment's reference frame.
q_in_pFrame_0 = np.dot((q - p), r)/np.dot(r, r)
q_in_pFrame_1 = np.dot((q + s - p), r)/np.dot(r, r)
p_in_qFrame_0 = np.dot((p - q), s)/np.dot(s, s)
p_in_qFrame_1 = np.dot((p + r - q), s)/np.dot(s, s)
# Take the ones that are on the other segment (the ratio along the segment is in [0,1]).
intersection_points = []
if q_in_pFrame_0 >= 0 and q_in_pFrame_0 <= 1:
intersection_points.append(p + r*q_in_pFrame_0)
if q_in_pFrame_1 >= 0 and q_in_pFrame_1 <= 1:
intersection_points.append(p + r*q_in_pFrame_1)
if p_in_qFrame_0 >= 0 and p_in_qFrame_0 <= 1:
intersection_points.append(q + s*p_in_qFrame_0)
if p_in_qFrame_1 >= 0 and p_in_qFrame_1 <= 1:
intersection_points.append(q + s*p_in_qFrame_1)
# There should be 2 endpoints that are on the other segment if the segments overlap, and 0 otherwise.
if len(intersection_points) == 0:
intersection_point = None
else:
# Use the midpoint between the two intersecting endpoints.
intersection_points = np.array(intersection_points)
intersection_point = np.mean(intersection_points, axis=0)
elif np.cross(r, s) == 0 and np.cross((q-p), r) != 0:
# The lines are parallel and non-intersecting.
scenario_info['parallel'] = True
intersection_point = None
else:
t = np.cross((q - p), s) / np.cross(r, s)
u = np.cross((q - p), r) / np.cross(r, s)
if np.cross(r, s) != 0 and t >= 0 and t <= 1 and u >= 0 and u <= 1:
# There was an intersection.
intersection_point = p + t*r
else:
# The segments do not intersect (and they are not parallel).
intersection_point = None
# Print/plot debugging information if desired.
if debug:
print('scenario_info:', scenario_info)
print('intersection_point:', intersection_point)
import matplotlib.pyplot as plt
plt.clf()
plt.plot(endpoints_1[:,0], endpoints_1[:,1], '.-')
plt.plot(endpoints_2[:,0], endpoints_2[:,1], '.-')
if intersection_point is not None:
plt.plot(intersection_point[0], intersection_point[1], '.', markersize=25)
plt.grid(True, color='lightgray')
plt.waitforbuttonpress(0)
# All done!
if return_scenario_info:
return (intersection_point, scenario_info)
else:
return intersection_point
# Parse a size string such as "10mm" into its value, and convert it to the target units.
# Adapted from https://github.com/SebKuzminsky/svg2gcode/blob/94f28c1877c721c66cd90a38750f78d8031ac85a/gcoder.py#L238
def _parse_svg_size_string(size_str, target_units='mm'):
@ -52,13 +136,22 @@ def _parse_svg_size_string(size_str, target_units='mm'):
return val*scale
def extract_pt_from_svg(file, scale, viz, target_units):
# Extract points stored in an SVG file.
# @param target_units If provided, will look for scale informaion in the SVG metadata
# and, if found, will scale the points to be in the target units.
# @param scale A scale factor to apply to the design after converting to target units.
# @param remove_duplicates Whether to remove successive duplicate points.
# Otherwise, it seems the SVG can have back-to-back duplicates
# (that may or may not start at the first point).
# @return (x_all, y_all) where x_all and y_all are lists.
# Each entry represents a path in the SVG, and contains a list of coordinates.
# So point i of path p is at (x_all[p][i], y_all[p][i]).
def extract_pt_from_svg(file, scale, viz, target_units=None, remove_duplicates=True):
# Read paths from the SVG file.
paths, attributes = svg2paths(file)
# print (paths) # 0:path, 1:sec, 2:point
# Convert the paths into lists of coordinates.
x_all = []
y_all = []
for i in range(len(paths)):
x = []
y = []
@ -67,17 +160,20 @@ def extract_pt_from_svg(file, scale, viz, target_units):
# print (paths[i][j][k])
x.append(paths[i][j][k].real)
y.append(-paths[i][j][k].imag)
# Ignore empty paths.
if len(x) == 0:
continue
# Append the new path.
x_all.append(x)
y_all.append(y)
# Visualize the control points if desired.
if viz:
plt.scatter(x,y, s=100, c='red')
plt.scatter(x,y, s=10, c='red')
plt.gca().set_aspect('equal')
plt.show()
if target_units != None:
# Scale to get the desired units.
# Scale to specified units if desired.
if target_units is not None:
doc = svgpathtools.Document(file)
svg_attributes = doc.root.attrib
if 'width' not in svg_attributes or 'height' not in svg_attributes:
@ -106,195 +202,141 @@ def extract_pt_from_svg(file, scale, viz, target_units):
# plt.figure()
# plt.plot(x_all[0], y_all[0])
# plt.show()
# Apply any user-specified scaling.
for (i, points) in enumerate(x_all):
x_all[i] = [point*scale for point in points]
for (i, points) in enumerate(y_all):
y_all[i] = [point*scale for point in points]
# Remove successive duplicate points if desired.
if remove_duplicates:
x_all_unique = []
y_all_unique = []
for path_index in range(len(x_all)):
x_all_unique.append([])
y_all_unique.append([])
# Add the first point.
x_all_unique[path_index].append(x_all[path_index][0])
y_all_unique[path_index].append(y_all[path_index][0])
# Add points that do not duplicate their previous point.
for point_index in range(1, len(x_all[path_index])):
x = x_all[path_index][point_index]
x_prev = x_all[path_index][point_index - 1]
y = y_all[path_index][point_index]
y_prev = y_all[path_index][point_index - 1]
if not (x == x_prev and y == y_prev):
x_all_unique[path_index].append(x)
y_all_unique[path_index].append(y)
x_all = x_all_unique
y_all = y_all_unique
# Return the control points!
return x_all, y_all
def draw_line(pattern, st, ed, pitch, endpoint):
x_pos = []
y_pos = []
x_dis = ed[0] - st[0]
y_dis = ed[1] - st[1]
dis = np.sqrt(x_dis**2 + y_dis**2)
sec = dis//pitch
if sec == 0:
sec = 1
x = np.linspace(st[0], ed[0], int(sec)+1, endpoint=endpoint)
y = np.linspace(st[1], ed[1], int(sec)+1, endpoint=endpoint)
# print (len(x))
for i in range(len(x)):
pattern.add_stitch_absolute(pyembroidery.STITCH, x[i], y[i])
x_pos.append(x[i])
y_pos.append(y[i])
return x_pos, y_pos
def draw_line_mid(pattern, st, ed, pitch):
x_pos = []
y_pos = []
x_dis = ed[0] - st[0]
y_dis = ed[1] - st[1]
dis = np.sqrt(x_dis**2 + y_dis**2)
if dis <= 3 * pitch:
x_mid = (st[0] + ed[0])/2
y_mid = (st[1] + ed[1])/2
pattern.add_stitch_absolute(pyembroidery.STITCH, x_mid, y_mid)
return [x_mid], [y_mid]
else:
sec = dis//pitch
x = np.linspace(st[0], ed[0], int(sec), endpoint=True)
y = np.linspace(st[1], ed[1], int(sec), endpoint=True)
x = x[1:-1]
y = y[1:-1]
# print (len(x))
for i in range(len(x)):
pattern.add_stitch_absolute(pyembroidery.STITCH, x[i], y[i])
x_pos.append(x[i])
y_pos.append(y[i])
return x_pos, y_pos
def line_intersection(x1, y1, x2, y2, x3, y3, x4, y4):
"""find the intersection of line segments A=(x1,y1)/(x2,y2) and
B=(x3,y3)/(x4,y4). Returns a point or None"""
denom = ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4))
if denom==0: return None
px = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom
py = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom
if (px - x1) * (px - x2) < 0 and (py - y1) * (py - y2) < 0 \
and (px - x3) * (px - x4) < 0 and (py - y3) * (py - y4) < 0:
return [px, py]
else:
return None
def drawline_halfway_1(pattern, x_all, y_all, pitch):
pos_x = []
pos_y = []
for i in range(len(x_all[0])//2):
pt_x = []
pt_y = []
pt_x.append(x_all[0][2*i])
pt_y.append(y_all[0][2*i])
for j in range(len(x_all[1])//2):
int_pt = line_intersection(x_all[0][2*i], y_all[0][2*i], x_all[0][2*i+1], y_all[0][2*i+1],
x_all[1][2*j], y_all[1][2*j], x_all[1][2*j+1], y_all[1][2*j+1])
if int_pt:
pt_x.append(int_pt[0])
pt_y.append(int_pt[1])
pt_x.append(x_all[0][2*i+1])
pt_y.append(y_all[0][2*i+1])
# print ('compare', x_all[1][2*i], x_all[1][2*i+1], pt_x[-2], pt_x[1])
if len(pt_x) > 3:
if (x_all[0][2*i] > x_all[0][2*i+1] and pt_x[-2] > pt_x[1]) or (x_all[0][2*i] < x_all[0][2*i+1] and pt_x[-2] < pt_x[1]):
temp_x = pt_x[1:-1]
x = temp_x[::-1]
temp_y = pt_y[1:-1]
y = temp_y[::-1]
pt_x[1:-1] = x
pt_y[1:-1] = y
# for l in range(len(pt_x)):
# # print (pt_x[l], pt_y[l])
# plt.scatter(pt_x[:l+1], pt_y[:l+1], c='red', s=20)
# # plt.ylim(-400, 0)
# # plt.xlim(-100, 600)
# # plt.show()
if len(pt_x) <= 1:
raise Exception("error!")
elif len(pt_x) == 2:
for l in range(len(pt_x)-1):
x, y = draw_line(pattern, [pt_x[l], pt_y[l]], [pt_x[l+1], pt_y[l+1]], pitch, True)
pos_x += x
pos_y += y
# Create a sequence of stitches for the specified path of the provided SVG control points.
# For each line segment, will compute the intersections with all other SVG paths.
# Then for each inter-intersection section of the segment, will add stitches
# according to the specified pitch (avoiding the intersections themselves).
# Will place stitches for each segment according to the directionality of the original SVG path.
# Will also ensure that stitches are placed at the SVG control points.
def stitch_path(pattern, x_all, y_all, path_index, pitch, print_debug=False):
x_stitch = []
y_stitch = []
num_paths = len(x_all)
# Iterate through each of the SVG control points.
num_points = len(x_all[path_index])
for point_index in range(num_points-1):
# Will create a list of notable points that define the line segment starting at the current point.
segment_points_x = np.array([])
segment_points_y = np.array([])
# Add the current control point as the starting point.
segment_points_x = np.append(segment_points_x, x_all[path_index][point_index])
segment_points_y = np.append(segment_points_y, y_all[path_index][point_index])
# Add any intersection points between the segment from this control point to the next one,
# and any segment of any other paths in the SVG.
for other_path_index in range(num_paths):
if other_path_index == path_index:
continue
num_other_points = len(x_all[other_path_index])
for other_point_index in range(num_other_points-1):
# if print_debug:
# print('intersecting (%0.2f, %0.2f)-(%0.2f, %0.2f) with (%0.2f, %0.2f)-(%0.2f, %0.2f)' %
# (x_all[path_index][point_index], y_all[path_index][point_index],
# x_all[path_index][point_index+1], y_all[path_index][point_index+1],
# x_all[other_path_index][other_point_index], y_all[other_path_index][other_point_index],
# x_all[other_path_index][other_point_index+1], y_all[other_path_index][other_point_index+1]))
intersection = get_segments_intersection(
[[x_all[path_index][point_index], y_all[path_index][point_index]],
[x_all[path_index][point_index+1], y_all[path_index][point_index+1]]],
[[x_all[other_path_index][other_point_index], y_all[other_path_index][other_point_index]],
[x_all[other_path_index][other_point_index+1], y_all[other_path_index][other_point_index+1]]],
)
if intersection is not None:
segment_points_x = np.append(segment_points_x, intersection[0])
segment_points_y = np.append(segment_points_y, intersection[1])
# Add the next control point as the ending point.
segment_points_x = np.append(segment_points_x, x_all[path_index][point_index+1])
segment_points_y = np.append(segment_points_y, y_all[path_index][point_index+1])
# Sort the segment points by their x then y coordinate,
# using the same directionality as the original SVG path.
if x_all[path_index][point_index+1] > x_all[path_index][point_index]:
x_sort_direction = 1
else:
# x, y = draw_line(pattern, [pt_x[0], pt_y[0]], [pt_x[1], pt_y[1]], pitch, False)
# print ('x0',len(x))
# pos_x += x
# pos_y += y
for l in range(len(pt_x)-1):
x, y = draw_line_mid(pattern, [pt_x[l], pt_y[l]], [pt_x[l+1], pt_y[l+1]], pitch)
pos_x += x
pos_y += y
# x, y = draw_line(pattern, [pt_x[-2], pt_y[-2]], [pt_x[-1], pt_y[-1]], pitch, False)
# # print ('x1', len(x))
# pos_x += x
# pos_y += y
# print (len(pos_x))
return pos_x, pos_y
def drawline_halfway_2(pattern, x_all, y_all, pitch):
pos_x = []
pos_y = []
for i in range(len(x_all[1])//2):
pt_x = []
pt_y = []
pt_x.append(x_all[1][2*i])
pt_y.append(y_all[1][2*i])
for j in range(len(x_all[0])//2):
int_pt = line_intersection(x_all[1][2*i], y_all[1][2*i], x_all[1][2*i+1], y_all[1][2*i+1],
x_all[0][2*j], y_all[0][2*j], x_all[0][2*j+1], y_all[0][2*j+1])
if int_pt:
pt_x.append(int_pt[0])
pt_y.append(int_pt[1])
pt_x.append(x_all[1][2*i+1])
pt_y.append(y_all[1][2*i+1])
if len(pt_x) > 3:
if (y_all[1][2*i] > y_all[1][2*i+1] and pt_y[-2] > pt_y[1]) or (y_all[1][2*i] < y_all[1][2*i+1] and pt_y[-2] < pt_y[1]):
temp_x = pt_x[1:-1]
x = temp_x[::-1]
temp_y = pt_y[1:-1]
y = temp_y[::-1]
pt_x[1:-1] = x
pt_y[1:-1] = y
if len(pt_x) <= 1:
raise Exception("error!")
elif len(pt_x) == 2:
for l in range(len(pt_x)-1):
x, y = draw_line(pattern, [pt_x[l], pt_y[l]], [pt_x[l+1], pt_y[l+1]], pitch, True)
pos_x += x
pos_y += y
x_sort_direction = -1
if y_all[path_index][point_index+1] > y_all[path_index][point_index]:
y_sort_direction = 1
else:
# x, y = draw_line(pattern, [pt_x[0], pt_y[0]], [pt_x[1], pt_y[1]], pitch, False)
# pos_x += x
# pos_y += y
for l in range(len(pt_x)-1):
x, y = draw_line_mid(pattern, [pt_x[l], pt_y[l]], [pt_x[l+1], pt_y[l+1]], pitch)
pos_x += x
pos_y += y
# x, y = draw_line(pattern, [pt_x[-2], pt_y[-2]], [pt_x[-1], pt_y[-1]], pitch, True)
# pos_x += x
# pos_y += y
return pos_x, pos_y
y_sort_direction = -1
sorted_indexes = np.lexsort((y_sort_direction*np.array(segment_points_y),
x_sort_direction*np.array(segment_points_x)))
segment_points_x = segment_points_x[sorted_indexes]
segment_points_y = segment_points_y[sorted_indexes]
# plt.figure()
# plt.plot(pt_x, pt_y, '.-')
# plt.title('path %d point %d' % (path_index, i))
# plt.show()
# Add a stitch at the first point (the control point).
x_stitch.append(x_all[path_index][point_index])
y_stitch.append(y_all[path_index][point_index])
# For each section of the line segment as divided by the intersection points,
# add however many stitches will fit according to the specified pitch.
for start_point_index in range(len(segment_points_x)-1):
start_point = np.array([segment_points_x[start_point_index], segment_points_y[start_point_index]])
end_point = np.array([segment_points_x[start_point_index+1], segment_points_y[start_point_index+1]])
distance = np.linalg.norm(end_point - start_point)
# Compute the number of stitches that can fit.
# Will compute the number of inter-stitch gaps that can fit, which is one more
# than the number of stitches since there will also be the start/end stitch.
num_stitches = np.floor(distance/pitch).astype(int)-1
# Put at least one stitch between each intersection pair.
num_stitches = max(1, num_stitches)
# Then compute the ratio along the segment that each stitch will be.
stitch_position_ratios = np.arange(0, 1, 1/(num_stitches+1))[1:]
# Finally, compute the actual stitch positions.
for stitch_index in range(num_stitches):
stitch_position = start_point + (end_point-start_point)*(stitch_position_ratios[stitch_index])
x_stitch.append(stitch_position[0])
y_stitch.append(stitch_position[1])
if print_debug:
print('path_index',path_index)
print('point_index',point_index)
print('start_point_index',start_point_index)
print('start_point',start_point)
print('end_point',end_point)
print('distance', distance)
print('num_stitches', num_stitches)
print('stitch_position_ratios',stitch_position_ratios)
print()
# Add a stitch at the next point (the control point), if it's the last point in the path.
# If it's not the last point, then the next iteration of the loop will add it as the starting point.
if point_index == (num_points-1)-1:
x_stitch.append(x_all[path_index][point_index+1])
y_stitch.append(y_all[path_index][point_index+1])
return x_stitch, y_stitch