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 layer
pull/38/head v1.1.0
Lex Neva 2018-01-23 20:13:37 -05:00 zatwierdzone przez GitHub
rodzic 462bf0bdbe
commit acaebaa956
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
13 zmienionych plików z 279 dodań i 349 usunięć

3
.gitmodules vendored 100644
Wyświetl plik

@ -0,0 +1,3 @@
[submodule "embroidermodder"]
path = embroidermodder
url = https://github.com/Embroidermodder/Embroidermodder

Wyświetl plik

@ -34,6 +34,9 @@ install:
# for wxPython # for wxPython
sudo apt-get install glib-networking 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 # This is the same as the pypi module PyGObject. We can't just do
# "pip install PyGObject" because it depends on a version of # "pip install PyGObject" because it depends on a version of
# libgirepository1.0-dev that doesn't exist in Trusty. # 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 --select=E901,E999,F821,F822,F823 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
elif [ -n "$BUILD" ]; then elif [ -n "$BUILD" ]; then
(
cd embroidermodder/experimental
qmake swigpython.pro
make
)
make dist make dist
else else
true true

318
PyEmb.py
Wyświetl plik

@ -2,8 +2,9 @@
# http://www.achatina.de/sewing/main/TECHNICL.HTM # http://www.achatina.de/sewing/main/TECHNICL.HTM
import math import math
import sys import libembroidery
from copy import deepcopy
PIXELS_PER_MM = 96 / 25.4
try: try:
from functools import lru_cache from functools import lru_cache
@ -76,267 +77,84 @@ class Point:
class Stitch(Point): class Stitch(Point):
def __init__(self, x, y, color=None, jump=False, stop=False, trim=False):
def __init__(self, x, y, color=None, jump_stitch=False): self.x = x
Point.__init__(self, x, y) self.y = y
self.color = color 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): thread = libembroidery.EmbThread()
self.stitches = deepcopy(stitches) thread.color = libembroidery.embColor_fromHexStr(color)
self.scale(1.0 / pixels_per_millimeter)
self.scale((1, -1))
self.translate_to_origin()
def translate_to_origin(self): thread.description = color
if (len(self.stitches) == 0): thread.catalogNumber = ""
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
self.translate(-minx, -miny) return thread
return (minx, miny)
def translate(self, dx, dy): def add_thread(pattern, thread):
for p in self.stitches: """Add a thread to a pattern and return the thread's index"""
p.x += dx
p.y += dy
def scale(self, sc): libembroidery.embPattern_addThread(pattern, thread)
if not isinstance(sc, (tuple, list)):
sc = (sc, sc)
for p in self.stitches:
p.x *= sc[0]
p.y *= sc[1]
def export_ksm(self): return libembroidery.embThreadList_count(pattern.threadList) - 1
str = ""
self.pos = Point(0, 0) def get_flags(stitch):
lastColor = None flags = 0
for stitch in self.stitches:
if (lastColor is not None and stitch.color != lastColor): if stitch.jump:
mode_byte = 0x99 flags |= libembroidery.JUMP
# dbg.write("Color change!\n")
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: else:
mode_byte = 0x80 thread_index = threads[stitch.color]
# 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
def export_melco(self): libembroidery.embPattern_changeColor(pattern, thread_index)
self.str = "" last_color = stitch.color
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
def move(x, y): flags = get_flags(stitch)
if (x < 0): libembroidery.embPattern_addStitchAbs(pattern, stitch.x - min_x, stitch.y - min_y, flags, 0)
x = x + 256
self.str += chr(x)
if (y < 0):
y = y + 256
self.str += chr(y)
while (delta.x != 0 or delta.y != 0): libembroidery.embPattern_addStitchAbs(pattern, stitch.x - min_x, stitch.y - min_y, libembroidery.END, 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
# dbg.write("Stitch: %s delta %s\n" % (stitch, delta)) # convert from pixels to millimeters
self.pos = stitch libembroidery.embPattern_scale(pattern, 1/PIXELS_PER_MM)
return self.str
def export_csv(self): # SVG and embroidery disagree on the direction of the Y axis
self.str = "" libembroidery.embPattern_flipVertical(pattern)
self.str += '"#","[THREAD_NUMBER]","[RED]","[GREEN]","[BLUE]","[DESCRIPTION]","[CATALOG_NUMBER]"\n'
self.str += '"#","[STITCH_TYPE]","[X]","[Y]"\n'
lastStitch = None libembroidery.embPattern_write(pattern, file_path)
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)

