Skip to content

Commit

Permalink
Merge pull request #166 from EmbroidePy/tbf-writer
Browse files Browse the repository at this point in the history
Add TBF Writer, improve TBF-Reader
  • Loading branch information
tatarize authored Nov 6, 2023
2 parents 68057a0 + 0cf034c commit e004041
Show file tree
Hide file tree
Showing 19 changed files with 644 additions and 22 deletions.
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 @@ -1549,6 +1551,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 @@ -1610,6 +1617,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 @@ -1708,6 +1720,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 @@ -18,6 +18,7 @@
read_json = EmbPattern.read_json
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 @@ -33,6 +34,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

0 comments on commit e004041

Please sign in to comment.