Skip to content

Commit e004041

Browse files
authored
Merge pull request #166 from EmbroidePy/tbf-writer
Add TBF Writer, improve TBF-Reader
2 parents 68057a0 + 0cf034c commit e004041

19 files changed

+644
-22
lines changed

.github/workflows/unittests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
fail-fast: false
3131
matrix:
3232
os: [ubuntu-latest, windows-2019, macos-11]
33-
python-version: [3.11]
33+
python-version: [3.8, 3.12]
3434

3535
steps:
3636

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pyembroidery must to be small enough to be finished in short order and big enoug
2424
* pyembroidery must fully support commands: STITCH, JUMP, TRIM, STOP, END, COLOR_CHANGE, NEEDLE_SET, SEQUIN_MODE and SEQUIN_EJECT.
2525

2626
Pyembroidery fully meets and exceeds all of these requirements.
27-
* It writes 9 embroidery formats including the mandated ones. 19 different format in total.
27+
* It writes 10 embroidery formats including the mandated ones. 20 different format in total.
2828
* It reads 40 embroidery formats including the mandated ones. 46 different formats in total.
2929
* It supports all the core commands where that format can use said command as well as FAST and SLOW for .u01.
3030
* SEQUINS work in all supported formats (.dst) that are known to support sequins. Further it supports SEQUIN to JUMP operations on the other formats.
@@ -115,6 +115,7 @@ Pyembroidery will write:
115115
* .u01
116116
* .pec
117117
* .xxx
118+
* .tbf
118119
* .gcode
119120

120121
Pyembroidery will read:
@@ -288,6 +289,7 @@ pyembroidery.write_u01(pattern, file)
288289
pyembroidery.write_svg(pattern, file)
289290
pyembroidery.write_csv(pattern, file)
290291
pyembroidery.write_xxx(pattern, file)
292+
pyembroidery.write_tbf(pattern, file)
291293
pyembroidery.write_png(pattern, file)
292294
pyembroidery.write_txt(pattern, file)
293295
pyemboridery.write_gcode(pattern,file)

pyembroidery/EmbEncoder.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,7 @@ def get_as_thread_change_sequence_events(self):
105105
source = self.source_pattern.stitches
106106
current_index = 0
107107
for stitch in source:
108-
change = decode_embroidery_command(stitch[2])
109-
command = change[0]
110-
flags = command & COMMAND_MASK
108+
flags, thread, needle, order = decode_embroidery_command(stitch[2])
111109
if current_index == 0:
112110
if (
113111
flags == STITCH
@@ -117,15 +115,8 @@ def get_as_thread_change_sequence_events(self):
117115
):
118116
current_index = 1
119117
if flags == SET_CHANGE_SEQUENCE:
120-
thread = change[1]
121-
needle = change[2]
122-
order = change[3]
123118
yield flags, thread, needle, order, None
124119
elif flags == NEEDLE_SET or flags == COLOR_CHANGE or flags == COLOR_BREAK:
125-
change = decode_embroidery_command(command)
126-
thread = change[1]
127-
needle = change[2]
128-
order = change[3]
129120
yield flags, thread, needle, order, current_index
130121
current_index += 1
131122

@@ -203,6 +194,8 @@ def transcode_main(self):
203194
self.position = 0
204195
self.order_index = -1
205196
self.change_sequence = self.build_thread_change_sequence()
197+
if self.thread_change_command == NEEDLE_SET:
198+
self.destination_pattern.threadlist.extend(self.source_pattern.threadlist)
206199

207200
flags = NO_COMMAND
208201
for self.position, self.stitch in enumerate(source):
@@ -593,13 +586,15 @@ def next_change_sequence(self):
593586
self.order_index += 1
594587
change = self.change_sequence[self.order_index]
595588
threadlist = self.destination_pattern.threadlist
596-
threadlist.append(change[3])
597589
if self.thread_change_command == COLOR_CHANGE:
590+
threadlist.append(change[3])
598591
if self.order_index != 0:
599592
self.add_thread_change(COLOR_CHANGE, change[1], change[2])
600593
elif self.thread_change_command == NEEDLE_SET:
594+
# We do not append the thread, we already have all threads
601595
self.add_thread_change(NEEDLE_SET, change[1], change[2])
602596
elif self.thread_change_command == STOP:
597+
threadlist.append(change[3])
603598
self.add_thread_change(STOP, change[1], change[2])
604599
self.state_trimmed = True
605600