Wyświetl plik

@ -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 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 ## Usage
### Basic 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. 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 ### 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`. 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`. 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.___`.
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!
### Ordering ### 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. 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 ### 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 ### 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 ### 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.

Wyświetl plik

@ -18,6 +18,9 @@ pyinstaller_args+="--hidden-import gi.repository.Gtk "
# This lets pyinstaller see inkex.py, etc. # This lets pyinstaller see inkex.py, etc.
pyinstaller_args+="-p /usr/share/inkscape/extensions " pyinstaller_args+="-p /usr/share/inkscape/extensions "
# for libembroidery
pyinstaller_args+="-p embroidermodder/experimental/python/binding "
mkdir -p dist/inkstitch/bin mkdir -p dist/inkstitch/bin
for extension in "$@"; do for extension in "$@"; do
# without the LD_LIBRARY_PATH, it seems that pyinstaller can't find all of # without the LD_LIBRARY_PATH, it seems that pyinstaller can't find all of

Wyświetl plik

@ -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 :('

Wyświetl plik

@ -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 :('

Wyświetl plik

@ -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

Wyświetl plik

@ -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="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="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"> <param name="output_format" type="optiongroup" _gui-text="Output file format" appearance="minimal">
<_option value="melco">Melco</_option> <_option value="csv">Comma Separated Values Format(.CSV)</_option>
<_option value="csv">Embroidermodder 2 CSV</_option> <_option value="col">Embroidery Thread Color Format(.COL)</_option>
<_option value="gcode">Franklin G-Code</_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>
<param name="path" type="string" _gui-text="Directory"></param> <param name="path" type="string" _gui-text="Directory"></param>
<effect> <effect>

Wyświetl plik

@ -51,7 +51,7 @@ EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
PIXELS_PER_MM = 96 / 25.4 PIXELS_PER_MM = 96 / 25.4
class Param(object): 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.name = name
self.description = description self.description = description
self.unit = unit self.unit = unit
@ -60,6 +60,8 @@ class Param(object):
self.group = group self.group = group
self.inverse = inverse self.inverse = inverse
self.default = default self.default = default
self.tooltip = tooltip
self.sort_index = sort_index
def __repr__(self): def __repr__(self):
return "Param(%s)" % vars(self) return "Param(%s)" % vars(self)
@ -310,8 +312,37 @@ class EmbroideryElement(object):
return flattened 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): 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): def fatal(self, message):
print >> sys.stderr, "error:", message print >> sys.stderr, "error:", message
@ -1196,7 +1227,7 @@ class Stroke(EmbroideryElement):
return self.get_float_param("zigzag_spacing_mm") return self.get_float_param("zigzag_spacing_mm")
@property @property
@param('repeats', 'Repeats', type='int') @param('repeats', 'Repeats', type='int', default="1")
def repeats(self): def repeats(self):
return self.get_int_param("repeats", 1) return self.get_int_param("repeats", 1)
@ -1762,6 +1793,7 @@ def detect_classes(node):
def descendants(node): def descendants(node):
nodes = [] nodes = []
element = EmbroideryElement(node) element = EmbroideryElement(node)
@ -1780,9 +1812,11 @@ def descendants(node):
return nodes return nodes
class Patch: 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.color = color
self.stitches = stitches or [] self.stitches = stitches or []
self.trim_after = trim_after
self.stop_after = stop_after
def __add__(self, other): def __add__(self, other):
if isinstance(other, Patch): if isinstance(other, Patch):
@ -1797,12 +1831,64 @@ class Patch:
return Patch(self.color, self.stitches[::-1]) 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): def patches_to_stitches(patch_list, collapse_len_px=0):
stitches = [] stitches = []
last_stitch = None last_stitch = None
last_color = None last_color = None
need_trim = False
for patch in patch_list: for patch in patch_list:
if not patch.stitches:
continue
jump_stitch = True jump_stitch = True
for stitch in patch.stitches: for stitch in patch.stitches:
if last_stitch and last_color == patch.color: 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") # dbg.write("... collapsed\n")
jump_stitch = False 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) if need_trim:
stitches.append(newStitch) 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 jump_stitch = False
last_stitch = stitch last_stitch = stitch
last_color = patch.color last_color = patch.color
if patch.trim_after:
need_trim = True
if patch.stop_after:
process_stop_after(stitches)
return stitches return stitches
def stitches_to_polylines(stitches): def stitches_to_polylines(stitches):
polylines = [] polylines = []
last_color = None last_color = None
last_stitch = None last_stitch = None
trimming = False
for stitch in stitches: for stitch in stitches:
#if stitch.jump_stitch: if stitch.color != last_color or stitch.trim:
#if last_color == stitch.color: trimming = True
# polylines.append([None, [last_stitch.as_tuple(), stitch.as_tuple()]])
# last_color = None
if stitch.color != last_color:
polylines.append([stitch.color, []]) polylines.append([stitch.color, []])
if trimming and (stitch.jump or stitch.trim):
continue
trimming = False
polylines[-1][1].append(stitch.as_tuple()) polylines[-1][1].append(stitch.as_tuple())
last_color = stitch.color last_color = stitch.color
@ -1905,10 +2006,9 @@ class Embroider(inkex.Effect):
dest="hide_layers", default="true", dest="hide_layers", default="true",
help="Hide all other layers when the embroidery layer is generated") help="Hide all other layers when the embroidery layer is generated")
self.OptionParser.add_option("-O", "--output_format", self.OptionParser.add_option("-O", "--output_format",
action="store", type="choice", action="store", type="string",
choices=["melco", "csv", "gcode"], dest="output_format", default="csv",
dest="output_format", default="melco", help="Output file extenstion (default: csv)")
help="File output format")
self.OptionParser.add_option("-P", "--path", self.OptionParser.add_option("-P", "--path",
action="store", type="string", action="store", type="string",
dest="path", default=".", dest="path", default=".",
@ -1936,7 +2036,7 @@ class Embroider(inkex.Effect):
output_path = os.path.join(self.options.path, self.options.output_file) output_path = os.path.join(self.options.path, self.options.output_file)
else: else:
svg_filename = self.document.getroot().get(inkex.addNS('docname', 'sodipodi'), "embroidery.svg") 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) output_path = os.path.join(self.options.path, csv_filename)
def add_suffix(path, suffix): def add_suffix(path, suffix):
@ -2008,11 +2108,10 @@ class Embroider(inkex.Effect):
else: else:
last_patch = None 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) stitches = patches_to_stitches(patches, self.options.collapse_length_mm * PIXELS_PER_MM)
emb = PyEmb.Embroidery(stitches, PIXELS_PER_MM) PyEmb.write_embroidery_file(self.get_output_path(), stitches)
emb.export(self.get_output_path(), self.options.output_format)
new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {}) new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {})
new_layer.set('id', self.uniqueId("embroidery")) new_layer.set('id', self.uniqueId("embroidery"))

