From 528f23e5c9d9f5dace1a6b3417efb5a8e2e5056a Mon Sep 17 00:00:00 2001 From: tatarize Date: Sat, 11 Aug 2018 19:24:21 -0700 Subject: [PATCH] Add files via upload --- LICENSE | 21 ++ README.md | 445 ++++++++++++++++++++++++- mass_convert.py | 18 + pyembroidery-convert.py | 15 + pyembroidery-exporter.py | 17 + pyembroidery/A100Reader.py | 30 ++ pyembroidery/A10oReader.py | 41 +++ pyembroidery/BroReader.py | 46 +++ pyembroidery/CsvReader.py | 97 ++++++ pyembroidery/CsvWriter.py | 230 +++++++++++++ pyembroidery/DatReader.py | 95 ++++++ pyembroidery/DsbReader.py | 48 +++ pyembroidery/DstReader.py | 87 +++++ pyembroidery/DstWriter.py | 159 +++++++++ pyembroidery/DszReader.py | 46 +++ pyembroidery/EmbConstant.py | 52 +++ pyembroidery/EmbEncoder.py | 622 +++++++++++++++++++++++++++++++++++ pyembroidery/EmbPattern.py | 374 +++++++++++++++++++++ pyembroidery/EmbThread.py | 83 +++++ pyembroidery/EmbThreadJef.py | 95 ++++++ pyembroidery/EmbThreadPec.py | 81 +++++ pyembroidery/EmbThreadSew.py | 95 ++++++ pyembroidery/EmbThreadShv.py | 59 ++++ pyembroidery/EmdReader.py | 45 +++ pyembroidery/ExpReader.py | 40 +++ pyembroidery/ExpWriter.py | 41 +++ pyembroidery/ExyReader.py | 6 + pyembroidery/FxyReader.py | 6 + pyembroidery/GtReader.py | 6 + pyembroidery/InbReader.py | 34 ++ pyembroidery/JefReader.py | 47 +++ pyembroidery/JefWriter.py | 134 ++++++++ pyembroidery/JpxReader.py | 48 +++ pyembroidery/KsmReader.py | 59 ++++ pyembroidery/MaxReader.py | 17 + pyembroidery/MitReader.py | 34 ++ pyembroidery/NewReader.py | 34 ++ pyembroidery/PcdReader.py | 48 +++ pyembroidery/PcmReader.py | 63 ++++ pyembroidery/PcqReader.py | 48 +++ pyembroidery/PcsReader.py | 48 +++ pyembroidery/PecGraphics.py | 164 +++++++++ pyembroidery/PecReader.py | 157 +++++++++ pyembroidery/PecWriter.py | 195 +++++++++++ pyembroidery/PesReader.py | 128 +++++++ pyembroidery/PesWriter.py | 350 ++++++++++++++++++++ pyembroidery/PhbReader.py | 30 ++ pyembroidery/PhcReader.py | 37 +++ pyembroidery/PmvReader.py | 105 ++++++ pyembroidery/PmvWriter.py | 132 ++++++++ pyembroidery/PyEmbroidery.py | 553 +++++++++++++++++++++++++++++++ pyembroidery/ReadHelper.py | 105 ++++++ pyembroidery/SewReader.py | 35 ++ pyembroidery/ShvReader.py | 72 ++++ pyembroidery/StcReader.py | 31 ++ pyembroidery/StxReader.py | 12 + pyembroidery/SvgWriter.py | 65 ++++ pyembroidery/TapReader.py | 5 + pyembroidery/TbfReader.py | 48 +++ pyembroidery/U01Reader.py | 80 +++++ pyembroidery/U01Writer.py | 88 +++++ pyembroidery/Vp3Reader.py | 118 +++++++ pyembroidery/Vp3Writer.py | 262 +++++++++++++++ pyembroidery/WriteEncoder.py | 172 ++++++++++ pyembroidery/WriteHelper.py | 90 +++++ pyembroidery/XxxReader.py | 45 +++ pyembroidery/ZhsReader.py | 34 ++ pyembroidery/ZxyReader.py | 37 +++ pyembroidery/__init__.py | 9 + setup.py | 22 ++ stitch_entry_pmv.py | 50 +++ test.py | 47 +++ test_fractals.py | 81 +++++ 73 files changed, 6972 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 mass_convert.py create mode 100644 pyembroidery-convert.py create mode 100644 pyembroidery-exporter.py create mode 100644 pyembroidery/A100Reader.py create mode 100644 pyembroidery/A10oReader.py create mode 100644 pyembroidery/BroReader.py create mode 100644 pyembroidery/CsvReader.py create mode 100644 pyembroidery/CsvWriter.py create mode 100644 pyembroidery/DatReader.py create mode 100644 pyembroidery/DsbReader.py create mode 100644 pyembroidery/DstReader.py create mode 100644 pyembroidery/DstWriter.py create mode 100644 pyembroidery/DszReader.py create mode 100644 pyembroidery/EmbConstant.py create mode 100644 pyembroidery/EmbEncoder.py create mode 100644 pyembroidery/EmbPattern.py create mode 100644 pyembroidery/EmbThread.py create mode 100644 pyembroidery/EmbThreadJef.py create mode 100644 pyembroidery/EmbThreadPec.py create mode 100644 pyembroidery/EmbThreadSew.py create mode 100644 pyembroidery/EmbThreadShv.py create mode 100644 pyembroidery/EmdReader.py create mode 100644 pyembroidery/ExpReader.py create mode 100644 pyembroidery/ExpWriter.py create mode 100644 pyembroidery/ExyReader.py create mode 100644 pyembroidery/FxyReader.py create mode 100644 pyembroidery/GtReader.py create mode 100644 pyembroidery/InbReader.py create mode 100644 pyembroidery/JefReader.py create mode 100644 pyembroidery/JefWriter.py create mode 100644 pyembroidery/JpxReader.py create mode 100644 pyembroidery/KsmReader.py create mode 100644 pyembroidery/MaxReader.py create mode 100644 pyembroidery/MitReader.py create mode 100644 pyembroidery/NewReader.py create mode 100644 pyembroidery/PcdReader.py create mode 100644 pyembroidery/PcmReader.py create mode 100644 pyembroidery/PcqReader.py create mode 100644 pyembroidery/PcsReader.py create mode 100644 pyembroidery/PecGraphics.py create mode 100644 pyembroidery/PecReader.py create mode 100644 pyembroidery/PecWriter.py create mode 100644 pyembroidery/PesReader.py create mode 100644 pyembroidery/PesWriter.py create mode 100644 pyembroidery/PhbReader.py create mode 100644 pyembroidery/PhcReader.py create mode 100644 pyembroidery/PmvReader.py create mode 100644 pyembroidery/PmvWriter.py create mode 100644 pyembroidery/PyEmbroidery.py create mode 100644 pyembroidery/ReadHelper.py create mode 100644 pyembroidery/SewReader.py create mode 100644 pyembroidery/ShvReader.py create mode 100644 pyembroidery/StcReader.py create mode 100644 pyembroidery/StxReader.py create mode 100644 pyembroidery/SvgWriter.py create mode 100644 pyembroidery/TapReader.py create mode 100644 pyembroidery/TbfReader.py create mode 100644 pyembroidery/U01Reader.py create mode 100644 pyembroidery/U01Writer.py create mode 100644 pyembroidery/Vp3Reader.py create mode 100644 pyembroidery/Vp3Writer.py create mode 100644 pyembroidery/WriteEncoder.py create mode 100644 pyembroidery/WriteHelper.py create mode 100644 pyembroidery/XxxReader.py create mode 100644 pyembroidery/ZhsReader.py create mode 100644 pyembroidery/ZxyReader.py create mode 100644 pyembroidery/__init__.py create mode 100644 setup.py create mode 100644 stitch_entry_pmv.py create mode 100644 test.py create mode 100644 test_fractals.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 04006b4..da0f5c2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,445 @@ # pyembroidery -pyembroidery library for reading and writing a variety of embroidery formats. + +Python library for the reading and writing of embroidery files. +Compatable with Python 2 and 3 Explictly tested with 3.6 and 2.7. + +To install: +```bash +pip install pyembroidery +``` + +Any suggestions or comments please raise an issue. + +pyembroidery was originally intended for in inkscape/inkstitch. However, it was entirely coded from the ground up with all projects in mind. It includes a lot of higher level and middle level pattern composition abilities, and accounts for any knowable error. It should be highly robust with a simple api in order to be entirely reasonable for *any* python embroidery project. + +It should be complex enough to go very easily from points to stitches, fine grained enough to let you control everything, and good enough that you shouldn't want to. + + +Mandate +--- +pyembroidery must to be small enough to be finished in short order and big enough to pack a punch. + +* pyembroidery must read and write: PES, DST, EXP, JEF, VP3. +* pyembroidery must fully support commands: STITCH, JUMP, TRIM, STOP, END, COLOR_CHANGE, SEQUIN_MODE and SEQUIN_EJECT. +* pyembroidery must support and function in Python 2.7 + +Pyembroidery fully meets and exceeds all of these requirements. +* It writes 9 formats, including the mandated ones. +* It reads 38 formats, including the mandated ones. +* 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. + * It is currently fully compatable with Python 2.7 and Python 3.6 + +Philosophy +--- +Pyembroidery will always attempt to minimize information loss. Embroidery reading and writing, the exporting and importing of these files, is always lossy. If there is information in a file, it is within the purview of the project (but not the mandate) to read that information and provide it to the user. If information can be written to a file, it is within the purview of the project to write that information to the file or provide means by which that can be done. + +* Low level commands: Those commands actually found in binary encoded embroidery files. + * Low level commands will be transcribed and preserved in their exact order, unless doing so will cause an error. +* Middle level commands: Useful ways of thinking about blocks of low level commands. Commands which describe the way the low level commands are encoded, but are not themselves commands executed by embroidery machines. + * Middle level commands will be helpful and converted to low-level commands during writing events. + * These will often be context sensitive converting to slightly different low level commands depending on intended writer, or encoder settings. +* High level commands: Conversion of shapes and fills into useful structures, patterning within stitches, modifiers of structures. + * High level commands will not exist. + +Other reasonable elements: +* Higher level objects like .PES or .THR containing shapes are currently ignored in favor of reading raw stitches, However, loading such things would be less lossy and thus within the scope of the project. +* Conversions from raw low level commands to some middle level interpretations or iterable generators are provided in the EmbPattern class. Additional methods are entirely reasonable feature requests. + + +How it works: +--- +Readers are sent a fileobject and an EmbPattern and parses the file, filling in the metadata, threads, stitches. + +EmbPattern objects contain all the relevant data. You can iterate stitch blocks .get_as_stitchblocks() or access the raw-stitches, threads, or metadata. + +Writers are called to save a pattern to disk. They save raw-stitch data to disk. This data may, however, not be formatted in a way the writer can utilize effectively. For this reason, writers will normalize the data with an encoder. + +The encoder encode a low level version of the commands in the pattern, not just from low level but also middle-level commands implemented with the encoder. The writer contain format specific information with which to call to the encoder with some format specific values. Each export will reencode the data for the format, without modifying or altering the original data. + +The encoder call can be made directly on the EmbPattern with .get_normalized_pattern() on the pattern, this returns a new pattern. Neither, encoding or saving will modify a pattern. Most operations performed on the data will have some degree of loss. So there is always a level of isolation between all lossy operation converting the pattern. + +* Read + * File -> Reader -> Pattern +* Write + * Pattern -> Encoder -> Pattern -> Writer -> File +* Convert + * File -> Reader -> Pattern -> Stablizer -> Pattern -> Encoder -> Pattern -> Writer -> File + +Formats: +--- + +Pyembroidery will write: +* .pes (mandated) +* .dst (mandated) +* .exp (mandated) +* .jef (mandated) +* .vp3 (mandated) +* .pec +* .u01 +* .csv +* .svg + +Pyembroidery will read: +* .pes (mandated) +* .dst (mandated) +* .exp (mandated) +* .jef (mandated) +* .vp3 (mandated) +* .10o +* .100 +* .bro +* .dat (barudan & sunstar) +* .dsb +* .dsz +* .emd +* .exy +* .fxy +* .gt +* .inb +* .jpx +* .ksm +* .max +* .mit +* .new +* .pcd +* .pcm +* .pcq +* .pcs +* .pec +* .phb +* .phc +* .sew +* .shv +* .stc +* .stx +* .tap +* .tbf +* .u01 +* .xxx +* .zxy + +Writing to SVG: +While not a binary writing format, the testing/debugging utility of SVG is unmatched. There is some notable irony in writing an SVG file in a library, whose main genesis is to help another program that *already* writes them. + +Writing to CSV: +Prints out a workable CSV file with the given data. It will be encoded like a .DST file by default. + + +Reading: +--- + +```python +import pyembroidery +``` + +To load a pattern from disk: + +```python +pattern = pyembroidery.read("myembroidery.exp") +``` + +If only a file name is given, pyembroidery will use the extension to determine what reader it should use. +(In the case of .dat where there are two non-compatable embroidery files with the same extension, the difference is detected by the reader.) + +For the discrete readers, the file may be a FileObject or a the string of the path. + +```python +pattern = pyembroidery.read_dst(file) +pattern = pyembroidery.read_pec(file) +pattern = pyembroidery.read_pes(file) +pattern = pyembroidery.read_exp(file) +pattern = pyembroidery.read_vp3(file) +pattern = pyembroidery.read_jef(file) +``` + +You can optionally add settings and pattern to these readers, it will use that pattern and append the new stitches to the end. + +```python +# append to an existing pattern +pattern = pyembroidery.read_pes(file, None, pattern) + +# or even chain together read calls +pattern = pyembroidery.read("secondread.dst", None, pyembroidery.read("firstread.jef")) +``` + +This will cause the pattern to have the stitches from both files. + + +Writing: +--- + +To write to a pattern do disk: + +```python +pyembroidery.write(pattern,"myembroidery.dst") +``` + +For the discrete writers, the file may be a FileObject or a string of the path. It does not need to know the filename to judge the extension. + +```python +pyembroidery.write_dst(pattern, file) +pyembroidery.write_pec(pattern, file) +pyembroidery.write_pes(pattern, file) +pyembroidery.write_exp(pattern, file) +pyembroidery.write_vp3(pattern, file) +pyembroidery.write_jef(pattern, file) +pyembroidery.write_svg(pattern, file) +``` + +In addition, you can add a dict object to the writer, reader, and converter with various settings. + +```python +pyembroidery.write(pattern, file.dst, { "tie_on": True, "tie_off": True, "translate": (40, 50) } +``` + +The parameters currently have recognized values for: +* `max_stitch` +* `max_jump` +* `full_jump` +* `long_stitch_contingency` +* `sequin_contingency` +* `explicit_trim` +* `translate` +* `scale` +* `rotate` +* `tie_on` +* `tie_off` +* `encode` +* `stable` + +The max_stitch, max_jump, full_jump, and sequin_contingency properties are appended by default depending on the format being writing to. For example, DST files support a maximum stitch length of 12.1mm, and this is set automatically. If you set these, they will override those values. If you override them in ways that cannot be accounted for by the reader, it may cause a crash. If you disable the encoder "encode" = False, it may crash. If you disable the stablizer for conversions "stable" = False, it will have less defined behavior. And may may have undefined behavior between specific different formats. + +Translate, Scale and Rotate occur in that order. If you need finer grain control over these they can be modified on the fly with middle-level commands. + +long_stitch_contingency sets the contingency protocol for when a stitch is longer than the format can encode and how to deal with that event. + +sequin_contingency sets the contingency protocol for when sequins exist in a file. By default this tends to be CONTINGENCY_SEQUIN_JUMP converting whatever sequins are in the data into jumps (this can sometimes be restored on various embroidery machines). For .dst files it uses CONTINGENCY_SEQUIN_UTILIZE as the format is able to fully encode sequin data. You may also use CONTINGENCY_SEQUIN_REMOVE to simply remove the commands completely as if they never existed or CONTINGENCY_SEQUIN_STITCH which converts the sequin stitches to stitches. This will look better, but is overtly more lossy. + +Explicit Trim sets whether the encoder should overtly include a trim before color change event or not. Default is True. Setting this to false will omit the trim if it is going to perform a color-change action. + +Conversion: +--- + +As pyembroidery is a fully fleshed out reader/writer within the mandate, it also does conversion. + +```python +pyembroidery.convert("embroidery.jef", "converted.dst") +``` + +This will read the embroidery.jef file in JEF format and will export it as converted.dst in DST format. + +Internally this stablizes the format: +* Reader -> Pattern -> Pattern.get_stablized_pattern() -> Encoder -> Writer + +The stablized pattern clips out the order of the particlar trims, jumps, colorchanges, stops, and turns it into middle-level commands of STITCH, COLOR_BREAK, SEQUENCE_BREAK. + +The stablizer can be disabled by setting "stable" to False. + +You can perform some finer grain controls like get_pattern_interpolate_trim(), or process the data yourself if you need this information. If there's a completely reasonable way to post-process loaded data that isn't accounted for, raise an issue. This is still an open question. + +```python +pyembroidery.convert("embroidery.jef", "converted.dst", {"stable": False}) +``` + +Depending on the formats and files in question this does not have a guarenteed result. It will still use the encoder and should be effective. + +You can disable both the stablizer and the encoder: + +```python +pyembroidery.convert("embroidery.jef", "converted.dst", {"stable": False, "encode": False}) +``` + + +Composing a pattern: +--- + +* Use core commands to compose a pattern +* Use shorthand commands to compose a pattern + * `pyembroidery.STITCH` + * `pyembroidery.SEQUENCE_BREAK` + * `pyembroidery.COLOR_BREAK` + * `pyembroidery.FRAME_EJECT` +* Use bulk dump stitchblock +* Mix these different command levels. + + +The constants for the stitch types are located in the EmbConstants.py + +To compose a pattern you will typically use: + +```python +import pyembroidery +pattern = pyembroidery.EmbPattern() +pattern.add_stitch_relative(COMMAND, dx, dy) +pattern.add_stitch_absolute(COMMAND, x, y) +pattern.add_command(command) +pattern.add_stitchblock(stitchblock) +``` +The relative and absolute markers determine whether the numbers given are relative to the last position or an absolute location. Calling add_command does not update the internal record of position. These are taken as positionless and the x and y are taken as parameters. Adding a command that is explicitly positioned with add_command will have undefined behavior. + +NOTE: the order here is `command, x, y`, not `x,y command`. Python is good with letting you omit values at the end. And the command is *always* needed but the dx,dy can be dropped quite reasonably. + +For `COMMAND`, you can: +* Use overt low-level commands: + * `pyembroidery.STITCH` + * move to position and drop needle once to make a stitch + * `pyembroidery.JUMP` + * move to position without dropping needle + * `pyembroidery.TRIM` + * trim the thread (for supported machines and file formats) + * `pyembroidery.COLOR_CHANGE` + * `pyembroidery.STOP` + * pause the machine (for applique, thread-change, etc) + * `pyembroidery.END` + * end the pattern + * `pyembroidery.SEQUIN_EJECT` + * ejects a sequin. These are overtly saved in .dst format. But, can be made to pattern JUMPs in other formats that may be used with various sequin attachments. + * `pyembroidery.SEQUIN_MODE` + * turns on sequin mode. this is done automatically for you if you eject a sequin. + * `pyembroidery.SLOW` + * .u01 only. Runs the machine in slow mode. + * `pyembroidery.FAST` + * .u01 only. Runs the machine in fast mode. + +Shorthand function calls for the above are also available. These all equate to `pattern.add_stitch_relative` calls using the above constants. You can omit the `dx` and `dy` parameters if the position should not change (especially useful for trim and color change). + +```python +pattern.stitch(dx, dy) +pattern.trim() +pattern.color_change() +``` + +StitchBlocks: +--- +Conceptually a lot of embroidery can be thought of as unbroken blocks of stitches. Given the ubiquity of this, pyembroidery allows several methods for manipulating stitchblocks for reading and writing. + +A stitch block currently has two parts a block and thread. + +The block is a list of lists, with each 3 values. x, y, command. iterable set of objects with stitch.command, stitch.x, stitch.y will also works for adding a stitch block to a pattern. + +If your internal schema is different than this, raise an issue to have it accounted for within pyembroidery. + +When a call is made to add_stitchblock(), the thread object is required to whether the current thread is different than the previous one. If a different thread is detected pyembroidery will append a COLOR_BREAK rather than SEQUENCE_BREAK after it adds the stitches into the pattern. Depending on your use case, you could implement this yourself using singular calls to add_stitch_relative() or add_stitch_absolute() and then determine the type of break with COLOR_BREAK or SEQUENCE_BREAK afterwards. No break command will cause it to merge these stitches (likely invoking whatever long_stitch_contingency is needed). + + +Middle-Level Commands: +---- + +The middle-level commands, as they currently stand: +* SEQUENCE_BREAK - Break between stitches. Inserts a trim and jumps to the next stitch in the sequence. +* COLOR_BREAK - Breaks between stitches. Changes to the next color (unless called before anything was stitched) +* FRAME_EJECT(x,y) - Breaks the stitches, jumps to the given location, performs a stop, then goes to next stitch accordingly. +* STITCH_BREAK - Next location is jumped to. Existing jumps are reallocated. +* MATRIX_TRANSLATE(tx,ty) - Applies an inline translation shift for the encoder. It will treat all future stitches translated from here. +* MATRIX_SCALE(sx,sy) - Applies an inline scale shift. It will scale by that factor for future stitches. +* MATRIX_ROTATE(r) - Applies an inline rotateion shift. It will rotate by that factor for future stitches (in degrees). +* MATRIX_RESET - Resets the affine transformation matrix. +* OPTION_ENABLE_TIE_ON - Enables Tie_on on the fly. +* OPTION_ENABLE_TIE_OFF - Enables Tie_off on the fly. +* OPTION_DISABLE_TIE_ON - Disables Tie_on on the fly. +* OPTION_DISABLE_TIE_OFF - Disables Tie_off on the fly. +* OPTION_MAX_STITCH_LENGTH(x) - Sets the max stitch length on the fly. +* OPTION_MAX_JUMP_LENGTH(x) - Sets the max jump length on the fly. +* OPTION_EXPLICIT_TRIM - (Default) includes trim command before color-change command explicitly. +* OPTION_IMPLICIT_TRIM - Sets trim to be implied by the color-change event. +* SEW_TO - STITCH but with forced CONTINGENCY_SEW_TO +* NEEDLE_AT - STITCH but with forced CONTINGENCY_JUMP_NEEDLE +* CONTINGENCY_NONE - Disables long stitch contingency encoding. +* CONTINGENCY_JUMP_NEEDLE - Sets, long stitch contingency to jump the needle to the new position. +* CONTINGENCY_SEW_TO - Sets, long stitch contingency to sew to the new position with interpolated stitches. +* CONTINGENCY_SEQUIN_UTILIZE - sets the equin contingency to use the sequin information. +* CONTINGENCY_SEQUIN_JUMP - Sets the sequin contingency to call the sequins jumps. +* CONTINGENCY_SEQUIN_STITCH - Sets the sequin contingency to call the sequins stitches. +* CONTINGENCY_SEQUIN_REMOVE - Sets the sequin contingency to remove the commands completely. + +Note: these do not need to have a 1 to 1 conversion to stitches. Many have 1 to 0 and trigger changes in states for the encoder, or the matrix being used to filter the locations, or specific higher level commands. + +The could can be made to do a lot at the encoder level. If something is needed and within scope of the project, raise an issue. + +--- + +COLOR_BREAK and SEQUENCE_BREAK: + +The main two middle-level commands simply serve as dividers for series of stitches. +* pattern.command(COLOR_BREAK) +* (add a bunch of stitches) +* pattern.command(SEQUENCE_BREAK) +* (add a bunch of stitches) +* pattern.command(COLOR_BREAK) +* (add a bunch of stitches) +* pattern.command(SEQUENCE_BREAK) + +The encoder will by default ignore any COLOR_BREAK that occurs before any stitches have been put down, or sequence or color breaks would occur after all stitching has happened. So you don't have to worry about the order you put them in. They work expressly as breaks that divide one block of stitches from another, and gives information as to whether this change also requires we use a new color. + +You can expressly add any of the core commands to the patterns. These are generalized and try to play nice with other commands. When the patterns are written to disk, they call pattern.get_normalized_pattern() and save the normalized pattern. Saving to any format does not modify the pattern, ever. It writes the modified pattern out. It adds the max_jump and max_stitch to the encoding when it normalizes this to save. So each format can compile to a different set of stitches due to the max_jump etc. This is expressly an attempt to maintain as much data integrity as possible. + +After a load, the pattern will be filled with raw basic stitch data, it's perfectly reasonable call .get_stable_pattern() on this which will make it into a series of stitches, color_breaks, sequence_breaks or get_pattern_interpolate_trim() which will allow you to introduce trim commands after a series of JUMP commands as specified and merge the untrimmed jumps. Or to iterate through the data with .get_as_stitchblocks() which is a generator that will produce stitch blocks from the raw loaded data. The stablized pattern simply makes a new pattern, iterates through the current pattern by the stitchblocks and feeds that into add_stitch_block(). This results in a pattern without any jumps, trims, etc. + +STITCH_BREAK + +Stitch break is only needed for reallocating jumps. It requires that the long stitch contingency is needle_to for the next stitch and any existing jumps directly afterwards are ignored. This causes the jump sequences to reallocate. If an existing jump sequence exists because it was loaded from a file and fed into a write routine. The write routine may only seek a contingency for the long jumps by providing extra subdivisions, because low level commands are only tweaked if a literal transcription would cause errors. However, calling pattern.get_pattern_merge_jumps() returns a pattern with all sequences of JUMP replaced with a single STITCH_BREAK command which is middle level and converted by the encoder into a series of jumps produced by the encoder rather than directly transcribed from their current sequence. + +Stitch Contingency +--- +The encoder needs to decide what to do when a stitch is too long. The current modes here are: +* CONTINGENCY_NEEDLE_JUMP (default) +* CONTINGENCY_SEW_TO +* CONTINGENCY_NONE + +When a stitch is beyond max_stitch (whether set by the format or by the user) it must deal with this event, however opinions differ as to how what a stitch beyond the maximum should do. If it is your intent that STITCH means SEW_TO this location then setting the stitch contingency to SEW_TO will create a series of stitches until we get to the end location. If you use the command SEW_TO this overtly works like a stitch with CONTINGENCY_SEW_TO. Likewise NEEDLE_AT is the STITCH flavor that jumps to to the end location and then stitches. If you set CONTINGENCY_NONE then no contingency method is used, long stitches are simply fed to the writer as they appear which may throw an error or crash. + +Sequin Contingency +--- +The enconder needs to decide what to do when there are sequins in a pattern. The current modes here are: +* CONTINGENCY_SEQUIN_UTILIZE - sets the equin contingency to use the sequin information. +* CONTINGENCY_SEQUIN_JUMP - Sets the sequin contingency to call the sequins jumps. +* CONTINGENCY_SEQUIN_STITCH - Sets the sequin contingency to call the sequins stitches. +* CONTINGENCY_SEQUIN_REMOVE - Sets the sequin contingency to remove the commands completely. + +Sequins being written into files that do not support sequins can go several ways, the two typical methods are JUMP and STITCH, this means to replace the SEQUIN_EJECTs with JUMP. This will allow some machines to manually enable sequins for a particular section and interpret the JUMPs as stitches. It is known that some Barudan machines have this ability. The other typical mode is STITCH which will preserve viewable structure of the underlying pattern while destroying the information of where the JUMPs were. With the JUMPs some data will appear to be corrupted, with STITCHes the data will look correct except without the sequins but the information is lost and not recoverable. REMOVE is given for completeness, but it calls all SEQUIN_EJECT commands NO OPERATIONS as if they don't appear in the pattern at all. + +Units +--- +* The core units are 1/10th mm. This is what 1 refers to within most formats, and internally within pyembroidery itself. You are entirely permitted to use floating point numbers. When writing to a format, fractional values will be lost, but this shall happen in such a way to avoid the propagation of error. Relative stitches from position ( 0.0, 0.31 ) of (+5.4, +5.4), (+5.4, +5,4), (+5.4, +5,4) should encode as changes of 5,6 6,5 5,6. Taking the relative distance in the format as the integer change from the last integer position to the new one, maintaining a position as close to the absolute position as possible. All fractional values are considered significant. + +In some read formats the formats themselves have a slightly different unit systems such as .PCD or .MIT these alternative units will be presented seemlessly as 1/10th mm units. + +Core Command Ordering +--- +Stitch is taken to mean move_position(x,y), needle_strike. Jump is taken to mean move_position(x,y), block_needle_bar. In those orders. +If a format takes stitch to mean needle_strike, move_position(x,y) in that order. The encoder will may insert an extra jump in to avoid stitching an unwanted element. These differences matter, and are accounted for by things like FULL_JUMP in places, and within the formats. However, within the pattern the understanding should be consistently be taken as displace then operation. + +Note: This is true for sequin_eject too. DST files are the only currently supported format with sequins and they use dx,dy then command. But, note the sequin is ejected at the destination of the dx dy. It will move, then sequin_eject this is the assumed order. It is also the DST order. + +So if write your own pattern and you intend to stitch at the origin and then go somewhere you must `stitch, 0, 0` then `stitch, x, y` if you start by stitching somewhere at x, y. It may insert jump stitches to get you to that location, then stitch at that location. + +Coordinate System +--- +Fundamentally pyembroidery stores the positions such that the +y direction is down and -y is up (when viewed horizontally) with +x right and -x left. This is consistent with most modern graphics coordinate systems, but this is different from how these values are stored within embroidery formats. pyembroidery reads by flipping the y-axis, and writes by flipping the y-axis (except for SVG which uses the same coordinate system). This allows for seemless reading, writing, and interfacing. The flips occur at the level of the format readers and writers and is not subject to encoding. However encoding with scale of (1, -1) would invert this during the encoding. All patterns are stored such that `top` is in the -y direction and `bottom` is in the +y direction. + +All patterns start at the origin point (0,0). + +--- + +This code is based on Embroidermodder/MobileViewer Java code, +Which in turn is based on Embroidermodder/libembroidery C++ code. + +Thanks to, +* The Embroidermodder Team +* Josh Varga +* Jonathan Greig redteam316 +* fabriciocouto +* frno7 +* Trever Adams +* Linus Torvalds +* Rudolfo @ http://www.achatina.de/sewing/main/TECHNICL.HTM +* Lex Neva, lexelby +* wwderw +* Purple-bobby +* Jason Weiler +* And the countless other people who put forward good works in figuring out these formats, and those who may yet do so. + +--- + +This software is in no way derived from or based on Jackson Yee's abandoned 2006 "pyembroidery" project. The name was simply taken from libEmbroidery and written in python and thus a portmanteau of those. I was unaware of the project until after the all the principle work on this project was complete. I apologize for any confusion this may cause. diff --git a/mass_convert.py b/mass_convert.py new file mode 100644 index 0000000..851e76e --- /dev/null +++ b/mass_convert.py @@ -0,0 +1,18 @@ +from pyembroidery import * + +for file_stream in os.listdir("convert"): + convert_file = os.path.join("convert", file_stream) + pattern = read(convert_file) + if pattern is None: + continue + pattern = pattern.get_pattern_interpolate_trim(3) + # pattern = pattern.get_pattern_merge_jumps() + for emb_format in supported_formats(): + if emb_format.get('writer', None) is None: + continue + results_file = os.path.join("results", file_stream) + \ + '.' + emb_format["extension"] + if emb_format["extension"] == "csv": + write(pattern, results_file, {"encode": False, "deltas": True}) + else: + write(pattern, results_file) diff --git a/pyembroidery-convert.py b/pyembroidery-convert.py new file mode 100644 index 0000000..3c5e436 --- /dev/null +++ b/pyembroidery-convert.py @@ -0,0 +1,15 @@ +from __future__ import print_function + +import sys +from pyembroidery import * + +if len(sys.argv) <= 1: + print("No command arguments") + exit(1) +input = sys.argv[1] +if len(sys.argv) >= 3: + output = sys.argv[2] +else: + output = sys.argv[1] + ".csv" +pattern = read(input) +write = write(pattern,output) diff --git a/pyembroidery-exporter.py b/pyembroidery-exporter.py new file mode 100644 index 0000000..96b6ada --- /dev/null +++ b/pyembroidery-exporter.py @@ -0,0 +1,17 @@ +from __future__ import print_function + +import sys +from pyembroidery import * + +if len(sys.argv) <= 1: + print("No command arguments") + exit(1) +input_file = sys.argv[1] +pattern = read(input_file) + +write(pattern, input_file + ".dst") +write(pattern, input_file + ".exp") +write(pattern, input_file + ".vp3") +write(pattern, input_file + ".pes") +write(pattern, input_file + ".jef") +write(pattern, input_file + ".u01") diff --git a/pyembroidery/A100Reader.py b/pyembroidery/A100Reader.py new file mode 100644 index 0000000..09dd22f --- /dev/null +++ b/pyembroidery/A100Reader.py @@ -0,0 +1,30 @@ +def read_100_stitches(f, out): + count = 0 + while True: + count += 1 + b = bytearray(f.read(4)) + if len(b) != 4: + break + x = b[2] + y = b[3] + if x > 0x80: + x -= 0x80 + x = -x + if y > 0x80: # because 2s complement is for chumps? + y -= 0x80 + y = -y + if b[0] == 0x61: + out.stitch(x, -y) + continue + elif (b[0] & 0x01) != 0: + out.move(x, -y) + continue + else: # too broad of catch + out.color_change() + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + read_100_stitches(f, out) diff --git a/pyembroidery/A10oReader.py b/pyembroidery/A10oReader.py new file mode 100644 index 0000000..8d65043 --- /dev/null +++ b/pyembroidery/A10oReader.py @@ -0,0 +1,41 @@ +def read_10o_sttiches(f, out): + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + ctrl = byte[0] + y = -byte[1] + x = byte[2] + if ctrl & 0x20 != 0: + x = -x + if ctrl & 0x40 != 0: + y = -y + + if (ctrl & 0b11111) == 0: + out.stitch(x, y) + continue + if (ctrl & 0b11111) == 0x10: + out.move(x, y) + continue + if ctrl == 0x8A: + # Start. + continue + if ctrl == 0x85: + out.color_change() + continue + if ctrl == 0x82: + out.stop() + continue + if ctrl == 0x81: + out.trim() + continue + if ctrl == 0x87: + break + break # Unknown Control + out.end() + + +def read(f, out, settings=None): + read_10o_sttiches(f, out) diff --git a/pyembroidery/BroReader.py b/pyembroidery/BroReader.py new file mode 100644 index 0000000..4731219 --- /dev/null +++ b/pyembroidery/BroReader.py @@ -0,0 +1,46 @@ +from .ReadHelper import read_int_16le, read_int_8, signed8, signed16 + + +# Do you even embroider .bro? + +def read_bro_stitches(f, out): + count = 0 + while True: + count += 1 + b = bytearray(f.read(2)) + if len(b) != 2: + break + if b[0] != 0x80: + out.stitch(signed8(b[0]), -signed8(b[1])) + continue + control = read_int_8(f) + if control == 0x00: + continue + if control == 0x02: + break + if control == 0xE0: + break + if control == 0x7E: + x = signed16(read_int_16le(f)) + y = signed16(read_int_16le(f)) + out.move(x, -y) + continue + if control == 0x03: + x = signed16(read_int_16le(f)) + y = signed16(read_int_16le(f)) + out.move(x, -y) + continue + if 0xE0 < control < 0xF0: + needle = control - 0xE0 + out.color_change() + x = signed16(read_int_16le(f)) + y = signed16(read_int_16le(f)) + out.move(x, -y) + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + f.seek(0x100, 0) + read_bro_stitches(f, out) diff --git a/pyembroidery/CsvReader.py b/pyembroidery/CsvReader.py new file mode 100644 index 0000000..fe09c48 --- /dev/null +++ b/pyembroidery/CsvReader.py @@ -0,0 +1,97 @@ +from pyembroidery.EmbConstant import * + +READ_FILE_IN_TEXT_MODE = True + + +def read(f, out, settings=None): + import csv + csv_reader = csv.reader(f, delimiter=',') + command_dict = get_command_dictionary() + for row in csv_reader: + if len(row) == 0: + continue + if "*" in row[0]: + command = command_dict[row[2]] + if len(row) == 3: + out.add_command(command) + else: + out.add_stitch_absolute(command, float(row[3]), float(row[4])) + elif "#" in row[0]: + continue + elif "@" in row[0]: + if len(row) != 3: + continue + out.metadata(row[1], row[2]) + elif "$" in row[0]: + thread_add = {} + if len(row) == 7 and len(row[2]) <= 3 and len(row[3]) <= 3 and len(row[4]) <= 3: + # This is an embroidermodder csv file, I changed the colors and added more details. + # [THREAD_NUMBER], [RED], [GREEN], [BLUE], [DESCRIPTION], [CATALOG_NUMBER]\"\n"); + thread_add["rgb"] = (int(row[2]), int(row[3]), int(row[4])) + thread_add["description"] = row[5] + thread_add["catalog"] = row[6] + else: + try: + thread_add["rgb"] = row[2] + except IndexError: + pass + try: + thread_add["name"] = row[3] + except IndexError: + pass + try: + thread_add["brand"] = row[4] + except IndexError: + pass + try: + thread_add["catalog"] = row[5] + except IndexError: + pass + try: + thread_add["details"] = row[6] + except IndexError: + pass + try: + thread_add["weight"] = row[7] + except IndexError: + pass + out.add_thread(thread_add) + + +def get_command_dictionary(): + return { + "NO_COMMAND": NO_COMMAND, + "STITCH": STITCH, + "JUMP": JUMP, + "TRIM": TRIM, + "STOP": STOP, + "END": END, + "SLOW": SLOW, + "FAST": FAST, + "COLOR_CHANGE": COLOR_CHANGE, + "SEQUIN_MODE": SEQUIN_MODE, + "SEQUIN_EJECT": SEQUIN_EJECT, + "SEW_TO": SEW_TO, + "NEEDLE_AT": NEEDLE_AT, + "STITCH_BREAK": STITCH_BREAK, + "SEQUENCE_BREAK": SEQUENCE_BREAK, + "COLOR_BREAK": COLOR_BREAK, + "TIE_ON": TIE_ON, + "TIE_OFF": TIE_OFF, + "FRAME_EJECT": FRAME_EJECT, + "MATRIX_TRANSLATE": MATRIX_TRANSLATE, + "MATRIX_SCALE": MATRIX_SCALE, + "MATRIX_ROTATE": MATRIX_ROTATE, + "MATRIX_RESET": MATRIX_RESET, + "OPTION_ENABLE_TIE_ON": OPTION_ENABLE_TIE_ON, + "OPTION_ENABLE_TIE_OFF": OPTION_ENABLE_TIE_OFF, + "OPTION_DISABLE_TIE_ON": OPTION_DISABLE_TIE_ON, + "OPTION_DISABLE_TIE_OFF": OPTION_DISABLE_TIE_OFF, + "OPTION_MAX_STITCH_LENGTH": OPTION_MAX_STITCH_LENGTH, + "OPTION_MAX_JUMP_LENGTH": OPTION_MAX_JUMP_LENGTH, + "OPTION_IMPLICIT_TRIM": OPTION_IMPLICIT_TRIM, + "OPTION_EXPLICIT_TRIM": OPTION_EXPLICIT_TRIM, + "CONTINGENCY_NONE": CONTINGENCY_NONE, + "CONTINGENCY_JUMP_NEEDLE": CONTINGENCY_JUMP_NEEDLE, + "CONTINGENCY_SEW_TO": CONTINGENCY_SEW_TO, + } diff --git a/pyembroidery/CsvWriter.py b/pyembroidery/CsvWriter.py new file mode 100644 index 0000000..e0db7d8 --- /dev/null +++ b/pyembroidery/CsvWriter.py @@ -0,0 +1,230 @@ +from .EmbConstant import * +from .WriteHelper import write_string_utf8 +from .PecGraphics import get_graphic_as_string +import math + +STRIP_SPEEDS = False +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_UTILIZE +MAX_JUMP_DISTANCE = 121 +MAX_STITCH_DISTANCE = 121 + + +def csv(f, values): + string = "" + for v in values: + if len(string) > 0: + string += ',' + string += ('\"%s\"' % v) + write_string_utf8(f, string + "\n") + + +def distance(dx, dy): + dx *= dx + dy *= dy + return math.sqrt(dx + dy) + + +def angle(dx, dy): + tau = math.pi * 2 + angle = math.atan2(dy, dx) + angle += tau / float(2) + angle /= tau + return angle + + +def write(pattern, f, settings=None): + names = get_common_name_dictionary() + + deltas = settings is not None and "deltas" in settings + displacement = settings is not None and "displacement" in settings + + extends = pattern.extends() + width = extends[2] - extends[0] + height = extends[3] - extends[1] + + csv(f, ('#', '[VAR_NAME]', '[VAR_VALUE]')) + count_stitches = pattern.count_stitches() + csv(f, ('>', 'STITCH_COUNT:', str(count_stitches))) + count_threads = pattern.count_color_changes() + csv(f, ('>', 'THREAD_COUNT:', str(count_threads))) + csv(f, ('>', 'EXTENTS_LEFT:', str(extends[0]))) + csv(f, ('>', 'EXTENTS_TOP:', str(extends[1]))) + csv(f, ('>', 'EXTENTS_RIGHT:', str(extends[2]))) + csv(f, ('>', 'EXTENTS_BOTTOM:', str(extends[3]))) + csv(f, ('>', 'EXTENTS_WIDTH:', str(width))) + csv(f, ('>', 'EXTENTS_HEIGHT:', str(height))) + + stitch_counts = {} + for s in pattern.stitches: + command = s[2] + if command in stitch_counts: + stitch_counts[command] += 1 + else: + stitch_counts[command] = 1 + + if len(stitch_counts) != 0: + for the_key, the_value in stitch_counts.items(): + try: + name = "COMMAND_" + names[the_key] + except (IndexError, KeyError): + name = "COMMAND_UNKNOWN_" + str(the_key) + csv(f, ( + '>', + name, + str(the_value) + )) + + write_string_utf8(f, "\n") + + if len(pattern.extras) > 0: + csv(f, ( + '#', + '[METADATA_NAME]', + '[METADATA]' + )) + for the_key, the_value in pattern.extras.items(): + if isinstance(the_value, tuple): + the_value = "\n" + get_graphic_as_string(the_value) + csv(f, ( + '@', + str(the_key), + str(the_value) + )) + write_string_utf8(f, "\n") + + if len(pattern.threadlist) > 0: + csv(f, ( + '#', + '[THREAD_NUMBER]', + '[HEX_COLOR]', + '[DESCRIPTION]', + '[BRAND]', + '[CATALOG_NUMBER]', + '[DETAILS]', + '[WEIGHT]' + )) + for i, thread in enumerate(pattern.threadlist): + csv(f, ( + '$', + str(i), + thread.hex_color(), + thread.description, + thread.brand, + thread.catalog_number, + thread.details, + thread.weight, + )) + write_string_utf8(f, "\n") + + if len(pattern.stitches) > 0: + if displacement: + csv(f, ( + '#', + '[STITCH_INDEX]', + '[STITCH_TYPE]', + '[X]', + '[Y]', + '[DX]', + '[R]', + '[ANGLE]' + )) + elif deltas: + csv(f, ( + '#', + '[STITCH_INDEX]', + '[STITCH_TYPE]', + '[X]', + '[Y]', + '[DX]', + '[DY]' + )) + else: + csv(f, ( + '#', + '[STITCH_INDEX]', + '[STITCH_TYPE]', + '[X]', + '[Y]' + )) + current_x = 0 + current_y = 0 + for i, stitch in enumerate(pattern.stitches): + try: + name = names[stitch[2]] + except (IndexError, KeyError): + name = "UNKNOWN " + str(stitch[2]) + if displacement: + dx = stitch[0] - current_x + dy = stitch[1] - current_y + csv(f, ( + '*', + str(i), + name, + str(stitch[0]), + str(stitch[1]), + str(dx), + str(dy), + str(distance(dx, dy)), + str(angle(dx, dy)) + )) + elif deltas: + dx = stitch[0] - current_x + dy = stitch[1] - current_y + csv(f, ( + '*', + str(i), + name, + str(stitch[0]), + str(stitch[1]), + str(dx), + str(dy) + )) + else: + csv(f, ( + '*', + str(i), + name, + str(stitch[0]), + str(stitch[1]), + )) + current_x = stitch[0] + current_y = stitch[1] + + +def get_common_name_dictionary(): + return { + NO_COMMAND: "NO_COMMAND", + STITCH: "STITCH", + JUMP: "JUMP", + TRIM: "TRIM", + STOP: "STOP", + END: "END", + SLOW: "SLOW", + FAST: "FAST", + COLOR_CHANGE: "COLOR_CHANGE", + SEQUIN_MODE: "SEQUIN_MODE", + SEQUIN_EJECT: "SEQUIN_EJECT", + SEW_TO: "SEW_TO", + NEEDLE_AT: "NEEDLE_AT", + STITCH_BREAK: "STITCH_BREAK", + SEQUENCE_BREAK: "SEQUENCE_BREAK", + COLOR_BREAK: "COLOR_BREAK", + TIE_ON: "TIE_ON", + TIE_OFF: "TIE_OFF", + FRAME_EJECT: "FRAME_EJECT", + MATRIX_TRANSLATE: "MATRIX_TRANSLATE", + MATRIX_SCALE: "MATRIX_SCALE", + MATRIX_ROTATE: "MATRIX_ROTATE", + MATRIX_RESET: "MATRIX_RESET", + OPTION_ENABLE_TIE_ON: "OPTION_ENABLE_TIE_ON", + OPTION_ENABLE_TIE_OFF: "OPTION_ENABLE_TIE_OFF", + OPTION_DISABLE_TIE_ON: "OPTION_DISABLE_TIE_ON", + OPTION_DISABLE_TIE_OFF: "OPTION_DISABLE_TIE_OFF", + OPTION_MAX_STITCH_LENGTH: "OPTION_MAX_STITCH_LENGTH", + OPTION_MAX_JUMP_LENGTH: "OPTION_MAX_JUMP_LENGTH", + OPTION_IMPLICIT_TRIM: "OPTION_IMPLICIT_TRIM", + OPTION_EXPLICIT_TRIM: "OPTION_EXPLICIT_TRIM", + CONTINGENCY_NONE: "CONTINGENCY_NONE", + CONTINGENCY_JUMP_NEEDLE: "CONTINGENCY_JUMP_NEEDLE", + CONTINGENCY_SEW_TO: "CONTINGENCY_SEW_TO", + } diff --git a/pyembroidery/DatReader.py b/pyembroidery/DatReader.py new file mode 100644 index 0000000..88868cb --- /dev/null +++ b/pyembroidery/DatReader.py @@ -0,0 +1,95 @@ +def read_barudan_dat(f, out): + stitched_yet = False + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + + ctrl = byte[0] + y = -byte[1] + x = byte[2] + + if ctrl & 0x80 == 0: + # This bit should always be set, must be other dat type. + return False + if ctrl & 0x40 != 0: + y = -y + if ctrl & 0x20 != 0: + x = -x + if (ctrl & 0b11111) == 0: + stitched_yet = True + out.stitch(x, y) + continue + if (ctrl & 0b11111) == 1: + out.move(x, y) + continue + if ctrl == 0xF8: + break + if ctrl == 0xE7: + out.trim() + continue + if ctrl == 0xE8: + if count > 1: + out.stop() + continue + if 0xE9 <= ctrl < 0xF8: + needle = ctrl - 0xE8 + if stitched_yet: + out.color_change() + continue + break # Uncaught Control + out.end() + return True + + +def read_sunstar_dat_stitches(f, out): + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + x = byte[0] & 0x7F + y = byte[1] & 0x7F + if byte[0] & 0x80: + x = -x + if byte[1] & 0x80: + y = -y + y = -y + ctrl = byte[2] + if ctrl == 0x07: + out.stitch(x, y) + continue + if ctrl == 0x04: + out.move(x, y) + continue + if ctrl == 0x80: + out.trim(x, y) + continue + if ctrl == 0x87: + out.color_change() + if x != 0 or y != 0: + out.stitch(x, y) + continue + if ctrl == 0x84: # Initialized info. + out.stitch(x, y) + continue + elif ctrl == 0: + break + break # Uncaught Control + out.end() + + +def read_sunstar_dat(f, out): + # f.seek(0x02, 0) + # stitches = read_int_16le(f) + f.seek(0x100, 0) + read_sunstar_dat_stitches(f, out) + + +def read(f, out, settings=None): + if not read_barudan_dat(f, out): + f.seek(0, 0) + read_sunstar_dat(f, out) diff --git a/pyembroidery/DsbReader.py b/pyembroidery/DsbReader.py new file mode 100644 index 0000000..7fc61e7 --- /dev/null +++ b/pyembroidery/DsbReader.py @@ -0,0 +1,48 @@ +from .DstReader import dst_read_header + + +def b_stitch_encoding_read(f, out): + stitched_yet = False + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + + ctrl = byte[0] + y = -byte[1] + x = byte[2] + + if ctrl & 0x40 != 0: + y = -y + if ctrl & 0x20 != 0: + x = -x + + if (ctrl & 0b11111) == 0: + stitched_yet = True + out.stitch(x, y) + continue + if (ctrl & 0b11111) == 1: + out.move(x, y) + continue + if ctrl == 0xF8: + break + if ctrl == 0xE7: + out.trim() + continue + if ctrl == 0xE8: + out.stop() + continue + if 0xE9 <= ctrl < 0xF8: + needle = ctrl - 0xE8 + if stitched_yet: + out.color_change() + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + dst_read_header(f, out) + b_stitch_encoding_read(f, out) diff --git a/pyembroidery/DstReader.py b/pyembroidery/DstReader.py new file mode 100644 index 0000000..971c9a7 --- /dev/null +++ b/pyembroidery/DstReader.py @@ -0,0 +1,87 @@ +def getbit(b, pos): + return (b >> pos) & 1 + + +def decode_dx(b0, b1, b2): + x = 0 + x += getbit(b2, 2) * (+81) + x += getbit(b2, 3) * (-81) + x += getbit(b1, 2) * (+27) + x += getbit(b1, 3) * (-27) + x += getbit(b0, 2) * (+9) + x += getbit(b0, 3) * (-9) + x += getbit(b1, 0) * (+3) + x += getbit(b1, 1) * (-3) + x += getbit(b0, 0) * (+1) + x += getbit(b0, 1) * (-1) + return x + + +def decode_dy(b0, b1, b2): + y = 0 + y += getbit(b2, 5) * (+81) + y += getbit(b2, 4) * (-81) + y += getbit(b1, 5) * (+27) + y += getbit(b1, 4) * (-27) + y += getbit(b0, 5) * (+9) + y += getbit(b0, 4) * (-9) + y += getbit(b1, 7) * (+3) + y += getbit(b1, 6) * (-3) + y += getbit(b0, 7) * (+1) + y += getbit(b0, 6) * (-1) + return -y + + +def process_header_info(out, prefix, value): + if prefix == "LA": + out.metadata("name", value) + elif prefix == "AU": + out.metadata("author", value) + elif prefix == "CP": + out.metadata("copyright", value) + elif prefix == "TC": + values = [x.strip() for x in value.split(',')] + out.add_thread({ + "hex": values[0], + "description": value[1], + "catalog": value[2] + }) + else: + out.metadata(prefix, value) + + +def dst_read_header(f, out): + header = f.read(512) + header_string = header.decode('utf8') + for line in [x.strip() for x in header_string.split('\r')]: + if len(line) > 3: + process_header_info(out, line[0:2].strip(), line[3:].strip()) + + +def dst_read_stitches(f, out): + sequin_mode = False + while True: + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + dx = decode_dx(byte[0], byte[1], byte[2]) + dy = decode_dy(byte[0], byte[1], byte[2]) + if byte[2] & 0b11110011 == 0b11110011: + out.stop(dx, dy) + elif byte[2] & 0b11000011 == 0b11000011: + out.color_change(dx, dy) + elif byte[2] & 0b01000011 == 0b01000011: + out.sequin_mode(dx, dy) + sequin_mode = not sequin_mode + elif byte[2] & 0b10000011 == 0b10000011: + if sequin_mode: + out.sequin_eject(dx, dy) + else: + out.move(dx, dy) + else: + out.stitch(dx, dy) + + +def read(f, out, settings=None): + dst_read_header(f, out) + dst_read_stitches(f, out) diff --git a/pyembroidery/DstWriter.py b/pyembroidery/DstWriter.py new file mode 100644 index 0000000..3daef08 --- /dev/null +++ b/pyembroidery/DstWriter.py @@ -0,0 +1,159 @@ +from .EmbConstant import * +from .WriteHelper import write_string_utf8 + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_UTILIZE +FULL_JUMP = False +MAX_JUMP_DISTANCE = 121 +MAX_STITCH_DISTANCE = 121 +PPMM = 10 +DSTHEADERSIZE = 512 + + +def bit(b): + return 1 << b + + +def encode_record(x, y, flags): + y = -y # flips the coordinate y space. + b0 = 0 + b1 = 0 + b2 = 0 + if flags == JUMP or flags == SEQUIN_EJECT: + b2 += bit(7) # jumpstitch 10xxxx11 + if flags == STITCH or flags == JUMP or flags == SEQUIN_EJECT: + b2 += bit(0) + b2 += bit(1) + if x > 40: + b2 += bit(2) + x -= 81 + if x < -40: + b2 += bit(3) + x += 81 + if x > 13: + b1 += bit(2) + x -= 27 + if x < -13: + b1 += bit(3) + x += 27 + if x > 4: + b0 += bit(2) + x -= 9 + if x < -4: + b0 += bit(3) + x += 9 + if x > 1: + b1 += bit(0) + x -= 3 + if x < -1: + b1 += bit(1) + x += 3 + if x > 0: + b0 += bit(0) + x -= 1 + if x < 0: + b0 += bit(1) + x += 1 + if x != 0: + raise ValueError("The dx value given to the writer exceeds maximum allowed.") + if y > 40: + b2 += bit(5) + y -= 81 + if y < -40: + b2 += bit(4) + y += 81 + if y > 13: + b1 += bit(5) + y -= 27 + if y < -13: + b1 += bit(4) + y += 27 + if y > 4: + b0 += bit(5) + y -= 9 + if y < -4: + b0 += bit(4) + y += 9 + if y > 1: + b1 += bit(7) + y -= 3 + if y < -1: + b1 += bit(6) + y += 3 + if y > 0: + b0 += bit(7) + y -= 1 + if y < 0: + b0 += bit(6) + y += 1 + if y != 0: + raise ValueError("The dy value given to the writer exceeds maximum allowed.") + elif flags is COLOR_CHANGE: + b2 = 0b11000011 + elif flags is STOP: + b2 = 0b11000011 + elif flags is END: + b2 = 0b11110011 + elif flags is SEQUIN_MODE: + b2 = 0b01000011 + return bytes(bytearray([b0, b1, b2])) + + +def write(pattern, f, settings=None): + extended_header = False + if settings is not None: + extended_header = settings.get("extended header", extended_header) + + extends = pattern.extends() + width = extends[2] - extends[0] + height = extends[3] - extends[1] + + name = pattern.get_metadata("name", "Untitled") + + 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_color_changes()) + x_extend = int(round(PPMM * width / 2)) + y_extend = int(round(PPMM * height / 2)) + write_string_utf8(f, "+X:%5d\r" % x_extend) + write_string_utf8(f, "-X:%5d\r" % x_extend) + write_string_utf8(f, "+Y:%5d\r" % y_extend) + write_string_utf8(f, "-Y:%5d\r" % y_extend) + write_string_utf8(f, "AX:+%5d\r" % 0) + write_string_utf8(f, "AY:+%5d\r" % 0) + write_string_utf8(f, "MX:+%5d\r" % 0) + write_string_utf8(f, "AY:+%5d\r" % 0) + write_string_utf8(f, "PD:%6s\r" % "******") + if extended_header: + author = pattern.get_metadata("author") + if author is not None: + write_string_utf8(f, "AU:%s\r" % author) + meta_copyright = pattern.get_metadata("copyright") + if meta_copyright is not None: + write_string_utf8(f, "CP:%s\r" % meta_copyright) + if len(pattern.threadlist) > 0: + for thread in pattern.threadlist: + write_string_utf8(f, "TC:%s,%s,%s\r" % + (thread.hex_color(), + thread.description, + thread.catalog_number)) + f.write(b'\x1a') + for i in range(f.tell(), DSTHEADERSIZE): + f.write(b'\x20') # space + + stitches = pattern.stitches + xx = 0 + yy = 0 + for stitch in stitches: + x = stitch[0] + y = stitch[1] + data = stitch[2] + dx = int(round(x - xx)) + dy = int(round(y - yy)) + xx += dx + yy += dy + if data == TRIM: + f.write(encode_record(2, 2, JUMP)) + f.write(encode_record(-4, -4, JUMP)) + f.write(encode_record(2, 2, JUMP)) + else: + f.write(encode_record(dx, dy, data)) diff --git a/pyembroidery/DszReader.py b/pyembroidery/DszReader.py new file mode 100644 index 0000000..05cbd79 --- /dev/null +++ b/pyembroidery/DszReader.py @@ -0,0 +1,46 @@ +from .DstReader import dst_read_header + + +def z_stitch_encoding_read(f, out): + stitched_yet = False + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + + y = -byte[0] + x = byte[1] + ctrl = byte[2] + + if ctrl & 0x40 != 0: + x = -x + if ctrl & 0x20 != 0: + y = -y + + if (ctrl & 0b11111) == 0: + stitched_yet = True + out.stitch(x, y) + continue + if (ctrl & 0b11111) == 1: + out.move(x, y) + continue + if ctrl == 0x82: + out.stop() + continue + if ctrl == 0x9B: + out.trim() + continue + if 0x83 <= ctrl <= 0x9A: + needle = (ctrl - 0x83) >> 1 + if stitched_yet: + out.color_change() + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + dst_read_header(f, out) + z_stitch_encoding_read(f, out) diff --git a/pyembroidery/EmbConstant.py b/pyembroidery/EmbConstant.py new file mode 100644 index 0000000..a34c162 --- /dev/null +++ b/pyembroidery/EmbConstant.py @@ -0,0 +1,52 @@ +NO_COMMAND = -1 +STITCH = 0 +JUMP = 1 +TRIM = 2 +STOP = 3 +END = 4 +COLOR_CHANGE = 5 +SEQUIN_MODE = 6 +SEQUIN_EJECT = 7 +SLOW = 0xB +FAST = 0xC + +# Stitch with implied contingency. +SEW_TO = 0xB0 +NEEDLE_AT = 0xB1 + +STITCH_BREAK = 0xE0 + +SEQUENCE_BREAK = 0xE1 +COLOR_BREAK = 0xE2 +TIE_ON = 0xE4 +TIE_OFF = 0xE5 +FRAME_EJECT = 0xE9 + + +MATRIX_TRANSLATE = 0xC0 +MATRIX_SCALE = 0xC1 +MATRIX_ROTATE = 0xC2 +MATRIX_RESET = 0xC3 + +OPTION_ENABLE_TIE_ON = 0xD1 +OPTION_ENABLE_TIE_OFF = 0xD2 +OPTION_DISABLE_TIE_ON = 0xD3 +OPTION_DISABLE_TIE_OFF = 0xD4 +OPTION_MAX_STITCH_LENGTH = 0xD5 +OPTION_MAX_JUMP_LENGTH = 0xD6 +OPTION_EXPLICIT_TRIM = 0xD7 +OPTION_IMPLICIT_TRIM = 0xD8 + +CONTINGENCY_NONE = 0xF0 +CONTINGENCY_JUMP_NEEDLE = 0xF1 +CONTINGENCY_SEW_TO = 0xF2 + +CONTINGENCY_SEQUIN_UTILIZE = 0xF5 +CONTINGENCY_SEQUIN_JUMP = 0xF6 +CONTINGENCY_SEQUIN_STITCH = 0xF7 +CONTINGENCY_SEQUIN_REMOVE = 0xF8 + +# Eventually the commands are supposed to be limited to 255 thereby +# allowing additional information like, color change to color in position n +# To be stored in the higher level bits. +COMMAND_MASK = 0xFF diff --git a/pyembroidery/EmbEncoder.py b/pyembroidery/EmbEncoder.py new file mode 100644 index 0000000..be37bf5 --- /dev/null +++ b/pyembroidery/EmbEncoder.py @@ -0,0 +1,622 @@ +import math + +from .EmbConstant import * + + +class Transcoder: + def __init__(self, settings=None): + if settings is None: + settings = {} + self.max_stitch = settings.get("max_stitch", float('inf')) + self.max_jump = settings.get("max_jump", float('inf')) + self.full_jump = settings.get("full_jump", False) + strip_sequins = settings.get("strip_sequins", True) + if strip_sequins: + self.sequin_contingency = CONTINGENCY_SEQUIN_UTILIZE + else: + self.sequin_contingency = CONTINGENCY_SEQUIN_JUMP + self.sequin_contingency = settings.get("sequin_contingency", self.sequin_contingency) + + self.strip_speeds = settings.get("strip_speeds", True) + self.explicit_trim = settings.get("explicit_trim", True) + + self.has_tie_on = settings.get("tie_on", False) + self.has_tie_off = settings.get("tie_off", False) + self.long_stitch_contingency = \ + settings.get("long_stitch_contingency", CONTINGENCY_JUMP_NEEDLE) + + self.matrix = get_identity() + translate = settings.get("translate", None) + if translate is not None: + try: + m = get_translate(translate[0], translate[1]) + self.matrix = matrix_multiply(self.matrix, m) + except IndexError: + try: + m = get_translate(translate.x, translate.y) + self.matrix = matrix_multiply(self.matrix, m) + except AttributeError: + pass + scale = settings.get("scale", None) + if scale is not None: + try: + m = get_scale(scale[0], scale[1]) + self.matrix = matrix_multiply(self.matrix, m) + except (IndexError, TypeError): + try: + m = get_scale(scale.x, scale.y) + self.matrix = matrix_multiply(self.matrix, m) + except AttributeError: + m = get_scale(scale, scale) + self.matrix = matrix_multiply(self.matrix, m) + rotate = settings.get("rotate", None) + if rotate is not None: + m = get_rotate(rotate) + self.matrix = matrix_multiply(self.matrix, m) + self.source_pattern = None + self.destination_pattern = None + self.position = 0 + self.color_index = -1 + self.stitch = None + self.state_trimmed = True + self.state_sequin_mode = False + self.needle_x = 0 + self.needle_y = 0 + self.state_jumping = False + + def transcode(self, source_pattern, destination_pattern): + self.source_pattern = source_pattern + self.destination_pattern = destination_pattern + self.transcode_metadata() + self.transcode_threads() + self.transcode_stitches() + return destination_pattern + + def transcode_metadata(self): + """Transcodes metadata, (just moves)""" + source = self.source_pattern.extras + dest = self.destination_pattern.extras + dest.update(source) + + def transcode_threads(self): + """Transcodes threads, (just moves)""" + source = self.source_pattern.threadlist + dest = self.destination_pattern.threadlist + dest.extend(source) + + def transcode_stitches(self): + """Transcodes stitches. + Converts middle-level commands and potentially incompatible + commands into a format friendly low level commands.""" + source = self.source_pattern.stitches + self.state_trimmed = True + self.needle_x = 0 + self.needle_y = 0 + self.position = 0 + self.color_index = -1 + + flags = NO_COMMAND + for self.position, self.stitch in enumerate(source): + p = point_in_matrix_space(self.matrix, self.stitch) + x = p[0] + y = p[1] + flags = self.stitch[2] + + if flags == NO_COMMAND: + continue + + elif flags == STITCH: + if self.state_trimmed: + self.jump_to_within_stitchrange(x, y) + self.stitch_at(x, y) + if self.has_tie_on: + self.tie_on() + elif self.state_jumping: + self.needle_to(x, y) + self.state_jumping = False + else: + self.stitch_with_contingency(x, y) + elif flags == NEEDLE_AT: + if self.state_trimmed: + self.jump_to_within_stitchrange(x, y) + self.stitch_at(x, y) + if self.has_tie_on: + self.tie_on() + elif self.state_jumping: + self.needle_to(x, y) + self.state_jumping = False + else: + self.needle_to(x, y) + elif flags == SEW_TO: + if self.state_trimmed: + self.jump_to_within_stitchrange(x, y) + self.stitch_at(x, y) + if self.has_tie_on: + self.tie_on() + elif self.state_jumping: + self.needle_to(x, y) + self.state_jumping = False + else: + self.sew_to(x, y) + + # Middle Level Commands. + elif flags == STITCH_BREAK: + self.state_jumping = True + elif flags == FRAME_EJECT: + self.tie_off_and_trim_if_needed() + self.jump_to(x, y) + self.stop_here() + elif flags == SEQUENCE_BREAK: + self.tie_off_and_trim_if_needed() + elif flags == COLOR_BREAK: + self.color_break() + elif flags == TIE_OFF: + self.tie_off() + elif flags == TIE_ON: + self.tie_on() + + # Core Commands. + elif flags == TRIM: + self.tie_off_and_trim_if_needed() + elif flags == JUMP: + if not self.state_jumping: + self.jump_to(x, y) + elif flags == SEQUIN_MODE: + self.toggle_sequins() + elif flags == SEQUIN_EJECT: + if self.state_trimmed: + self.jump_to_within_stitchrange(x, y) + self.stitch_at(x, y) + if self.has_tie_on: + self.tie_on() + if not self.state_sequin_mode: + self.toggle_sequins() + self.sequin_at(x, y) + elif flags == COLOR_CHANGE: + self.tie_off_trim_color_change() + # If we are told to do something we do it. + # Even if it's the first command and makes no sense. + elif flags == STOP: + self.stop_here() + elif flags == SLOW: + self.slow_command_here() + elif flags == FAST: + self.fast_command_here() + elif flags == END: + self.end_here() + break + + # On-the-fly Settings Commands. + elif flags == OPTION_ENABLE_TIE_ON: + self.has_tie_on = True + elif flags == OPTION_ENABLE_TIE_OFF: + self.has_tie_off = True + elif flags == OPTION_DISABLE_TIE_ON: + self.has_tie_on = False + elif flags == OPTION_DISABLE_TIE_OFF: + self.has_tie_off = False + elif flags == OPTION_MAX_JUMP_LENGTH: + x = self.stitch[0] + self.max_jump = x + elif flags == OPTION_MAX_STITCH_LENGTH: + x = self.stitch[0] + self.max_stitch = x + elif flags == OPTION_EXPLICIT_TRIM: + self.explicit_trim = True + elif flags == OPTION_IMPLICIT_TRIM: + self.explicit_trim = False + elif flags == CONTINGENCY_NONE: + self.long_stitch_contingency = CONTINGENCY_NONE + elif flags == CONTINGENCY_JUMP_NEEDLE: + self.long_stitch_contingency = CONTINGENCY_JUMP_NEEDLE + elif flags == CONTINGENCY_SEW_TO: + self.long_stitch_contingency = CONTINGENCY_SEW_TO + elif flags == CONTINGENCY_SEQUIN_REMOVE: + if self.state_sequin_mode: # if sequin_mode, turn it off. + self.toggle_sequins() + self.sequin_contingency = CONTINGENCY_SEQUIN_REMOVE + elif flags == CONTINGENCY_SEQUIN_STITCH: + if self.state_sequin_mode: # if sequin_mode, turn it off. + self.toggle_sequins() + self.sequin_contingency = CONTINGENCY_SEQUIN_STITCH + elif flags == CONTINGENCY_SEQUIN_JUMP: + if self.state_sequin_mode: # if sequin_mode, turn it off. + self.toggle_sequins() + self.sequin_contingency = CONTINGENCY_SEQUIN_REMOVE + elif flags == CONTINGENCY_SEQUIN_UTILIZE: + self.sequin_contingency = CONTINGENCY_SEQUIN_UTILIZE + elif flags == MATRIX_TRANSLATE: + m = get_translate(self.stitch[0], self.stitch[1]) + self.matrix = matrix_multiply(self.matrix, m) + elif flags == MATRIX_SCALE: + m = get_scale(self.stitch[0], self.stitch[1]) + self.matrix = matrix_multiply(self.matrix, m) + elif flags == MATRIX_ROTATE: + m = get_rotate(self.stitch[0]) + self.matrix = matrix_multiply(self.matrix, m) + elif flags == MATRIX_RESET: + self.matrix = get_identity() + if flags != END: + self.end_here() + + def update_needle_position(self, x, y): + self.needle_x = x + self.needle_y = y + + def declare_not_trimmed(self): + if self.state_trimmed: + self.state_trimmed = False + if self.color_index == -1: + self.color_index = 0 + + def add(self, flags, x=None, y=None): + if x is None: + x = self.needle_x + if y is None: + y = self.needle_y + self.destination_pattern.stitches.append([x, y, flags]) + + def lookahead_stitch(self): + """Looks forward from current position and + determines if anymore stitching will occur.""" + source = self.source_pattern.stitches + for pos in range(self.position, len(source)): + stitch = source[pos] + flags = stitch[2] + if flags == STITCH: + return True + elif flags == NEEDLE_AT: + return True + elif flags == SEW_TO: + return True + elif flags == TIE_ON: + return True + elif flags == SEQUIN_EJECT: + return True + elif flags == END: + return False + return False + + def color_break(self): + """Implements color break. Should add color changes add needed only.""" + if self.color_index < 0: + return # We haven't stitched anything, colorbreak happens, before start. Ignore. + if not self.state_trimmed: + if self.has_tie_off: + self.tie_off() + if self.explicit_trim: + self.trim_here() + if not self.lookahead_stitch(): + return # No more stitching will happen, colorchange unneeded. + self.add(COLOR_CHANGE) + self.color_index += 1 + self.state_trimmed = True + + def tie_off_trim_color_change(self): + if not self.state_trimmed: + if self.has_tie_off: + self.tie_off() + if self.explicit_trim: + self.trim_here() + self.add(COLOR_CHANGE) + self.color_index += 1 + self.state_trimmed = True + + def tie_off_and_trim_if_needed(self): + if not self.state_trimmed: + self.tie_off_and_trim() + + def tie_off_and_trim(self): + if self.has_tie_off: + self.tie_off() + self.trim_here() + + def tie_off(self): + try: + b = point_in_matrix_space( + self.matrix, + self.source_pattern.stitches[self.position - 1], + ) + flags = b[2] + if flags == STITCH or flags == NEEDLE_AT or \ + flags == SEW_TO or flags == SEQUIN_EJECT: + self.lock_stitch(self.needle_x, self.needle_y, + b[0], b[1], self.max_stitch) + except IndexError: + pass # must be an island stitch. jump-stitch-jump + + def tie_on(self): + try: + b = point_in_matrix_space( + self.matrix, + self.source_pattern.stitches[self.position + 1] + ) + flags = b[2] + if flags == STITCH or flags == NEEDLE_AT or \ + flags == SEW_TO or flags == SEQUIN_EJECT: + self.lock_stitch(self.needle_x, self.needle_y, + b[0], b[1], self.max_stitch) + except IndexError: + pass # must be an island stitch. jump-stitch-jump + + def trim_here(self): + if self.state_sequin_mode: + # Can't trim in sequin mode. DST uses jumps to trigger sequin eject and to trim. + self.toggle_sequins() + self.add(TRIM) + self.state_trimmed = True + + def toggle_sequins(self): + """Sequin mode toggle can be called whenever but will only actually turn on if set + to utilize mode for the sequin contingency.""" + contingency = self.sequin_contingency + if contingency == CONTINGENCY_SEQUIN_UTILIZE: + self.add(SEQUIN_MODE) + self.state_sequin_mode = not self.state_sequin_mode + + def jump_to_within_stitchrange(self, new_x, new_y): + """Jumps close enough to stitch a position in x,y + without violating the length constraints.""" + x0 = self.needle_x + y0 = self.needle_y + max_length = self.max_jump + self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, JUMP) + if self.full_jump: + if self.needle_x != new_x or self.needle_y != new_y: + self.jump_at(new_x, new_y) + # We are currently assuming that max_jump is also max_stitch. + # Properly it might be the case that some format could require + # a split constraint here where we would need to jump further + # so that we could then stitch closer. + + def jump_to(self, new_x, new_y): + x0 = self.needle_x + y0 = self.needle_y + max_length = self.max_jump + self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, JUMP) + self.jump_at(new_x, new_y) + + def jump_at(self, new_x, new_y): + if self.state_sequin_mode: + self.toggle_sequins() # can't jump with sequin mode on. + self.add(JUMP, new_x, new_y) + self.update_needle_position(new_x, new_y) + + def stitch_with_contingency(self, new_x, new_y): + if self.long_stitch_contingency == CONTINGENCY_SEW_TO: + self.sew_to(new_x, new_y) + elif self.long_stitch_contingency == CONTINGENCY_JUMP_NEEDLE: + self.needle_to(new_x, new_y) + else: + self.stitch_at(new_x, new_y) + + def sew_to(self, new_x, new_y): + """Stitches to a specific location, with the emphasis on sewing. + Subdivides long stitches into additional stitches. + """ + x0 = self.needle_x + y0 = self.needle_y + max_length = self.max_stitch + self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, STITCH) + self.stitch_at(new_x, new_y) + + def needle_to(self, new_x, new_y): + """Insert needle at specific location, emphasis on the needle. + Uses jumps to avoid needle penetrations where possible. + + The limit here is the max stitch limit or jump threshold. + If jump threshold is set low, it will insert jumps even + between stitches it could have technically encoded values for. + + Stitches to the new location, adding jumps if needed. + """ + x0 = self.needle_x + y0 = self.needle_y + max_length = self.max_stitch + self.interpolate_gap_stitches(x0, y0, new_x, new_y, max_length, JUMP) + self.stitch_at(new_x, new_y) + + def stitch_at(self, new_x, new_y): + """Inserts a stitch at the specific location. + Should have already been checked for constraints.""" + self.add(STITCH, new_x, new_y) + self.update_needle_position(new_x, new_y) + self.declare_not_trimmed() + + def sequin_at(self, new_x, new_y): + contingency = self.sequin_contingency + if contingency == CONTINGENCY_SEQUIN_UTILIZE: + self.add(SEQUIN_EJECT, new_x, new_y) + elif contingency == CONTINGENCY_SEQUIN_JUMP: + self.add(JUMP, new_x, new_y) + elif contingency == CONTINGENCY_SEQUIN_STITCH: + self.add(STITCH, new_x, new_y) + elif contingency == CONTINGENCY_SEQUIN_REMOVE: + # Do not update the needle position or declare untrimmed. + return + self.update_needle_position(new_x, new_y) + self.declare_not_trimmed() + + def slow_command_here(self): + if not self.strip_speeds: + self.add(SLOW) + + def fast_command_here(self): + if not self.strip_speeds: + self.add(FAST) + + def stop_here(self): + self.add(STOP) + self.state_trimmed = True + + def end_here(self): + self.add(END) + self.state_trimmed = True + + def color_change_here(self): + self.add(COLOR_CHANGE) + self.color_index += 1 + self.state_trimmed = True + + def position_will_exceed_constraint(self, length=None, new_x=None, new_y=None): + """Check if the stitch is too long before trying to deal with it.""" + if length is None: + length = self.max_stitch + if new_x is None or new_y is None: + p = point_in_matrix_space(self.matrix, + self.stitch[0], + self.stitch[1]) + new_x = p[0] + new_y = p[1] + distance_x = new_x - self.needle_x + distance_y = new_y - self.needle_y + return abs(distance_x) > length or abs(distance_y) > length + + def interpolate_gap_stitches(self, x0, y0, x1, y1, max_length, data): + """Command sequence line to x, y, respecting length as maximum. + This does not arrive_at, it steps to within striking distance. + The next step can arrive at (x, y) without violating constraint. + If these are already in range, this command will do nothing. + + returns the last stitch interpolated by the code. + """ + transcode = self.destination_pattern.stitches + distance_x = x1 - x0 + distance_y = y1 - y0 + if abs(distance_x) > max_length or abs(distance_y) > max_length: + if data == JUMP and self.state_sequin_mode: + self.toggle_sequins() # can't jump with sequin mode on. + + # python 2,3 patch of division that could be integer. + steps_x = math.ceil(abs(distance_x / (max_length * 1.0))) + steps_y = math.ceil(abs(distance_y / (max_length * 1.0))) + if steps_x > steps_y: + steps = steps_x + else: + steps = steps_y + step_size_x = distance_x / steps + step_size_y = distance_y / steps + qx = x0 + qy = y0 + for q in range(1, int(steps)): + # we need the gap stitches only, not start or end stitch. + qx += step_size_x + qy += step_size_y + stitch = [round(qx), round(qy), data] + transcode.append(stitch) + self.update_needle_position(stitch[0], stitch[1]) + + def lock_stitch(self, x, y, anchor_x, anchor_y, max_length=None): + """Tie-on, Tie-off. Lock stitch from current location towards + anchor location.Ends again at lock location. May not exceed + max_length in the process.""" + if max_length is None: + max_length = self.max_stitch + transcode = self.destination_pattern.stitches + length = distance(x, y, anchor_x, anchor_y) + if length > max_length: + p = oriented(x, y, anchor_x, anchor_y, max_length) + anchor_x = p[0] + anchor_y = p[1] + for amount in (.33, .66, .33, 0): + transcode.append([ + towards(x, anchor_x, amount), + towards(y, anchor_y, amount), + STITCH]) + + +def distance_squared(x0, y0, x1, y1): + """squared of distance between x0,y0 and x1,y1""" + dx = x1 - x0 + dy = y1 - y0 + dx *= dx + dy *= dy + return dx + dy + + +def distance(x0, y0, x1, y1): + """distance between x0,y0 and x1,y1""" + return math.sqrt(distance_squared(x0, y0, x1, y1)) + + +def towards(a, b, amount): + """amount between [0,1] -> [a,b]""" + return (amount * (b - a)) + a + + +def angle_radians(x0, y0, x1, y1): + """Angle in radians between x0,y0 and x1,y1""" + return math.atan2(y1 - y0, x1 - x0) + + +def oriented(x0, y0, x1, y1, r): + """from x0,y0 in the direction of x1,y1 in the distance of r""" + radians = angle_radians(x0, y0, x1, y1) + return x0 + (r * math.cos(radians)), y0 + (r * math.sin(radians)) + + +def get_identity(): + return \ + 1, 0, 0, \ + 0, 1, 0, \ + 0, 0, 1 # identity + + +def get_scale(sx, sy=None): + if sy is None: + sy = sx + return \ + sx, 0, 0, \ + 0, sy, 0, \ + 0, 0, 1 + + +def get_translate(tx, ty): + return \ + 1, 0, 0, \ + 0, 1, 0, \ + tx, ty, 1 + + +def get_rotate(theta): + tau = math.pi * 2 + theta *= tau / 360 + ct = math.cos(theta) + st = math.sin(theta) + return \ + ct, st, 0, \ + -st, ct, 0, \ + 0, 0, 1 + + +def matrix_multiply(a, b): + return [ + a[0] * b[0] + a[1] * b[3] + a[2] * b[6], + a[0] * b[1] + a[1] * b[4] + a[2] * b[7], + a[0] * b[2] + a[1] * b[5] + a[2] * b[8], + a[3] * b[0] + a[4] * b[3] + a[5] * b[6], + a[3] * b[1] + a[4] * b[4] + a[5] * b[7], + a[3] * b[2] + a[4] * b[5] + a[5] * b[8], + a[6] * b[0] + a[7] * b[3] + a[8] * b[6], + a[6] * b[1] + a[7] * b[4] + a[8] * b[7], + a[6] * b[2] + a[7] * b[5] + a[8] * b[8]] + + +def point_in_matrix_space(matrix, v0, v1=None): + if v1 is None: + try: + return [ + v0[0] * matrix[0] + v0[1] * matrix[3] + 1 * matrix[6], + v0[0] * matrix[1] + v0[1] * matrix[4] + 1 * matrix[7], + v0[2] + ] + except IndexError: + return [ + v0[0] * matrix[0] + v0[1] * matrix[3] + 1 * matrix[6], + v0[0] * matrix[1] + v0[1] * matrix[4] + 1 * matrix[7] + # Must not have had a 3rd element. + ] + return [ + v0 * matrix[0] + v1 * matrix[3] + 1 * matrix[6], + v0 * matrix[1] + v1 * matrix[4] + 1 * matrix[7] + ] diff --git a/pyembroidery/EmbPattern.py b/pyembroidery/EmbPattern.py new file mode 100644 index 0000000..7c0fd20 --- /dev/null +++ b/pyembroidery/EmbPattern.py @@ -0,0 +1,374 @@ +import random + +from .EmbThread import EmbThread +from .EmbEncoder import Transcoder as Normalizer +from .EmbConstant import * + + +class EmbPattern: + def __init__(self): + self.stitches = [] # type: list + self.threadlist = [] # type: list + self.extras = {} + # filename, name, category, author, keywords, comments, are typical + self._previousX = 0 # type: float + self._previousY = 0 # type: float + + def move(self, dx=0, dy=0): + """Move dx, dy""" + self.add_stitch_relative(JUMP, dx, dy) + + def move_abs(self, x, y): + """Move absolute x, y""" + self.add_stitch_absolute(JUMP, x, y) + + def stitch(self, dx=0, dy=0): + """Stitch dx, dy""" + self.add_stitch_relative(STITCH, dx, dy) + + def stitch_abs(self, x, y): + """Stitch absolute x, y""" + self.add_stitch_absolute(STITCH, x, y) + + def stop(self, dx=0, dy=0): + """Stop dx, dy""" + self.add_stitch_relative(STOP, dx, dy) + + def trim(self, dx=0, dy=0): + """Trim dx, dy""" + self.add_stitch_relative(TRIM, dx, dy) + + def color_change(self, dx=0, dy=0): + """Color Change dx, dy""" + self.add_stitch_relative(COLOR_CHANGE, dx, dy) + + def sequin_eject(self, dx=0, dy=0): + """Eject Sequin dx, dy""" + self.add_stitch_relative(SEQUIN_EJECT, dx, dy) + + def sequin_mode(self, dx=0, dy=0): + """Eject Sequin dx, dy""" + self.add_stitch_relative(SEQUIN_MODE, dx, dy) + + def end(self, dx=0, dy=0): + """End Design dx, dy""" + self.add_stitch_relative(END, dx, dy) + + def add_thread(self, thread): + """Adds thread to design. + Note: this has no effect on stitching and can be done at any point.""" + if isinstance(thread, EmbThread): + self.threadlist.append(thread) + elif isinstance(thread, int): + thread_object = EmbThread() + thread_object.color = thread + self.threadlist.append(thread_object) + elif isinstance(thread, dict): + thread_object = EmbThread() + if "name" in thread: + thread_object.description = thread["name"] + if "description" in thread: + thread_object.description = thread["description"] + if "desc" in thread: + thread_object.description = thread["desc"] + if "brand" in thread: + thread_object.brand = thread["brand"] + if "manufacturer" in thread: + thread_object.brand = thread["manufacturer"] + if "color" in thread or "rgb" in thread: + try: + color = thread["color"] + except KeyError: + color = thread["rgb"] + if isinstance(color, int): + thread_object.color = thread["color"] + elif isinstance(color, str): + if color == "random": + thread_object.color = 0xFF000000 | random.randint(0, 0xFFFFFF) + if color[0:1] == "#": + thread_object.set_hex_color(color[1:]) + elif isinstance(color, tuple) or isinstance(color, list): + thread_object.color = (color[0] & 0xFF) << 16 | \ + (color[1] & 0xFF) << 8 | \ + (color[2] & 0xFF) + if "hex" in thread: + thread_object.set_hex_color(thread["hex"]) + if "id" in thread: + thread_object.catalog_number = thread["id"] + if "catalog" in thread: + thread_object.catalog_number = thread["catalog"] + self.threadlist.append(thread_object) + + def metadata(self, name, data): + """Adds select metadata to design. + Note: this has no effect on stitching and can be done at any point.""" + self.extras[name] = data + + def get_metadata(self, name, default=None): + return self.extras.get(name, default) + + def extends(self): + min_x = float('inf') + min_y = float('inf') + max_x = -float('inf') + max_y = -float('inf') + + for stitch in self.stitches: + if stitch[0] > max_x: + max_x = stitch[0] + if stitch[0] < min_x: + min_x = stitch[0] + if stitch[1] > max_y: + max_y = stitch[1] + if stitch[1] < min_y: + min_y = stitch[1] + return min_x, min_y, max_x, max_y + + def count_stitch_commands(self, command): + count = 0 + for stitch in self.stitches: + flags = stitch[2] + if flags == command: + count += 1 + return count + + def count_color_changes(self): + return self.count_stitch_commands(COLOR_CHANGE) + + def count_stitches(self): + return len(self.stitches) + + def count_threads(self): + return len(self.threadlist) + + @staticmethod + def get_random_thread(): + thread = EmbThread() + thread.color = 0xFF000000 | random.randint(0, 0xFFFFFF) + thread.description = "Random" + return thread + + def get_thread_or_filler(self, index): + if len(self.threadlist) <= index: + return self.get_random_thread() + else: + return self.threadlist[index] + + def get_as_stitchblock(self): + stitchblock = [] + thread = self.get_thread_or_filler(0) + thread_index = 1 + for stitch in self.stitches: + flags = stitch[2] + if flags == STITCH: + stitchblock.append(stitch) + else: + if len(stitchblock) > 0: + yield (stitchblock, thread) + stitchblock = [] + if flags == COLOR_CHANGE: + thread = self.get_thread_or_filler(thread_index) + thread_index += 1 + if len(stitchblock) > 0: + yield (stitchblock, thread) + + def get_as_command_blocks(self): + last_pos = 0 + last_command = NO_COMMAND + for pos, stitch in enumerate(self.stitches): + command = stitch[2] + if command == last_command or last_command == NO_COMMAND: + last_command = command + continue + last_command = command + yield self.stitches[last_pos:pos] + last_pos = pos + yield self.stitches[last_pos:] + + def get_as_colorblocks(self): + thread_index = 0 + last_pos = 0 + for pos, stitch in enumerate(self.stitches): + if stitch[2] != COLOR_CHANGE: + continue + thread = self.get_thread_or_filler(thread_index) + thread_index += 1 + yield (self.stitches[last_pos:pos], thread) + last_pos = pos + thread = self.get_thread_or_filler(thread_index) + yield (self.stitches[last_pos:], thread) + + def get_unique_threadlist(self): + return set(self.threadlist) + + def get_singleton_threadlist(self): + singleton = [] + last_thread = None + for thread in self.threadlist: + if thread != last_thread: + singleton.append(thread) + last_thread = thread + return singleton + + def move_center_to_origin(self): + extends = self.extends() + 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): + for stitch in self.stitches: + stitch[0] += dx + stitch[1] += dy + + def fix_color_count(self): + """Ensure the there are threads for all color blocks.""" + thread_index = 0 + init_color = True + for stitch in self.stitches: + data = stitch[2] & COMMAND_MASK + if data == STITCH or data == SEW_TO or data == NEEDLE_AT: + if init_color: + thread_index += 1 + init_color = False + elif data == COLOR_CHANGE or data == COLOR_BREAK: + init_color = True + while len(self.threadlist) < thread_index: + self.add_thread(self.get_thread_or_filler(len(self.threadlist))) + + def add_stitch_absolute(self, cmd, x=0, y=0): + """Add a command at the absolute location: x, y""" + self.stitches.append([x, y, cmd]) + self._previousX = x + self._previousY = y + + def add_stitch_relative(self, cmd, dx=0, dy=0): + """Add a command relative to the previous location""" + x = self._previousX + dx + y = self._previousY + dy + self.add_stitch_absolute(cmd, x, y) + + def add_command(self, cmd, x=0, y=0): + """Add a command, without treating parameters as locations + that require an update""" + self.stitches.append([x, y, cmd]) + + def add_stitchblock(self, stitchblock): + threadlist = self.threadlist + block = stitchblock[0] + thread = stitchblock[1] + if len(threadlist) == 0 or thread is not threadlist[-1]: + threadlist.append(thread) + self.add_stitch_relative(COLOR_BREAK) + else: + self.add_stitch_relative(SEQUENCE_BREAK) + + for stitch in block: + try: + self.add_stitch_absolute(stitch.command, stitch.x, stitch.y) + except AttributeError: + self.add_stitch_absolute(stitch[2], stitch[0], stitch[1]) + + def get_pattern_interpolate_trim(self, jumps_to_require_trim): + """Gets a processed pattern with untrimmed jumps merged + and trims added if merged jumps are beyond the given value. + The expectation is that it has core commands and not + middle-level commands""" + new_pattern = EmbPattern() + i = -1 + ie = len(self.stitches) - 1 + count = 0 + trimmed = True + while i < ie: + i += 1 + stitch = self.stitches[i] + command = stitch[2] + if command == STITCH or command == SEQUIN_EJECT: + trimmed = False + elif command == COLOR_CHANGE or command == TRIM: + trimmed = True + if trimmed or stitch[2] != JUMP: + new_pattern.add_stitch_absolute(stitch[2], + stitch[0], + stitch[1]) + continue + while i < ie and command == JUMP: + i += 1 + stitch = self.stitches[i] + command = stitch[2] + count += 1 + if command != JUMP: + i -= 1 + stitch = self.stitches[i] + if count >= jumps_to_require_trim: + new_pattern.trim() + count = 0 + new_pattern.add_stitch_absolute(stitch[2], + stitch[0], + stitch[1]) + new_pattern.threadlist.extend(self.threadlist) + new_pattern.extras.update(self.extras) + return new_pattern + + def get_pattern_merge_jumps(self): + """Returns a pattern with all multiple jumps merged.""" + new_pattern = EmbPattern() + i = -1 + ie = len(self.stitches) - 1 + stitch_break = False + while i < ie: + i += 1 + stitch = self.stitches[i] + if stitch[2] == JUMP: + if stitch_break: + continue + new_pattern.add_command(STITCH_BREAK) + stitch_break = True + continue + new_pattern.add_stitch_absolute(stitch[2], + stitch[0], + stitch[1]) + new_pattern.threadlist.extend(self.threadlist) + new_pattern.extras.update(self.extras) + return new_pattern + + def get_stable_pattern(self): + """Gets a stablized version of the pattern.""" + stable_pattern = EmbPattern() + for stitchblock in self.get_as_stitchblock(): + stable_pattern.add_stitchblock(stitchblock) + stable_pattern.extras.update(self.extras) + return stable_pattern + + def get_normalized_pattern(self, encode_settings=None): + """Encodes""" + normal_pattern = EmbPattern() + transcoder = Normalizer(encode_settings) + transcoder.transcode(self, normal_pattern) + return normal_pattern + + def append_translation(self, x, y): + """Appends translation to the pattern. + All commands will be translated by the given amount, + including absolute location commands.""" + self.add_stitch_relative(MATRIX_TRANSLATE, x, y, ) + + def append_enable_tie_on(self, x=0, y=0): + """Appends enable tie on. + All starts of new stitching will be tied on""" + self.add_stitch_relative(OPTION_ENABLE_TIE_ON, x, y) + + def append_enable_tie_off(self, x=0, y=0): + """Appends enable tie off. + All ends of stitching will be tied off""" + self.add_stitch_relative(OPTION_ENABLE_TIE_OFF, x, y) + + def append_disable_tie_on(self, x=0, y=0): + """Appends disable tie on. + New stitching will no longer be tied on""" + self.add_stitch_relative(OPTION_DISABLE_TIE_ON, x, y) + + def append_disable_tie_off(self, x=0, y=0): + """Appends enable tie off. + Ends of stitching will no longer be tied off""" + self.add_stitch_relative(OPTION_DISABLE_TIE_OFF, x, y) diff --git a/pyembroidery/EmbThread.py b/pyembroidery/EmbThread.py new file mode 100644 index 0000000..d15b805 --- /dev/null +++ b/pyembroidery/EmbThread.py @@ -0,0 +1,83 @@ +def find_nearest_color_index(find_color, values): + if isinstance(find_color, EmbThread): + find_color = find_color.color + red = (find_color >> 16) & 0xff + green = (find_color >> 8) & 0xff + blue = find_color & 0xff + closest_index = -1 + current_index = -1 + current_closest_value = float("inf") + for t in values: + current_index += 1 + if t is None: + continue + dist = color_distance_red_mean( + red, + green, + blue, + t.get_red(), + t.get_green(), + t.get_blue()) + if dist <= current_closest_value: # <= choose second if they tie. + current_closest_value = dist + closest_index = current_index + return closest_index + + +def color_distance_red_mean( + r1, g1, b1, + r2, g2, b2): + red_mean = int(round((r1 + r2) / 2)) + r = int(r1 - r2) + g = int(g1 - g2) + b = int(b1 - b2) + return (((512 + red_mean) * r * r) >> 8) + 4 * g * g + \ + (((767 - red_mean) * b * b) >> 8) + # See the very good color distance paper: + # https://www.compuphase.com/cmetric.htm + + +class EmbThread: + + def __init__(self): + self.color = 0xFF000000 + self.description = None # type: str + self.catalog_number = None # type: str + self.details = None # type: str + self.brand = None # type: str + self.chart = None # type: str + self.weight = None # type: str + # description, catalog_number, details, brand, chart, weight + + def set_color(self, r, g, b): + self.color = 0xFF000000 | ( + (r & 255) << 16) | ( + (g & 255) << 8) | ( + b & 255) + + def get_opaque_color(self): + return 0xFF000000 | self.color + + def get_red(self): + return (self.color >> 16) & 0xFF + + def get_green(self): + return (self.color >> 8) & 0xFF + + def get_blue(self): + return self.color & 0xFF + + def find_nearest_color_index(self, values): + return find_nearest_color_index(self.color, values) + + def hex_color(self): + return "#%02x%02x%02x" % ( + self.get_red(), self.get_green(), self.get_blue()) + + def set_hex_color(self, hex_string): + h = hex_string.lstrip('#') + size = len(h) + if size == 6 or size == 8: + self.color = int(h[:6], 16) + elif size == 4 or size == 3: + self.color = int(h[2] + h[2] + h[1] + h[1] + h[0] + h[0], 16) diff --git a/pyembroidery/EmbThreadJef.py b/pyembroidery/EmbThreadJef.py new file mode 100644 index 0000000..47dbcef --- /dev/null +++ b/pyembroidery/EmbThreadJef.py @@ -0,0 +1,95 @@ +from .EmbThread import EmbThread + + +def get_thread_set(): + return [ + EmbThreadJef(0x000000, "Placeholder", "000"), + EmbThreadJef(0x000000, "Black", "002"), + EmbThreadJef(0xffffff, "White", "001"), + EmbThreadJef(0xffff17, "Yellow", "204"), + EmbThreadJef(0xff6600, "Orange", "203"), + EmbThreadJef(0x2f5933, "Olive Green", "219"), + EmbThreadJef(0x237336, "Green", "226"), + EmbThreadJef(0x65c2c8, "Sky", "217"), + EmbThreadJef(0xab5a96, "Purple", "208"), + EmbThreadJef(0xf669a0, "Pink", "201"), + EmbThreadJef(0xff0000, "Red", "225"), + EmbThreadJef(0xb1704e, "Brown", "214"), + EmbThreadJef(0x0b2f84, "Blue", "207"), + EmbThreadJef(0xe4c35d, "Gold", "003"), + EmbThreadJef(0x481a05, "Dark Brown", "205"), + EmbThreadJef(0xac9cc7, "Pale Violet", "209"), + EmbThreadJef(0xfcf294, "Pale Yellow", "210"), + EmbThreadJef(0xf999b7, "Pale Pink", "211"), + EmbThreadJef(0xfab381, "Peach", "212"), + EmbThreadJef(0xc9a480, "Beige", "213"), + EmbThreadJef(0x970533, "Wine Red", "215"), + EmbThreadJef(0xa0b8cc, "Pale Sky", "216"), + EmbThreadJef(0x7fc21c, "Yellow Green", "218"), + EmbThreadJef(0xe5e5e5, "Silver Gray", "220"), + EmbThreadJef(0x889b9b, "Gray", "221"), + EmbThreadJef(0x98d6bd, "Pale Aqua", "227"), + EmbThreadJef(0xb2e1e3, "Baby Blue", "228"), + EmbThreadJef(0x368ba0, "Powder Blue", "229"), + EmbThreadJef(0x4f83ab, "Bright Blue", "230"), + EmbThreadJef(0x386a91, "Slate Blue", "231"), + EmbThreadJef(0x071650, "Navy Blue", "232"), + EmbThreadJef(0xf999a2, "Salmon Pink", "233"), + EmbThreadJef(0xf9676b, "Coral", "234"), + EmbThreadJef(0xe3311f, "Burnt Orange", "235"), + EmbThreadJef(0xe2a188, "Cinnamon", "236"), + EmbThreadJef(0xb59474, "Umber", "237"), + EmbThreadJef(0xe4cf99, "Blond", "238"), + EmbThreadJef(0xffcb00, "Sunflower", "239"), + EmbThreadJef(0xe1add4, "Orchid Pink", "240"), + EmbThreadJef(0xc3007e, "Peony Purple", "241"), + EmbThreadJef(0x80004b, "Burgundy", "242"), + EmbThreadJef(0x540571, "Royal Purple", "243"), + EmbThreadJef(0xb10525, "Cardinal Red", "244"), + EmbThreadJef(0xcae0c0, "Opal Green", "245"), + EmbThreadJef(0x899856, "Moss Green", "246"), + EmbThreadJef(0x5c941a, "Meadow Green", "247"), + EmbThreadJef(0x003114, "Dark Green", "248"), + EmbThreadJef(0x5dae94, "Aquamarine", "249"), + EmbThreadJef(0x4cbf8f, "Emerald Green", "250"), + EmbThreadJef(0x007772, "Peacock Green", "251"), + EmbThreadJef(0x595b61, "Dark Gray", "252"), + EmbThreadJef(0xfffff2, "Ivory White", "253"), + EmbThreadJef(0xb15818, "Hazel", "254"), + EmbThreadJef(0xcb8a07, "Toast", "255"), + EmbThreadJef(0x986c80, "Salmon", "256"), + EmbThreadJef(0x98692d, "Cocoa Brown", "257"), + EmbThreadJef(0x4d3419, "Sienna", "258"), + EmbThreadJef(0x4c330b, "Sepia", "259"), + EmbThreadJef(0x33200a, "Dark Sepia", "260"), + EmbThreadJef(0x523a97, "Violet Blue", "261"), + EmbThreadJef(0x0d217e, "Blue Ink", "262"), + EmbThreadJef(0x1e77ac, "Sola Blue", "263"), + EmbThreadJef(0xb2dd53, "Green Dust", "264"), + EmbThreadJef(0xf33689, "Crimson", "265"), + EmbThreadJef(0xde649e, "Floral Pink", "266"), + EmbThreadJef(0x984161, "Wine", "267"), + EmbThreadJef(0x4c5612, "Olive Drab", "268"), + EmbThreadJef(0x4c881f, "Meadow", "269"), + EmbThreadJef(0xe4de79, "Mustard", "270"), + EmbThreadJef(0xcb8a1a, "Yellow Ocher", "271"), + EmbThreadJef(0xcba21c, "Old Gold", "272"), + EmbThreadJef(0xff9805, "Honey Dew", "273"), + EmbThreadJef(0xfcb257, "Tangerine", "274"), + EmbThreadJef(0xffe505, "Canary Yellow", "275"), + EmbThreadJef(0xf0331f, "Vermilion", "202"), + EmbThreadJef(0x1a842d, "Bright Green", "206"), + EmbThreadJef(0x386cae, "Ocean Blue", "222"), + EmbThreadJef(0xe3c4b4, "Beige Gray", "223"), + EmbThreadJef(0xe3ac81, "Bamboo", "224") + ] + + +class EmbThreadJef(EmbThread): + def __init__(self, color, description, catalog_number): + EmbThread.__init__(self) + self.color = color + self.description = description + self.catalog_number = catalog_number + self.brand = "Jef" + self.chart = "Jef" diff --git a/pyembroidery/EmbThreadPec.py b/pyembroidery/EmbThreadPec.py new file mode 100644 index 0000000..abb51e8 --- /dev/null +++ b/pyembroidery/EmbThreadPec.py @@ -0,0 +1,81 @@ +from .EmbThread import EmbThread + + +def get_thread_set(): + return [ + EmbThreadPec(0, 0, 0, "Unknown", "0"), + EmbThreadPec(14, 31, 124, "Prussian Blue", "1"), + EmbThreadPec(10, 85, 163, "Blue", "2"), + EmbThreadPec(0, 135, 119, "Teal Green", "3"), + EmbThreadPec(75, 107, 175, "Cornflower Blue", "4"), + EmbThreadPec(237, 23, 31, "Red", "5"), + EmbThreadPec(209, 92, 0, "Reddish Brown", "6"), + EmbThreadPec(145, 54, 151, "Magenta", "7"), + EmbThreadPec(228, 154, 203, "Light Lilac", "8"), + EmbThreadPec(145, 95, 172, "Lilac", "9"), + EmbThreadPec(158, 214, 125, "Mint Green", "10"), + EmbThreadPec(232, 169, 0, "Deep Gold", "11"), + EmbThreadPec(254, 186, 53, "Orange", "12"), + EmbThreadPec(255, 255, 0, "Yellow", "13"), + EmbThreadPec(112, 188, 31, "Lime Green", "14"), + EmbThreadPec(186, 152, 0, "Brass", "15"), + EmbThreadPec(168, 168, 168, "Silver", "16"), + EmbThreadPec(125, 111, 0, "Russet Brown", "17"), + EmbThreadPec(255, 255, 179, "Cream Brown", "18"), + EmbThreadPec(79, 85, 86, "Pewter", "19"), + EmbThreadPec(0, 0, 0, "Black", "20"), + EmbThreadPec(11, 61, 145, "Ultramarine", "21"), + EmbThreadPec(119, 1, 118, "Royal Purple", "22"), + EmbThreadPec(41, 49, 51, "Dark Gray", "23"), + EmbThreadPec(42, 19, 1, "Dark Brown", "24"), + EmbThreadPec(246, 74, 138, "Deep Rose", "25"), + EmbThreadPec(178, 118, 36, "Light Brown", "26"), + EmbThreadPec(252, 187, 197, "Salmon Pink", "27"), + EmbThreadPec(254, 55, 15, "Vermillion", "28"), + EmbThreadPec(240, 240, 240, "White", "29"), + EmbThreadPec(106, 28, 138, "Violet", "30"), + EmbThreadPec(168, 221, 196, "Seacrest", "31"), + EmbThreadPec(37, 132, 187, "Sky Blue", "32"), + EmbThreadPec(254, 179, 67, "Pumpkin", "33"), + EmbThreadPec(255, 243, 107, "Cream Yellow", "34"), + EmbThreadPec(208, 166, 96, "Khaki", "35"), + EmbThreadPec(209, 84, 0, "Clay Brown", "36"), + EmbThreadPec(102, 186, 73, "Leaf Green", "37"), + EmbThreadPec(19, 74, 70, "Peacock Blue", "38"), + EmbThreadPec(135, 135, 135, "Gray", "39"), + EmbThreadPec(216, 204, 198, "Warm Gray", "40"), + EmbThreadPec(67, 86, 7, "Dark Olive", "41"), + EmbThreadPec(253, 217, 222, "Flesh Pink", "42"), + EmbThreadPec(249, 147, 188, "Pink", "43"), + EmbThreadPec(0, 56, 34, "Deep Green", "44"), + EmbThreadPec(178, 175, 212, "Lavender", "45"), + EmbThreadPec(104, 106, 176, "Wisteria Violet", "46"), + EmbThreadPec(239, 227, 185, "Beige", "47"), + EmbThreadPec(247, 56, 102, "Carmine", "48"), + EmbThreadPec(181, 75, 100, "Amber Red", "49"), + EmbThreadPec(19, 43, 26, "Olive Green", "50"), + EmbThreadPec(199, 1, 86, "Dark Fuschia", "51"), + EmbThreadPec(254, 158, 50, "Tangerine", "52"), + EmbThreadPec(168, 222, 235, "Light Blue", "53"), + EmbThreadPec(0, 103, 62, "Emerald Green", "54"), + EmbThreadPec(78, 41, 144, "Purple", "55"), + EmbThreadPec(47, 126, 32, "Moss Green", "56"), + EmbThreadPec(255, 204, 204, "Flesh Pink", "57"), + EmbThreadPec(255, 217, 17, "Harvest Gold", "58"), + EmbThreadPec(9, 91, 166, "Electric Blue", "59"), + EmbThreadPec(240, 249, 112, "Lemon Yellow", "60"), + EmbThreadPec(227, 243, 91, "Fresh Green", "61"), + EmbThreadPec(255, 153, 0, "Orange", "62"), + EmbThreadPec(255, 240, 141, "Cream Yellow", "63"), + EmbThreadPec(255, 200, 200, "Applique", "64") + ] + + +class EmbThreadPec(EmbThread): + def __init__(self, red, green, blue, description, catalog_number): + EmbThread.__init__(self) + self.set_color(red, green, blue) + self.description = description + self.catalog_number = catalog_number + self.brand = "Brother" + self.chart = "Brother" diff --git a/pyembroidery/EmbThreadSew.py b/pyembroidery/EmbThreadSew.py new file mode 100644 index 0000000..f54ec81 --- /dev/null +++ b/pyembroidery/EmbThreadSew.py @@ -0,0 +1,95 @@ +from .EmbThread import EmbThread + + +def get_thread_set(): + return [ + EmbThreadSew(0, 0, 0, "Unknown", "0"), + EmbThreadSew(0, 0, 0, "Black", "1"), + EmbThreadSew(255, 255, 255, "White", "2"), + EmbThreadSew(255, 255, 23, "Sunflower", "3"), + EmbThreadSew(250, 160, 96, "Hazel", "4"), + EmbThreadSew(92, 118, 73, "Green Dust", "5"), + EmbThreadSew(64, 192, 48, "Green", "6"), + EmbThreadSew(101, 194, 200, "Sky", "7"), + EmbThreadSew(172, 128, 190, "Purple", "8"), + EmbThreadSew(245, 188, 203, "Pink", "9"), + EmbThreadSew(255, 0, 0, "Red", "10"), + EmbThreadSew(192, 128, 0, "Brown", "11"), + EmbThreadSew(0, 0, 240, "Blue", "12"), + EmbThreadSew(228, 195, 93, "Gold", "13"), + EmbThreadSew(165, 42, 42, "Dark Brown", "14"), + EmbThreadSew(213, 176, 212, "Pale Violet", "15"), + EmbThreadSew(252, 242, 148, "Pale Yellow", "16"), + EmbThreadSew(240, 208, 192, "Pale Pink", "17"), + EmbThreadSew(255, 192, 0, "Peach", "18"), + EmbThreadSew(201, 164, 128, "Beige", "19"), + EmbThreadSew(155, 61, 75, "Wine Red", "20"), + EmbThreadSew(160, 184, 204, "Pale Sky", "21"), + EmbThreadSew(127, 194, 28, "Yellow Green", "22"), + EmbThreadSew(185, 185, 185, "Silver Grey", "23"), + EmbThreadSew(160, 160, 160, "Grey", "24"), + EmbThreadSew(152, 214, 189, "Pale Aqua", "25"), + EmbThreadSew(184, 240, 240, "Baby Blue", "26"), + EmbThreadSew(54, 139, 160, "Powder Blue", "27"), + EmbThreadSew(79, 131, 171, "Bright Blue", "28"), + EmbThreadSew(56, 106, 145, "Slate Blue", "29"), + EmbThreadSew(0, 32, 107, "Nave Blue", "30"), + EmbThreadSew(229, 197, 202, "Salmon Pink", "31"), + EmbThreadSew(249, 103, 107, "Coral", "32"), + EmbThreadSew(227, 49, 31, "Burnt Orange", "33"), + EmbThreadSew(226, 161, 136, "Cinnamon", "34"), + EmbThreadSew(181, 148, 116, "Umber", "35"), + EmbThreadSew(228, 207, 153, "Blonde", "36"), + EmbThreadSew(225, 203, 0, "Sunflower", "37"), + EmbThreadSew(225, 173, 212, "Orchid Pink", "38"), + EmbThreadSew(195, 0, 126, "Peony Purple", "39"), + EmbThreadSew(128, 0, 75, "Burgundy", "40"), + EmbThreadSew(160, 96, 176, "Royal Purple", "41"), + EmbThreadSew(192, 64, 32, "Cardinal Red", "42"), + EmbThreadSew(202, 224, 192, "Opal Green", "43"), + EmbThreadSew(137, 152, 86, "Moss Green", "44"), + EmbThreadSew(0, 170, 0, "Meadow Green", "45"), + EmbThreadSew(33, 138, 33, "Dark Green", "46"), + EmbThreadSew(93, 174, 148, "Aquamarine", "47"), + EmbThreadSew(76, 191, 143, "Emerald Green", "48"), + EmbThreadSew(0, 119, 114, "Peacock Green", "49"), + EmbThreadSew(112, 112, 112, "Dark Grey", "50"), + EmbThreadSew(242, 255, 255, "Ivory White", "51"), + EmbThreadSew(177, 88, 24, "Hazel", "52"), + EmbThreadSew(203, 138, 7, "Toast", "53"), + EmbThreadSew(247, 146, 123, "Salmon", "54"), + EmbThreadSew(152, 105, 45, "Cocoa Brown", "55"), + EmbThreadSew(162, 113, 72, "Sienna", "56"), + EmbThreadSew(123, 85, 74, "Sepia", "57"), + EmbThreadSew(79, 57, 70, "Dark Sepia", "58"), + EmbThreadSew(82, 58, 151, "Violet Blue", "59"), + EmbThreadSew(0, 0, 160, "Blue Ink", "60"), + EmbThreadSew(0, 150, 222, "Solar Blue", "61"), + EmbThreadSew(178, 221, 83, "Green Dust", "62"), + EmbThreadSew(250, 143, 187, "Crimson", "63"), + EmbThreadSew(222, 100, 158, "Floral Pink", "64"), + EmbThreadSew(181, 80, 102, "Wine", "65"), + EmbThreadSew(94, 87, 71, "Olive Drab", "66"), + EmbThreadSew(76, 136, 31, "Meadow", "67"), + EmbThreadSew(228, 220, 121, "Canary Yellow", "68"), + EmbThreadSew(203, 138, 26, "Toast", "69"), + EmbThreadSew(198, 170, 66, "Beige", "70"), + EmbThreadSew(236, 176, 44, "Honey Dew", "71"), + EmbThreadSew(248, 128, 64, "Tangerine", "72"), + EmbThreadSew(255, 229, 5, "Ocean Blue", "73"), + EmbThreadSew(250, 122, 122, "Sepia", "74"), + EmbThreadSew(107, 224, 0, "Royal Purple", "75"), + EmbThreadSew(56, 108, 174, "Yellow Ocher", "76"), + EmbThreadSew(208, 186, 176, "Beige Grey", "77"), + EmbThreadSew(227, 190, 129, "Bamboo", "78"), + ] + + +class EmbThreadSew(EmbThread): + def __init__(self, red, green, blue, description, catalog_number): + EmbThread.__init__(self) + self.set_color(red, green, blue) + self.description = description + self.catalog_number = catalog_number + self.brand = "Sew" + self.chart = "Sew" diff --git a/pyembroidery/EmbThreadShv.py b/pyembroidery/EmbThreadShv.py new file mode 100644 index 0000000..e17211b --- /dev/null +++ b/pyembroidery/EmbThreadShv.py @@ -0,0 +1,59 @@ +from .EmbThread import EmbThread + + +def get_thread_set(): + return [ + EmbThreadShv(0, 0, 0, "Black", "0"), + EmbThreadShv(0, 0, 255, "Blue", "1"), + EmbThreadShv(51, 204, 102, "Green", "2"), + EmbThreadShv(255, 0, 0, "Red", "3"), + EmbThreadShv(255, 0, 255, "Purple", "4"), + EmbThreadShv(255, 255, 0, "Yellow", "5"), + EmbThreadShv(127, 127, 127, "Gray", "6"), + EmbThreadShv(51, 154, 255, "Light Blue", "7"), + EmbThreadShv(0, 255, 0, "Light Green", "8"), + EmbThreadShv(255, 127, 0, "Orange", "9"), + EmbThreadShv(255, 160, 180, "Pink", "10"), + EmbThreadShv(153, 75, 0, "Brown", "11"), + EmbThreadShv(255, 255, 255, "White", "12"), + EmbThreadShv(0, 0, 0, "Black", "13"), + EmbThreadShv(0, 0, 0, "Black", "14"), + EmbThreadShv(0, 0, 0, "Black", "15"), + EmbThreadShv(0, 0, 0, "Black", "16"), + EmbThreadShv(0, 0, 0, "Black", "17"), + EmbThreadShv(0, 0, 0, "Black", "18"), + EmbThreadShv(255, 127, 127, "Light Red", "19"), + EmbThreadShv(255, 127, 255, "Light Purple", "20"), + EmbThreadShv(255, 255, 153, "Light Yellow", "21"), + EmbThreadShv(192, 192, 192, "Light Gray", "22"), + EmbThreadShv(0, 0, 0, "Black", "23"), + EmbThreadShv(0, 0, 0, "Black", "24"), + EmbThreadShv(255, 165, 65, "Light Orange", "25"), + EmbThreadShv(255, 204, 204, "Light Pink", "26"), + EmbThreadShv(175, 90, 10, "Light Brown", "27"), + EmbThreadShv(0, 0, 0, "Black", "28"), + EmbThreadShv(0, 0, 0, "Black", "29"), + EmbThreadShv(0, 0, 0, "Black", "30"), + EmbThreadShv(0, 0, 0, "Black", "31"), + EmbThreadShv(0, 0, 0, "Black", "32"), + EmbThreadShv(0, 0, 127, "Dark Blue", "33"), + EmbThreadShv(0, 127, 0, "Dark Green", "34"), + EmbThreadShv(127, 0, 0, "Dark Red", "35"), + EmbThreadShv(127, 0, 127, "Dark Purple", "36"), + EmbThreadShv(200, 200, 0, "Dark Yellow", "37"), + EmbThreadShv(60, 60, 60, "Dark Gray", "38"), + EmbThreadShv(0, 0, 0, "Black", "39"), + EmbThreadShv(0, 0, 0, "Black", "40"), + EmbThreadShv(232, 63, 0, "Dark Orange", "41"), + EmbThreadShv(255, 102, 122, "Dark Pink", "42") + ] + + +class EmbThreadShv(EmbThread): + def __init__(self, red, green, blue, description, catalog_number): + EmbThread.__init__(self) + self.set_color(red, green, blue) + self.description = description + self.catalog_number = catalog_number + self.brand = "Shv" + self.chart = "Shv" diff --git a/pyembroidery/EmdReader.py b/pyembroidery/EmdReader.py new file mode 100644 index 0000000..d8f2f75 --- /dev/null +++ b/pyembroidery/EmdReader.py @@ -0,0 +1,45 @@ +from .ReadHelper import signed8 + + +def read_emd_stitches(f, out): + count = 0 + while True: + count += 1 + b = bytearray(f.read(2)) + if len(b) != 2: + break + + if b[0] != 0x80: + x = signed8(b[0]) + y = -signed8(b[1]) + out.stitch(x, y) + continue + control = b[1] + if control == 0x80: + b = bytearray(f.read(2)) + if len(b) != 2: + break + x = signed8(b[0]) + y = -signed8(b[1]) + out.move(x, y) + continue + if control == 0x2A: + out.color_change() + continue + if control == 0x7D: + continue # Dunno, occurs at position 0. + if control == 0xAD: + out.trim() + continue + if control == 0x90: + out.trim() # Final command before returning to start. + continue + elif control == 0xFD: + break + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + f.seek(0x30, 0) + read_emd_stitches(f, out) diff --git a/pyembroidery/ExpReader.py b/pyembroidery/ExpReader.py new file mode 100644 index 0000000..f6f53ca --- /dev/null +++ b/pyembroidery/ExpReader.py @@ -0,0 +1,40 @@ +from .ReadHelper import signed8 + + +def read_exp_stitches(f, out): + while True: + b = bytearray(f.read(2)) + if len(b) != 2: + break + if b[0] != 0x80: + x = signed8(b[0]) + y = -signed8(b[1]) + out.stitch(x, y) + continue + + control = b[1] + b = bytearray(f.read(2)) # 07 00 + if len(b) != 2: + break + x = signed8(b[0]) + y = -signed8(b[1]) + if control == 0x80: # Trim + out.trim() + continue + elif control == 0x02: + out.stitch(x, y) + # This shouldn't exist. + continue + elif control == 0x04: # Jump + out.move(x, y) + continue + elif control == 0x01: # Colorchange + out.color_change() + out.move(x, y) + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + read_exp_stitches(f, out) diff --git a/pyembroidery/ExpWriter.py b/pyembroidery/ExpWriter.py new file mode 100644 index 0000000..5992c4c --- /dev/null +++ b/pyembroidery/ExpWriter.py @@ -0,0 +1,41 @@ +from .EmbConstant import * + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_JUMP +FULL_JUMP = True +MAX_JUMP_DISTANCE = 127 +MAX_STITCH_DISTANCE = 127 + + +def write(pattern, f, settings=None): + stitches = pattern.stitches + xx = 0 + yy = 0 + for stitch in stitches: + x = stitch[0] + y = stitch[1] + data = stitch[2] + dx = int(round(x - xx)) + dy = int(round(y - yy)) + xx += dx + yy += dy + if data is STITCH: + # consider bounds checking the delta_x, delta_y and raising ValueError if exceeds. + delta_x = dx & 0xFF + delta_y = -dy & 0xFF + f.write(bytes(bytearray([delta_x, delta_y]))) + elif data == JUMP: + delta_x = dx & 0xFF + delta_y = -dy & 0xFF + f.write(b'\x80\x04') + f.write(bytes(bytearray([delta_x, delta_y]))) + elif data == TRIM: + f.write(b'\x80\x80\x07\x00') + continue + elif data == COLOR_CHANGE: + f.write(b'\x80\x01\x00\x00') + continue + elif data == STOP: + f.write(b'\x80\x01\x00\x00') + continue + elif data == END: + pass diff --git a/pyembroidery/ExyReader.py b/pyembroidery/ExyReader.py new file mode 100644 index 0000000..9a4645d --- /dev/null +++ b/pyembroidery/ExyReader.py @@ -0,0 +1,6 @@ +from .DstReader import dst_read_stitches + + +def read(f, out, settings=None): + f.seek(0x100) + dst_read_stitches(f, out) diff --git a/pyembroidery/FxyReader.py b/pyembroidery/FxyReader.py new file mode 100644 index 0000000..d3239a0 --- /dev/null +++ b/pyembroidery/FxyReader.py @@ -0,0 +1,6 @@ +from .DszReader import z_stitch_encoding_read + + +def read(f, out, settings=None): + f.seek(0x100) + z_stitch_encoding_read(f, out) diff --git a/pyembroidery/GtReader.py b/pyembroidery/GtReader.py new file mode 100644 index 0000000..3162eed --- /dev/null +++ b/pyembroidery/GtReader.py @@ -0,0 +1,6 @@ +from .DszReader import z_stitch_encoding_read + + +def read(f, out, settings=None): + f.seek(0x200) + z_stitch_encoding_read(f, out) diff --git a/pyembroidery/InbReader.py b/pyembroidery/InbReader.py new file mode 100644 index 0000000..769201a --- /dev/null +++ b/pyembroidery/InbReader.py @@ -0,0 +1,34 @@ + + +def read_inb_stitches(f, out): + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + x = byte[0] + y = -byte[1] + ctrl = byte[2] + if ctrl & 0x20 != 0: + y = -y + if ctrl & 0x40 != 0: + x = -x + if (ctrl & 0b1111) == 0x00: + out.stitch(x, y) + continue + if (ctrl & 0b1111) == 0x01: + out.color_change(x, y) + continue + if (ctrl & 0b1111) == 0x02: + out.move(x, y) + continue + if ctrl == 0x04: + break + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + f.seek(0x2000, 0) + read_inb_stitches(f, out) diff --git a/pyembroidery/JefReader.py b/pyembroidery/JefReader.py new file mode 100644 index 0000000..188770c --- /dev/null +++ b/pyembroidery/JefReader.py @@ -0,0 +1,47 @@ +from .EmbThreadJef import get_thread_set +from .ReadHelper import read_int_32le, signed8 + + +def read_jef_stitches(f, out): + count = 0 + while True: + count += 1 + b = bytearray(f.read(2)) + if len(b) != 2: + break + if b[0] != 0x80: + x = signed8(b[0]) + y = -signed8(b[1]) + out.stitch(x, y) + continue + ctrl = b[1] + b = bytearray(f.read(2)) + if len(b) != 2: + break + x = signed8(b[0]) + y = -signed8(b[1]) + if ctrl == 0x02: + out.move(x, y) + continue + if ctrl == 0x01: + out.color_change(0, 0) + continue + if ctrl == 0x10: + break + break # Uncaught Control + out.end(0, 0) + + +def read(f, out, settings=None): + jef_threads = get_thread_set() + stitch_offset = read_int_32le(f) + f.seek(20, 1) + count_colors = read_int_32le(f) + f.seek(88, 1) + + for i in range(0, count_colors): + index = abs(read_int_32le(f)) + out.add_thread(jef_threads[index % len(jef_threads)]) + + f.seek(stitch_offset, 0) + read_jef_stitches(f, out) diff --git a/pyembroidery/JefWriter.py b/pyembroidery/JefWriter.py new file mode 100644 index 0000000..6166ac6 --- /dev/null +++ b/pyembroidery/JefWriter.py @@ -0,0 +1,134 @@ +from .EmbConstant import * +from .EmbThreadJef import get_thread_set +from .WriteHelper import write_string_utf8, write_int_32le, write_int_8 +import datetime + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_JUMP +FULL_JUMP = True +MAX_JUMP_DISTANCE = 127 +MAX_STITCH_DISTANCE = 127 + +# These are in mm, embroidery units are 1/10 mm +HOOP_110X110 = 0 +HOOP_50X50 = 1 +HOOP_140X200 = 2 +HOOP_126X110 = 3 +HOOP_200X200 = 4 + + +def write(pattern, f, settings=None): + pattern.fix_color_count() + color_count = pattern.count_threads() + offsets = 0x74 + (color_count * 8) + write_int_32le(f, offsets) + write_int_32le(f, 0x14) + date_string = datetime.datetime.today().strftime('%Y%m%d%H%M%S') + # write_string_utf8(f, "20122017218088") + write_string_utf8(f, date_string) + write_int_8(f, 0) + write_int_8(f, 0) + write_int_32le(f, color_count) + point_count = 1 # 1 command for END statement + for stitch in pattern.stitches: + data = stitch[2] + if data == STITCH: + point_count += 1 + elif data == JUMP: + point_count += 2 + elif data == COLOR_CHANGE: + point_count += 2 + elif data == END: + break + write_int_32le(f, point_count) + extends = pattern.extends() + design_width = int(round(extends[2] - extends[0])) + design_height = int(round(extends[3] - extends[1])) + write_int_32le(f, get_jef_hoop_size(design_width, design_height)) + half_width = int(round(design_width / 2)) + half_height = int(round(design_height / 2)) + + # distance from center of hoop. + write_int_32le(f, half_width) + write_int_32le(f, half_height) + write_int_32le(f, half_width) + write_int_32le(f, half_height) + + # distance from default 110 x 110 hoop + x_hoop_edge = 550 - half_width + y_hoop_edge = 550 - half_height + write_hoop_edge_distance(f, x_hoop_edge, y_hoop_edge) + + # distance from default 50 x 50 hoop + x_hoop_edge = 250 - half_width + y_hoop_edge = 250 - half_height + write_hoop_edge_distance(f, x_hoop_edge, y_hoop_edge) + + # distance from default 140 x 200 hoop + x_hoop_edge = 700 - half_width + y_hoop_edge = 1000 - half_height + write_hoop_edge_distance(f, x_hoop_edge, y_hoop_edge) + + # distance from custom hoop, but this should be accepted. + x_hoop_edge = 700 - half_width + y_hoop_edge = 1000 - half_height + write_hoop_edge_distance(f, x_hoop_edge, y_hoop_edge) + + jef_threads = get_thread_set() + for thread in pattern.threadlist: + thread_index = thread.find_nearest_color_index(jef_threads) + write_int_32le(f, thread_index) + for i in range(0, color_count): + write_int_32le(f, 0x0D) + + xx = 0 + yy = 0 + for stitch in pattern.stitches: + x = stitch[0] + y = stitch[1] + data = stitch[2] + dx = int(round(x - xx)) + dy = int(round(y - yy)) + xx += dx + yy += dy + if data == STITCH: + write_int_8(f, dx) + write_int_8(f, -dy) + continue + elif data == COLOR_CHANGE: + f.write(b'\x80\x01') + write_int_8(f, dx) + write_int_8(f, -dy) + continue + elif data == JUMP: + f.write(b'\x80\x02') + write_int_8(f, dx) + write_int_8(f, -dy) + continue + elif data == END: + break + f.write(b'\x80\x10') + + +def get_jef_hoop_size(width, height): + if width < 500 and height < 500: + return HOOP_50X50 + if width < 1260 and height < 1100: + return HOOP_126X110 + if width < 1400 and height < 2000: + return HOOP_140X200 + if width < 2000 and height < 2000: + return HOOP_200X200 + return HOOP_110X110 + + +def write_hoop_edge_distance(f, x_hoop_edge, y_hoop_edge): + if min(x_hoop_edge, y_hoop_edge) >= 0: + write_int_32le(f, x_hoop_edge) # left + write_int_32le(f, y_hoop_edge) # top + write_int_32le(f, x_hoop_edge) # right + write_int_32le(f, y_hoop_edge) # bottom + else: + write_int_32le(f, -1) + write_int_32le(f, -1) + write_int_32le(f, -1) + write_int_32le(f, -1) diff --git a/pyembroidery/JpxReader.py b/pyembroidery/JpxReader.py new file mode 100644 index 0000000..156ffa1 --- /dev/null +++ b/pyembroidery/JpxReader.py @@ -0,0 +1,48 @@ +from .ReadHelper import signed8, read_int_32le + + +def read_jpx_stitches(f, out): + while True: + b = bytearray(f.read(2)) + if len(b) != 2: + break + if b[0] != 0x80: + x = signed8(b[0]) + y = -signed8(b[1]) + out.stitch(x, y) + continue + ctrl = b[1] + b = bytearray(f.read(2)) + if len(b) != 2: + break + x = signed8(b[0]) + y = -signed8(b[1]) + if ctrl == 0x02: + out.move(x, y) + continue + if ctrl == 0x01: # Colorchange + out.color_change() + if x != 0 and y != 0: + out.move(x, y) + continue + if ctrl == 0x10: + break + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + stitch_start_position = read_int_32le(f) + f.seek(0x1C, 1) + colors = read_int_32le(f) + f.seek(0x18, 1) + for i in range(0, colors): + color_index = read_int_32le(f) + if color_index is None: + break + out.add_thread({ + "color": "random", + "name": "JPX index " + str(color_index) + }) + f.seek(stitch_start_position, 0) + read_jpx_stitches(f, out) diff --git a/pyembroidery/KsmReader.py b/pyembroidery/KsmReader.py new file mode 100644 index 0000000..10e0b25 --- /dev/null +++ b/pyembroidery/KsmReader.py @@ -0,0 +1,59 @@ +def read_ksm_stitches(f, out): + trimmed = False + stitched_yet = False + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + + y = -byte[0] + x = byte[1] + ctrl = byte[2] + + if ctrl & 0x40 != 0: + x = -x + if ctrl & 0x20 != 0: + y = -y + + ctrl &= 0b11111 + if x != 0 or y != 0: + if trimmed: # any x, y gets executed regardless. + out.move(x, y) + else: + out.stitch(x, y) + stitched_yet = True + + if ctrl == 0x00: + continue + # print(str(f), " ", str(count), " ", str("{0:b}").format(ctrl), " 0x%0.2X " % ctrl, x, " ", y) + + if ctrl == 0x07 or ctrl == 0x13 or ctrl == 0x1D: + if stitched_yet: + out.trim() + trimmed = True + continue + if 0x17 <= ctrl <= 0x19: # start sewing again. + trimmed = False + continue + if 0x0B <= ctrl <= 0x12: + needle = ctrl - 0x0A + out.color_change() + trimmed = True + continue + if ctrl == 0x05: + out.stop() + continue + if ctrl == 0x1B: # Called before end command + trimmed = False + continue + if ctrl == 0x08: # End command #88 zero direction. + break + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + f.seek(0x200, 0) + read_ksm_stitches(f, out) diff --git a/pyembroidery/MaxReader.py b/pyembroidery/MaxReader.py new file mode 100644 index 0000000..bd47ddf --- /dev/null +++ b/pyembroidery/MaxReader.py @@ -0,0 +1,17 @@ +from .ReadHelper import read_int_8, read_int_32le, read_int_24le, signed24 + + +def read(f, out, settings=None): + f.seek(0xD5, 0) + stitch_count = read_int_32le(f) + for i in range(0, stitch_count): + x = read_int_24le(f) + c0 = read_int_8(f) + y = read_int_24le(f) + c1 = read_int_8(f) + if c1 is None: + break + x = signed24(x) + y = signed24(y) + out.stitch_abs(x, y) + out.end() diff --git a/pyembroidery/MitReader.py b/pyembroidery/MitReader.py new file mode 100644 index 0000000..e43cde9 --- /dev/null +++ b/pyembroidery/MitReader.py @@ -0,0 +1,34 @@ +def read(f, out, settings=None): + count = 0 + previous_ctrl = -1 + while True: + count += 1 + byte = bytearray(f.read(2)) + if len(byte) != 2: + break + x = byte[0] & 0x1F + y = - (byte[1] & 0x1F) + if byte[0] & 0b10000000: + x = -x + if byte[1] & 0b10000000: + y = -y + ctrl = ((byte[0] & 0x60) >> 3) | ((byte[1] & 0x60) >> 5) + if ctrl == 0b0111: + out.stitch(x, y) + previous_ctrl = ctrl + continue + elif ctrl == 0b1100: + out.move(x, y) + elif ctrl == 0b0100: + out.stitch(x, y) + elif ctrl == 0b0101: + out.stitch(x, y) + elif ctrl == 0b1000: + if previous_ctrl == 0b111: + out.color_change() + elif ctrl == 0b0000: + out.end() # 0 appears at end. + else: + out.stitch(x, y) + previous_ctrl = ctrl + out.end() diff --git a/pyembroidery/NewReader.py b/pyembroidery/NewReader.py new file mode 100644 index 0000000..22e20a3 --- /dev/null +++ b/pyembroidery/NewReader.py @@ -0,0 +1,34 @@ +def new_stitch_encoding_read(f, out): + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + + x = byte[0] + y = -byte[1] + ctrl = byte[2] + + if ctrl & 0b01000000 != 0: + x = -x + if ctrl & 0b00100000 != 0: + y = -y + ctrl &= ~0b11100000 + if ctrl == 0: + out.stitch(x, y) + continue + if ctrl == 0b00010001: + break + if ctrl & 0b00000010 != 0: + out.color_change() + continue + if ctrl & 0b00000001 != 0: + out.move(x, y) + continue + out.end() + + +def read(f, out, settings=None): + f.seek(2, 1) # stitchcount. + new_stitch_encoding_read(f, out) diff --git a/pyembroidery/PcdReader.py b/pyembroidery/PcdReader.py new file mode 100644 index 0000000..6335437 --- /dev/null +++ b/pyembroidery/PcdReader.py @@ -0,0 +1,48 @@ +from .ReadHelper import read_int_8, read_int_24be, read_int_24le, read_int_16le, signed24 +from .EmbThread import EmbThread + +PC_SIZE_CONVERSION_RATIO = 5.0 / 3.0 + + +def read_pc_file(f, out, settings=None): + version = read_int_8(f) + hoop_size = read_int_8(f) + # 0 for PCD, + # 1 for PCQ (MAXI), + # 2 for PCS small hoop(80x80), + # 3 for PCS with large hoop. + color_count = read_int_16le(f) + for i in range(0, color_count): + thread = EmbThread() + thread.color = read_int_24be(f) + out.add_thread(thread) + f.seek(1, 1) + + stitch_count = read_int_16le(f) + while True: + c0 = read_int_8(f) + x = read_int_24le(f) + c1 = read_int_8(f) + y = read_int_24le(f) + ctrl = read_int_8(f) + if ctrl is None: + break + x = signed24(x) + y = -signed24(y) + x *= PC_SIZE_CONVERSION_RATIO + y *= PC_SIZE_CONVERSION_RATIO + if ctrl == 0x00: + out.stitch_abs(x, y) + continue + if ctrl & 0x01: + out.color_change() + continue + if ctrl & 0x04: + out.move_abs(x, y) + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + read_pc_file(f, out) diff --git a/pyembroidery/PcmReader.py b/pyembroidery/PcmReader.py new file mode 100644 index 0000000..538e3b1 --- /dev/null +++ b/pyembroidery/PcmReader.py @@ -0,0 +1,63 @@ +from .ReadHelper import read_int_8, read_int_24be, signed24, read_int_16be + +PC_SIZE_CONVERSION_RATIO = 5.0 / 3.0 + + +def read_pc_file(f, out, settings=None): + pcm_threads = [ + {"color": 0x000000, "description": "PCM Color 1"}, + {"color": 0x000080, "description": "PCM Color 2"}, + {"color": 0x0000FF, "description": "PCM Color 3"}, + {"color": 0x008080, "description": "PCM Color 4"}, + {"color": 0x00FFFF, "description": "PCM Color 5"}, + {"color": 0x800080, "description": "PCM Color 6"}, + {"color": 0xFF00FF, "description": "PCM Color 7"}, + {"color": 0x800000, "description": "PCM Color 8"}, + {"color": 0xFF0000, "description": "PCM Color 9"}, + {"color": 0x008000, "description": "PCM Color 10"}, + {"color": 0x00FF00, "description": "PCM Color 11"}, + {"color": 0x808000, "description": "PCM Color 12"}, + {"color": 0xFFFF00, "description": "PCM Color 13"}, + {"color": 0x808080, "description": "PCM Color 14"}, + {"color": 0xC0C0C0, "description": "PCM Color 15"}, + {"color": 0xFFFFFF, "description": "PCM Color 16"}, + ] + + f.seek(2, 0) + + colors = read_int_16be(f) + if colors is None: + return # File is blank. + for i in range(0, colors): + color_index = read_int_16be(f) + thread = pcm_threads[color_index] + out.add_thread(thread) + + stitch_count = read_int_16be(f) + while True: + x = read_int_24be(f) + c0 = read_int_8(f) + y = read_int_24be(f) + c1 = read_int_8(f) + ctrl = read_int_8(f) + if ctrl is None: + break + x = signed24(x) + y = -signed24(y) + x *= PC_SIZE_CONVERSION_RATIO + y *= PC_SIZE_CONVERSION_RATIO + if ctrl == 0x00: + out.stitch_abs(x, y) + continue + if ctrl & 0x01: + out.color_change() + continue + if ctrl & 0x04: + out.move_abs(x, y) + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + read_pc_file(f, out) diff --git a/pyembroidery/PcqReader.py b/pyembroidery/PcqReader.py new file mode 100644 index 0000000..6335437 --- /dev/null +++ b/pyembroidery/PcqReader.py @@ -0,0 +1,48 @@ +from .ReadHelper import read_int_8, read_int_24be, read_int_24le, read_int_16le, signed24 +from .EmbThread import EmbThread + +PC_SIZE_CONVERSION_RATIO = 5.0 / 3.0 + + +def read_pc_file(f, out, settings=None): + version = read_int_8(f) + hoop_size = read_int_8(f) + # 0 for PCD, + # 1 for PCQ (MAXI), + # 2 for PCS small hoop(80x80), + # 3 for PCS with large hoop. + color_count = read_int_16le(f) + for i in range(0, color_count): + thread = EmbThread() + thread.color = read_int_24be(f) + out.add_thread(thread) + f.seek(1, 1) + + stitch_count = read_int_16le(f) + while True: + c0 = read_int_8(f) + x = read_int_24le(f) + c1 = read_int_8(f) + y = read_int_24le(f) + ctrl = read_int_8(f) + if ctrl is None: + break + x = signed24(x) + y = -signed24(y) + x *= PC_SIZE_CONVERSION_RATIO + y *= PC_SIZE_CONVERSION_RATIO + if ctrl == 0x00: + out.stitch_abs(x, y) + continue + if ctrl & 0x01: + out.color_change() + continue + if ctrl & 0x04: + out.move_abs(x, y) + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + read_pc_file(f, out) diff --git a/pyembroidery/PcsReader.py b/pyembroidery/PcsReader.py new file mode 100644 index 0000000..6335437 --- /dev/null +++ b/pyembroidery/PcsReader.py @@ -0,0 +1,48 @@ +from .ReadHelper import read_int_8, read_int_24be, read_int_24le, read_int_16le, signed24 +from .EmbThread import EmbThread + +PC_SIZE_CONVERSION_RATIO = 5.0 / 3.0 + + +def read_pc_file(f, out, settings=None): + version = read_int_8(f) + hoop_size = read_int_8(f) + # 0 for PCD, + # 1 for PCQ (MAXI), + # 2 for PCS small hoop(80x80), + # 3 for PCS with large hoop. + color_count = read_int_16le(f) + for i in range(0, color_count): + thread = EmbThread() + thread.color = read_int_24be(f) + out.add_thread(thread) + f.seek(1, 1) + + stitch_count = read_int_16le(f) + while True: + c0 = read_int_8(f) + x = read_int_24le(f) + c1 = read_int_8(f) + y = read_int_24le(f) + ctrl = read_int_8(f) + if ctrl is None: + break + x = signed24(x) + y = -signed24(y) + x *= PC_SIZE_CONVERSION_RATIO + y *= PC_SIZE_CONVERSION_RATIO + if ctrl == 0x00: + out.stitch_abs(x, y) + continue + if ctrl & 0x01: + out.color_change() + continue + if ctrl & 0x04: + out.move_abs(x, y) + continue + break # Uncaught Control + out.end() + + +def read(f, out, settings=None): + read_pc_file(f, out) diff --git a/pyembroidery/PecGraphics.py b/pyembroidery/PecGraphics.py new file mode 100644 index 0000000..3d84d03 --- /dev/null +++ b/pyembroidery/PecGraphics.py @@ -0,0 +1,164 @@ +from math import floor + +blank = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x10, + 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +] + + +def get_blank(): + return [m for m in blank] + + +def create(width, height): + width /= 8 + return [0x00] * width * height + + +def draw(points, graphic, stride=6): + for point in points: + try: + try: + graphic_mark_bit(graphic, + int(point.x), + int(point.y), + stride) + except AttributeError: + graphic_mark_bit(graphic, + int(point[0]), + int(point[1]), + stride) + except IndexError: + pass + + +def draw_scaled(extends, points, graphic, stride, buffer=5): + if extends is None: + draw(points, graphic, stride) + return + try: + left = extends.left + top = extends.top + right = extends.right + bottom = extends.bottom + except AttributeError: + left = extends[0] + top = extends[1] + right = extends[2] + bottom = extends[3] + + diagram_width = right - left + diagram_height = bottom - top + + graphic_width = stride * 8 + graphic_height = len(graphic) / stride + + if diagram_width == 0: + diagram_width = 1 + if diagram_height == 0: + diagram_height = 1 + + scale_x = (graphic_width - buffer) / diagram_width + scale_y = (graphic_height - buffer) / diagram_height + + scale = min(scale_x, scale_y) + + cx = (right + left) / 2 + cy = (bottom + top) / 2 + + translate_x = -cx + translate_y = -cy + + translate_x *= scale + translate_y *= scale + + translate_x += graphic_width / 2 + translate_y += graphic_height / 2 + + for point in points: + try: + try: + graphic_mark_bit(graphic, + int(floor((point.x * scale) + translate_x)), + int(floor((point.y * scale) + translate_y)), + stride) + except AttributeError: + graphic_mark_bit(graphic, + int(floor((point[0] * scale) + translate_x)), + int(floor((point[1] * scale) + translate_y)), + stride) + except IndexError: + pass + + +def clear(graphic): + for b in graphic: + b = 0 + + +def graphic_mark_bit(graphic, x, y, stride=6): + """expressly sets the bit in the give graphic object""" + graphic[(y * stride) + int(x / 8)] |= 1 << (x % 8) + + +def graphic_unmark_bit(graphic, x, y, stride=6): + """expressly unsets the bit in the give graphic object""" + graphic[(y * stride) + int(x / 8)] &= ~(1 << (x % 8)) + + +def get_graphic_as_string(graphic, one="#", zero=" "): + """Prints graphic object in text.""" + stride = 6 + if isinstance(graphic, tuple): + stride = graphic[1] + graphic = graphic[0] + + if isinstance(graphic, str): + graphic = bytearray(graphic) + + list_string = [ + one if (byte >> i) & 1 else zero + for byte in graphic + for i in range(0, 8) + ] + bit_stride = 8 * stride + bit_length = 8 * len(graphic) + return '\n'.join( + ''.join(list_string[m:m + bit_stride]) + for m in range(0, bit_length, bit_stride)) diff --git a/pyembroidery/PecReader.py b/pyembroidery/PecReader.py new file mode 100644 index 0000000..1cec658 --- /dev/null +++ b/pyembroidery/PecReader.py @@ -0,0 +1,157 @@ +from .EmbThreadPec import get_thread_set +from .ReadHelper import read_string_8, read_int_8, read_int_24le + +JUMP_CODE = 0x10 +TRIM_CODE = 0x20 +FLAG_LONG = 0x80 + + +def read(f, out, settings=None): + pec_string = read_string_8(f, 8) + # pec_string must equal #PEC0001 + read_pec(f, out) + + +def read_pec(f, out, pes_chart=None): + f.seek(3, 1) # LA: + label = read_string_8(f, 16) # Label + if label is not None: + out.metadata("Label", label.strip()) + f.seek(0xF, 1) # Dunno, spaces then 0xFF 0x00 + pec_graphic_byte_stride = read_int_8(f) + pec_graphic_icon_height = read_int_8(f) + f.seek(0xC, 1) + color_changes = read_int_8(f) + count_colors = color_changes + 1 # PEC uses cc - 1, 0xFF means 0. + color_bytes = bytearray(f.read(count_colors)) + threads = [] + map_pec_colors(color_bytes, out, pes_chart, threads) + f.seek(0x1D0 - color_changes, 1) + stitch_block_end = read_int_24le(f) - 5 + f.tell() + # The end of this value is already 5 into the stitchblock. + + # 3 bytes, '\x31\xff\xf0', 6 2-byte shorts. 15 total. + f.seek(0x0F, 1) + read_pec_stitches(f, out) + f.seek(stitch_block_end, 0) + + byte_size = pec_graphic_byte_stride * pec_graphic_icon_height + + read_pec_graphics(f, + out, + byte_size, + pec_graphic_byte_stride, + count_colors + 1, + threads + ) + + +def read_pec_graphics(f, out, size, stride, count, values): + values.insert(0, None) + for i in range(0, count): + graphic = bytearray(f.read(size)) + if f is not None: + out.metadata(i, (graphic, stride, values[i])) + + +def process_pec_colors(colorbytes, out, values): + thread_set = get_thread_set() + max_value = len(thread_set) + for byte in colorbytes: + thread_value = thread_set[byte % max_value] + out.add_thread(thread_value) + values.append(thread_value) + + +def process_pec_table(colorbytes, out, chart, values): + # This is how PEC actually allocates pre-defined threads to blocks. + thread_set = get_thread_set() + max_value = len(thread_set) + thread_map = {} + for i in range(0, len(colorbytes)): + color_index = int(colorbytes[i] % max_value) + thread_value = thread_map.get(color_index, None) + if thread_value is None: + if len(chart) > 0: + thread_value = chart.pop(0) + else: + thread_value = thread_set[color_index] + thread_map[color_index] = thread_value + out.add_thread(thread_value) + values.append(thread_value) + + +def map_pec_colors(colorbytes, out, chart, values): + if chart is None or len(chart) == 0: + # Reading pec colors. + process_pec_colors(colorbytes, out, values) + + elif len(chart) >= len(colorbytes): + # Reading threads in 1 : 1 mode. + for thread in chart: + out.add_thread(thread) + values.append(thread) + else: + # Reading tabled mode threads. + process_pec_table(colorbytes, out, chart, values) + + +def signed12(b): + b &= 0xFFF + if b > 0x7FF: + return - 0x1000 + b + else: + return b + + +def signed7(b): + if b > 63: + return - 128 + b + else: + return b + + +def read_pec_stitches(f, out): + while True: + val1 = read_int_8(f) + val2 = read_int_8(f) + if (val1 == 0xFF and val2 == 0x00) or val2 is None: + break + if val1 == 0xFE and val2 == 0xB0: + f.seek(1, 1) + out.color_change(0, 0) + continue + jump = False + trim = False + if val1 & FLAG_LONG != 0: + if val1 & TRIM_CODE != 0: + trim = True + if val1 & JUMP_CODE != 0: + jump = True + code = (val1 << 8) | val2 + x = signed12(code) + val2 = read_int_8(f) + if val2 is None: + break + else: + x = signed7(val1) + + if val2 & FLAG_LONG != 0: + if val2 & TRIM_CODE != 0: + trim = True + if val2 & JUMP_CODE != 0: + jump = True + val3 = read_int_8(f) + if val3 is None: + break + code = val2 << 8 | val3 + y = signed12(code) + else: + y = signed7(val2) + if jump: + out.move(x, y) + elif trim: + out.trim(x, y) + else: + out.stitch(x, y) + out.end() diff --git a/pyembroidery/PecWriter.py b/pyembroidery/PecWriter.py new file mode 100644 index 0000000..62c0153 --- /dev/null +++ b/pyembroidery/PecWriter.py @@ -0,0 +1,195 @@ +from .EmbConstant import * +from .EmbThreadPec import get_thread_set +from .PecGraphics import get_blank, draw_scaled +from .WriteHelper import write_int_8, write_int_16le, write_int_16be, \ + write_int_24le, write_string_utf8 + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_JUMP +FULL_JUMP = True +MAX_JUMP_DISTANCE = 2047 +MAX_STITCH_DISTANCE = 2047 + +MASK_07_BIT = 0b01111111 +JUMP_CODE = 0b00010000 +TRIM_CODE = 0b00100000 +FLAG_LONG = 0b10000000 +PEC_ICON_WIDTH = 48 +PEC_ICON_HEIGHT = 38 + + +def write(pattern, f, settings=None): + f.write(bytes("#PEC0001".encode('utf8'))) + write_pec(pattern, f) + + +def write_pec(pattern, f, threadlist=None): + if threadlist is None: + pattern.fix_color_count() + threadlist = pattern.threadlist + extends = pattern.extends() + + write_pec_header(pattern, f, threadlist) + write_pec_block(pattern, f, extends) + write_pec_graphics(pattern, f, extends) + + +def write_pec_header(pattern, f, threadlist): + name = pattern.get_metadata("name", "Untitled") + write_string_utf8(f, "LA:%-16s\r" % name[:8]) + f.write(b'\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\xFF\x00') + write_int_8(f, int(PEC_ICON_WIDTH / 8)) # PEC BYTE STRIDE + write_int_8(f, int(PEC_ICON_HEIGHT)) # PEC ICON HEIGHT + + thread_set = get_thread_set() + + if len(thread_set) <= len(threadlist): + threadlist = thread_set[:] + # Data is corrupt. Cheat so it won't crash. + + chart = [None] * len(thread_set) + for thread in set(threadlist): + index = thread.find_nearest_color_index(thread_set) + thread_set[index] = None + chart[index] = thread + + color_index_list = [] + for thread in threadlist: + color_index_list.append(thread.find_nearest_color_index(chart)) + + current_thread_count = len(color_index_list) + if current_thread_count is not 0: + f.write(b'\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20') + add_value = current_thread_count - 1 + color_index_list.insert(0, add_value) + f.write(bytes(bytearray(color_index_list))) + else: + f.write(b'\x20\x20\x20\x20\x64\x20\x00\x20\x00\x20\x20\x20\xFF') + + for i in range(current_thread_count, 463): + f.write(b'\x20') # 520 + + +def write_pec_block(pattern, f, extends): + width = extends[2] - extends[0] + height = extends[3] - extends[1] + + stitch_block_start_position = f.tell() + f.write(b'\x00\x00') + write_int_24le(f, 0) # Space holder. + f.write(b'\x31\xff\xf0') + write_int_16le(f, int(round(width))) + write_int_16le(f, int(round(height))) + write_int_16le(f, 0x1E0) + write_int_16le(f, 0x1B0) + + write_int_16be(f, 0x9000 | -int(round(extends[0]))) + write_int_16be(f, 0x9000 | -int(round(extends[1]))) + + pec_encode(pattern, f) + + stitch_block_length = f.tell() - stitch_block_start_position + + current_position = f.tell() + f.seek(stitch_block_start_position + 2, 0) + write_int_24le(f, stitch_block_length) + f.seek(current_position, 0) + + +def write_pec_graphics(pattern, f, extends): + blank = get_blank() + for block in pattern.get_as_stitchblock(): + stitches = block[0] + draw_scaled(extends, stitches, blank, 6, 4) + f.write(bytes(bytearray(blank))) + + for block in pattern.get_as_colorblocks(): + stitches = [s for s in block[0] if s[2] == STITCH] + blank = get_blank() # [ 0 ] * 6 * 38 + draw_scaled(extends, stitches, blank, 6) + f.write(bytes(bytearray(blank))) + + +def encode_long_form(value): + value &= 0b0000111111111111 + value |= 0b1000000000000000 + return value + + +def flag_jump(longForm): + return longForm | (JUMP_CODE << 8) + + +def flag_trim(longForm): + return longForm | (TRIM_CODE << 8) + + +def pec_encode(pattern, f): + color_change_jump = False + color_two = True + jumping = False + stitches = pattern.stitches + xx = 0 + yy = 0 + for stitch in stitches: + x = stitch[0] + y = stitch[1] + data = stitch[2] + dx = int(round(x - xx)) + dy = int(round(y - yy)) + xx += dx + yy += dy + if data is STITCH: + if jumping and dx is not 0 and dy is not 0: + f.write(b'\x00\x00') + jumping = False + if -64 < dx < 63 and -64 < dy < 63: + f.write(bytes(bytearray([dx & MASK_07_BIT, dy & MASK_07_BIT]))) + else: + dx = encode_long_form(dx) + dy = encode_long_form(dy) + data = [ + (dx >> 8) & 0xFF, + dx & 0xFF, + (dy >> 8) & 0xFF, + dy & 0xFF] + f.write(bytes(bytearray(data))) + elif data == JUMP: + jumping = True + dx = encode_long_form(dx) + if color_change_jump: + dx = flag_jump(dx) + else: + dx = flag_trim(dx) + dy = encode_long_form(dy) + if color_change_jump: + dy = flag_jump(dy) + else: + dy = flag_trim(dy) + f.write(bytes(bytearray([ + (dx >> 8) & 0xFF, + dx & 0xFF, + (dy >> 8) & 0xFF, + dy & 0xFF + ]))) + color_change_jump = False + elif data == COLOR_CHANGE: + if jumping: + f.write(b'\x00\x00') + jumping = False + f.write(b'\xfe\xb0') + if color_two: + f.write(b'\x02') + else: + f.write(b'\x01') + color_two = not color_two + elif data == STOP: + # if jumping: + # f.write(b'\x00\x00') + # jumping = False + # f.write(b'\x80\x01\x00\x00') + pass + elif data == END: + if jumping: + f.write(b'\x00\x00') + jumping = False + f.write(b'\xff') diff --git a/pyembroidery/PesReader.py b/pyembroidery/PesReader.py new file mode 100644 index 0000000..adf1146 --- /dev/null +++ b/pyembroidery/PesReader.py @@ -0,0 +1,128 @@ +from .PecReader import read_pec +from .EmbThread import EmbThread +from .ReadHelper import read_string_8, read_int_8, read_int_32le, read_int_24be, read_int_16le + + +def read(f, out, settings=None): + loaded_thread_values = [] + pes_string = read_string_8(f, 8) + + if pes_string == "#PEC0001": + read_pec(f, out, loaded_thread_values) + return + + pec_block_position = read_int_32le(f) + + # Ignoring several known PES versions, just abort and read PEC block + # All versions allow, abort and read PEC block. + # Metadata started appearing in V4 + # Threads appeared in V5. + # We quickly abort if there's any complex items in the header. + # "#PES0100", "#PES0090" "#PES0080" "#PES0070", "#PES0040", + # "#PES0030", "#PES0022", "#PES0020" + if pes_string == "#PES0060": + read_pes_header_version_6(f, out, loaded_thread_values) + elif pes_string == "#PES0050": + read_pes_header_version_5(f, out, loaded_thread_values) + elif pes_string == "#PES0055": + read_pes_header_version_5(f, out, loaded_thread_values) + elif pes_string == "#PES0056": + read_pes_header_version_5(f, out, loaded_thread_values) + elif pes_string == "#PES0040": + read_pes_header_version_4(f, out) + elif pes_string == "#PES0001": + read_pes_header_version_1(f, out) + else: + pass # Header is unrecognised. + f.seek(pec_block_position, 0) + read_pec(f, out, loaded_thread_values) + + +def read_pes_string(f): + length = read_int_8(f) + if length == 0: + return None + return read_string_8(f, length) + + +def read_pes_metadata(f, out): + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("name", v) + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("category", v) + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("author", v) + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("keywords", v) + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("comments", v) + + +def read_pes_thread(f, threadlist): + thread = EmbThread() + thread.catalog_number = read_pes_string(f) + thread.color = 0xFF000000 | read_int_24be(f) + f.seek(5, 1) + thread.description = read_pes_string(f) + thread.brand = read_pes_string(f) + thread.chart = read_pes_string(f) + threadlist.append(thread) + + +def read_pes_header_version_1(f, out): + # Nothing I care about. + pass + + +def read_pes_header_version_4(f, out): + f.seek(4, 1) + read_pes_metadata(f, out) + + +def read_pes_header_version_5(f, out, threadlist): + f.seek(4, 1) + read_pes_metadata(f, out) + f.seek(24, 1) # this is 36 in version 6 and 24 in version 5 + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("image", v) + f.seek(24, 1) + count_programmable_fills = read_int_16le(f) + if count_programmable_fills != 0: + return + count_motifs = read_int_16le(f) + if count_motifs != 0: + return + count_feather_patterns = read_int_16le(f) + if count_feather_patterns != 0: + return + count_threads = read_int_16le(f) + for i in range(0, count_threads): + read_pes_thread(f, threadlist) + + +def read_pes_header_version_6(f, out, threadlist): + f.seek(4, 1) + read_pes_metadata(f, out) + f.seek(36, 1) # this is 36 in version 6 and 24 in version 5 + v = read_pes_string(f) + if v is not None and len(v) > 0: + out.metadata("image_file", v) + f.seek(24, 1) + count_programmable_fills = read_int_16le(f) + if count_programmable_fills != 0: + return + count_motifs = read_int_16le(f) + if count_motifs != 0: + return + count_feather_patterns = read_int_16le(f) + if count_feather_patterns != 0: + return + count_threads = read_int_16le(f) + for i in range(0, count_threads): + read_pes_thread(f, threadlist) diff --git a/pyembroidery/PesWriter.py b/pyembroidery/PesWriter.py new file mode 100644 index 0000000..f89e1c2 --- /dev/null +++ b/pyembroidery/PesWriter.py @@ -0,0 +1,350 @@ +from .PecWriter import write_pec +from .EmbThreadPec import get_thread_set +from .WriteHelper import write_string_utf8, write_int_32le, write_int_16le, write_int_8, write_float_32le +from .EmbConstant import * + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_JUMP +FULL_JUMP = True +MAX_JUMP_DISTANCE = 2047 +MAX_STITCH_DISTANCE = 2047 + +VERSION_1 = 1 +VERSION_6 = 6 + +PES_VERSION_1_SIGNATURE = "#PES0001" +PES_VERSION_6_SIGNATURE = "#PES0060" + +EMB_ONE = "CEmbOne" +EMB_SEG = "CSewSeg" + + +def write(pattern, f, settings=None): + if settings is not None: + version = settings.get("pes version", VERSION_6) + truncated = settings.get("truncated", False) + else: + version = VERSION_6 + truncated = False + if truncated: + if version == VERSION_1: + write_truncated_version_1(pattern, f) + elif version == VERSION_6: + write_truncated_version_6(pattern, f) + else: + if version == VERSION_1: + write_version_1(pattern, f) + elif version == VERSION_6: + write_version_6(pattern, f) + + +def write_truncated_version_1(pattern, f): + write_string_utf8(f, PES_VERSION_1_SIGNATURE) + f.write(b'\x16\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + write_pec(pattern, f) + + +def write_truncated_version_6(pattern, f): + chart = pattern.threadlist + write_string_utf8(f, PES_VERSION_6_SIGNATURE) + placeholder_pec_block = f.tell() + write_int_32le(f, 0) # Placeholder for PEC BLOCK + write_pes_header_v6(pattern, f, chart, 0) + write_int_16le(f, 0x0000) + write_int_16le(f, 0x0000) + current_position = f.tell() + f.seek(placeholder_pec_block, 0) + write_int_32le(f, current_position) + f.seek(current_position, 0) + # this might need that node table thing. + write_pec(pattern, f) + + +def write_version_1(pattern, f): + chart = get_thread_set() + write_string_utf8(f, PES_VERSION_1_SIGNATURE) + + extends = pattern.extends() + cx = (extends[2] + extends[0]) / 2.0 + cy = (extends[3] + extends[1]) / 2.0 + + left = extends[0] - cx + top = extends[1] - cy + right = extends[2] - cx + bottom = extends[3] - cy + + placeholder_pec_block = f.tell() + write_int_32le(f, 0) # Placeholder for PEC BLOCK + + if len(pattern.stitches) == 0: + write_pes_header_v1(f, 0) + write_int_16le(f, 0x0000) + write_int_16le(f, 0x0000) + else: + write_pes_header_v1(f, 1) + write_int_16le(f, 0xFFFF) + write_int_16le(f, 0x0000) + write_pes_blocks(f, pattern, chart, left, top, right, bottom, cx, cy) + + current_position = f.tell() + f.seek(placeholder_pec_block, 0) + write_int_32le(f, current_position) + f.seek(current_position, 0) + + write_pec(pattern, f) + + +def write_version_6(pattern, f): + pattern.fix_color_count() + chart = pattern.threadlist + write_string_utf8(f, PES_VERSION_6_SIGNATURE) + + extends = pattern.extends() + cx = (extends[2] + extends[0]) / 2.0 + cy = (extends[3] + extends[1]) / 2.0 + + left = extends[0] - cx + top = extends[1] - cy + right = extends[2] - cx + bottom = extends[3] - cy + + placeholder_pec_block = f.tell() + write_int_32le(f, 0) # Placeholder for PEC BLOCK + + if len(pattern.stitches) == 0: + write_pes_header_v6(pattern, f, chart, 0) + write_int_16le(f, 0x0000) + write_int_16le(f, 0x0000) + else: + write_pes_header_v6(pattern, f, chart, 1) + write_int_16le(f, 0xFFFF) + write_int_16le(f, 0x0000) + log = write_pes_blocks(f, pattern, chart, left, top, right, bottom, cx, cy) + # In version 6 there is some node, tree, order thing. + write_int_32le(f, 0) + write_int_32le(f, 0) + for i in range(0, len(log)): + write_int_32le(f, i) + write_int_32le(f, 0) + + current_position = f.tell() + f.seek(placeholder_pec_block, 0) + write_int_32le(f, current_position) + f.seek(current_position, 0) + write_pec(pattern, f) + + +def write_pes_header_v1(f, distinct_block_objects): + write_int_16le(f, 0x01) # scale to fit + write_int_16le(f, 0x01) # 0 = 100x100, 130x180 hoop + write_int_16le(f, distinct_block_objects) + + +def write_pes_header_v6(pattern, f, chart, distinct_block_objects): + write_int_16le(f, 0x01) # 0 = 100x100, 130x180 hoop + f.write(b'02') # This is an 2-digit ascii number. + write_pes_string_8(f, pattern.get_metadata("name", None)) + write_pes_string_8(f, pattern.get_metadata("category", None)) + write_pes_string_8(f, pattern.get_metadata("author", None)) + write_pes_string_8(f, pattern.get_metadata("keywords", None)) + write_pes_string_8(f, pattern.get_metadata("comments", None)) + write_int_16le(f, 0) # OptimizeHoopChange = False + write_int_16le(f, 0) # Design Page Is Custom = False + write_int_16le(f, 0x64) # Hoop Width + write_int_16le(f, 0x64) # Hoop Height + write_int_16le(f, 0) # Use Existing Design Area = False + write_int_16le(f, 0xC8) # designWidth + write_int_16le(f, 0xC8) # designHeight + write_int_16le(f, 0x64) # designPageSectionWidth + write_int_16le(f, 0x64) # designPageSectionHeight + write_int_16le(f, 0x64) # p6 # 100 + write_int_16le(f, 0x07) # designPageBackgroundColor + write_int_16le(f, 0x13) # designPageForegroundColor + write_int_16le(f, 0x01) # ShowGrid + write_int_16le(f, 0x01) # WithAxes + write_int_16le(f, 0x00) # SnapToGrid + write_int_16le(f, 100) # GridInterval + write_int_16le(f, 0x01) # p9 curves? + write_int_16le(f, 0x00) # OptimizeEntryExitPoints + write_int_8(f, 0) # fromImageStringLength + # String FromImageFilename + write_float_32le(f, float(1)) + write_float_32le(f, float(0)) + write_float_32le(f, float(0)) + write_float_32le(f, float(1)) + write_float_32le(f, float(0)) + write_float_32le(f, float(0)) + write_int_16le(f, 0) # numberOfProgrammableFillPatterns + write_int_16le(f, 0) # numberOfMotifPatterns + write_int_16le(f, 0) # featherPatternCount + count_thread = len(chart) + write_int_16le(f, count_thread) # numberOfColors + for thread in chart: + write_pes_thread(f, thread) + write_int_16le(f, distinct_block_objects) # number ofdistinct blocks + + +def write_pes_string_8(f, string): + if string is None: + write_int_8(f, 0) + return + if len(string) > 255: + string = string[:255] + write_int_8(f, len(string)) + write_string_utf8(f, string) + + +def write_pes_string_16(f, string): + if string is None: + write_int_16le(f, 0) + return + write_int_16le(f, len(string)) + # 16 refers to the size write not the string encoding. + write_string_utf8(f, string) + + +def write_pes_thread(f, thread): + write_pes_string_8(f, thread.catalog_number) + 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, 0) # unknown + write_int_32le(f, 0xA) # A is custom color + write_pes_string_8(f, thread.description) + write_pes_string_8(f, thread.brand) + write_pes_string_8(f, thread.chart) + + +def write_pes_blocks(f, pattern, chart, left, top, right, bottom, cx, cy): + if len(pattern.stitches) == 0: + return + + write_pes_string_16(f, EMB_ONE) + placeholder = write_pes_sewsegheader(f, left, top, right, bottom) + write_int_16le(f, 0xFFFF) + write_int_16le(f, 0x0000) # FFFF0000 means more blocks exist + + write_pes_string_16(f, EMB_SEG) + data = write_pes_embsewseg_segments(f, pattern, chart, left, bottom, cx, cy) + + sections = data[0] + colorlog = data[1] + + current_position = f.tell() + f.seek(placeholder, 0) + write_int_16le(f, sections) + f.seek(current_position, 0) # patch final section count. + + # If there were addition embsewsegheaders or segments they would go here. + + write_int_16le(f, 0x0000) + write_int_16le(f, 0x0000) # 00000000 means no more blocks. + + return colorlog + + +def write_pes_sewsegheader(f, left, top, right, bottom): + width = right - left + height = bottom - top + hoop_height = 1800 + hoop_width = 1300 + write_int_16le(f, 0) # left + write_int_16le(f, 0) # top + write_int_16le(f, 0) # right + write_int_16le(f, 0) # bottom + write_int_16le(f, 0) # left + write_int_16le(f, 0) # top + write_int_16le(f, 0) # right + write_int_16le(f, 0) # bottom + trans_x = 0 + trans_y = 0 + trans_x += float(350) + trans_y += float(100) + height + trans_x += hoop_width / 2 + trans_y += hoop_height / 2 + trans_x += -width / 2 + trans_y += -height / 2 + write_float_32le(f, float(1)) + write_float_32le(f, float(0)) + write_float_32le(f, float(0)) + write_float_32le(f, float(1)) + write_float_32le(f, float(trans_x)) + write_float_32le(f, float(trans_y)) + + write_int_16le(f, 1) + write_int_16le(f, 0) + write_int_16le(f, 0) + write_int_16le(f, int(width)) + write_int_16le(f, int(height)) + f.write(b'\x00\x00\x00\x00\x00\x00\x00\x00') + + placeholder_needs_section_data = f.tell() + # sections + write_int_16le(f, 0) + return placeholder_needs_section_data + + +def get_as_segments_blocks(pattern, chart, adjust_x, adjust_y): + color_index = 0 + current_thread = pattern.get_thread_or_filler(color_index) + color_index += 1 + color_code = current_thread.find_nearest_color_index(chart) + stitched_x = 0 + stitched_y = 0 + for command_block in pattern.get_as_command_blocks(): + block = [] + command = command_block[0][2] + if command == JUMP: + block.append([stitched_x - adjust_x, stitched_y - adjust_y]) + last_pos = command_block[-1] + block.append([last_pos[0] - adjust_x, last_pos[1] - adjust_y]) + flag = 1 + elif command == COLOR_CHANGE: + current_thread = pattern.get_thread_or_filler(color_index) + color_index += 1 + color_code = current_thread.find_nearest_color_index(chart) + flag = 1 + continue + elif command == STITCH: + for stitch in command_block: + stitched_x = stitch[0] + stitched_y = stitch[1] + block.append([stitched_x - adjust_x, stitched_y - adjust_y]) + flag = 0 + else: + continue + yield (block, color_code, flag) + + +def write_pes_embsewseg_segments(f, pattern, chart, left, bottom, cx, cy): + section = 0 + colorlog = [] + + previous_color_code = -1 + flag = -1 + adjust_x = left + cx + adjust_y = bottom + cy + for segs in get_as_segments_blocks(pattern, chart, adjust_x, adjust_y): + if flag != -1: + write_int_16le(f, 0x8003) # section end. + segments = segs[0] + color_code = segs[1] + flag = segs[2] + + if previous_color_code != color_code: + colorlog.append([section, color_code]) + previous_color_code = color_code + # This must trigger first segment. + write_int_16le(f, flag) + write_int_16le(f, color_code) + write_int_16le(f, len(segments)) + for segs in segments: + write_int_16le(f, int(segs[0])) + write_int_16le(f, int(segs[1])) + section += 1 + + write_int_16le(f, len(colorlog)) + for log_item in colorlog: + write_int_16le(f, log_item[0]) + write_int_16le(f, log_item[1]) + + return section, colorlog # how many sections, how color transitions. diff --git a/pyembroidery/PhbReader.py b/pyembroidery/PhbReader.py new file mode 100644 index 0000000..b7e7970 --- /dev/null +++ b/pyembroidery/PhbReader.py @@ -0,0 +1,30 @@ +from .PecReader import read_pec_stitches +from .EmbThreadPec import get_thread_set +from .ReadHelper import read_int_8, read_int_32le, read_int_16le + + +def read(f, out, settings=None): + # should start #PHB0003 + f.seek(0x71, 0) + color_count = read_int_16le(f) + threadset = get_thread_set() + for i in range(0, color_count): + out.add_thread(threadset[read_int_8(f) % len(threadset)]) + + file_offset = 0x52 + + f.seek(0x54, 0) + file_offset += read_int_32le(f) + + f.seek(file_offset, 0) + file_offset += read_int_32le(f) + 2 + + f.seek(file_offset, 0) + file_offset += read_int_32le(f) + + f.seek(file_offset + 14, 0) + + color_count2 = read_int_8(f) + f.seek(color_count2 + 23, 1) + + read_pec_stitches(f, out) diff --git a/pyembroidery/PhcReader.py b/pyembroidery/PhcReader.py new file mode 100644 index 0000000..2d90632 --- /dev/null +++ b/pyembroidery/PhcReader.py @@ -0,0 +1,37 @@ +from .PecReader import read_pec_stitches, read_pec_graphics +from .EmbThreadPec import get_thread_set +from .ReadHelper import read_int_8, read_int_32le, read_int_16le + + +def read(f, out, settings=None): + f.seek(0x4A, 0) + pec_graphic_icon_height = read_int_8(f) + f.seek(1,1) + pec_graphic_byte_stride = read_int_8(f) + color_count = read_int_16le(f) + threadset = get_thread_set() + for i in range(0, color_count): + color_index = read_int_8(f) + if color_index is None: + return # File terminated before expected end. + out.add_thread(threadset[color_index % len(threadset)]) + byte_size = pec_graphic_byte_stride * pec_graphic_icon_height + read_pec_graphics(f, + out, + byte_size, + pec_graphic_byte_stride, + color_count, + out.threadlist + ) + f.seek(0x2B, 0) + pec_add = read_int_8(f) + f.seek(4, 1) + pec_offset = read_int_16le(f) + f.seek(pec_offset + pec_add, 0) + bytes_in_section = read_int_16le(f) + f.seek(bytes_in_section, 1) + bytes_in_section2 = read_int_32le(f) + f.seek(bytes_in_section2, 1) + bytes_in_section3 = read_int_16le(f) + f.seek(bytes_in_section3 + 0x12, 1) + read_pec_stitches(f, out) diff --git a/pyembroidery/PmvReader.py b/pyembroidery/PmvReader.py new file mode 100644 index 0000000..139fb7d --- /dev/null +++ b/pyembroidery/PmvReader.py @@ -0,0 +1,105 @@ +from .ReadHelper import read_int_8, read_int_16le + + +def find_extends(stitches): + min_x = float('inf') + min_y = float('inf') + max_x = -float('inf') + max_y = -float('inf') + + for stitch in stitches: + if stitch[0] > max_x: + max_x = stitch[0] + if stitch[0] < min_x: + min_x = stitch[0] + if stitch[1] > max_y: + max_y = stitch[1] + if stitch[1] < min_y: + min_y = stitch[1] + return min_x, min_y, max_x, max_y + + +def read_pmv_stitches(f, out, settings=None): + """PMV files are stitch files, not embroidery.""" + px = 0 + # stitches = [] + while True: + stitch_count = read_int_16le(f) + block_length = read_int_16le(f) + if block_length is None: + return + if block_length >= 256: + break + if stitch_count == 0: + continue + for i in range(0, stitch_count): + x = read_int_8(f) + y = read_int_8(f) + if y > 16: + y = -(32 - y) # This is 5 bit signed number. + if x > 32: + x = -(64 - x) # This is a 6 bit signed number. + x *= 2.5 + y *= 2.5 + dx = x + out.stitch_abs(px + x, y) # This is a hybrid relative, absolute value. + px += dx + # stitches.append((x, y)) + out.end() + # f.seek(0x10, 1) # 16 bytes + # block_end = read_int_16le(f) + # if block_end != 256: + # return + # steps = [] + # dunno0 = read_int_8(f) + # dunno1 = read_int_8(f) + # length_steps = read_int_8(f) + # steps_size = read_int_8(f) + # for i in range(0, steps_size): + # x = read_int_16le(f) + # y = read_int_16le(f) + # if x is None or y is None: + # break + # steps.append((x, y)) + # width_units = read_int_8(f) + # steps2_size = read_int_8(f) + # steps2 = [] + # for i in range(0, steps2_size): + # x = read_int_16le(f) + # y = read_int_16le(f) + # if x is None or y is None: + # break + # steps2.append((x, y)) + # dunno4 = read_int_16le(f) # seems to be 0x12. + # f.seek(0x10, 1) # 16 bytes + # # EOF - This should be End of File. + # none_bytes = read_int_8(f) + # if none_bytes is None: + # pass + # + # extends = find_extends(stitches) + # print(f) + # print("Stitches: Total ", len(stitches), " : ", stitches) + # print("Unknown0:", dunno0) + # print("Unknown1:", dunno1) + # print("Length Position:", length_steps) + # print("Length Lookup: ", len(steps), " : ", steps) + # print("Length value:", steps[length_steps]) + # length_max = extends[2] - extends[0] + # width_max = extends[3] - extends[1] + # print("Max Length:", length_max) + # print("Max dx+:", extends[2]) + # print("Max dx-:", extends[0]) + # print("Width Position:", width_units) + # print("Width Lookup:", len(steps2), " : ", steps2) + # print("Width value:", steps2[width_units]) + # print("Max Width:", width_max) + # + # print("Unknown4:", dunno4) + + out.end() + + +def read(f, out, settings=None): + f.seek(0x64, 0) + read_pmv_stitches(f, out) diff --git a/pyembroidery/PmvWriter.py b/pyembroidery/PmvWriter.py new file mode 100644 index 0000000..1280e8d --- /dev/null +++ b/pyembroidery/PmvWriter.py @@ -0,0 +1,132 @@ +from .EmbConstant import * +from .WriteHelper import write_string_utf8, write_int_8, write_int_16le + +MAX_STITCH_DISTANCE = 70 +MAX_PERMITTED_STITCHES = 100 + + +def write(pattern, f, settings=None): + max_x = -200000000 + min_x = +200000000 + max_y = -200000000 + min_y = +200000000 + point_count = 0 + for stitch in pattern.stitches: + data = stitch[2] + x = stitch[0] + y = stitch[1] + if data == STITCH or data == JUMP: + point_count += 1 + if x > max_x: + max_x = x + if x < min_x: + min_x = x + if y > max_y: + max_y = y + if y < min_y: + min_y = y + if point_count >= MAX_PERMITTED_STITCHES: + break + center_y = (min_y + max_y) / 2.0 + normal_max_y = max_y - center_y + if normal_max_y > 35.0: # 14 * 2.5 = 350 + scale_y = 14.0 / normal_max_y # 1 / (normal_max_y / 14.0) + else: + scale_y = 1.0 / 2.5 # pure unit conversion. + scale_x = 1.0 / 2.5 + + write_string_utf8(f, "#PMV0001") + header = "...................................." + write_string_utf8(f, header[0:36]) + f.write(b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00') + + write_int_16le(f, point_count) + write_int_16le(f, point_count * 2) + point_index = -1 + max_x = -200000000 + min_x = +200000000 + max_y = -200000000 + min_y = +200000000 + xx = 0 + for stitch in pattern.stitches: + point_index += 1 + if point_index >= point_count: + break + data = stitch[2] + x = stitch[0] + y = stitch[1] + x *= scale_x + y -= center_y + y *= scale_y + y = int(round(y)) + x = int(round(x - xx)) + xx += x + if data == STITCH or data == JUMP: + if x > max_x: + max_x = x + if x < min_x: + min_x = x + if y > max_y: + max_y = y + if y < min_y: + min_y = y + + if x < 0: + x += 64 + if y < 0: + y += 32 + write_int_8(f, x) + write_int_8(f, y) + continue + write_int_16le(f, 0) + write_int_16le(f, 256) + f.write(b'\x00\x00\x00\x00\x05\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x02\x00') + write_int_16le(f, 256) + write_int_8(f, 0) + write_int_8(f, 0) + length_range = max_x - min_x + write_length_lookup_table(f, length_range) + width_range = max_y - min_y + write_width_lookup_table(f, width_range) + write_int_16le(f, 0x12) + f.write(b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00') + + +def write_length_lookup_table(f, length_range): + # I've not solved this for how they are actually made, writing a something that should work. + write_values = [(0, 0), (10, 71), (20, 143), (40, 214), (60, 286), (80, 357), + (100, 429), (120, 500), (140, 571), (160, 714), (180, 786), (200, 857), + (250, 1000), (300, 1286), (350, 1429), (400, 1571), (450, 1786), (500, 2000)] + write_int_8(f, 12) + steps = len(write_values) + write_int_8(f, steps) + for value in write_values: + length_at_step = value[0] + other_at_step = value[1] + write_int_16le(f, length_at_step) + write_int_16le(f, other_at_step) + + +def write_width_lookup_table(f, width_range): + pos = int(width_range / 2) + write_int_8(f, pos) + if width_range == 0: + write_int_8(f, 1) + write_int_16le(f, 8192) + write_int_16le(f, 1000) + return + steps = 15 + write_int_8(f, steps) + second_max = other_step = 28000.0 / float(width_range) + second_step = second_max / float(steps - 1) + for i in range(0, steps): + width_at_step = 50 * i + other_at_step = second_step * i + + write_int_16le(f, width_at_step) + write_int_16le(f, int(round(other_at_step))) diff --git a/pyembroidery/PyEmbroidery.py b/pyembroidery/PyEmbroidery.py new file mode 100644 index 0000000..02c3f19 --- /dev/null +++ b/pyembroidery/PyEmbroidery.py @@ -0,0 +1,553 @@ +import os.path + +from .EmbPattern import EmbPattern +import pyembroidery.DstWriter as DstWriter +import pyembroidery.PecWriter as PecWriter +import pyembroidery.PesWriter as PesWriter +import pyembroidery.ExpWriter as ExpWriter +import pyembroidery.Vp3Writer as Vp3Writer +import pyembroidery.JefWriter as JefWriter +import pyembroidery.SvgWriter as SvgWriter +import pyembroidery.CsvWriter as CsvWriter +import pyembroidery.U01Writer as U01Writer + +import pyembroidery.DstReader as DstReader +import pyembroidery.PecReader as PecReader +import pyembroidery.PesReader as PesReader +import pyembroidery.ExpReader as ExpReader +import pyembroidery.Vp3Reader as Vp3Reader +import pyembroidery.JefReader as JefReader +import pyembroidery.XxxReader as XxxReader +import pyembroidery.SewReader as SewReader +import pyembroidery.U01Reader as U01Reader +import pyembroidery.ShvReader as ShvReader +import pyembroidery.A10oReader as A10oReader +import pyembroidery.A100Reader as A100Reader +import pyembroidery.BroReader as BroReader +import pyembroidery.DsbReader as DsbReader +import pyembroidery.DszReader as DszReader +import pyembroidery.EmdReader as EmdReader +import pyembroidery.InbReader as InbReader +import pyembroidery.TbfReader as TbfReader +import pyembroidery.KsmReader as KsmReader +import pyembroidery.TapReader as TapReader +import pyembroidery.StxReader as StxReader +import pyembroidery.PhbReader as PhbReader +import pyembroidery.PcdReader as PcdReader +import pyembroidery.PcmReader as PcmReader +import pyembroidery.PcqReader as PcqReader +import pyembroidery.PcsReader as PcsReader +import pyembroidery.MitReader as MitReader +import pyembroidery.NewReader as NewReader +import pyembroidery.ExyReader as ExyReader +import pyembroidery.FxyReader as FxyReader +import pyembroidery.GtReader as GtReader +import pyembroidery.DatReader as DatReader +import pyembroidery.PhcReader as PhcReader +import pyembroidery.MaxReader as MaxReader +import pyembroidery.JpxReader as JpxReader +import pyembroidery.StcReader as StcReader +# import pyembroidery.ZhsReader as ZhsReader +import pyembroidery.ZxyReader as ZxyReader +import pyembroidery.PmvReader as PmvReader +import pyembroidery.PmvWriter as PmvWriter +import pyembroidery.CsvReader as CsvReader + + +def supported_formats(): + """Generates dictionary entries for supported formats. Each entry will + always have description, extension, mimetype, and category. Reader + will provide the reader, if one exists, writer will provide the writer, + if one exists. + + Metadata gives a list of metadata read and/or written by that type. + + Options provides accepted options by the format and their accepted values. + """ + yield ({ + "description": "Brother Embroidery Format", + "extension": "pec", + "mimetype": "application/x-pec", + "category": "embroidery", + "reader": PecReader, + "writer": PecWriter, + "metadata": ("name") + }) + yield ({ + "description": "Brother Embroidery Format", + "extension": "pes", + "mimetype": "application/x-pes", + "category": "embroidery", + "reader": PesReader, + "writer": PesWriter, + "options": { + "pes version": (1, 6), + "truncated": (True, False) + }, + "metadata": ("name", "author", "category", "keywords", "comments") + }) + yield ({ + "description": "Melco Embroidery Format", + "extension": "exp", + "mimetype": "application/x-exp", + "category": "embroidery", + "reader": ExpReader, + "writer": ExpWriter, + }) + yield ({ + "description": "Tajima Embroidery Format", + "extension": "dst", + "mimetype": "application/x-dst", + "category": "embroidery", + "reader": DstReader, + "writer": DstWriter, + "options": { + "extended headers": (True, False) + }, + "metadata": ("name") + }) + yield ({ + "description": "Janome Embroidery Format", + "extension": "jef", + "mimetype": "application/x-jef", + "category": "embroidery", + "reader": JefReader, + "writer": JefWriter, + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "vp3", + "mimetype": "application/x-vp3", + "category": "embroidery", + "reader": Vp3Reader, + "writer": Vp3Writer, + }) + yield ({ + "description": "Scalable Vector Graphics", + "extension": "svg", + "mimetype": "image/svg+xml", + "category": "vector", + "writer": SvgWriter, + }) + yield ({ + "description": "Comma-separated values", + "extension": "csv", + "mimetype": "text/csv", + "category": "debug", + "reader": CsvReader, + "writer": CsvWriter, + "options": { + "deltas": (True, False) + }, + }) + yield ({ + "description": "Singer Embroidery Format", + "extension": "xxx", + "mimetype": "application/x-xxx", + "category": "embroidery", + "reader": XxxReader + }) + yield ({ + "description": "Janome Embroidery Format", + "extension": "sew", + "mimetype": "application/x-sew", + "category": "embroidery", + "reader": SewReader + }) + yield ({ + "description": "Barudan Embroidery Format", + "extension": "u01", + "mimetype": "application/x-u01", + "category": "embroidery", + "reader": U01Reader, + "writer": U01Writer + }) + yield ({ + "description": "Husqvarna Viking Embroidery Format", + "extension": "shv", + "mimetype": "application/x-shv", + "category": "embroidery", + "reader": ShvReader + }) + yield ({ + "description": "Toyota Embroidery Format", + "extension": "10o", + "mimetype": "application/x-10o", + "category": "embroidery", + "reader": A10oReader + }) + yield ({ + "description": "Toyota Embroidery Format", + "extension": "100", + "mimetype": "application/x-100", + "category": "embroidery", + "reader": A100Reader + }) + yield ({ + "description": "Bits & Volts Embroidery Format", + "extension": "bro", + "mimetype": "application/x-Bro", + "category": "embroidery", + "reader": BroReader + }) + yield ({ + "description": "Sunstar or Barudan Embroidery Format", + "extension": "dat", + "mimetype": "application/x-dat", + "category": "embroidery", + "reader": DatReader + }) + yield ({ + "description": "Tajima(Barudan) Embroidery Format", + "extension": "dsb", + "mimetype": "application/x-dsb", + "category": "embroidery", + "reader": DsbReader + }) + yield ({ + "description": "ZSK USA Embroidery Format", + "extension": "dsz", + "mimetype": "application/x-dsz", + "category": "embroidery", + "reader": DszReader + }) + yield ({ + "description": "Elna Embroidery Format", + "extension": "emd", + "mimetype": "application/x-emd", + "category": "embroidery", + "reader": EmdReader + }) + yield ({ + "description": "Eltac Embroidery Format", + "extension": "exy", # e??, e01 + "mimetype": "application/x-exy", + "category": "embroidery", + "reader": ExyReader + }) + yield ({ + "description": "Fortron Embroidery Format", + "extension": "fxy", # f??, f01 + "mimetype": "application/x-fxy", + "category": "embroidery", + "reader": FxyReader + }) + yield ({ + "description": "Gold Thread Embroidery Format", + "extension": "gt", + "mimetype": "application/x-exy", + "category": "embroidery", + "reader": GtReader + }) + yield ({ + "description": "Inbro Embroidery Format", + "extension": "inb", + "mimetype": "application/x-inb", + "category": "embroidery", + "reader": InbReader + }) + yield ({ + "description": "Tajima Embroidery Format", + "extension": "tbf", + "mimetype": "application/x-tbf", + "category": "embroidery", + "reader": TbfReader + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "ksm", + "mimetype": "application/x-ksm", + "category": "embroidery", + "reader": KsmReader + }) + yield ({ + "description": "Happy Embroidery Format", + "extension": "tap", + "mimetype": "application/x-tap", + "category": "embroidery", + "reader": TapReader + }) + yield ({ + "description": "Data Stitch Embroidery Format", + "extension": "stx", + "mimetype": "application/x-stx", + "category": "embroidery", + "reader": StxReader + }) + yield ({ + "description": "Brother Embroidery Format", + "extension": "phb", + "mimetype": "application/x-phb", + "category": "embroidery", + "reader": PhbReader + }) + yield ({ + "description": "Brother Embroidery Format", + "extension": "phc", + "mimetype": "application/x-phc", + "category": "embroidery", + "reader": PhcReader + }) + yield ({ + "description": "Ameco Embroidery Format", + "extension": "new", + "mimetype": "application/x-new", + "category": "embroidery", + "reader": NewReader + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "max", + "mimetype": "application/x-max", + "category": "embroidery", + "reader": MaxReader + }) + yield ({ + "description": "Mitsubishi Embroidery Format", + "extension": "mit", + "mimetype": "application/x-mit", + "category": "embroidery", + "reader": MitReader + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "pcd", + "mimetype": "application/x-pcd", + "category": "embroidery", + "reader": PcdReader + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "pcq", + "mimetype": "application/x-pcq", + "category": "embroidery", + "reader": PcqReader + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "pcm", + "mimetype": "application/x-pcm", + "category": "embroidery", + "reader": PcmReader + }) + yield ({ + "description": "Pfaff Embroidery Format", + "extension": "pcs", + "mimetype": "application/x-pcs", + "category": "embroidery", + "reader": PcsReader + }) + yield ({ + "description": "Janome Embroidery Format", + "extension": "jpx", + "mimetype": "application/x-jpx", + "category": "embroidery", + "reader": JpxReader + }) + yield ({ + "description": "Gunold Embroidery Format", + "extension": "stc", + "mimetype": "application/x-stc", + "category": "embroidery", + "reader": StcReader + }) + # yield ({ + # "description": "Zeng Hsing Embroidery Format", + # "extension": "zhs", + # "mimetype": "application/x-zhs", + # "category": "embroidery", + # "reader": ZhsReader + # }) + yield ({ + "description": "ZSK TC Embroidery Format", + "extension": "zxy", + "mimetype": "application/x-zxy", + "category": "embroidery", + "reader": ZxyReader + }) + yield ({ + "description": "Brother Stitch Format", + "extension": "pmv", + "mimetype": "application/x-pmv", + "category": "stitch", + "reader": PmvReader, + "writer": PmvWriter + }) + + +def convert(filename_from, filename_to, settings=None): + pattern = read(filename_from, settings) + if pattern is None: + return + if settings is not None: + stable = settings.get("stable", True) + if stable: + pattern = pattern.get_stable_pattern() + else: + pattern = pattern.get_stable_pattern() + write(pattern, filename_to, settings) + + +def get_extension_by_filename(filename): + """extracts he extension from a filename""" + return os.path.splitext(filename)[1][1:] + + +def read_embroidery(reader, f, settings=None, pattern=None): + """Reads fileobject or filename with reader.""" + if pattern is None: + pattern = EmbPattern() + if isinstance(f, str): + text_mode = False + try: + text_mode = reader.READ_FILE_IN_TEXT_MODE + except AttributeError: + pass + if text_mode: + try: + with open(f, "r") as stream: + reader.read(stream, pattern, settings) + stream.close() + except IOError: + pass + else: + try: + with open(f, "rb") as stream: + reader.read(stream, pattern, settings) + stream.close() + except IOError: + pass + else: + reader.read(f, pattern, settings) + return pattern + + +def read_dst(f, settings=None, pattern=None): + """Reads fileobject as DST file""" + return read_embroidery(DstReader, f, settings, pattern) + + +def read_pec(f, settings=None, pattern=None): + """Reads fileobject as PEC file""" + return read_embroidery(PecReader, f, settings, pattern) + + +def read_pes(f, settings=None, pattern=None): + """Reads fileobject as PES file""" + return read_embroidery(PesReader, f, settings, pattern) + + +def read_exp(f, settings=None, pattern=None): + """Reads fileobject as EXP file""" + return read_embroidery(ExpReader, f, settings, pattern) + + +def read_vp3(f, settings=None, pattern=None): + """Reads fileobject as VP3 file""" + return read_embroidery(Vp3Reader, f, settings, pattern) + + +def read_jef(f, settings=None, pattern=None): + """Reads fileobject as JEF file""" + return read_embroidery(JefReader, f, settings, pattern) + + +def read(filename, settings=None, pattern=None): + """Reads file, assuming type by extension""" + extension = get_extension_by_filename(filename) + extension = extension.lower() + for file_type in supported_formats(): + if file_type['extension'] != extension: + continue + reader = file_type.get("reader", None) + return read_embroidery(reader, filename, settings, pattern) + return None + + +def write_embroidery(writer, pattern, stream, settings=None): + if settings is None: + settings = {} + else: + settings = settings.copy() + if settings.get("encode", True): + if not ("max_jump" in settings): + try: + settings["max_jump"] = writer.MAX_JUMP_DISTANCE + except AttributeError: + pass + if not ("max_stitch" in settings): + try: + settings["max_stitch"] = writer.MAX_STITCH_DISTANCE + except AttributeError: + pass + if not ("full_jump" in settings): + try: + settings["full_jump"] = writer.FULL_JUMP + except AttributeError: + pass + if not ("strip_speeds" in settings): + try: + settings["strip_speeds"] = writer.STRIP_SPEEDS + except AttributeError: + pass + if not ("sequin_contingency" in settings): + try: + settings["sequin_contingency"] = writer.SEQUIN_CONTINGENCY + except AttributeError: + pass + pattern = pattern.get_normalized_pattern(settings) + + if isinstance(stream, str): + with open(stream, "wb") as stream: + writer.write(pattern, stream, settings) + else: + writer.write(pattern, stream, settings) + + +def write_dst(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(DstWriter, pattern, stream, settings) + + +def write_pec(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(PecWriter, pattern, stream, settings) + + +def write_pes(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(PesWriter, pattern, stream, settings) + + +def write_exp(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(ExpWriter, pattern, stream, settings) + + +def write_vp3(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(Vp3Writer, pattern, stream, settings) + + +def write_jef(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(JefWriter, pattern, stream, settings) + + +def write_svg(pattern, stream, settings=None): + """Writes fileobject as DST file""" + write_embroidery(SvgWriter, pattern, stream, settings) + + +def write(pattern, filename, settings=None): + """Writes file, assuming type by extension""" + extension = get_extension_by_filename(filename) + extension = extension.lower() + + for file_type in supported_formats(): + if file_type['extension'] != extension: + continue + writer = file_type.get("writer", None) + if writer is None: + continue + write_embroidery(writer, pattern, filename, settings) diff --git a/pyembroidery/ReadHelper.py b/pyembroidery/ReadHelper.py new file mode 100644 index 0000000..28b0743 --- /dev/null +++ b/pyembroidery/ReadHelper.py @@ -0,0 +1,105 @@ +def signed8(b): + if b > 127: + return -256 + b + else: + return b + + +def signed16(v): + v &= 0xFFFF + if v > 0x7FFF: + return - 0x10000 + v + else: + return v + + +def signed24(v): + v &= 0xFFFFFF + if v > 0x7FFFFF: + return - 0x1000000 + v + else: + return v + + +def read_signed(stream, n): + byte = bytearray(stream.read(n)) + signed_bytes = [] + for b in byte: + signed_bytes.append(signed8(b)) + return signed_bytes + + +def read_sint_8(stream): + byte = bytearray(stream.read(1)) + if len(byte) is 1: + return signed8(byte[0]) + return None + + +def read_int_8(stream): + byte = bytearray(stream.read(1)) + if len(byte) is 1: + return byte[0] + return None + + +def read_int_16le(stream): + byte = bytearray(stream.read(2)) + if len(byte) is 2: + return (byte[0] & 0xFF) + ((byte[1] & 0xFF) << 8) + return None + + +def read_int_16be(stream): + byte = bytearray(stream.read(2)) + if len(byte) is 2: + return (byte[1] & 0xFF) + ((byte[0] & 0xFF) << 8) + return None + + +def read_int_24le(stream): + b = bytearray(stream.read(3)) + if len(b) is 3: + return (b[0] & 0xFF) + ((b[1] & 0xFF) << 8) + \ + ((b[2] & 0xFF) << 16) + return None + + +def read_int_24be(stream): + b = bytearray(stream.read(3)) + if len(b) is 3: + return (b[2] & 0xFF) + ((b[1] & 0xFF) << 8) + \ + ((b[0] & 0xFF) << 16) + return None + + +def read_int_32le(stream): + b = bytearray(stream.read(4)) + if len(b) is 4: + return (b[0] & 0xFF) + ((b[1] & 0xFF) << 8) + \ + ((b[2] & 0xFF) << 16) + ((b[3] & 0xFF) << 24) + return None + + +def read_int_32be(stream): + b = bytearray(stream.read(4)) + if len(b) is 4: + return (b[3] & 0xFF) + ((b[2] & 0xFF) << 8) + \ + ((b[1] & 0xFF) << 16) + ((b[0] & 0xFF) << 24) + return None + + +def read_string_8(stream, length): + byte = stream.read(length) + try: + return byte.decode('utf8') + except UnicodeDecodeError: + return None # Must be > 128 chars. + + +def read_string_16(stream, length): + byte = stream.read(length) + try: + return byte.decode('utf16') + except UnicodeDecodeError: + return None diff --git a/pyembroidery/SewReader.py b/pyembroidery/SewReader.py new file mode 100644 index 0000000..bbe092c --- /dev/null +++ b/pyembroidery/SewReader.py @@ -0,0 +1,35 @@ +from .ReadHelper import read_int_16le, signed8 +from .EmbThreadSew import get_thread_set + + +def read(f, out, settings=None): + threads = get_thread_set() + colors = read_int_16le(f) + for c in range(0, colors): + index = read_int_16le(f) + index %= len(threads) + out.add_thread(threads[index]) + + f.seek(0x1D78, 0) + while True: + b = bytearray(f.read(2)) + if len(b) != 2: + break + if b[0] != 0x80: + out.stitch(signed8(b[0]), -signed8(b[1])) + continue + control = b[1] + b = bytearray(f.read(2)) + if len(b) != 2: + break + if control & 1: + out.color_change() + continue + if control == 0x04 or control == 0x02: + out.move(signed8(b[0]), -signed8(b[1])) + continue + if control == 0x10: + out.stitch(signed8(b[0]), -signed8(b[1])) + continue + break + out.end() diff --git a/pyembroidery/ShvReader.py b/pyembroidery/ShvReader.py new file mode 100644 index 0000000..0906588 --- /dev/null +++ b/pyembroidery/ShvReader.py @@ -0,0 +1,72 @@ +import math +from .ReadHelper import read_int_16be, read_int_32be, \ + read_int_8, read_string_8, signed16, signed8 +from .EmbThreadShv import get_thread_set +from .EmbConstant import * + + +def read(f, out, settings=None): + in_jump = False + f.seek(0x56, 1) # header text + length = read_int_8(f) + out.metadata("name", read_string_8(f, length)) + + design_width = read_int_8(f) + design_height = read_int_8(f) + + skip = math.ceil(design_height / 2.0) * design_width + f.seek(4 + int(skip), 1) + colors = read_int_8(f) + f.seek(18, 1) + threads = get_thread_set() + stitch_per_color = {} + for i in range(colors): + stitch_count = read_int_32be(f) + color_code = read_int_8(f) + thread = threads[color_code % len(threads)] + out.add_thread(thread) + stitch_per_color[i] = stitch_count + f.seek(9, 1) + f.seek(-2, 1) + + stitches_since_stop = 0 + current_color_index = 0 + try: + max_stitches = stitch_per_color[current_color_index] + except IndexError: + max_stitches = 0 + while True: + flags = STITCH + if in_jump: + flags = JUMP + b0 = read_int_8(f) + b1 = read_int_8(f) + if b1 is None: + break + if stitches_since_stop >= max_stitches: + out.color_change() + stitches_since_stop = 0 + current_color_index += 1 + try: + max_stitches = stitch_per_color[current_color_index] + except KeyError: + max_stitches = 0xFFFFFFFF + if b0 == 0x80: + stitches_since_stop += 1 + if b1 == 3: + continue + elif b1 == 2: + in_jump = False + continue + elif b1 == 1: + stitches_since_stop += 2 + sx = signed16(read_int_16be(f)) + sy = signed16(read_int_16be(f)) + in_jump = True + out.move(sx, sy) + continue + dx = signed8(b0) + dy = signed8(b1) + stitches_since_stop += 1 + out.add_stitch_relative(flags, dx, dy) + out.end() diff --git a/pyembroidery/StcReader.py b/pyembroidery/StcReader.py new file mode 100644 index 0000000..124cbe6 --- /dev/null +++ b/pyembroidery/StcReader.py @@ -0,0 +1,31 @@ +from .ReadHelper import signed8 + + +def stc_stitch_encoding_read(f, out): + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + x = signed8(byte[0]) + y = -signed8(byte[1]) + ctrl = byte[2] + + if ctrl == 0x01: + out.stitch(x, y) + continue + if ctrl == 0x00: + out.move(x, y) + continue + if ctrl == 25: + break + else: + needle = ctrl - 2 + out.color_change() + out.end() + + +def read(f, out, settings=None): + f.seek(0x28, 1) # DESIGN: xxxxxx STITCHES: xxxx. + stc_stitch_encoding_read(f, out) diff --git a/pyembroidery/StxReader.py b/pyembroidery/StxReader.py new file mode 100644 index 0000000..18d6c98 --- /dev/null +++ b/pyembroidery/StxReader.py @@ -0,0 +1,12 @@ +from .ReadHelper import read_int_32le +from .ExpReader import read_exp_stitches + + +def read(f, out, settings=None): + # File starts with STX + f.seek(0x0C, 1) + color_start_position = read_int_32le(f) + dunno_block_start_position = read_int_32le(f) + stitch_start_position = read_int_32le(f) + f.seek(stitch_start_position, 0) + read_exp_stitches(f, out) diff --git a/pyembroidery/SvgWriter.py b/pyembroidery/SvgWriter.py new file mode 100644 index 0000000..14eccf3 --- /dev/null +++ b/pyembroidery/SvgWriter.py @@ -0,0 +1,65 @@ +from xml.etree.cElementTree import Element, ElementTree, SubElement + +from .EmbConstant import CONTINGENCY_SEQUIN_STITCH + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_STITCH +# technically I could use svg to draw a sequin as a 2 element circle path. +FULL_JUMP = False # just drops all jumps anyway. +MAX_JUMP_DISTANCE = float('inf') +MAX_STITCH_DISTANCE = float('inf') + +NAME_SVG = "svg" +ATTR_VERSION = "version" +VALUE_SVG_VERSION = "1.1" +ATTR_XMLNS = "xmlns" +VALUE_XMLNS = "http://www.w3.org/2000/svg" +ATTR_XMLNS_LINK = "xmlns:xlink" +VALUE_XLINK = "http://www.w3.org/1999/xlink" +ATTR_XMLNS_EV = "xmlns:ev" +VALUE_XMLNS_EV = "http://www.w3.org/2001/xml-events" +ATTR_WIDTH = "width" +ATTR_HEIGHT = "height" +ATTR_VIEWBOX = "viewBox" +NAME_PATH = "path" +ATTR_DATA = "d" +ATTR_FILL = "fill" +ATTR_STROKE = "stroke" +ATTR_STROKE_WIDTH = "stroke-width" +VALUE_NONE = "none" + + +def write(pattern, f, settings=None): + """Writes an svg file of the stitchblocks.""" + + root = Element(NAME_SVG) + root.set(ATTR_VERSION, VALUE_SVG_VERSION) + root.set(ATTR_XMLNS, VALUE_XMLNS) + root.set(ATTR_XMLNS_LINK, VALUE_XLINK) + root.set(ATTR_XMLNS_EV, VALUE_XMLNS_EV) + extends = pattern.extends() + width = extends[2] - extends[0] + height = extends[3] - extends[1] + root.set(ATTR_WIDTH, str(width)) + root.set(ATTR_HEIGHT, str(height)) + viewbox = \ + str(extends[0]) + " " +\ + str(extends[1]) + " " +\ + str(width) + " " +\ + str(height) + root.set(ATTR_VIEWBOX, viewbox) + + for stitchblock in pattern.get_as_stitchblock(): + block = stitchblock[0] + thread = stitchblock[1] + path = SubElement(root, NAME_PATH) + data = "M" + for stitch in block: + x = stitch[0] + y = stitch[1] + data += " " + str(x) + "," + str(y) + path.set(ATTR_DATA, data) + path.set(ATTR_FILL, VALUE_NONE) + path.set(ATTR_STROKE, thread.hex_color()) + path.set(ATTR_STROKE_WIDTH, "3") + tree = ElementTree(root) + tree.write(f) diff --git a/pyembroidery/TapReader.py b/pyembroidery/TapReader.py new file mode 100644 index 0000000..f4d1d81 --- /dev/null +++ b/pyembroidery/TapReader.py @@ -0,0 +1,5 @@ +from .DstReader import dst_read_stitches + + +def read(f, out, settings=None): + dst_read_stitches(f, out) diff --git a/pyembroidery/TbfReader.py b/pyembroidery/TbfReader.py new file mode 100644 index 0000000..667f76e --- /dev/null +++ b/pyembroidery/TbfReader.py @@ -0,0 +1,48 @@ +from .ReadHelper import signed8, read_int_8, read_int_24be +from .EmbThread import EmbThread + + +def read(f, out, settings=None): + f.seek(0x20E, 0) + while True: + if read_int_8(f) == 0x45: + thread = EmbThread() + thread.color = read_int_24be(f) + read_int_8(f) # Should be 0x20 " " + out.add_thread(thread) + else: + break + f.seek(0x600, 0) + + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + x = byte[0] + y = byte[1] + ctrl = byte[2] + if ctrl == 0x80: + out.stitch(signed8(x), -signed8(y)) + continue + elif ctrl == 0x81: + if count > 1: + out.color_change() + elif ctrl == 0x90: + if x == 0 and y == 0: + out.trim() + else: + out.move(signed8(x), -signed8(y)) + continue + elif ctrl == 0x40: + out.stop() + continue + elif ctrl == 0x86: + out.trim() + continue + elif ctrl == 0x8F: + break + else: + print("odd.") + out.end() diff --git a/pyembroidery/U01Reader.py b/pyembroidery/U01Reader.py new file mode 100644 index 0000000..a96711c --- /dev/null +++ b/pyembroidery/U01Reader.py @@ -0,0 +1,80 @@ +from .EmbConstant import * + + +def read_u01_stitches(f, out): + count = 0 + while True: + count += 1 + byte = bytearray(f.read(3)) + if len(byte) != 3: + break + ctrl = byte[0] + dy = -byte[1] + dx = byte[2] + if (ctrl & 0x20) != 0: + dx = -dx + if (ctrl & 0x40) != 0: + dy = -dy + command = ctrl & 0b11111 + # print(str(count), " ", str("{0:b}").format(ctrl), " 0x%0.2X " % ctrl, str(command), " " + str(dx), " ", str(dy)) + if command == 0x0: + # Stitch + out.stitch(dx, dy) + continue + if command == 0x01: + # Jump + out.move(dx, dy) + continue + if command == 0x02: + # Fast + out.add_stitch_relative(FAST) + if dx != 0 or dy != 0: + out.stitch(dx, dy) + continue + if command == 0x03: + # Fast, Jump + out.add_stitch_relative(FAST) + if dx != 0 or dy != 0: + out.move(dx, dy) + continue + if command == 0x04: + # Slow + out.add_stitch_relative(SLOW) + if dx != 0 or dy != 0: + out.stitch(dx, dy) + continue + if command == 0x05: + # Slow, Jump + out.add_stitch_relative(SLOW) + if dx != 0 or dy != 0: + out.move(dx, dy) + continue + if command == 0x06: + # T1 Top Thread Trimming, TTrim. + out.trim(dx, dy) + continue + if command == 0x07: + # T2 Bobbin Threading + out.trim(dx, dy) + continue + if command == 0x08: # ww, stop file had proper A8 rather than E8 and displacement + # C00 Stop + out.stop(dx, dy) + continue + if 0x09 <= command <= 0x17: + # C01 - C14 + if count > 1: + out.color_change(dx, dy) + continue + if command == 0x18: + break + if ctrl == 0x2B: + break # Rare postfix data from machine. Do not read this. + break # Uncaught Command + out.end() + + +def read(f, out, settings=None): + f.seek(0x80, 1) + f.seek(0x80, 1) + read_u01_stitches(f, out) diff --git a/pyembroidery/U01Writer.py b/pyembroidery/U01Writer.py new file mode 100644 index 0000000..aafcb16 --- /dev/null +++ b/pyembroidery/U01Writer.py @@ -0,0 +1,88 @@ +from .EmbConstant import * +from .WriteHelper import write_int_16le, write_int_32le + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_JUMP +STRIP_SPEEDS = False +FULL_JUMP = False +MAX_JUMP_DISTANCE = 127 +MAX_STITCH_DISTANCE = 127 + + +def write(pattern, f, settings=None): + stitches = pattern.stitches + stitch_count = len(stitches) + for i in range(0, 0x80): + f.write(b'0') + if stitch_count == 0: + return + extends = pattern.extends() + write_int_16le(f, int(extends[0])) + write_int_16le(f, -int(extends[3])) + write_int_16le(f, int(extends[2])) + write_int_16le(f, -int(extends[1])) + write_int_32le(f, 0) # Dunno. + + write_int_32le(f, stitch_count + 1) # force write first needle position + last_stitch = stitches[stitch_count - 1] + write_int_16le(f, int(last_stitch[0])) + write_int_16le(f, -int(last_stitch[1])) + for i in range(f.tell(), 0x100): + f.write(b'\x00') + xx = 0 + yy = 0 + needle = 1 + f.write(b'\xE9\x00\x00') # Needle to C1 + trigger_fast = False + trigger_slow = False + for stitch in stitches: + x = stitch[0] + y = stitch[1] + data = stitch[2] + dx = int(round(x - xx)) + dy = int(round(y - yy)) + xx += dx + yy += dy + if data == SLOW: + trigger_slow = True + continue + if data == FAST: + trigger_fast = True + continue + cmd = 0x80 + if dy >= 0: + cmd |= 0x40 + if dx <= 0: + cmd |= 0x20 + delta_x = abs(dx) + delta_y = abs(dy) + if data == STITCH: + if trigger_fast: + trigger_fast = False + cmd |= 0x02 + if trigger_slow: + trigger_slow = False + cmd |= 0x04 + f.write(bytes(bytearray([cmd, delta_y, delta_x]))) + elif data == JUMP: # If you did both FAST, SLOW, and JUMP, you'd get a trim. + if trigger_fast: + trigger_fast = False + cmd |= 0x02 + if trigger_slow: + trigger_slow = False + cmd |= 0x04 + cmd |= 0x01 + f.write(bytes(bytearray([cmd, delta_y, delta_x]))) + elif data == STOP: + cmd |= 0x08 + f.write(bytes(bytearray([cmd, delta_y, delta_x]))) + elif data == TRIM: + cmd |= 0x07 + f.write(bytes(bytearray([cmd, delta_y, delta_x]))) + elif data == COLOR_CHANGE: + needle %= 7 + needle += 1 + cmd = cmd + 8 + needle + f.write(bytes(bytearray([cmd, delta_y, delta_x]))) + elif data == END: + f.write(b'\xF8\x00\x00') + break diff --git a/pyembroidery/Vp3Reader.py b/pyembroidery/Vp3Reader.py new file mode 100644 index 0000000..8fa67b3 --- /dev/null +++ b/pyembroidery/Vp3Reader.py @@ -0,0 +1,118 @@ +from .EmbThread import EmbThread +from .ReadHelper import read_int_16be, read_int_8, read_int_32be, read_int_24be, read_signed, read_string_8, \ + read_string_16 + + +def read_vp3_string_16(stream): + # Reads the header strings which are 16le numbers of size followed by + # utf-16 text + string_length = read_int_16be(stream) + return read_string_16(stream, string_length) + + +def read_vp3_string_8(stream): + # Reads the body strings which are 16be numbers followed by utf-8 text + string_length = read_int_16be(stream) + return read_string_8(stream, string_length) + + +def skip_vp3_string(stream): + string_length = read_int_16be(stream) + stream.seek(string_length, 1) + + +def signed32(b): + b &= 0xFFFFFFFF + if b > 0x7FFFFFFF: + return - 0x100000000 + b + else: + return b + + +def signed16(b0, b1): + b0 &= 0xFF + b1 &= 0xFF + b = (b0 << 8) | b1 + if b > 0x7FFF: + return - 0x10000 + b + else: + return b + + +def read(f, out, settings=None): + b = f.read(6) + # magic code: %vsm%\0 + skip_vp3_string(f) # "Produced by Software Ltd" + f.seek(7, 1) + skip_vp3_string(f) # "" comments and note string. + f.seek(32, 1) + center_x = (signed32(read_int_32be(f)) / 100) + center_y = -(signed32(read_int_32be(f)) / 100) + f.seek(27, 1) + skip_vp3_string(f) # "" + f.seek(24, 1) + skip_vp3_string(f) # "Produced by Software Ltd" + count_colors = read_int_16be(f) + for i in range(0, count_colors): + vp3_read_colorblock(f, out, center_x, center_y) + + +def vp3_read_colorblock(f, read_object, center_x, center_y): + bytescheck = f.read(3) # \x00\x05\x00 + distance_to_next_block_050 = read_int_32be(f) + block_end_position = distance_to_next_block_050 + f.tell() + + start_position_x = (signed32(read_int_32be(f)) / 100) + start_position_y = -(signed32(read_int_32be(f)) / 100) + abs_x = start_position_x + center_x + abs_y = start_position_y + center_y + if abs_x != 0 and abs_y != 0: + read_object.move_abs(abs_x, abs_y) + thread = vp3_read_thread(f) + read_object.add_thread(thread) + f.seek(15, 1) + bytescheck = f.read(3) # \x0A\xF6\x00 + stitch_byte_length = block_end_position - f.tell() + stitch_bytes = read_signed(f, stitch_byte_length) + + i = 0 + while i < len(stitch_bytes) - 1: + x = stitch_bytes[i] + y = stitch_bytes[i + 1] + i += 2 + if (x & 0xFF) == 0x80: + if y == 0x01: + x = signed16(stitch_bytes[i], stitch_bytes[i + 1]) + i += 2 + y = signed16(stitch_bytes[i], stitch_bytes[i + 1]) + i += 2 + if abs(x) > 255 or abs(y) > 255: + read_object.trim(0, 0) + read_object.move(x, y) + else: + read_object.stitch(x, y) + elif y == 0x02: + pass # ends long stitch mode. + elif y == 0x03: + read_object.end(0, 0) + return + else: + read_object.stitch(x, y) + read_object.trim(0, 0) + read_object.color_change(0, 0) + + +def vp3_read_thread(f): + thread = EmbThread() + colors = read_int_8(f) + transition = read_int_8(f) + for m in range(0, colors): + thread.color = read_int_24be(f) + parts = read_int_8(f) + color_length = read_int_16be(f) + thread_type = read_int_8(f) + weight = read_int_8(f) + thread.catalog_number = read_vp3_string_8(f) + thread.description = read_vp3_string_8(f) + thread.brand = read_vp3_string_8(f) + return thread diff --git a/pyembroidery/Vp3Writer.py b/pyembroidery/Vp3Writer.py new file mode 100644 index 0000000..7dc7003 --- /dev/null +++ b/pyembroidery/Vp3Writer.py @@ -0,0 +1,262 @@ +from .EmbConstant import * +from .WriteHelper import write_int_8, write_int_24be, \ + write_int_32be, write_int_16be, write_string_utf8, write_string + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_JUMP +FULL_JUMP = False +# Since jump is stitch, full jump will result in pointless double stitch. +# Vp3 can encode signed 16 bit deltas. +MAX_JUMP_DISTANCE = 3200 +# coded beyond 255 tho, they count as jumps. +MAX_STITCH_DISTANCE = 255 + + +def vp3_write_string_8(stream, string): + write_int_16be(stream, len(string)) + write_string_utf8(stream, string) + # vp3_write_length_and_bytes(stream, bytestring) + + +def vp3_write_string_16(stream, string): + write_int_16be(stream, len(string) * 2) + write_string(stream, string, "UTF-16BE") + # vp3_write_length_and_bytes(stream, bytestring) + + +def vp3_write_length_and_bytes(stream, bytestring): + write_int_16be(stream, len(bytestring)) + stream.write(bytestring) + + +def vp3_patch_byte_offset(stream, offset): + currentPos = stream.tell() + stream.seek(offset, 0) # Absolute position seek. + position = currentPos - offset - 4 # 4 bytes int32 + write_int_32be(stream, position) + stream.seek(currentPos, 0) # Absolute position seek. + + +def get_as_colorblocks(pattern): + thread_index = 0 + last_pos = 0 + end = len(pattern.stitches) + for pos, stitch in enumerate(pattern.stitches): + if stitch[2] != COLOR_CHANGE: + continue + thread = pattern.get_thread_or_filler(thread_index) + thread_index += 1 + yield (pattern.stitches[last_pos:pos], thread) + last_pos = pos + thread = pattern.get_thread_or_filler(thread_index) + thread_index += 1 + yield (pattern.stitches[last_pos:end], thread) + + +def write(pattern, f, settings=None): + pattern.fix_color_count() + + write_string_utf8(f, "%vsm%") + write_int_8(f, 0) + vp3_write_string_16(f, "Produced by Software Ltd") + write_file(pattern, f) + + +def write_file(pattern, f): + f.write(b'\x00\x02\x00') + placeholder_distance_end_of_file_block_020 = f.tell() + write_int_32be(f, 0) # placeholder + # This refers to the end of the final block, not entire bytes. + + vp3_write_string_16(f, "") + # This is global notes and settings string. + # "Setting:" followed by settings text. + + count_stitches = len(pattern.stitches) + colorblocks = [i for i in get_as_colorblocks(pattern)] + + count_colorblocks_total = len(colorblocks) + + extends = pattern.extends() + write_int_32be(f, int(extends[2] * 100)) # right + write_int_32be(f, int(extends[1] * -100)) # -top + write_int_32be(f, int(extends[0] * 100)) # left + write_int_32be(f, int(extends[3] * -100)) # -bottom + + # EmbroiderModder Comment: + # "this would be some(unknown) function of thread length" + # Wilcom: 0C 54 == 3156 + # Note, this is the total stitch count, sans end command. + ends = pattern.count_stitch_commands(END) + count_just_stitches = count_stitches - ends + + write_int_32be(f, count_just_stitches) + write_int_8(f, 0) + write_int_8(f, count_colorblocks_total) + write_int_8(f, 12) # 0xC + write_int_8(f, 0) + + count_designs = 1 + write_int_8(f, count_designs) # Number of designs. + for i in range(0, count_designs): + write_design_block(f, extends, colorblocks) + vp3_patch_byte_offset(f, placeholder_distance_end_of_file_block_020) + + +def write_design_block(f, extends, colorblocks): + f.write(b'\x00\x03\x00') + placeholder_distance_end_of_design_block_030 = f.tell() + write_int_32be(f, 0) + + count_colorblocks_total = len(colorblocks) + + width = extends[2] - extends[0] + height = extends[3] - extends[1] + half_width = width / 2 + half_height = height / 2 + center_x = extends[2] - half_width + center_y = extends[3] - half_height + + write_int_32be(f, int(center_x) * 100) # initial x + write_int_32be(f, int(center_y) * -100) # initial y + write_int_8(f, 0) + write_int_8(f, 0) + write_int_8(f, 0) + + # extends 2 + write_int_32be(f, int(half_width) * -100) + write_int_32be(f, int(half_width) * 100) + write_int_32be(f, int(half_height) * -100) + write_int_32be(f, int(half_height) * 100) + + write_int_32be(f, int(width) * 100) + write_int_32be(f, int(height) * 100) + vp3_write_string_16(f, "") # This is notes and settings string. + + f.write(b'\x64\x64') # write_int_16be(f, 25700) + # maybe b'dd', maybe 100, 100 + write_int_32be(f, 4096) # b'\x00\x00\x10\x00' + write_int_32be(f, 0) # b'\x00\x00\x00\x00' + write_int_32be(f, 0) # b'\x00\x00\x10\x00' + write_int_32be(f, 4096) # b'\x00\x00\x10\x00' + + f.write(b'xxPP\x01\x00') + + vp3_write_string_16(f, "Produced by Software Ltd") + + write_int_16be(f, count_colorblocks_total) + + first = True + for colorblock in colorblocks: + stitches = colorblock[0] + thread = colorblock[1] + write_vp3_colorblock(f, first, center_x, center_y, stitches, thread) + first = False + vp3_patch_byte_offset(f, placeholder_distance_end_of_design_block_030) + + +def write_vp3_colorblock(f, first, center_x, center_y, stitches, thread): + f.write(b'\x00\x05\x00') + placeholder_distance_end_of_color_block_050 = f.tell() + write_int_32be(f, 0) + + if len(stitches) > 0: + first_pos_x = stitches[0][0] + first_pos_y = stitches[0][1] + if first: + first_pos_x = 0 + first_pos_y = 0 + last_pos_x = stitches[-1][0] + last_pos_y = stitches[-1][1] + else: + first_pos_x = 0 + first_pos_y = 0 + last_pos_x = 0 + last_pos_y = 0 + start_position_from_center_x = first_pos_x - center_x + start_position_from_center_y = -(first_pos_y - center_y) + write_int_32be(f, int(start_position_from_center_x) * 100) + write_int_32be(f, int(start_position_from_center_y) * 100) + + vp3_write_thread(f, thread) + + block_shift_x = last_pos_x - first_pos_x + block_shift_y = -(last_pos_y - first_pos_y) + + write_int_32be(f, int(block_shift_x) * 100) + write_int_32be(f, int(block_shift_y) * 100) + + write_stitches_block(f, stitches, first_pos_x, first_pos_y) + + write_int_8(f, 0) + vp3_patch_byte_offset(f, placeholder_distance_end_of_color_block_050) + + +def vp3_write_thread(f, thread): + f.write(b'\x01\x00') # Single color, no transition. + write_int_24be(f, thread.color) + f.write(b'\x00\x00\x00\x05\x28') # no parts, no length, Rayon 40-weight + if thread.catalog_number is not None: + vp3_write_string_8(f, thread.catalog_number) + else: + vp3_write_string_8(f, "") + if thread.description is not None: + vp3_write_string_8(f, thread.description) + else: + vp3_write_string_8(f, thread.hex_color()) + if thread.brand is not None: + vp3_write_string_8(f, thread.brand) + else: + vp3_write_string_8(f, "") + + +def write_stitches_block(f, stitches, first_pos_x, first_pos_y): + # Embroidermodder code has + # vp3_write_string(f, "\x00"); + # The 0, x, 0 bytes come before placeholders + # Given this consisistency, it's doubtful this is a string. + # Those aren't + f.write(b'\x00\x01\x00') + placeholder_distance_to_end_of_stitches_block_010 = f.tell() + write_int_32be(f, 0) # placeholder + + f.write(b'\x0A\xF6\x00') + last_x = first_pos_x + last_y = first_pos_y + + for stitch in stitches: + x = stitch[0] + y = stitch[1] + flags = stitch[2] + if flags == END: + f.write(b'\x80\x03') + break + elif flags == COLOR_CHANGE: + continue + elif flags == TRIM: + continue + elif flags == SEQUIN_MODE: + continue + elif flags == SEQUIN_EJECT: + continue + elif flags == STOP: + # Not sure what to do here. + # f.write(b'\x80\x04') + continue + elif flags == JUMP: + # Since VP3.Jump == VP3.Stitch, we combine jumps. + continue + dx = int(x - last_x) + dy = int(y - last_y) + last_x += dx + last_y += dy + if flags == STITCH: + if -127 <= dx <= 127 and -127 <= dy <= 127: + write_int_8(f, dx) + write_int_8(f, dy) + else: + f.write(b'\x80\x01') + write_int_16be(f, dx) + write_int_16be(f, dy) + f.write(b'\x80\x02') + # VSM gave ending stitches as 80 03 35 A5, so, 80 03 isn't strictly end. + vp3_patch_byte_offset(f, placeholder_distance_to_end_of_stitches_block_010) diff --git a/pyembroidery/WriteEncoder.py b/pyembroidery/WriteEncoder.py new file mode 100644 index 0000000..da9281d --- /dev/null +++ b/pyembroidery/WriteEncoder.py @@ -0,0 +1,172 @@ +import pyembroidery.EmbPattern as EmbPattern +import math + + +def distance_squared(x0, y0, x1, y1): + dx = x1 - x0 + dy = y1 - y0 + dx *= dx + dy *= dy + return dx + dy + + +def distance(x0, y0, x1, y1): + return math.sqrt(distance_squared(x0, y0, x1, y1)) + + +def towards(a, b, amount): + return (amount * (b - a)) + a + + +def angleR(x0, y0, x1, y1): + return math.atan2(y1 - y0, x1 - x0) + + +def oriented(x0, y0, x1, y1, r): + radians = angleR(x0, y0, x1, y1) + return (x0 + (r * math.cos(radians)), y0 + (r * math.sin(radians))) + + +class WriteEncoder(): + def __init__(self): + self.max_jump = float('inf') # type: float + self.max_stitch = float('inf') # type: float + self.tie_on = False # type: bool + self.tie_off = False # type: bool + self.needle_x = 0 # type: float + self.needle_y = 0 # type: float + self.translate_x = 0 # type: float + self.translate_y = 0 # type: float + + def set_translation(self, x, y): + self.translate_x = x + self.translate_y = y + + def jumpTo(self, transcode, x, y): + self.step_to(transcode, x, y, self.max_jump, EmbPattern.JUMP) + transcode.append([x, y, EmbPattern.JUMP]) + + def stitchTo(self, transcode, x, y): + self.step_to(transcode, x, y, self.max_stitch, EmbPattern.STITCH) + transcode.append([x, y, EmbPattern.STITCH]) + + def step_to(self, transcode, x, y, length, data): + distance_x = x - self.needle_x + distance_y = y - self.needle_y + if abs(distance_x) > length or abs(distance_y) > length: + stepsX = math.ceil(abs(distance_x / length)) + stepsY = math.ceil(abs(distance_y / length)) + if (stepsX > stepsY): + steps = stepsX + else: + steps = stepsY + stepSizeX = distance_x / steps + stepSizeY = distance_y / steps + + q = 0 + qe = steps + qx = self.needle_x + qy = self.needle_y + while q < qe: + transcode.append([round(qx), round(qy), data]) + q += 1 + qx += stepSizeX + qy += stepSizeY + + def lock_stitch(self, transcode, lock_x, lock_y, anchor_x, anchor_y): + dist = distance(lock_x, lock_y, anchor_x, anchor_y) + if dist > self.max_stitch: + p = oriented(lock_x, lock_y, anchor_x, anchor_y, self.max_stitch) + anchor_x = p[0] + anchor_y = p[1] + f = (towards(lock_x, anchor_x, .33), towards(lock_y, anchor_y, .33)) + s = (towards(lock_x, anchor_x, .66), towards(lock_y, anchor_y, .66)) + self.stitchTo(transcode, lock_x, lock_y) + self.stitchTo(transcode, f[0], f[1]) + self.stitchTo(transcode, s[0], s[1]) + self.stitchTo(transcode, f[0], f[1]) + + def process(self, p): + self.needle_x = 0 + self.needle_y = 0 + copy = EmbPattern.EmbPattern() + EmbPattern.set(p, copy) + layer = copy.stitches + for stitch in layer: + stitch[0] = round(stitch[0] - self.translate_x) + stitch[1] = round(stitch[1] - self.translate_y) + p.stitches = [] + p.threadlist = [] + self.write_code(copy, p) + self.write_thread(copy, p) + return p + + def write_thread(self, pattern_from, pattern_to): + threads_to = pattern_to.threadlist + threads_to.extend(pattern_from.threadlist) + + def write_code(self, pattern_source, pattern_dest): + source = pattern_source.stitches + dest = pattern_dest.stitches + flags = EmbPattern.NO_COMMAND + trimmed = True + for i, stitch in enumerate(source): + x = stitch[0] + y = stitch[1] + flags = stitch[2] + if flags == EmbPattern.STITCH: + if trimmed: + self.jumpTo(dest, x, y) + self.needle_x = x + self.needle_y = y + if self.tie_on: + b = source[i + 1] + bx = b[0] + by = b[1] + self.lock_stitch(dest, x, y, bx, by) + dest.append([x, y, EmbPattern.STITCH]) + trimmed = False; + else: + self.stitchTo(dest, x, y) + elif flags == EmbPattern.FRAME_EJECT: + if not trimmed: + dest.append([self.needle_x, self.needle_y, EmbPattern.TRIM]) + trimmed = True + self.jumpTo(dest, x, y) + dest.append([x, y, EmbPattern.STOP]) + elif flags == EmbPattern.BREAK: + if not trimmed: + dest.append([self.needle_x, self.needle_y, EmbPattern.TRIM]) + trimmed = True + continue # do not update the needle. + elif flags == EmbPattern.BREAK_COLOR: + if not trimmed: + dest.append([self.needle_x, self.needle_y, EmbPattern.TRIM]) + trimmed = True + dest.append([x, y, EmbPattern.COLOR_CHANGE]) + continue # do not update the needle. + elif flags == EmbPattern.STITCH_FINAL: + if self.tie_off: + b = source[i - 1] + bx = b[0] + by = b[1] + self.lock_stitch(dest, x, y, bx, by) + self.stitchTo(dest, x, y) + dest.append([x, y, EmbPattern.TRIM]) + trimmed = True + elif flags == EmbPattern.STITCH_FINAL_COLOR: + if self.tie_off: + b = source[i - 1] + bx = b[0] + by = b[1] + self.lock_stitch(dest, x, y, bx, by) + self.stitchTo(dest, x, y) + dest.append([x, y, EmbPattern.TRIM]) + dest.append([x, y, EmbPattern.COLOR_CHANGE]) + trimmed = True + else: + dest.append(stitch) + self.needle_x = x + self.needle_y = y + if flags != EmbPattern.END: + dest.append([self.needle_x, self.needle_y, EmbPattern.END]) diff --git a/pyembroidery/WriteHelper.py b/pyembroidery/WriteHelper.py new file mode 100644 index 0000000..a2a82b8 --- /dev/null +++ b/pyembroidery/WriteHelper.py @@ -0,0 +1,90 @@ +import struct + + +def write_int_array_8(stream, int_array): + for value in int_array: + v = bytes(bytearray([ + value & 0xFF, + ])) + stream.write(v) + + +def write_int_8(stream, value): + v = bytes(bytearray([ + value & 0xFF, + ])) + stream.write(v) + + +def write_int_16le(stream, value): + v = bytes(bytearray([ + (value >> 0) & 0xFF, + (value >> 8) & 0xFF, + ])) + stream.write(v) + + +def write_int_16be(stream, value): + v = bytes(bytearray([ + (value >> 8) & 0xFF, + (value >> 0) & 0xFF, + ])) + stream.write(v) + + +def write_int_24le(stream, value): + v = bytes(bytearray([ + (value >> 0) & 0xFF, + (value >> 8) & 0xFF, + (value >> 16) & 0xFF, + ])) + stream.write(v) + + +def write_int_24be(stream, value): + v = bytes(bytearray([ + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + (value >> 0) & 0xFF, + ])) + stream.write(v) + + +def write_int_32le(stream, value): + v = bytes(bytearray([ + (value >> 0) & 0xFF, + (value >> 8) & 0xFF, + (value >> 16) & 0xFF, + (value >> 24) & 0xFF + ])) + stream.write(v) + + +def write_int_32be(stream, value): + v = bytes(bytearray([ + (value >> 24) & 0xFF, + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + (value >> 0) & 0xFF + ])) + stream.write(v) + + +def write_float_32le(stream, value): + stream.write(struct.pack("