kopia lustrzana https://github.com/inkstitch/inkstitch
add params for "TRIM after" and "STOP after" (#29)
* adds new options to Params: "TRIM after" and "STOP after" * adds tooltip support to Params * inkstitch now includes libembroidery and can directly output any supported file type * this avoids the need for `libembroidery-convert` and compiling embroidermodder! * TRIM support for DST format (inserts 3 JUMPs) * STOP command supported as an extra color change that the operator can assign to code C00 * TRIMs cause the following jump stitch not to be displayed in the Embroidery layerpull/38/head v1.1.0
rodzic
462bf0bdbe
commit
acaebaa956
|
@ -0,0 +1,3 @@
|
|||
[submodule "embroidermodder"]
|
||||
path = embroidermodder
|
||||
url = https://github.com/Embroidermodder/Embroidermodder
|
|
@ -34,6 +34,9 @@ install:
|
|||
# for wxPython
|
||||
sudo apt-get install glib-networking
|
||||
|
||||
# for embroidermodder/libembroidery
|
||||
sudo apt-get install swig python-dev
|
||||
|
||||
# This is the same as the pypi module PyGObject. We can't just do
|
||||
# "pip install PyGObject" because it depends on a version of
|
||||
# libgirepository1.0-dev that doesn't exist in Trusty.
|
||||
|
@ -60,6 +63,11 @@ script:
|
|||
flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
elif [ -n "$BUILD" ]; then
|
||||
(
|
||||
cd embroidermodder/experimental
|
||||
qmake swigpython.pro
|
||||
make
|
||||
)
|
||||
make dist
|
||||
else
|
||||
true
|
||||
|
|
318
PyEmb.py
318
PyEmb.py
|
@ -2,8 +2,9 @@
|
|||
# http://www.achatina.de/sewing/main/TECHNICL.HTM
|
||||
|
||||
import math
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
import libembroidery
|
||||
|
||||
PIXELS_PER_MM = 96 / 25.4
|
||||
|
||||
try:
|
||||
from functools import lru_cache
|
||||
|
@ -76,267 +77,84 @@ class Point:
|
|||
|
||||
|
||||
class Stitch(Point):
|
||||
|
||||
def __init__(self, x, y, color=None, jump_stitch=False):
|
||||
Point.__init__(self, x, y)
|
||||
def __init__(self, x, y, color=None, jump=False, stop=False, trim=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.color = color
|
||||
self.jump_stitch = jump_stitch
|
||||
self.jump = jump
|
||||
self.trim = trim
|
||||
self.stop = stop
|
||||
|
||||
def __repr__(self):
|
||||
return "Stitch(%s, %s, %s, %s, %s, %s)" % (self.x, self.y, self.color, "JUMP" if self.jump else "", "TRIM" if self.trim else "", "STOP" if self.stop else "")
|
||||
|
||||
class Embroidery:
|
||||
def make_thread(color):
|
||||
# strip off the leading "#"
|
||||
if color.startswith("#"):
|
||||
color = color[1:]
|
||||
|
||||
def __init__(self, stitches, pixels_per_millimeter=1):
|
||||
self.stitches = deepcopy(stitches)
|
||||
self.scale(1.0 / pixels_per_millimeter)
|
||||
self.scale((1, -1))
|
||||
self.translate_to_origin()
|
||||
thread = libembroidery.EmbThread()
|
||||
thread.color = libembroidery.embColor_fromHexStr(color)
|
||||
|
||||
def translate_to_origin(self):
|
||||
if (len(self.stitches) == 0):
|
||||
return
|
||||
(maxx, maxy) = (self.stitches[0].x, self.stitches[0].y)
|
||||
(minx, miny) = (self.stitches[0].x, self.stitches[0].y)
|
||||
for p in self.stitches:
|
||||
minx = min(minx, p.x)
|
||||
miny = min(miny, p.y)
|
||||
maxx = max(maxx, p.x)
|
||||
maxy = max(maxy, p.y)
|
||||
sx = maxx - minx
|
||||
sy = maxy - miny
|
||||
thread.description = color
|
||||
thread.catalogNumber = ""
|
||||
|
||||
self.translate(-minx, -miny)
|
||||
return (minx, miny)
|
||||
return thread
|
||||
|
||||
def translate(self, dx, dy):
|
||||
for p in self.stitches:
|
||||
p.x += dx
|
||||
p.y += dy
|
||||
def add_thread(pattern, thread):
|
||||
"""Add a thread to a pattern and return the thread's index"""
|
||||
|
||||
def scale(self, sc):
|
||||
if not isinstance(sc, (tuple, list)):
|
||||
sc = (sc, sc)
|
||||
for p in self.stitches:
|
||||
p.x *= sc[0]
|
||||
p.y *= sc[1]
|
||||
libembroidery.embPattern_addThread(pattern, thread)
|
||||
|
||||
def export_ksm(self):
|
||||
str = ""
|
||||
self.pos = Point(0, 0)
|
||||
lastColor = None
|
||||
for stitch in self.stitches:
|
||||
if (lastColor is not None and stitch.color != lastColor):
|
||||
mode_byte = 0x99
|
||||
# dbg.write("Color change!\n")
|
||||
return libembroidery.embThreadList_count(pattern.threadList) - 1
|
||||
|
||||
def get_flags(stitch):
|
||||
flags = 0
|
||||
|
||||
if stitch.jump:
|
||||
flags |= libembroidery.JUMP
|
||||
|
||||
if stitch.trim:
|
||||
flags |= libembroidery.TRIM
|
||||
|
||||
if stitch.stop:
|
||||
flags |= libembroidery.STOP
|
||||
|
||||
return flags
|
||||
|
||||
def write_embroidery_file(file_path, stitches):
|
||||
# Embroidery machines don't care about our canvas size, so we relocate the
|
||||
# design to the origin. It might make sense to center it about the origin
|
||||
# instead.
|
||||
min_x = min(stitch.x for stitch in stitches)
|
||||
min_y = min(stitch.y for stitch in stitches)
|
||||
|
||||
pattern = libembroidery.embPattern_create()
|
||||
threads = {}
|
||||
|
||||
last_color = None
|
||||
|
||||
for stitch in stitches:
|
||||
if stitch.color != last_color:
|
||||
if stitch.color not in threads:
|
||||
thread = make_thread(stitch.color)
|
||||
thread_index = add_thread(pattern, thread)
|
||||
threads[stitch.color] = thread_index
|
||||
else:
|
||||
mode_byte = 0x80
|
||||
# dbg.write("color still %s\n" % stitch.color)
|
||||
lastColor = stitch.color
|
||||
new_int = stitch.as_int()
|
||||
old_int = self.pos.as_int()
|
||||
delta = new_int - old_int
|
||||
assert(abs(delta.x) <= 127)
|
||||
assert(abs(delta.y) <= 127)
|
||||
str += chr(abs(delta.y))
|
||||
str += chr(abs(delta.x))
|
||||
if (delta.y < 0):
|
||||
mode_byte |= 0x20
|
||||
if (delta.x < 0):
|
||||
mode_byte |= 0x40
|
||||
str += chr(mode_byte)
|
||||
self.pos = stitch
|
||||
return str
|
||||
thread_index = threads[stitch.color]
|
||||
|
||||
def export_melco(self):
|
||||
self.str = ""
|
||||
self.pos = self.stitches[0]
|
||||
# dbg.write("stitch count: %d\n" % len(self.stitches))
|
||||
lastColor = None
|
||||
numColors = 0x0
|
||||
for stitch in self.stitches[1:]:
|
||||
if (lastColor is not None and stitch.color != lastColor):
|
||||
numColors += 1
|
||||
# color change
|
||||
self.str += chr(0x80)
|
||||
self.str += chr(0x01)
|
||||
# self.str += chr(numColors)
|
||||
# self.str += chr(((numColors+0x80)>>8)&0xff)
|
||||
# self.str += chr(((numColors+0x80)>>0)&0xff)
|
||||
lastColor = stitch.color
|
||||
new_int = stitch.as_int()
|
||||
old_int = self.pos.as_int()
|
||||
delta = new_int - old_int
|
||||
libembroidery.embPattern_changeColor(pattern, thread_index)
|
||||
last_color = stitch.color
|
||||
|
||||
def move(x, y):
|
||||
if (x < 0):
|
||||
x = x + 256
|
||||
self.str += chr(x)
|
||||
if (y < 0):
|
||||
y = y + 256
|
||||
self.str += chr(y)
|
||||
flags = get_flags(stitch)
|
||||
libembroidery.embPattern_addStitchAbs(pattern, stitch.x - min_x, stitch.y - min_y, flags, 0)
|
||||
|
||||
while (delta.x != 0 or delta.y != 0):
|
||||
def clamp(v):
|
||||
if (v > 127):
|
||||
v = 127
|
||||
if (v < -127):
|
||||
v = -127
|
||||
return v
|
||||
dx = clamp(delta.x)
|
||||
dy = clamp(delta.y)
|
||||
move(dx, dy)
|
||||
delta.x -= dx
|
||||
delta.y -= dy
|
||||
libembroidery.embPattern_addStitchAbs(pattern, stitch.x - min_x, stitch.y - min_y, libembroidery.END, 0)
|
||||
|
||||
# dbg.write("Stitch: %s delta %s\n" % (stitch, delta))
|
||||
self.pos = stitch
|
||||
return self.str
|
||||
# convert from pixels to millimeters
|
||||
libembroidery.embPattern_scale(pattern, 1/PIXELS_PER_MM)
|
||||
|
||||
def export_csv(self):
|
||||
self.str = ""
|
||||
self.str += '"#","[THREAD_NUMBER]","[RED]","[GREEN]","[BLUE]","[DESCRIPTION]","[CATALOG_NUMBER]"\n'
|
||||
self.str += '"#","[STITCH_TYPE]","[X]","[Y]"\n'
|
||||
# SVG and embroidery disagree on the direction of the Y axis
|
||||
libembroidery.embPattern_flipVertical(pattern)
|
||||
|
||||
lastStitch = None
|
||||
colorIndex = 0
|
||||
for stitch in self.stitches:
|
||||
if lastStitch is not None and stitch.color != lastStitch.color:
|
||||
self.str += '"*","COLOR","%f","%f"\n' % (lastStitch.x, lastStitch.y)
|
||||
if lastStitch is None or stitch.color != lastStitch.color:
|
||||
colorIndex += 1
|
||||
self.str += '"$","%d","%d","%d","%d","(null)","(null)"\n' % (
|
||||
colorIndex,
|
||||
int(stitch.color[1:3], 16),
|
||||
int(stitch.color[3:5], 16),
|
||||
int(stitch.color[5:7], 16))
|
||||
if stitch.jump_stitch:
|
||||
self.str += '"*","JUMP","%f","%f"\n' % (stitch.x, stitch.y)
|
||||
self.str += '"*","STITCH","%f","%f"\n' % (stitch.x, stitch.y)
|
||||
lastStitch = stitch
|
||||
self.str += '"*","END","%f","%f"\n' % (lastStitch.x, lastStitch.y)
|
||||
return self.str
|
||||
|
||||
def export_gcode(self):
|
||||
ret = []
|
||||
lastColor = None
|
||||
for stitch in self.stitches:
|
||||
if stitch.color != lastColor:
|
||||
ret.append('M0 ;MSG, Color change; prepare for %s\n' % stitch.color)
|
||||
lastColor = stitch.color
|
||||
ret.append('G1 X%f Y%f\n' % stitch.as_tuple())
|
||||
ret.append('M0 ;MSG, EMBROIDER stitch\n')
|
||||
return ''.join(ret)
|
||||
|
||||
def export(self, filename, format):
|
||||
fp = open(filename, "wb")
|
||||
|
||||
if format == "melco":
|
||||
fp.write(self.export_melco())
|
||||
elif format == "csv":
|
||||
fp.write(self.export_csv())
|
||||
elif format == "gcode":
|
||||
fp.write(self.export_gcode())
|
||||
fp.close()
|
||||
|
||||
|
||||
class Test:
|
||||
|
||||
def __init__(self):
|
||||
emb = Embroidery()
|
||||
for x in range(0, 301, 30):
|
||||
emb.addStitch(Point(x, 0))
|
||||
emb.addStitch(Point(x, 15))
|
||||
emb.addStitch(Point(x, 0))
|
||||
|
||||
for x in range(300, -1, -30):
|
||||
emb.addStitch(Point(x, -12))
|
||||
emb.addStitch(Point(x, -27))
|
||||
emb.addStitch(Point(x, -12))
|
||||
|
||||
fp = open("test.exp", "wb")
|
||||
fp.write(emb.export_melco())
|
||||
fp.close()
|
||||
|
||||
|
||||
class Turtle:
|
||||
|
||||
def __init__(self):
|
||||
self.emb = Embroidery()
|
||||
self.pos = Point(0.0, 0.0)
|
||||
self.dir = Point(1.0, 0.0)
|
||||
self.emb.addStitch(self.pos)
|
||||
|
||||
def forward(self, dist):
|
||||
self.pos = self.pos + self.dir.mul(dist)
|
||||
self.emb.addStitch(self.pos)
|
||||
|
||||
def turn(self, degreesccw):
|
||||
radcw = -degreesccw / 180.0 * 3.141592653589
|
||||
self.dir = Point(
|
||||
math.cos(radcw) * self.dir.x - math.sin(radcw) * self.dir.y,
|
||||
math.sin(radcw) * self.dir.x + math.cos(radcw) * self.dir.y)
|
||||
|
||||
def right(self, degreesccw):
|
||||
self.turn(degreesccw)
|
||||
|
||||
def left(self, degreesccw):
|
||||
self.turn(-degreesccw)
|
||||
|
||||
|
||||
class Koch(Turtle):
|
||||
|
||||
def __init__(self, depth):
|
||||
Turtle.__init__(self)
|
||||
|
||||
edgelen = 750.0
|
||||
for i in range(3):
|
||||
self.edge(depth, edgelen)
|
||||
self.turn(120.0)
|
||||
|
||||
fp = open("koch%d.exp" % depth, "wb")
|
||||
fp.write(self.emb.export_melco())
|
||||
fp.close()
|
||||
|
||||
def edge(self, depth, dist):
|
||||
if (depth == 0):
|
||||
self.forward(dist)
|
||||
else:
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
self.turn(-60.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
self.turn(120.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
self.turn(-60.0)
|
||||
self.edge(depth - 1, dist / 3.0)
|
||||
|
||||
|
||||
class Hilbert(Turtle):
|
||||
|
||||
def __init__(self, level):
|
||||
Turtle.__init__(self)
|
||||
|
||||
self.size = 10.0
|
||||
self.hilbert(level, 90.0)
|
||||
|
||||
fp = open("hilbert%d.exp" % level, "wb")
|
||||
fp.write(self.emb.export_melco())
|
||||
fp.close()
|
||||
|
||||
# http://en.wikipedia.org/wiki/Hilbert_curve#Python
|
||||
def hilbert(self, level, angle):
|
||||
if (level == 0):
|
||||
return
|
||||
self.right(angle)
|
||||
self.hilbert(level - 1, -angle)
|
||||
self.forward(self.size)
|
||||
self.left(angle)
|
||||
self.hilbert(level - 1, angle)
|
||||
self.forward(self.size)
|
||||
self.hilbert(level - 1, angle)
|
||||
self.left(angle)
|
||||
self.forward(self.size)
|
||||
self.hilbert(level - 1, -angle)
|
||||
self.right(angle)
|
||||
|
||||
if (__name__ == '__main__'):
|
||||
# Koch(4)
|
||||
Hilbert(6)
|
||||
libembroidery.embPattern_write(pattern, file_path)
|
||||
|
|
23
README.md
23
README.md
|
@ -47,15 +47,6 @@ pip install -r requirements.txt
|
|||
|
||||
I prefer to symbolically link into my git clone, which allows me to hack on the code. Changes to the Python code take effect the next time the extension is run. Changes to the extension description files (`*.inx`) take effect the next time Inkscape is restarted
|
||||
|
||||
### Optional: conversion program
|
||||
The extension can output machine embroidery design files directly in Melco format. I don't even know what that is, so I don't use it. I prefer to use the **CSV output format** which can be consumed by another awesome open source project: [Embroidermodder2](https://github.com/Embroidermodder/Embroidermodder). In theory, this project was going to be exactly what I wanted. In practice, it never got funded on Kickstarter and it's largely incomplete.
|
||||
|
||||
However, it contains a really awesome core library that knows pretty much every machine embroidery format and how to convert between them. I use it to convert the CSV files that ink/stitch outputs into the PES files that my SE400 uses.
|
||||
|
||||
Grab the source: `git clone https://github.com/Embroidermodder/Embroidermodder`. Build just `libembroidery-convert` using the instructions in "3)" in the [Embroidermodder build docs](https://github.com/Embroidermodder/Embroidermodder/wiki/Compiling-parts-of-the-project). You can then use it like this: `./libembroidery-convert your-file.csv your-file.pes`.
|
||||
|
||||
Since the CSV + libembroidery-convert method is the only method I use, it's the one I'll assume from here on. I'm not even sure if the other output formats from ink/stitch still work (or ever worked).
|
||||
|
||||
## Usage
|
||||
### Basic Usage
|
||||
First things first: I'm going to assume you know a few embroidery terms like "fill stitch" and "satin". Look those up if you're mentally 404ing, then come back here. I'm *not* going to assume you know some of the more advanced terms, because I had to learn all that when I started this project, so I might as well teach you too.
|
||||
|
@ -76,9 +67,7 @@ The stitching preview you're looking at just now isn't intended to be permanent.
|
|||
### Stitching Out the Design
|
||||
Where'd the design file go? One of the parameters you were able to specify in the filter settings dialog was the output directory. By default, the directory used is the place where you installed the extension's Python files. I output mine to `~/Documents/embroidery/output`.
|
||||
|
||||
ink/stitch will create a file named `something.csv`, where `something` is the name of your svg file (e.g. `something.svg`). If `something.csv` already existed, it will be renamed to `something.csv.1`, and `something.csv.1` will be renamed to `something.csv.2`, etc, up to 5 backup copies. When you've got the design the way you like it, save off a copy of `something.csv`.
|
||||
|
||||
Next, convert it to your machine's format using `libembroidery-convert` (as described above). Send it to your machine in whatever way one does that for your machine, and try stitching it out!
|
||||
ink/stitch will create a file named `something.___`, where `something` is the name of your svg file (e.g. `something.svg`) and `___` is the proper extension for the output format you select. If `something.___` already exists, it will be renamed to `something.___.1`, and `something.___.1` will be renamed to `something.___.2`, etc, up to 5 backup copies. When you've got the design the way you like it, save off a copy of `something.___`.
|
||||
|
||||
### Ordering
|
||||
|
||||
|
@ -278,15 +267,15 @@ To solve this, I created the *Reorder* extension. To use it, hold down the shif
|
|||
|
||||
You can also manually manipulate the underlying SVG XML structure by using Inkscape's XML Editor pane. Its "Raise" and "Lower" buttons directly manipulate the order of XML tags in the SVG file and are not subject to the same limitations as PageUp and PageDown. Note that the ordering of XML tags in the XML Editor tool is the _reverse_ of the order of objects in the Objects tool.
|
||||
|
||||
### Step 4: Render to CSV
|
||||
### Step 4: Render to a file format supported by your machine
|
||||
|
||||
Once I've got everything in the right order, I deselect all objects and run *Embroider* again. This will embroider all visible objects in the document. As described in the Setup section above, I render my embroidery file in CSV format and convert it with EmbroiderModder's `libembroidery-convert` utility.
|
||||
Once I've got everything in the right order, I deselect all objects and run *Embroider* again. This will embroider all visible objects in the document. In the extension settings, select a file format supported by your machine. Most machines can support DST, and some Brother machines prefer PES.
|
||||
|
||||
*Embroider* will create a file in the specified output directory named after your SVG file, but with the extension changed to `.csv`. It will back up any existing file there, storing up to 5 old copies of each file.
|
||||
*Embroider* will create a file in the specified output directory named after your SVG file, but with the extension changed to `.DST`, `.PES`, or whatever format you selected. It will back up any existing file there, storing up to 5 old copies of each file.
|
||||
|
||||
### Step 5: Convert to PES and upload
|
||||
|
||||
My sewing machine uses the PES format, so I convert the CSV file into a .PES and send it over to my sewing machine. My Brother SE400 acts like a (very small!) USB flash drive. I use a [script](bin/embroider-remote) to do the CSV and upload steps all at once.
|
||||
Transfer the design to your machine in whatever manner is appropriate. My machine exposes itself as a (tiny) USB thumb drive, so I can upload directly.
|
||||
|
||||
### Step 6: Test-sew
|
||||
|
||||
|
@ -296,5 +285,5 @@ I sew out the design, watching the machine to make sure that there aren't any su
|
|||
|
||||
### Step 7+: iterate
|
||||
|
||||
Then I go back and tweak my design. Hopefully it only takes a few tries to get it how I want it. Once I'm done, I copy the CSV file from my output directory, just to avoid accidentally overwriting it in the future.
|
||||
Then I go back and tweak my design. Hopefully it only takes a few tries to get it how I want it. Once I'm done, I copy the final embroidery file from my output directory, just to avoid accidentally overwriting it in the future.
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ pyinstaller_args+="--hidden-import gi.repository.Gtk "
|
|||
# This lets pyinstaller see inkex.py, etc.
|
||||
pyinstaller_args+="-p /usr/share/inkscape/extensions "
|
||||
|
||||
# for libembroidery
|
||||
pyinstaller_args+="-p embroidermodder/experimental/python/binding "
|
||||
|
||||
mkdir -p dist/inkstitch/bin
|
||||
for extension in "$@"; do
|
||||
# without the LD_LIBRARY_PATH, it seems that pyinstaller can't find all of
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# /etc/fstab entry: /dev/disk/by-id/usb-B-EMB_USB_RAM_Disk_INST_0-0:0-part1 /mnt/embroidery vfat user,uid=1000,gid=1000,nobootwait,noauto 0 0
|
||||
|
||||
|
||||
set -e
|
||||
|
||||
if [[ "$1" == *.csv ]]; then
|
||||
pes=$(mktemp /tmp/XXXXXXXXXXX.pes)
|
||||
libembroidery-convert "$1" "$pes"
|
||||
file="$pes"
|
||||
else
|
||||
file="$1"
|
||||
fi
|
||||
|
||||
(
|
||||
mount /mnt/embroidery && \
|
||||
rm -f /mnt/embroidery/* && \
|
||||
cp $file /mnt/embroidery/embroidery.pes && \
|
||||
umount /mnt/embroidery
|
||||
) || echo '(local) failed to upload embroidery :('
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This tool converts a .CSV file to a .PES file and uploads it to my embroidery
|
||||
# machine, which is connected to my home server. This way, I can embroider from my
|
||||
# laptop over my wifi without having to connect the sewing machine to my laptop every
|
||||
# time.
|
||||
|
||||
# /etc/fstab entry: /dev/disk/by-id/usb-B-EMB_USB_RAM_Disk_INST_0-0:0-part1 /mnt/embroidery vfat user,uid=1000,gid=1000,nobootwait,noauto 0 0
|
||||
|
||||
HOST=myhomeserver.local
|
||||
|
||||
set -e
|
||||
|
||||
if [[ "$1" == *.csv ]]; then
|
||||
pes=$(mktemp /tmp/XXXXXXXXXXX.pes)
|
||||
libembroidery-convert "$1" "$pes"
|
||||
file="$pes"
|
||||
else
|
||||
file="$1"
|
||||
fi
|
||||
|
||||
cat "$file" | ssh $HOST "
|
||||
mount /mnt/embroidery &&
|
||||
rm -f /mnt/embroidery/* &&
|
||||
cat > /mnt/embroidery/embroidery.pes &&
|
||||
umount /mnt/embroidery" \
|
||||
|| echo 'failed to upload embroidery :('
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.append('embroidermodder/experimental/python/binding')
|
||||
from libembroidery import *
|
||||
|
||||
formatList = embFormatList_create()
|
||||
curFormat = formatList
|
||||
while(curFormat):
|
||||
extension = embFormat_extension(curFormat)
|
||||
description = embFormat_description(curFormat)
|
||||
writerState = embFormat_writerState(curFormat)
|
||||
|
||||
if writerState.strip() and embFormat_type(curFormat) != EMBFORMAT_OBJECTONLY:
|
||||
print '<_option value="%s">%s(%s)</_option>' % (extension[1:], description, extension.upper())
|
||||
|
||||
curFormat = curFormat.next
|
||||
|
|
@ -11,9 +11,29 @@
|
|||
<param name="collapse_len_mm" type="float" min="0.0" max="10.0" _gui-text="Maximum collapse length (mm)">0.0</param>
|
||||
<param name="hide_layers" type="boolean" _gui-text="Hide other layers" description="Hide all other top-level layers when the embroidery layer is generated, in order to make stitching discernable.">true</param>
|
||||
<param name="output_format" type="optiongroup" _gui-text="Output file format" appearance="minimal">
|
||||
<_option value="melco">Melco</_option>
|
||||
<_option value="csv">Embroidermodder 2 CSV</_option>
|
||||
<_option value="gcode">Franklin G-Code</_option>
|
||||
<_option value="csv">Comma Separated Values Format(.CSV)</_option>
|
||||
<_option value="col">Embroidery Thread Color Format(.COL)</_option>
|
||||
<_option value="dst">Tajima Embroidery Format(.DST)</_option>
|
||||
<_option value="edr">Embird Embroidery Format(.EDR)</_option>
|
||||
<_option value="exp">Melco Embroidery Format(.EXP)</_option>
|
||||
<_option value="hus">Husqvarna Viking Embroidery Format(.HUS)</_option>
|
||||
<_option value="inf">Embroidery Color Format(.INF)</_option>
|
||||
<_option value="jef">Janome Embroidery Format(.JEF)</_option>
|
||||
<_option value="ksm">Pfaff Embroidery Format(.KSM)</_option>
|
||||
<_option value="max">Pfaff Embroidery Format(.MAX)</_option>
|
||||
<_option value="pcd">Pfaff Embroidery Format(.PCD)</_option>
|
||||
<_option value="pcq">Pfaff Embroidery Format(.PCQ)</_option>
|
||||
<_option value="pcs">Pfaff Embroidery Format(.PCS)</_option>
|
||||
<_option value="pec">Brother Embroidery Format(.PEC)</_option>
|
||||
<_option value="pes">Brother Embroidery Format(.PES)</_option>
|
||||
<_option value="plt">AutoCAD Plot Drawing Format(.PLT)</_option>
|
||||
<_option value="rgb">RGB Embroidery Format(.RGB)</_option>
|
||||
<_option value="sew">Janome Embroidery Format(.SEW)</_option>
|
||||
<_option value="tap">Happy Embroidery Format(.TAP)</_option>
|
||||
<_option value="thr">ThredWorks Embroidery Format(.THR)</_option>
|
||||
<_option value="txt">Text File(.TXT)</_option>
|
||||
<_option value="vp3">Pfaff Embroidery Format(.VP3)</_option>
|
||||
<_option value="xxx">Singer Embroidery Format(.XXX)</_option>
|
||||
</param>
|
||||
<param name="path" type="string" _gui-text="Directory"></param>
|
||||
<effect>
|
||||
|
|
143
embroider.py
143
embroider.py
|
@ -51,7 +51,7 @@ EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
|
|||
PIXELS_PER_MM = 96 / 25.4
|
||||
|
||||
class Param(object):
|
||||
def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None):
|
||||
def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None, tooltip=None, sort_index=0):
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.unit = unit
|
||||
|
@ -60,6 +60,8 @@ class Param(object):
|
|||
self.group = group
|
||||
self.inverse = inverse
|
||||
self.default = default
|
||||
self.tooltip = tooltip
|
||||
self.sort_index = sort_index
|
||||
|
||||
def __repr__(self):
|
||||
return "Param(%s)" % vars(self)
|
||||
|
@ -310,8 +312,37 @@ class EmbroideryElement(object):
|
|||
|
||||
return flattened
|
||||
|
||||
@property
|
||||
@param('trim_after',
|
||||
'TRIM after',
|
||||
tooltip='Trim thread after this object (for supported machines and file formats)',
|
||||
type='boolean',
|
||||
default=False,
|
||||
sort_index=1000)
|
||||
def trim_after(self):
|
||||
return self.get_boolean_param('trim_after', False)
|
||||
|
||||
@property
|
||||
@param('stop_after',
|
||||
'STOP after',
|
||||
tooltip='Add STOP instruction after this object (for supported machines and file formats)',
|
||||
type='boolean',
|
||||
default=False,
|
||||
sort_index=1000)
|
||||
def stop_after(self):
|
||||
return self.get_boolean_param('stop_after', False)
|
||||
|
||||
def to_patches(self, last_patch):
|
||||
raise NotImplementedError("%s must implement to_path()" % self.__class__.__name__)
|
||||
raise NotImplementedError("%s must implement to_patches()" % self.__class__.__name__)
|
||||
|
||||
def embroider(self, last_patch):
|
||||
patches = self.to_patches(last_patch)
|
||||
|
||||
if patches:
|
||||
patches[-1].trim_after = self.trim_after
|
||||
patches[-1].stop_after = self.stop_after
|
||||
|
||||
return patches
|
||||
|
||||
def fatal(self, message):
|
||||
print >> sys.stderr, "error:", message
|
||||
|
@ -1196,7 +1227,7 @@ class Stroke(EmbroideryElement):
|
|||
return self.get_float_param("zigzag_spacing_mm")
|
||||
|
||||
@property
|
||||
@param('repeats', 'Repeats', type='int')
|
||||
@param('repeats', 'Repeats', type='int', default="1")
|
||||
def repeats(self):
|
||||
return self.get_int_param("repeats", 1)
|
||||
|
||||
|
@ -1762,6 +1793,7 @@ def detect_classes(node):
|
|||
|
||||
|
||||
def descendants(node):
|
||||
|
||||
nodes = []
|
||||
element = EmbroideryElement(node)
|
||||
|
||||
|
@ -1780,9 +1812,11 @@ def descendants(node):
|
|||
return nodes
|
||||
|
||||
class Patch:
|
||||
def __init__(self, color=None, stitches=None):
|
||||
def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False):
|
||||
self.color = color
|
||||
self.stitches = stitches or []
|
||||
self.trim_after = trim_after
|
||||
self.stop_after = stop_after
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, Patch):
|
||||
|
@ -1797,12 +1831,64 @@ class Patch:
|
|||
return Patch(self.color, self.stitches[::-1])
|
||||
|
||||
|
||||
def process_stop_after(stitches):
|
||||
# The user wants the machine to pause after this patch. This can
|
||||
# be useful for applique and similar on multi-needle machines that
|
||||
# normally would not stop between colors.
|
||||
#
|
||||
# On such machines, the user assigns needles to the colors in the
|
||||
# design before starting stitching. C01, C02, etc are normal
|
||||
# needles, but C00 is special. For a block of stitches assigned
|
||||
# to C00, the machine will continue sewing with the last color it
|
||||
# had and pause after it completes the C00 block.
|
||||
#
|
||||
# That means we need to introduce an artificial color change
|
||||
# shortly before the current stitch so that the user can set that
|
||||
# to C00. We'll go back 3 stitches and do that:
|
||||
|
||||
if len(stitches) >= 3:
|
||||
stitches[-3].stop = True
|
||||
|
||||
# and also add a color change on this stitch, completing the C00
|
||||
# block:
|
||||
|
||||
stitches[-1].stop = True
|
||||
|
||||
# reference for the above: https://github.com/lexelby/inkstitch/pull/29#issuecomment-359175447
|
||||
|
||||
|
||||
def process_trim(stitches, next_stitch):
|
||||
# DST (and maybe other formats?) has no actual TRIM instruction.
|
||||
# Instead, 3 sequential JUMPs cause the machine to trim the thread.
|
||||
#
|
||||
# To support both DST and other formats, we'll add a TRIM and two
|
||||
# JUMPs. The TRIM will be converted to a JUMP by libembroidery
|
||||
# if saving to DST, resulting in the 3-jump sequence.
|
||||
|
||||
delta = next_stitch - stitches[-1]
|
||||
delta = delta * (1/4.0)
|
||||
|
||||
pos = stitches[-1]
|
||||
|
||||
for i in xrange(3):
|
||||
pos += delta
|
||||
stitches.append(PyEmb.Stitch(pos.x, pos.y, stitches[-1].color, jump=True))
|
||||
|
||||
# first one should be TRIM instead of JUMP
|
||||
stitches[-3].jump = False
|
||||
stitches[-3].trim = True
|
||||
|
||||
|
||||
def patches_to_stitches(patch_list, collapse_len_px=0):
|
||||
stitches = []
|
||||
|
||||
last_stitch = None
|
||||
last_color = None
|
||||
need_trim = False
|
||||
for patch in patch_list:
|
||||
if not patch.stitches:
|
||||
continue
|
||||
|
||||
jump_stitch = True
|
||||
for stitch in patch.stitches:
|
||||
if last_stitch and last_color == patch.color:
|
||||
|
@ -1818,32 +1904,47 @@ def patches_to_stitches(patch_list, collapse_len_px=0):
|
|||
# dbg.write("... collapsed\n")
|
||||
jump_stitch = False
|
||||
|
||||
# dbg.write("stitch color %s\n" % patch.color)
|
||||
if stitches and last_color and last_color != patch.color:
|
||||
# add a color change
|
||||
stitches.append(PyEmb.Stitch(last_stitch.x, last_stitch.y, last_color, stop=True))
|
||||
|
||||
newStitch = PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump_stitch)
|
||||
stitches.append(newStitch)
|
||||
if need_trim:
|
||||
process_trim(stitches, stitch)
|
||||
need_trim = False
|
||||
|
||||
if jump_stitch:
|
||||
stitches.append(PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump=True))
|
||||
|
||||
stitches.append(PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump=False))
|
||||
|
||||
jump_stitch = False
|
||||
last_stitch = stitch
|
||||
last_color = patch.color
|
||||
|
||||
if patch.trim_after:
|
||||
need_trim = True
|
||||
|
||||
if patch.stop_after:
|
||||
process_stop_after(stitches)
|
||||
|
||||
return stitches
|
||||
|
||||
def stitches_to_polylines(stitches):
|
||||
polylines = []
|
||||
last_color = None
|
||||
last_stitch = None
|
||||
trimming = False
|
||||
|
||||
for stitch in stitches:
|
||||
#if stitch.jump_stitch:
|
||||
#if last_color == stitch.color:
|
||||
# polylines.append([None, [last_stitch.as_tuple(), stitch.as_tuple()]])
|
||||
|
||||
# last_color = None
|
||||
|
||||
if stitch.color != last_color:
|
||||
if stitch.color != last_color or stitch.trim:
|
||||
trimming = True
|
||||
polylines.append([stitch.color, []])
|
||||
|
||||
if trimming and (stitch.jump or stitch.trim):
|
||||
continue
|
||||
|
||||
trimming = False
|
||||
|
||||
polylines[-1][1].append(stitch.as_tuple())
|
||||
|
||||
last_color = stitch.color
|
||||
|
@ -1905,10 +2006,9 @@ class Embroider(inkex.Effect):
|
|||
dest="hide_layers", default="true",
|
||||
help="Hide all other layers when the embroidery layer is generated")
|
||||
self.OptionParser.add_option("-O", "--output_format",
|
||||
action="store", type="choice",
|
||||
choices=["melco", "csv", "gcode"],
|
||||
dest="output_format", default="melco",
|
||||
help="File output format")
|
||||
action="store", type="string",
|
||||
dest="output_format", default="csv",
|
||||
help="Output file extenstion (default: csv)")
|
||||
self.OptionParser.add_option("-P", "--path",
|
||||
action="store", type="string",
|
||||
dest="path", default=".",
|
||||
|
@ -1936,7 +2036,7 @@ class Embroider(inkex.Effect):
|
|||
output_path = os.path.join(self.options.path, self.options.output_file)
|
||||
else:
|
||||
svg_filename = self.document.getroot().get(inkex.addNS('docname', 'sodipodi'), "embroidery.svg")
|
||||
csv_filename = svg_filename.replace('.svg', '.csv')
|
||||
csv_filename = svg_filename.replace('.svg', '.%s' % self.options.output_format)
|
||||
output_path = os.path.join(self.options.path, csv_filename)
|
||||
|
||||
def add_suffix(path, suffix):
|
||||
|
@ -2008,11 +2108,10 @@ class Embroider(inkex.Effect):
|
|||
else:
|
||||
last_patch = None
|
||||
|
||||
patches.extend(element.to_patches(last_patch))
|
||||
patches.extend(element.embroider(last_patch))
|
||||
|
||||
stitches = patches_to_stitches(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
|
||||
emb = PyEmb.Embroidery(stitches, PIXELS_PER_MM)
|
||||
emb.export(self.get_output_path(), self.options.output_format)
|
||||
PyEmb.write_embroidery_file(self.get_output_path(), stitches)
|
||||
|
||||
new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {})
|
||||
new_layer.set('id', self.uniqueId("embroidery"))
|
||||
|
|
|
@ -135,7 +135,10 @@ class ParamsTab(ScrolledPanel):
|
|||
return self.parent_tab is not None
|
||||
|
||||
def enabled(self):
|
||||
if self.toggle_checkbox:
|
||||
return self.toggle_checkbox.IsChecked()
|
||||
else:
|
||||
return True
|
||||
|
||||
def update_toggle_state(self, event=None, notify_pair=True):
|
||||
enable = self.enabled()
|
||||
|
@ -160,7 +163,6 @@ class ParamsTab(ScrolledPanel):
|
|||
|
||||
if self.enabled() != new_value:
|
||||
self.set_toggle_state(not value)
|
||||
self.toggle_checkbox.changed = True
|
||||
self.update_toggle_state(notify_pair=False)
|
||||
|
||||
def dependent_enable(self, enable):
|
||||
|
@ -169,11 +171,12 @@ class ParamsTab(ScrolledPanel):
|
|||
else:
|
||||
self.set_toggle_state(False)
|
||||
self.toggle_checkbox.Disable()
|
||||
self.toggle_checkbox.changed = True
|
||||
self.update_toggle_state()
|
||||
|
||||
def set_toggle_state(self, value):
|
||||
if self.toggle_checkbox:
|
||||
self.toggle_checkbox.SetValue(value)
|
||||
self.changed_inputs.add(self.toggle_checkbox)
|
||||
|
||||
def get_values(self):
|
||||
values = {}
|
||||
|
@ -189,7 +192,7 @@ class ParamsTab(ScrolledPanel):
|
|||
return values
|
||||
|
||||
for name, input in self.param_inputs.iteritems():
|
||||
if input in self.changed_inputs:
|
||||
if input in self.changed_inputs and input != self.toggle_checkbox:
|
||||
values[name] = input.GetValue()
|
||||
|
||||
return values
|
||||
|
@ -197,7 +200,7 @@ class ParamsTab(ScrolledPanel):
|
|||
def apply(self):
|
||||
values = self.get_values()
|
||||
for node in self.nodes:
|
||||
#print >> sys.stderr, node.id, values
|
||||
# print >> sys.stderr, "apply: ", self.name, node.id, values
|
||||
for name, value in values.iteritems():
|
||||
node.set_param(name, value)
|
||||
|
||||
|
@ -282,7 +285,10 @@ class ParamsTab(ScrolledPanel):
|
|||
box.Add(self.toggle_checkbox, proportion=0, flag=wx.BOTTOM, border=10)
|
||||
|
||||
for param in self.params:
|
||||
self.settings_grid.Add(wx.StaticText(self, label=param.description), proportion=1, flag=wx.EXPAND|wx.RIGHT, border=40)
|
||||
description = wx.StaticText(self, label=param.description)
|
||||
description.SetToolTip(param.tooltip)
|
||||
|
||||
self.settings_grid.Add(description, proportion=1, flag=wx.EXPAND|wx.RIGHT, border=40)
|
||||
|
||||
if param.type == 'boolean':
|
||||
|
||||
|
@ -456,7 +462,7 @@ class SettingsFrame(wx.Frame):
|
|||
# way to drop the cache in the @cache decorators used
|
||||
# for many params in embroider.py.
|
||||
|
||||
patches.extend(copy(node).to_patches(None))
|
||||
patches.extend(copy(node).embroider(None))
|
||||
except SystemExit:
|
||||
raise
|
||||
except:
|
||||
|
@ -670,10 +676,13 @@ class EmbroiderParams(inkex.Effect):
|
|||
return values
|
||||
|
||||
def group_params(self, params):
|
||||
def by_group_and_sort_index(param):
|
||||
return param.group, param.sort_index
|
||||
|
||||
def by_group(param):
|
||||
return param.group
|
||||
|
||||
return groupby(sorted(params, key=by_group), by_group)
|
||||
return groupby(sorted(params, key=by_group_and_sort_index), by_group)
|
||||
|
||||
def create_tabs(self, parent):
|
||||
tabs = []
|
||||
|
|
|
@ -149,6 +149,7 @@ class EmbroiderySimulator(wx.Frame):
|
|||
return segments
|
||||
|
||||
def _parse_stitch_file(self, stitch_file_path):
|
||||
# "#", "comment"
|
||||
# "$","1","229","229","229","(null)","(null)"
|
||||
# "*","JUMP","1.595898","48.731899"
|
||||
# "*","STITCH","1.595898","48.731899"
|
||||
|
@ -161,6 +162,10 @@ class EmbroiderySimulator(wx.Frame):
|
|||
|
||||
with open(stitch_file_path) as stitch_file:
|
||||
for line in stitch_file:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
fields = line.strip().split(",")
|
||||
fields = [self._strip_quotes(field) for field in fields]
|
||||
|
||||
|
@ -180,6 +185,10 @@ class EmbroiderySimulator(wx.Frame):
|
|||
x, y = fields[2:]
|
||||
new_pos = (float(x) * PIXELS_PER_MM, float(y) * PIXELS_PER_MM)
|
||||
|
||||
if not segments and new_pos == (0.0, 0.0):
|
||||
# libembroidery likes to throw an extra JUMP in at the start
|
||||
continue
|
||||
|
||||
if not cut:
|
||||
segments.append(((pos, new_pos), pen))
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 406d261a1a2a3d046c77aaef2161e67e00672f39
|
Ładowanie…
Reference in New Issue