Wyświetl plik

@ -135,7 +135,10 @@ class ParamsTab(ScrolledPanel):
return self.parent_tab is not None return self.parent_tab is not None
def enabled(self): def enabled(self):
return self.toggle_checkbox.IsChecked() if self.toggle_checkbox:
return self.toggle_checkbox.IsChecked()
else:
return True
def update_toggle_state(self, event=None, notify_pair=True): def update_toggle_state(self, event=None, notify_pair=True):
enable = self.enabled() enable = self.enabled()
@ -160,7 +163,6 @@ class ParamsTab(ScrolledPanel):
if self.enabled() != new_value: if self.enabled() != new_value:
self.set_toggle_state(not value) self.set_toggle_state(not value)
self.toggle_checkbox.changed = True
self.update_toggle_state(notify_pair=False) self.update_toggle_state(notify_pair=False)
def dependent_enable(self, enable): def dependent_enable(self, enable):
@ -169,11 +171,12 @@ class ParamsTab(ScrolledPanel):
else: else:
self.set_toggle_state(False) self.set_toggle_state(False)
self.toggle_checkbox.Disable() self.toggle_checkbox.Disable()
self.toggle_checkbox.changed = True
self.update_toggle_state() self.update_toggle_state()
def set_toggle_state(self, value): def set_toggle_state(self, value):
self.toggle_checkbox.SetValue(value) if self.toggle_checkbox:
self.toggle_checkbox.SetValue(value)
self.changed_inputs.add(self.toggle_checkbox)
def get_values(self): def get_values(self):
values = {} values = {}
@ -189,7 +192,7 @@ class ParamsTab(ScrolledPanel):
return values return values
for name, input in self.param_inputs.iteritems(): 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() values[name] = input.GetValue()
return values return values
@ -197,7 +200,7 @@ class ParamsTab(ScrolledPanel):
def apply(self): def apply(self):
values = self.get_values() values = self.get_values()
for node in self.nodes: 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(): for name, value in values.iteritems():
node.set_param(name, value) node.set_param(name, value)
@ -282,7 +285,10 @@ class ParamsTab(ScrolledPanel):
box.Add(self.toggle_checkbox, proportion=0, flag=wx.BOTTOM, border=10) box.Add(self.toggle_checkbox, proportion=0, flag=wx.BOTTOM, border=10)
for param in self.params: 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': if param.type == 'boolean':
@ -456,7 +462,7 @@ class SettingsFrame(wx.Frame):
# way to drop the cache in the @cache decorators used # way to drop the cache in the @cache decorators used
# for many params in embroider.py. # for many params in embroider.py.
patches.extend(copy(node).to_patches(None)) patches.extend(copy(node).embroider(None))
except SystemExit: except SystemExit:
raise raise
except: except:
@ -670,10 +676,13 @@ class EmbroiderParams(inkex.Effect):
return values return values
def group_params(self, params): def group_params(self, params):
def by_group_and_sort_index(param):
return param.group, param.sort_index
def by_group(param): def by_group(param):
return param.group 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): def create_tabs(self, parent):
tabs = [] tabs = []

