kopia lustrzana https://github.com/inkstitch/inkstitch
Merge 669e5343fe
into 3afd6e500f
commit
ede80f4789
|
@ -1,2 +1,3 @@
|
|||
.*.swp
|
||||
*.pyc
|
||||
*.pyc
|
||||
embroider-debug.txt
|
18
PyEmb.py
18
PyEmb.py
|
@ -5,14 +5,14 @@ import math
|
|||
import sys
|
||||
from copy import deepcopy
|
||||
|
||||
try:
|
||||
from functools import lru_cache
|
||||
except ImportError:
|
||||
from backports.functools_lru_cache import lru_cache
|
||||
#try:
|
||||
# from functools import lru_cache
|
||||
#except ImportError:
|
||||
# from backports.functools_lru_cache import lru_cache
|
||||
|
||||
# simplify use of lru_cache decorator
|
||||
def cache(*args, **kwargs):
|
||||
return lru_cache(maxsize=None)(*args, **kwargs)
|
||||
#def cache(*args, **kwargs):
|
||||
# return lru_cache(maxsize=None)(*args, **kwargs)
|
||||
|
||||
class Point:
|
||||
|
||||
|
@ -77,7 +77,7 @@ class Point:
|
|||
|
||||
class Stitch(Point):
|
||||
|
||||
def __init__(self, x, y, color=None, jump_stitch=False):
|
||||
def __init__(self, x, y, color=None, jump_stitch='s'):
|
||||
Point.__init__(self, x, y)
|
||||
self.color = color
|
||||
self.jump_stitch = jump_stitch
|
||||
|
@ -208,8 +208,10 @@ class Embroidery:
|
|||
int(stitch.color[1:3], 16),
|
||||
int(stitch.color[3:5], 16),
|
||||
int(stitch.color[5:7], 16))
|
||||
if stitch.jump_stitch:
|
||||
if stitch.jump_stitch=='j':
|
||||
self.str += '"*","JUMP","%f","%f"\n' % (stitch.x, stitch.y)
|
||||
if stitch.jump_stitch=='t':
|
||||
self.str += '"*","TRIM","%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)
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
# inkscape-embroidery: An Inkscape plugin for designing machine embroidery patterns
|
||||
## Introduction
|
||||
**Want to design embroidery pattern files (PES, DST, etc) using free, open source software? Hate all the other options? Try this one.**
|
||||
|
||||
I received a really wonderful christmas gift for a geeky programmer hacker: an [embroidery machine](http://www.brother-usa.com/homesewing/ModelDetail.aspx?ProductID=SE400). It's pretty much a CNC thread-bot... I just had to figure out how to design programs for it. The problem is, **all free embroidery design software seems to be terrible**, especially when you add in the requirement of being able to run in Linux, my OS of choice.
|
||||
|
||||
So I wrote one.
|
||||
|
||||
Okay, not really. I'm pretty terrible at GUIs, but I found this nifty inkscape extension that was created and hacked on by a couple of other folks. It was pretty rudimentary, but it got the job done, and more importantly, it was super hackable. I hacked the hell out of it, and at this point **inkscape-embroidery is a viable entry-level machine embroidery design tool**.
|
||||
|
||||
|
||||
## Setup
|
||||
|
||||
To use this tool, you're going to need to set it up. It's an inkscape extension written as a Python file. Once you get it working, you'll need to learn how to design vectors in the way that inkscape-embroidery expects, and then you can generate your design files.
|
||||
|
||||
### Inkscape
|
||||
First, install Inkscape if you don't have it. I highly recommend the **development version**, which has a really key feature: the Objects panel. This gives you a heirarchical list of objects in your SVG file, listed in their stacking order. This is really important because the stacking order dictates the order that the shapes will be sewn in.
|
||||
|
||||
I've had success running version `0.91.0+devel+14591+61`. Installation instructions are [here](https://inkscape.org/da/release/trunk/).
|
||||
|
||||
### Python Dependencies
|
||||
Make sure you have the `shapely` python module installed. The `appdirs` python module is also useful but is not required. On Ubuntu:
|
||||
|
||||
```
|
||||
apt-get install python-shapely python-appdirs
|
||||
```
|
||||
|
||||
### Extension installation
|
||||
1. Clone the extension source: `git clone https://github.com/lexelby/inkscape-embroidery`
|
||||
2. Install it as directed [here](https://inkscape.org/da/gallery/%3Dextension/)
|
||||
|
||||
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 inkscape-embroidery 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 inkscape-embroidery 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.
|
||||
|
||||
1. Open up Inkscape and create a rectangle.
|
||||
2. Make sure it has both a stroke and a fill.
|
||||
3. Convert it to a path using **Path -> Object to Path** (because inkscape-embroidery doesn't understand rectangles, circles, and the like, and ignores them).
|
||||
4. Run **Extensions -> Embroidery -> Embroider**. Use the default settings.
|
||||
|
||||
The rectangle you made will disappear and be replaced with some stripes and zig-zags. inkscape-embroidery has hidden all of your layers and created a new one called Embroidery, in which it has palced a visual representation of the stitch plan it created. It has interpreted your shape as two instructions: Fill and Stroke. Fill is implemented using fill stitching, and Stroke is implemented by running satin stitching along the outline.
|
||||
|
||||
Select the horizontal lines using the "Edit Paths by Nodes" tool. Zoom in a bit and you'll see that the lines are actually made up of lots of points. Each point represents one stitch -- one needle penetration and interlocking of the top thread with the bobbin thread. Notice how the points all line up nicely in diagonals. This will give the fill stitching a nice, orderly visual appearance.
|
||||
|
||||
Now look at the zig-zags. These are the satin stitches. Note that the corners look pretty ugly. This is because satin stitches generated from a shape's stroke are pretty rudimentary and aren't implemented intelligently. You can exert much greater control over satin stitching using a Satin Column, described later.
|
||||
|
||||
The stitching preview you're looking at just now isn't intended to be permanent. I usually immediately undo it (ctrl-Z) after I've looked at the stitches. The actual work that inkscape-embroidery does is to output a design file.
|
||||
|
||||
### 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`.
|
||||
|
||||
inkscape-embroidery 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!
|
||||
|
||||
### Ordering
|
||||
|
||||
Copy your rectangle and paste it elsewhere on your canvas. Deselect any shapes (**Edit -> Deselect**), re-run the extension, and look at the output. You'll see that both regions have been stitched, and there will be a line connecting them. That's a jump-stitch, where the machine will move a long distance between stitching the sections.
|
||||
|
||||
If you're like me, your machine can't automatically cut the thread between stitching sections, so you'll need to minimize jump stitches as much as possible through careful planning of your stitch path. If your machine *can* do thread cuts, congratulations! But you'll need to modify inkscape-embroidery to allow you to specify a thread cut, because there's no way to do that right now.
|
||||
|
||||
However, note that inkscape-embroidery pays attention to the colors you use for objects. If you change colors from one object to the next, inkscape-embroidery will include a color-change instruction using the color you've set for the object. My machine cuts the thread and waits for me to switch to the new thread color.
|
||||
|
||||
#### Reordering
|
||||
|
||||
Use the Objects panel to view the stacking order of the objects in your SVG file. Inkscape-embroidery will stitch them in their stacking order, from lowest to highest. You can reorder them in the normal way in inkscape to affect the stitching order.
|
||||
|
||||
You can also use the Reorder extension. Hold shift and select the objects you'd like to reorder, one at a time, in the order you'd like them to end up in (lowest to highest). Run **Embroidery -> Reorder**. This extension will pull all of the selected objects out of wherever they were in the stacking order and insert them in order at the same place as the *first* object you selected. This can save you a ton of time.
|
||||
|
||||
### Seeing the stitch plan for selected objects
|
||||
|
||||
If you have one or more objects selected when you run the **Embroider** extension, only those objects will be embroidered. This can be useful to help you fine-tune just one small section of your design.
|
||||
|
||||
### Embroidery Parameters
|
||||
When you run **Embroider**, you'll have the option to specify a few parameters like stitch length, fill stitch row spacing, etc. These are used as defaults for all objects in your design. You can override these parameters and set many more using the **Embroidery -> Params** extension.
|
||||
|
||||
This extension gives you an interface to control many aspects of the stitching of each object individually. To use it, first select one or more objects. Parameters will be applied to them all as a group. If the selected objects already have parameters set, these settings will be pre-loaded into the interface.
|
||||
|
||||
Parameters are stored in your SVG file as additional attributes on the XML objects. You can view these attributes using Inkscape's built-in XML editor panel, but you shouldn't actually need to do this during normal usage. Inkscape ignores attributes that it doesn't know, so these attributes will be saved right along with your SVG file. Note that other SVG programs may *not* retain these attributes, so be careful!
|
||||
|
||||
I recommend avoiding dependence on the default settings specified in the **Embroider** extension's settings window. In fact, I bypass it entirely by binding a keystroke (ctrl+e) to "Embroider (no preferences)" in Inkscape's settings. This way, I can quickly see the stitch plan just by pressing the keystroke. I also bind a keystroke to **Params** so that I can quickly view and change settings for each object.
|
||||
|
||||
### Sidenote on extensions
|
||||
**Params** is a bit weird, in that the dialog is produced by an entirely separate program (the extension) rather than Inkscape itself. This is due to the way Inkscape structures extensions. I wish inkscape-embroidery could have deeper integration into Inkscape's user interface, but it's currently not possible. This is the price we pay for not having to write an entire vector graphics editor program :)
|
||||
|
||||
Another issue is that Inkscape has a memory leak related to extensions. The more times you run an extension, the more memory Inkscape uses and the slower it gets. I periodically save my SVG file, close Inkscape, and restart it to work around this issue. See above re: putting up with this kind of hassle so as not to have a to implement an entire vector graphics editor. Hopefully they'll fix this bug soon.
|
||||
|
||||
### AutoFill
|
||||
|
||||
AutoFill is the default method for generating fill stitching. To use it, create a closed path in Inskcape and add a fill color.
|
||||
|
||||
inkscape-embroidery will break the shape up into sections that it can embroider at once using back-and-forth rows of stitches. It then adds straight-stitching between sections until it's filled in the entire design. The staggered pattern of stitches is continued seamlessly between sections, so the end result doesn't appear to have any breaks. When moving from one section to the next, it generates running stitching along the outside edge of the shape.
|
||||
|
||||
This algorithm works great for simple shapes, convex or concave. However, it doesn't work for shapes with holes, because the stitching could get "stuck" on the edge of a hole and be unable to reach any remaining section. For this reason, AutoFill rejects regions with holes in them.
|
||||
|
||||
So what do you do if your shape does have holes? You have two choices: use manually-routed fill (described below), or break the shape up into one or more shapes without holes.
|
||||
|
||||
Here's an example of converting a region with a hole into a region without:
|
||||

|
||||
|
||||
An SVG version is available in `images/autofill_hole_example.svg` for you to test out.
|
||||
|
||||
Note the thin line drawn from the hole to the edge. In fact, this is a very thin strip missing from the shape -- thinner than the spacing between the rows of stitches. This allows the autofill system to travel into and out of the center of the shape if necessary to get from section to section.
|
||||
|
||||
Note that I've drawn the gap at exactly the same angle as the fill. When the autofill system sees that it is traveling in the same direction as the fill, **it places stitches correctly to match the fill pattern**. This means that the gap will be virtually undetectable because the travel stitches will be hidden in the fill. This may double- or triple-up one of the fill rows, but it's really hard to tell unless you look very closely.
|
||||
|
||||
#### AutoFill parameters
|
||||
|
||||
Using the **Params** extension, you can set these parameters:
|
||||
|
||||
* **angle**: The angle of the rows of stitches, in degrees. 0 is horizontal, and the angle increases in a counter-clockwise direction. Negative angles are allowed.
|
||||
* **row spacing**: distance between rows of stitches
|
||||
* **maximum stitch length**: the length of each stitch in a row. "Max" is because a shorter stitch may be used at the start or end of a row.
|
||||
* **running stitch length**: length of stitches around the outline of the fill region used when moving from section to section
|
||||
* **staggers**: stitches are staggered so that neighboring rows of stitches don't all fall in the same column (which would create a distracting valley effect). Setting this dictates how many rows apart the stitches will be before they fall in the same column position.
|
||||
|
||||
#### AutoFill Underlay
|
||||
|
||||
By default, AutoFill will cover the shape with one layer of stitches. In almost all cases, this won't look any good. The individual stitches will sink into the fabric (even if it's thin) and the fill will appear sparse. The fabric may even stick up between rows.
|
||||
|
||||
To solve this, you need underlay: an initial layer of stitches that hold up the final stitches. Underlay for fill stitch it's usually comprised of fill stitching 90 degrees offset from the final fill (called "top stitching"). The row spacing should be much wider than in the top stitching. The goal is to flatten out the fabric and give the top stitches "rails" to sit on.
|
||||
|
||||
In **Params**, you'll see an underlay tab next to the AutoFill tab. Enable it by checking the box. The default settings should be good enough for most cases: 90 degrees offset and row spacing 3x the spacing of the top stitching.
|
||||
|
||||
### Manual Fill
|
||||
|
||||
Manual Fill is the old mode from before I figured out how to implement automatic fill routing. In some cases, AutoFill may not be an option, such as when the running stitches between sections are not acceptable for your design. Usually, fill region edges are covered over by satin, but not always.
|
||||
|
||||
In manual fill, the extension will still break up the shape into sections, each of which can be embroidered in one go. Then these sections will be fill-stitched one at a time, jumping directly between sections. You'll almost certainly want to break your shape up into smaller shapes and connect then using running stitches (described below). It's a painstaking process, made moreso because you'll need to do it twice: once for the underlay and again for the top stitching.
|
||||
|
||||
The **flip** option can help you with routing your stitch path. When you enable **flip**, stitching goes from right-to-left instead of left-to-right. Using **flip** and rotating 180 additional degrees (by adding or subtracting 180 from **angle**), you can cause fill stitching for a given shape to start from any of the four possible corners.
|
||||
|
||||
|
||||
### Running Stitch
|
||||
|
||||
Running stitch can be created by setting a dashed stroke on a path. Any kind of dashes will do the job, and the stroke width is irrelevant. inkscape-embroidery will create stitches along the path using the stroke width you specify.
|
||||
|
||||
In order to avoid rounding corners, ash extra stitch will be added at the point of any sharp corners.
|
||||
|
||||
The **repeats** parameter says how many times time run down and back song the path. An odd number of repeats means that the stitches will end at the end of the path, while an even number means that stitching will return to the start of the path. The default is one repeat; that is, just traveling once from the start to the end of the path.
|
||||
|
||||
If an object consists of multiple paths, they will be stitched in order with a jump between each.
|
||||
|
||||
### Simple Satin
|
||||
|
||||
A line without dashes will result in satin stitching. The width of the satin will be dictated by the stroke width. (For historical reasons, a stroke width less than 0.5 pixels will result in running stitch instead).
|
||||
|
||||
This is "simple satin": **Embroider** will plot zig-zags to the left and right of the line from start to end, but it won't do anything special around curves and corners. Sharper curves and corners will result in sparse stitching around the outside of the curve and dense stitching around the i. T
|
||||
|
||||
This won't look good and may even poke holes in the insides of corners. I avoid using plain satin entirely; it's just kept in for backward compatibility. It'll probably work fine for straight lines.
|
||||
|
||||
### Satin Column
|
||||
|
||||
Satin Column mode gives you much greater control over how the satin is generated. You define a satin column using a shape made of two mostly-parallel lines. **Embroider** will draw zig-zags back and forth between the two lines. You can vary the thickness of the column as you like.
|
||||
|
||||
The two paths must have the same number of points. This means that each path will be made up of an equal number of Bezier curves. Each pair of points acts as a "checkpoint": **Embroider** will ensure that a "zag" ends up going from one point to the other.
|
||||
|
||||
**Embroider** considers each pair of Bezier curves, one at a time. It picks the longest if the two and determines how many zig-zags will be necessary to satisfy the **zig-zag spacing** setting. This makes it so that the outside of a curve will never have sparse stitching like with simple satin.
|
||||
|
||||
However, this does mean that the inside of a curve will have a higher stitch density than you specified. Be careful how you design sharp curves, because **stitching at too high a density may poke a hole in the fabric**!
|
||||
|
||||
To avoid this issue, transition your stitching to go around the corner at an angle, like this:
|
||||
|
||||
Some embroidery design programs solve this problem differently. They modify the satin such that some stitches on the inside corner don't go all the way to the edge, to avoid having the make penetrate the fabric too many times in the same spot. I haven't gotten around to implementing that yet. Pull requests welcome!
|
||||
|
||||
Satin Column supports these settings:
|
||||
|
||||
* **zig-zag spacing**: the peak-to-peak distance between zig-zags.
|
||||
* **pull compensation**: Satin stitches pull the fabric together, resulting in a column narrower than you draw in Inkscape. This setting expands each pair of needle penetrations outward from the center of the satin column. You'll have to determine experimentally how much compensation you need for your combination of fabric, thread, and stabilizer.
|
||||
|
||||
Satin Column also supports three kinds of underlay, of which you can use any or all simultaneously. I use the terms defined in [this excellent article](http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/) on satin column design.
|
||||
|
||||
#### Center Walk Underlay
|
||||
|
||||
This is a row of running stitch down the center of the column and back. This may be all you need for thin satin columns. You can also use it as a base for more elaborate underlay.
|
||||
|
||||
#### Contour Underlay
|
||||
|
||||
This is a row of running stitch up one side of the column and back down the other. The rows are set in from the edge of the column by an amount you specify. For small or medium width satin, this may serve well enough by itself.
|
||||
|
||||
#### Zig-Zag Underlay
|
||||
|
||||
This is essentially a lower-density satin stitch sewn to the end of the column and back to the start. Added with contour underlay, you get the "German Underlay" mentioned in the article linked above. For wide columns or challenging fabrics, you can use all three underlay types together.
|
||||
|
||||
|
||||
## Workflow
|
||||
|
||||
Here's how I use inkscape-embroidery to design embroidery patterns.
|
||||
|
||||
### Pixels Per Millimeter
|
||||
|
||||
My embroidery machine (a Brother SE400) can handle patterns up to 10cm x 10cm (about 4in x 4in). Most machine embroidery design advice articles I've read talk in terms of millimeters, so that's what I work in.
|
||||
|
||||
My machine can (theoretically) position the needle with an accuracy of a tenth of a millimeter. The Brother PES format cannot encode a position any mute precisely than this. In practice, even if a machine had finer accuracy than this, the realities of sewing on real fabric, even with the best stabilizer, mean that you can't get any more accurate than this (and you shouldn't bother trying).
|
||||
|
||||
I set the Inkscape's default document size to 1000 x 1000 pixels and set the "Pixels Per Millimeter" setting in **Embroider** to ten. This means that every "pixel" in Inkscape is worth a tenth of a millimeter. Practically speaking, there's no reason I couldn't choose to have one "pixel" equal one millimeter, because pixels don't really have much meaning in vector graphics.
|
||||
|
||||
### Step 1: Sketch design or use an image
|
||||
|
||||
First, I get an idea for what I want my finished product to look like. If I'm basing my design off an existing picture or graphic, I load it into Inkscape in its own layer. Some graphics are amenable to Inkscape's auto-tracing feature, especially if I simplify the image in GIMP first.
|
||||
|
||||
After auto-tracing, I clean up the vector shapes, using "Simplify" and deleting nodes by hand when possible. My goal is to use as few Bezier curves as reasonably possible to represent the image.
|
||||
|
||||
If I need to trace an image by hand, I usually use the freehand drawing tool. This tool creates paths with a lot of Bezier nodes, so again, I'll simplify the curves as much as possible.
|
||||
|
||||
Working with an existing SVG image can save a ton of time, so consider using Google image search with the filter set to SVG.
|
||||
|
||||
For text, choose a font carefully. It's quite hard to make satin look good when it's 1mm wide our narrower. Sans-serif fonts tend to be the easiest. For text smaller than 4mm tall, you'll have a very difficult time making lowercase letters look good, so consider block-caps. Cursive/script fonts can work well, but it's not going to be as easy as you think. I find that I spend the most time on text by far.
|
||||
|
||||
|
||||
### Step 2: Plan stitch path and color changes
|
||||
|
||||
At this point, you'll have a vector graphic representation of your image. The next thing to do is to convert your vectors into the kind that **Embroider** understands and our then in the right order.
|
||||
|
||||
When you're designing for embroidery machines that can't cut the thread mid-sew or switch colors automatically, you're going to want to optimize your stitch path to reduce or hide jump stitches and make minimal color changes. I also try to avoid stitching over jump stitches when possible, because it's a total pain to trim them by hand when you do.
|
||||
|
||||
The order of stitching also affects how the fabric pulls and pushes. Each stitch will distort the fabric, and you'll need to take this into account and compensate accordingly. Look for articles on machine embroidery distortion for more info on this.
|
||||
|
||||
### Step 3: create the embroidery vectors
|
||||
|
||||
I make heavy use of layers and groups at this point. If I've traced an image, I'll leave it as the lowest layer and set it invisible in the Layers or Objects palette. Any layer, group, or vector shape that is set invisible will be ignored by **Embroider**.
|
||||
|
||||
I keep my initial traced vectors in their own layer and use them as a reference when designing embroidery vectors. I copy and paste then as necessary into a higher layer and work with the copies.
|
||||
|
||||
I use only AutoFill and Satin Columns in my designs. I begin converting filled areas to AutoFill regions. Each time I create an AutoFill shape, I set its parameters using **Params**. Then I select it and run **Embroider**, which will cause it to show a stitch plan for just the selected object(s).
|
||||
|
||||
I examine the resulting stitch plan using the node editor tool. Each node is a single stitch; the needle will penetrate the fabric and interlock with the bobbin thread at this point. Once I'm done examining the stitch plan, I Undo the **Embroider** operation to remove the stitch plan and make my vectors visible again. Then I make any changes necessary, re-run **Embroider**, and repeat until it looks right.
|
||||
|
||||
At this point, I save my SVG file. If Inkscape is starting to become sluggish (due to the memory leak described above), I'll restart it before continuing.
|
||||
|
||||
Next, I work on Satins. Remember that a Satin Column is defined by two lines that run along the edges of the column. It's usually a good idea to run satin along the outside border of a fill region. Inkscape makes this easy. I copy and paste the shape from the traced vectors, then disable Fill and enable Stroke. I set the stroke width to my desired satin width. Finally, I use the "Stroke to Path" option to convert just the stroke into its own path.
|
||||
|
||||
At this point, it's necessary to cut the paths so that they aren't a continuous loop. The cut will also tell **Embroider** where to start stitching from. Add a point at the desired cut location by double-clicking on the path with the Node Editor tool active. Cut at this point by selecting at and pressing shift+j. Repeat for the second path.
|
||||
|
||||
Now you've got an object made of two paths. They need to be going on the same direction for Satin Column to work. You can tell what direction the path goes in by enabling direction indicators in Inkscape's preferences. To reverse one of the paths, select one of its points and choose "Reverse Path". I bind this to ctrl+r in Inkscape's preferences.
|
||||
|
|
@ -9,8 +9,10 @@
|
|||
<param name="row_spacing_mm" type="float" min="0.01" max="5.00" precision="2" _gui-text="Row spacing (mm)">0.25</param>
|
||||
<param name="max_stitch_len_mm" type="float" min="0.1" max="100.0" _gui-text="Maximum stitch length (mm)">3.0</param>
|
||||
<param name="running_stitch_len_mm" type="float" min="0.1" max="100.0" _gui-text="Running stitch length (mm)">1.5</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="collapse_len_mm" type="float" min="0.0" max="60.0" _gui-text="Maximum collapse length (mm)">0.0</param>
|
||||
<param name="trim_len_mm" type="float" min="0.0" max="300.0" _gui-text="Minimum trim 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="split_path_on_jumps" type="boolean" _gui-text="Split paths on jumps" description="">false</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>
|
||||
|
|
643
embroider.py
643
embroider.py
|
@ -40,9 +40,9 @@ import shapely.ops
|
|||
from pprint import pformat
|
||||
|
||||
import PyEmb
|
||||
from PyEmb import cache
|
||||
#from PyEmb import cache
|
||||
|
||||
dbg = open("/tmp/embroider-debug.txt", "w")
|
||||
dbg = open("./embroider-debug.txt", "w")
|
||||
PyEmb.dbg = dbg
|
||||
|
||||
SVG_PATH_TAG = inkex.addNS('path', 'svg')
|
||||
|
@ -96,7 +96,7 @@ class EmbroideryElement(object):
|
|||
|
||||
return params
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def get_param(self, param, default):
|
||||
value = self.node.get("embroider_" + param, "").strip()
|
||||
|
||||
|
@ -105,7 +105,7 @@ class EmbroideryElement(object):
|
|||
|
||||
return value
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def get_boolean_param(self, param, default=None):
|
||||
value = self.get_param(param, default)
|
||||
|
||||
|
@ -114,7 +114,7 @@ class EmbroideryElement(object):
|
|||
else:
|
||||
return value and (value.lower() in ('yes', 'y', 'true', 't', '1'))
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def get_float_param(self, param, default=None):
|
||||
try:
|
||||
value = float(self.get_param(param, default))
|
||||
|
@ -127,7 +127,7 @@ class EmbroideryElement(object):
|
|||
|
||||
return value
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def get_int_param(self, param, default=None):
|
||||
try:
|
||||
value = int(self.get_param(param, default))
|
||||
|
@ -142,7 +142,7 @@ class EmbroideryElement(object):
|
|||
def set_param(self, name, value):
|
||||
self.node.set("embroider_%s" % name, str(value))
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def get_style(self, style_name):
|
||||
style = simplestyle.parseStyle(self.node.get("style"))
|
||||
if (style_name not in style):
|
||||
|
@ -152,12 +152,12 @@ class EmbroideryElement(object):
|
|||
return None
|
||||
return value
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def has_style(self, style_name):
|
||||
style = simplestyle.parseStyle(self.node.get("style"))
|
||||
return style_name in style
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def parse_path(self):
|
||||
# A CSP is a "cubic superpath".
|
||||
#
|
||||
|
@ -237,7 +237,7 @@ class Fill(EmbroideryElement):
|
|||
|
||||
@property
|
||||
@param('angle', 'Angle of lines of stitches', unit='deg', type='float')
|
||||
@cache
|
||||
#@cache
|
||||
def angle(self):
|
||||
return math.radians(self.get_float_param('angle', 0))
|
||||
|
||||
|
@ -266,12 +266,12 @@ class Fill(EmbroideryElement):
|
|||
return self.get_int_param("staggers", 4)
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def paths(self):
|
||||
return self.flatten(self.parse_path())
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def shape(self):
|
||||
poly_ary = []
|
||||
for sub_path in self.paths:
|
||||
|
@ -300,12 +300,12 @@ class Fill(EmbroideryElement):
|
|||
# print >> sys.stderr, "polygon valid:", polygon.is_valid
|
||||
return polygon
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def east(self, angle):
|
||||
# "east" is the name of the direction that is to the right along a row
|
||||
return PyEmb.Point(1, 0).rotate(-angle)
|
||||
|
||||
@cache
|
||||
#@cache
|
||||
def north(self, angle):
|
||||
return self.east(angle).rotate(math.pi / 2)
|
||||
|
||||
|
@ -391,18 +391,10 @@ class Fill(EmbroideryElement):
|
|||
|
||||
return rows
|
||||
|
||||
def make_quadrilateral(self, segment1, segment2):
|
||||
return shgeo.Polygon((segment1[0], segment1[1], segment2[1], segment2[0], segment1[0]))
|
||||
|
||||
def is_same_run(self, segment1, segment2):
|
||||
if shgeo.LineString(segment1).distance(shgeo.LineString(segment1)) > self.row_spacing * 1.1:
|
||||
if shgeo.LineString(segment1).distance(shgeo.LineString(segment2)) > self.row_spacing * 1.001:
|
||||
return False
|
||||
|
||||
quad = self.make_quadrilateral(segment1, segment2)
|
||||
quad_area = quad.area
|
||||
intersection_area = self.shape.intersection(quad).area
|
||||
|
||||
return (intersection_area / quad_area) >= 0.9
|
||||
return True
|
||||
|
||||
def pull_runs(self, rows):
|
||||
# Given a list of rows, each containing a set of line segments,
|
||||
|
@ -499,7 +491,7 @@ class Fill(EmbroideryElement):
|
|||
|
||||
# only stitch the first point if it's a reasonable distance away from the
|
||||
# last stitch
|
||||
if last_end is None or (beg - last_end).length() > 0.5 * self.options.pixels_per_mm:
|
||||
if last_end is None or (beg - last_end).length() > 0.1 * self.options.pixels_per_mm:
|
||||
patch.add_stitch(beg)
|
||||
|
||||
first_stitch = self.adjust_stagger(beg, angle, row_spacing, max_stitch_length)
|
||||
|
@ -536,12 +528,12 @@ class AutoFill(Fill):
|
|||
return self.get_boolean_param('auto_fill', True)
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def outline(self):
|
||||
return self.shape.boundary[0]
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def outline_length(self):
|
||||
return self.outline.length
|
||||
|
||||
|
@ -561,7 +553,7 @@ class AutoFill(Fill):
|
|||
|
||||
@property
|
||||
@param('fill_underlay_angle', 'Fill angle (default: fill angle + 90 deg)', unit='deg', group='AutoFill Underlay', type='float')
|
||||
@cache
|
||||
#@cache
|
||||
def fill_underlay_angle(self):
|
||||
underlay_angle = self.get_float_param("fill_underlay_angle")
|
||||
|
||||
|
@ -572,13 +564,13 @@ class AutoFill(Fill):
|
|||
|
||||
@property
|
||||
@param('fill_underlay_row_spacing_mm', 'Row spacing (default: 3x fill row spacing)', unit='mm', group='AutoFill Underlay', type='float')
|
||||
@cache
|
||||
#@cache
|
||||
def fill_underlay_row_spacing(self):
|
||||
return self.get_float_param("fill_underlay_row_spacing_mm") or self.row_spacing * 3
|
||||
|
||||
@property
|
||||
@param('fill_underlay_max_stitch_length_mm', 'Max stitch length', unit='mm', group='AutoFill Underlay', type='float')
|
||||
@cache
|
||||
#@cache
|
||||
def fill_underlay_max_stitch_length(self):
|
||||
return self.get_float_param("fill_underlay_max_stitch_length_mm" or self.max_stitch_length)
|
||||
|
||||
|
@ -707,6 +699,399 @@ class AutoFill(Fill):
|
|||
return patches
|
||||
|
||||
|
||||
class SergFill(EmbroideryElement):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SergFill, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
@param('auto_fill', 'Manually routed fill stitching', type='toggle', inverse=True, default=True)
|
||||
def auto_fill(self):
|
||||
return self.get_boolean_param('auto_fill', True)
|
||||
|
||||
@property
|
||||
@param('angle', 'Angle of lines of stitches', unit='deg', type='float')
|
||||
#@cache
|
||||
def angle(self):
|
||||
return math.radians(self.get_float_param('angle', 0))
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return self.get_style("fill")
|
||||
|
||||
@property
|
||||
@param('flip', 'Flip fill (start right-to-left)', type='boolean')
|
||||
def flip(self):
|
||||
return self.get_boolean_param("flip", False)
|
||||
|
||||
@property
|
||||
@param('row_spacing_mm', 'Spacing between rows', unit='mm', type='float')
|
||||
def row_spacing(self):
|
||||
return self.get_float_param("row_spacing_mm")
|
||||
|
||||
@property
|
||||
@param('max_stitch_length_mm', 'Maximum fill stitch length', unit='mm', type='float')
|
||||
def max_stitch_length(self):
|
||||
return self.get_float_param("max_stitch_length_mm")
|
||||
|
||||
@property
|
||||
@param('staggers', 'Stagger rows this many times before repeating', type='int')
|
||||
def staggers(self):
|
||||
return self.get_int_param("staggers", 4)
|
||||
|
||||
@property
|
||||
#@cache
|
||||
def paths(self):
|
||||
return self.flatten(self.parse_path())
|
||||
|
||||
@property
|
||||
#@cache
|
||||
def shape(self):
|
||||
poly_ary = []
|
||||
for sub_path in self.paths:
|
||||
point_ary = []
|
||||
last_pt = None
|
||||
for pt in sub_path:
|
||||
if (last_pt is not None):
|
||||
vp = (pt[0] - last_pt[0], pt[1] - last_pt[1])
|
||||
dp = math.sqrt(math.pow(vp[0], 2.0) + math.pow(vp[1], 2.0))
|
||||
# dbg.write("dp %s\n" % dp)
|
||||
if (dp > 0.01):
|
||||
# I think too-close points confuse shapely.
|
||||
point_ary.append(pt)
|
||||
last_pt = pt
|
||||
else:
|
||||
last_pt = pt
|
||||
poly_ary.append(point_ary)
|
||||
|
||||
# shapely's idea of "holes" are to subtract everything in the second set
|
||||
# from the first. So let's at least make sure the "first" thing is the
|
||||
# biggest path.
|
||||
# TODO: actually figure out which things are holes and which are shells
|
||||
poly_ary.sort(key=lambda point_list: shgeo.Polygon(point_list).area, reverse=True)
|
||||
|
||||
polygon = shgeo.MultiPolygon([(poly_ary[0], poly_ary[1:])])
|
||||
# print >> sys.stderr, "polygon valid:", polygon.is_valid
|
||||
return polygon
|
||||
|
||||
#@cache
|
||||
def east(self, angle):
|
||||
# "east" is the name of the direction that is to the right along a row
|
||||
return PyEmb.Point(1, 0).rotate(-angle)
|
||||
|
||||
#@cache
|
||||
def north(self, angle):
|
||||
return self.east(angle).rotate(math.pi / 2)
|
||||
|
||||
def adjust_stagger(self, stitch, angle, row_spacing, max_stitch_length):
|
||||
row_num = round((stitch * self.north(angle)) / row_spacing)
|
||||
row_stagger = row_num % self.staggers
|
||||
stagger_offset = (float(row_stagger) / self.staggers) * max_stitch_length
|
||||
offset = ((stitch * self.east(angle)) - stagger_offset) % max_stitch_length
|
||||
|
||||
return stitch - offset * self.east(angle)
|
||||
|
||||
def intersect_region_with_grating(self, angle=None, row_spacing=None):
|
||||
if angle is None:
|
||||
angle = self.angle
|
||||
|
||||
if row_spacing is None:
|
||||
row_spacing = self.row_spacing
|
||||
|
||||
# the max line length I'll need to intersect the whole shape is the diagonal
|
||||
(minx, miny, maxx, maxy) = self.shape.bounds
|
||||
upper_left = PyEmb.Point(minx, miny)
|
||||
lower_right = PyEmb.Point(maxx, maxy)
|
||||
length = (upper_left - lower_right).length()
|
||||
half_length = length / 2.0
|
||||
|
||||
# Now get a unit vector rotated to the requested angle. I use -angle
|
||||
# because shapely rotates clockwise, but my geometry textbooks taught
|
||||
# me to consider angles as counter-clockwise from the X axis.
|
||||
direction = PyEmb.Point(1, 0).rotate(-angle)
|
||||
|
||||
# and get a normal vector
|
||||
normal = direction.rotate(math.pi / 2)
|
||||
|
||||
# I'll start from the center, move in the normal direction some amount,
|
||||
# and then walk left and right half_length in each direction to create
|
||||
# a line segment in the grating.
|
||||
center = PyEmb.Point((minx + maxx) / 2.0, (miny + maxy) / 2.0)
|
||||
|
||||
# I need to figure out how far I need to go along the normal to get to
|
||||
# the edge of the shape. To do that, I'll rotate the bounding box
|
||||
# angle degrees clockwise and ask for the new bounding box. The max
|
||||
# and min y tell me how far to go.
|
||||
|
||||
_, start, _, end = affinity.rotate(self.shape, angle, origin='center', use_radians=True).bounds
|
||||
|
||||
# convert start and end to be relative to center (simplifies things later)
|
||||
start -= center.y
|
||||
end -= center.y
|
||||
|
||||
# offset start slightly so that rows are always an even multiple of
|
||||
# row_spacing_px from the origin. This makes it so that abutting
|
||||
# fill regions at the same angle and spacing always line up nicely.
|
||||
start -= (start + normal * center) % row_spacing
|
||||
|
||||
rows = []
|
||||
|
||||
while start < end:
|
||||
p0 = center + normal * start + direction * half_length
|
||||
p1 = center + normal * start - direction * half_length
|
||||
endpoints = [p0.as_tuple(), p1.as_tuple()]
|
||||
grating_line = shgeo.LineString(endpoints)
|
||||
|
||||
res = grating_line.intersection(self.shape)
|
||||
|
||||
if (isinstance(res, shgeo.MultiLineString)):
|
||||
runs = map(lambda line_string: line_string.coords, res.geoms)
|
||||
else:
|
||||
if res.is_empty or len(res.coords) == 1:
|
||||
# ignore if we intersected at a single point or no points
|
||||
start += row_spacing
|
||||
continue
|
||||
runs = [res.coords]
|
||||
|
||||
runs.sort(key=lambda seg: (PyEmb.Point(*seg[0]) - upper_left).length())
|
||||
|
||||
if self.flip:
|
||||
runs.reverse()
|
||||
runs = map(lambda run: tuple(reversed(run)), runs)
|
||||
|
||||
rows.append(runs)
|
||||
|
||||
start += row_spacing
|
||||
|
||||
return rows
|
||||
|
||||
def inter_distance(self, segment1, segment2):
|
||||
return shgeo.LineString(segment1).distance(shgeo.LineString(segment2))
|
||||
|
||||
def is_same_run(self, segment1, segment2):
|
||||
#if we have intersection due to hole with sharp corner and an angle fill
|
||||
dist=shgeo.Point(segment1[0]).distance(shgeo.Point(segment1[1]))
|
||||
dist1=shgeo.Point(segment1[0]).distance(shgeo.LineString(segment2))
|
||||
dist2=shgeo.Point(segment1[1]).distance(shgeo.LineString(segment2))
|
||||
if (dist1 > dist-self.row_spacing or dist2 > dist-self.row_spacing) and dist > self.row_spacing * 3:
|
||||
return False
|
||||
|
||||
if self.inter_distance(segment1, segment2) <= self.row_spacing * 1.001:
|
||||
return True
|
||||
return False
|
||||
|
||||
def adjacent_count(self, row, segment):
|
||||
count = 0
|
||||
if len(row):
|
||||
for onesegment in row:
|
||||
if self.is_same_run(onesegment,segment):
|
||||
count = count + 1
|
||||
return count
|
||||
|
||||
def find_adjacent_run(self, runs, prevrow, segment):
|
||||
for onesegment in prevrow:
|
||||
if self.is_same_run(onesegment,segment):
|
||||
for run in runs:
|
||||
if run[-1] == onesegment:
|
||||
return runs.index(run)
|
||||
return -1 #something went wrong, we must crash here or smth else
|
||||
|
||||
def neighbours_count(self, runs, run):
|
||||
top = 0
|
||||
bottom = 0
|
||||
for onerun in runs:
|
||||
if (onerun == run):
|
||||
continue
|
||||
if self.is_same_run(run[0],onerun[-1]):
|
||||
top = top + 1
|
||||
if self.is_same_run(run[-1],onerun[0]):
|
||||
bottom = bottom + 1
|
||||
return top,bottom
|
||||
|
||||
def pull_runs(self, rows):
|
||||
#remake: splitting into runs
|
||||
#each run should not be enclosed by any other one
|
||||
#all runs can figure out their neighbours up and down
|
||||
#thus we can determine right sequence for jump stitches
|
||||
#which connect runs to avoid going through already filled area
|
||||
#Sequence detection algorithm:
|
||||
#run can be filled starting from side where it has no neighbours (top or bottom)
|
||||
#after being filled the run is eliminated from list
|
||||
#Shape detection algorithm
|
||||
#scan each segment of current row agains all previous row segments and
|
||||
#each segment of previous row against all segments of current row
|
||||
#if adjacent_count == 1: append this segment to shape
|
||||
#if adjacent_count == 0: start new run
|
||||
#if adjacent_count >1: start new run (obstacle detected)
|
||||
# (aka close corresponding shape from bottom)
|
||||
#--------------------
|
||||
#-------------------- segment from prevrow intersect couple of segments from
|
||||
#------/^^^^^\------- next row. Obstacle begin detected. We split block here
|
||||
#------| |-------
|
||||
#------| |-------
|
||||
#-------\ /--------
|
||||
#--------\_/--------- segment from next row intersect couple of segments from
|
||||
#-------------------- prev row. obstacle end detected. We split block here too.
|
||||
#Jump stitch will go though the middle of common segments betwenn adjacent blocks
|
||||
fill_logic = "serg_reordered"
|
||||
runs = []
|
||||
prevrow = []
|
||||
for row in rows:
|
||||
for segment in row:
|
||||
#print >>sys.stderr, "P", str(prevrow)
|
||||
#print >>sys.stderr, "R", str(row)
|
||||
obstacleedge = True
|
||||
if self.adjacent_count(prevrow,segment) == 1:
|
||||
rindex = self.find_adjacent_run(runs,prevrow,segment)
|
||||
if self.adjacent_count(row,runs[rindex][-1]) == 1:
|
||||
runs[rindex].append(segment)
|
||||
obstacleedge = False
|
||||
if(obstacleedge):
|
||||
run = []
|
||||
run.append(segment)
|
||||
runs.append(run)
|
||||
|
||||
prevrow = row
|
||||
|
||||
#let's try to reorder runs for no visible jumpstitches on the surface
|
||||
if (fill_logic == "serg_reordered"):
|
||||
orderedruns = []
|
||||
direction = 'down'
|
||||
isadjacent = False
|
||||
while (len(runs)>1):
|
||||
mincnt = len(runs)
|
||||
minrun = runs[0]
|
||||
mindist = 1000;
|
||||
mintop = 100
|
||||
for run in runs:
|
||||
top,bottom = self.neighbours_count(runs,run)
|
||||
isadjacent = False
|
||||
if top == 0 or bottom == 0:
|
||||
dist = 0
|
||||
if len(orderedruns) >0:
|
||||
dist = self.inter_distance(orderedruns[-1][-1],run[0])
|
||||
dist1 = self.inter_distance(orderedruns[-1][-1],run[-1])
|
||||
if dist1 < dist:
|
||||
dist = dist1
|
||||
#print >> sys.stderr, "dist:", dist, "dist1:", dist1
|
||||
if direction == 'down':
|
||||
isadjacent = isadjacent or self.is_same_run(orderedruns[-1][-1],run[0])
|
||||
else:
|
||||
isadjacent = isadjacent or self.is_same_run(orderedruns[-1][-1],run[-1])
|
||||
|
||||
if ((top+bottom) < mincnt or
|
||||
(top+bottom) == mincnt and (mindist > dist)):
|
||||
minrun = run
|
||||
mincnt = top+bottom
|
||||
mintop = top
|
||||
mindist = dist
|
||||
if isadjacent:
|
||||
mindist = -1
|
||||
runs.remove(minrun)
|
||||
#if (mintop != 0 or (isadjacent and direction == 'up')): #if needs to fill from bottom
|
||||
#print >> sys.stderr, "mintop:", mintop
|
||||
if (mintop != 0): #if needs to fill from bottom
|
||||
minrun.reverse()
|
||||
direction = 'up'
|
||||
else:
|
||||
direction = 'down'
|
||||
orderedruns.append(minrun)
|
||||
if (direction == 'up'): #if prelast was filled from bottom last will bee the same
|
||||
runs[0].reverse()
|
||||
orderedruns.append(runs[0])
|
||||
|
||||
return orderedruns
|
||||
|
||||
else:
|
||||
return runs
|
||||
|
||||
def section_to_patch(self, group_of_segments, angle=None, row_spacing=None, max_stitch_length=None):
|
||||
if max_stitch_length is None:
|
||||
max_stitch_length = self.max_stitch_length
|
||||
|
||||
if row_spacing is None:
|
||||
row_spacing = self.row_spacing
|
||||
|
||||
if angle is None:
|
||||
angle = self.angle
|
||||
|
||||
# print >> sys.stderr, len(groups_of_segments)
|
||||
|
||||
patch = Patch(color=self.color)
|
||||
first_segment = True
|
||||
swap = False
|
||||
last_end = None
|
||||
|
||||
for segment in group_of_segments:
|
||||
# We want our stitches to look like this:
|
||||
#
|
||||
# ---*-----------*-----------
|
||||
# ------*-----------*--------
|
||||
# ---------*-----------*-----
|
||||
# ------------*-----------*--
|
||||
# ---*-----------*-----------
|
||||
#
|
||||
# Each successive row of stitches will be staggered, with
|
||||
# num_staggers rows before the pattern repeats. A value of
|
||||
# 4 gives a nice fill while hiding the needle holes. The
|
||||
# first row is offset 0%, the second 25%, the third 50%, and
|
||||
# the fourth 75%.
|
||||
#
|
||||
# Actually, instead of just starting at an offset of 0, we
|
||||
# can calculate a row's offset relative to the origin. This
|
||||
# way if we have two abutting fill regions, they'll perfectly
|
||||
# tile with each other. That's important because we often get
|
||||
# abutting fill regions from pull_runs().
|
||||
|
||||
(beg, end) = segment
|
||||
|
||||
if (swap):
|
||||
(beg, end) = (end, beg)
|
||||
|
||||
beg = PyEmb.Point(*beg)
|
||||
end = PyEmb.Point(*end)
|
||||
|
||||
row_direction = (end - beg).unit()
|
||||
segment_length = (end - beg).length()
|
||||
|
||||
# only stitch the first point if it's a reasonable distance away from the
|
||||
# last stitch
|
||||
if last_end is None or (beg - last_end).length() > 0.1 * self.options.pixels_per_mm:
|
||||
patch.add_stitch(beg)
|
||||
|
||||
first_stitch = self.adjust_stagger(beg, angle, row_spacing, max_stitch_length)
|
||||
|
||||
# we might have chosen our first stitch just outside this row, so move back in
|
||||
if (first_stitch - beg) * row_direction < 0:
|
||||
first_stitch += row_direction * max_stitch_length
|
||||
|
||||
offset = (first_stitch - beg).length()
|
||||
|
||||
while offset < segment_length:
|
||||
patch.add_stitch(beg + offset * row_direction)
|
||||
offset += max_stitch_length
|
||||
|
||||
if (end - patch.stitches[-1]).length() > 0.1 * self.options.pixels_per_mm:
|
||||
patch.add_stitch(end)
|
||||
|
||||
last_end = end
|
||||
swap = not swap
|
||||
|
||||
return patch
|
||||
|
||||
def to_patches(self, last_patch):
|
||||
dbg.write("sergfill to_patches: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
rows_of_segments = self.intersect_region_with_grating()
|
||||
dbg.write("sergfill rows_of: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
groups_of_segments = self.pull_runs(rows_of_segments)
|
||||
dbg.write("sergfill groups_of: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
return [self.section_to_patch(group) for group in groups_of_segments]
|
||||
|
||||
|
||||
class Stroke(EmbroideryElement):
|
||||
@property
|
||||
@param('satin_column', 'Satin along paths', type='toggle', inverse=True)
|
||||
|
@ -718,7 +1103,7 @@ class Stroke(EmbroideryElement):
|
|||
return self.get_style("stroke")
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def width(self):
|
||||
stroke_width = self.get_style("stroke-width")
|
||||
|
||||
|
@ -738,7 +1123,7 @@ class Stroke(EmbroideryElement):
|
|||
|
||||
@property
|
||||
@param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float')
|
||||
@cache
|
||||
#@cache
|
||||
def zigzag_spacing(self):
|
||||
return self.get_float_param("zigzag_spacing_mm")
|
||||
|
||||
|
@ -755,6 +1140,12 @@ class Stroke(EmbroideryElement):
|
|||
# stroke width <= 0.5 pixels is deprecated in favor of dashed lines
|
||||
return self.dashed or self.width <= 0.5
|
||||
|
||||
def stroke_points_exact(self, emb_point_list, zigzag_spacing, stroke_width):
|
||||
patch = Patch(color=self.color)
|
||||
for p0 in emb_point_list:
|
||||
patch.add_stitch(p0)
|
||||
return patch
|
||||
|
||||
def stroke_points(self, emb_point_list, zigzag_spacing, stroke_width):
|
||||
patch = Patch(color=self.color)
|
||||
p0 = emb_point_list[0]
|
||||
|
@ -809,7 +1200,9 @@ class Stroke(EmbroideryElement):
|
|||
|
||||
for path in self.paths:
|
||||
path = [PyEmb.Point(x, y) for x, y in path]
|
||||
if self.is_running_stitch():
|
||||
if self.options.svg2emb == "true":
|
||||
patch = self.stroke_points_exact(path, self.running_stitch_length, stroke_width=0.0)
|
||||
elif self.is_running_stitch():
|
||||
patch = self.stroke_points(path, self.running_stitch_length, stroke_width=0.0)
|
||||
else:
|
||||
patch = self.stroke_points(path, self.zigzag_spacing / 2.0, stroke_width=self.width)
|
||||
|
@ -902,12 +1295,12 @@ class SatinColumn(EmbroideryElement):
|
|||
return self.get_float_param("zigzag_underlay_inset_mm") or self.contour_underlay_inset / 2.0
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def csp(self):
|
||||
return self.parse_path()
|
||||
|
||||
@property
|
||||
@cache
|
||||
#@cache
|
||||
def flattened_beziers(self):
|
||||
# Given a pair of paths made up of bezier segments, flatten
|
||||
# each individual bezier segment into line segments that approximate
|
||||
|
@ -1185,7 +1578,7 @@ def detect_classes(node):
|
|||
if element.get_boolean_param("auto_fill", True):
|
||||
classes.append(AutoFill)
|
||||
else:
|
||||
classes.append(Fill)
|
||||
classes.append(SergFill)
|
||||
|
||||
if element.get_style("stroke"):
|
||||
classes.append(Stroke)
|
||||
|
@ -1232,72 +1625,6 @@ class Patch:
|
|||
return Patch(self.color, self.stitches[::-1])
|
||||
|
||||
|
||||
def patches_to_stitches(patch_list, collapse_len_px=0):
|
||||
stitches = []
|
||||
|
||||
last_stitch = None
|
||||
last_color = None
|
||||
for patch in patch_list:
|
||||
jump_stitch = True
|
||||
for stitch in patch.stitches:
|
||||
if last_stitch and last_color == patch.color:
|
||||
l = (stitch - last_stitch).length()
|
||||
if l <= 0.1:
|
||||
# filter out duplicate successive stitches
|
||||
jump_stitch = False
|
||||
continue
|
||||
|
||||
if jump_stitch:
|
||||
# consider collapsing jump stitch, if it is pretty short
|
||||
if l < collapse_len_px:
|
||||
# dbg.write("... collapsed\n")
|
||||
jump_stitch = False
|
||||
|
||||
# dbg.write("stitch color %s\n" % patch.color)
|
||||
|
||||
newStitch = PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump_stitch)
|
||||
stitches.append(newStitch)
|
||||
|
||||
jump_stitch = False
|
||||
last_stitch = stitch
|
||||
last_color = patch.color
|
||||
|
||||
return stitches
|
||||
|
||||
|
||||
def stitches_to_paths(stitches):
|
||||
paths = []
|
||||
last_color = None
|
||||
last_stitch = None
|
||||
for stitch in stitches:
|
||||
if stitch.jump_stitch:
|
||||
if last_color == stitch.color:
|
||||
paths.append([None, []])
|
||||
if last_stitch is not None:
|
||||
paths[-1][1].append(['M', last_stitch.as_tuple()])
|
||||
paths[-1][1].append(['L', stitch.as_tuple()])
|
||||
last_color = None
|
||||
if stitch.color != last_color:
|
||||
paths.append([stitch.color, []])
|
||||
paths[-1][1].append(['L' if len(paths[-1][1]) > 0 else 'M', stitch.as_tuple()])
|
||||
last_color = stitch.color
|
||||
last_stitch = stitch
|
||||
return paths
|
||||
|
||||
|
||||
def emit_inkscape(parent, stitches):
|
||||
for color, path in stitches_to_paths(stitches):
|
||||
# dbg.write('path: %s %s\n' % (color, repr(path)))
|
||||
inkex.etree.SubElement(parent,
|
||||
inkex.addNS('path', 'svg'),
|
||||
{'style': simplestyle.formatStyle(
|
||||
{'stroke': color if color is not None else '#000000',
|
||||
'stroke-width': "0.4",
|
||||
'fill': 'none'}),
|
||||
'd': simplepath.formatPath(path),
|
||||
})
|
||||
|
||||
|
||||
class Embroider(inkex.Effect):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -1322,6 +1649,15 @@ class Embroider(inkex.Effect):
|
|||
action="store", type="float",
|
||||
dest="collapse_length_mm", default=0.0,
|
||||
help="max collapse length (mm)")
|
||||
self.OptionParser.add_option("-t", "--trim_len_mm",
|
||||
action="store", type="float",
|
||||
dest="trim_len_mm", default=20.0,
|
||||
help="min trim length (mm)")
|
||||
self.OptionParser.add_option("--split_path_on_jumps",
|
||||
action="store", type="choice",
|
||||
choices=["true","false"],
|
||||
dest="split_path_on_jumps", default="false",
|
||||
help="Split SVG path on every jump stitch")
|
||||
self.OptionParser.add_option("-f", "--flatness",
|
||||
action="store", type="float",
|
||||
dest="flat", default=0.1,
|
||||
|
@ -1345,9 +1681,14 @@ class Embroider(inkex.Effect):
|
|||
dest="max_backups", default=5,
|
||||
help="Max number of backups of output files to keep.")
|
||||
self.OptionParser.add_option("-p", "--pixels_per_mm",
|
||||
action="store", type="int",
|
||||
action="store", type="float",
|
||||
dest="pixels_per_mm", default=10,
|
||||
help="Number of on-screen pixels per millimeter.")
|
||||
self.OptionParser.add_option("--svg2emb",
|
||||
action="store", type="choice",
|
||||
choices=["true","false"],
|
||||
dest="svg2emb", default="false",
|
||||
help="Just generate embroidmodder2 CSV from existing paths.")
|
||||
self.patches = []
|
||||
|
||||
def handle_node(self, node):
|
||||
|
@ -1424,6 +1765,8 @@ class Embroider(inkex.Effect):
|
|||
|
||||
if self.options.hide_layers:
|
||||
self.hide_layers()
|
||||
dbg.write("finished hide layers: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
patches = []
|
||||
for element in self.elements:
|
||||
|
@ -1433,19 +1776,99 @@ class Embroider(inkex.Effect):
|
|||
last_patch = None
|
||||
|
||||
patches.extend(element.to_patches(last_patch))
|
||||
dbg.write("finished patches: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
stitches = patches_to_stitches(patches, self.options.collapse_length_mm * self.options.pixels_per_mm)
|
||||
stitches = self.patches_to_stitches(patches, self.options.collapse_length_mm * self.options.pixels_per_mm
|
||||
, self.options.trim_len_mm * self.options.pixels_per_mm)
|
||||
dbg.write("finished stitches: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
emb = PyEmb.Embroidery(stitches, self.options.pixels_per_mm)
|
||||
dbg.write("finished emb: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
emb.export(self.get_output_path(), self.options.output_format)
|
||||
dbg.write("finished export: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {})
|
||||
new_layer.set('id', self.uniqueId("embroidery"))
|
||||
new_layer.set(inkex.addNS('label', 'inkscape'), 'Embroidery')
|
||||
new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
|
||||
emit_inkscape(new_layer, stitches)
|
||||
if self.options.svg2emb != "true":
|
||||
new_layer = inkex.etree.SubElement(self.document.getroot(), SVG_GROUP_TAG, {})
|
||||
new_layer.set('id', self.uniqueId("embroidery"))
|
||||
new_layer.set(inkex.addNS('label', 'inkscape'), 'Embroidery')
|
||||
new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
self.emit_inkscape(new_layer, stitches)
|
||||
|
||||
sys.stdout = old_stdout
|
||||
dbg.write("finished output: %s\n" % time.time())
|
||||
dbg.flush()
|
||||
|
||||
def patches_to_stitches(self, patch_list, collapse_len_px=0, trim_len_px=0):
|
||||
stitches = []
|
||||
|
||||
last_stitch = None
|
||||
last_color = None
|
||||
for patch in patch_list:
|
||||
jump_stitch = 'j'
|
||||
for stitch in patch.stitches:
|
||||
if last_stitch and last_color == patch.color:
|
||||
l = (stitch - last_stitch).length()
|
||||
if l <= 0.1:
|
||||
# filter out duplicate successive stitches
|
||||
jump_stitch = 's'
|
||||
continue
|
||||
|
||||
if jump_stitch == 'j':
|
||||
# consider collapsing jump stitch, if it is pretty short
|
||||
if l < collapse_len_px or collapse_len_px == 0:
|
||||
#dbg.write("... collapsed\n")
|
||||
jump_stitch = 's'
|
||||
if l >= trim_len_px and trim_len_px > 0:
|
||||
jump_stitch = 't'
|
||||
|
||||
# dbg.write("stitch color %s\n" % patch.color)
|
||||
|
||||
new_stitch = PyEmb.Stitch(stitch.x, stitch.y, patch.color, jump_stitch)
|
||||
stitches.append(new_stitch)
|
||||
|
||||
jump_stitch = 's'
|
||||
last_stitch = stitch
|
||||
last_color = patch.color
|
||||
|
||||
return stitches
|
||||
|
||||
|
||||
def stitches_to_paths(self, stitches):
|
||||
paths = []
|
||||
last_color = None
|
||||
last_stitch = None
|
||||
for stitch in stitches:
|
||||
if stitch.jump_stitch=='t' or (stitch.jump_stitch=='j' and self.options.split_path_on_jumps=='true'):
|
||||
if last_color == stitch.color:
|
||||
paths.append([None, []])
|
||||
if last_stitch is not None:
|
||||
paths[-1][1].append(['M', last_stitch.as_tuple()])
|
||||
paths[-1][1].append(['L', stitch.as_tuple()])
|
||||
last_color = None
|
||||
if stitch.color != last_color:
|
||||
paths.append([stitch.color, []])
|
||||
paths[-1][1].append(['L' if len(paths[-1][1]) > 0 else 'M', stitch.as_tuple()])
|
||||
last_color = stitch.color
|
||||
last_stitch = stitch
|
||||
return paths
|
||||
|
||||
|
||||
def emit_inkscape(self, parent, stitches):
|
||||
for color, path in self.stitches_to_paths(stitches):
|
||||
# dbg.write('path: %s %s\n' % (color, repr(path)))
|
||||
inkex.etree.SubElement(parent,
|
||||
inkex.addNS('path', 'svg'),
|
||||
{'style': simplestyle.formatStyle(
|
||||
{'stroke': color if color is not None else '#000000',
|
||||
'stroke-width': "0.4",
|
||||
'fill': 'none'}),
|
||||
'd': simplepath.formatPath(path),
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.setrecursionlimit(100000)
|
||||
|
|
|
@ -10,7 +10,7 @@ import wx
|
|||
from wx.lib.scrolledpanel import ScrolledPanel
|
||||
from collections import defaultdict
|
||||
import inkex
|
||||
from embroider import Param, EmbroideryElement, Fill, AutoFill, Stroke, SatinColumn, descendants
|
||||
from embroider import Param, EmbroideryElement, Fill, AutoFill,SergFill, Stroke, SatinColumn, descendants
|
||||
from functools import partial
|
||||
from itertools import groupby
|
||||
|
||||
|
@ -430,7 +430,7 @@ class SettingsFrame(wx.Frame):
|
|||
|
||||
def __set_properties(self):
|
||||
# begin wxGlade: MyFrame.__set_properties
|
||||
self.SetTitle("frame_1")
|
||||
self.SetTitle("Embroidery Params")
|
||||
self.notebook.SetMinSize((800, 400))
|
||||
self.preset_chooser.SetSelection(-1)
|
||||
# end wxGlade
|
||||
|
@ -477,7 +477,8 @@ class EmbroiderParams(inkex.Effect):
|
|||
if element.get_style("fill"):
|
||||
classes.append(AutoFill)
|
||||
classes.append(Fill)
|
||||
elif element.get_style("stroke"):
|
||||
classes.append(SergFill)
|
||||
if element.get_style("stroke"):
|
||||
classes.append(Stroke)
|
||||
|
||||
if element.get_style("stroke-dasharray") is None:
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="1000"
|
||||
height="1000"
|
||||
viewBox="0 0 999.99999 999.99999"
|
||||
id="svg8375"
|
||||
version="1.1"
|
||||
inkscape:version="0.91+devel r"
|
||||
sodipodi:docname="fill_hole_test.svg">
|
||||
<sodipodi:namedview
|
||||
showguides="false"
|
||||
inkscape:snap-global="false"
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="468.45776"
|
||||
inkscape:cy="536.24327"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="svg8375"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="705"
|
||||
inkscape:window-x="-4"
|
||||
inkscape:window-y="-4"
|
||||
inkscape:window-maximized="1" />
|
||||
<defs
|
||||
id="defs8377" />
|
||||
<metadata
|
||||
id="metadata8380">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-52.362271)"
|
||||
id="layer1"
|
||||
inkscape:groupmode="layer"
|
||||
inkscape:label="Layer 1">
|
||||
<path
|
||||
embroider_flip="false"
|
||||
embroider_angle="0"
|
||||
sodipodi:nodetypes="ccccccscccccccsccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
d="m 414.90565,693.75697 c -6.5866,20.30142 15.21664,49.37053 44.57587,52.0303 45.44418,8.51926 23.68134,10.31142 70.19909,15.5613 27.00355,7.96974 63.40857,-0.89056 70.47442,-31.65165 19.06371,-19.4564 6.3559,-23.21007 -15.77354,-50.43359 -25.27364,-19.92334 4.88868,39.08324 -25.52808,35.42564 -12.65191,-0.24114 -48.93148,7.24395 -61.76545,7.50181 -8.17255,0.1642 -14.77902,-58.59734 -37.38799,-55.83935 -22.60896,2.75798 -34.41447,-3.39812 -44.61575,27.25253 l -140.24492,-0.008 c 3.63842,-35.17072 14.75399,-69.76994 14.85157,-87.53594 l 3.12805,-32.91829 c 4.41043,-22.01287 21.67084,-9.66993 42.99708,-7.86081 30.64108,-4.8033 61.35018,-6.93231 92.35657,-5.95846 23.17821,0.32264 46.99947,-1.47767 70.41305,-0.50455 16.9103,0.70283 33.60794,2.85238 49.69706,8.29335 34.7607,13.40607 72.10758,1.7815 107.61777,9.46979 21.71457,8.56118 49.58446,13.3113 63.84245,33.12969 9.64206,38.43081 -23.11024,71.4676 -23.23456,109.20592 3.79524,28.37642 -2.66541,56.15236 -6.20726,84.14589 6.91104,29.48759 -11.37684,58.7607 -44.77434,50.89406 -82.68536,-13.30843 -149.52668,-65.52565 -233.27987,-62.68255 -33.26856,3.94593 -78.41763,30.92651 -106.60905,16.90432 -36.17407,-4.34864 -36.41922,-41.07773 -29.93402,-68.73791 -2.75654,-15.37135 -2.60739,-30.5701 -1.04307,-45.6915 z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="path4418-8"
|
||||
embroider_auto_fill="True"
|
||||
embroider_row_spacing_mm="0.25"
|
||||
embroider_running_stitch_length_mm="1.2"
|
||||
embroider_staggers="4"
|
||||
embroider_max_stitch_length_mm="2.5"
|
||||
embroider_fill_underlay_max_stitch_length_mm=""
|
||||
embroider_fill_underlay_angle=""
|
||||
embroider_fill_underlay_row_spacing_mm=""
|
||||
embroider_fill_underlay="True" />
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 3.6 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 7.2 KiB |
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<_name>svg2emb</_name>
|
||||
<id>serg.svg2emb</id>
|
||||
<dependency type="executable" location="extensions">embroider.py</dependency>
|
||||
<dependency type="executable" location="extensions">inkex.py</dependency>
|
||||
<param name="svg2emb" type="boolean" _gui-text="Save to CSV" gui-hidden="true">true</param>
|
||||
<param name="pixels_per_mm" type="float" min="1" max="100" precision="2" _gui-text="Pixels per millimeter">10</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="trim_len_mm" type="float" min="0.0" max="300.0" _gui-text="Minimum trim length (mm)">20.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="split_path_on_jumps" type="boolean" _gui-text="Split SVG path on jumpStitch" description="If false SVG path generated as one continuous path. Splitting on trimStitches only. If true every jump or trim stitch breaks the path.">false</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>
|
||||
</param>
|
||||
<param name="path" type="string" _gui-text="Directory"></param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu _name="Embroidery"/>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command reldir="extensions" interpreter="python">embroider.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
Ładowanie…
Reference in New Issue