Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TBF Writer, improve TBF-Reader #166

Merged
merged 31 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
767aa8c
Add provisional TBF Writer
tatarize Oct 27, 2023
8295693
Corrections to TBFWriter
tatarize Oct 27, 2023
b2a1eaa
Add tbf-read/write shorthand
tatarize Oct 27, 2023
712379b
Add convert tests for tbf format
tatarize Oct 27, 2023
9a2d796
Change version range of the tests being made
tatarize Oct 27, 2023
70aabe4
Fix thread order writing pattern
tatarize Oct 27, 2023
8818ba6
Correct threads colorchange sequence
tatarize Oct 27, 2023
82420ee
Add color pattern shift needle set values
tatarize Oct 27, 2023
c9ad54d
Correct shift_pattern with needle sets
tatarize Oct 27, 2023
113f6f9
Update reader to express needle values given the thread-order
tatarize Oct 27, 2023
21cf9da
During pattern building do not use needle=0
tatarize Oct 27, 2023
0e95ff6
Update and correct needles test for U01 and TBF
tatarize Oct 27, 2023
ef6111c
Use needle set for TBF writer
tatarize Oct 27, 2023
33eb233
Update the readme for the added format
tatarize Oct 27, 2023
71f7794
Add alternative simple version of needle shift
tatarize Oct 27, 2023
032c75a
Update tests for needle-set
tatarize Oct 27, 2023
14f61b5
Update unittests for 3.8 and 3.12
tatarize Oct 27, 2023
8d324b8
formatting
tatarize Oct 27, 2023
769f23f
Add in would-be failing test for lookup-color error
tatarize Oct 27, 2023
2224a2c
Add writer call for `explicit_trim`
tatarize Oct 27, 2023
60e4da7
Set TbfWriter.py to request explicit trims
tatarize Oct 27, 2023
54eaaa9
Needle-set just moves the colors from the source to destination
tatarize Oct 27, 2023
d522956
update test file
tatarize Oct 27, 2023
f23cc2b
Fix writer's count of needle set values
tatarize Oct 27, 2023
c14647a
Update test_need_tbf writing of range
tatarize Oct 27, 2023
f033515
Change position of 0d1a
tatarize Oct 27, 2023
1aefcc7
Remove unused load/save test
tatarize Oct 27, 2023
8667d11
Add optional ct0 writer for tbf
tatarize Oct 27, 2023
7268266
Add optional ct0 write test
tatarize Oct 27, 2023
4a24598
Version bump 1.4.37
tatarize Nov 6, 2023
0cf034c
Fix move_center_to_origin
tatarize Nov 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2019, macos-11]
python-version: [3.11]
python-version: [3.8, 3.12]

steps:

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pyembroidery must to be small enough to be finished in short order and big enoug
* pyembroidery must fully support commands: STITCH, JUMP, TRIM, STOP, END, COLOR_CHANGE, NEEDLE_SET, SEQUIN_MODE and SEQUIN_EJECT.

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

Pyembroidery will read:
Expand Down Expand Up @@ -288,6 +289,7 @@ pyembroidery.write_u01(pattern, file)
pyembroidery.write_svg(pattern, file)
pyembroidery.write_csv(pattern, file)
pyembroidery.write_xxx(pattern, file)
pyembroidery.write_tbf(pattern, file)
pyembroidery.write_png(pattern, file)
pyembroidery.write_txt(pattern, file)
pyemboridery.write_gcode(pattern,file)
Expand Down
17 changes: 6 additions & 11 deletions pyembroidery/EmbEncoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ def get_as_thread_change_sequence_events(self):
source = self.source_pattern.stitches
current_index = 0
for stitch in source:
change = decode_embroidery_command(stitch[2])
command = change[0]
flags = command & COMMAND_MASK
flags, thread, needle, order = decode_embroidery_command(stitch[2])
if current_index == 0:
if (
flags == STITCH
Expand All @@ -117,15 +115,8 @@ def get_as_thread_change_sequence_events(self):
):
current_index = 1
if flags == SET_CHANGE_SEQUENCE:
thread = change[1]
needle = change[2]
order = change[3]
yield flags, thread, needle, order, None
elif flags == NEEDLE_SET or flags == COLOR_CHANGE or flags == COLOR_BREAK:
change = decode_embroidery_command(command)
thread = change[1]
needle = change[2]
order = change[3]
yield flags, thread, needle, order, current_index
current_index += 1