Wyświetl plik

@ -149,6 +149,7 @@ class EmbroiderySimulator(wx.Frame):
return segments return segments
def _parse_stitch_file(self, stitch_file_path): def _parse_stitch_file(self, stitch_file_path):
# "#", "comment"
# "$","1","229","229","229","(null)","(null)" # "$","1","229","229","229","(null)","(null)"
# "*","JUMP","1.595898","48.731899" # "*","JUMP","1.595898","48.731899"
# "*","STITCH","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: with open(stitch_file_path) as stitch_file:
for line in stitch_file: for line in stitch_file:
line = line.strip()
if not line:
continue
fields = line.strip().split(",") fields = line.strip().split(",")
fields = [self._strip_quotes(field) for field in fields] fields = [self._strip_quotes(field) for field in fields]
@ -180,6 +185,10 @@ class EmbroiderySimulator(wx.Frame):
x, y = fields[2:] x, y = fields[2:]
new_pos = (float(x) * PIXELS_PER_MM, float(y) * PIXELS_PER_MM) 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: if not cut:
segments.append(((pos, new_pos), pen)) segments.append(((pos, new_pos), pen))

1
embroidermodder 160000

@ -0,0 +1 @@
Subproject commit 406d261a1a2a3d046c77aaef2161e67e00672f39