from pyembroidery import JUMP, STITCH, TRIM, COLOR_CHANGE
import tkinter as tk
from tkinter.constants import LEFT, RIGHT
import os
from tqdm import tqdm
USE_SPHINX_GALLERY = False
# Check if in IDLE, disable if so
import sys
class non_idle_tqdm:
def __init__(self, iterable, *args, **kwargs):
self.iterable = iterable
if 'idlelib.run' not in sys.modules:
self.tqdm = tqdm
else:
self.tqdm = None
def __iter__(self):
if self.tqdm is not None:
return iter(self.tqdm(self.iterable))
else:
return iter(self.iterable)
class no_tqdm:
def __init__(self, iterable, *args, **kwargs):
self.iterable = iterable
def __iter__(self):
return iter(self.iterable)
def __call__(self, *args, **kwargs):
return iter(self.iterable)
def get_dimensions(stitches):
if len(stitches) == 0: return 0, 0
x = [s[0] for s in stitches]
y = [s[1] for s in stitches]
return max(*x) - min(*x), max(*y) - min(*y)
def centered_dot(turtle, diameter):
speed = turtle.speed()
turtle.speed(0)
pensize = turtle.pensize()
turtle.pensize(diameter)
turtle.goto(turtle.position())
turtle.pensize(pensize)
turtle.speed(speed)
def centered_cross(turtle, length):
speed = turtle.speed()
turtle.speed(0)
r = length / 2
x, y = turtle.position()
turtle.goto(x + r, y + r)
turtle.goto(x - r, y - r)
turtle.goto(x, y)
turtle.goto(x + r, y - r)
turtle.goto(x - r, y + r)
turtle.goto(x, y)
turtle.speed(speed)
def centered_line(turtle, length):
speed = turtle.speed()
turtle.speed(0)
r = length / 2
tr = turtle._tracer()
dl = turtle._delay()
turtle._tracer(0, 0)
x, y = turtle.position()
turtle.right(90)
turtle.forward(r)
turtle.penup()
turtle.backward(r)
turtle.pendown()
turtle.backward(r)
turtle.penup()
turtle.forward(r)
turtle.left(90)
turtle.pendown()
turtle._tracer(tr, dl)
turtle.speed(speed)
def _finish_visualise(done, bye):
import turtle # Import turtle only here to avoid cluttering module namespace
if done:
try:
turtle.done()
except turtle.Terminator:
pass
if bye:
try:
turtle.bye()
except turtle.Terminator:
pass
[docs]
def visualise_pattern(pattern, turtle=None, width=800, height=800, scale=1, speed=6, trace_jump=False, skip=False,
check_density=True, done=True, bye=True):
"""Use the builtin ``turtle`` library to visualise an embroidery pattern.
Parameters
----------
pattern : pyembroidery.EmbPattern
Embroidery pattern to visualise
turtle : turtle.Turtle (optional)
Python turtle object to use for drawing. If not specified, then the default turtle
is used.
width : int
Canvas width
height : int
Canvas height
scale : int
Factor the embroidery length's are scaled by.
speed : int
Speed that the turtle object moves at.
trace_jump : bool
If True, then draw a grey line connecting the origin and destination of jumps.
skip : bool
If True, then skip the drawing animation and jump to the completed visualisation.
check_density : bool
If True, then check the density of the embroidery pattern. Recommended but slow.
done : bool
If True, then ``turtle.done()`` will be called after drawing.
bye : bool
If True, then ``turtle.bye()`` will be called after drawing.
"""
if USE_SPHINX_GALLERY:
return
# Lazy import of 'turtle' module just for visualization so that the rest of
# the library can be used on Python installs where the GUI libraries are not
# available.
#
# (This looks like it would conflict with the 'turtle' variable but it does not)
from turtle import Screen, Turtle
if os.name == 'nt':
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(2)
if turtle is None:
# If turtle is None, grab the default turtle and set its speed to fastest
if Turtle._pen is None:
Turtle._pen = Turtle()
turtle = Turtle._pen
turtle.speed(speed)
if skip:
turtle._tracer(0)
screen = Screen()
screen.setup(width, height)
canvas = screen.getcanvas()
root = canvas.master
# Draw grid
# Vertical thin
i = 0
while i < 1000:
canvas.create_line(i, -1000, i, 1000, width=2, fill="#eeeeee")
i += 100*scale
i = 0
while i > -1000:
canvas.create_line(i, -1000, i, 1000, width=2, fill="#eeeeee")
i -= 100 * scale
# Horizontal thin
i = 0
while i < 1000:
canvas.create_line(-1000, i, 1000, i, width=2, fill="#eeeeee")
i += 100 * scale
i = 0
while i > -1000:
canvas.create_line(-1000, i, 1000, i, width=2, fill="#eeeeee")
i -= 100 * scale
# Vertical thick
i = 0
while i < 1000:
canvas.create_line(i, -1000, i, 1000, width=5, fill="#cccccc")
i += 500 * scale
i = 0
while i > -1000:
canvas.create_line(i, -1000, i, 1000, width=5, fill="#cccccc")
i -= 500 * scale
# Horizontal thick
i = 0
while i < 1000:
canvas.create_line(-1000, i, 1000, i, width=5, fill="#cccccc")
i += 500 * scale
i = 0
while i > -1000:
canvas.create_line(-1000, i, 1000, i, width=5, fill="#cccccc")
i -= 500 * scale
# Write to window directly by getting tkinter object - a bit cursed
# There probably is a better way to do it
# Calculate estimated pattern size
xy = [i / 100 for i in get_dimensions(pattern.stitches)] # 100 units = 1cm
# Write pattern size
tk.Label(
root,
text=f"Width: {xy[0]:.2f} cm\nHeight: {xy[1]:.2f} cm",
justify=LEFT
).pack(anchor="s", side="left")
# Count number of stitches
stitch_count = sum([1 if command == STITCH else 0 for *_, command in pattern.stitches])
# Estimate time assuming 600spm
time = stitch_count / 10
s = time % 60
m = time // 60 % 60
h = time // 3600
# Write stitch count
tk.Label(
root,
text=f"{stitch_count} stitches in total\n{f'{h}h' if h else ''}{f'{m}m' if m else ''}{s:.1f}s (assuming 600spm)",
justify=RIGHT
).pack(anchor="s", side="right")
if check_density and density(pattern.stitches):
tk.Label(
root,
text=f"WARNING: POTENTIAL HIGH STITCH DENSITY",
fg="#f00"
).pack(side="bottom")
if len(pattern.stitches) == 0:
_finish_visualise(done=done, bye=bye)
return
turtle.penup()
turtle.goto(pattern.stitches[0][0], pattern.stitches[0][1])
turtle.pendown()
raise_error = False
threads = list(pattern.threadlist)
thread_idx = 0
progressbar = non_idle_tqdm
if skip:
print("Rendering... (show animation with skip=False)")
progressbar = no_tqdm
else:
print("Visualising... (skip with skip=True)")
if len(pattern.stitches) < 50:
progressbar = no_tqdm
for x, y, command in progressbar(pattern.stitches):
x = scale * x
y = scale * y
if command == JUMP:
# turtle.color("red")
turtle.color(0.8, 0.8, 0.8)
if not trace_jump: turtle.penup()
turtle.goto(x, -y)
turtle.pendown()
centered_dot(turtle, 10 * scale)
elif command == TRIM:
turtle.penup()
turtle.goto(x, -y)
turtle.pendown()
# turtle.color("black")
turtle.color(0.8, 0.8, 0.8)
centered_cross(turtle, 10 * scale)
elif command == STITCH:
turtle.setheading(turtle.towards(x, -y))
if len(threads) > 0:
turtle.color(threads[thread_idx].hex_color())
else:
turtle.color("blue")
# 20% 60% 20%
# blank line blank
# if stitch is long, limit blank part
xcur, ycur = turtle.position()
d = ((xcur - x) ** 2 + (ycur - (-y)) ** 2) ** 0.5 # maybe find a way to avoid fp errors here? prob unnecessary
blank = min(d/5, 2)
solid = d - 2*blank
w = turtle.width()
turtle.width(2)
turtle.penup()
turtle.forward(blank)
turtle.pendown()
turtle.forward(solid)
turtle.penup()
turtle.forward(blank)
turtle.pendown()
turtle.width(w)
elif command == COLOR_CHANGE:
thread_idx += 1
else:
raise_error = True
break
if skip:
turtle._update()
turtle._tracer(1)
_finish_visualise(done=done, bye=bye)
if raise_error:
ValueError(f"Command not supported: {command}")
def density(stitches):
# Considering that most stitches are of relatively short length,
# we can estimate the each stitch by some equally spaced apart
# points, and then find areas with high density.
# TODO: make algorithm work for longer stitches as well
if len(stitches) == 0: return False
# Get sampling points
points = []
prevx, prevy = stitches[0][:2]
for x, y, t in stitches[1:]:
if t == STITCH:
points.append(((x+prevx)/4, (y+prevy)/4))
points.append(((x+prevx)/2, (y+prevy)/2))
points.append((3*(x+prevx)/4, 3*(y+prevy)/4))
return density_from_points(points, dist=0.5, num=20)
def density_from_points(pts, dist=0.5, num=20):
print("Checking density of stitches... (skip with check_density=False)")
adjmat = [ [ -1 for j in range(len(pts))] for i in range(len(pts))]
progressbar = non_idle_tqdm
if len(pts) < 5000:
progressbar = no_tqdm
for i in progressbar(range(len(pts))):
adjmat[i][i] = 1 # close enough
for j in range(i+1, len(pts)):
dx = pts[i][0] - pts[j][0]
dy = pts[i][1] - pts[j][1]
if (dx**2 + dy**2 < dist**2):
# close enough!!
adjmat[i][j] = 1
adjmat[j][i] = 1
else:
adjmat[i][j] = 0
adjmat[j][i] = 0
# pruning: check if already reached
if sum(adjmat[i]) >= num:
return True
return False
from . import stitches
def fast_visualise(te, turtle=None, width=800, height=800, scale=1, speed=0, extra_speed=1, trace_jump=False, skip=False,
check_density=True, done=True, bye=True):
"""Use the builtin ``turtle`` library to visualise an embroidery pattern.
Parameters
----------
te : turtlethread.Turtle
TurtleThread Turtle object to visualise
turtle : turtle.Turtle (optional)
Python turtle object to use for drawing. If not specified, then the default turtle
is used.
width : int
Canvas width
height : int
Canvas height
scale : int
Factor the embroidery length's are scaled by.
speed : int
Speed that the turtle object moves at.
trace_jump : bool
If True, then draw a grey line connecting the origin and destination of jumps.
skip : bool
If True, then skip the drawing animation and jump to the completed visualisation.
check_density : bool
If True, then check the density of the embroidery pattern. Recommended but slow.
done : bool
If True, then ``turtle.done()`` will be called after drawing.
bye : bool
If True, then ``turtle.bye()`` will be called after drawing.
"""
pattern = te.pattern.to_pyembroidery()
if USE_SPHINX_GALLERY:
return
# Lazy import of 'turtle' module just for visualization so that the rest of
# the library can be used on Python installs where the GUI libraries are not
# available.
#
# (This looks like it would conflict with the 'turtle' variable but it does not)
from turtle import Screen, Turtle
if os.name == 'nt':
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(2)
if turtle is None:
# If turtle is None, grab the default turtle and set its speed to fastest
if Turtle._pen is None:
Turtle._pen = Turtle()
turtle = Turtle._pen
turtle.speed(speed)
if skip:
turtle._tracer(0)
screen = Screen()
screen.setup(width, height)
canvas = screen.getcanvas()
root = canvas.master
# Draw grid
# Vertical thin
i = 0
while i < 1000:
canvas.create_line(i, -1000, i, 1000, width=2, fill="#eeeeee")
i += 100*scale
i = 0
while i > -1000:
canvas.create_line(i, -1000, i, 1000, width=2, fill="#eeeeee")
i -= 100 * scale
# Horizontal thin
i = 0
while i < 1000:
canvas.create_line(-1000, i, 1000, i, width=2, fill="#eeeeee")
i += 100 * scale
i = 0
while i > -1000:
canvas.create_line(-1000, i, 1000, i, width=2, fill="#eeeeee")
i -= 100 * scale
# Vertical thick
i = 0
while i < 1000:
canvas.create_line(i, -1000, i, 1000, width=5, fill="#cccccc")
i += 500 * scale
i = 0
while i > -1000:
canvas.create_line(i, -1000, i, 1000, width=5, fill="#cccccc")
i -= 500 * scale
# Horizontal thick
i = 0
while i < 1000:
canvas.create_line(-1000, i, 1000, i, width=5, fill="#cccccc")
i += 500 * scale
i = 0
while i > -1000:
canvas.create_line(-1000, i, 1000, i, width=5, fill="#cccccc")
i -= 500 * scale
# Write to window directly by getting tkinter object - a bit cursed
# There probably is a better way to do it
# Calculate estimated pattern size
xy = [i / 100 for i in get_dimensions(pattern.stitches)] # 100 units = 1cm
# Write pattern size
tk.Label(
root,
text=f"Width: {xy[0]:.2f} cm\nHeight: {xy[1]:.2f} cm",
justify=LEFT
).pack(anchor="s", side="left")
# Count number of stitches
stitch_count = sum([1 if command == STITCH else 0 for *_, command in pattern.stitches])
# Estimate time assuming 600spm
time = stitch_count / 10
s = time % 60
m = time // 60 % 60
h = time // 3600
# Write stitch count
tk.Label(
root,
text=f"{stitch_count} stitches in total\n{f'{h}h' if h else ''}{f'{m}m' if m else ''}{s:.1f}s (assuming 600spm)",
justify=RIGHT
).pack(anchor="s", side="right")
if check_density and density(pattern.stitches):
tk.Label(
root,
text=f"WARNING: POTENTIAL HIGH STITCH DENSITY",
fg="#f00"
).pack(side="bottom")
if len(pattern.stitches) == 0:
_finish_visualise(done=done, bye=bye)
return
turtle.penup()
turtle.goto(pattern.stitches[0][0], pattern.stitches[0][1])
turtle.pendown()
raise_error = False
threads = list(pattern.threadlist)
thread_idx = 0
progressbar = non_idle_tqdm
if skip:
print("Rendering... (show animation with skip=False)")
progressbar = no_tqdm
else:
print("Visualising... (skip with skip=True)")
if len(pattern.stitches) < 50:
progressbar = no_tqdm
for i in range(len(te.pattern.stitch_groups)):
#print(te.pattern.stitch_groups[i]._parent_stitch_group)
#speedup = isinstance(te.pattern.stitch_groups[i]._parent_stitch_group, stitches.DirectStitch)
#speedup |= isinstance(te.pattern.stitch_groups[i]._parent_stitch_group, stitches.SatinStitch)
speedup = te.pattern.stitch_groups[i].__class__.speedup
if speedup:
turtle._tracer(extra_speed+speedup)
else:
turtle._tracer(extra_speed)
#for x, y, command in progressbar(pattern.stitches):
for x, y, command in te.pattern.get_pyembroidery_of(i).stitches:
x = scale * x
y = scale * y
if command == JUMP:
# turtle.color("red")
turtle.color(0.8, 0.8, 0.8)
if not trace_jump: turtle.penup()
turtle.goto(x, -y)
turtle.pendown()
centered_dot(turtle, 10 * scale)
elif command == TRIM:
turtle.penup()
turtle.goto(x, -y)
turtle.pendown()
# turtle.color("black")
turtle.color(0.8, 0.8, 0.8)
centered_cross(turtle, 10 * scale)
elif command == STITCH:
turtle.setheading(turtle.towards(x, -y))
if len(threads) > 0:
turtle.color(threads[thread_idx].hex_color())
else:
turtle.color("blue")
# 20% 60% 20%
# blank line blank
# if stitch is long, limit blank part
xcur, ycur = turtle.position()
d = ((xcur - x) ** 2 + (ycur - (-y)) ** 2) ** 0.5 # maybe find a way to avoid fp errors here? prob unnecessary
blank = min(d/5, 2)
solid = d - 2*blank
w = turtle.width()
turtle.width(2)
turtle.penup()
turtle.forward(blank)
turtle.pendown()
turtle.forward(solid)
turtle.penup()
turtle.forward(blank)
turtle.pendown()
turtle.width(w)
elif command == COLOR_CHANGE:
thread_idx += 1
else:
raise_error = True
break
if skip:
turtle._update()
turtle._tracer(1)
_finish_visualise(done=done, bye=bye)
if raise_error:
ValueError(f"Command not supported: {command}")