Expand Down Expand Up @@ -203,6 +194,8 @@ def transcode_main(self):
self.position = 0
self.order_index = -1
self.change_sequence = self.build_thread_change_sequence()
if self.thread_change_command == NEEDLE_SET:
self.destination_pattern.threadlist.extend(self.source_pattern.threadlist)

flags = NO_COMMAND
for self.position, self.stitch in enumerate(source):
Expand Down Expand Up @@ -593,13 +586,15 @@ def next_change_sequence(self):
self.order_index += 1
change = self.change_sequence[self.order_index]
threadlist = self.destination_pattern.threadlist
threadlist.append(change[3])
if self.thread_change_command == COLOR_CHANGE:
threadlist.append(change[3])
if self.order_index != 0:
self.add_thread_change(COLOR_CHANGE, change[1], change[2])
elif self.thread_change_command == NEEDLE_SET:
# We do not append the thread, we already have all threads
self.add_thread_change(NEEDLE_SET, change[1], change[2])
elif self.thread_change_command == STOP:
threadlist.append(change[3])
self.add_thread_change(STOP, change[1], change[2])
self.state_trimmed = True

Expand Down
21 changes: 19 additions & 2 deletions pyembroidery/EmbPattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import pyembroidery.SvgWriter as SvgWriter
import pyembroidery.TapReader as TapReader
import pyembroidery.TbfReader as TbfReader
import pyembroidery.TbfWriter as TbfWriter
import pyembroidery.TxtWriter as TxtWriter
import pyembroidery.U01Reader as U01Reader
import pyembroidery.U01Writer as U01Writer
Expand Down Expand Up @@ -476,8 +477,8 @@ def get_singleton_threadlist(self):

def move_center_to_origin(self):
extends = self.bounds()
cx = round((extends[2] - extends[0]) / 2.0)
cy = round((extends[3] - extends[1]) / 2.0)
cx = round((extends[2] + extends[0]) / 2.0)
cy = round((extends[3] + extends[1]) / 2.0)
self.translate(-cx, -cy)

