Skip to content

Initial SewWriter implementation#135

Merged
kaalleen merged 3 commits into
inkstitch:mainfrom
Zedeldi:feature/sew-writer
Jun 9, 2026
Merged

Initial SewWriter implementation#135
kaalleen merged 3 commits into
inkstitch:mainfrom
Zedeldi:feature/sew-writer

Conversation

@Zedeldi

@Zedeldi Zedeldi commented May 2, 2026

Copy link
Copy Markdown
Contributor

This is a draft implementation of SewWriter.

  • Added pystitch.SewWriter module
  • Added writer to supported_formats for sew extension

Testing

Patterns have been tested by:

  1. Importing a bitmap into Janome Customizer 2000
  2. Exporting pattern as SEW format
  3. Importing pattern with pystitch.SewReader
  4. Exporting pattern with pystitch.SewWriter (this PR)
  5. Opening exported pattern with original software

It has not yet been tested on a Janome Memory Craft machine, but likely will be in due time.

Format

The format is a simplified version of JEF, with similar control sequences but with less metadata (see header from KDE Liberty community project documentation below).

Offset Length Type Description
0 2 Count Colour count; maximum of 12 colours
2 12x2 Table Colour index table padded with 0; JEF colour code
0x1A 12x2 Table Colour unknown; all entries appear to have the same value: 8 or 0 or 255
0x32 12x2 Table Stitches per colour (minus a few)

The stitch data seems to be correct, matching the original data, with the exception of occasional \x80\x10 control sequences, which are read by SewReader as a normal stitch. As this data is lost, it is not possible to reinsert these sequences when writing.
Currently, the writer does not obtain the correct values for the number of stitches per thread/colour (offset 0x32), causing part of the design not to be displayed in Customizer 2000 (although displayed correctly in InkStitch). When overwritten manually, the patterns display correctly. Any assistance with this would be greatly appreciated. By splitting the original bytes by control sequence \x80\x02\x00\x00, I could replicate the original numbers for one pattern (four colours) but not another (one colour). The original bytes are not available in an EmbPattern object either, so it would be ideal to get these values from available methods, e.g. EmbPattern.get_as_colorblocks.
The bitmap data described in the resources below is not implemented, but does not appear to be necessary.

Resources

https://edutechwiki.unige.ch/en/Embroidery_format
https://community.kde.org/Projects/Liberty/File_Formats/Janome_Embroidery_Format#SEW_Format

@Zedeldi

Zedeldi commented May 17, 2026

Copy link
Copy Markdown
Contributor Author

After a bit more testing (and hair loss), I noticed two issues:

Stitch Count

The stitch count values always appeared to be slightly incorrect, even when produced via Janome's own software. Splitting the stitch data on \x80\x01 (COLOR_CHANGE) and \x80\x10 (END) seemed to produce the closest values, where the stitch data is everything between the header and \x80\x00:

for index in range(len(pattern.threadlist)):
    len(data.split(b"\x80\x01")[index].split(b"\x80\x10")[0]) // 2

The value for the last thread was (more) wrong as it didn't remove data after \x80\x10 (see below).
However, getting these values from the stitch data in an EmbPattern results in lower values, due to lost data.

Missing End Stitch

When looking at original SEW files, in the last thread block, there was often an \x80\x10 (which was treated as a normal stitch by pystitch, followed shortly after by an \x80\x00, which would break the read loop; the rest of the data is ignored.
My initial incorrect assumption was that the \x80\x00 was the end, but the painfully old Customizer 2000 software crashed when attempting to write the pattern to disk, even though it rendered correctly. After changing the \x80\x00 to \x80\x10, it was able to write the pattern successfully.

Reverse engineering with Ghidra of RwStitch.dll also showed StInsEndCode with a value of \x80\x10 and StInsStopCode as \x80\x01\x00\x00.

Testing

After writing the file to disk and attempting the pattern on a physical embroidery machine, it was not able to read the file until I copied the remaining, unknown data from the header of another SEW file generated from official software. As I am not sure what this data is, I am unable to produce it in the writer at the moment, but I suspect it may relate to hoop size.

After copying the header over, it did (partially to my surprise) actually stitch the pattern.
I'm marking this PR as ready for review but I suggest write support for SEW is currently marked as unstable.

P.S. I appreciate this format is ancient and likely no longer used by anyone, but we have recently inherited a Memory Craft 9000 and wanted to print custom patterns.

@Zedeldi
Zedeldi marked this pull request as ready for review May 17, 2026 20:06
@kaalleen

kaalleen commented Jun 5, 2026

Copy link
Copy Markdown
Member

Thank you for your pull request.
Can you update the README file to include the sew writer by running UPDATE=1 python -m unittest test.test_meta?

@Zedeldi

Zedeldi commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@kaalleen README has been updated. Thanks!


As a side-note, when converting between formats, I noticed that the pattern was displayed rather close to the top-left corner in the Customizer 2000 software, causing some of the stitches to repeatedly use the same location.
Should this occur, an offset can be applied to all the stitches like so:

OFFSET = 192

pattern = pystitch.read(path)
for stitch in pattern.stitches:
    stitch[0] += OFFSET  # x-coordinate

pystitch.write(pattern, output)

To copy the header from a working pattern:

HEADER_SIZE = 0x1D78

with open(path, "rb") as original:
    header = original.read(HEADER_SIZE)

with open(output, "rb") as patched:
    data = patched.read()

with open(output, "wb") as file:
    file.write(header + data[HEADER_SIZE:])

Where path is the original file and output is the path to write.

@kaalleen

kaalleen commented Jun 9, 2026

Copy link
Copy Markdown
Member

Should this occur, an offset can be applied to all the stitches like so:

OFFSET = 192
      
pattern = pystitch.read(path)
    for stitch in pattern.stitches:
        stitch[0] += OFFSET  # x-coordinate
 
 pystitch.write(pattern, output)

You can pass a translate value directly to the writer (or converter).

OFFSET = (192, 0)
pystitch.convert(input_path, output_path, {'translate': OFFSET})

@kaalleen
kaalleen merged commit da4f23c into inkstitch:main Jun 9, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants