pyembroidery/pyembroidery/EmbEncoder.py

623 wiersze
23 KiB
Python

import math
from .EmbConstant import *
class Transcoder:
def __init__(self, settings=None):
if settings is None:
settings = {}
self.max_stitch = settings.get("max_stitch", float('inf'))
self.max_jump = settings.get("max_jump", float('inf'))
self.full_jump = settings.get("full_jump", False)
strip_sequins = settings.get("strip_sequins", True)
if strip_sequins:
self.sequin_contingency = CONTINGENCY_SEQUIN_UTILIZE
else:
self.sequin_contingency = CONTINGENCY_SEQUIN_JUMP
self.sequin_contingency = settings.get("sequin_contingency", self.sequin_contingency)
self.strip_speeds = settings.get("strip_speeds", True)
self.explicit_trim = settings.get("explicit_trim", True)
self.has_tie_on = settings.get("tie_on", False)
self.has_tie_off = settings.get("tie_off", False)
self.long_stitch_contingency = \
settings.get("long_stitch_contingency", CONTINGENCY_JUMP_NEEDLE)
self.matrix = get_identity()
translate = settings.get("translate", None)
if translate is not None:
try:
m = get_translate(translate[0], translate[1])
self.matrix = matrix_multiply(self.matrix, m)
except IndexError:
try:
m = get_translate(translate.x, translate.y)
self.matrix = matrix_multiply(self.matrix, m)
except AttributeError:
pass
scale = settings.get("scale", None)
if scale is not None:
try:
m = get_scale(scale[0], scale[1])
self.matrix = matrix_multiply(self.matrix, m)
except (IndexError, TypeError):
try:
m = get_scale(scale.x, scale.y)
self.matrix = matrix_multiply(self.matrix, m)
except AttributeError:
m = get_scale(scale, scale)
self.matrix = matrix_multiply(self.matrix, m)
rotate = settings.get("rotate", None)
if rotate is not None:
m = get_rotate(rotate)
self.matrix = matrix_multiply(self.matrix, m)
self.source_pattern = None
self.destination_pattern = None
self.position = 0
self.color_index = -1
self.stitch = None
self.state_trimmed = True
self.state_sequin_mode = False
self.needle_x = 0
self.needle_y = 0
self.state_jumping = False
def transcode(self, source_pattern, destination_pattern):
self.source_pattern = source_pattern
self.destination_pattern = destination_pattern
self.transcode_metadata()
self.transcode_threads()
self.transcode_stitches()
return destination_pattern
def transcode_metadata(self):
"""Transcodes metadata, (just moves)"""
source = self.source_pattern.extras
dest = self.destination_pattern.extras
dest.update(source)
def transcode_threads(self):
"""Transcodes threads, (just moves)"""
source = self.source_pattern.threadlist
dest = self.destination_pattern.threadlist
dest.extend(source)
def transcode_stitches(self):
"""Transcodes stitches.
Converts middle-level commands and potentially incompatible
commands into a format friendly low level commands."""
source = self.source_pattern.stitches
self.state_trimmed = True
self.needle_x = 0
self.needle_y = 0
self.position = 0
self.color_index = -1
flags = NO_COMMAND
for self.position, self.stitch in enumerate(source):
p = point_in_matrix_space(self.matrix, self.stitch)
x = p[0]
y = p[1]
flags = self.stitch[2]
if flags == NO_COMMAND:
continue
elif flags == STITCH:
if self.state_trimmed:
self.jump_to_within_stitchrange(x, y)
self.stitch_at(x, y)
if self.has_tie_on:
self.tie_on()
elif self.state_jumping:
self.needle_to(x, y)
self.state_jumping = False
else:
self.stitch_with_contingency(x, y)
elif flags == NEEDLE_AT:
if self.state_trimmed:
self.jump_to_within_stitchrange(x, y)
self.stitch_at(x, y)
if self.has_tie_on:
self.tie_on()
elif self.state_jumping:
self.needle_to(x, y)
self.state_jumping = False
else:
self.needle_to(x, y)
elif flags == SEW_TO:
if self.state_trimmed:
self.jump_to_within_stitchrange(x, y)
self.stitch_at(x, y)
if self.has_tie_on:
self.tie_on()
elif self.state_jumping:
self.needle_to(x, y)
self.state_jumping = False
else:
self.sew_to(x, y)
# Middle Level Commands.
elif flags == STITCH_BREAK:
self.state_jumping = True
elif flags == FRAME_EJECT:
self.tie_off_and_trim_if_needed()
self.jump_to(x, y)
self.stop_here()
elif flags == SEQUENCE_BREAK:
self.tie_off_and_trim_if_needed()
elif flags == COLOR_BREAK:
self.color_break()
elif flags == TIE_OFF:
self.tie_off()
elif flags == TIE_ON:
self.tie_on()
# Core Commands.
elif flags == TRIM:
self.tie_off_and_trim_if_needed()
elif flags == JUMP:
if not self.state_jumping:
self.jump_to(x, y)
elif flags == SEQUIN_MODE:
self.toggle_sequins()
elif flags == SEQUIN_EJECT:
if self.state_trimmed:
self.jump_to_within_stitchrange(x, y)
self.stitch_at(x, y)
if self.has_tie_on:
self.tie_on()
if not self.state_sequin_mode:
self.toggle_sequins()
self.sequin_at(x, y)
elif flags == COLOR_CHANGE:
self.tie_off_trim_color_change()
# If we are told to do something we do it.
# Even if it's the first command and makes no sense.
elif flags == STOP:
self.stop_here()
elif flags == SLOW:
self.slow_command_here()
elif flags == FAST:
self.fast_command_here()
elif flags == END:
self.end_here()
break
# On-the-fly Settings Commands.
elif flags == OPTION_ENABLE_TIE_ON:
self.has_tie_on = True
elif flags == OPTION_ENABLE_TIE_OFF:
self.has_tie_off = True
elif flags == OPTION_DISABLE_TIE_ON:
self.has_tie_on = False
elif flags == OPTION_DISABLE_TIE_OFF:
self.has_tie_off = False
elif flags == OPTION_MAX_JUMP_LENGTH:
x = self.stitch[0]
self.max_jump = x
elif flags == OPTION_MAX_STITCH_LENGTH:
x = self.stitch[0]
self.max_stitch = x
elif flags == OPTION_EXPLICIT_TRIM:
self.explicit_trim = True
elif flags == OPTION_IMPLICIT_TRIM:
self.explicit_trim = False
elif flags == CONTINGENCY_NONE:
self.long_stitch_contingency = CONTINGENCY_NONE
elif flags == CONTINGENCY_JUMP_NEEDLE:
self.long_stitch_contingency = CONTINGENCY_JUMP_NEEDLE
elif flags == CONTINGENCY_SEW_TO:
self.long_stitch_contingency = CONTINGENCY_SEW_TO
elif flags == CONTINGENCY_SEQUIN_REMOVE:
if self.state_sequin_mode: # if sequin_mode, turn it off.
self.toggle_sequins()
self.sequin_contingency = CONTINGENCY_SEQUIN_REMOVE
elif flags == CONTINGENCY_SEQUIN_STITCH:
if self.state_sequin_mode: # if sequin_mode, turn it off.
self.toggle_sequins()
self.sequin_contingency = CONTINGENCY_SEQUIN_STITCH
elif flags == CONTINGENCY_SEQUIN_JUMP:
if self.state_sequin_mode: # if sequin_mode, turn it off.
self.toggle_sequins()
self.sequin_contingency = CONTINGENCY_SEQUIN_REMOVE
elif flags == CONTINGENCY_SEQUIN_UTILIZE:
self.sequin_contingency = CONTINGENCY_SEQUIN_UTILIZE
elif flags == MATRIX_TRANSLATE:
m = get_translate(self.stitch[0], self.stitch[1])
self.matrix = matrix_multiply(self.matrix, m)
elif flags == MATRIX_SCALE:
m = get_scale(self.stitch[0], self.stitch[1])
self.matrix = matrix_multiply(self.matrix, m)
elif flags == MATRIX_ROTATE:
m = get_rotate(self.stitch[0])
self.matrix = matrix_multiply(self.matrix, m)
elif flags == MATRIX_RESET:
self.matrix = get_identity()
if flags != END:
self.end_here()
def update_needle_position(self, x, y):
self.needle_x = x
self.needle_y = y
def declare_not_trimmed(self):
if self.state_trimmed:
self.state_trimmed = False
if self.color_index == -1:
self.color_index = 0
def add(self, flags, x=None, y=None):
if x is None:
x = self.needle_x
if y is None:
y = self.needle_y
self.destination_pattern.stitches.append([x, y, flags])
def lookahead_stitch(self):
"""Looks forward from current position and
determines if anymore stitching will occur."""
source = self.source_pattern.stitches
for pos in range(self.position, len(source)):
stitch = source[pos]
flags = stitch[2]
if flags == STITCH:
return True
elif flags == NEEDLE_AT:
return True
elif flags == SEW_TO:
return True
elif flags == TIE_ON:
return True
elif flags == SEQUIN_EJECT:
return True
elif flags == END:
return False
return False
def color_break(self):
"""Implements color break. Should add color changes add needed only."""
if self.color_index < 0:
return # We haven't stitched anything, colorbreak happens, before start. Ignore.
if not self.state_trimmed:
if self.has_tie_off:
self.tie_off()
if self.explicit_trim:
self.trim_here()
if not self.lookahead_stitch():
return # No more stitching will happen, colorchange unneeded.
self.add(COLOR_CHANGE)
self.color_index += 1
self.state_trimmed = True
def tie_off_trim_color_change(self):
if not self.state_trimmed:
if self.has_tie_off:
self.tie_off()
if self.explicit_trim:
self.trim_here()
self.add(COLOR_CHANGE)
self.color_index += 1
self.state_trimmed = True
def tie_off_and_trim_if_needed(self):
if not self.state_trimmed:
self.tie_off_and_trim()
def tie_off_and_trim(self):
if self.has_tie_off:
self.tie_off()
self.trim_here()
def tie_off(self):
try:
b = point_in_matrix_space(
self.matrix,
self.source_pattern.stitches[self.position - 1],
)
flags = b[2]
if flags == STITCH or flags == NEEDLE_AT or \
flags == SEW_TO or flags == SEQUIN_EJECT:
self.lock_stitch(self.needle_x, self.needle_y,
b[0], b[1], self.max_stitch)
except IndexError:
pass # must be an island stitch. jump-stitch-jump
def tie_on(self):
try:
b = point_in_matrix_space(
self.matrix,
self.source_pattern.stitches[self.position + 1]
)
flags = b[2]
if flags == STITCH or flags == NEEDLE_AT or \
flags == SEW_TO or flags == SEQUIN_EJECT:
self.lock_stitch(self.needle_x, self.needle_y,
b[0], b[1], self.max_stitch)
except IndexError:
pass # must be an island stitch. jump-stitch-jump
def trim_here(self):
if self.state_sequin_mode:
# Can't trim in sequin mode. DST uses jumps to trigger sequin eject and to trim.
self.toggle_sequins()
self.add(TRIM)
self.state_trimmed = True
def toggle_sequins(self):
"""Sequin mode toggle can be called whenever but will only actually turn on if set
to utilize mode for the sequin contingency."""
contingency = self.sequin_contingency
if contingency == CONTINGENCY_SEQUIN_UTILIZE:
self.add(SEQUIN_MODE)
self.state_sequin_mode = not self.state_sequin_mode
def jump_to_within_stitchrange(self, new_x, new_y):
"""Jumps close enough to stitch a position in x,y
without violating the length constraints."""
x0 = self.needle_x
y0 = self.needle_y
max_length = self.max_jump
self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, JUMP)
if self.full_jump:
if self.needle_x != new_x or self.needle_y != new_y:
self.jump_at(new_x, new_y)
# We are currently assuming that max_jump is also max_stitch.
# Properly it might be the case that some format could require
# a split constraint here where we would need to jump further
# so that we could then stitch closer.
def jump_to(self, new_x, new_y):
x0 = self.needle_x
y0 = self.needle_y
max_length = self.max_jump
self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, JUMP)
self.jump_at(new_x, new_y)
def jump_at(self, new_x, new_y):
if self.state_sequin_mode:
self.toggle_sequins() # can't jump with sequin mode on.
self.add(JUMP, new_x, new_y)
self.update_needle_position(new_x, new_y)
def stitch_with_contingency(self, new_x, new_y):
if self.long_stitch_contingency == CONTINGENCY_SEW_TO:
self.sew_to(new_x, new_y)
elif self.long_stitch_contingency == CONTINGENCY_JUMP_NEEDLE:
self.needle_to(new_x, new_y)
else:
self.stitch_at(new_x, new_y)
def sew_to(self, new_x, new_y):
"""Stitches to a specific location, with the emphasis on sewing.
Subdivides long stitches into additional stitches.
"""
x0 = self.needle_x
y0 = self.needle_y
max_length = self.max_stitch
self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, STITCH)
self.stitch_at(new_x, new_y)
def needle_to(self, new_x, new_y):
"""Insert needle at specific location, emphasis on the needle.
Uses jumps to avoid needle penetrations where possible.
The limit here is the max stitch limit or jump threshold.
If jump threshold is set low, it will insert jumps even
between stitches it could have technically encoded values for.
Stitches to the new location, adding jumps if needed.
"""
x0 = self.needle_x
y0 = self.needle_y
max_length = self.max_stitch
self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, JUMP)
self.stitch_at(new_x, new_y)
def stitch_at(self, new_x, new_y):
"""Inserts a stitch at the specific location.
Should have already been checked for constraints."""
self.add(STITCH, new_x, new_y)
self.update_needle_position(new_x, new_y)
self.declare_not_trimmed()
def sequin_at(self, new_x, new_y):
contingency = self.sequin_contingency
if contingency == CONTINGENCY_SEQUIN_UTILIZE:
self.add(SEQUIN_EJECT, new_x, new_y)
elif contingency == CONTINGENCY_SEQUIN_JUMP:
self.add(JUMP, new_x, new_y)
elif contingency == CONTINGENCY_SEQUIN_STITCH:
self.add(STITCH, new_x, new_y)
elif contingency == CONTINGENCY_SEQUIN_REMOVE:
# Do not update the needle position or declare untrimmed.
return
self.update_needle_position(new_x, new_y)
self.declare_not_trimmed()
def slow_command_here(self):
if not self.strip_speeds:
self.add(SLOW)
def fast_command_here(self):
if not self.strip_speeds:
self.add(FAST)
def stop_here(self):
self.add(STOP)
self.state_trimmed = True
def end_here(self):
self.add(END)
self.state_trimmed = True
def color_change_here(self):
self.add(COLOR_CHANGE)
self.color_index += 1
self.state_trimmed = True
def position_will_exceed_constraint(self, length=None, new_x=None, new_y=None):
"""Check if the stitch is too long before trying to deal with it."""
if length is None:
length = self.max_stitch
if new_x is None or new_y is None:
p = point_in_matrix_space(self.matrix,
self.stitch[0],
self.stitch[1])
new_x = p[0]
new_y = p[1]
distance_x = new_x - self.needle_x
distance_y = new_y - self.needle_y
return abs(distance_x) > length or abs(distance_y) > length
def interpolate_gap_stitches(self, x0, y0, x1, y1, max_length, data):
"""Command sequence line to x, y, respecting length as maximum.
This does not arrive_at, it steps to within striking distance.
The next step can arrive at (x, y) without violating constraint.
If these are already in range, this command will do nothing.
returns the last stitch interpolated by the code.
"""
transcode = self.destination_pattern.stitches
distance_x = x1 - x0
distance_y = y1 - y0
if abs(distance_x) > max_length or abs(distance_y) > max_length:
if data == JUMP and self.state_sequin_mode:
self.toggle_sequins() # can't jump with sequin mode on.
# python 2,3 patch of division that could be integer.
steps_x = math.ceil(abs(distance_x / (max_length * 1.0)))
steps_y = math.ceil(abs(distance_y / (max_length * 1.0)))
if steps_x > steps_y:
steps = steps_x
else:
steps = steps_y
step_size_x = distance_x / steps
step_size_y = distance_y / steps
qx = x0
qy = y0
for q in range(1, int(steps)):
# we need the gap stitches only, not start or end stitch.
qx += step_size_x
qy += step_size_y
stitch = [round(qx), round(qy), data]
transcode.append(stitch)
self.update_needle_position(stitch[0], stitch[1])
def lock_stitch(self, x, y, anchor_x, anchor_y, max_length=None):
"""Tie-on, Tie-off. Lock stitch from current location towards
anchor location.Ends again at lock location. May not exceed
max_length in the process."""
if max_length is None:
max_length = self.max_stitch
transcode = self.destination_pattern.stitches
length = distance(x, y, anchor_x, anchor_y)
if length > max_length:
p = oriented(x, y, anchor_x, anchor_y, max_length)
anchor_x = p[0]
anchor_y = p[1]
for amount in (.33, .66, .33, 0):
transcode.append([
towards(x, anchor_x, amount),
towards(y, anchor_y, amount),
STITCH])
def distance_squared(x0, y0, x1, y1):
"""squared of distance between x0,y0 and x1,y1"""
dx = x1 - x0
dy = y1 - y0
dx *= dx
dy *= dy
return dx + dy
def distance(x0, y0, x1, y1):
"""distance between x0,y0 and x1,y1"""
return math.sqrt(distance_squared(x0, y0, x1, y1))
def towards(a, b, amount):
"""amount between [0,1] -> [a,b]"""
return (amount * (b - a)) + a
def angle_radians(x0, y0, x1, y1):
"""Angle in radians between x0,y0 and x1,y1"""
return math.atan2(y1 - y0, x1 - x0)
def oriented(x0, y0, x1, y1, r):
"""from x0,y0 in the direction of x1,y1 in the distance of r"""
radians = angle_radians(x0, y0, x1, y1)
return x0 + (r * math.cos(radians)), y0 + (r * math.sin(radians))
def get_identity():
return \
1, 0, 0, \
0, 1, 0, \
0, 0, 1 # identity
def get_scale(sx, sy=None):
if sy is None:
sy = sx
return \
sx, 0, 0, \
0, sy, 0, \
0, 0, 1
def get_translate(tx, ty):
return \
1, 0, 0, \
0, 1, 0, \
tx, ty, 1
def get_rotate(theta):
tau = math.pi * 2
theta *= tau / 360
ct = math.cos(theta)
st = math.sin(theta)
return \
ct, st, 0, \
-st, ct, 0, \
0, 0, 1
def matrix_multiply(a, b):
return [
a[0] * b[0] + a[1] * b[3] + a[2] * b[6],
a[0] * b[1] + a[1] * b[4] + a[2] * b[7],
a[0] * b[2] + a[1] * b[5] + a[2] * b[8],
a[3] * b[0] + a[4] * b[3] + a[5] * b[6],
a[3] * b[1] + a[4] * b[4] + a[5] * b[7],
a[3] * b[2] + a[4] * b[5] + a[5] * b[8],
a[6] * b[0] + a[7] * b[3] + a[8] * b[6],
a[6] * b[1] + a[7] * b[4] + a[8] * b[7],
a[6] * b[2] + a[7] * b[5] + a[8] * b[8]]
def point_in_matrix_space(matrix, v0, v1=None):
if v1 is None:
try:
return [
v0[0] * matrix[0] + v0[1] * matrix[3] + 1 * matrix[6],
v0[0] * matrix[1] + v0[1] * matrix[4] + 1 * matrix[7],
v0[2]
]
except IndexError:
return [
v0[0] * matrix[0] + v0[1] * matrix[3] + 1 * matrix[6],
v0[0] * matrix[1] + v0[1] * matrix[4] + 1 * matrix[7]
# Must not have had a 3rd element.
]
return [
v0 * matrix[0] + v1 * matrix[3] + 1 * matrix[6],
v0 * matrix[1] + v1 * matrix[4] + 1 * matrix[7]
]