pyembroidery/EmbPattern.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import pyembroidery.SvgWriter as SvgWriter
5959
import pyembroidery.TapReader as TapReader
6060
import pyembroidery.TbfReader as TbfReader
61+
import pyembroidery.TbfWriter as TbfWriter
6162
import pyembroidery.TxtWriter as TxtWriter
6263
import pyembroidery.U01Reader as U01Reader
6364
import pyembroidery.U01Writer as U01Writer
@@ -476,8 +477,8 @@ def get_singleton_threadlist(self):
476477

477478
def move_center_to_origin(self):
478479
extends = self.bounds()
479-
cx = round((extends[2] - extends[0]) / 2.0)
480-
cy = round((extends[3] - extends[1]) / 2.0)
480+
cx = round((extends[2] + extends[0]) / 2.0)
481+
cy = round((extends[3] + extends[1]) / 2.0)
481482
self.translate(-cx, -cy)
482483

483484
def translate(self, dx, dy):
@@ -1175,6 +1176,7 @@ def supported_formats():
11751176
"mimetype": "application/x-tbf",
11761177
"category": "embroidery",
11771178
"reader": TbfReader,
1179+
"writer": TbfWriter,
11781180
}
11791181
)
11801182
yield (
@@ -1549,6 +1551,11 @@ def read_xxx(f, settings=None, pattern=None):
15491551
"""Reads fileobject as XXX file"""
15501552
return EmbPattern.read_embroidery(XxxReader, f, settings, pattern)
15511553

1554+
@staticmethod
1555+
def read_tbf(f, settings=None, pattern=None):
1556+
"""Reads fileobject as TBF file"""
1557+
return EmbPattern.read_embroidery(TbfReader, f, settings, pattern)
1558+
15521559
@staticmethod
15531560
def static_read(filename, settings=None, pattern=None):
15541561
"""Reads file, assuming type by extension"""
@@ -1610,6 +1617,11 @@ def write_embroidery(writer, pattern, stream, settings=None):
16101617
settings["thread_change_command"] = writer.THREAD_CHANGE_COMMAND
16111618
except AttributeError:
16121619
pass
1620+
if not ("explicit_trim" in settings):
1621+
try:
1622+
settings["explicit_trim"] = writer.EXPLICIT_TRIM
1623+
except AttributeError:
1624+
pass
16131625
if not ("translate" in settings):
16141626
try:
16151627
settings["translate"] = writer.TRANSLATE
@@ -1708,6 +1720,11 @@ def write_xxx(pattern, stream, settings=None):
17081720
"""Writes fileobject as XXX file"""
17091721
EmbPattern.write_embroidery(XxxWriter, pattern, stream, settings)
17101722

1723+
@staticmethod
1724+
def write_tbf(pattern, stream, settings=None):
1725+
"""Writes fileobject as TBF file"""
1726+
EmbPattern.write_embroidery(TbfWriter, pattern, stream, settings)
1727+
17111728
@staticmethod
17121729
def write_svg(pattern, stream, settings=None):
17131730
"""Writes fileobject as DST file"""

pyembroidery/PyEmbroidery.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
read_json = EmbPattern.read_json
1919
read_gcode = EmbPattern.read_gcode
2020
read_xxx = EmbPattern.read_xxx
21+
read_tbf = EmbPattern.read_tbf
2122
read = EmbPattern.static_read
2223

2324
write_embroidery = EmbPattern.write_embroidery
@@ -33,6 +34,7 @@
3334
write_txt = EmbPattern.write_txt
3435
write_gcode = EmbPattern.write_gcode
3536
write_xxx = EmbPattern.write_xxx
37+
write_tbf = EmbPattern.write_tbf
3638
write_svg = EmbPattern.write_svg
3739
write_png = EmbPattern.write_png
3840
write = EmbPattern.static_write

pyembroidery/TbfReader.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from .EmbThread import EmbThread
2-
from .ReadHelper import read_int_8, read_int_24be, signed8
2+
from .ReadHelper import read_int_8, read_int_24be, signed8, read_string_8
33

44

55
def read(f, out, settings=None):
6+
f.seek(0x83, 0)
7+
name = read_string_8(f, 0x10).strip()
8+
out.metadata("name", name)
9+
f.seek(0x10A, 0)
10+
thread_order = list(f.read(0x100))
611
f.seek(0x20E, 0)
712
while True:
813
if read_int_8(f) == 0x45:
@@ -14,9 +19,8 @@ def read(f, out, settings=None):
1419
break
1520
f.seek(0x600, 0)
1621

17-
count = 0
22+
needle = 0
1823
while True:
19-
count += 1
2024
byte = bytearray(f.read(3))
2125
if len(byte) != 3:
2226
break
@@ -27,8 +31,14 @@ def read(f, out, settings=None):
2731
out.stitch(signed8(x), -signed8(y))
2832
continue
2933
elif ctrl == 0x81:
30-
if count > 1: # This might rather be a needle change.
31-
out.color_change()
34+
needle_value = thread_order[needle]
35+
needle += 1
36+
if needle_value == 0:
37+
# Needle value 0, shouldn't typically exist, but if it does its considered stop.
38+
out.stop()
39+
else:
40+
out.needle_change(needle=needle_value)
41+
continue
3242
elif ctrl == 0x90:
3343
if x == 0 and y == 0:
3444
out.trim()

pyembroidery/TbfWriter.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
from . import decode_embroidery_command
2+
from .EmbConstant import *
3+
from .WriteHelper import write_string_utf8, write_int_8
4+
5+
FULL_JUMP = False
6+
ROUND = True
7+
MAX_JUMP_DISTANCE = 127
8+
MAX_STITCH_DISTANCE = 127
9+
THREAD_CHANGE_COMMAND = NEEDLE_SET
10+
EXPLICIT_TRIM = True
11+
12+
13+
def write(pattern, f, settings=None):
14+
if settings is not None and "ct0" in settings:
15+
ct0 = settings.get("ct0")
16+
write_ct0(pattern, ct0)
17+
bounds = pattern.bounds()
18+
19+
name = pattern.get_metadata("name", "Untitled")
20+
write_string_utf8(f, "3.00")
21+
for i in range(f.tell(), 0x80):
22+
f.write(b"\x20") # space
23+
write_string_utf8(f, "LA:%-16s\r" % name)
24+
write_string_utf8(f, "ST:%7d\r" % pattern.count_stitches())
25+
write_string_utf8(f, "CO:%3d\r" % pattern.count_needle_sets())
26+
27+
write_string_utf8(f, "+X:%5d\r" % abs(bounds[2]))
28+
write_string_utf8(f, "-X:%5d\r" % abs(bounds[0]))
29+
write_string_utf8(f, "+Y:%5d\r" % abs(bounds[3]))
30+
write_string_utf8(f, "-Y:%5d\r" % abs(bounds[1]))
31+
ax = 0
32+
ay = 0
33+
if len(pattern.stitches) > 0:
34+
last = len(pattern.stitches) - 1
35+
ax = int(pattern.stitches[last][0])
36+
ay = -int(pattern.stitches[last][1])
37+
if ax >= 0:
38+
write_string_utf8(f, "AX:+%5d\r" % ax)
39+
else:
40+
write_string_utf8(f, "AX:-%5d\r" % abs(ax))
41+
if ay >= 0:
42+
write_string_utf8(f, "AY:+%5d\r" % ay)
43+
else:
44+
write_string_utf8(f, "AY:-%5d\r" % abs(ay))
45+
46+
# TP is unknown.
47+
tp = pattern.get_metadata("tp", "EG/")
48+
write_string_utf8(f, "TP:%-32s\r" % tp)
49+
50+
# JC is unknown.
51+
jc = "3"
52+
write_string_utf8(f, "JC:%s\r" % jc)
53+
54+
# DO is the thread order.
55+
write_string_utf8(f, "DO:")
56+
thread_order = [0] * 0x100
57+
index = 0
58+
for stitch in pattern.stitches:
59+
data = stitch[2] & COMMAND_MASK
60+
if data == NEEDLE_SET:
61+
flag, thread, needle, order = decode_embroidery_command(stitch[2])
62+
thread_order[index] = needle
63+
index += 1
64+
for n in thread_order:
65+
write_int_8(f, n)
66+
write_string_utf8(f, "\r")
67+
68+
# DA is the threadlist. This is not *only* the used threads but any threads in the set.
69+
write_string_utf8(f, "DA:")
70+
if len(pattern.threadlist) > 0:
71+
for thread in pattern.threadlist:
72+
write_int_8(f, 0x45)
73+
write_int_8(f, thread.get_red())
74+
write_int_8(f, thread.get_green())
75+
write_int_8(f, thread.get_blue())
76+
write_int_8(f, 0x20)
77+
78+
# Padding to 501
79+
for i in range(f.tell(), 0x376):
80+
f.write(b"\x20") # space
81+
82+
# Seen in only some files.
83+
f.write(b"\x0d\x1A")
84+
85+
# Pad to the end of the header.
86+
for i in range(f.tell(), 0x600):
87+
f.write(b"\x20") # space
88+
# END HEADER
89+
90+
stitches = pattern.stitches
91+
xx = 0
92+
yy = 0
93+
for stitch in stitches:
94+
x = stitch[0]
95+
y = stitch[1]
96+
data = stitch[2] & COMMAND_MASK
97+
dx = int(round(x - xx))
98+
dy = int(round(y - yy))
99+
xx += dx
100+
yy += dy
101+
102+
if data == STITCH:
103+
cmd = 0x80
104+
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
105+
elif data == JUMP:
106+
cmd = 0x90
107+
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
108+
elif data == STOP:
109+
cmd = 0x40
110+
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
111+
elif data == TRIM:
112+
cmd = 0x86
113+
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
114+
elif data == NEEDLE_SET:
115+
cmd = 0x81
116+
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
117+
elif data == END:
118+
cmd = 0x8F
119+
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
120+
break
121+
# Terminal character.
122+
f.write(b"\x1a")
123+
124+
125+
def write_ct0(pattern, filename, settings=None):
126+
with open(filename, "wb") as f:
127+
_write_ct0(pattern, f, settings=settings)
128+
129+
130+
def _write_ct0(pattern, f, settings=None):
131+
write_string_utf8(f, "TAJ-DGML-PULSE 1-1A 2060(550.0")
132+
write_int_8(f, 0x81)
133+
write_string_utf8(f, "~400.0)S 2.00")
134+
for i in range(f.tell(), 0x60):
135+
f.write(b"\x20")
136+
write_string_utf8(f, "DC1:100\rDC2:100\rDC3: 0\rDC4:N\rDC5:S\r")
137+
for i in range(f.tell(), 0x108):
138+
f.write(b"\x20")
139+
write_string_utf8(f, "NS1:11")
140+
index = 0
141+
for stitch in pattern.stitches:
142+
data = stitch[2] & COMMAND_MASK
143+
if data == NEEDLE_SET:
144+
flag, thread, needle, order = decode_embroidery_command(stitch[2])
145+
write_int_8(f, needle + 0x30)
146+
write_int_8(f, 0x31)
147+
index += 1
148+
for i in range(f.tell(), 0x30D):
149+
f.write(b"\x20")
150+
write_string_utf8(f, "\rRP0:N\rRP1: \rRP2: \rRP3: \rRP4: \rRP5: \rRP6: \rRP7: \rST1:")
151+
for i in range(f.tell(), 0x434):
152+
f.write(b"\x20")
153+
write_string_utf8(f, "ST0:0\rAO1:0\rAO2:0\rAO3:0\rOF1: \rOF2: \rOF3: \rNS2:")
154+
for i in range(index):
155+
write_int_8(f, 0x30)
156+
for i in range(f.tell(), 0x583):
157+
f.write(b"\x20")
158+
write_string_utf8(f, "\rNS3:")
159+
for i in range(f.tell(), 0x778):
160+
f.write(b"\x20")
161+
write_string_utf8(f, "\r\x1A")
162+
for i in range(f.tell(), 0x790):
163+
f.write(b"\x20")

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name="pyembroidery",
8-
version="1.4.36",
8+
version="1.4.38",
99
author="Tatarize",
1010
author_email="[email protected]",
1111
description="Embroidery IO library",

0 commit comments

Comments
 (0)