def translate(self, dx, dy):
Expand Down Expand Up @@ -1175,6 +1176,7 @@ def supported_formats():
"mimetype": "application/x-tbf",
"category": "embroidery",
"reader": TbfReader,
"writer": TbfWriter,
}
)
yield (
Expand Down Expand Up @@ -1544,6 +1546,11 @@ def read_xxx(f, settings=None, pattern=None):
"""Reads fileobject as XXX file"""
return EmbPattern.read_embroidery(XxxReader, f, settings, pattern)

@staticmethod
def read_tbf(f, settings=None, pattern=None):
"""Reads fileobject as TBF file"""
return EmbPattern.read_embroidery(TbfReader, f, settings, pattern)

@staticmethod
def static_read(filename, settings=None, pattern=None):
"""Reads file, assuming type by extension"""
Expand Down Expand Up @@ -1605,6 +1612,11 @@ def write_embroidery(writer, pattern, stream, settings=None):
settings["thread_change_command"] = writer.THREAD_CHANGE_COMMAND
except AttributeError:
pass
if not ("explicit_trim" in settings):
try:
settings["explicit_trim"] = writer.EXPLICIT_TRIM
except AttributeError:
pass
if not ("translate" in settings):
try:
settings["translate"] = writer.TRANSLATE
Expand Down Expand Up @@ -1698,6 +1710,11 @@ def write_xxx(pattern, stream, settings=None):
"""Writes fileobject as XXX file"""
EmbPattern.write_embroidery(XxxWriter, pattern, stream, settings)

@staticmethod
def write_tbf(pattern, stream, settings=None):
"""Writes fileobject as TBF file"""
EmbPattern.write_embroidery(TbfWriter, pattern, stream, settings)

@staticmethod
def write_svg(pattern, stream, settings=None):
"""Writes fileobject as DST file"""
Expand Down
2 changes: 2 additions & 0 deletions pyembroidery/PyEmbroidery.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
read_csv = EmbPattern.read_csv
read_gcode = EmbPattern.read_gcode
read_xxx = EmbPattern.read_xxx
read_tbf = EmbPattern.read_tbf
read = EmbPattern.static_read

write_embroidery = EmbPattern.write_embroidery
Expand All @@ -31,6 +32,7 @@
write_txt = EmbPattern.write_txt
write_gcode = EmbPattern.write_gcode
write_xxx = EmbPattern.write_xxx
write_tbf = EmbPattern.write_tbf
write_svg = EmbPattern.write_svg
write_png = EmbPattern.write_png
write = EmbPattern.static_write
Expand Down
20 changes: 15 additions & 5 deletions pyembroidery/TbfReader.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from .EmbThread import EmbThread
from .ReadHelper import read_int_8, read_int_24be, signed8
from .ReadHelper import read_int_8, read_int_24be, signed8, read_string_8


def read(f, out, settings=None):
f.seek(0x83, 0)
name = read_string_8(f, 0x10).strip()
out.metadata("name", name)
f.seek(0x10A, 0)
thread_order = list(f.read(0x100))
f.seek(0x20E, 0)
while True:
if read_int_8(f) == 0x45:
Expand All @@ -14,9 +19,8 @@ def read(f, out, settings=None):
break
f.seek(0x600, 0)

count = 0
needle = 0
while True:
count += 1
byte = bytearray(f.read(3))
if len(byte) != 3:
break
Expand All @@ -27,8 +31,14 @@ def read(f, out, settings=None):
out.stitch(signed8(x), -signed8(y))
continue
elif ctrl == 0x81:
if count > 1: # This might rather be a needle change.
out.color_change()
needle_value = thread_order[needle]
needle += 1
if needle_value == 0:
# Needle value 0, shouldn't typically exist, but if it does its considered stop.
out.stop()
else:
out.needle_change(needle=needle_value)
continue
elif ctrl == 0x90:
if x == 0 and y == 0:
out.trim()
Expand Down
163 changes: 163 additions & 0 deletions pyembroidery/TbfWriter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from . import decode_embroidery_command
from .EmbConstant import *
from .WriteHelper import write_string_utf8, write_int_8

FULL_JUMP = False
ROUND = True
MAX_JUMP_DISTANCE = 127
MAX_STITCH_DISTANCE = 127
THREAD_CHANGE_COMMAND = NEEDLE_SET
EXPLICIT_TRIM = True


def write(pattern, f, settings=None):
if settings is not None and "ct0" in settings:
ct0 = settings.get("ct0")
write_ct0(pattern, ct0)
bounds = pattern.bounds()

name = pattern.get_metadata("name", "Untitled")
write_string_utf8(f, "3.00")
for i in range(f.tell(), 0x80):
f.write(b"\x20") # space
write_string_utf8(f, "LA:%-16s\r" % name)
write_string_utf8(f, "ST:%7d\r" % pattern.count_stitches())
write_string_utf8(f, "CO:%3d\r" % pattern.count_needle_sets())

write_string_utf8(f, "+X:%5d\r" % abs(bounds[2]))
write_string_utf8(f, "-X:%5d\r" % abs(bounds[0]))
write_string_utf8(f, "+Y:%5d\r" % abs(bounds[3]))
write_string_utf8(f, "-Y:%5d\r" % abs(bounds[1]))
ax = 0
ay = 0
if len(pattern.stitches) > 0:
last = len(pattern.stitches) - 1
ax = int(pattern.stitches[last][0])
ay = -int(pattern.stitches[last][1])
if ax >= 0:
write_string_utf8(f, "AX:+%5d\r" % ax)
else:
write_string_utf8(f, "AX:-%5d\r" % abs(ax))
if ay >= 0:
write_string_utf8(f, "AY:+%5d\r" % ay)
else:
write_string_utf8(f, "AY:-%5d\r" % abs(ay))

# TP is unknown.
tp = pattern.get_metadata("tp", "EG/")
write_string_utf8(f, "TP:%-32s\r" % tp)

# JC is unknown.
jc = "3"
write_string_utf8(f, "JC:%s\r" % jc)

# DO is the thread order.
write_string_utf8(f, "DO:")
thread_order = [0] * 0x100
index = 0
for stitch in pattern.stitches:
data = stitch[2] & COMMAND_MASK
if data == NEEDLE_SET:
flag, thread, needle, order = decode_embroidery_command(stitch[2])
thread_order[index] = needle
index += 1
for n in thread_order:
write_int_8(f, n)
write_string_utf8(f, "\r")

# DA is the threadlist. This is not *only* the used threads but any threads in the set.
write_string_utf8(f, "DA:")
if len(pattern.threadlist) > 0:
for thread in pattern.threadlist:
write_int_8(f, 0x45)
write_int_8(f, thread.get_red())
write_int_8(f, thread.get_green())
write_int_8(f, thread.get_blue())
write_int_8(f, 0x20)

# Padding to 501
for i in range(f.tell(), 0x376):
f.write(b"\x20") # space

# Seen in only some files.
f.write(b"\x0d\x1A")

# Pad to the end of the header.
for i in range(f.tell(), 0x600):
f.write(b"\x20") # space
# END HEADER

stitches = pattern.stitches
xx = 0
yy = 0
for stitch in stitches:
x = stitch[0]
y = stitch[1]
data = stitch[2] & COMMAND_MASK
dx = int(round(x - xx))
dy = int(round(y - yy))
xx += dx
yy += dy

if data == STITCH:
cmd = 0x80
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
elif data == JUMP:
cmd = 0x90
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
elif data == STOP:
cmd = 0x40
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
elif data == TRIM:
cmd = 0x86
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
elif data == NEEDLE_SET:
cmd = 0x81
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
elif data == END:
cmd = 0x8F
f.write(bytes(bytearray([dx & 0xFF, dy & 0xFF, cmd])))
break
# Terminal character.
f.write(b"\x1a")


def write_ct0(pattern, filename, settings=None):
with open(filename, "wb") as f:
_write_ct0(pattern, f, settings=settings)


def _write_ct0(pattern, f, settings=None):
write_string_utf8(f, "TAJ-DGML-PULSE 1-1A 2060(550.0")
write_int_8(f, 0x81)
write_string_utf8(f, "~400.0)S 2.00")
for i in range(f.tell(), 0x60):
f.write(b"\x20")
write_string_utf8(f, "DC1:100\rDC2:100\rDC3: 0\rDC4:N\rDC5:S\r")
for i in range(f.tell(), 0x108):
f.write(b"\x20")
write_string_utf8(f, "NS1:11")
index = 0
for stitch in pattern.stitches:
data = stitch[2] & COMMAND_MASK
if data == NEEDLE_SET:
flag, thread, needle, order = decode_embroidery_command(stitch[2])
write_int_8(f, needle + 0x30)
write_int_8(f, 0x31)
index += 1
for i in range(f.tell(), 0x30D):
f.write(b"\x20")
write_string_utf8(f, "\rRP0:N\rRP1: \rRP2: \rRP3: \rRP4: \rRP5: \rRP6: \rRP7: \rST1:")
for i in range(f.tell(), 0x434):
f.write(b"\x20")
write_string_utf8(f, "ST0:0\rAO1:0\rAO2:0\rAO3:0\rOF1: \rOF2: \rOF3: \rNS2:")
for i in range(index):
write_int_8(f, 0x30)
for i in range(f.tell(), 0x583):
f.write(b"\x20")
write_string_utf8(f, "\rNS3:")
for i in range(f.tell(), 0x778):
f.write(b"\x20")
write_string_utf8(f, "\r\x1A")
for i in range(f.tell(), 0x790):
f.write(b"\x20")
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="pyembroidery",
version="1.4.36",
version="1.4.38",
author="Tatarize",
author_email="tatarize@gmail.com",
description="Embroidery IO library",
Expand Down
Loading
Loading